gtsocial-umbx

Unnamed repository; edit this file 'description' to name the repository.
Log | Files | Refs | README | LICENSE

ifd_builder_encode.go (15437B)


      1 package exif
      2 
      3 import (
      4 	"bytes"
      5 	"fmt"
      6 	"strings"
      7 
      8 	"encoding/binary"
      9 
     10 	"github.com/dsoprea/go-logging"
     11 
     12 	"github.com/dsoprea/go-exif/v3/common"
     13 )
     14 
     15 const (
     16 	// Tag-ID + Tag-Type + Unit-Count + Value/Offset.
     17 	IfdTagEntrySize = uint32(2 + 2 + 4 + 4)
     18 )
     19 
     20 type ByteWriter struct {
     21 	b         *bytes.Buffer
     22 	byteOrder binary.ByteOrder
     23 }
     24 
     25 func NewByteWriter(b *bytes.Buffer, byteOrder binary.ByteOrder) (bw *ByteWriter) {
     26 	return &ByteWriter{
     27 		b:         b,
     28 		byteOrder: byteOrder,
     29 	}
     30 }
     31 
     32 func (bw ByteWriter) writeAsBytes(value interface{}) (err error) {
     33 	defer func() {
     34 		if state := recover(); state != nil {
     35 			err = log.Wrap(state.(error))
     36 		}
     37 	}()
     38 
     39 	err = binary.Write(bw.b, bw.byteOrder, value)
     40 	log.PanicIf(err)
     41 
     42 	return nil
     43 }
     44 
     45 func (bw ByteWriter) WriteUint32(value uint32) (err error) {
     46 	defer func() {
     47 		if state := recover(); state != nil {
     48 			err = log.Wrap(state.(error))
     49 		}
     50 	}()
     51 
     52 	err = bw.writeAsBytes(value)
     53 	log.PanicIf(err)
     54 
     55 	return nil
     56 }
     57 
     58 func (bw ByteWriter) WriteUint16(value uint16) (err error) {
     59 	defer func() {
     60 		if state := recover(); state != nil {
     61 			err = log.Wrap(state.(error))
     62 		}
     63 	}()
     64 
     65 	err = bw.writeAsBytes(value)
     66 	log.PanicIf(err)
     67 
     68 	return nil
     69 }
     70 
     71 func (bw ByteWriter) WriteFourBytes(value []byte) (err error) {
     72 	defer func() {
     73 		if state := recover(); state != nil {
     74 			err = log.Wrap(state.(error))
     75 		}
     76 	}()
     77 
     78 	len_ := len(value)
     79 	if len_ != 4 {
     80 		log.Panicf("value is not four-bytes: (%d)", len_)
     81 	}
     82 
     83 	_, err = bw.b.Write(value)
     84 	log.PanicIf(err)
     85 
     86 	return nil
     87 }
     88 
     89 // ifdOffsetIterator keeps track of where the next IFD should be written by
     90 // keeping track of where the offsets start, the data that has been added, and
     91 // bumping the offset *when* the data is added.
     92 type ifdDataAllocator struct {
     93 	offset uint32
     94 	b      bytes.Buffer
     95 }
     96 
     97 func newIfdDataAllocator(ifdDataAddressableOffset uint32) *ifdDataAllocator {
     98 	return &ifdDataAllocator{
     99 		offset: ifdDataAddressableOffset,
    100 	}
    101 }
    102 
    103 func (ida *ifdDataAllocator) Allocate(value []byte) (offset uint32, err error) {
    104 	_, err = ida.b.Write(value)
    105 	log.PanicIf(err)
    106 
    107 	offset = ida.offset
    108 	ida.offset += uint32(len(value))
    109 
    110 	return offset, nil
    111 }
    112 
    113 func (ida *ifdDataAllocator) NextOffset() uint32 {
    114 	return ida.offset
    115 }
    116 
    117 func (ida *ifdDataAllocator) Bytes() []byte {
    118 	return ida.b.Bytes()
    119 }
    120 
    121 // IfdByteEncoder converts an IB to raw bytes (for writing) while also figuring
    122 // out all of the allocations and indirection that is required for extended
    123 // data.
    124 type IfdByteEncoder struct {
    125 	// journal holds a list of actions taken while encoding.
    126 	journal [][3]string
    127 }
    128 
    129 func NewIfdByteEncoder() (ibe *IfdByteEncoder) {
    130 	return &IfdByteEncoder{
    131 		journal: make([][3]string, 0),
    132 	}
    133 }
    134 
    135 func (ibe *IfdByteEncoder) Journal() [][3]string {
    136 	return ibe.journal
    137 }
    138 
    139 func (ibe *IfdByteEncoder) TableSize(entryCount int) uint32 {
    140 	// Tag-Count + (Entry-Size * Entry-Count) + Next-IFD-Offset.
    141 	return uint32(2) + (IfdTagEntrySize * uint32(entryCount)) + uint32(4)
    142 }
    143 
    144 func (ibe *IfdByteEncoder) pushToJournal(where, direction, format string, args ...interface{}) {
    145 	event := [3]string{
    146 		direction,
    147 		where,
    148 		fmt.Sprintf(format, args...),
    149 	}
    150 
    151 	ibe.journal = append(ibe.journal, event)
    152 }
    153 
    154 // PrintJournal prints a hierarchical representation of the steps taken during
    155 // encoding.
    156 func (ibe *IfdByteEncoder) PrintJournal() {
    157 	maxWhereLength := 0
    158 	for _, event := range ibe.journal {
    159 		where := event[1]
    160 
    161 		len_ := len(where)
    162 		if len_ > maxWhereLength {
    163 			maxWhereLength = len_
    164 		}
    165 	}
    166 
    167 	level := 0
    168 	for i, event := range ibe.journal {
    169 		direction := event[0]
    170 		where := event[1]
    171 		message := event[2]
    172 
    173 		if direction != ">" && direction != "<" && direction != "-" {
    174 			log.Panicf("journal operation not valid: [%s]", direction)
    175 		}
    176 
    177 		if direction == "<" {
    178 			if level <= 0 {
    179 				log.Panicf("journal operations unbalanced (too many closes)")
    180 			}
    181 
    182 			level--
    183 		}
    184 
    185 		indent := strings.Repeat("  ", level)
    186 
    187 		fmt.Printf("%3d %s%s %s: %s\n", i, indent, direction, where, message)
    188 
    189 		if direction == ">" {
    190 			level++
    191 		}
    192 	}
    193 
    194 	if level != 0 {
    195 		log.Panicf("journal operations unbalanced (too many opens)")
    196 	}
    197 }
    198 
    199 // encodeTagToBytes encodes the given tag to a byte stream. If
    200 // `nextIfdOffsetToWrite` is more than (0), recurse into child IFDs
    201 // (`nextIfdOffsetToWrite` is required in order for them to know where the its
    202 // IFD data will be written, in order for them to know the offset of where
    203 // their allocated-data block will start, which follows right behind).
    204 func (ibe *IfdByteEncoder) encodeTagToBytes(ib *IfdBuilder, bt *BuilderTag, bw *ByteWriter, ida *ifdDataAllocator, nextIfdOffsetToWrite uint32) (childIfdBlock []byte, err error) {
    205 	defer func() {
    206 		if state := recover(); state != nil {
    207 			err = log.Wrap(state.(error))
    208 		}
    209 	}()
    210 
    211 	// Write tag-ID.
    212 	err = bw.WriteUint16(bt.tagId)
    213 	log.PanicIf(err)
    214 
    215 	// Works for both values and child IFDs (which have an official size of
    216 	// LONG).
    217 	err = bw.WriteUint16(uint16(bt.typeId))
    218 	log.PanicIf(err)
    219 
    220 	// Write unit-count.
    221 
    222 	if bt.value.IsBytes() == true {
    223 		effectiveType := bt.typeId
    224 		if bt.typeId == exifcommon.TypeUndefined {
    225 			effectiveType = exifcommon.TypeByte
    226 		}
    227 
    228 		// It's a non-unknown value.Calculate the count of values of
    229 		// the type that we're writing and the raw bytes for the whole list.
    230 
    231 		typeSize := uint32(effectiveType.Size())
    232 
    233 		valueBytes := bt.value.Bytes()
    234 
    235 		len_ := len(valueBytes)
    236 		unitCount := uint32(len_) / typeSize
    237 
    238 		if _, found := tagsWithoutAlignment[bt.tagId]; found == false {
    239 			remainder := uint32(len_) % typeSize
    240 
    241 			if remainder > 0 {
    242 				log.Panicf("tag (0x%04x) value of (%d) bytes not evenly divisible by type-size (%d)", bt.tagId, len_, typeSize)
    243 			}
    244 		}
    245 
    246 		err = bw.WriteUint32(unitCount)
    247 		log.PanicIf(err)
    248 
    249 		// Write four-byte value/offset.
    250 
    251 		if len_ > 4 {
    252 			offset, err := ida.Allocate(valueBytes)
    253 			log.PanicIf(err)
    254 
    255 			err = bw.WriteUint32(offset)
    256 			log.PanicIf(err)
    257 		} else {
    258 			fourBytes := make([]byte, 4)
    259 			copy(fourBytes, valueBytes)
    260 
    261 			err = bw.WriteFourBytes(fourBytes)
    262 			log.PanicIf(err)
    263 		}
    264 	} else {
    265 		if bt.value.IsIb() == false {
    266 			log.Panicf("tag value is not a byte-slice but also not a child IB: %v", bt)
    267 		}
    268 
    269 		// Write unit-count (one LONG representing one offset).
    270 		err = bw.WriteUint32(1)
    271 		log.PanicIf(err)
    272 
    273 		if nextIfdOffsetToWrite > 0 {
    274 			var err error
    275 
    276 			ibe.pushToJournal("encodeTagToBytes", ">", "[%s]->[%s]", ib.IfdIdentity().UnindexedString(), bt.value.Ib().IfdIdentity().UnindexedString())
    277 
    278 			// Create the block of IFD data and everything it requires.
    279 			childIfdBlock, err = ibe.encodeAndAttachIfd(bt.value.Ib(), nextIfdOffsetToWrite)
    280 			log.PanicIf(err)
    281 
    282 			ibe.pushToJournal("encodeTagToBytes", "<", "[%s]->[%s]", bt.value.Ib().IfdIdentity().UnindexedString(), ib.IfdIdentity().UnindexedString())
    283 
    284 			// Use the next-IFD offset for it. The IFD will actually get
    285 			// attached after we return.
    286 			err = bw.WriteUint32(nextIfdOffsetToWrite)
    287 			log.PanicIf(err)
    288 
    289 		} else {
    290 			// No child-IFDs are to be allocated. Finish the entry with a NULL
    291 			// pointer.
    292 
    293 			ibe.pushToJournal("encodeTagToBytes", "-", "*Not* descending to child: [%s]", bt.value.Ib().IfdIdentity().UnindexedString())
    294 
    295 			err = bw.WriteUint32(0)
    296 			log.PanicIf(err)
    297 		}
    298 	}
    299 
    300 	return childIfdBlock, nil
    301 }
    302 
    303 // encodeIfdToBytes encodes the given IB to a byte-slice. We are given the
    304 // offset at which this IFD will be written. This method is used called both to
    305 // pre-determine how big the table is going to be (so that we can calculate the
    306 // address to allocate data at) as well as to write the final table.
    307 //
    308 // It is necessary to fully realize the table in order to predetermine its size
    309 // because it is not enough to know the size of the table: If there are child
    310 // IFDs, we will not be able to allocate them without first knowing how much
    311 // data we need to allocate for the current IFD.
    312 func (ibe *IfdByteEncoder) encodeIfdToBytes(ib *IfdBuilder, ifdAddressableOffset uint32, nextIfdOffsetToWrite uint32, setNextIb bool) (data []byte, tableSize uint32, dataSize uint32, childIfdSizes []uint32, err error) {
    313 	defer func() {
    314 		if state := recover(); state != nil {
    315 			err = log.Wrap(state.(error))
    316 		}
    317 	}()
    318 
    319 	ibe.pushToJournal("encodeIfdToBytes", ">", "%s", ib)
    320 
    321 	tableSize = ibe.TableSize(len(ib.tags))
    322 
    323 	b := new(bytes.Buffer)
    324 	bw := NewByteWriter(b, ib.byteOrder)
    325 
    326 	// Write tag count.
    327 	err = bw.WriteUint16(uint16(len(ib.tags)))
    328 	log.PanicIf(err)
    329 
    330 	ida := newIfdDataAllocator(ifdAddressableOffset)
    331 
    332 	childIfdBlocks := make([][]byte, 0)
    333 
    334 	// Write raw bytes for each tag entry. Allocate larger data to be referred
    335 	// to in the follow-up data-block as required. Any "unknown"-byte tags that
    336 	// we can't parse will not be present here (using AddTagsFromExisting(), at
    337 	// least).
    338 	for _, bt := range ib.tags {
    339 		childIfdBlock, err := ibe.encodeTagToBytes(ib, bt, bw, ida, nextIfdOffsetToWrite)
    340 		log.PanicIf(err)
    341 
    342 		if childIfdBlock != nil {
    343 			// We aren't allowed to have non-nil child IFDs if we're just
    344 			// sizing things up.
    345 			if nextIfdOffsetToWrite == 0 {
    346 				log.Panicf("no IFD offset provided for child-IFDs; no new child-IFDs permitted")
    347 			}
    348 
    349 			nextIfdOffsetToWrite += uint32(len(childIfdBlock))
    350 			childIfdBlocks = append(childIfdBlocks, childIfdBlock)
    351 		}
    352 	}
    353 
    354 	dataBytes := ida.Bytes()
    355 	dataSize = uint32(len(dataBytes))
    356 
    357 	childIfdSizes = make([]uint32, len(childIfdBlocks))
    358 	childIfdsTotalSize := uint32(0)
    359 	for i, childIfdBlock := range childIfdBlocks {
    360 		len_ := uint32(len(childIfdBlock))
    361 		childIfdSizes[i] = len_
    362 		childIfdsTotalSize += len_
    363 	}
    364 
    365 	// N the link from this IFD to the next IFD that will be written in the
    366 	// next cycle.
    367 	if setNextIb == true {
    368 		// Write address of next IFD in chain. This will be the original
    369 		// allocation offset plus the size of everything we have allocated for
    370 		// this IFD and its child-IFDs.
    371 		//
    372 		// It is critical that this number is stepped properly. We experienced
    373 		// an issue whereby it first looked like we were duplicating the IFD and
    374 		// then that we were duplicating the tags in the wrong IFD, and then
    375 		// finally we determined that the next-IFD offset for the first IFD was
    376 		// accidentally pointing back to the EXIF IFD, so we were visiting it
    377 		// twice when visiting through the tags after decoding. It was an
    378 		// expensive bug to find.
    379 
    380 		ibe.pushToJournal("encodeIfdToBytes", "-", "Setting 'next' IFD to (0x%08x).", nextIfdOffsetToWrite)
    381 
    382 		err := bw.WriteUint32(nextIfdOffsetToWrite)
    383 		log.PanicIf(err)
    384 	} else {
    385 		err := bw.WriteUint32(0)
    386 		log.PanicIf(err)
    387 	}
    388 
    389 	_, err = b.Write(dataBytes)
    390 	log.PanicIf(err)
    391 
    392 	// Append any child IFD blocks after our table and data blocks. These IFDs
    393 	// were equipped with the appropriate offset information so it's expected
    394 	// that all offsets referred to by these will be correct.
    395 	//
    396 	// Note that child-IFDs are append after the current IFD and before the
    397 	// next IFD, as opposed to the root IFDs, which are chained together but
    398 	// will be interrupted by these child-IFDs (which is expected, per the
    399 	// standard).
    400 
    401 	for _, childIfdBlock := range childIfdBlocks {
    402 		_, err = b.Write(childIfdBlock)
    403 		log.PanicIf(err)
    404 	}
    405 
    406 	ibe.pushToJournal("encodeIfdToBytes", "<", "%s", ib)
    407 
    408 	return b.Bytes(), tableSize, dataSize, childIfdSizes, nil
    409 }
    410 
    411 // encodeAndAttachIfd is a reentrant function that processes the IFD chain.
    412 func (ibe *IfdByteEncoder) encodeAndAttachIfd(ib *IfdBuilder, ifdAddressableOffset uint32) (data []byte, err error) {
    413 	defer func() {
    414 		if state := recover(); state != nil {
    415 			err = log.Wrap(state.(error))
    416 		}
    417 	}()
    418 
    419 	ibe.pushToJournal("encodeAndAttachIfd", ">", "%s", ib)
    420 
    421 	b := new(bytes.Buffer)
    422 
    423 	i := 0
    424 
    425 	for thisIb := ib; thisIb != nil; thisIb = thisIb.nextIb {
    426 
    427 		// Do a dry-run in order to pre-determine its size requirement.
    428 
    429 		ibe.pushToJournal("encodeAndAttachIfd", ">", "Beginning encoding process: (%d) [%s]", i, thisIb.IfdIdentity().UnindexedString())
    430 
    431 		ibe.pushToJournal("encodeAndAttachIfd", ">", "Calculating size: (%d) [%s]", i, thisIb.IfdIdentity().UnindexedString())
    432 
    433 		_, tableSize, allocatedDataSize, _, err := ibe.encodeIfdToBytes(thisIb, ifdAddressableOffset, 0, false)
    434 		log.PanicIf(err)
    435 
    436 		ibe.pushToJournal("encodeAndAttachIfd", "<", "Finished calculating size: (%d) [%s]", i, thisIb.IfdIdentity().UnindexedString())
    437 
    438 		ifdAddressableOffset += tableSize
    439 		nextIfdOffsetToWrite := ifdAddressableOffset + allocatedDataSize
    440 
    441 		ibe.pushToJournal("encodeAndAttachIfd", ">", "Next IFD will be written at offset (0x%08x)", nextIfdOffsetToWrite)
    442 
    443 		// Write our IFD as well as any child-IFDs (now that we know the offset
    444 		// where new IFDs and their data will be allocated).
    445 
    446 		setNextIb := thisIb.nextIb != nil
    447 
    448 		ibe.pushToJournal("encodeAndAttachIfd", ">", "Encoding starting: (%d) [%s] NEXT-IFD-OFFSET-TO-WRITE=(0x%08x)", i, thisIb.IfdIdentity().UnindexedString(), nextIfdOffsetToWrite)
    449 
    450 		tableAndAllocated, effectiveTableSize, effectiveAllocatedDataSize, childIfdSizes, err :=
    451 			ibe.encodeIfdToBytes(thisIb, ifdAddressableOffset, nextIfdOffsetToWrite, setNextIb)
    452 
    453 		log.PanicIf(err)
    454 
    455 		if effectiveTableSize != tableSize {
    456 			log.Panicf("written table size does not match the pre-calculated table size: (%d) != (%d) %s", effectiveTableSize, tableSize, ib)
    457 		} else if effectiveAllocatedDataSize != allocatedDataSize {
    458 			log.Panicf("written allocated-data size does not match the pre-calculated allocated-data size: (%d) != (%d) %s", effectiveAllocatedDataSize, allocatedDataSize, ib)
    459 		}
    460 
    461 		ibe.pushToJournal("encodeAndAttachIfd", "<", "Encoding done: (%d) [%s]", i, thisIb.IfdIdentity().UnindexedString())
    462 
    463 		totalChildIfdSize := uint32(0)
    464 		for _, childIfdSize := range childIfdSizes {
    465 			totalChildIfdSize += childIfdSize
    466 		}
    467 
    468 		if len(tableAndAllocated) != int(tableSize+allocatedDataSize+totalChildIfdSize) {
    469 			log.Panicf("IFD table and data is not a consistent size: (%d) != (%d)", len(tableAndAllocated), tableSize+allocatedDataSize+totalChildIfdSize)
    470 		}
    471 
    472 		// TODO(dustin): We might want to verify the original tableAndAllocated length, too.
    473 
    474 		_, err = b.Write(tableAndAllocated)
    475 		log.PanicIf(err)
    476 
    477 		// Advance past what we've allocated, thus far.
    478 
    479 		ifdAddressableOffset += allocatedDataSize + totalChildIfdSize
    480 
    481 		ibe.pushToJournal("encodeAndAttachIfd", "<", "Finishing encoding process: (%d) [%s] [FINAL:] NEXT-IFD-OFFSET-TO-WRITE=(0x%08x)", i, ib.IfdIdentity().UnindexedString(), nextIfdOffsetToWrite)
    482 
    483 		i++
    484 	}
    485 
    486 	ibe.pushToJournal("encodeAndAttachIfd", "<", "%s", ib)
    487 
    488 	return b.Bytes(), nil
    489 }
    490 
    491 // EncodeToExifPayload is the base encoding step that transcribes the entire IB
    492 // structure to its on-disk layout.
    493 func (ibe *IfdByteEncoder) EncodeToExifPayload(ib *IfdBuilder) (data []byte, err error) {
    494 	defer func() {
    495 		if state := recover(); state != nil {
    496 			err = log.Wrap(state.(error))
    497 		}
    498 	}()
    499 
    500 	data, err = ibe.encodeAndAttachIfd(ib, ExifDefaultFirstIfdOffset)
    501 	log.PanicIf(err)
    502 
    503 	return data, nil
    504 }
    505 
    506 // EncodeToExif calls EncodeToExifPayload and then packages the result into a
    507 // complete EXIF block.
    508 func (ibe *IfdByteEncoder) EncodeToExif(ib *IfdBuilder) (data []byte, err error) {
    509 	defer func() {
    510 		if state := recover(); state != nil {
    511 			err = log.Wrap(state.(error))
    512 		}
    513 	}()
    514 
    515 	encodedIfds, err := ibe.EncodeToExifPayload(ib)
    516 	log.PanicIf(err)
    517 
    518 	// Wrap the IFD in a formal EXIF block.
    519 
    520 	b := new(bytes.Buffer)
    521 
    522 	headerBytes, err := BuildExifHeader(ib.byteOrder, ExifDefaultFirstIfdOffset)
    523 	log.PanicIf(err)
    524 
    525 	_, err = b.Write(headerBytes)
    526 	log.PanicIf(err)
    527 
    528 	_, err = b.Write(encodedIfds)
    529 	log.PanicIf(err)
    530 
    531 	return b.Bytes(), nil
    532 }