Skip to main content

rawshift_image/tiff/
parser.rs

1//! TIFF IFD parser and navigation.
2//!
3//! This module provides the core TIFF parsing functionality:
4//! - Header parsing (byte order, magic number, IFD0 offset)
5//! - IFD entry parsing and navigation
6//! - SubIFD tree traversal
7//! - Value resolution (inline vs offset)
8
9use binrw::{BinRead, BinReaderExt, binread};
10use std::collections::{HashMap, HashSet};
11use std::io::{Read, Seek, SeekFrom};
12
13use crate::error::{ParseError, RawError, RawResult};
14use crate::tiff::tags::TiffTag;
15use crate::tiff::types::{ByteOrder, Rational, SRational, TiffType, TiffValue};
16
17/// Standard TIFF magic number (42).
18pub const TIFF_MAGIC: u16 = 42;
19/// BigTIFF magic number (43).
20pub const BIGTIFF_MAGIC: u16 = 43;
21
22// ============================================================================
23// Raw binary structures for binrw parsing
24// ============================================================================
25
26/// Raw standard TIFF header (8 bytes) for binrw parsing.
27#[derive(Debug, Clone, BinRead)]
28pub struct RawTiffHeader {
29    /// Byte order marker
30    pub byte_order: ByteOrder,
31    /// Magic number (should be 42)
32    #[br(is_little = matches!(byte_order, ByteOrder::LittleEndian))]
33    pub magic: u16,
34    /// Offset to IFD0
35    #[br(is_little = matches!(byte_order, ByteOrder::LittleEndian))]
36    pub ifd0_offset: u32,
37}
38
39/// Raw BigTIFF header (16 bytes) for binrw parsing.
40#[derive(Debug, Clone, BinRead)]
41pub struct RawBigTiffHeader {
42    /// Byte order marker
43    pub byte_order: ByteOrder,
44    /// Magic number (should be 43)
45    #[br(is_little = matches!(byte_order, ByteOrder::LittleEndian))]
46    pub magic: u16,
47    /// Offset byte size (always 8 for BigTIFF)
48    #[br(is_little = matches!(byte_order, ByteOrder::LittleEndian))]
49    pub offset_bytesize: u16,
50    /// Always zero
51    #[br(is_little = matches!(byte_order, ByteOrder::LittleEndian))]
52    pub always_zero: u16,
53    /// Offset to IFD0
54    #[br(is_little = matches!(byte_order, ByteOrder::LittleEndian))]
55    pub ifd0_offset: u64,
56}
57
58/// Raw IFD entry for standard TIFF (12 bytes).
59#[binread]
60#[derive(Debug, Clone)]
61#[br(import { is_little: bool })]
62pub struct RawIfdEntry {
63    /// Tag ID
64    #[br(is_little = is_little)]
65    pub tag_id: u16,
66    /// Data type
67    #[br(is_little = is_little)]
68    pub data_type: u16,
69    /// Count of values
70    #[br(is_little = is_little)]
71    pub count: u32,
72    /// Value or offset
73    #[br(is_little = is_little)]
74    pub value_offset: u32,
75}
76
77/// Raw IFD entry for BigTIFF (20 bytes).
78#[binread]
79#[derive(Debug, Clone)]
80#[br(import { is_little: bool })]
81pub struct RawBigTiffIfdEntry {
82    /// Tag ID
83    #[br(is_little = is_little)]
84    pub tag_id: u16,
85    /// Data type
86    #[br(is_little = is_little)]
87    pub data_type: u16,
88    /// Count of values
89    #[br(is_little = is_little)]
90    pub count: u64,
91    /// Value or offset
92    #[br(is_little = is_little)]
93    pub value_offset: u64,
94}
95
96// ============================================================================
97// Higher-level parsed structures
98// ============================================================================
99
100/// TIFF file header (first 8 bytes).
101#[derive(Debug, Clone)]
102pub struct TiffHeader {
103    /// Byte order (LE or BE)
104    pub byte_order: ByteOrder,
105    /// Version/magic number (42 for TIFF, 43 for BigTIFF)
106    pub magic: u16,
107    /// Offset to the first IFD (IFD0)
108    pub ifd0_offset: u64,
109    /// Whether this is a BigTIFF file
110    pub is_bigtiff: bool,
111}
112
113impl TiffHeader {
114    /// Parse a TIFF header from a reader.
115    pub fn parse<R: Read + Seek>(reader: &mut R) -> RawResult<Self> {
116        reader.seek(SeekFrom::Start(0))?;
117
118        // First, read just the byte order and magic to determine TIFF type
119        let raw_header: RawTiffHeader = reader
120            .read_ne()
121            .map_err(|_| RawError::Parse(ParseError::InvalidByteOrder(0)))?;
122
123        // Validate magic number and determine if BigTIFF
124        let is_bigtiff = match raw_header.magic {
125            TIFF_MAGIC => false,
126            BIGTIFF_MAGIC => true,
127            _ => {
128                return Err(RawError::Parse(ParseError::InvalidMagic {
129                    expected: TIFF_MAGIC,
130                    found: raw_header.magic,
131                }));
132            }
133        };
134
135        // For BigTIFF, we need to re-parse with the full header
136        let ifd0_offset = if is_bigtiff {
137            reader.seek(SeekFrom::Start(0))?;
138            let bigtiff_header: RawBigTiffHeader = reader
139                .read_ne()
140                .map_err(|e| RawError::Parse(ParseError::BinaryParse(e.to_string())))?;
141            bigtiff_header.ifd0_offset
142        } else {
143            raw_header.ifd0_offset as u64
144        };
145
146        Ok(TiffHeader {
147            byte_order: raw_header.byte_order,
148            magic: raw_header.magic,
149            ifd0_offset,
150            is_bigtiff,
151        })
152    }
153}
154
155/// Raw IFD entry as stored in the file (before value resolution).
156#[derive(Debug, Clone)]
157pub struct IfdEntry {
158    /// Tag ID
159    pub tag_id: u16,
160    /// Data type
161    pub data_type: u16,
162    /// Number of values
163    pub count: u64,
164    /// Value or offset to value (raw 4 or 8 bytes depending on TIFF/BigTIFF)
165    pub value_offset: u64,
166    /// The resolved type, if known
167    pub tiff_type: Option<TiffType>,
168    /// The resolved tag, if known
169    pub tag: Option<TiffTag>,
170}
171
172impl IfdEntry {
173    /// Check if this entry's value is stored inline (in the value_offset field).
174    pub fn is_inline(&self, is_bigtiff: bool) -> bool {
175        if let Some(tiff_type) = self.tiff_type {
176            if is_bigtiff {
177                tiff_type.fits_inline_bigtiff(self.count)
178            } else {
179                tiff_type.fits_inline(self.count as u32)
180            }
181        } else {
182            false // Unknown type, assume offset
183        }
184    }
185
186    /// Get the total byte size of this entry's value.
187    pub fn value_size(&self) -> u64 {
188        self.tiff_type
189            .map(|t| t.size() as u64 * self.count)
190            .unwrap_or(0)
191    }
192}
193
194/// A parsed IFD (Image File Directory).
195#[derive(Debug, Clone)]
196pub struct Ifd {
197    /// Offset of this IFD in the file
198    pub offset: u64,
199    /// Parsed entries (known tags)
200    pub entries: HashMap<TiffTag, IfdEntry>,
201    /// Other tags (format-specific, not in TiffTag enum)
202    pub other_tags: HashMap<u16, IfdEntry>,
203    /// Offset to next IFD (0 if none)
204    pub next_ifd_offset: u64,
205    /// SubIFDs (parsed from SubIFDs tag)
206    pub sub_ifds: Vec<Ifd>,
207    /// EXIF IFD (if present)
208    pub exif_ifd: Option<Box<Ifd>>,
209    /// GPS IFD (if present)
210    pub gps_ifd: Option<Box<Ifd>>,
211}
212
213impl Ifd {
214    /// Get a tag value if present.
215    pub fn get(&self, tag: TiffTag) -> Option<&IfdEntry> {
216        self.entries.get(&tag)
217    }
218
219    /// Check if a tag is present.
220    pub fn contains(&self, tag: TiffTag) -> bool {
221        self.entries.contains_key(&tag)
222    }
223
224    /// Returns true if any other (format-specific) tags exist.
225    pub fn has_other_tags(&self) -> bool {
226        !self.other_tags.is_empty()
227    }
228
229    /// Returns list of other tag IDs in this IFD.
230    pub fn other_tag_ids(&self) -> Vec<u16> {
231        self.other_tags.keys().copied().collect()
232    }
233
234    /// Get all tag IDs present in this IFD (both known and other).
235    pub fn all_tag_ids(&self) -> Vec<u16> {
236        let mut ids: Vec<u16> = self.entries.keys().map(|t| t.as_u16()).collect();
237        ids.extend(self.other_tags.keys().copied());
238        ids.sort();
239        ids
240    }
241}
242
243/// TIFF file parser.
244pub struct TiffParser<R> {
245    reader: R,
246    header: TiffHeader,
247    /// Cache of parsed IFDs by offset
248    ifd_cache: HashMap<u64, Ifd>,
249    /// Set of visited offsets (for circular reference detection)
250    visited_offsets: HashSet<u64>,
251}
252
253impl<R: Read + Seek> TiffParser<R> {
254    /// Create a new parser and parse the header.
255    pub fn new(mut reader: R) -> RawResult<Self> {
256        let header = TiffHeader::parse(&mut reader)?;
257
258        Ok(TiffParser {
259            reader,
260            header,
261            ifd_cache: HashMap::new(),
262            visited_offsets: HashSet::new(),
263        })
264    }
265
266    /// Get the parsed header.
267    pub fn header(&self) -> &TiffHeader {
268        &self.header
269    }
270
271    /// Get the byte order.
272    pub fn byte_order(&self) -> ByteOrder {
273        self.header.byte_order
274    }
275
276    /// Check if this is a BigTIFF file.
277    pub fn is_bigtiff(&self) -> bool {
278        self.header.is_bigtiff
279    }
280
281    /// Parse an IFD at the given offset.
282    pub fn parse_ifd_at(&mut self, offset: u64) -> RawResult<Ifd> {
283        // Check for circular references
284        if self.visited_offsets.contains(&offset) {
285            return Err(RawError::Parse(ParseError::CircularReference(offset)));
286        }
287        self.visited_offsets.insert(offset);
288
289        // Check cache
290        if let Some(ifd) = self.ifd_cache.get(&offset) {
291            return Ok(ifd.clone());
292        }
293
294        self.reader.seek(SeekFrom::Start(offset))?;
295
296        // Read entry count
297        let entry_count: u64 = if self.header.is_bigtiff {
298            self.read_u64()?
299        } else {
300            self.read_u16()? as u64
301        };
302
303        // Sanity check on entry count
304        if entry_count > 65535 {
305            return Err(RawError::Parse(ParseError::InvalidIfd {
306                offset,
307                reason: format!("Entry count {} is unreasonably large", entry_count),
308            }));
309        }
310
311        let mut entries = HashMap::new();
312        let mut other_tags = HashMap::new();
313
314        // Parse each entry
315        for _ in 0..entry_count {
316            let entry = self.parse_ifd_entry()?;
317
318            if let Some(tag) = entry.tag {
319                entries.insert(tag, entry);
320            } else {
321                // Other tag (format-specific) - store as IfdEntry
322                other_tags.insert(entry.tag_id, entry);
323            }
324        }
325
326        // Read next IFD offset
327        let next_ifd_offset = if self.header.is_bigtiff {
328            self.read_u64()?
329        } else {
330            self.read_u32()? as u64
331        };
332
333        let mut ifd = Ifd {
334            offset,
335            entries,
336            other_tags,
337            next_ifd_offset,
338            sub_ifds: Vec::new(),
339            exif_ifd: None,
340            gps_ifd: None,
341        };
342
343        // Parse SubIFDs if present
344        if let Some(sub_ifd_entry) = ifd.entries.get(&TiffTag::SubIFDs).cloned() {
345            let offsets = self.read_value_as_u64_vec(&sub_ifd_entry)?;
346            for sub_offset in offsets {
347                if sub_offset != 0 {
348                    match self.parse_ifd_at(sub_offset) {
349                        Ok(sub_ifd) => ifd.sub_ifds.push(sub_ifd),
350                        Err(e) => {
351                            tracing::warn!(
352                                "Failed to parse SubIFD at offset {}: {}",
353                                sub_offset,
354                                e
355                            );
356                        }
357                    }
358                }
359            }
360        }
361
362        // Parse EXIF IFD if present
363        if let Some(exif_entry) = ifd.entries.get(&TiffTag::ExifIFDPointer).cloned()
364            && let Some(exif_offset) = self.read_value_as_u64(&exif_entry)?
365            && exif_offset != 0
366        {
367            match self.parse_ifd_at(exif_offset) {
368                Ok(exif_ifd) => ifd.exif_ifd = Some(Box::new(exif_ifd)),
369                Err(e) => {
370                    tracing::warn!("Failed to parse EXIF IFD at offset {}: {}", exif_offset, e);
371                }
372            }
373        }
374
375        // Parse GPS IFD if present
376        if let Some(gps_entry) = ifd.entries.get(&TiffTag::GPSInfoIFDPointer).cloned()
377            && let Some(gps_offset) = self.read_value_as_u64(&gps_entry)?
378            && gps_offset != 0
379        {
380            match self.parse_ifd_at(gps_offset) {
381                Ok(gps_ifd) => ifd.gps_ifd = Some(Box::new(gps_ifd)),
382                Err(e) => {
383                    tracing::warn!("Failed to parse GPS IFD at offset {}: {}", gps_offset, e);
384                }
385            }
386        }
387
388        // Cache the result
389        self.ifd_cache.insert(offset, ifd.clone());
390
391        Ok(ifd)
392    }
393
394    /// Parse a single IFD entry (12 bytes for TIFF, 20 bytes for BigTIFF).
395    fn parse_ifd_entry(&mut self) -> RawResult<IfdEntry> {
396        let is_little = matches!(self.header.byte_order, ByteOrder::LittleEndian);
397
398        let (tag_id, data_type, count, value_offset) = if self.header.is_bigtiff {
399            let raw: RawBigTiffIfdEntry = self
400                .reader
401                .read_ne_args::<RawBigTiffIfdEntry>(binrw::args! { is_little })
402                .map_err(|e| RawError::Parse(ParseError::BinaryParse(e.to_string())))?;
403            (raw.tag_id, raw.data_type, raw.count, raw.value_offset)
404        } else {
405            let raw: RawIfdEntry = self
406                .reader
407                .read_ne_args::<RawIfdEntry>(binrw::args! { is_little })
408                .map_err(|e| RawError::Parse(ParseError::BinaryParse(e.to_string())))?;
409            (
410                raw.tag_id,
411                raw.data_type,
412                raw.count as u64,
413                raw.value_offset as u64,
414            )
415        };
416
417        let tiff_type = TiffType::from_u16(data_type);
418        let tag = TiffTag::from_u16(tag_id);
419
420        Ok(IfdEntry {
421            tag_id,
422            data_type,
423            count,
424            value_offset,
425            tiff_type,
426            tag,
427        })
428    }
429
430    /// Walk the IFD chain starting from IFD0.
431    pub fn walk_ifd_chain(&mut self) -> RawResult<Vec<Ifd>> {
432        self.visited_offsets.clear();
433        let mut ifds = Vec::new();
434        let mut offset = self.header.ifd0_offset;
435
436        while offset != 0 {
437            let ifd = self.parse_ifd_at(offset)?;
438            offset = ifd.next_ifd_offset;
439            ifds.push(ifd);
440        }
441
442        Ok(ifds)
443    }
444
445    /// Parse IFD0 (the first/main IFD) without validation.
446    pub fn parse_ifd0(&mut self) -> RawResult<Ifd> {
447        self.visited_offsets.clear();
448        self.parse_ifd_at(self.header.ifd0_offset)
449    }
450
451    /// Parse an IFD at a specific offset.
452    pub fn parse_ifd(&mut self, offset: u64) -> RawResult<Ifd> {
453        self.parse_ifd_at(offset)
454    }
455
456    /// Read the value for an IFD entry.
457    pub fn read_value(&mut self, entry: &IfdEntry) -> RawResult<TiffValue> {
458        let tiff_type = entry
459            .tiff_type
460            .ok_or(RawError::Parse(ParseError::UnknownDataType(
461                entry.data_type,
462            )))?;
463
464        // Determine if inline or offset
465        let is_inline = entry.is_inline(self.header.is_bigtiff);
466
467        if !is_inline {
468            // Seek to the data offset
469            self.reader.seek(SeekFrom::Start(entry.value_offset))?;
470        }
471
472        // For inline values, we need to handle them from the value_offset bytes
473        let count = entry.count as usize;
474
475        match tiff_type {
476            TiffType::Byte => {
477                let mut data = vec![0u8; count];
478                if is_inline {
479                    // For inline BYTE values, extract raw bytes from value_offset.
480                    // Cast to u32 first (for non-BigTIFF), then convert to bytes in file order.
481                    let value32 = entry.value_offset as u32;
482                    let bytes = match self.header.byte_order {
483                        ByteOrder::LittleEndian => value32.to_le_bytes(),
484                        ByteOrder::BigEndian => value32.to_be_bytes(),
485                    };
486                    let copy_count = count.min(4);
487                    data[..copy_count].copy_from_slice(&bytes[..copy_count]);
488                } else {
489                    self.reader.read_exact(&mut data)?;
490                }
491                Ok(TiffValue::Bytes(data))
492            }
493            TiffType::Ascii => {
494                let mut data = vec![0u8; count];
495                if is_inline {
496                    let value32 = entry.value_offset as u32;
497                    let bytes = match self.header.byte_order {
498                        ByteOrder::LittleEndian => value32.to_le_bytes(),
499                        ByteOrder::BigEndian => value32.to_be_bytes(),
500                    };
501                    let copy_count = count.min(4);
502                    data[..copy_count].copy_from_slice(&bytes[..copy_count]);
503                } else {
504                    self.reader.read_exact(&mut data)?;
505                }
506                // Remove null terminator and trailing garbage
507                let s = String::from_utf8_lossy(&data)
508                    .trim_end_matches('\0')
509                    .trim()
510                    .to_string();
511                Ok(TiffValue::Ascii(s))
512            }
513            TiffType::Short => {
514                let mut values = Vec::with_capacity(count);
515                if is_inline {
516                    // For inline values, value_offset contains the value from the 4-byte field.
517                    // For non-BigTIFF, value_offset was a u32 extended to u64.
518                    //
519                    // To get the original bytes as they appeared in the file:
520                    // - For LE file: value_offset holds the numeric value, to_le_bytes() gives original order
521                    // - For BE file: value_offset was read as BE u32, so value is shifted.
522                    //   Example: bytes [0x88, 0x4C, 0x00, 0x00] read as BE u32 = 0x884C0000
523                    //   We need to recover the original 4 bytes to extract the SHORT.
524                    //
525                    // We use to_ne_bytes() and then reorder based on file byte order:
526                    // - For non-BigTIFF, the 4-byte value fits in lower 32 bits
527                    // - Cast to u32 first to get just the original 4-byte portion
528                    let value32 = entry.value_offset as u32;
529                    let bytes = match self.header.byte_order {
530                        ByteOrder::LittleEndian => {
531                            // LE: lower byte first, so to_le_bytes gives file order
532                            value32.to_le_bytes()
533                        }
534                        ByteOrder::BigEndian => {
535                            // BE: value was read as BE, so 0x884C0000 needs to become [0x88, 0x4C, 0x00, 0x00]
536                            value32.to_be_bytes()
537                        }
538                    };
539                    for i in 0..count {
540                        let idx = i * 2;
541                        if idx + 1 < 4 {
542                            let v = match self.header.byte_order {
543                                ByteOrder::LittleEndian => {
544                                    u16::from_le_bytes([bytes[idx], bytes[idx + 1]])
545                                }
546                                ByteOrder::BigEndian => {
547                                    u16::from_be_bytes([bytes[idx], bytes[idx + 1]])
548                                }
549                            };
550                            values.push(v);
551                        }
552                    }
553                } else {
554                    for _ in 0..count {
555                        values.push(self.read_u16()?);
556                    }
557                }
558                Ok(TiffValue::Shorts(values))
559            }
560            TiffType::Long | TiffType::Ifd => {
561                let mut values = Vec::with_capacity(count);
562                if is_inline && count == 1 {
563                    values.push(entry.value_offset as u32);
564                } else {
565                    for _ in 0..count {
566                        values.push(self.read_u32()?);
567                    }
568                }
569                Ok(TiffValue::Longs(values))
570            }
571            TiffType::Rational => {
572                let mut values = Vec::with_capacity(count);
573                for _ in 0..count {
574                    let num = self.read_u32()?;
575                    let den = self.read_u32()?;
576                    values.push(Rational::new(num, den));
577                }
578                Ok(TiffValue::Rationals(values))
579            }
580            TiffType::SByte => {
581                let mut data = vec![0u8; count];
582                if is_inline {
583                    let bytes = entry.value_offset.to_le_bytes();
584                    data.copy_from_slice(&bytes[..count.min(8)]);
585                } else {
586                    self.reader.read_exact(&mut data)?;
587                }
588                let signed: Vec<i8> = data.into_iter().map(|b| b as i8).collect();
589                Ok(TiffValue::SBytes(signed))
590            }
591            TiffType::Undefined => {
592                let mut data = vec![0u8; count];
593                if is_inline {
594                    let bytes = entry.value_offset.to_le_bytes();
595                    data.copy_from_slice(&bytes[..count.min(8)]);
596                } else {
597                    self.reader.read_exact(&mut data)?;
598                }
599                Ok(TiffValue::Undefined(data))
600            }
601            TiffType::SShort => {
602                let mut values = Vec::with_capacity(count);
603                if is_inline {
604                    let bytes = entry.value_offset.to_le_bytes();
605                    for i in 0..count {
606                        let idx = i * 2;
607                        if idx + 1 < 8 {
608                            let v = match self.header.byte_order {
609                                ByteOrder::LittleEndian => {
610                                    i16::from_le_bytes([bytes[idx], bytes[idx + 1]])
611                                }
612                                ByteOrder::BigEndian => {
613                                    i16::from_be_bytes([bytes[idx], bytes[idx + 1]])
614                                }
615                            };
616                            values.push(v);
617                        }
618                    }
619                } else {
620                    for _ in 0..count {
621                        values.push(self.read_i16()?);
622                    }
623                }
624                Ok(TiffValue::SShorts(values))
625            }
626            TiffType::SLong => {
627                let mut values = Vec::with_capacity(count);
628                if is_inline && count == 1 {
629                    values.push(entry.value_offset as i32);
630                } else {
631                    for _ in 0..count {
632                        values.push(self.read_i32()?);
633                    }
634                }
635                Ok(TiffValue::SLongs(values))
636            }
637            TiffType::SRational => {
638                let mut values = Vec::with_capacity(count);
639                for _ in 0..count {
640                    let num = self.read_i32()?;
641                    let den = self.read_i32()?;
642                    values.push(SRational::new(num, den));
643                }
644                Ok(TiffValue::SRationals(values))
645            }
646            TiffType::Float => {
647                let mut values = Vec::with_capacity(count);
648                for _ in 0..count {
649                    values.push(self.read_f32()?);
650                }
651                Ok(TiffValue::Floats(values))
652            }
653            TiffType::Double => {
654                let mut values = Vec::with_capacity(count);
655                for _ in 0..count {
656                    values.push(self.read_f64()?);
657                }
658                Ok(TiffValue::Doubles(values))
659            }
660            TiffType::Long8 | TiffType::Ifd8 => {
661                let mut values = Vec::with_capacity(count);
662                if is_inline && count == 1 {
663                    values.push(entry.value_offset);
664                } else {
665                    for _ in 0..count {
666                        values.push(self.read_u64()?);
667                    }
668                }
669                Ok(TiffValue::Long8s(values))
670            }
671            TiffType::SLong8 => {
672                let mut values = Vec::with_capacity(count);
673                if is_inline && count == 1 {
674                    values.push(entry.value_offset as i64);
675                } else {
676                    for _ in 0..count {
677                        values.push(self.read_i64()?);
678                    }
679                }
680                Ok(TiffValue::SLong8s(values))
681            }
682        }
683    }
684
685    /// Read entry value as a single u64 (for offset/pointer tags).
686    fn read_value_as_u64(&mut self, entry: &IfdEntry) -> RawResult<Option<u64>> {
687        if entry.is_inline(self.header.is_bigtiff) {
688            Ok(Some(entry.value_offset))
689        } else {
690            self.reader.seek(SeekFrom::Start(entry.value_offset))?;
691            Ok(Some(self.read_u32()? as u64))
692        }
693    }
694
695    /// Read entry value as a vector of u64 (for SubIFDs, StripOffsets, etc.).
696    fn read_value_as_u64_vec(&mut self, entry: &IfdEntry) -> RawResult<Vec<u64>> {
697        let count = entry.count as usize;
698        let mut values = Vec::with_capacity(count);
699
700        if entry.is_inline(self.header.is_bigtiff) && count == 1 {
701            values.push(entry.value_offset);
702        } else {
703            self.reader.seek(SeekFrom::Start(entry.value_offset))?;
704            for _ in 0..count {
705                let v = if self.header.is_bigtiff {
706                    self.read_u64()?
707                } else {
708                    self.read_u32()? as u64
709                };
710                values.push(v);
711            }
712        }
713
714        Ok(values)
715    }
716
717    // ========================================
718    // Raw read helpers (respecting byte order)
719    // ========================================
720
721    fn read_u16(&mut self) -> RawResult<u16> {
722        Ok(match self.header.byte_order {
723            ByteOrder::LittleEndian => self.reader.read_le()?,
724            ByteOrder::BigEndian => self.reader.read_be()?,
725        })
726    }
727
728    fn read_i16(&mut self) -> RawResult<i16> {
729        Ok(match self.header.byte_order {
730            ByteOrder::LittleEndian => self.reader.read_le()?,
731            ByteOrder::BigEndian => self.reader.read_be()?,
732        })
733    }
734
735    fn read_u32(&mut self) -> RawResult<u32> {
736        Ok(match self.header.byte_order {
737            ByteOrder::LittleEndian => self.reader.read_le()?,
738            ByteOrder::BigEndian => self.reader.read_be()?,
739        })
740    }
741
742    fn read_i32(&mut self) -> RawResult<i32> {
743        Ok(match self.header.byte_order {
744            ByteOrder::LittleEndian => self.reader.read_le()?,
745            ByteOrder::BigEndian => self.reader.read_be()?,
746        })
747    }
748
749    fn read_u64(&mut self) -> RawResult<u64> {
750        Ok(match self.header.byte_order {
751            ByteOrder::LittleEndian => self.reader.read_le()?,
752            ByteOrder::BigEndian => self.reader.read_be()?,
753        })
754    }
755
756    fn read_i64(&mut self) -> RawResult<i64> {
757        Ok(match self.header.byte_order {
758            ByteOrder::LittleEndian => self.reader.read_le()?,
759            ByteOrder::BigEndian => self.reader.read_be()?,
760        })
761    }
762
763    fn read_f32(&mut self) -> RawResult<f32> {
764        Ok(match self.header.byte_order {
765            ByteOrder::LittleEndian => self.reader.read_le()?,
766            ByteOrder::BigEndian => self.reader.read_be()?,
767        })
768    }
769
770    fn read_f64(&mut self) -> RawResult<f64> {
771        Ok(match self.header.byte_order {
772            ByteOrder::LittleEndian => self.reader.read_le()?,
773            ByteOrder::BigEndian => self.reader.read_be()?,
774        })
775    }
776
777    // ========================================
778    // Public read helpers for format modules
779    // ========================================
780
781    /// Seek to a specific offset in the file.
782    pub fn seek_to(&mut self, offset: u64) -> RawResult<()> {
783        self.reader.seek(SeekFrom::Start(offset))?;
784        Ok(())
785    }
786
787    /// Read a specified number of bytes from the current position.
788    pub fn read_bytes(&mut self, count: usize) -> RawResult<Vec<u8>> {
789        let mut buffer = vec![0u8; count];
790        self.reader.read_exact(&mut buffer)?;
791        Ok(buffer)
792    }
793
794    /// Get the total file size.
795    pub fn file_size(&mut self) -> RawResult<u64> {
796        let current = self.reader.stream_position()?;
797        let size = self.reader.seek(SeekFrom::End(0))?;
798        self.reader.seek(SeekFrom::Start(current))?;
799        Ok(size)
800    }
801
802    /// Validate that all IFD data references are within file bounds.
803    ///
804    /// This method walks the entire IFD structure and verifies that:
805    /// - All IFD offsets are within file bounds
806    /// - All value data referenced by entries (that's not inline) is within bounds
807    /// - No truncated data exists
808    ///
809    /// Returns an error if any data would extend past the end of the file.
810    pub fn validate_complete(&mut self) -> RawResult<()> {
811        let file_size = self.file_size()?;
812
813        // Validate header offset
814        if self.header.ifd0_offset >= file_size {
815            return Err(RawError::Parse(ParseError::OffsetOutOfBounds {
816                offset: self.header.ifd0_offset,
817                size: 0,
818                file_size,
819            }));
820        }
821
822        // Walk all IFDs and validate
823        let ifds = self.walk_ifd_chain()?;
824        for ifd in &ifds {
825            self.validate_ifd(ifd, file_size)?;
826        }
827
828        Ok(())
829    }
830
831    /// Validate a single IFD and its entries.
832    fn validate_ifd(&mut self, ifd: &Ifd, file_size: u64) -> RawResult<()> {
833        // Validate each entry's data reference (both known and other tags)
834        for entry in ifd.entries.values().chain(ifd.other_tags.values()) {
835            if !entry.is_inline(self.header.is_bigtiff) {
836                let end = entry.value_offset.saturating_add(entry.value_size());
837                if end > file_size {
838                    return Err(RawError::Parse(ParseError::OffsetOutOfBounds {
839                        offset: entry.value_offset,
840                        size: entry.value_size(),
841                        file_size,
842                    }));
843                }
844            }
845        }
846
847        // Validate sub-IFDs recursively
848        for sub_ifd in &ifd.sub_ifds {
849            self.validate_ifd(sub_ifd, file_size)?;
850        }
851
852        // Validate EXIF IFD if present
853        if let Some(ref exif_ifd) = ifd.exif_ifd {
854            self.validate_ifd(exif_ifd, file_size)?;
855        }
856
857        Ok(())
858    }
859}
860
861#[cfg(test)]
862mod tests {
863    use super::*;
864    use std::io::Cursor;
865
866    /// Create a minimal valid TIFF header (little-endian, no IFD entries).
867    fn make_minimal_tiff() -> Vec<u8> {
868        let mut data = Vec::new();
869        // Byte order: II (little-endian)
870        data.extend_from_slice(b"II");
871        // Magic: 42
872        data.extend_from_slice(&42u16.to_le_bytes());
873        // IFD0 offset: 8 (right after header)
874        data.extend_from_slice(&8u32.to_le_bytes());
875        // IFD with 0 entries
876        data.extend_from_slice(&0u16.to_le_bytes()); // entry count
877        data.extend_from_slice(&0u32.to_le_bytes()); // next IFD offset
878        data
879    }
880
881    #[test]
882    fn test_parse_header_le() {
883        let data = make_minimal_tiff();
884        let mut cursor = Cursor::new(data);
885        let header = TiffHeader::parse(&mut cursor).unwrap();
886
887        assert_eq!(header.byte_order, ByteOrder::LittleEndian);
888        assert_eq!(header.magic, 42);
889        assert_eq!(header.ifd0_offset, 8);
890        assert!(!header.is_bigtiff);
891    }
892
893    #[test]
894    fn test_parse_header_be() {
895        let mut data = Vec::new();
896        data.extend_from_slice(b"MM"); // Big-endian
897        data.extend_from_slice(&42u16.to_be_bytes());
898        data.extend_from_slice(&8u32.to_be_bytes());
899        data.extend_from_slice(&0u16.to_be_bytes()); // entry count
900        data.extend_from_slice(&0u32.to_be_bytes()); // next IFD
901
902        let mut cursor = Cursor::new(data);
903        let header = TiffHeader::parse(&mut cursor).unwrap();
904
905        assert_eq!(header.byte_order, ByteOrder::BigEndian);
906        assert_eq!(header.magic, 42);
907    }
908
909    #[test]
910    fn test_invalid_byte_order() {
911        let mut data = Vec::new();
912        data.extend_from_slice(b"XX"); // Invalid
913        data.extend_from_slice(&42u16.to_le_bytes());
914        data.extend_from_slice(&8u32.to_le_bytes());
915
916        let mut cursor = Cursor::new(data);
917        let result = TiffHeader::parse(&mut cursor);
918
919        assert!(matches!(
920            result,
921            Err(RawError::Parse(ParseError::InvalidByteOrder(_)))
922        ));
923    }
924
925    #[test]
926    fn test_invalid_magic() {
927        let mut data = Vec::new();
928        data.extend_from_slice(b"II");
929        data.extend_from_slice(&99u16.to_le_bytes()); // Invalid magic
930        data.extend_from_slice(&8u32.to_le_bytes());
931
932        let mut cursor = Cursor::new(data);
933        let result = TiffHeader::parse(&mut cursor);
934
935        assert!(matches!(
936            result,
937            Err(RawError::Parse(ParseError::InvalidMagic { .. }))
938        ));
939    }
940
941    #[test]
942    fn test_parse_empty_ifd() {
943        let data = make_minimal_tiff();
944        let cursor = Cursor::new(data);
945        let mut parser = TiffParser::new(cursor).unwrap();
946
947        let ifd = parser.parse_ifd0().unwrap();
948        assert_eq!(ifd.offset, 8);
949        assert!(ifd.entries.is_empty());
950        assert_eq!(ifd.next_ifd_offset, 0);
951    }
952
953    #[test]
954    fn test_walk_ifd_chain() {
955        let data = make_minimal_tiff();
956        let cursor = Cursor::new(data);
957        let mut parser = TiffParser::new(cursor).unwrap();
958
959        let ifds = parser.walk_ifd_chain().unwrap();
960        assert_eq!(ifds.len(), 1);
961    }
962
963    // ========================================================================
964    // Strict parsing validation tests
965    // ========================================================================
966
967    #[test]
968    fn test_truncated_header() {
969        // Only 4 bytes - header needs 8
970        let data = vec![b'I', b'I', 42, 0];
971        let cursor = Cursor::new(data);
972        let result = TiffParser::new(cursor);
973
974        assert!(result.is_err(), "Should fail on truncated header");
975    }
976
977    #[test]
978    fn test_truncated_header_no_offset() {
979        // Header but no IFD offset
980        let mut data = Vec::new();
981        data.extend_from_slice(b"II");
982        data.extend_from_slice(&42u16.to_le_bytes());
983        // Missing 4 bytes for IFD offset
984
985        let cursor = Cursor::new(data);
986        let result = TiffParser::new(cursor);
987
988        assert!(result.is_err(), "Should fail when IFD offset is missing");
989    }
990
991    #[test]
992    fn test_ifd_offset_past_eof() {
993        // Valid header but IFD offset points past end of file
994        let mut data = Vec::new();
995        data.extend_from_slice(b"II");
996        data.extend_from_slice(&42u16.to_le_bytes());
997        data.extend_from_slice(&1000u32.to_le_bytes()); // IFD at offset 1000, but file is only ~8 bytes
998
999        let cursor = Cursor::new(data);
1000        let mut parser = TiffParser::new(cursor).unwrap();
1001
1002        // Parsing should fail when trying to read IFD at invalid offset
1003        let result = parser.parse_ifd0();
1004        assert!(result.is_err(), "Should fail when IFD offset is past EOF");
1005    }
1006
1007    #[test]
1008    fn test_truncated_ifd_entry_count() {
1009        // Valid header pointing to IFD, but IFD is truncated (no entry count)
1010        let mut data = Vec::new();
1011        data.extend_from_slice(b"II");
1012        data.extend_from_slice(&42u16.to_le_bytes());
1013        data.extend_from_slice(&8u32.to_le_bytes()); // IFD at offset 8
1014        // File ends here - no IFD data
1015
1016        let cursor = Cursor::new(data);
1017        let mut parser = TiffParser::new(cursor).unwrap();
1018
1019        let result = parser.parse_ifd0();
1020        assert!(
1021            result.is_err(),
1022            "Should fail when IFD entry count is missing"
1023        );
1024    }
1025
1026    #[test]
1027    fn test_truncated_ifd_entries() {
1028        // Valid header and entry count, but entries are truncated
1029        let mut data = Vec::new();
1030        data.extend_from_slice(b"II");
1031        data.extend_from_slice(&42u16.to_le_bytes());
1032        data.extend_from_slice(&8u32.to_le_bytes()); // IFD at offset 8
1033        data.extend_from_slice(&2u16.to_le_bytes()); // 2 entries expected
1034        // Only partial first entry (needs 12 bytes per entry)
1035        data.extend_from_slice(&[0u8; 6]); // Only 6 bytes
1036
1037        let cursor = Cursor::new(data);
1038        let mut parser = TiffParser::new(cursor).unwrap();
1039
1040        let result = parser.parse_ifd0();
1041        assert!(
1042            result.is_err(),
1043            "Should fail when IFD entries are truncated"
1044        );
1045    }
1046
1047    #[test]
1048    fn test_validate_complete_valid_file() {
1049        // Valid minimal TIFF should pass validation
1050        let data = make_minimal_tiff();
1051        let cursor = Cursor::new(data);
1052        let mut parser = TiffParser::new(cursor).unwrap();
1053
1054        let result = parser.validate_complete();
1055        assert!(
1056            result.is_ok(),
1057            "Valid TIFF should pass validation: {:?}",
1058            result
1059        );
1060    }
1061
1062    #[test]
1063    fn test_validate_complete_value_past_eof() {
1064        // TIFF with an entry whose value offset points past EOF
1065        let mut data = Vec::new();
1066        data.extend_from_slice(b"II");
1067        data.extend_from_slice(&42u16.to_le_bytes());
1068        data.extend_from_slice(&8u32.to_le_bytes()); // IFD at offset 8
1069
1070        // IFD with 1 entry
1071        data.extend_from_slice(&1u16.to_le_bytes()); // 1 entry
1072
1073        // Entry: tag 0x0100 (ImageWidth), type SHORT (3), count 10, offset 1000
1074        data.extend_from_slice(&0x0100u16.to_le_bytes()); // tag
1075        data.extend_from_slice(&3u16.to_le_bytes()); // type SHORT
1076        data.extend_from_slice(&10u32.to_le_bytes()); // count (10 shorts = 20 bytes, won't fit inline)
1077        data.extend_from_slice(&1000u32.to_le_bytes()); // offset past EOF
1078
1079        // Next IFD offset
1080        data.extend_from_slice(&0u32.to_le_bytes());
1081
1082        let cursor = Cursor::new(data);
1083        let mut parser = TiffParser::new(cursor).unwrap();
1084
1085        let result = parser.validate_complete();
1086        assert!(
1087            matches!(
1088                result,
1089                Err(RawError::Parse(ParseError::OffsetOutOfBounds { .. }))
1090            ),
1091            "Should detect value data past EOF: {:?}",
1092            result
1093        );
1094    }
1095
1096    #[test]
1097    fn test_unknown_tag_preserved() {
1098        // TIFF with an unknown tag should have it stored in other_tags
1099        let mut data = Vec::new();
1100        data.extend_from_slice(b"II");
1101        data.extend_from_slice(&42u16.to_le_bytes());
1102        data.extend_from_slice(&8u32.to_le_bytes()); // IFD at offset 8
1103
1104        // IFD with 1 entry
1105        data.extend_from_slice(&1u16.to_le_bytes()); // 1 entry
1106
1107        // Entry: unknown tag 0xFFFF, type SHORT (3), count 1, inline value 42
1108        data.extend_from_slice(&0xFFFFu16.to_le_bytes()); // unknown tag
1109        data.extend_from_slice(&3u16.to_le_bytes()); // type SHORT
1110        data.extend_from_slice(&1u32.to_le_bytes()); // count 1
1111        data.extend_from_slice(&42u32.to_le_bytes()); // value (inline)
1112
1113        // Next IFD offset
1114        data.extend_from_slice(&0u32.to_le_bytes());
1115
1116        let cursor = Cursor::new(data);
1117        let mut parser = TiffParser::new(cursor).unwrap();
1118
1119        let ifd = parser.parse_ifd0().unwrap();
1120
1121        assert!(
1122            ifd.other_tags.contains_key(&0xFFFF),
1123            "Unknown tag should be preserved"
1124        );
1125        assert!(ifd.entries.is_empty(), "No known tags should be parsed");
1126    }
1127
1128    #[test]
1129    fn test_unknown_data_type_preserved() {
1130        // TIFF with an unknown data type should still be stored
1131        let mut data = Vec::new();
1132        data.extend_from_slice(b"II");
1133        data.extend_from_slice(&42u16.to_le_bytes());
1134        data.extend_from_slice(&8u32.to_le_bytes()); // IFD at offset 8
1135
1136        // IFD with 1 entry
1137        data.extend_from_slice(&1u16.to_le_bytes()); // 1 entry
1138
1139        // Entry: tag 0x0100 (ImageWidth), type 99 (unknown), count 1, inline value
1140        data.extend_from_slice(&0x0100u16.to_le_bytes()); // known tag
1141        data.extend_from_slice(&99u16.to_le_bytes()); // unknown type
1142        data.extend_from_slice(&1u32.to_le_bytes()); // count 1
1143        data.extend_from_slice(&42u32.to_le_bytes()); // value
1144
1145        // Next IFD offset
1146        data.extend_from_slice(&0u32.to_le_bytes());
1147
1148        let cursor = Cursor::new(data);
1149        let mut parser = TiffParser::new(cursor).unwrap();
1150
1151        let ifd = parser.parse_ifd0().unwrap();
1152
1153        // Entry should exist but with unknown tiff_type
1154        let entry = ifd.entries.get(&TiffTag::ImageWidth);
1155        assert!(entry.is_some(), "Entry should exist");
1156        assert!(
1157            entry.unwrap().tiff_type.is_none(),
1158            "Unknown type should be None"
1159        );
1160    }
1161}