ifd_builder: Implemented NewBuilderTagFromConfig() BT factory for testing.

- Updated IfdByteEncoder tests to use it instead of hacking-together
  their own BT's (makes for more standardized, consistent testing).

- Universally refactored all core IFD knowledge implemented upon a
  single IFD name to instead work with IfdIdentity instances, instead,
  in order to validate that we only recognize the IFDs only in the
  context of the correct parents in the hierarchy.

- Implemented standard testing byte-order (assigned to
  TestDefaultByteOrder).
pull/3/head
Dustin Oprea 2018-04-27 03:42:59 -04:00
parent a23c437e5e
commit d06a3c8963
16 changed files with 1402 additions and 979 deletions

View File

@ -147,7 +147,7 @@ func (e *Exif) Visit(exifData []byte, visitor TagVisitor) (eh ExifHeader, err er
ie := NewIfdEnumerate(exifData, eh.ByteOrder)
err = ie.Scan(IfdStandard, eh.FirstIfdOffset, visitor)
err = ie.Scan(eh.FirstIfdOffset, visitor)
log.PanicIf(err)
return eh, nil

View File

@ -70,7 +70,7 @@ func TestVisit(t *testing.T) {
ti := NewTagIndex()
tags := make([]string, 0)
visitor := func(indexedIfdName string, tagId uint16, tagType TagType, valueContext ValueContext) (err error) {
visitor := func(ii IfdIdentity, ifdIndex int, tagId uint16, tagType TagType, valueContext ValueContext) (err error) {
defer func() {
if state := recover(); state != nil {
err = log.Wrap(state.(error))
@ -78,10 +78,10 @@ func TestVisit(t *testing.T) {
}
}()
it, err := ti.Get(indexedIfdName, tagId)
it, err := ti.Get(ii, tagId)
if err != nil {
if log.Is(err, ErrTagNotFound) {
fmt.Printf("Unknown tag: [%s] (%04x)\n", indexedIfdName, tagId)
fmt.Printf("Unknown tag: [%v] (%04x)\n", ii, tagId)
return nil
} else {
log.Panic(err)
@ -90,7 +90,7 @@ func TestVisit(t *testing.T) {
valueString := ""
if tagType.Type() == TypeUndefined {
value, err := UndefinedValue(indexedIfdName, tagId, valueContext, tagType.ByteOrder())
value, err := UndefinedValue(ii, tagId, valueContext, tagType.ByteOrder())
if log.Is(err, ErrUnhandledUnknownTypedTag) {
valueString = "!UNDEFINED!"
} else if err != nil {
@ -103,7 +103,7 @@ func TestVisit(t *testing.T) {
log.PanicIf(err)
}
description := fmt.Sprintf("IFD=[%s] ID=(0x%04x) NAME=[%s] COUNT=(%d) TYPE=[%s] VALUE=[%s]", indexedIfdName, tagId, it.Name, valueContext.UnitCount, tagType.Name(), valueString)
description := fmt.Sprintf("IFD=[%s] ID=(0x%04x) NAME=[%s] COUNT=(%d) TYPE=[%s] VALUE=[%s]", ii.IfdName, tagId, it.Name, valueContext.UnitCount, tagType.Name(), valueString)
tags = append(tags, description)
return nil
@ -242,34 +242,54 @@ func TestCollect(t *testing.T) {
t.Fatalf("Exif IFD child is not an IOP IFD: [%s]", rootIfd.Children[0].Children[0].Name)
}
if lookup[IfdStandard][0].Name != IfdStandard {
rootIi, _ := IfdIdOrFail("", IfdStandard)
if lookup[rootIi][0].Name != IfdStandard {
t.Fatalf("Lookup for standard IFD not correct.")
} else if lookup[IfdStandard][1].Name != IfdStandard {
} else if lookup[rootIi][1].Name != IfdStandard {
t.Fatalf("Lookup for standard IFD not correct.")
} else if lookup[IfdExif][0].Name != IfdExif {
}
exifIi, _ := IfdIdOrFail(IfdStandard, IfdExif)
if lookup[exifIi][0].Name != IfdExif {
t.Fatalf("Lookup for EXIF IFD not correct.")
} else if lookup[IfdGps][0].Name != IfdGps {
}
gpsIi, _ := IfdIdOrFail(IfdStandard, IfdGps)
if lookup[gpsIi][0].Name != IfdGps {
t.Fatalf("Lookup for EXIF IFD not correct.")
} else if lookup[IfdIop][0].Name != IfdIop {
}
iopIi, _ := IfdIdOrFail(IfdExif, IfdIop)
if lookup[iopIi][0].Name != IfdIop {
t.Fatalf("Lookup for EXIF IFD not correct.")
}
foundExif := 0
foundGps := 0
for _, ite := range lookup[IfdStandard][0].Entries {
for _, ite := range lookup[rootIi][0].Entries {
if ite.ChildIfdName == IfdExif {
foundExif++
if IfdTagNames[ite.TagId] != IfdExif {
t.Fatalf("EXIF IFD tag-ID mismatch: (0x%02x) [%s] != [%s]", ite.TagId, IfdTagNames[ite.TagId], IfdExif)
name, found := IfdTagNameWithId(IfdStandard, ite.TagId)
if found != true {
t.Fatalf("could not find tag-ID for EXIF IFD")
} else if name != IfdExif {
t.Fatalf("EXIF IFD tag-ID mismatch: (0x%02x) [%s] != [%s]", ite.TagId, name, IfdExif)
}
}
if ite.ChildIfdName == IfdGps {
foundGps++
if IfdTagNames[ite.TagId] != IfdGps {
t.Fatalf("EXIF GPS tag-ID mismatch: (0x%02x) [%s] != [%s]", ite.TagId, IfdTagNames[ite.TagId] != IfdGps)
name, found := IfdTagNameWithId(IfdStandard, ite.TagId)
if found != true {
t.Fatalf("could not find tag-ID for GPS IFD")
} else if name != IfdGps {
t.Fatalf("GPS IFD tag-ID mismatch: (0x%02x) [%s] != [%s]", ite.TagId, name, IfdGps)
}
}
}
@ -281,12 +301,15 @@ func TestCollect(t *testing.T) {
}
foundIop := 0
for _, ite := range lookup[IfdExif][0].Entries {
for _, ite := range lookup[exifIi][0].Entries {
if ite.ChildIfdName == IfdIop {
foundIop++
if IfdTagNames[ite.TagId] != IfdIop {
t.Fatalf("IOP IFD tag-ID mismatch: (0x%02x) [%s] != [%s]", ite.TagId, IfdTagNames[ite.TagId], IfdIop)
name, found := IfdTagNameWithId(IfdExif, ite.TagId)
if found != true {
t.Fatalf("could not find tag-ID for IOP IFD")
} else if name != IfdIop {
t.Fatalf("IOP IFD tag-ID mismatch: (0x%02x) [%s] != [%s]", ite.TagId, name, IfdIop)
}
}
}

View File

@ -62,8 +62,8 @@ func (ibtv IfdBuilderTagValue) Ib() *IfdBuilder {
type builderTag struct {
// ifdName is non-empty if represents a child-IFD.
ifdName string
// ii is non-empty if represents a child-IFD.
ii IfdIdentity
tagId uint16
@ -88,13 +88,35 @@ func (bt builderTag) String() string {
valuePhrase = fmt.Sprintf("%v", bt.value.Ib())
}
return fmt.Sprintf("BuilderTag<TAG-ID=(0x%02x) IFD=[%s] VALUE=[%v]>", bt.tagId, bt.ifdName, valuePhrase)
return fmt.Sprintf("BuilderTag<TAG-ID=(0x%02x) IFD=[%s] VALUE=[%v]>", bt.tagId, bt.ii, valuePhrase)
}
// NewBuilderTagFromConfig allows us to easily generate solid, consistent tags
// for testing with.
func NewBuilderTagFromConfig(ii IfdIdentity, tagId uint16, byteOrder binary.ByteOrder, value interface{}) builderTag {
ti := NewTagIndex()
it, err := ti.Get(ii, tagId)
log.PanicIf(err)
tt := NewTagType(it.Type, byteOrder)
ve := NewValueEncoder(byteOrder)
ed, err := ve.EncodeWithType(tt, value)
log.PanicIf(err)
return builderTag{
ii: ii,
tagId: tagId,
value: NewIfdBuilderTagValueFromBytes(ed.Encoded),
}
}
type IfdBuilder struct {
// ifdName is the name of the IFD that owns the current tag.
ifdName string
// ifd is the IfdIdentity instance of the IFD that owns the current tag.
ii IfdIdentity
// ifdTagId will be non-zero if we're a child IFD.
ifdTagId uint16
@ -112,12 +134,15 @@ type IfdBuilder struct {
nextIb *IfdBuilder
}
func NewIfdBuilder(ifdName string, byteOrder binary.ByteOrder) (ib *IfdBuilder) {
ib = &IfdBuilder{
ifdName: ifdName,
func NewIfdBuilder(ii IfdIdentity, byteOrder binary.ByteOrder) (ib *IfdBuilder) {
ifdTagId, _ := IfdTagIdWithIdentity(ii)
// ifdName is empty unless it's a child-IFD.
ifdTagId: IfdTagIds[ifdName],
ib = &IfdBuilder{
// ii describes the current IFD and its parent.
ii: ii,
// ifdTagId is empty unless it's a child-IFD.
ifdTagId: ifdTagId,
byteOrder: byteOrder,
tags: make([]builderTag, 0),
@ -129,13 +154,11 @@ func NewIfdBuilder(ifdName string, byteOrder binary.ByteOrder) (ib *IfdBuilder)
// NewIfdBuilderWithExistingIfd creates a new IB using the same header type
// information as the given IFD.
func NewIfdBuilderWithExistingIfd(ifd *Ifd) (ib *IfdBuilder) {
ifdTagId, found := IfdTagIds[ifd.Name]
if found == false {
log.Panicf("tag-ID for IFD not found: [%s]", ifd.Name)
}
ii := ifd.Identity()
ifdTagId := IfdTagIdWithIdentityOrFail(ii)
ib = &IfdBuilder{
ifdName: ifd.Name,
ii: ii,
ifdTagId: ifdTagId,
byteOrder: ifd.ByteOrder,
existingOffset: ifd.Offset,
@ -155,12 +178,9 @@ func NewIfdBuilderFromExistingChain(rootIfd *Ifd, exifData []byte) (rootIb *IfdB
for thisExistingIfd := rootIfd; thisExistingIfd != nil; thisExistingIfd = thisExistingIfd.NextIfd {
lastIb := newIb
ifdName := thisExistingIfd.Name
if ifdName == "" {
ifdName = IfdStandard
}
ii := thisExistingIfd.Identity()
newIb = NewIfdBuilder(ifdName, binary.BigEndian)
newIb = NewIfdBuilder(ii, binary.BigEndian)
if lastIb != nil {
lastIb.SetNextIfd(newIb)
}
@ -188,10 +208,10 @@ func NewIfdBuilderFromExistingChain(rootIfd *Ifd, exifData []byte) (rootIb *IfdB
func (ib *IfdBuilder) String() string {
nextIfdPhrase := ""
if ib.nextIb != nil {
nextIfdPhrase = ib.nextIb.ifdName
nextIfdPhrase = ib.nextIb.ii.String()
}
return fmt.Sprintf("IfdBuilder<NAME=[%s] TAG-ID=(0x%02x) BO=[%s] COUNT=(%d) OFFSET=(0x%04x) NEXT-IFD=(0x%04x)>", ib.ifdName, ib.ifdTagId, ib.byteOrder, len(ib.tags), ib.existingOffset, nextIfdPhrase)
return fmt.Sprintf("IfdBuilder<NAME=[%s] TAG-ID=(0x%02x) BO=[%s] COUNT=(%d) OFFSET=(0x%04x) NEXT-IFD=(0x%04x)>", ib.ii, ib.ifdTagId, ib.byteOrder, len(ib.tags), ib.existingOffset, nextIfdPhrase)
}
@ -230,7 +250,8 @@ func (ib *IfdBuilder) dump(levels int) {
fmt.Printf("\n")
for i, tag := range ib.tags {
_, isChildIb := IfdTagNames[tag.tagId]
// TODO(dustin): Pre-supposes that IFDs are uniquely named, which is fince since we're in charge of their actual naming (only the tag-IDs are important in the spec). However, we're referring to them by name which is inconsistent with our implementation where we take the name as non-unique and our self-imposed unique IDs as the better choice. Reimplement using these.
_, isChildIb := IfdTagNames[ib.ii.IfdName][tag.tagId]
tagName := ""
@ -238,7 +259,7 @@ func (ib *IfdBuilder) dump(levels int) {
if isChildIb == true {
tagName = "<Child IFD>"
} else {
it, err := ti.Get(tag.ifdName, tag.tagId)
it, err := ti.Get(tag.ii, tag.tagId)
if log.Is(err, ErrTagNotFound) == true {
tagName = "<UNKNOWN>"
} else if err != nil {
@ -278,19 +299,19 @@ func (ib *IfdBuilder) dumpToStrings(thisIb *IfdBuilder, prefix string, lines []s
}
for i, tag := range thisIb.tags {
line := fmt.Sprintf("<PARENTS=[%s] IFD-NAME=[%s]> IFD-TAG-ID=(0x%02x) CHILD-IFD=[%s] INDEX=(%d) TAG=[0x%02x]", prefix, thisIb.ifdName, thisIb.ifdTagId, tag.ifdName, i, tag.tagId)
line := fmt.Sprintf("<PARENTS=[%s] IFD-NAME=[%s]> IFD-TAG-ID=(0x%02x) CHILD-IFD=[%s] INDEX=(%d) TAG=[0x%02x]", prefix, thisIb.ii.IfdName, thisIb.ifdTagId, tag.ii.IfdName, i, tag.tagId)
linesOutput = append(linesOutput, line)
if tag.ifdName != "" {
if tag.ii.IfdName != "" {
if tag.value.IsIb() == false {
log.Panicf("tag has IFD tag-ID (0x%02x) but not a child IB instance: %v", tag.tagId, tag)
log.Panicf("tag has IFD tag-ID (0x%02x) and is acting like a child IB but does not *look* like a child IB: %v", tag.tagId, tag)
}
childPrefix := ""
if prefix == "" {
childPrefix = fmt.Sprintf("%s", thisIb.ifdName)
childPrefix = fmt.Sprintf("%s", thisIb.ii.IfdName)
} else {
childPrefix = fmt.Sprintf("%s->%s", prefix, thisIb.ifdName)
childPrefix = fmt.Sprintf("%s->%s", prefix, thisIb.ii.IfdName)
}
linesOutput = thisIb.dumpToStrings(tag.value.Ib(), childPrefix, linesOutput)
@ -599,8 +620,6 @@ func (ib *IfdBuilder) Find(tagId uint16) (position int, err error) {
return found[0], nil
}
// TODO(dustin): !! Switch to producing bytes immediately so that they're validated.
func (ib *IfdBuilder) Add(bt builderTag) (err error) {
defer func() {
if state := recover(); state != nil {
@ -635,14 +654,14 @@ func (ib *IfdBuilder) AddChildIb(childIb *IfdBuilder) (err error) {
// the current IFD and *not every* IFD.
for _, bt := range childIb.tags {
if bt.tagId == childIb.ifdTagId {
log.Panicf("child-IFD already added: [%s]", childIb.ifdName)
log.Panicf("child-IFD already added: %v", childIb.ii)
}
}
value := NewIfdBuilderTagValueFromIfdBuilder(childIb)
bt := builderTag{
ifdName: childIb.ifdName,
ii: childIb.ii,
tagId: childIb.ifdTagId,
value: value,
}

View File

@ -147,7 +147,7 @@ func (ibe *IfdByteEncoder) encodeTagToBytes(ib *IfdBuilder, bt *builderTag, bw *
// Write type.
it, err := ti.Get(ib.ifdName, bt.tagId)
it, err := ti.Get(ib.ii, bt.tagId)
log.PanicIf(err)
// Works for both values and child IFDs (which have an official size of

View File

@ -4,14 +4,12 @@ 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)
bw := NewByteWriter(b, TestDefaultByteOrder)
err := bw.writeAsBytes(uint8(0x12))
log.PanicIf(err)
@ -23,7 +21,7 @@ func Test_ByteWriter_writeAsBytes_uint8(t *testing.T) {
func Test_ByteWriter_writeAsBytes_uint16(t *testing.T) {
b := new(bytes.Buffer)
bw := NewByteWriter(b, binary.BigEndian)
bw := NewByteWriter(b, TestDefaultByteOrder)
err := bw.writeAsBytes(uint16(0x1234))
log.PanicIf(err)
@ -35,7 +33,7 @@ func Test_ByteWriter_writeAsBytes_uint16(t *testing.T) {
func Test_ByteWriter_writeAsBytes_uint32(t *testing.T) {
b := new(bytes.Buffer)
bw := NewByteWriter(b, binary.BigEndian)
bw := NewByteWriter(b, TestDefaultByteOrder)
err := bw.writeAsBytes(uint32(0x12345678))
log.PanicIf(err)
@ -47,7 +45,7 @@ func Test_ByteWriter_writeAsBytes_uint32(t *testing.T) {
func Test_ByteWriter_WriteUint16(t *testing.T) {
b := new(bytes.Buffer)
bw := NewByteWriter(b, binary.BigEndian)
bw := NewByteWriter(b, TestDefaultByteOrder)
err := bw.WriteUint16(uint16(0x1234))
log.PanicIf(err)
@ -59,7 +57,7 @@ func Test_ByteWriter_WriteUint16(t *testing.T) {
func Test_ByteWriter_WriteUint32(t *testing.T) {
b := new(bytes.Buffer)
bw := NewByteWriter(b, binary.BigEndian)
bw := NewByteWriter(b, TestDefaultByteOrder)
err := bw.WriteUint32(uint32(0x12345678))
log.PanicIf(err)
@ -71,7 +69,7 @@ func Test_ByteWriter_WriteUint32(t *testing.T) {
func Test_ByteWriter_WriteFourBytes(t *testing.T) {
b := new(bytes.Buffer)
bw := NewByteWriter(b, binary.BigEndian)
bw := NewByteWriter(b, TestDefaultByteOrder)
err := bw.WriteFourBytes([]byte { 0x11, 0x22, 0x33, 0x44 })
log.PanicIf(err)
@ -83,7 +81,7 @@ func Test_ByteWriter_WriteFourBytes(t *testing.T) {
func Test_ByteWriter_WriteFourBytes_TooMany(t *testing.T) {
b := new(bytes.Buffer)
bw := NewByteWriter(b, binary.BigEndian)
bw := NewByteWriter(b, TestDefaultByteOrder)
err := bw.WriteFourBytes([]byte { 0x11, 0x22, 0x33, 0x44, 0x55 })
if err == nil {
@ -182,24 +180,22 @@ func Test_IfdByteEncoder__Arithmetic(t *testing.T) {
func Test_IfdByteEncoder_encodeTagToBytes_bytes_embedded1(t *testing.T) {
ibe := NewIfdByteEncoder()
gpsIi, _ := IfdIdOrFail(IfdStandard, IfdGps)
ib := &IfdBuilder{
ifdName: IfdGps,
ii: gpsIi,
}
bt := &builderTag{
tagId: 0x0000,
value: NewIfdBuilderTagValueFromBytes([]byte { 0x12 }),
}
bt := NewBuilderTagFromConfig(GpsIi, 0x0000, TestDefaultByteOrder, []uint8 { uint8(0x12) })
b := new(bytes.Buffer)
bw := NewByteWriter(b, binary.BigEndian)
bw := NewByteWriter(b, TestDefaultByteOrder)
addressableOffset := uint32(0x1234)
ida := newIfdDataAllocator(addressableOffset)
// TODO(dustin): !! Test with and without nextIfdOffsetToWrite.
// TODO(dustin): !! Formally generate a BT properly and test here for every type. Make sure everything that we accomodate slices and properly encode (since things originally decode as slices)..
childIfdBlock, err := ibe.encodeTagToBytes(ib, bt, bw, ida, uint32(0))
childIfdBlock, err := ibe.encodeTagToBytes(ib, &bt, bw, ida, uint32(0))
log.PanicIf(err)
if childIfdBlock != nil {
@ -214,24 +210,22 @@ func Test_IfdByteEncoder_encodeTagToBytes_bytes_embedded1(t *testing.T) {
func Test_IfdByteEncoder_encodeTagToBytes_bytes_embedded2(t *testing.T) {
ibe := NewIfdByteEncoder()
gpsIi, _ := IfdIdOrFail(IfdStandard, IfdGps)
ib := &IfdBuilder{
ifdName: IfdGps,
ii: gpsIi,
}
bt := &builderTag{
tagId: 0x0000,
value: NewIfdBuilderTagValueFromBytes([]byte { 0x12, 0x34, 0x56, 0x78 }),
}
bt := NewBuilderTagFromConfig(GpsIi, 0x0000, TestDefaultByteOrder, []uint8 { uint8(0x12), uint8(0x34), uint8(0x56), uint8(0x78) })
b := new(bytes.Buffer)
bw := NewByteWriter(b, binary.BigEndian)
bw := NewByteWriter(b, TestDefaultByteOrder)
addressableOffset := uint32(0x1234)
ida := newIfdDataAllocator(addressableOffset)
// TODO(dustin): !! Test with and without nextIfdOffsetToWrite.
// TODO(dustin): !! Formally generate a BT properly and test here for every type. Make sure everything that we accomodate slices and properly encode (since things originally decode as slices)..
childIfdBlock, err := ibe.encodeTagToBytes(ib, bt, bw, ida, uint32(0))
childIfdBlock, err := ibe.encodeTagToBytes(ib, &bt, bw, ida, uint32(0))
log.PanicIf(err)
if childIfdBlock != nil {
@ -246,24 +240,22 @@ func Test_IfdByteEncoder_encodeTagToBytes_bytes_embedded2(t *testing.T) {
func Test_IfdByteEncoder_encodeTagToBytes_bytes_allocated(t *testing.T) {
ibe := NewIfdByteEncoder()
gpsIi, _ := IfdIdOrFail(IfdStandard, IfdGps)
ib := &IfdBuilder{
ifdName: IfdGps,
ii: gpsIi,
}
b := new(bytes.Buffer)
bw := NewByteWriter(b, binary.BigEndian)
bw := NewByteWriter(b, TestDefaultByteOrder)
addressableOffset := uint32(0x1234)
ida := newIfdDataAllocator(addressableOffset)
bt := &builderTag{
tagId: 0x0000,
value: NewIfdBuilderTagValueFromBytes([]byte { 0x12, 0x34, 0x56, 0x78, 0x9A }),
}
bt := NewBuilderTagFromConfig(GpsIi, 0x0000, TestDefaultByteOrder, []uint8 { uint8(0x12), uint8(0x34), uint8(0x56), uint8(0x78), uint8(0x9a) })
// TODO(dustin): !! Test with and without nextIfdOffsetToWrite.
// TODO(dustin): !! Formally generate a BT properly and test here for every type. Make sure everything that we accomodate slices and properly encode (since things originally decode as slices)..
childIfdBlock, err := ibe.encodeTagToBytes(ib, bt, bw, ida, uint32(0))
childIfdBlock, err := ibe.encodeTagToBytes(ib, &bt, bw, ida, uint32(0))
log.PanicIf(err)
if childIfdBlock != nil {
@ -278,14 +270,10 @@ func Test_IfdByteEncoder_encodeTagToBytes_bytes_allocated(t *testing.T) {
// Test that another allocation encodes to the new offset.
bt = &builderTag{
tagId: 0x0000,
value: NewIfdBuilderTagValueFromBytes([]byte { 0xbc, 0xde, 0xf0, 0x12, 0x34 }),
}
bt = NewBuilderTagFromConfig(GpsIi, 0x0000, TestDefaultByteOrder, []uint8 { uint8(0xbc), uint8(0xde), uint8(0xf0), uint8(0x12), uint8(0x34) })
// TODO(dustin): !! Test with and without nextIfdOffsetToWrite.
// TODO(dustin): !! Formally generate a BT properly and test here for every type. Make sure everything that we accomodate slices and properly encode (since things originally decode as slices)..
childIfdBlock, err = ibe.encodeTagToBytes(ib, bt, bw, ida, uint32(0))
childIfdBlock, err = ibe.encodeTagToBytes(ib, &bt, bw, ida, uint32(0))
log.PanicIf(err)
if childIfdBlock != nil {

View File

@ -6,8 +6,6 @@ import (
"bytes"
"path"
"encoding/binary"
"github.com/dsoprea/go-logging"
)
@ -16,7 +14,7 @@ import (
func TestAdd(t *testing.T) {
ib := NewIfdBuilder(IfdStandard, binary.BigEndian)
ib := NewIfdBuilder(RootIi, TestDefaultByteOrder)
bt := builderTag{
tagId: 0x11,
@ -52,11 +50,11 @@ func TestAdd(t *testing.T) {
err = ib.Add(bt)
log.PanicIf(err)
if ib.ifdName != IfdStandard {
if ib.ii != RootIi {
t.Fatalf("IFD name not correct.")
} else if ib.ifdTagId != 0 {
t.Fatalf("IFD tag-ID not correct.")
} else if ib.byteOrder != binary.BigEndian {
} else if ib.byteOrder != TestDefaultByteOrder {
t.Fatalf("IFD byte-order not correct.")
} else if len(ib.tags) != 4 {
t.Fatalf("IFD tag-count not correct.")
@ -94,8 +92,8 @@ func TestAdd(t *testing.T) {
}
func TestSetNextIfd(t *testing.T) {
ib1 := NewIfdBuilder(IfdStandard, binary.BigEndian)
ib2 := NewIfdBuilder(IfdStandard, binary.BigEndian)
ib1 := NewIfdBuilder(RootIi, TestDefaultByteOrder)
ib2 := NewIfdBuilder(RootIi, TestDefaultByteOrder)
if ib1.nextIb != nil {
t.Fatalf("Next-IFD for IB1 not initially terminal.")
@ -113,7 +111,7 @@ func TestSetNextIfd(t *testing.T) {
func TestAddChildIb(t *testing.T) {
ib := NewIfdBuilder(IfdStandard, binary.BigEndian)
ib := NewIfdBuilder(RootIi, TestDefaultByteOrder)
bt := builderTag{
tagId: 0x11,
@ -123,7 +121,9 @@ func TestAddChildIb(t *testing.T) {
err := ib.Add(bt)
log.PanicIf(err)
ibChild := NewIfdBuilder(IfdExif, binary.BigEndian)
exifIi, _ := IfdIdOrFail(IfdStandard, IfdExif)
ibChild := NewIfdBuilder(exifIi, TestDefaultByteOrder)
err = ib.AddChildIb(ibChild)
log.PanicIf(err)
@ -154,7 +154,7 @@ func TestAddTagsFromExisting(t *testing.T) {
}
}()
ib := NewIfdBuilder(IfdStandard, binary.BigEndian)
ib := NewIfdBuilder(RootIi, TestDefaultByteOrder)
entries := make([]IfdTagEntry, 3)
@ -194,7 +194,7 @@ func TestAddTagsFromExisting(t *testing.T) {
}
func TestAddTagsFromExisting__Includes(t *testing.T) {
ib := NewIfdBuilder(IfdStandard, binary.BigEndian)
ib := NewIfdBuilder(RootIi, TestDefaultByteOrder)
entries := make([]IfdTagEntry, 3)
@ -226,7 +226,7 @@ func TestAddTagsFromExisting__Includes(t *testing.T) {
}
func TestAddTagsFromExisting__Excludes(t *testing.T) {
ib := NewIfdBuilder(IfdStandard, binary.BigEndian)
ib := NewIfdBuilder(RootIi, TestDefaultByteOrder)
entries := make([]IfdTagEntry, 3)
@ -258,7 +258,7 @@ func TestAddTagsFromExisting__Excludes(t *testing.T) {
}
func TestFindN_First_1(t *testing.T) {
ib := NewIfdBuilder(IfdStandard, binary.BigEndian)
ib := NewIfdBuilder(RootIi, TestDefaultByteOrder)
bt := builderTag{
tagId: 0x11,
@ -302,7 +302,7 @@ func TestFindN_First_1(t *testing.T) {
}
func TestFindN_First_2_1Returned(t *testing.T) {
ib := NewIfdBuilder(IfdStandard, binary.BigEndian)
ib := NewIfdBuilder(RootIi, TestDefaultByteOrder)
bt := builderTag{
tagId: 0x11,
@ -346,7 +346,7 @@ func TestFindN_First_2_1Returned(t *testing.T) {
}
func TestFindN_First_2_2Returned(t *testing.T) {
ib := NewIfdBuilder(IfdStandard, binary.BigEndian)
ib := NewIfdBuilder(RootIi, TestDefaultByteOrder)
bt := builderTag{
tagId: 0x11,
@ -413,7 +413,7 @@ func TestFindN_First_2_2Returned(t *testing.T) {
}
func TestFindN_Middle_WithDuplicates(t *testing.T) {
ib := NewIfdBuilder(IfdStandard, binary.BigEndian)
ib := NewIfdBuilder(RootIi, TestDefaultByteOrder)
bt := builderTag{
tagId: 0x11,
@ -481,7 +481,7 @@ func TestFindN_Middle_WithDuplicates(t *testing.T) {
}
func TestFindN_Middle_NoDuplicates(t *testing.T) {
ib := NewIfdBuilder(IfdStandard, binary.BigEndian)
ib := NewIfdBuilder(RootIi, TestDefaultByteOrder)
bt := builderTag{
tagId: 0x11,
@ -533,7 +533,7 @@ func TestFindN_Middle_NoDuplicates(t *testing.T) {
}
func TestFindN_Miss(t *testing.T) {
ib := NewIfdBuilder(IfdStandard, binary.BigEndian)
ib := NewIfdBuilder(RootIi, TestDefaultByteOrder)
found, err := ib.FindN(0x11, 1)
log.PanicIf(err)
@ -544,7 +544,7 @@ func TestFindN_Miss(t *testing.T) {
}
func TestFind_Hit(t *testing.T) {
ib := NewIfdBuilder(IfdStandard, binary.BigEndian)
ib := NewIfdBuilder(RootIi, TestDefaultByteOrder)
bt := builderTag{
tagId: 0x11,
@ -594,7 +594,7 @@ func TestFind_Hit(t *testing.T) {
}
func TestFind_Miss(t *testing.T) {
ib := NewIfdBuilder(IfdStandard, binary.BigEndian)
ib := NewIfdBuilder(RootIi, TestDefaultByteOrder)
bt := builderTag{
tagId: 0x11,
@ -637,7 +637,7 @@ func TestFind_Miss(t *testing.T) {
}
func TestReplace(t *testing.T) {
ib := NewIfdBuilder(IfdStandard, binary.BigEndian)
ib := NewIfdBuilder(RootIi, TestDefaultByteOrder)
bt := builderTag{
tagId: 0x11,
@ -691,7 +691,7 @@ func TestReplace(t *testing.T) {
}
func TestReplaceN(t *testing.T) {
ib := NewIfdBuilder(IfdStandard, binary.BigEndian)
ib := NewIfdBuilder(RootIi, TestDefaultByteOrder)
bt := builderTag{
tagId: 0x11,
@ -745,7 +745,7 @@ func TestReplaceN(t *testing.T) {
}
func TestDeleteFirst(t *testing.T) {
ib := NewIfdBuilder(IfdStandard, binary.BigEndian)
ib := NewIfdBuilder(RootIi, TestDefaultByteOrder)
bt := builderTag{
tagId: 0x11,
@ -837,7 +837,7 @@ func TestDeleteFirst(t *testing.T) {
}
func TestDeleteN(t *testing.T) {
ib := NewIfdBuilder(IfdStandard, binary.BigEndian)
ib := NewIfdBuilder(RootIi, TestDefaultByteOrder)
bt := builderTag{
tagId: 0x11,
@ -929,7 +929,7 @@ func TestDeleteN(t *testing.T) {
}
func TestDeleteN_Two(t *testing.T) {
ib := NewIfdBuilder(IfdStandard, binary.BigEndian)
ib := NewIfdBuilder(RootIi, TestDefaultByteOrder)
bt := builderTag{
tagId: 0x11,
@ -1004,7 +1004,7 @@ func TestDeleteN_Two(t *testing.T) {
}
func TestDeleteAll(t *testing.T) {
ib := NewIfdBuilder(IfdStandard, binary.BigEndian)
ib := NewIfdBuilder(RootIi, TestDefaultByteOrder)
bt := builderTag{
tagId: 0x11,
@ -1166,18 +1166,23 @@ func TestNewIfdBuilderFromExistingChain(t *testing.T) {
func TestNewIfdBuilderWithExistingIfd(t *testing.T) {
testIfdName := IfdGps
tagId := IfdTagIds[testIfdName]
ii, _ := IfdIdOrFail(IfdStandard, IfdGps)
tagId := IfdTagIdWithIdentityOrFail(ii)
parentIfd := &Ifd{
Name: IfdStandard,
}
ifd := &Ifd{
Name: testIfdName,
ByteOrder: binary.BigEndian,
Name: IfdGps,
ByteOrder: TestDefaultByteOrder,
Offset: 0x123,
ParentIfd: parentIfd,
}
ib := NewIfdBuilderWithExistingIfd(ifd)
if ib.ifdName != ifd.Name {
if ib.ii.IfdName != ifd.Name {
t.Fatalf("IFD-name not correct.")
} else if ib.ifdTagId != tagId {
t.Fatalf("IFD tag-ID not correct.")
@ -1187,3 +1192,29 @@ func TestNewIfdBuilderWithExistingIfd(t *testing.T) {
t.Fatalf("IFD offset not correct.")
}
}
func TestNewBuilderTagFromConfig_OneUnit(t *testing.T) {
bt := NewBuilderTagFromConfig(ExifIi, 0x8833, TestDefaultByteOrder, []uint32 { uint32(0x1234) })
if bt.ii != ExifIi {
t.Fatalf("II in builderTag not correct")
} else if bt.tagId != 0x8833 {
t.Fatalf("tag-ID not correct")
} else if bytes.Compare(bt.value.Bytes(), []byte { 0x0, 0x0, 0x12, 0x34, }) != 0 {
t.Fatalf("value not correct")
}
}
func TestNewBuilderTagFromConfig_TwoUnits(t *testing.T) {
bt := NewBuilderTagFromConfig(ExifIi, 0x8833, TestDefaultByteOrder, []uint32 { uint32(0x1234), uint32(0x5678) })
if bt.ii != ExifIi {
t.Fatalf("II in builderTag not correct")
} else if bt.tagId != 0x8833 {
t.Fatalf("tag-ID not correct")
} else if bytes.Compare(bt.value.Bytes(), []byte {
0x0, 0x0, 0x12, 0x34,
0x0, 0x0, 0x56, 0x78, }) != 0 {
t.Fatalf("value not correct")
}
}

View File

@ -128,31 +128,18 @@ func (ie *IfdEnumerate) getTagEnumerator(ifdOffset uint32) (ite *IfdTagEnumerato
// TagVisitor is an optional callback that can get hit for every tag we parse
// through. `addressableData` is the byte array startign after the EXIF header
// (where the offsets of all IFDs and values are calculated from).
type TagVisitor func(indexedIfdName string, tagId uint16, tagType TagType, valueContext ValueContext) (err error)
type TagVisitor func(ii IfdIdentity, ifdIndex int, tagId uint16, tagType TagType, valueContext ValueContext) (err error)
// ParseIfd decodes the IFD block that we're currently sitting on the first
// byte of.
func (ie *IfdEnumerate) ParseIfd(ifdName string, ifdIndex int, ifdOffset uint32, visitor TagVisitor, doDescend bool) (nextIfdOffset uint32, entries []IfdTagEntry, err error) {
func (ie *IfdEnumerate) ParseIfd(ii IfdIdentity, ifdIndex int, ifdOffset uint32, visitor TagVisitor, doDescend bool) (nextIfdOffset uint32, entries []IfdTagEntry, err error) {
defer func() {
if state := recover(); state != nil {
err = log.Wrap(state.(error))
}
}()
ifdEnumerateLogger.Debugf(nil, "Parsing IFD [%s] (%d) at offset (%04x).", ifdName, ifdIndex, ifdOffset)
// Return the name of the IFD as its known in our tag-index. We should skip
// over the current IFD if this is empty (which means we don't recognize/
// understand the IFD and, therefore, don't know the tags that are valid for
// it). Note that we could leave ignoring the tags as a responsibility for
// the visitor, but then it'd be easy for people to integrate that logic and
// not realize that they needed to specially handle an empty IFD name until
// they happened upon some obscure media one day and suddenly have issue if
// they unwittingly write something that breaks in that situation.
indexedIfdName := IfdName(ifdName, ifdIndex)
if indexedIfdName == "" {
ifdEnumerateLogger.Debugf(nil, "IFD not known and will not be visited: [%s] (%d)", ifdName, ifdIndex)
}
ifdEnumerateLogger.Debugf(nil, "Parsing IFD [%s] (%d) at offset (%04x).", ii.IfdName, ifdIndex, ifdOffset)
ite := ie.getTagEnumerator(ifdOffset)
@ -176,7 +163,7 @@ func (ie *IfdEnumerate) ParseIfd(ifdName string, ifdIndex int, ifdOffset uint32,
valueOffset, rawValueOffset, err := ite.getUint32()
log.PanicIf(err)
if visitor != nil && indexedIfdName != "" {
if visitor != nil {
tt := NewTagType(tagType, ie.byteOrder)
vc := ValueContext{
@ -186,12 +173,12 @@ func (ie *IfdEnumerate) ParseIfd(ifdName string, ifdIndex int, ifdOffset uint32,
AddressableData: ie.exifData[ExifAddressableAreaStart:],
}
err := visitor(indexedIfdName, tagId, tt, vc)
err := visitor(ii, ifdIndex, tagId, tt, vc)
log.PanicIf(err)
}
tag := IfdTagEntry{
IfdName: ifdName,
Ii: ii,
TagId: tagId,
TagIndex: int(i),
TagType: tagType,
@ -200,14 +187,20 @@ func (ie *IfdEnumerate) ParseIfd(ifdName string, ifdIndex int, ifdOffset uint32,
RawValueOffset: rawValueOffset,
}
childIfdName, isIfd := IsIfdTag(tagId)
childIfdName, isIfd := IfdTagNameWithId(ii.IfdName, tagId)
// If it's an IFD but not a standard one, it'll just be seen as a LONG
// (the standard IFD tag type), later, unless we skip it because it's
// [likely] not even in the standard list of known tags.
if isIfd == true {
tag.ChildIfdName = childIfdName
if doDescend == true {
ifdEnumerateLogger.Debugf(nil, "Descending to IFD [%s].", childIfdName)
err := ie.Scan(childIfdName, valueOffset, visitor)
childIi, _ := IfdIdOrFail(ii.IfdName, childIfdName)
err := ie.scan(childIi, valueOffset, visitor)
log.PanicIf(err)
}
}
@ -224,7 +217,7 @@ func (ie *IfdEnumerate) ParseIfd(ifdName string, ifdIndex int, ifdOffset uint32,
}
// Scan enumerates the different EXIF blocks (called IFDs).
func (ie *IfdEnumerate) Scan(ifdName string, ifdOffset uint32, visitor TagVisitor) (err error) {
func (ie *IfdEnumerate) scan(ii IfdIdentity, ifdOffset uint32, visitor TagVisitor) (err error) {
defer func() {
if state := recover(); state != nil {
err = log.Wrap(state.(error))
@ -232,7 +225,7 @@ func (ie *IfdEnumerate) Scan(ifdName string, ifdOffset uint32, visitor TagVisito
}()
for ifdIndex := 0;; ifdIndex++ {
nextIfdOffset, _, err := ie.ParseIfd(ifdName, ifdIndex, ifdOffset, visitor, true)
nextIfdOffset, _, err := ie.ParseIfd(ii, ifdIndex, ifdOffset, visitor, true)
log.PanicIf(err)
if nextIfdOffset == 0 {
@ -245,6 +238,21 @@ func (ie *IfdEnumerate) Scan(ifdName string, ifdOffset uint32, visitor TagVisito
return nil
}
// Scan enumerates the different EXIF blocks (called IFDs).
func (ie *IfdEnumerate) Scan(ifdOffset uint32, visitor TagVisitor) (err error) {
defer func() {
if state := recover(); state != nil {
err = log.Wrap(state.(error))
}
}()
ii, _ := IfdIdOrFail("", IfdStandard)
err = ie.scan(ii, ifdOffset, visitor)
log.PanicIf(err)
return nil
}
type Ifd struct {
ByteOrder binary.ByteOrder
@ -272,6 +280,18 @@ func (ifd Ifd) String() string {
return fmt.Sprintf("IFD<ID=(%d) N=[%s] IDX=(%d) OFF=(0x%04x) COUNT=(%d) CHILDREN=(%d) PARENT=(0x%04x) NEXT-IFD=(0x%04x)", ifd.Id, ifd.Name, ifd.Index, ifd.Offset, len(ifd.Entries), len(ifd.Children), parentOffset, ifd.NextIfdOffset)
}
func (ifd Ifd) Identity() IfdIdentity {
parentIfdName := ""
if ifd.ParentIfd != nil {
parentIfdName = ifd.ParentIfd.Name
}
// TODO(dustin): !! We should be checking using the parent ID, not the parent name.
ii, _ := IfdIdOrFail(parentIfdName, ifd.Name)
return ii
}
func (ifd Ifd) printNode(level int, nextLink bool) {
indent := strings.Repeat(" ", level * 2)
@ -297,7 +317,7 @@ func (ifd Ifd) PrintTree() {
type QueuedIfd struct {
Name string
Ii IfdIdentity
Index int
Offset uint32
Parent *Ifd
@ -308,7 +328,7 @@ type IfdIndex struct {
RootIfd *Ifd
Ifds []*Ifd
Tree map[int]*Ifd
Lookup map[string][]*Ifd
Lookup map[IfdIdentity][]*Ifd
}
@ -322,11 +342,11 @@ func (ie *IfdEnumerate) Collect(rootIfdOffset uint32) (index IfdIndex, err error
tree := make(map[int]*Ifd)
ifds := make([]*Ifd, 0)
lookup := make(map[string][]*Ifd)
lookup := make(map[IfdIdentity][]*Ifd)
queue := []QueuedIfd {
{
Name: IfdStandard,
Ii: RootIi,
Index: 0,
Offset: rootIfdOffset,
},
@ -339,14 +359,15 @@ func (ie *IfdEnumerate) Collect(rootIfdOffset uint32) (index IfdIndex, err error
break
}
name := queue[0].Name
ii := queue[0].Ii
name := ii.IfdName
index := queue[0].Index
offset := queue[0].Offset
parentIfd := queue[0].Parent
queue = queue[1:]
nextIfdOffset, entries, err := ie.ParseIfd(name, index, offset, nil, false)
nextIfdOffset, entries, err := ie.ParseIfd(ii, index, offset, nil, false)
log.PanicIf(err)
id := len(ifds)
@ -371,13 +392,13 @@ func (ie *IfdEnumerate) Collect(rootIfdOffset uint32) (index IfdIndex, err error
// Install into by-name buckets.
if list_, found := lookup[name]; found == true {
lookup[name] = append(list_, &ifd)
if list_, found := lookup[ii]; found == true {
lookup[ii] = append(list_, &ifd)
} else {
list_ = make([]*Ifd, 1)
list_[0] = &ifd
lookup[name] = list_
lookup[ii] = list_
}
// Add a link from the previous IFD in the chain to us.
@ -397,8 +418,13 @@ func (ie *IfdEnumerate) Collect(rootIfdOffset uint32) (index IfdIndex, err error
continue
}
childId := IfdIdentity{
ParentIfdName: name,
IfdName: entry.ChildIfdName,
}
qi := QueuedIfd {
Name: entry.ChildIfdName,
Ii: childId,
Index: 0,
Offset: entry.ValueOffset,
Parent: &ifd,
@ -413,7 +439,7 @@ func (ie *IfdEnumerate) Collect(rootIfdOffset uint32) (index IfdIndex, err error
edges[nextIfdOffset] = &ifd
qi := QueuedIfd {
Name: IfdStandard,
Ii: ii,
Index: index + 1,
Offset: nextIfdOffset,
}

View File

@ -144,7 +144,8 @@ func TestIfdTagEntry_Resolver_ValueBytes__Unknown_Field_And_Nonroot_Ifd(t *testi
eh, index, err := e.Collect(rawExif)
log.PanicIf(err)
ifdExif := index.Lookup[IfdExif][0]
ii, _ := IfdIdOrFail(IfdStandard, IfdExif)
ifdExif := index.Lookup[ii][0]
var ite *IfdTagEntry
for _, thisIte := range ifdExif.Entries {

View File

@ -25,7 +25,7 @@ type IfdTagEntry struct {
ChildIfdName string
// IfdName is the IFD that this tag belongs to.
IfdName string
Ii IfdIdentity
}
func (ite IfdTagEntry) String() string {
@ -47,7 +47,7 @@ func (ite IfdTagEntry) ValueBytes(addressableData []byte, byteOrder binary.ByteO
AddressableData: addressableData,
}
value, err := UndefinedValue(ite.IfdName, ite.TagId, valueContext, byteOrder)
value, err := UndefinedValue(ite.Ii, ite.TagId, valueContext, byteOrder)
log.PanicIf(err)
switch value.(type) {

175
tags.go
View File

@ -12,14 +12,20 @@ import (
const (
IfdStandard = "IFD"
IfdExif = "Exif"
IfdGps = "GPSInfo"
IfdIop = "Iop"
IfdExifId = 0x8769
IfdGpsId = 0x8825
IfdIopId = 0xA005
)
var (
tagDataFilepath = ""
// TODO(dustin): !! Get rid of this in favor of one of the two lookups, just below.
validIfds = []string {
IfdStandard,
IfdExif,
@ -27,14 +33,49 @@ var (
IfdIop,
}
IfdTagIds = map[string]uint16 {
IfdExif: 0x8769,
IfdGps: 0x8825,
IfdIop: 0xA005,
// A lookup for IFDs by their parents.
// TODO(dustin): !! We should switch to indexing by their unique integer IDs (defined below) rather than exposing ourselves to non-unique IFD names (even if we *do* manage the naming ourselves).
IfdTagIds = map[string]map[string]uint16 {
"": map[string]uint16 {
// A root IFD type. Not allowed to be a child (tag-based) IFD.
IfdStandard: 0x0,
},
IfdStandard: map[string]uint16 {
IfdExif: IfdExifId,
IfdGps: IfdGpsId,
},
IfdExif: map[string]uint16 {
IfdIop: IfdIopId,
},
}
// IfdTagNames is populated in the init(), below.
IfdTagNames = map[uint16]string {}
IfdTagNames = map[string]map[uint16]string {}
// IFD Identities. These are often how we refer to IFDs, from call to call.
// The NULL-type instance for search misses and empty responses.
ZeroIi = IfdIdentity{}
RootIi = IfdIdentity{ IfdName: IfdStandard }
ExifIi = IfdIdentity{ ParentIfdName: IfdStandard, IfdName: IfdExif }
GpsIi = IfdIdentity{ ParentIfdName: IfdStandard, IfdName: IfdGps }
ExifIopIi = IfdIdentity{ ParentIfdName: IfdExif, IfdName: IfdIop }
// Produce a list of unique IDs for each IFD that we can pass around (so we
// don't always have to be comparing parent and child names).
//
// For lack of need, this is just static.
//
// (0) is reserved for not-found/miss responses.
IfdIds = map[IfdIdentity]int {
RootIi: 1,
ExifIi: 2,
GpsIi: 3,
ExifIopIi: 4,
}
)
var (
@ -43,6 +84,20 @@ var (
)
type IfdIdentity struct {
ParentIfdName string
IfdName string
}
func (ii IfdIdentity) String() string {
return fmt.Sprintf("IfdIdentity<PARENT-NAME=[%s] NAME=[%s]>", ii.ParentIfdName, ii.IfdName)
}
func (ii IfdIdentity) Id() int {
return IfdIdWithIdentityOrFail(ii)
}
// File structures.
type encodedTag struct {
@ -161,14 +216,14 @@ if tagTypeName == "SSHORT" || tagTypeName == "FLOAT" || tagTypeName == "DOUBLE"
return nil
}
func (ti *TagIndex) Get(ifdName string, id uint16) (it *IndexedTag, err error) {
func (ti *TagIndex) Get(ii IfdIdentity, id uint16) (it *IndexedTag, err error) {
defer func() {
if state := recover(); state != nil {
err = log.Wrap(state.(error))
}
}()
family, found := ti.tagsByIfd[ifdName]
family, found := ti.tagsByIfd[ii.IfdName]
if found == false {
log.Panic(ErrTagNotFound)
}
@ -181,26 +236,90 @@ func (ti *TagIndex) Get(ifdName string, id uint16) (it *IndexedTag, err error) {
return it, nil
}
// IfdName returns the known index name for the tags that are expected/allowed
// for the IFD. If there's an error, returns "". If returns "", the IFD should
// be skipped.
func IfdName(ifdName string, ifdIndex int) string {
// There's an IFD0 and IFD1, but the others must be unique.
if ifdName == IfdStandard && ifdIndex > 1 {
tagsLogger.Errorf(nil, "The 'IFD' IFD can not occur more than twice: [%s]. Ignoring IFD.", ifdName)
return ""
} else if ifdName != IfdStandard && ifdIndex > 0 {
tagsLogger.Errorf(nil, "Only the 'IFD' IFD can be repeated: [%s]. Ignoring IFD.", ifdName)
return ""
// IfdTagWithId returns true if the given tag points to a child IFD block.
func IfdTagNameWithIdOrFail(parentIfdName string, tagId uint16) string {
name, found := IfdTagNameWithId(parentIfdName, tagId)
if found == false {
log.Panicf("tag-ID (0x%02x) under parent IFD [%s] not associated with a child IFD", tagId, parentIfdName)
}
return ifdName
return name
}
// IsIfdTag returns true if the given tag points to a child IFD block.
func IsIfdTag(tagId uint16) (name string, found bool) {
name, found = IfdTagNames[tagId]
return name, found
// IfdTagWithId returns true if the given tag points to a child IFD block.
func IfdTagNameWithId(parentIfdName string, tagId uint16) (name string, found bool) {
if tags, found := IfdTagNames[parentIfdName]; found == true {
if name, found = tags[tagId]; found == true {
return name, true
}
}
return "", false
}
// IfdTagIdWithIdentity returns true if the given tag points to a child IFD
// block.
func IfdTagIdWithIdentity(ii IfdIdentity) (tagId uint16, found bool) {
if tags, found := IfdTagIds[ii.ParentIfdName]; found == true {
if tagId, found = tags[ii.IfdName]; found == true {
return tagId, true
}
}
return 0, false
}
func IfdTagIdWithIdentityOrFail(ii IfdIdentity) (tagId uint16) {
if tags, found := IfdTagIds[ii.ParentIfdName]; found == true {
if tagId, found = tags[ii.IfdName]; found == true {
if tagId == 0 {
// This IFD is not the type that can be linked to from a tag.
log.Panicf("not a child IFD")
}
return tagId
}
}
log.Panicf("no tag for invalid IFD identity")
return 0
}
func IfdIdWithIdentity(ii IfdIdentity) int {
id, _ := IfdIds[ii]
return id
}
func IfdIdWithIdentityOrFail(ii IfdIdentity) int {
id, _ := IfdIds[ii]
if id == 0 {
log.Panicf("IFD not valid: %v", ii)
}
return id
}
func IfdIdOrFail(parentIfdName, ifdName string) (ii IfdIdentity, id int) {
ii, id = IfdId(parentIfdName, ifdName)
if id == 0 {
log.Panicf("IFD is not valid: [%s] [%s]", parentIfdName, ifdName)
}
return ii, id
}
func IfdId(parentIfdName, ifdName string) (ii IfdIdentity, id int) {
ii = IfdIdentity{
ParentIfdName: parentIfdName,
IfdName: ifdName,
}
id, found := IfdIds[ii]
if found != true {
return IfdIdentity{}, 0
}
return ii, id
}
func init() {
@ -212,7 +331,13 @@ func init() {
assetsPath := path.Join(goPath, "src", "github.com", "dsoprea", "go-exif", "assets")
tagDataFilepath = path.Join(assetsPath, "tags.yaml")
for name, tagId := range IfdTagIds {
IfdTagNames[tagId] = name
for ifdName, tags := range IfdTagIds {
tagsR := make(map[uint16]string)
for tagName, tagId := range tags {
tagsR[tagId] = tagName
}
IfdTagNames[ifdName] = tagsR
}
}

View File

@ -6,15 +6,182 @@ import (
"github.com/dsoprea/go-logging"
)
var (
invalidIi = IfdIdentity{
ParentIfdName: "invalid-parent",
IfdName: "invalid-child",
}
)
func TestGet(t *testing.T) {
ti := NewTagIndex()
indexedIfdName := IfdName(IfdStandard, 0)
it, err := ti.Get(indexedIfdName, 0x10f)
it, err := ti.Get(RootIi, 0x10f)
log.PanicIf(err)
if it.Is(0x10f) == false || it.IsName("IFD", "Make") == false {
t.Fatalf("tag info not correct")
}
}
func TestIfdTagNameWithIdOrFail_Miss(t *testing.T) {
defer func() {
if state := recover(); state != nil {
err := log.Wrap(state.(error))
if err.Error() != "tag-ID (0x1234) under parent IFD [Exif] not associated with a child IFD" {
log.Panic(err)
}
}
}()
IfdTagNameWithIdOrFail(IfdExif, 0x1234)
t.Fatalf("expected failing for invalid IFD tag-ID")
}
func TestIfdTagNameWithIdOrFail_RootChildIfd(t *testing.T) {
name := IfdTagNameWithIdOrFail(IfdStandard, IfdExifId)
if name != IfdExif {
t.Fatalf("expected EXIF IFD name for hit on EXIF IFD")
}
}
func TestIfdTagNameWithIdOrFail_ChildChildIfd(t *testing.T) {
name := IfdTagNameWithIdOrFail(IfdExif, IfdIopId)
if name != IfdIop {
t.Fatalf("expected IOP IFD name for hit on IOP IFD")
}
}
func TestIfdTagNameWithId_Hit(t *testing.T) {
name, found := IfdTagNameWithId(IfdExif, IfdIopId)
if found != true {
t.Fatalf("could not get name for IOP IFD tag-ID")
} else if name != IfdIop {
t.Fatalf("expected IOP IFD name for hit on IOP IFD")
}
}
func TestIfdTagNameWithId_Miss(t *testing.T) {
name, found := IfdTagNameWithId(IfdExif, 0x1234)
if found != false {
t.Fatalf("expected failure for invalid IFD iag-ID under EXIF IFD")
} else if name != "" {
t.Fatalf("expected empty IFD name for miss")
}
}
func TestIfdTagIdWithIdentity_Hit(t *testing.T) {
tagId, found := IfdTagIdWithIdentity(GpsIi)
if found != true {
t.Fatalf("could not get tag-ID for II")
} else if tagId != IfdGpsId {
t.Fatalf("incorrect tag-ID returned for II")
}
}
func TestIfdTagIdWithIdentity_Miss(t *testing.T) {
tagId, found := IfdTagIdWithIdentity(invalidIi)
if found != false {
t.Fatalf("expected failure")
} else if tagId != 0 {
t.Fatalf("expected tag-ID of (0) for failure")
}
}
func TestIfdTagIdWithIdentityOrFail_Hit(t *testing.T) {
IfdTagIdWithIdentityOrFail(GpsIi)
}
func TestIfdTagIdWithIdentityOrFail_Miss(t *testing.T) {
defer func() {
if state := recover(); state != nil {
err := log.Wrap(state.(error))
if err.Error() != "no tag for invalid IFD identity" {
log.Panic(err)
}
}
}()
IfdTagIdWithIdentityOrFail(invalidIi)
t.Fatalf("invalid II didn't panic")
}
func TestIfdIdWithIdentity_Hit(t *testing.T) {
id := IfdIdWithIdentity(GpsIi)
if id != 3 {
t.Fatalf("II doesn't have the right ID")
}
}
func TestIfdIdWithIdentity_Miss(t *testing.T) {
id := IfdIdWithIdentity(invalidIi)
if id != 0 {
t.Fatalf("II doesn't have the right ID for a miss")
}
}
func TestIfdIdWithIdentityOrFail_Hit(t *testing.T) {
id := IfdIdWithIdentityOrFail(GpsIi)
if id != 3 {
t.Fatalf("II doesn't have the right ID")
}
}
func TestIfdIdWithIdentityOrFail_Miss(t *testing.T) {
defer func() {
if state := recover(); state != nil {
err := log.Wrap(state.(error))
if err.Error() != "IFD not valid: IfdIdentity<PARENT-NAME=[invalid-parent] NAME=[invalid-child]>" {
log.Panic(err)
}
}
}()
IfdIdWithIdentityOrFail(invalidIi)
t.Fatalf("invalid II doesn't panic")
}
func TestIfdIdOrFail_Hit(t *testing.T) {
ii, id := IfdIdOrFail(IfdStandard, IfdExif)
if ii != ExifIi {
t.Fatalf("wrong II for IFD returned")
} else if id != 2 {
t.Fatalf("wrong ID for II returned")
}
}
func TestIfdIdOrFail_Miss(t *testing.T) {
defer func() {
if state := recover(); state != nil {
err := log.Wrap(state.(error))
if err.Error() != "IFD is not valid: [IFD] [invalid-ifd]" {
log.Panic(err)
}
}
}()
IfdIdOrFail(IfdStandard, "invalid-ifd")
t.Fatalf("expected panic for invalid IFD")
}
func TestIfdId_Hit(t *testing.T) {
ii, id := IfdId(IfdStandard, IfdExif)
if ii != ExifIi {
t.Fatalf("wrong II for IFD returned")
} else if id != 2 {
t.Fatalf("wrong ID for II returned")
}
}
func TestIfdId_Miss(t *testing.T) {
ii, id := IfdId(IfdStandard, "invalid-ifd")
if id != 0 {
t.Fatalf("non-zero ID returned for invalid IFD")
} else if ii != ZeroIi {
t.Fatalf("expected zero-instance result for miss")
}
}

9
test_common.go Normal file
View File

@ -0,0 +1,9 @@
package exif
import (
"encoding/binary"
)
var (
TestDefaultByteOrder = binary.BigEndian
)

774
type.go
View File

@ -2,6 +2,12 @@ package exif
import (
"errors"
"bytes"
"fmt"
"encoding/binary"
"github.com/dsoprea/go-logging"
)
const (
@ -18,6 +24,10 @@ const (
TypeAsciiNoNul = uint16(0xf0)
)
var (
typeLogger = log.NewLogger("exif.type")
)
var (
TypeNames = map[uint16]string {
TypeByte: "BYTE",
@ -66,3 +76,767 @@ func init() {
TypeNamesR[typeName] = typeId
}
}
type TagType struct {
tagType uint16
name string
byteOrder binary.ByteOrder
}
func NewTagType(tagType uint16, byteOrder binary.ByteOrder) TagType {
name, found := TypeNames[tagType]
if found == false {
log.Panicf("tag-type not valid: 0x%04x", tagType)
}
return TagType{
tagType: tagType,
name: name,
byteOrder: byteOrder,
}
}
func (tt TagType) String() string {
return fmt.Sprintf("TagType<NAME=[%s]>", tt.name)
}
func (tt TagType) Name() string {
return tt.name
}
func (tt TagType) Type() uint16 {
return tt.tagType
}
func (tt TagType) ByteOrder() binary.ByteOrder {
return tt.byteOrder
}
func (tt TagType) Size() int {
return TagTypeSize(tt.Type())
}
func TagTypeSize(tagType uint16) int {
if tagType == TypeByte {
return 1
} else if tagType == TypeAscii || tagType == TypeAsciiNoNul {
return 1
} else if tagType == TypeShort {
return 2
} else if tagType == TypeLong {
return 4
} else if tagType == TypeRational {
return 8
} else if tagType == TypeSignedLong {
return 4
} else if tagType == TypeSignedRational {
return 8
} else {
log.Panicf("can not determine tag-value size for type (%d): [%s]", tagType, TypeNames[tagType])
// Never called.
return 0
}
}
// ValueIsEmbedded will return a boolean indicating whether the value should be
// found directly within the IFD entry or an offset to somewhere else.
func (tt TagType) ValueIsEmbedded(unitCount uint32) bool {
return (tt.Size() * int(unitCount)) <= 4
}
func (tt TagType) ParseBytes(data []byte, unitCount uint32) (value []uint8, err error) {
defer func() {
if state := recover(); state != nil {
err = log.Wrap(state.(error))
}
}()
if tt.tagType != TypeByte {
log.Panic(ErrWrongType)
}
count := int(unitCount)
if len(data) < (tt.Size() * count) {
log.Panic(ErrNotEnoughData)
}
value = []uint8(data[:count])
return value, nil
}
// ParseAscii returns a string and auto-strips the trailing NUL character.
func (tt TagType) ParseAscii(data []byte, unitCount uint32) (value string, err error) {
defer func() {
if state := recover(); state != nil {
err = log.Wrap(state.(error))
}
}()
if tt.tagType != TypeAscii && tt.tagType != TypeAsciiNoNul {
log.Panic(ErrWrongType)
}
count := int(unitCount)
if len(data) < (tt.Size() * count) {
log.Panic(ErrNotEnoughData)
}
if len(data) == 0 || data[count - 1] != 0 {
typeLogger.Warningf(nil, "ascii not terminated with nul")
return string(data[:count]), nil
} else {
return string(data[:count - 1]), nil
}
}
// ParseAsciiNoNul returns a string without any consideration for a trailing NUL
// character.
func (tt TagType) ParseAsciiNoNul(data []byte, unitCount uint32) (value string, err error) {
defer func() {
if state := recover(); state != nil {
err = log.Wrap(state.(error))
}
}()
if tt.tagType != TypeAscii && tt.tagType != TypeAsciiNoNul {
log.Panic(ErrWrongType)
}
count := int(unitCount)
if len(data) < (tt.Size() * count) {
log.Panic(ErrNotEnoughData)
}
return string(data[:count]), nil
}
func (tt TagType) ParseShorts(data []byte, unitCount uint32) (value []uint16, err error) {
defer func() {
if state := recover(); state != nil {
err = log.Wrap(state.(error))
}
}()
if tt.tagType != TypeShort {
log.Panic(ErrWrongType)
}
count := int(unitCount)
if len(data) < (tt.Size() * count) {
log.Panic(ErrNotEnoughData)
}
value = make([]uint16, count)
for i := 0; i < count; i++ {
if tt.byteOrder == binary.BigEndian {
value[i] = binary.BigEndian.Uint16(data[i*2:])
} else {
value[i] = binary.LittleEndian.Uint16(data[i*2:])
}
}
return value, nil
}
func (tt TagType) ParseLongs(data []byte, unitCount uint32) (value []uint32, err error) {
defer func() {
if state := recover(); state != nil {
err = log.Wrap(state.(error))
}
}()
if tt.tagType != TypeLong {
log.Panic(ErrWrongType)
}
count := int(unitCount)
if len(data) < (tt.Size() * count) {
log.Panic(ErrNotEnoughData)
}
value = make([]uint32, count)
for i := 0; i < count; i++ {
if tt.byteOrder == binary.BigEndian {
value[i] = binary.BigEndian.Uint32(data[i*4:])
} else {
value[i] = binary.LittleEndian.Uint32(data[i*4:])
}
}
return value, nil
}
func (tt TagType) ParseRationals(data []byte, unitCount uint32) (value []Rational, err error) {
defer func() {
if state := recover(); state != nil {
err = log.Wrap(state.(error))
}
}()
if tt.tagType != TypeRational {
log.Panic(ErrWrongType)
}
count := int(unitCount)
if len(data) < (tt.Size() * count) {
log.Panic(ErrNotEnoughData)
}
value = make([]Rational, count)
for i := 0; i < count; i++ {
if tt.byteOrder == binary.BigEndian {
value[i].Numerator = binary.BigEndian.Uint32(data[i*8:])
value[i].Denominator = binary.BigEndian.Uint32(data[i*8 + 4:])
} else {
value[i].Numerator = binary.LittleEndian.Uint32(data[i*8:])
value[i].Denominator = binary.LittleEndian.Uint32(data[i*8 + 4:])
}
}
return value, nil
}
func (tt TagType) ParseSignedLongs(data []byte, unitCount uint32) (value []int32, err error) {
defer func() {
if state := recover(); state != nil {
err = log.Wrap(state.(error))
}
}()
if tt.tagType != TypeSignedLong {
log.Panic(ErrWrongType)
}
count := int(unitCount)
if len(data) < (tt.Size() * count) {
log.Panic(ErrNotEnoughData)
}
b := bytes.NewBuffer(data)
value = make([]int32, count)
for i := 0; i < count; i++ {
if tt.byteOrder == binary.BigEndian {
err := binary.Read(b, binary.BigEndian, &value[i])
log.PanicIf(err)
} else {
err := binary.Read(b, binary.LittleEndian, &value[i])
log.PanicIf(err)
}
}
return value, nil
}
func (tt TagType) ParseSignedRationals(data []byte, unitCount uint32) (value []SignedRational, err error) {
defer func() {
if state := recover(); state != nil {
err = log.Wrap(state.(error))
}
}()
if tt.tagType != TypeSignedRational {
log.Panic(ErrWrongType)
}
count := int(unitCount)
if len(data) < (tt.Size() * count) {
log.Panic(ErrNotEnoughData)
}
b := bytes.NewBuffer(data)
value = make([]SignedRational, count)
for i := 0; i < count; i++ {
if tt.byteOrder == binary.BigEndian {
err = binary.Read(b, binary.BigEndian, &value[i].Numerator)
log.PanicIf(err)
err = binary.Read(b, binary.BigEndian, &value[i].Denominator)
log.PanicIf(err)
} else {
err = binary.Read(b, binary.LittleEndian, &value[i].Numerator)
log.PanicIf(err)
err = binary.Read(b, binary.LittleEndian, &value[i].Denominator)
log.PanicIf(err)
}
}
return value, nil
}
func (tt TagType) ReadByteValues(valueContext ValueContext) (value []byte, err error) {
defer func() {
if state := recover(); state != nil {
err = log.Wrap(state.(error))
}
}()
if tt.ValueIsEmbedded(valueContext.UnitCount) == true {
typeLogger.Debugf(nil, "Reading BYTE value (embedded).")
// In this case, the bytes normally used for the offset are actually
// data.
value, err = tt.ParseBytes(valueContext.RawValueOffset, valueContext.UnitCount)
log.PanicIf(err)
} else {
typeLogger.Debugf(nil, "Reading BYTE value (at offset).")
value, err = tt.ParseBytes(valueContext.AddressableData[valueContext.ValueOffset:], valueContext.UnitCount)
log.PanicIf(err)
}
return value, nil
}
func (tt TagType) ReadAsciiValue(valueContext ValueContext) (value string, err error) {
defer func() {
if state := recover(); state != nil {
err = log.Wrap(state.(error))
}
}()
if tt.ValueIsEmbedded(valueContext.UnitCount) == true {
typeLogger.Debugf(nil, "Reading ASCII value (no-nul; embedded).")
value, err = tt.ParseAscii(valueContext.RawValueOffset, valueContext.UnitCount)
log.PanicIf(err)
} else {
typeLogger.Debugf(nil, "Reading ASCII value (no-nul; at offset).")
value, err = tt.ParseAscii(valueContext.AddressableData[valueContext.ValueOffset:], valueContext.UnitCount)
log.PanicIf(err)
}
return value, nil
}
func (tt TagType) ReadAsciiNoNulValue(valueContext ValueContext) (value string, err error) {
defer func() {
if state := recover(); state != nil {
err = log.Wrap(state.(error))
}
}()
if tt.ValueIsEmbedded(valueContext.UnitCount) == true {
typeLogger.Debugf(nil, "Reading ASCII value (no-nul; embedded).")
value, err = tt.ParseAsciiNoNul(valueContext.RawValueOffset, valueContext.UnitCount)
log.PanicIf(err)
} else {
typeLogger.Debugf(nil, "Reading ASCII value (no-nul; at offset).")
value, err = tt.ParseAsciiNoNul(valueContext.AddressableData[valueContext.ValueOffset:], valueContext.UnitCount)
log.PanicIf(err)
}
return value, nil
}
func (tt TagType) ReadShortValues(valueContext ValueContext) (value []uint16, err error) {
defer func() {
if state := recover(); state != nil {
err = log.Wrap(state.(error))
}
}()
if tt.ValueIsEmbedded(valueContext.UnitCount) == true {
typeLogger.Debugf(nil, "Reading SHORT value (embedded).")
value, err = tt.ParseShorts(valueContext.RawValueOffset, valueContext.UnitCount)
log.PanicIf(err)
} else {
typeLogger.Debugf(nil, "Reading SHORT value (at offset).")
value, err = tt.ParseShorts(valueContext.AddressableData[valueContext.ValueOffset:], valueContext.UnitCount)
log.PanicIf(err)
}
return value, nil
}
func (tt TagType) ReadLongValues(valueContext ValueContext) (value []uint32, err error) {
defer func() {
if state := recover(); state != nil {
err = log.Wrap(state.(error))
}
}()
if tt.ValueIsEmbedded(valueContext.UnitCount) == true {
typeLogger.Debugf(nil, "Reading LONG value (embedded).")
value, err = tt.ParseLongs(valueContext.RawValueOffset, valueContext.UnitCount)
log.PanicIf(err)
} else {
typeLogger.Debugf(nil, "Reading LONG value (at offset).")
value, err = tt.ParseLongs(valueContext.AddressableData[valueContext.ValueOffset:], valueContext.UnitCount)
log.PanicIf(err)
}
return value, nil
}
func (tt TagType) ReadRationalValues(valueContext ValueContext) (value []Rational, err error) {
defer func() {
if state := recover(); state != nil {
err = log.Wrap(state.(error))
}
}()
if tt.ValueIsEmbedded(valueContext.UnitCount) == true {
typeLogger.Debugf(nil, "Reading RATIONAL value (embedded).")
value, err = tt.ParseRationals(valueContext.RawValueOffset, valueContext.UnitCount)
log.PanicIf(err)
} else {
typeLogger.Debugf(nil, "Reading RATIONAL value (at offset).")
value, err = tt.ParseRationals(valueContext.AddressableData[valueContext.ValueOffset:], valueContext.UnitCount)
log.PanicIf(err)
}
return value, nil
}
func (tt TagType) ReadSignedLongValues(valueContext ValueContext) (value []int32, err error) {
defer func() {
if state := recover(); state != nil {
err = log.Wrap(state.(error))
}
}()
if tt.ValueIsEmbedded(valueContext.UnitCount) == true {
typeLogger.Debugf(nil, "Reading SLONG value (embedded).")
value, err = tt.ParseSignedLongs(valueContext.RawValueOffset, valueContext.UnitCount)
log.PanicIf(err)
} else {
typeLogger.Debugf(nil, "Reading SLONG value (at offset).")
value, err = tt.ParseSignedLongs(valueContext.AddressableData[valueContext.ValueOffset:], valueContext.UnitCount)
log.PanicIf(err)
}
return value, nil
}
func (tt TagType) ReadSignedRationalValues(valueContext ValueContext) (value []SignedRational, err error) {
defer func() {
if state := recover(); state != nil {
err = log.Wrap(state.(error))
}
}()
if tt.ValueIsEmbedded(valueContext.UnitCount) == true {
typeLogger.Debugf(nil, "Reading SRATIONAL value (embedded).")
value, err = tt.ParseSignedRationals(valueContext.RawValueOffset, valueContext.UnitCount)
log.PanicIf(err)
} else {
typeLogger.Debugf(nil, "Reading SRATIONAL value (at offset).")
value, err = tt.ParseSignedRationals(valueContext.AddressableData[valueContext.ValueOffset:], valueContext.UnitCount)
log.PanicIf(err)
}
return value, nil
}
// ValueString extracts and parses the given value, and returns a flat string.
// Where the type is not ASCII, `justFirst` indicates whether to just stringify
// the first item in the slice (or return an empty string if the slice is
// empty).
func (tt TagType) ValueString(valueContext ValueContext, justFirst bool) (value string, err error) {
defer func() {
if state := recover(); state != nil {
err = log.Wrap(state.(error))
}
}()
if tt.Type() == TypeByte {
raw, err := tt.ReadByteValues(valueContext)
log.PanicIf(err)
if justFirst == false {
return fmt.Sprintf("%v", raw), nil
} else if valueContext.UnitCount > 0 {
return fmt.Sprintf("%v", raw[0]), nil
} else {
return "", nil
}
} else if tt.Type() == TypeAscii {
raw, err := tt.ReadAsciiValue(valueContext)
log.PanicIf(err)
return fmt.Sprintf("%s", raw), nil
} else if tt.Type() == TypeAsciiNoNul {
raw, err := tt.ReadAsciiNoNulValue(valueContext)
log.PanicIf(err)
return fmt.Sprintf("%s", raw), nil
} else if tt.Type() == TypeShort {
raw, err := tt.ReadShortValues(valueContext)
log.PanicIf(err)
if justFirst == false {
return fmt.Sprintf("%v", raw), nil
} else if valueContext.UnitCount > 0 {
return fmt.Sprintf("%v", raw[0]), nil
} else {
return "", nil
}
} else if tt.Type() == TypeLong {
raw, err := tt.ReadLongValues(valueContext)
log.PanicIf(err)
if justFirst == false {
return fmt.Sprintf("%v", raw), nil
} else if valueContext.UnitCount > 0 {
return fmt.Sprintf("%v", raw[0]), nil
} else {
return "", nil
}
} else if tt.Type() == TypeRational {
raw, err := tt.ReadRationalValues(valueContext)
log.PanicIf(err)
parts := make([]string, len(raw))
for i, r := range raw {
parts[i] = fmt.Sprintf("%d/%d", r.Numerator, r.Denominator)
}
if justFirst == false {
return fmt.Sprintf("%v", parts), nil
} else if valueContext.UnitCount > 0 {
return parts[0], nil
} else {
return "", nil
}
} else if tt.Type() == TypeSignedLong {
raw, err := tt.ReadSignedLongValues(valueContext)
log.PanicIf(err)
if justFirst == false {
return fmt.Sprintf("%v", raw), nil
} else if valueContext.UnitCount > 0 {
return fmt.Sprintf("%v", raw[0]), nil
} else {
return "", nil
}
} else if tt.Type() == TypeSignedRational {
raw, err := tt.ReadSignedRationalValues(valueContext)
log.PanicIf(err)
parts := make([]string, len(raw))
for i, r := range raw {
parts[i] = fmt.Sprintf("%d/%d", r.Numerator, r.Denominator)
}
if justFirst == false {
return fmt.Sprintf("%v", raw), nil
} else if valueContext.UnitCount > 0 {
return parts[0], nil
} else {
return "", nil
}
} else {
log.Panicf("value of type (%d) [%s] is unparseable", tt.Type(), tt)
// Never called.
return "", nil
}
}
func (tt TagType) ValueBytes(byteOrder binary.ByteOrder, value interface{}) (encoded []byte, err error) {
defer func() {
if state := recover(); state != nil {
err = log.Wrap(state.(error))
}
}()
ve := NewValueEncoder(byteOrder)
ed, err := ve.EncodeWithType(tt, value)
log.PanicIf(err)
return ed.Encoded, err
}
// UndefinedValue returns the value for a tag of "undefined" type.
func UndefinedValue(ii IfdIdentity, tagId uint16, valueContext ValueContext, byteOrder binary.ByteOrder) (value interface{}, err error) {
defer func() {
if state := recover(); state != nil {
err = log.Wrap(state.(error))
}
}()
typeLogger.Debugf(nil, "UndefinedValue: IFD=[%v] TAG-ID=(0x%02x)", ii, tagId)
if ii == ExifIi {
if tagId == 0x9000 {
// ExifVersion
tt := NewTagType(TypeAsciiNoNul, byteOrder)
valueString, err := tt.ReadAsciiNoNulValue(valueContext)
log.PanicIf(err)
return TagUnknownType_GeneralString(valueString), nil
} else if tagId == 0xa000 {
// FlashpixVersion
tt := NewTagType(TypeAsciiNoNul, byteOrder)
valueString, err := tt.ReadAsciiNoNulValue(valueContext)
log.PanicIf(err)
return TagUnknownType_GeneralString(valueString), nil
} else if tagId == 0x9286 {
// UserComment
tt := NewTagType(TypeByte, byteOrder)
valueBytes, err := tt.ReadByteValues(valueContext)
log.PanicIf(err)
unknownUc := TagUnknownType_9298_UserComment{
EncodingType: TagUnknownType_9298_UserComment_Encoding_UNDEFINED,
EncodingBytes: []byte{},
}
encoding := valueBytes[:8]
for encodingIndex, encodingBytes := range TagUnknownType_9298_UserComment_Encodings {
if bytes.Compare(encoding, encodingBytes) == 0 {
// If unknown, return the default rather than what we have
// because there will be a big list of NULs (which aren't
// functional) and this won't equal the default instance
// (above).
if encodingIndex == TagUnknownType_9298_UserComment_Encoding_UNDEFINED {
return unknownUc, nil
} else {
uc := TagUnknownType_9298_UserComment{
EncodingType: encodingIndex,
EncodingBytes: valueBytes[8:],
}
return uc, nil
}
}
}
typeLogger.Warningf(nil, "User-comment encoding not valid. Returning 'unknown' type (the default).")
return unknownUc, nil
} else if tagId == 0x927c {
// MakerNote
// TODO(dustin): !! This is the Wild Wild West. This very well might be a child IFD, but any and all OEM's define their own formats. If we're going to be writing changes and this is complete EXIF (which may not have the first eight bytes), it might be fine. However, if these are just IFDs they'll be relative to the main EXIF, this will invalidate the MakerNote data for IFDs and any other implementations that use offsets unless we can interpret them all. It be best to return to this later and just exclude this from being written for now, though means a loss of a wealth of image metadata.
// -> We can also just blindly try to interpret as an IFD and just validate that it's looks good (maybe it will even have a 'next ifd' pointer that we can validate is 0x0).
tt := NewTagType(TypeByte, byteOrder)
valueBytes, err := tt.ReadByteValues(valueContext)
log.PanicIf(err)
// TODO(dustin): Doesn't work, but here as an example.
// ie := NewIfdEnumerate(valueBytes, byteOrder)
// // TODO(dustin): !! Validate types (might have proprietary types, but it might be worth splitting the list between valid and not validate; maybe fail if a certain proportion are invalid, or maybe aren't less then a certain small integer)?
// ii, err := ie.Collect(0x0)
// for _, entry := range ii.RootIfd.Entries {
// fmt.Printf("ENTRY: 0x%02x %d\n", entry.TagId, entry.TagType)
// }
mn := TagUnknownType_927C_MakerNote{
MakerNoteType: valueBytes[:20],
// MakerNoteBytes has the whole length of bytes. There's always
// the chance that the first 20 bytes includes actual data.
MakerNoteBytes: valueBytes,
}
return mn, nil
} else if tagId == 0x9101 {
// ComponentsConfiguration
tt := NewTagType(TypeByte, byteOrder)
valueBytes, err := tt.ReadByteValues(valueContext)
log.PanicIf(err)
for configurationId, configurationBytes := range TagUnknownType_9101_ComponentsConfiguration_Configurations {
if bytes.Compare(valueBytes, configurationBytes) == 0 {
cc := TagUnknownType_9101_ComponentsConfiguration{
ConfigurationId: configurationId,
ConfigurationBytes: valueBytes,
}
return cc, nil
}
}
cc := TagUnknownType_9101_ComponentsConfiguration{
ConfigurationId: TagUnknownType_9101_ComponentsConfiguration_OTHER,
ConfigurationBytes: valueBytes,
}
return cc, nil
}
} else if ii == GpsIi {
if tagId == 0x001c {
// GPSAreaInformation
tt := NewTagType(TypeAsciiNoNul, byteOrder)
valueString, err := tt.ReadAsciiNoNulValue(valueContext)
log.PanicIf(err)
return TagUnknownType_GeneralString(valueString), nil
} else if tagId == 0x001b {
// GPSProcessingMethod
tt := NewTagType(TypeAsciiNoNul, byteOrder)
valueString, err := tt.ReadAsciiNoNulValue(valueContext)
log.PanicIf(err)
return TagUnknownType_GeneralString(valueString), nil
}
} else if ii == ExifIopIi {
if tagId == 0x0002 {
// InteropVersion
tt := NewTagType(TypeAsciiNoNul, byteOrder)
valueString, err := tt.ReadAsciiNoNulValue(valueContext)
log.PanicIf(err)
return TagUnknownType_GeneralString(valueString), nil
}
}
// TODO(dustin): !! Still need to do:
//
// complex: 0xa302, 0xa20c, 0x8828
// long: 0xa301, 0xa300
// 0xa40b is device-specific and unhandled.
log.Panic(ErrUnhandledUnknownTypedTag)
return nil, nil
}

View File

@ -1,762 +0,0 @@
package exif
import (
"bytes"
"fmt"
"encoding/binary"
"github.com/dsoprea/go-logging"
)
var (
typeDecodeLogger = log.NewLogger("exif.type_decode")
)
type TagType struct {
tagType uint16
name string
byteOrder binary.ByteOrder
}
func NewTagType(tagType uint16, byteOrder binary.ByteOrder) TagType {
name, found := TypeNames[tagType]
if found == false {
log.Panicf("tag-type not valid: 0x%04x", tagType)
}
return TagType{
tagType: tagType,
name: name,
byteOrder: byteOrder,
}
}
func (tt TagType) String() string {
return fmt.Sprintf("TagType<NAME=[%s]>", tt.name)
}
func (tt TagType) Name() string {
return tt.name
}
func (tt TagType) Type() uint16 {
return tt.tagType
}
func (tt TagType) ByteOrder() binary.ByteOrder {
return tt.byteOrder
}
func (tt TagType) Size() int {
return TagTypeSize(tt.Type())
}
func TagTypeSize(tagType uint16) int {
if tagType == TypeByte {
return 1
} else if tagType == TypeAscii || tagType == TypeAsciiNoNul {
return 1
} else if tagType == TypeShort {
return 2
} else if tagType == TypeLong {
return 4
} else if tagType == TypeRational {
return 8
} else if tagType == TypeSignedLong {
return 4
} else if tagType == TypeSignedRational {
return 8
} else {
log.Panicf("can not determine tag-value size for type (%d): [%s]", tagType, TypeNames[tagType])
// Never called.
return 0
}
}
// ValueIsEmbedded will return a boolean indicating whether the value should be
// found directly within the IFD entry or an offset to somewhere else.
func (tt TagType) ValueIsEmbedded(unitCount uint32) bool {
return (tt.Size() * int(unitCount)) <= 4
}
func (tt TagType) ParseBytes(data []byte, unitCount uint32) (value []uint8, err error) {
defer func() {
if state := recover(); state != nil {
err = log.Wrap(state.(error))
}
}()
if tt.tagType != TypeByte {
log.Panic(ErrWrongType)
}
count := int(unitCount)
if len(data) < (tt.Size() * count) {
log.Panic(ErrNotEnoughData)
}
value = []uint8(data[:count])
return value, nil
}
// ParseAscii returns a string and auto-strips the trailing NUL character.
func (tt TagType) ParseAscii(data []byte, unitCount uint32) (value string, err error) {
defer func() {
if state := recover(); state != nil {
err = log.Wrap(state.(error))
}
}()
if tt.tagType != TypeAscii && tt.tagType != TypeAsciiNoNul {
log.Panic(ErrWrongType)
}
count := int(unitCount)
if len(data) < (tt.Size() * count) {
log.Panic(ErrNotEnoughData)
}
if len(data) == 0 || data[count - 1] != 0 {
typeDecodeLogger.Warningf(nil, "ascii not terminated with nul")
return string(data[:count]), nil
} else {
return string(data[:count - 1]), nil
}
}
// ParseAsciiNoNul returns a string without any consideration for a trailing NUL
// character.
func (tt TagType) ParseAsciiNoNul(data []byte, unitCount uint32) (value string, err error) {
defer func() {
if state := recover(); state != nil {
err = log.Wrap(state.(error))
}
}()
if tt.tagType != TypeAscii && tt.tagType != TypeAsciiNoNul {
log.Panic(ErrWrongType)
}
count := int(unitCount)
if len(data) < (tt.Size() * count) {
log.Panic(ErrNotEnoughData)
}
return string(data[:count]), nil
}
func (tt TagType) ParseShorts(data []byte, unitCount uint32) (value []uint16, err error) {
defer func() {
if state := recover(); state != nil {
err = log.Wrap(state.(error))
}
}()
if tt.tagType != TypeShort {
log.Panic(ErrWrongType)
}
count := int(unitCount)
if len(data) < (tt.Size() * count) {
log.Panic(ErrNotEnoughData)
}
value = make([]uint16, count)
for i := 0; i < count; i++ {
if tt.byteOrder == binary.BigEndian {
value[i] = binary.BigEndian.Uint16(data[i*2:])
} else {
value[i] = binary.LittleEndian.Uint16(data[i*2:])
}
}
return value, nil
}
func (tt TagType) ParseLongs(data []byte, unitCount uint32) (value []uint32, err error) {
defer func() {
if state := recover(); state != nil {
err = log.Wrap(state.(error))
}
}()
if tt.tagType != TypeLong {
log.Panic(ErrWrongType)
}
count := int(unitCount)
if len(data) < (tt.Size() * count) {
log.Panic(ErrNotEnoughData)
}
value = make([]uint32, count)
for i := 0; i < count; i++ {
if tt.byteOrder == binary.BigEndian {
value[i] = binary.BigEndian.Uint32(data[i*4:])
} else {
value[i] = binary.LittleEndian.Uint32(data[i*4:])
}
}
return value, nil
}
func (tt TagType) ParseRationals(data []byte, unitCount uint32) (value []Rational, err error) {
defer func() {
if state := recover(); state != nil {
err = log.Wrap(state.(error))
}
}()
if tt.tagType != TypeRational {
log.Panic(ErrWrongType)
}
count := int(unitCount)
if len(data) < (tt.Size() * count) {
log.Panic(ErrNotEnoughData)
}
value = make([]Rational, count)
for i := 0; i < count; i++ {
if tt.byteOrder == binary.BigEndian {
value[i].Numerator = binary.BigEndian.Uint32(data[i*8:])
value[i].Denominator = binary.BigEndian.Uint32(data[i*8 + 4:])
} else {
value[i].Numerator = binary.LittleEndian.Uint32(data[i*8:])
value[i].Denominator = binary.LittleEndian.Uint32(data[i*8 + 4:])
}
}
return value, nil
}
func (tt TagType) ParseSignedLongs(data []byte, unitCount uint32) (value []int32, err error) {
defer func() {
if state := recover(); state != nil {
err = log.Wrap(state.(error))
}
}()
if tt.tagType != TypeSignedLong {
log.Panic(ErrWrongType)
}
count := int(unitCount)
if len(data) < (tt.Size() * count) {
log.Panic(ErrNotEnoughData)
}
b := bytes.NewBuffer(data)
value = make([]int32, count)
for i := 0; i < count; i++ {
if tt.byteOrder == binary.BigEndian {
err := binary.Read(b, binary.BigEndian, &value[i])
log.PanicIf(err)
} else {
err := binary.Read(b, binary.LittleEndian, &value[i])
log.PanicIf(err)
}
}
return value, nil
}
func (tt TagType) ParseSignedRationals(data []byte, unitCount uint32) (value []SignedRational, err error) {
defer func() {
if state := recover(); state != nil {
err = log.Wrap(state.(error))
}
}()
if tt.tagType != TypeSignedRational {
log.Panic(ErrWrongType)
}
count := int(unitCount)
if len(data) < (tt.Size() * count) {
log.Panic(ErrNotEnoughData)
}
b := bytes.NewBuffer(data)
value = make([]SignedRational, count)
for i := 0; i < count; i++ {
if tt.byteOrder == binary.BigEndian {
err = binary.Read(b, binary.BigEndian, &value[i].Numerator)
log.PanicIf(err)
err = binary.Read(b, binary.BigEndian, &value[i].Denominator)
log.PanicIf(err)
} else {
err = binary.Read(b, binary.LittleEndian, &value[i].Numerator)
log.PanicIf(err)
err = binary.Read(b, binary.LittleEndian, &value[i].Denominator)
log.PanicIf(err)
}
}
return value, nil
}
func (tt TagType) ReadByteValues(valueContext ValueContext) (value []byte, err error) {
defer func() {
if state := recover(); state != nil {
err = log.Wrap(state.(error))
}
}()
if tt.ValueIsEmbedded(valueContext.UnitCount) == true {
typeDecodeLogger.Debugf(nil, "Reading BYTE value (embedded).")
// In this case, the bytes normally used for the offset are actually
// data.
value, err = tt.ParseBytes(valueContext.RawValueOffset, valueContext.UnitCount)
log.PanicIf(err)
} else {
typeDecodeLogger.Debugf(nil, "Reading BYTE value (at offset).")
value, err = tt.ParseBytes(valueContext.AddressableData[valueContext.ValueOffset:], valueContext.UnitCount)
log.PanicIf(err)
}
return value, nil
}
func (tt TagType) ReadAsciiValue(valueContext ValueContext) (value string, err error) {
defer func() {
if state := recover(); state != nil {
err = log.Wrap(state.(error))
}
}()
if tt.ValueIsEmbedded(valueContext.UnitCount) == true {
typeDecodeLogger.Debugf(nil, "Reading ASCII value (no-nul; embedded).")
value, err = tt.ParseAscii(valueContext.RawValueOffset, valueContext.UnitCount)
log.PanicIf(err)
} else {
typeDecodeLogger.Debugf(nil, "Reading ASCII value (no-nul; at offset).")
value, err = tt.ParseAscii(valueContext.AddressableData[valueContext.ValueOffset:], valueContext.UnitCount)
log.PanicIf(err)
}
return value, nil
}
func (tt TagType) ReadAsciiNoNulValue(valueContext ValueContext) (value string, err error) {
defer func() {
if state := recover(); state != nil {
err = log.Wrap(state.(error))
}
}()
if tt.ValueIsEmbedded(valueContext.UnitCount) == true {
typeDecodeLogger.Debugf(nil, "Reading ASCII value (no-nul; embedded).")
value, err = tt.ParseAsciiNoNul(valueContext.RawValueOffset, valueContext.UnitCount)
log.PanicIf(err)
} else {
typeDecodeLogger.Debugf(nil, "Reading ASCII value (no-nul; at offset).")
value, err = tt.ParseAsciiNoNul(valueContext.AddressableData[valueContext.ValueOffset:], valueContext.UnitCount)
log.PanicIf(err)
}
return value, nil
}
func (tt TagType) ReadShortValues(valueContext ValueContext) (value []uint16, err error) {
defer func() {
if state := recover(); state != nil {
err = log.Wrap(state.(error))
}
}()
if tt.ValueIsEmbedded(valueContext.UnitCount) == true {
typeDecodeLogger.Debugf(nil, "Reading SHORT value (embedded).")
value, err = tt.ParseShorts(valueContext.RawValueOffset, valueContext.UnitCount)
log.PanicIf(err)
} else {
typeDecodeLogger.Debugf(nil, "Reading SHORT value (at offset).")
value, err = tt.ParseShorts(valueContext.AddressableData[valueContext.ValueOffset:], valueContext.UnitCount)
log.PanicIf(err)
}
return value, nil
}
func (tt TagType) ReadLongValues(valueContext ValueContext) (value []uint32, err error) {
defer func() {
if state := recover(); state != nil {
err = log.Wrap(state.(error))
}
}()
if tt.ValueIsEmbedded(valueContext.UnitCount) == true {
typeDecodeLogger.Debugf(nil, "Reading LONG value (embedded).")
value, err = tt.ParseLongs(valueContext.RawValueOffset, valueContext.UnitCount)
log.PanicIf(err)
} else {
typeDecodeLogger.Debugf(nil, "Reading LONG value (at offset).")
value, err = tt.ParseLongs(valueContext.AddressableData[valueContext.ValueOffset:], valueContext.UnitCount)
log.PanicIf(err)
}
return value, nil
}
func (tt TagType) ReadRationalValues(valueContext ValueContext) (value []Rational, err error) {
defer func() {
if state := recover(); state != nil {
err = log.Wrap(state.(error))
}
}()
if tt.ValueIsEmbedded(valueContext.UnitCount) == true {
typeDecodeLogger.Debugf(nil, "Reading RATIONAL value (embedded).")
value, err = tt.ParseRationals(valueContext.RawValueOffset, valueContext.UnitCount)
log.PanicIf(err)
} else {
typeDecodeLogger.Debugf(nil, "Reading RATIONAL value (at offset).")
value, err = tt.ParseRationals(valueContext.AddressableData[valueContext.ValueOffset:], valueContext.UnitCount)
log.PanicIf(err)
}
return value, nil
}
func (tt TagType) ReadSignedLongValues(valueContext ValueContext) (value []int32, err error) {
defer func() {
if state := recover(); state != nil {
err = log.Wrap(state.(error))
}
}()
if tt.ValueIsEmbedded(valueContext.UnitCount) == true {
typeDecodeLogger.Debugf(nil, "Reading SLONG value (embedded).")
value, err = tt.ParseSignedLongs(valueContext.RawValueOffset, valueContext.UnitCount)
log.PanicIf(err)
} else {
typeDecodeLogger.Debugf(nil, "Reading SLONG value (at offset).")
value, err = tt.ParseSignedLongs(valueContext.AddressableData[valueContext.ValueOffset:], valueContext.UnitCount)
log.PanicIf(err)
}
return value, nil
}
func (tt TagType) ReadSignedRationalValues(valueContext ValueContext) (value []SignedRational, err error) {
defer func() {
if state := recover(); state != nil {
err = log.Wrap(state.(error))
}
}()
if tt.ValueIsEmbedded(valueContext.UnitCount) == true {
typeDecodeLogger.Debugf(nil, "Reading SRATIONAL value (embedded).")
value, err = tt.ParseSignedRationals(valueContext.RawValueOffset, valueContext.UnitCount)
log.PanicIf(err)
} else {
typeDecodeLogger.Debugf(nil, "Reading SRATIONAL value (at offset).")
value, err = tt.ParseSignedRationals(valueContext.AddressableData[valueContext.ValueOffset:], valueContext.UnitCount)
log.PanicIf(err)
}
return value, nil
}
// ValueString extracts and parses the given value, and returns a flat string.
// Where the type is not ASCII, `justFirst` indicates whether to just stringify
// the first item in the slice (or return an empty string if the slice is
// empty).
func (tt TagType) ValueString(valueContext ValueContext, justFirst bool) (value string, err error) {
defer func() {
if state := recover(); state != nil {
err = log.Wrap(state.(error))
}
}()
if tt.Type() == TypeByte {
raw, err := tt.ReadByteValues(valueContext)
log.PanicIf(err)
if justFirst == false {
return fmt.Sprintf("%v", raw), nil
} else if valueContext.UnitCount > 0 {
return fmt.Sprintf("%v", raw[0]), nil
} else {
return "", nil
}
} else if tt.Type() == TypeAscii {
raw, err := tt.ReadAsciiValue(valueContext)
log.PanicIf(err)
return fmt.Sprintf("%s", raw), nil
} else if tt.Type() == TypeAsciiNoNul {
raw, err := tt.ReadAsciiNoNulValue(valueContext)
log.PanicIf(err)
return fmt.Sprintf("%s", raw), nil
} else if tt.Type() == TypeShort {
raw, err := tt.ReadShortValues(valueContext)
log.PanicIf(err)
if justFirst == false {
return fmt.Sprintf("%v", raw), nil
} else if valueContext.UnitCount > 0 {
return fmt.Sprintf("%v", raw[0]), nil
} else {
return "", nil
}
} else if tt.Type() == TypeLong {
raw, err := tt.ReadLongValues(valueContext)
log.PanicIf(err)
if justFirst == false {
return fmt.Sprintf("%v", raw), nil
} else if valueContext.UnitCount > 0 {
return fmt.Sprintf("%v", raw[0]), nil
} else {
return "", nil
}
} else if tt.Type() == TypeRational {
raw, err := tt.ReadRationalValues(valueContext)
log.PanicIf(err)
parts := make([]string, len(raw))
for i, r := range raw {
parts[i] = fmt.Sprintf("%d/%d", r.Numerator, r.Denominator)
}
if justFirst == false {
return fmt.Sprintf("%v", parts), nil
} else if valueContext.UnitCount > 0 {
return parts[0], nil
} else {
return "", nil
}
} else if tt.Type() == TypeSignedLong {
raw, err := tt.ReadSignedLongValues(valueContext)
log.PanicIf(err)
if justFirst == false {
return fmt.Sprintf("%v", raw), nil
} else if valueContext.UnitCount > 0 {
return fmt.Sprintf("%v", raw[0]), nil
} else {
return "", nil
}
} else if tt.Type() == TypeSignedRational {
raw, err := tt.ReadSignedRationalValues(valueContext)
log.PanicIf(err)
parts := make([]string, len(raw))
for i, r := range raw {
parts[i] = fmt.Sprintf("%d/%d", r.Numerator, r.Denominator)
}
if justFirst == false {
return fmt.Sprintf("%v", raw), nil
} else if valueContext.UnitCount > 0 {
return parts[0], nil
} else {
return "", nil
}
} else {
log.Panicf("value of type (%d) [%s] is unparseable", tt.Type(), tt)
// Never called.
return "", nil
}
}
// UndefinedValue returns the value for a tag of "undefined" type.
func UndefinedValue(indexedIfdName string, tagId uint16, valueContext ValueContext, byteOrder binary.ByteOrder) (value interface{}, err error) {
defer func() {
if state := recover(); state != nil {
err = log.Wrap(state.(error))
}
}()
typeDecodeLogger.Debugf(nil, "UndefinedValue: IFD=[%s] TAG-ID=(0x%02x)", indexedIfdName, tagId)
if indexedIfdName == IfdName(IfdExif, 0) {
if tagId == 0x9000 {
// ExifVersion
tt := NewTagType(TypeAsciiNoNul, byteOrder)
valueString, err := tt.ReadAsciiNoNulValue(valueContext)
log.PanicIf(err)
return TagUnknownType_GeneralString(valueString), nil
} else if tagId == 0xa000 {
// FlashpixVersion
tt := NewTagType(TypeAsciiNoNul, byteOrder)
valueString, err := tt.ReadAsciiNoNulValue(valueContext)
log.PanicIf(err)
return TagUnknownType_GeneralString(valueString), nil
} else if tagId == 0x9286 {
// UserComment
tt := NewTagType(TypeByte, byteOrder)
valueBytes, err := tt.ReadByteValues(valueContext)
log.PanicIf(err)
unknownUc := TagUnknownType_9298_UserComment{
EncodingType: TagUnknownType_9298_UserComment_Encoding_UNDEFINED,
EncodingBytes: []byte{},
}
encoding := valueBytes[:8]
for encodingIndex, encodingBytes := range TagUnknownType_9298_UserComment_Encodings {
if bytes.Compare(encoding, encodingBytes) == 0 {
// If unknown, return the default rather than what we have
// because there will be a big list of NULs (which aren't
// functional) and this won't equal the default instance
// (above).
if encodingIndex == TagUnknownType_9298_UserComment_Encoding_UNDEFINED {
return unknownUc, nil
} else {
uc := TagUnknownType_9298_UserComment{
EncodingType: encodingIndex,
EncodingBytes: valueBytes[8:],
}
return uc, nil
}
}
}
typeDecodeLogger.Warningf(nil, "User-comment encoding not valid. Returning 'unknown' type (the default).")
return unknownUc, nil
} else if tagId == 0x927c {
// MakerNote
// TODO(dustin): !! This is the Wild Wild West. This very well might be a child IFD, but any and all OEM's define their own formats. If we're going to be writing changes and this is complete EXIF (which may not have the first eight bytes), it might be fine. However, if these are just IFDs they'll be relative to the main EXIF, this will invalidate the MakerNote data for IFDs and any other implementations that use offsets unless we can interpret them all. It be best to return to this later and just exclude this from being written for now, though means a loss of a wealth of image metadata.
// -> We can also just blindly try to interpret as an IFD and just validate that it's looks good (maybe it will even have a 'next ifd' pointer that we can validate is 0x0).
tt := NewTagType(TypeByte, byteOrder)
valueBytes, err := tt.ReadByteValues(valueContext)
log.PanicIf(err)
// TODO(dustin): Doesn't work, but here as an example.
// ie := NewIfdEnumerate(valueBytes, byteOrder)
// // TODO(dustin): !! Validate types (might have proprietary types, but it might be worth splitting the list between valid and not validate; maybe fail if a certain proportion are invalid, or maybe aren't less then a certain small integer)?
// ii, err := ie.Collect(0x0)
// for _, entry := range ii.RootIfd.Entries {
// fmt.Printf("ENTRY: 0x%02x %d\n", entry.TagId, entry.TagType)
// }
mn := TagUnknownType_927C_MakerNote{
MakerNoteType: valueBytes[:20],
// MakerNoteBytes has the whole length of bytes. There's always
// the chance that the first 20 bytes includes actual data.
MakerNoteBytes: valueBytes,
}
return mn, nil
} else if tagId == 0x9101 {
// ComponentsConfiguration
tt := NewTagType(TypeByte, byteOrder)
valueBytes, err := tt.ReadByteValues(valueContext)
log.PanicIf(err)
for configurationId, configurationBytes := range TagUnknownType_9101_ComponentsConfiguration_Configurations {
if bytes.Compare(valueBytes, configurationBytes) == 0 {
cc := TagUnknownType_9101_ComponentsConfiguration{
ConfigurationId: configurationId,
ConfigurationBytes: valueBytes,
}
return cc, nil
}
}
cc := TagUnknownType_9101_ComponentsConfiguration{
ConfigurationId: TagUnknownType_9101_ComponentsConfiguration_OTHER,
ConfigurationBytes: valueBytes,
}
return cc, nil
}
} else if indexedIfdName == IfdName(IfdGps, 0) {
if tagId == 0x001c {
// GPSAreaInformation
tt := NewTagType(TypeAsciiNoNul, byteOrder)
valueString, err := tt.ReadAsciiNoNulValue(valueContext)
log.PanicIf(err)
return TagUnknownType_GeneralString(valueString), nil
} else if tagId == 0x001b {
// GPSProcessingMethod
tt := NewTagType(TypeAsciiNoNul, byteOrder)
valueString, err := tt.ReadAsciiNoNulValue(valueContext)
log.PanicIf(err)
return TagUnknownType_GeneralString(valueString), nil
}
} else if indexedIfdName == IfdName(IfdIop, 0) {
if tagId == 0x0002 {
// InteropVersion
tt := NewTagType(TypeAsciiNoNul, byteOrder)
valueString, err := tt.ReadAsciiNoNulValue(valueContext)
log.PanicIf(err)
return TagUnknownType_GeneralString(valueString), nil
}
}
// TODO(dustin): !! Still need to do:
//
// complex: 0xa302, 0xa20c, 0x8828
// long: 0xa301, 0xa300
// 0xa40b is device-specific and unhandled.
log.Panic(ErrUnhandledUnknownTypedTag)
return nil, nil
}

View File

@ -239,3 +239,39 @@ func (ve *ValueEncoder) Encode(value interface{}) (ed EncodedData, err error) {
return ed, nil
}
func (ve *ValueEncoder) EncodeWithType(tt TagType, value interface{}) (ed EncodedData, err error) {
defer func() {
if state := recover(); state != nil {
err = log.Wrap(state.(error))
}
}()
switch tt.Type() {
case TypeByte:
ed, err = ve.encodeBytes(value.([]byte))
log.PanicIf(err)
case TypeAscii:
ed, err = ve.encodeAscii(value.(string))
log.PanicIf(err)
case TypeShort:
ed, err = ve.encodeShorts(value.([]uint16))
log.PanicIf(err)
case TypeLong:
ed, err = ve.encodeLongs(value.([]uint32))
log.PanicIf(err)
case TypeRational:
ed, err = ve.encodeRationals(value.([]Rational))
log.PanicIf(err)
case TypeSignedLong:
ed, err = ve.encodeSignedLongs(value.([]int32))
log.PanicIf(err)
case TypeSignedRational:
ed, err = ve.encodeSignedRationals(value.([]SignedRational))
log.PanicIf(err)
default:
log.Panicf("value not encodable (with type): %v [%v]", tt, value)
}
return ed, nil
}

View File

@ -4,13 +4,11 @@ import (
"testing"
"reflect"
"encoding/binary"
"github.com/dsoprea/go-logging"
)
func TestByteCycle(t *testing.T) {
byteOrder := binary.BigEndian
byteOrder := TestDefaultByteOrder
ve := NewValueEncoder(byteOrder)
original := []byte("original text")
@ -39,7 +37,7 @@ func TestByteCycle(t *testing.T) {
}
func TestAsciiCycle(t *testing.T) {
byteOrder := binary.BigEndian
byteOrder := TestDefaultByteOrder
ve := NewValueEncoder(byteOrder)
original := "original text"
@ -72,7 +70,7 @@ func TestAsciiCycle(t *testing.T) {
}
func TestAsciiNoNulCycle(t *testing.T) {
byteOrder := binary.BigEndian
byteOrder := TestDefaultByteOrder
ve := NewValueEncoder(byteOrder)
original := "original text"
@ -104,7 +102,7 @@ func TestAsciiNoNulCycle(t *testing.T) {
}
func TestShortCycle(t *testing.T) {
byteOrder := binary.BigEndian
byteOrder := TestDefaultByteOrder
ve := NewValueEncoder(byteOrder)
original := []uint16 { 0x11, 0x22, 0x33, 0x44, 0x55 }
@ -139,7 +137,7 @@ func TestShortCycle(t *testing.T) {
}
func TestLongCycle(t *testing.T) {
byteOrder := binary.BigEndian
byteOrder := TestDefaultByteOrder
ve := NewValueEncoder(byteOrder)
original := []uint32 { 0x11, 0x22, 0x33, 0x44, 0x55 }
@ -174,7 +172,7 @@ func TestLongCycle(t *testing.T) {
}
func TestRationalCycle(t *testing.T) {
byteOrder := binary.BigEndian
byteOrder := TestDefaultByteOrder
ve := NewValueEncoder(byteOrder)
original := []Rational {
@ -235,7 +233,7 @@ func TestRationalCycle(t *testing.T) {
}
func TestSignedLongCycle(t *testing.T) {
byteOrder := binary.BigEndian
byteOrder := TestDefaultByteOrder
ve := NewValueEncoder(byteOrder)
original := []int32 { 0x11, 0x22, 0x33, 0x44, 0x55 }
@ -270,7 +268,7 @@ func TestSignedLongCycle(t *testing.T) {
}
func TestSignedRationalCycle(t *testing.T) {
byteOrder := binary.BigEndian
byteOrder := TestDefaultByteOrder
ve := NewValueEncoder(byteOrder)
original := []SignedRational {
@ -330,20 +328,8 @@ func TestSignedRationalCycle(t *testing.T) {
}
}
func TestEncode_Byte(t *testing.T) {
byteOrder := binary.BigEndian
byteOrder := TestDefaultByteOrder
ve := NewValueEncoder(byteOrder)
original := []byte("original text")
@ -365,7 +351,7 @@ func TestEncode_Byte(t *testing.T) {
}
func TestEncode_Ascii(t *testing.T) {
byteOrder := binary.BigEndian
byteOrder := TestDefaultByteOrder
ve := NewValueEncoder(byteOrder)
original := "original text"
@ -388,7 +374,7 @@ func TestEncode_Ascii(t *testing.T) {
}
func TestEncode_Short(t *testing.T) {
byteOrder := binary.BigEndian
byteOrder := TestDefaultByteOrder
ve := NewValueEncoder(byteOrder)
original := []uint16 { 0x11, 0x22, 0x33, 0x44, 0x55 }
@ -416,7 +402,7 @@ func TestEncode_Short(t *testing.T) {
}
func TestEncode_Long(t *testing.T) {
byteOrder := binary.BigEndian
byteOrder := TestDefaultByteOrder
ve := NewValueEncoder(byteOrder)
original := []uint32 { 0x11, 0x22, 0x33, 0x44, 0x55 }
@ -444,7 +430,7 @@ func TestEncode_Long(t *testing.T) {
}
func TestEncode_Rational(t *testing.T) {
byteOrder := binary.BigEndian
byteOrder := TestDefaultByteOrder
ve := NewValueEncoder(byteOrder)
original := []Rational {
@ -498,7 +484,7 @@ func TestEncode_Rational(t *testing.T) {
}
func TestEncode_SignedLong(t *testing.T) {
byteOrder := binary.BigEndian
byteOrder := TestDefaultByteOrder
ve := NewValueEncoder(byteOrder)
original := []int32 { 0x11, 0x22, 0x33, 0x44, 0x55 }
@ -526,7 +512,7 @@ func TestEncode_SignedLong(t *testing.T) {
}
func TestEncode_SignedRational(t *testing.T) {
byteOrder := binary.BigEndian
byteOrder := TestDefaultByteOrder
ve := NewValueEncoder(byteOrder)
original := []SignedRational {