diff --git a/ifd_builder.go b/ifd_builder.go index 64ef8b1..5d442d3 100644 --- a/ifd_builder.go +++ b/ifd_builder.go @@ -3,7 +3,6 @@ package exif import ( "errors" "fmt" - "bytes" "strings" "encoding/binary" diff --git a/ifd_builder_encode.go b/ifd_builder_encode.go index 4cb4256..c148240 100644 --- a/ifd_builder_encode.go +++ b/ifd_builder_encode.go @@ -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 diff --git a/ifd_builder_encode_test.go b/ifd_builder_encode_test.go new file mode 100644 index 0000000..81e641e --- /dev/null +++ b/ifd_builder_encode_test.go @@ -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)") + } +} diff --git a/ifd_builder_test.go b/ifd_builder_test.go index 8c11af1..25d8b77 100644 --- a/ifd_builder_test.go +++ b/ifd_builder_test.go @@ -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.") } } diff --git a/type_decode.go b/type_decode.go index 43d3a84..2b8bfd3 100644 --- a/type_decode.go +++ b/type_decode.go @@ -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