mirror of https://github.com/dsoprea/go-exif.git
ifd_builder: Added tests for ancillary builder logic.
parent
04631a36c3
commit
cabbc2fca1
|
@ -3,7 +3,6 @@ package exif
|
|||
import (
|
||||
"errors"
|
||||
"fmt"
|
||||
"bytes"
|
||||
"strings"
|
||||
|
||||
"encoding/binary"
|
||||
|
|
|
@ -79,9 +79,9 @@ func (bw ByteWriter) WriteFourBytes(value []byte) (err error) {
|
|||
}
|
||||
|
||||
|
||||
// ifdOffsetIterator keeps track of where the next IFD should be written
|
||||
// (relative to the end of the EXIF header bytes; all addresses are relative to
|
||||
// this).
|
||||
// ifdOffsetIterator keeps track of where the next IFD should be written by
|
||||
// keeping track of where the offsets start, the data that has been added, and
|
||||
// bumping the offset *when* the data is added.
|
||||
type ifdDataAllocator struct {
|
||||
offset uint32
|
||||
b bytes.Buffer
|
||||
|
@ -103,6 +103,10 @@ func (ida *ifdDataAllocator) Allocate(value []byte) (offset uint32, err error) {
|
|||
return offset, nil
|
||||
}
|
||||
|
||||
func (ida *ifdDataAllocator) NextOffset() uint32 {
|
||||
return ida.offset
|
||||
}
|
||||
|
||||
func (ida *ifdDataAllocator) Bytes() []byte {
|
||||
return ida.b.Bytes()
|
||||
}
|
||||
|
@ -125,7 +129,7 @@ func (ibe *IfdByteEncoder) EntrySize() uint32 {
|
|||
|
||||
func (ibe *IfdByteEncoder) TableSize(entryCount int) uint32 {
|
||||
// Tag-Count + (Entry-Size * Entry-Count) + Next-IFD-Offset.
|
||||
return uint32(2) + (ibe.entryCount() * uint32(entryCount)) + uint32(4)
|
||||
return uint32(2) + (ibe.EntrySize() * uint32(entryCount)) + uint32(4)
|
||||
}
|
||||
|
||||
func (ibe *IfdByteEncoder) encodeTagToBytes(ib *IfdBuilder, bt *builderTag, bw *ByteWriter, ida *ifdDataAllocator, nextIfdOffsetToWrite uint32) (childIfdBlock []byte, err error) {
|
||||
|
@ -135,12 +139,10 @@ func (ibe *IfdByteEncoder) encodeTagToBytes(ib *IfdBuilder, bt *builderTag, bw *
|
|||
}
|
||||
}()
|
||||
|
||||
newDataOffset = currentDataOffset
|
||||
|
||||
ti := NewTagIndex()
|
||||
|
||||
// Write tag-ID.
|
||||
err := bw.WriteUint16(bt.tagId)
|
||||
err = bw.WriteUint16(bt.tagId)
|
||||
log.PanicIf(err)
|
||||
|
||||
// Write type.
|
||||
|
@ -160,8 +162,6 @@ func (ibe *IfdByteEncoder) encodeTagToBytes(ib *IfdBuilder, bt *builderTag, bw *
|
|||
|
||||
if bt.value.IsBytes() == true {
|
||||
effectiveType := it.Type
|
||||
unitCount := uint32(len(bt.value.Bytes()))
|
||||
|
||||
if it.Type == TypeUndefined {
|
||||
effectiveType = TypeByte
|
||||
}
|
||||
|
@ -169,13 +169,13 @@ func (ibe *IfdByteEncoder) encodeTagToBytes(ib *IfdBuilder, bt *builderTag, bw *
|
|||
// It's a non-unknown value.Calculate the count of values of
|
||||
// the type that we're writing and the raw bytes for the whole list.
|
||||
|
||||
typeSize := TagTypeSize(it.Type)
|
||||
typeSize := uint32(TagTypeSize(effectiveType))
|
||||
|
||||
valueBytes := bt.value.Bytes()
|
||||
|
||||
len_ := len(valueBytes)
|
||||
unitCount := len_ / typeSize
|
||||
remainder := len_ % typeSize
|
||||
unitCount := uint32(len_) / typeSize
|
||||
remainder := uint32(len_) % typeSize
|
||||
|
||||
if remainder > 0 {
|
||||
log.Panicf("tag value of (%d) bytes not evenly divisible by type-size (%d)", len_, typeSize)
|
||||
|
@ -187,9 +187,7 @@ func (ibe *IfdByteEncoder) encodeTagToBytes(ib *IfdBuilder, bt *builderTag, bw *
|
|||
// Write four-byte value/offset.
|
||||
|
||||
if len_ > 4 {
|
||||
var err error
|
||||
|
||||
offset, err = ida.Allocate(valueBytes)
|
||||
offset, err := ida.Allocate(valueBytes)
|
||||
log.PanicIf(err)
|
||||
|
||||
err = bw.WriteUint32(offset)
|
||||
|
@ -246,14 +244,14 @@ func (ibe *IfdByteEncoder) encodeTagToBytes(ib *IfdBuilder, bt *builderTag, bw *
|
|||
// because it is not enough to know the size of the table: If there are child
|
||||
// IFDs, we will not be able to allocate them without first knowing how much
|
||||
// data we need to allocate for the current IFD.
|
||||
func (ibe *IfdByteEncoder) encodeIfdToBytes(ib *IfdBuilder, ifdAddressableOffset uint32, nextIfdOffsetToWrite uint32, setNextIb bool) (data []byte, tableSize int, dataSize int, childIfdSizes []uint32, err error) {
|
||||
func (ibe *IfdByteEncoder) encodeIfdToBytes(ib *IfdBuilder, ifdAddressableOffset uint32, nextIfdOffsetToWrite uint32, setNextIb bool) (data []byte, tableSize uint32, dataSize uint32, childIfdSizes []uint32, err error) {
|
||||
defer func() {
|
||||
if state := recover(); state != nil {
|
||||
err = log.Wrap(state.(error))
|
||||
}
|
||||
}()
|
||||
|
||||
tableSize := ibe.TableSize(len(ib.tags))
|
||||
tableSize = ibe.TableSize(len(ib.tags))
|
||||
|
||||
// ifdDataAddressableOffset is the smallest offset where we can allocate
|
||||
// data.
|
||||
|
@ -263,7 +261,7 @@ func (ibe *IfdByteEncoder) encodeIfdToBytes(ib *IfdBuilder, ifdAddressableOffset
|
|||
bw := NewByteWriter(b, ib.byteOrder)
|
||||
|
||||
// Write tag count.
|
||||
err = bw.WriteUint16(len(ib.tags))
|
||||
err = bw.WriteUint16(uint16(len(ib.tags)))
|
||||
log.PanicIf(err)
|
||||
|
||||
ida := newIfdDataAllocator(ifdDataAddressableOffset)
|
||||
|
@ -289,26 +287,27 @@ func (ibe *IfdByteEncoder) encodeIfdToBytes(ib *IfdBuilder, ifdAddressableOffset
|
|||
}
|
||||
|
||||
dataBytes := ida.Bytes()
|
||||
dataSize = uint32(len(dataBytes))
|
||||
|
||||
childIfdSizes := make([]uint32, len(childIfdBlocks))
|
||||
childIfdSizes = make([]uint32, len(childIfdBlocks))
|
||||
childIfdsTotalSize := uint32(0)
|
||||
for i, childIfdBlock := range childIfdBlocks {
|
||||
len_ := len(childIfdBlock)
|
||||
len_ := uint32(len(childIfdBlock))
|
||||
childIfdSizes[i] = len_
|
||||
childIfdsTotalSize += uint32(len_)
|
||||
childIfdsTotalSize += len_
|
||||
}
|
||||
|
||||
// Set the link from this IFD to the next IFD that will be written in the
|
||||
// next cycle.
|
||||
if setNextIb == true {
|
||||
nextIfdOffsetToWrite += tableSize + uint32(len(dataBytes)) + childIfdsTotalSize
|
||||
nextIfdOffsetToWrite += tableSize + dataSize + childIfdsTotalSize
|
||||
} else {
|
||||
nextIfdOffsetToWrite = 0
|
||||
}
|
||||
|
||||
// Write address of next IFD in chain.
|
||||
err = bw.WriteUint32(nextIfdOffsetToWrite)
|
||||
log.PancIf(err)
|
||||
log.PanicIf(err)
|
||||
|
||||
_, err = b.Write(dataBytes)
|
||||
log.PanicIf(err)
|
||||
|
@ -322,15 +321,12 @@ func (ibe *IfdByteEncoder) encodeIfdToBytes(ib *IfdBuilder, ifdAddressableOffset
|
|||
// will be interrupted by these child-IFDs (which is expected, per the
|
||||
// standard).
|
||||
|
||||
childIfdSizes := make([]uint32, len(childIfdBlocks)
|
||||
for i, childIfdBlock := range childIfdBlocks {
|
||||
for _, childIfdBlock := range childIfdBlocks {
|
||||
_, err = b.Write(childIfdBlock)
|
||||
log.PanicIf(err)
|
||||
|
||||
childIfdSizes[i] = len(childIfdBlock)
|
||||
}
|
||||
|
||||
return b.Bytes(), int(tableSize), len(dataBytes), childIfdSizes, nil
|
||||
return b.Bytes(), tableSize, dataSize, childIfdSizes, nil
|
||||
}
|
||||
|
||||
// encodeAndAttachIfd is a reentrant function that processes the IFD chain.
|
||||
|
@ -345,26 +341,27 @@ func (ibe *IfdByteEncoder) encodeAndAttachIfd(ib *IfdBuilder, ifdAddressableOffs
|
|||
b := new(bytes.Buffer)
|
||||
|
||||
nextIfdOffsetToWrite := uint32(0)
|
||||
for thisIb := ib; thisIb != nil; thisIb = thisIb.NextIfd {
|
||||
for thisIb := ib; thisIb != nil; thisIb = thisIb.nextIb {
|
||||
// Do a dry-run in order to pre-determine its size requirement.
|
||||
|
||||
rawBytes, tableSize, dataSize, childIfdSizes, err := ibe.encodeIfdToBytes(ib, ifdAddressableOffset, 0, false)
|
||||
rawBytes, _, _, _, err := ibe.encodeIfdToBytes(ib, ifdAddressableOffset, 0, false)
|
||||
log.PanicIf(err)
|
||||
|
||||
nextIfdOffsetToWrite := ifdAddressableOffset + len(rawBytes)
|
||||
nextIfdOffsetToWrite = ifdAddressableOffset + uint32(len(rawBytes))
|
||||
|
||||
// Write our IFD as well as any child-IFDs (now that we know the offset
|
||||
// where new IFDs and their data will be allocated).
|
||||
|
||||
setNextIb := thisIb.NextIb != nil
|
||||
rawBytes, tableSize, dataSize, childIfdSizes, err := ibe.encodeIfdToBytes(ib, ifdAddressableOffset, nextIfdOffsetToWrite, setNextIb)
|
||||
setNextIb := thisIb.nextIb != nil
|
||||
// TODO(dustin): !! Test the output sizes.
|
||||
rawBytes, _, _, _, err = ibe.encodeIfdToBytes(ib, ifdAddressableOffset, nextIfdOffsetToWrite, setNextIb)
|
||||
log.PanicIf(err)
|
||||
|
||||
_, err = b.Write(rawBytes)
|
||||
log.PanicIf(err)
|
||||
|
||||
// This will include the child-IFDs, as well. This will actually advance the offset for our next loop.
|
||||
ifdAddressableOffset = ifdAddressableOffset + len(rawBytes)
|
||||
ifdAddressableOffset = ifdAddressableOffset + uint32(len(rawBytes))
|
||||
}
|
||||
|
||||
return b.Bytes(), nil
|
||||
|
|
|
@ -0,0 +1,169 @@
|
|||
package exif
|
||||
|
||||
import (
|
||||
"testing"
|
||||
"bytes"
|
||||
|
||||
"encoding/binary"
|
||||
|
||||
"github.com/dsoprea/go-logging"
|
||||
)
|
||||
|
||||
func Test_ByteWriter_writeAsBytes_uint8(t *testing.T) {
|
||||
b := new(bytes.Buffer)
|
||||
bw := NewByteWriter(b, binary.BigEndian)
|
||||
|
||||
err := bw.writeAsBytes(uint8(0x12))
|
||||
log.PanicIf(err)
|
||||
|
||||
if bytes.Compare(b.Bytes(), []byte { 0x12 }) != 0 {
|
||||
t.Fatalf("uint8 not encoded correctly.")
|
||||
}
|
||||
}
|
||||
|
||||
func Test_ByteWriter_writeAsBytes_uint16(t *testing.T) {
|
||||
b := new(bytes.Buffer)
|
||||
bw := NewByteWriter(b, binary.BigEndian)
|
||||
|
||||
err := bw.writeAsBytes(uint16(0x1234))
|
||||
log.PanicIf(err)
|
||||
|
||||
if bytes.Compare(b.Bytes(), []byte { 0x12, 0x34 }) != 0 {
|
||||
t.Fatalf("uint16 not encoded correctly.")
|
||||
}
|
||||
}
|
||||
|
||||
func Test_ByteWriter_writeAsBytes_uint32(t *testing.T) {
|
||||
b := new(bytes.Buffer)
|
||||
bw := NewByteWriter(b, binary.BigEndian)
|
||||
|
||||
err := bw.writeAsBytes(uint32(0x12345678))
|
||||
log.PanicIf(err)
|
||||
|
||||
if bytes.Compare(b.Bytes(), []byte { 0x12, 0x34, 0x56, 0x78 }) != 0 {
|
||||
t.Fatalf("uint32 not encoded correctly.")
|
||||
}
|
||||
}
|
||||
|
||||
func Test_ByteWriter_WriteUint16(t *testing.T) {
|
||||
b := new(bytes.Buffer)
|
||||
bw := NewByteWriter(b, binary.BigEndian)
|
||||
|
||||
err := bw.WriteUint16(uint16(0x1234))
|
||||
log.PanicIf(err)
|
||||
|
||||
if bytes.Compare(b.Bytes(), []byte { 0x12, 0x34 }) != 0 {
|
||||
t.Fatalf("uint16 not encoded correctly (as bytes).")
|
||||
}
|
||||
}
|
||||
|
||||
func Test_ByteWriter_WriteUint32(t *testing.T) {
|
||||
b := new(bytes.Buffer)
|
||||
bw := NewByteWriter(b, binary.BigEndian)
|
||||
|
||||
err := bw.WriteUint32(uint32(0x12345678))
|
||||
log.PanicIf(err)
|
||||
|
||||
if bytes.Compare(b.Bytes(), []byte { 0x12, 0x34, 0x56, 0x78 }) != 0 {
|
||||
t.Fatalf("uint32 not encoded correctly (as bytes).")
|
||||
}
|
||||
}
|
||||
|
||||
func Test_ByteWriter_WriteFourBytes(t *testing.T) {
|
||||
b := new(bytes.Buffer)
|
||||
bw := NewByteWriter(b, binary.BigEndian)
|
||||
|
||||
err := bw.WriteFourBytes([]byte { 0x11, 0x22, 0x33, 0x44 })
|
||||
log.PanicIf(err)
|
||||
|
||||
if bytes.Compare(b.Bytes(), []byte { 0x11, 0x22, 0x33, 0x44 }) != 0 {
|
||||
t.Fatalf("four-bytes not encoded correctly.")
|
||||
}
|
||||
}
|
||||
|
||||
func Test_ByteWriter_WriteFourBytes_TooMany(t *testing.T) {
|
||||
b := new(bytes.Buffer)
|
||||
bw := NewByteWriter(b, binary.BigEndian)
|
||||
|
||||
err := bw.WriteFourBytes([]byte { 0x11, 0x22, 0x33, 0x44, 0x55 })
|
||||
if err == nil {
|
||||
t.Fatalf("expected error for not exactly four-bytes")
|
||||
} else if err.Error() != "value is not four-bytes: (5)" {
|
||||
t.Fatalf("wrong error for not exactly four bytes: %v", err)
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
func Test_IfdDataAllocator_Allocate_InitialOffset1(t *testing.T) {
|
||||
addressableOffset := uint32(0)
|
||||
ida := newIfdDataAllocator(addressableOffset)
|
||||
|
||||
if ida.NextOffset() != addressableOffset {
|
||||
t.Fatalf("initial offset not correct: (%d) != (%d)", ida.NextOffset(), addressableOffset)
|
||||
} else if len(ida.Bytes()) != 0 {
|
||||
t.Fatalf("initial buffer not empty")
|
||||
}
|
||||
|
||||
data := []byte { 0x1, 0x2, 0x3 }
|
||||
offset, err := ida.Allocate(data)
|
||||
log.PanicIf(err)
|
||||
|
||||
expected := uint32(addressableOffset + 0)
|
||||
if offset != expected {
|
||||
t.Fatalf("offset not bumped correctly (2): (%d) != (%d)", offset, expected)
|
||||
} else if ida.NextOffset() != offset + uint32(3) {
|
||||
t.Fatalf("position counter not advanced properly")
|
||||
} else if bytes.Compare(ida.Bytes(), []byte { 0x1, 0x2, 0x3 }) != 0 {
|
||||
t.Fatalf("buffer not correct after write (1)")
|
||||
}
|
||||
|
||||
data = []byte { 0x4, 0x5, 0x6 }
|
||||
offset, err = ida.Allocate(data)
|
||||
log.PanicIf(err)
|
||||
|
||||
expected = uint32(addressableOffset + 3)
|
||||
if offset != expected {
|
||||
t.Fatalf("offset not bumped correctly (3): (%d) != (%d)", offset, expected)
|
||||
} else if ida.NextOffset() != offset + uint32(3) {
|
||||
t.Fatalf("position counter not advanced properly")
|
||||
} else if bytes.Compare(ida.Bytes(), []byte { 0x1, 0x2, 0x3, 0x4, 0x5, 0x6 }) != 0 {
|
||||
t.Fatalf("buffer not correct after write (2)")
|
||||
}
|
||||
}
|
||||
|
||||
func Test_IfdDataAllocator_Allocate_InitialOffset2(t *testing.T) {
|
||||
addressableOffset := uint32(10)
|
||||
ida := newIfdDataAllocator(addressableOffset)
|
||||
|
||||
if ida.NextOffset() != addressableOffset {
|
||||
t.Fatalf("initial offset not correct: (%d) != (%d)", ida.NextOffset(), addressableOffset)
|
||||
} else if len(ida.Bytes()) != 0 {
|
||||
t.Fatalf("initial buffer not empty")
|
||||
}
|
||||
|
||||
data := []byte { 0x1, 0x2, 0x3 }
|
||||
offset, err := ida.Allocate(data)
|
||||
log.PanicIf(err)
|
||||
|
||||
expected := uint32(addressableOffset + 0)
|
||||
if offset != expected {
|
||||
t.Fatalf("offset not bumped correctly (2): (%d) != (%d)", offset, expected)
|
||||
} else if ida.NextOffset() != offset + uint32(3) {
|
||||
t.Fatalf("position counter not advanced properly")
|
||||
} else if bytes.Compare(ida.Bytes(), []byte { 0x1, 0x2, 0x3 }) != 0 {
|
||||
t.Fatalf("buffer not correct after write (1)")
|
||||
}
|
||||
|
||||
data = []byte { 0x4, 0x5, 0x6 }
|
||||
offset, err = ida.Allocate(data)
|
||||
log.PanicIf(err)
|
||||
|
||||
expected = uint32(addressableOffset + 3)
|
||||
if offset != expected {
|
||||
t.Fatalf("offset not bumped correctly (3): (%d) != (%d)", offset, expected)
|
||||
} else if ida.NextOffset() != offset + uint32(3) {
|
||||
t.Fatalf("position counter not advanced properly")
|
||||
} else if bytes.Compare(ida.Bytes(), []byte { 0x1, 0x2, 0x3, 0x4, 0x5, 0x6 }) != 0 {
|
||||
t.Fatalf("buffer not correct after write (2)")
|
||||
}
|
||||
}
|
|
@ -62,7 +62,7 @@ func TestAdd(t *testing.T) {
|
|||
t.Fatalf("IFD tag-count not correct.")
|
||||
} else if ib.existingOffset != 0 {
|
||||
t.Fatalf("IFD offset not correct.")
|
||||
} else if ib.nextIfd != nil {
|
||||
} else if ib.nextIb != nil {
|
||||
t.Fatalf("Next-IFD not correct.")
|
||||
}
|
||||
|
||||
|
@ -97,16 +97,16 @@ func TestSetNextIfd(t *testing.T) {
|
|||
ib1 := NewIfdBuilder(IfdStandard, binary.BigEndian)
|
||||
ib2 := NewIfdBuilder(IfdStandard, binary.BigEndian)
|
||||
|
||||
if ib1.nextIfd != nil {
|
||||
if ib1.nextIb != nil {
|
||||
t.Fatalf("Next-IFD for IB1 not initially terminal.")
|
||||
}
|
||||
|
||||
err := ib1.SetNextIfd(ib2)
|
||||
log.PanicIf(err)
|
||||
|
||||
if ib1.nextIfd != ib2 {
|
||||
if ib1.nextIb != ib2 {
|
||||
t.Fatalf("Next-IFD for IB1 not correct.")
|
||||
} else if ib2.nextIfd != nil {
|
||||
} else if ib2.nextIb != nil {
|
||||
t.Fatalf("Next-IFD for IB2 terminal.")
|
||||
}
|
||||
}
|
||||
|
|
|
@ -53,10 +53,10 @@ func (tt TagType) Size() int {
|
|||
return TagTypeSize(tt.Type())
|
||||
}
|
||||
|
||||
func TagTypeSize(tagType int) int {
|
||||
func TagTypeSize(tagType uint16) int {
|
||||
if tagType == TypeByte {
|
||||
return 1
|
||||
} else if tagType == TypeAscii || tt.tagType == TypeAsciiNoNul {
|
||||
} else if tagType == TypeAscii || tagType == TypeAsciiNoNul {
|
||||
return 1
|
||||
} else if tagType == TypeShort {
|
||||
return 2
|
||||
|
|
Loading…
Reference in New Issue