ifd_builder: Added tests for ancillary builder logic.

pull/3/head
Dustin Oprea 2018-04-25 04:32:58 -04:00
parent 04631a36c3
commit cabbc2fca1
5 changed files with 206 additions and 41 deletions

View File

@ -3,7 +3,6 @@ package exif
import (
"errors"
"fmt"
"bytes"
"strings"
"encoding/binary"

View File

@ -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

169
ifd_builder_encode_test.go Normal file
View File

@ -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)")
}
}

View File

@ -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.")
}
}

View File

@ -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