use binrw::{BinRead, BinReaderExt, binread};
use std::collections::{HashMap, HashSet};
use std::io::{Read, Seek, SeekFrom};
use crate::error::{ParseError, RawError, RawResult};
use crate::tiff::tags::TiffTag;
use crate::tiff::types::{ByteOrder, Rational, SRational, TiffType, TiffValue};
pub const TIFF_MAGIC: u16 = 42;
pub const BIGTIFF_MAGIC: u16 = 43;
#[derive(Debug, Clone, BinRead)]
pub struct RawTiffHeader {
pub byte_order: ByteOrder,
#[br(is_little = matches!(byte_order, ByteOrder::LittleEndian))]
pub magic: u16,
#[br(is_little = matches!(byte_order, ByteOrder::LittleEndian))]
pub ifd0_offset: u32,
}
#[derive(Debug, Clone, BinRead)]
pub struct RawBigTiffHeader {
pub byte_order: ByteOrder,
#[br(is_little = matches!(byte_order, ByteOrder::LittleEndian))]
pub magic: u16,
#[br(is_little = matches!(byte_order, ByteOrder::LittleEndian))]
pub offset_bytesize: u16,
#[br(is_little = matches!(byte_order, ByteOrder::LittleEndian))]
pub always_zero: u16,
#[br(is_little = matches!(byte_order, ByteOrder::LittleEndian))]
pub ifd0_offset: u64,
}
#[binread]
#[derive(Debug, Clone)]
#[br(import { is_little: bool })]
pub struct RawIfdEntry {
#[br(is_little = is_little)]
pub tag_id: u16,
#[br(is_little = is_little)]
pub data_type: u16,
#[br(is_little = is_little)]
pub count: u32,
#[br(is_little = is_little)]
pub value_offset: u32,
}
#[binread]
#[derive(Debug, Clone)]
#[br(import { is_little: bool })]
pub struct RawBigTiffIfdEntry {
#[br(is_little = is_little)]
pub tag_id: u16,
#[br(is_little = is_little)]
pub data_type: u16,
#[br(is_little = is_little)]
pub count: u64,
#[br(is_little = is_little)]
pub value_offset: u64,
}
#[derive(Debug, Clone)]
pub struct TiffHeader {
pub byte_order: ByteOrder,
pub magic: u16,
pub ifd0_offset: u64,
pub is_bigtiff: bool,
}
impl TiffHeader {
pub fn parse<R: Read + Seek>(reader: &mut R) -> RawResult<Self> {
reader.seek(SeekFrom::Start(0))?;
let raw_header: RawTiffHeader = reader
.read_ne()
.map_err(|_| RawError::Parse(ParseError::InvalidByteOrder(0)))?;
let is_bigtiff = match raw_header.magic {
TIFF_MAGIC => false,
BIGTIFF_MAGIC => true,
_ => {
return Err(RawError::Parse(ParseError::InvalidMagic {
expected: TIFF_MAGIC,
found: raw_header.magic,
}));
}
};
let ifd0_offset = if is_bigtiff {
reader.seek(SeekFrom::Start(0))?;
let bigtiff_header: RawBigTiffHeader = reader
.read_ne()
.map_err(|e| RawError::Parse(ParseError::BinaryParse(e.to_string())))?;
bigtiff_header.ifd0_offset
} else {
raw_header.ifd0_offset as u64
};
Ok(TiffHeader {
byte_order: raw_header.byte_order,
magic: raw_header.magic,
ifd0_offset,
is_bigtiff,
})
}
}
#[derive(Debug, Clone)]
pub struct IfdEntry {
pub tag_id: u16,
pub data_type: u16,
pub count: u64,
pub value_offset: u64,
pub tiff_type: Option<TiffType>,
pub tag: Option<TiffTag>,
}
impl IfdEntry {
pub fn is_inline(&self, is_bigtiff: bool) -> bool {
if let Some(tiff_type) = self.tiff_type {
if is_bigtiff {
tiff_type.fits_inline_bigtiff(self.count)
} else {
tiff_type.fits_inline(self.count as u32)
}
} else {
false }
}
pub fn value_size(&self) -> u64 {
self.tiff_type
.map(|t| t.size() as u64 * self.count)
.unwrap_or(0)
}
}
#[derive(Debug, Clone)]
pub struct Ifd {
pub offset: u64,
pub entries: HashMap<TiffTag, IfdEntry>,
pub other_tags: HashMap<u16, IfdEntry>,
pub next_ifd_offset: u64,
pub sub_ifds: Vec<Ifd>,
pub exif_ifd: Option<Box<Ifd>>,
pub gps_ifd: Option<Box<Ifd>>,
}
impl Ifd {
pub fn get(&self, tag: TiffTag) -> Option<&IfdEntry> {
self.entries.get(&tag)
}
pub fn contains(&self, tag: TiffTag) -> bool {
self.entries.contains_key(&tag)
}
pub fn has_other_tags(&self) -> bool {
!self.other_tags.is_empty()
}
pub fn other_tag_ids(&self) -> Vec<u16> {
self.other_tags.keys().copied().collect()
}
pub fn all_tag_ids(&self) -> Vec<u16> {
let mut ids: Vec<u16> = self.entries.keys().map(|t| t.as_u16()).collect();
ids.extend(self.other_tags.keys().copied());
ids.sort();
ids
}
}
pub struct TiffParser<R> {
reader: R,
header: TiffHeader,
ifd_cache: HashMap<u64, Ifd>,
visited_offsets: HashSet<u64>,
}
impl<R: Read + Seek> TiffParser<R> {
pub fn new(mut reader: R) -> RawResult<Self> {
let header = TiffHeader::parse(&mut reader)?;
Ok(TiffParser {
reader,
header,
ifd_cache: HashMap::new(),
visited_offsets: HashSet::new(),
})
}
pub fn header(&self) -> &TiffHeader {
&self.header
}
pub fn byte_order(&self) -> ByteOrder {
self.header.byte_order
}
pub fn is_bigtiff(&self) -> bool {
self.header.is_bigtiff
}
pub fn parse_ifd_at(&mut self, offset: u64) -> RawResult<Ifd> {
if self.visited_offsets.contains(&offset) {
return Err(RawError::Parse(ParseError::CircularReference(offset)));
}
self.visited_offsets.insert(offset);
if let Some(ifd) = self.ifd_cache.get(&offset) {
return Ok(ifd.clone());
}
self.reader.seek(SeekFrom::Start(offset))?;
let entry_count: u64 = if self.header.is_bigtiff {
self.read_u64()?
} else {
self.read_u16()? as u64
};
if entry_count > 65535 {
return Err(RawError::Parse(ParseError::InvalidIfd {
offset,
reason: format!("Entry count {} is unreasonably large", entry_count),
}));
}
let mut entries = HashMap::new();
let mut other_tags = HashMap::new();
for _ in 0..entry_count {
let entry = self.parse_ifd_entry()?;
if let Some(tag) = entry.tag {
entries.insert(tag, entry);
} else {
other_tags.insert(entry.tag_id, entry);
}
}
let next_ifd_offset = if self.header.is_bigtiff {
self.read_u64()?
} else {
self.read_u32()? as u64
};
let mut ifd = Ifd {
offset,
entries,
other_tags,
next_ifd_offset,
sub_ifds: Vec::new(),
exif_ifd: None,
gps_ifd: None,
};
if let Some(sub_ifd_entry) = ifd.entries.get(&TiffTag::SubIFDs).cloned() {
let offsets = self.read_value_as_u64_vec(&sub_ifd_entry)?;
for sub_offset in offsets {
if sub_offset != 0 {
match self.parse_ifd_at(sub_offset) {
Ok(sub_ifd) => ifd.sub_ifds.push(sub_ifd),
Err(e) => {
tracing::warn!(
"Failed to parse SubIFD at offset {}: {}",
sub_offset,
e
);
}
}
}
}
}
if let Some(exif_entry) = ifd.entries.get(&TiffTag::ExifIFDPointer).cloned()
&& let Some(exif_offset) = self.read_value_as_u64(&exif_entry)?
&& exif_offset != 0
{
match self.parse_ifd_at(exif_offset) {
Ok(exif_ifd) => ifd.exif_ifd = Some(Box::new(exif_ifd)),
Err(e) => {
tracing::warn!("Failed to parse EXIF IFD at offset {}: {}", exif_offset, e);
}
}
}
if let Some(gps_entry) = ifd.entries.get(&TiffTag::GPSInfoIFDPointer).cloned()
&& let Some(gps_offset) = self.read_value_as_u64(&gps_entry)?
&& gps_offset != 0
{
match self.parse_ifd_at(gps_offset) {
Ok(gps_ifd) => ifd.gps_ifd = Some(Box::new(gps_ifd)),
Err(e) => {
tracing::warn!("Failed to parse GPS IFD at offset {}: {}", gps_offset, e);
}
}
}
self.ifd_cache.insert(offset, ifd.clone());
Ok(ifd)
}
fn parse_ifd_entry(&mut self) -> RawResult<IfdEntry> {
let is_little = matches!(self.header.byte_order, ByteOrder::LittleEndian);
let (tag_id, data_type, count, value_offset) = if self.header.is_bigtiff {
let raw: RawBigTiffIfdEntry = self
.reader
.read_ne_args::<RawBigTiffIfdEntry>(binrw::args! { is_little })
.map_err(|e| RawError::Parse(ParseError::BinaryParse(e.to_string())))?;
(raw.tag_id, raw.data_type, raw.count, raw.value_offset)
} else {
let raw: RawIfdEntry = self
.reader
.read_ne_args::<RawIfdEntry>(binrw::args! { is_little })
.map_err(|e| RawError::Parse(ParseError::BinaryParse(e.to_string())))?;
(
raw.tag_id,
raw.data_type,
raw.count as u64,
raw.value_offset as u64,
)
};
let tiff_type = TiffType::from_u16(data_type);
let tag = TiffTag::from_u16(tag_id);
Ok(IfdEntry {
tag_id,
data_type,
count,
value_offset,
tiff_type,
tag,
})
}
pub fn walk_ifd_chain(&mut self) -> RawResult<Vec<Ifd>> {
self.visited_offsets.clear();
let mut ifds = Vec::new();
let mut offset = self.header.ifd0_offset;
while offset != 0 {
let ifd = self.parse_ifd_at(offset)?;
offset = ifd.next_ifd_offset;
ifds.push(ifd);
}
Ok(ifds)
}
pub fn parse_ifd0(&mut self) -> RawResult<Ifd> {
self.visited_offsets.clear();
self.parse_ifd_at(self.header.ifd0_offset)
}
pub fn parse_ifd(&mut self, offset: u64) -> RawResult<Ifd> {
self.parse_ifd_at(offset)
}
pub fn read_value(&mut self, entry: &IfdEntry) -> RawResult<TiffValue> {
let tiff_type = entry
.tiff_type
.ok_or(RawError::Parse(ParseError::UnknownDataType(
entry.data_type,
)))?;
let is_inline = entry.is_inline(self.header.is_bigtiff);
if !is_inline {
self.reader.seek(SeekFrom::Start(entry.value_offset))?;
}
let count = entry.count as usize;
match tiff_type {
TiffType::Byte => {
let mut data = vec![0u8; count];
if is_inline {
let value32 = entry.value_offset as u32;
let bytes = match self.header.byte_order {
ByteOrder::LittleEndian => value32.to_le_bytes(),
ByteOrder::BigEndian => value32.to_be_bytes(),
};
let copy_count = count.min(4);
data[..copy_count].copy_from_slice(&bytes[..copy_count]);
} else {
self.reader.read_exact(&mut data)?;
}
Ok(TiffValue::Bytes(data))
}
TiffType::Ascii => {
let mut data = vec![0u8; count];
if is_inline {
let value32 = entry.value_offset as u32;
let bytes = match self.header.byte_order {
ByteOrder::LittleEndian => value32.to_le_bytes(),
ByteOrder::BigEndian => value32.to_be_bytes(),
};
let copy_count = count.min(4);
data[..copy_count].copy_from_slice(&bytes[..copy_count]);
} else {
self.reader.read_exact(&mut data)?;
}
let s = String::from_utf8_lossy(&data)
.trim_end_matches('\0')
.trim()
.to_string();
Ok(TiffValue::Ascii(s))
}
TiffType::Short => {
let mut values = Vec::with_capacity(count);
if is_inline {
let value32 = entry.value_offset as u32;
let bytes = match self.header.byte_order {
ByteOrder::LittleEndian => {
value32.to_le_bytes()
}
ByteOrder::BigEndian => {
value32.to_be_bytes()
}
};
for i in 0..count {
let idx = i * 2;
if idx + 1 < 4 {
let v = match self.header.byte_order {
ByteOrder::LittleEndian => {
u16::from_le_bytes([bytes[idx], bytes[idx + 1]])
}
ByteOrder::BigEndian => {
u16::from_be_bytes([bytes[idx], bytes[idx + 1]])
}
};
values.push(v);
}
}
} else {
for _ in 0..count {
values.push(self.read_u16()?);
}
}
Ok(TiffValue::Shorts(values))
}
TiffType::Long | TiffType::Ifd => {
let mut values = Vec::with_capacity(count);
if is_inline && count == 1 {
values.push(entry.value_offset as u32);
} else {
for _ in 0..count {
values.push(self.read_u32()?);
}
}
Ok(TiffValue::Longs(values))
}
TiffType::Rational => {
let mut values = Vec::with_capacity(count);
for _ in 0..count {
let num = self.read_u32()?;
let den = self.read_u32()?;
values.push(Rational::new(num, den));
}
Ok(TiffValue::Rationals(values))
}
TiffType::SByte => {
let mut data = vec![0u8; count];
if is_inline {
let bytes = entry.value_offset.to_le_bytes();
data.copy_from_slice(&bytes[..count.min(8)]);
} else {
self.reader.read_exact(&mut data)?;
}
let signed: Vec<i8> = data.into_iter().map(|b| b as i8).collect();
Ok(TiffValue::SBytes(signed))
}
TiffType::Undefined => {
let mut data = vec![0u8; count];
if is_inline {
let bytes = entry.value_offset.to_le_bytes();
data.copy_from_slice(&bytes[..count.min(8)]);
} else {
self.reader.read_exact(&mut data)?;
}
Ok(TiffValue::Undefined(data))
}
TiffType::SShort => {
let mut values = Vec::with_capacity(count);
if is_inline {
let bytes = entry.value_offset.to_le_bytes();
for i in 0..count {
let idx = i * 2;
if idx + 1 < 8 {
let v = match self.header.byte_order {
ByteOrder::LittleEndian => {
i16::from_le_bytes([bytes[idx], bytes[idx + 1]])
}
ByteOrder::BigEndian => {
i16::from_be_bytes([bytes[idx], bytes[idx + 1]])
}
};
values.push(v);
}
}
} else {
for _ in 0..count {
values.push(self.read_i16()?);
}
}
Ok(TiffValue::SShorts(values))
}
TiffType::SLong => {
let mut values = Vec::with_capacity(count);
if is_inline && count == 1 {
values.push(entry.value_offset as i32);
} else {
for _ in 0..count {
values.push(self.read_i32()?);
}
}
Ok(TiffValue::SLongs(values))
}
TiffType::SRational => {
let mut values = Vec::with_capacity(count);
for _ in 0..count {
let num = self.read_i32()?;
let den = self.read_i32()?;
values.push(SRational::new(num, den));
}
Ok(TiffValue::SRationals(values))
}
TiffType::Float => {
let mut values = Vec::with_capacity(count);
for _ in 0..count {
values.push(self.read_f32()?);
}
Ok(TiffValue::Floats(values))
}
TiffType::Double => {
let mut values = Vec::with_capacity(count);
for _ in 0..count {
values.push(self.read_f64()?);
}
Ok(TiffValue::Doubles(values))
}
TiffType::Long8 | TiffType::Ifd8 => {
let mut values = Vec::with_capacity(count);
if is_inline && count == 1 {
values.push(entry.value_offset);
} else {
for _ in 0..count {
values.push(self.read_u64()?);
}
}
Ok(TiffValue::Long8s(values))
}
TiffType::SLong8 => {
let mut values = Vec::with_capacity(count);
if is_inline && count == 1 {
values.push(entry.value_offset as i64);
} else {
for _ in 0..count {
values.push(self.read_i64()?);
}
}
Ok(TiffValue::SLong8s(values))
}
}
}
fn read_value_as_u64(&mut self, entry: &IfdEntry) -> RawResult<Option<u64>> {
if entry.is_inline(self.header.is_bigtiff) {
Ok(Some(entry.value_offset))
} else {
self.reader.seek(SeekFrom::Start(entry.value_offset))?;
Ok(Some(self.read_u32()? as u64))
}
}
fn read_value_as_u64_vec(&mut self, entry: &IfdEntry) -> RawResult<Vec<u64>> {
let count = entry.count as usize;
let mut values = Vec::with_capacity(count);
if entry.is_inline(self.header.is_bigtiff) && count == 1 {
values.push(entry.value_offset);
} else {
self.reader.seek(SeekFrom::Start(entry.value_offset))?;
for _ in 0..count {
let v = if self.header.is_bigtiff {
self.read_u64()?
} else {
self.read_u32()? as u64
};
values.push(v);
}
}
Ok(values)
}
fn read_u16(&mut self) -> RawResult<u16> {
Ok(match self.header.byte_order {
ByteOrder::LittleEndian => self.reader.read_le()?,
ByteOrder::BigEndian => self.reader.read_be()?,
})
}
fn read_i16(&mut self) -> RawResult<i16> {
Ok(match self.header.byte_order {
ByteOrder::LittleEndian => self.reader.read_le()?,
ByteOrder::BigEndian => self.reader.read_be()?,
})
}
fn read_u32(&mut self) -> RawResult<u32> {
Ok(match self.header.byte_order {
ByteOrder::LittleEndian => self.reader.read_le()?,
ByteOrder::BigEndian => self.reader.read_be()?,
})
}
fn read_i32(&mut self) -> RawResult<i32> {
Ok(match self.header.byte_order {
ByteOrder::LittleEndian => self.reader.read_le()?,
ByteOrder::BigEndian => self.reader.read_be()?,
})
}
fn read_u64(&mut self) -> RawResult<u64> {
Ok(match self.header.byte_order {
ByteOrder::LittleEndian => self.reader.read_le()?,
ByteOrder::BigEndian => self.reader.read_be()?,
})
}
fn read_i64(&mut self) -> RawResult<i64> {
Ok(match self.header.byte_order {
ByteOrder::LittleEndian => self.reader.read_le()?,
ByteOrder::BigEndian => self.reader.read_be()?,
})
}
fn read_f32(&mut self) -> RawResult<f32> {
Ok(match self.header.byte_order {
ByteOrder::LittleEndian => self.reader.read_le()?,
ByteOrder::BigEndian => self.reader.read_be()?,
})
}
fn read_f64(&mut self) -> RawResult<f64> {
Ok(match self.header.byte_order {
ByteOrder::LittleEndian => self.reader.read_le()?,
ByteOrder::BigEndian => self.reader.read_be()?,
})
}
pub fn seek_to(&mut self, offset: u64) -> RawResult<()> {
self.reader.seek(SeekFrom::Start(offset))?;
Ok(())
}
pub fn read_bytes(&mut self, count: usize) -> RawResult<Vec<u8>> {
let mut buffer = vec![0u8; count];
self.reader.read_exact(&mut buffer)?;
Ok(buffer)
}
pub fn file_size(&mut self) -> RawResult<u64> {
let current = self.reader.stream_position()?;
let size = self.reader.seek(SeekFrom::End(0))?;
self.reader.seek(SeekFrom::Start(current))?;
Ok(size)
}
pub fn validate_complete(&mut self) -> RawResult<()> {
let file_size = self.file_size()?;
if self.header.ifd0_offset >= file_size {
return Err(RawError::Parse(ParseError::OffsetOutOfBounds {
offset: self.header.ifd0_offset,
size: 0,
file_size,
}));
}
let ifds = self.walk_ifd_chain()?;
for ifd in &ifds {
self.validate_ifd(ifd, file_size)?;
}
Ok(())
}
fn validate_ifd(&mut self, ifd: &Ifd, file_size: u64) -> RawResult<()> {
for entry in ifd.entries.values().chain(ifd.other_tags.values()) {
if !entry.is_inline(self.header.is_bigtiff) {
let end = entry.value_offset.saturating_add(entry.value_size());
if end > file_size {
return Err(RawError::Parse(ParseError::OffsetOutOfBounds {
offset: entry.value_offset,
size: entry.value_size(),
file_size,
}));
}
}
}
for sub_ifd in &ifd.sub_ifds {
self.validate_ifd(sub_ifd, file_size)?;
}
if let Some(ref exif_ifd) = ifd.exif_ifd {
self.validate_ifd(exif_ifd, file_size)?;
}
Ok(())
}
}
#[cfg(test)]
mod tests {
use super::*;
use std::io::Cursor;
fn make_minimal_tiff() -> Vec<u8> {
let mut data = Vec::new();
data.extend_from_slice(b"II");
data.extend_from_slice(&42u16.to_le_bytes());
data.extend_from_slice(&8u32.to_le_bytes());
data.extend_from_slice(&0u16.to_le_bytes()); data.extend_from_slice(&0u32.to_le_bytes()); data
}
#[test]
fn test_parse_header_le() {
let data = make_minimal_tiff();
let mut cursor = Cursor::new(data);
let header = TiffHeader::parse(&mut cursor).unwrap();
assert_eq!(header.byte_order, ByteOrder::LittleEndian);
assert_eq!(header.magic, 42);
assert_eq!(header.ifd0_offset, 8);
assert!(!header.is_bigtiff);
}
#[test]
fn test_parse_header_be() {
let mut data = Vec::new();
data.extend_from_slice(b"MM"); data.extend_from_slice(&42u16.to_be_bytes());
data.extend_from_slice(&8u32.to_be_bytes());
data.extend_from_slice(&0u16.to_be_bytes()); data.extend_from_slice(&0u32.to_be_bytes());
let mut cursor = Cursor::new(data);
let header = TiffHeader::parse(&mut cursor).unwrap();
assert_eq!(header.byte_order, ByteOrder::BigEndian);
assert_eq!(header.magic, 42);
}
#[test]
fn test_invalid_byte_order() {
let mut data = Vec::new();
data.extend_from_slice(b"XX"); data.extend_from_slice(&42u16.to_le_bytes());
data.extend_from_slice(&8u32.to_le_bytes());
let mut cursor = Cursor::new(data);
let result = TiffHeader::parse(&mut cursor);
assert!(matches!(
result,
Err(RawError::Parse(ParseError::InvalidByteOrder(_)))
));
}
#[test]
fn test_invalid_magic() {
let mut data = Vec::new();
data.extend_from_slice(b"II");
data.extend_from_slice(&99u16.to_le_bytes()); data.extend_from_slice(&8u32.to_le_bytes());
let mut cursor = Cursor::new(data);
let result = TiffHeader::parse(&mut cursor);
assert!(matches!(
result,
Err(RawError::Parse(ParseError::InvalidMagic { .. }))
));
}
#[test]
fn test_parse_empty_ifd() {
let data = make_minimal_tiff();
let cursor = Cursor::new(data);
let mut parser = TiffParser::new(cursor).unwrap();
let ifd = parser.parse_ifd0().unwrap();
assert_eq!(ifd.offset, 8);
assert!(ifd.entries.is_empty());
assert_eq!(ifd.next_ifd_offset, 0);
}
#[test]
fn test_walk_ifd_chain() {
let data = make_minimal_tiff();
let cursor = Cursor::new(data);
let mut parser = TiffParser::new(cursor).unwrap();
let ifds = parser.walk_ifd_chain().unwrap();
assert_eq!(ifds.len(), 1);
}
#[test]
fn test_truncated_header() {
let data = vec![b'I', b'I', 42, 0];
let cursor = Cursor::new(data);
let result = TiffParser::new(cursor);
assert!(result.is_err(), "Should fail on truncated header");
}
#[test]
fn test_truncated_header_no_offset() {
let mut data = Vec::new();
data.extend_from_slice(b"II");
data.extend_from_slice(&42u16.to_le_bytes());
let cursor = Cursor::new(data);
let result = TiffParser::new(cursor);
assert!(result.is_err(), "Should fail when IFD offset is missing");
}
#[test]
fn test_ifd_offset_past_eof() {
let mut data = Vec::new();
data.extend_from_slice(b"II");
data.extend_from_slice(&42u16.to_le_bytes());
data.extend_from_slice(&1000u32.to_le_bytes());
let cursor = Cursor::new(data);
let mut parser = TiffParser::new(cursor).unwrap();
let result = parser.parse_ifd0();
assert!(result.is_err(), "Should fail when IFD offset is past EOF");
}
#[test]
fn test_truncated_ifd_entry_count() {
let mut data = Vec::new();
data.extend_from_slice(b"II");
data.extend_from_slice(&42u16.to_le_bytes());
data.extend_from_slice(&8u32.to_le_bytes());
let cursor = Cursor::new(data);
let mut parser = TiffParser::new(cursor).unwrap();
let result = parser.parse_ifd0();
assert!(
result.is_err(),
"Should fail when IFD entry count is missing"
);
}
#[test]
fn test_truncated_ifd_entries() {
let mut data = Vec::new();
data.extend_from_slice(b"II");
data.extend_from_slice(&42u16.to_le_bytes());
data.extend_from_slice(&8u32.to_le_bytes()); data.extend_from_slice(&2u16.to_le_bytes()); data.extend_from_slice(&[0u8; 6]);
let cursor = Cursor::new(data);
let mut parser = TiffParser::new(cursor).unwrap();
let result = parser.parse_ifd0();
assert!(
result.is_err(),
"Should fail when IFD entries are truncated"
);
}
#[test]
fn test_validate_complete_valid_file() {
let data = make_minimal_tiff();
let cursor = Cursor::new(data);
let mut parser = TiffParser::new(cursor).unwrap();
let result = parser.validate_complete();
assert!(
result.is_ok(),
"Valid TIFF should pass validation: {:?}",
result
);
}
#[test]
fn test_validate_complete_value_past_eof() {
let mut data = Vec::new();
data.extend_from_slice(b"II");
data.extend_from_slice(&42u16.to_le_bytes());
data.extend_from_slice(&8u32.to_le_bytes());
data.extend_from_slice(&1u16.to_le_bytes());
data.extend_from_slice(&0x0100u16.to_le_bytes()); data.extend_from_slice(&3u16.to_le_bytes()); data.extend_from_slice(&10u32.to_le_bytes()); data.extend_from_slice(&1000u32.to_le_bytes());
data.extend_from_slice(&0u32.to_le_bytes());
let cursor = Cursor::new(data);
let mut parser = TiffParser::new(cursor).unwrap();
let result = parser.validate_complete();
assert!(
matches!(
result,
Err(RawError::Parse(ParseError::OffsetOutOfBounds { .. }))
),
"Should detect value data past EOF: {:?}",
result
);
}
#[test]
fn test_unknown_tag_preserved() {
let mut data = Vec::new();
data.extend_from_slice(b"II");
data.extend_from_slice(&42u16.to_le_bytes());
data.extend_from_slice(&8u32.to_le_bytes());
data.extend_from_slice(&1u16.to_le_bytes());
data.extend_from_slice(&0xFFFFu16.to_le_bytes()); data.extend_from_slice(&3u16.to_le_bytes()); data.extend_from_slice(&1u32.to_le_bytes()); data.extend_from_slice(&42u32.to_le_bytes());
data.extend_from_slice(&0u32.to_le_bytes());
let cursor = Cursor::new(data);
let mut parser = TiffParser::new(cursor).unwrap();
let ifd = parser.parse_ifd0().unwrap();
assert!(
ifd.other_tags.contains_key(&0xFFFF),
"Unknown tag should be preserved"
);
assert!(ifd.entries.is_empty(), "No known tags should be parsed");
}
#[test]
fn test_unknown_data_type_preserved() {
let mut data = Vec::new();
data.extend_from_slice(b"II");
data.extend_from_slice(&42u16.to_le_bytes());
data.extend_from_slice(&8u32.to_le_bytes());
data.extend_from_slice(&1u16.to_le_bytes());
data.extend_from_slice(&0x0100u16.to_le_bytes()); data.extend_from_slice(&99u16.to_le_bytes()); data.extend_from_slice(&1u32.to_le_bytes()); data.extend_from_slice(&42u32.to_le_bytes());
data.extend_from_slice(&0u32.to_le_bytes());
let cursor = Cursor::new(data);
let mut parser = TiffParser::new(cursor).unwrap();
let ifd = parser.parse_ifd0().unwrap();
let entry = ifd.entries.get(&TiffTag::ImageWidth);
assert!(entry.is_some(), "Entry should exist");
assert!(
entry.unwrap().tiff_type.is_none(),
"Unknown type should be None"
);
}
}