ifd_builder: Added tests for ancillary builder logic.

This commit is contained in:
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 ( import (
"errors" "errors"
"fmt" "fmt"
"bytes"
"strings" "strings"
"encoding/binary" "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 // ifdOffsetIterator keeps track of where the next IFD should be written by
// (relative to the end of the EXIF header bytes; all addresses are relative to // keeping track of where the offsets start, the data that has been added, and
// this). // bumping the offset *when* the data is added.
type ifdDataAllocator struct { type ifdDataAllocator struct {
offset uint32 offset uint32
b bytes.Buffer b bytes.Buffer
@ -103,6 +103,10 @@ func (ida *ifdDataAllocator) Allocate(value []byte) (offset uint32, err error) {
return offset, nil return offset, nil
} }
func (ida *ifdDataAllocator) NextOffset() uint32 {
return ida.offset
}
func (ida *ifdDataAllocator) Bytes() []byte { func (ida *ifdDataAllocator) Bytes() []byte {
return ida.b.Bytes() return ida.b.Bytes()
} }
@ -125,7 +129,7 @@ func (ibe *IfdByteEncoder) EntrySize() uint32 {
func (ibe *IfdByteEncoder) TableSize(entryCount int) uint32 { func (ibe *IfdByteEncoder) TableSize(entryCount int) uint32 {
// Tag-Count + (Entry-Size * Entry-Count) + Next-IFD-Offset. // 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) { 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() ti := NewTagIndex()
// Write tag-ID. // Write tag-ID.
err := bw.WriteUint16(bt.tagId) err = bw.WriteUint16(bt.tagId)
log.PanicIf(err) log.PanicIf(err)
// Write type. // Write type.
@ -160,8 +162,6 @@ func (ibe *IfdByteEncoder) encodeTagToBytes(ib *IfdBuilder, bt *builderTag, bw *
if bt.value.IsBytes() == true { if bt.value.IsBytes() == true {
effectiveType := it.Type effectiveType := it.Type
unitCount := uint32(len(bt.value.Bytes()))
if it.Type == TypeUndefined { if it.Type == TypeUndefined {
effectiveType = TypeByte 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 // 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. // 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() valueBytes := bt.value.Bytes()
len_ := len(valueBytes) len_ := len(valueBytes)
unitCount := len_ / typeSize unitCount := uint32(len_) / typeSize
remainder := len_ % typeSize remainder := uint32(len_) % typeSize
if remainder > 0 { if remainder > 0 {
log.Panicf("tag value of (%d) bytes not evenly divisible by type-size (%d)", len_, typeSize) 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. // Write four-byte value/offset.
if len_ > 4 { if len_ > 4 {
var err error offset, err := ida.Allocate(valueBytes)
offset, err = ida.Allocate(valueBytes)
log.PanicIf(err) log.PanicIf(err)
err = bw.WriteUint32(offset) 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 // 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 // IFDs, we will not be able to allocate them without first knowing how much
// data we need to allocate for the current IFD. // 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() { defer func() {
if state := recover(); state != nil { if state := recover(); state != nil {
err = log.Wrap(state.(error)) 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 // ifdDataAddressableOffset is the smallest offset where we can allocate
// data. // data.
@ -263,7 +261,7 @@ func (ibe *IfdByteEncoder) encodeIfdToBytes(ib *IfdBuilder, ifdAddressableOffset
bw := NewByteWriter(b, ib.byteOrder) bw := NewByteWriter(b, ib.byteOrder)
// Write tag count. // Write tag count.
err = bw.WriteUint16(len(ib.tags)) err = bw.WriteUint16(uint16(len(ib.tags)))
log.PanicIf(err) log.PanicIf(err)
ida := newIfdDataAllocator(ifdDataAddressableOffset) ida := newIfdDataAllocator(ifdDataAddressableOffset)
@ -289,26 +287,27 @@ func (ibe *IfdByteEncoder) encodeIfdToBytes(ib *IfdBuilder, ifdAddressableOffset
} }
dataBytes := ida.Bytes() dataBytes := ida.Bytes()
dataSize = uint32(len(dataBytes))
childIfdSizes := make([]uint32, len(childIfdBlocks)) childIfdSizes = make([]uint32, len(childIfdBlocks))
childIfdsTotalSize := uint32(0) childIfdsTotalSize := uint32(0)
for i, childIfdBlock := range childIfdBlocks { for i, childIfdBlock := range childIfdBlocks {
len_ := len(childIfdBlock) len_ := uint32(len(childIfdBlock))
childIfdSizes[i] = len_ childIfdSizes[i] = len_
childIfdsTotalSize += uint32(len_) childIfdsTotalSize += len_
} }
// Set the link from this IFD to the next IFD that will be written in the // Set the link from this IFD to the next IFD that will be written in the
// next cycle. // next cycle.
if setNextIb == true { if setNextIb == true {
nextIfdOffsetToWrite += tableSize + uint32(len(dataBytes)) + childIfdsTotalSize nextIfdOffsetToWrite += tableSize + dataSize + childIfdsTotalSize
} else { } else {
nextIfdOffsetToWrite = 0 nextIfdOffsetToWrite = 0
} }
// Write address of next IFD in chain. // Write address of next IFD in chain.
err = bw.WriteUint32(nextIfdOffsetToWrite) err = bw.WriteUint32(nextIfdOffsetToWrite)
log.PancIf(err) log.PanicIf(err)
_, err = b.Write(dataBytes) _, err = b.Write(dataBytes)
log.PanicIf(err) 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 // will be interrupted by these child-IFDs (which is expected, per the
// standard). // standard).
childIfdSizes := make([]uint32, len(childIfdBlocks) for _, childIfdBlock := range childIfdBlocks {
for i, childIfdBlock := range childIfdBlocks {
_, err = b.Write(childIfdBlock) _, err = b.Write(childIfdBlock)
log.PanicIf(err) 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. // 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) b := new(bytes.Buffer)
nextIfdOffsetToWrite := uint32(0) 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. // 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) 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 // Write our IFD as well as any child-IFDs (now that we know the offset
// where new IFDs and their data will be allocated). // where new IFDs and their data will be allocated).
setNextIb := thisIb.NextIb != nil setNextIb := thisIb.nextIb != nil
rawBytes, tableSize, dataSize, childIfdSizes, err := ibe.encodeIfdToBytes(ib, ifdAddressableOffset, nextIfdOffsetToWrite, setNextIb) // TODO(dustin): !! Test the output sizes.
rawBytes, _, _, _, err = ibe.encodeIfdToBytes(ib, ifdAddressableOffset, nextIfdOffsetToWrite, setNextIb)
log.PanicIf(err) log.PanicIf(err)
_, err = b.Write(rawBytes) _, err = b.Write(rawBytes)
log.PanicIf(err) log.PanicIf(err)
// This will include the child-IFDs, as well. This will actually advance the offset for our next loop. // 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 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.") t.Fatalf("IFD tag-count not correct.")
} else if ib.existingOffset != 0 { } else if ib.existingOffset != 0 {
t.Fatalf("IFD offset not correct.") t.Fatalf("IFD offset not correct.")
} else if ib.nextIfd != nil { } else if ib.nextIb != nil {
t.Fatalf("Next-IFD not correct.") t.Fatalf("Next-IFD not correct.")
} }
@ -97,16 +97,16 @@ func TestSetNextIfd(t *testing.T) {
ib1 := NewIfdBuilder(IfdStandard, binary.BigEndian) ib1 := NewIfdBuilder(IfdStandard, binary.BigEndian)
ib2 := 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.") t.Fatalf("Next-IFD for IB1 not initially terminal.")
} }
err := ib1.SetNextIfd(ib2) err := ib1.SetNextIfd(ib2)
log.PanicIf(err) log.PanicIf(err)
if ib1.nextIfd != ib2 { if ib1.nextIb != ib2 {
t.Fatalf("Next-IFD for IB1 not correct.") 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.") t.Fatalf("Next-IFD for IB2 terminal.")
} }
} }

View File

@ -53,10 +53,10 @@ func (tt TagType) Size() int {
return TagTypeSize(tt.Type()) return TagTypeSize(tt.Type())
} }
func TagTypeSize(tagType int) int { func TagTypeSize(tagType uint16) int {
if tagType == TypeByte { if tagType == TypeByte {
return 1 return 1
} else if tagType == TypeAscii || tt.tagType == TypeAsciiNoNul { } else if tagType == TypeAscii || tagType == TypeAsciiNoNul {
return 1 return 1
} else if tagType == TypeShort { } else if tagType == TypeShort {
return 2 return 2