1use crate::error::{Error, Result};
2
3pub use tiff_core::ByteOrder;
4
5#[derive(Debug, Clone)]
7pub struct TiffHeader {
8 pub byte_order: ByteOrder,
9 pub version: u16,
11 pub first_ifd_offset: u64,
13}
14
15impl TiffHeader {
16 pub fn is_bigtiff(&self) -> bool {
18 self.version == 43
19 }
20
21 pub fn parse(data: &[u8]) -> Result<Self> {
23 if data.len() < 8 {
24 return Err(Error::InvalidMagic);
25 }
26
27 let byte_order = match &data[0..2] {
28 b"II" => ByteOrder::LittleEndian,
29 b"MM" => ByteOrder::BigEndian,
30 _ => return Err(Error::InvalidMagic),
31 };
32
33 let read_u16 = |offset: usize| -> u16 {
34 let bytes = [data[offset], data[offset + 1]];
35 match byte_order {
36 ByteOrder::LittleEndian => u16::from_le_bytes(bytes),
37 ByteOrder::BigEndian => u16::from_be_bytes(bytes),
38 }
39 };
40
41 let version = read_u16(2);
42
43 match version {
44 42 => {
45 let read_u32 = |offset: usize| -> u32 {
47 let bytes: [u8; 4] = data[offset..offset + 4].try_into().unwrap();
48 match byte_order {
49 ByteOrder::LittleEndian => u32::from_le_bytes(bytes),
50 ByteOrder::BigEndian => u32::from_be_bytes(bytes),
51 }
52 };
53 let first_ifd_offset = read_u32(4) as u64;
54 Ok(Self {
55 byte_order,
56 version,
57 first_ifd_offset,
58 })
59 }
60 43 => {
61 if data.len() < 16 {
63 return Err(Error::InvalidMagic);
64 }
65 let offset_size = read_u16(4);
66 if offset_size != 8 {
67 return Err(Error::InvalidBigTiffHeader(format!(
68 "offset size must be 8, found {offset_size}"
69 )));
70 }
71 let reserved = read_u16(6);
72 if reserved != 0 {
73 return Err(Error::InvalidBigTiffHeader(format!(
74 "reserved field must be zero, found {reserved}"
75 )));
76 }
77 let read_u64 = |offset: usize| -> u64 {
78 let bytes: [u8; 8] = data[offset..offset + 8].try_into().unwrap();
79 match byte_order {
80 ByteOrder::LittleEndian => u64::from_le_bytes(bytes),
81 ByteOrder::BigEndian => u64::from_be_bytes(bytes),
82 }
83 };
84 let first_ifd_offset = read_u64(8);
85 Ok(Self {
86 byte_order,
87 version,
88 first_ifd_offset,
89 })
90 }
91 _ => Err(Error::UnsupportedVersion(version)),
92 }
93 }
94}
95
96#[cfg(test)]
97mod tests {
98 use super::*;
99
100 #[test]
101 fn parse_little_endian_classic() {
102 let data = b"II\x2a\x00\x08\x00\x00\x00";
104 let header = TiffHeader::parse(data).unwrap();
105 assert_eq!(header.byte_order, ByteOrder::LittleEndian);
106 assert_eq!(header.version, 42);
107 assert_eq!(header.first_ifd_offset, 8);
108 assert!(!header.is_bigtiff());
109 }
110
111 #[test]
112 fn parse_big_endian_classic() {
113 let data = b"MM\x00\x2a\x00\x00\x00\x08";
115 let header = TiffHeader::parse(data).unwrap();
116 assert_eq!(header.byte_order, ByteOrder::BigEndian);
117 assert_eq!(header.version, 42);
118 assert_eq!(header.first_ifd_offset, 8);
119 }
120
121 #[test]
122 fn parse_bigtiff() {
123 let mut data = Vec::new();
125 data.extend_from_slice(b"II"); data.extend_from_slice(&43u16.to_le_bytes()); data.extend_from_slice(&8u16.to_le_bytes()); data.extend_from_slice(&0u16.to_le_bytes()); data.extend_from_slice(&16u64.to_le_bytes()); let header = TiffHeader::parse(&data).unwrap();
131 assert!(header.is_bigtiff());
132 assert_eq!(header.first_ifd_offset, 16);
133 }
134
135 #[test]
136 fn reject_invalid_magic() {
137 let data = b"XX\x2a\x00\x08\x00\x00\x00";
138 assert!(TiffHeader::parse(data).is_err());
139 }
140
141 #[test]
142 fn rejects_invalid_bigtiff_offset_size_and_reserved_field() {
143 let mut data = Vec::new();
144 data.extend_from_slice(b"II");
145 data.extend_from_slice(&43u16.to_le_bytes());
146 data.extend_from_slice(&4u16.to_le_bytes());
147 data.extend_from_slice(&0u16.to_le_bytes());
148 data.extend_from_slice(&16u64.to_le_bytes());
149 assert!(matches!(
150 TiffHeader::parse(&data),
151 Err(Error::InvalidBigTiffHeader(message)) if message.contains("offset size")
152 ));
153
154 data[4..6].copy_from_slice(&8u16.to_le_bytes());
155 data[6..8].copy_from_slice(&1u16.to_le_bytes());
156 assert!(matches!(
157 TiffHeader::parse(&data),
158 Err(Error::InvalidBigTiffHeader(message)) if message.contains("reserved")
159 ));
160 }
161}