1use crate::types::{ExifError, Result};
8
9#[derive(Debug, Clone, Copy, PartialEq, Eq)]
12#[repr(u8)]
13pub enum TiffFormat {
14 Byte = 1, Ascii = 2, Short = 3, Long = 4, Rational = 5, SByte = 6, Undefined = 7, SShort = 8, SLong = 9, SRational = 10, Float = 11, Double = 12, Ifd = 13, }
28
29impl TiffFormat {
30 pub fn byte_size(self) -> usize {
33 match self {
34 TiffFormat::Byte | TiffFormat::Ascii | TiffFormat::SByte | TiffFormat::Undefined => 1,
35 TiffFormat::Short | TiffFormat::SShort => 2,
36 TiffFormat::Long | TiffFormat::SLong | TiffFormat::Float | TiffFormat::Ifd => 4,
37 TiffFormat::Rational | TiffFormat::SRational | TiffFormat::Double => 8,
38 }
39 }
40
41 pub fn from_u16(format: u16) -> Result<Self> {
44 match format {
45 1 => Ok(TiffFormat::Byte),
46 2 => Ok(TiffFormat::Ascii),
47 3 => Ok(TiffFormat::Short),
48 4 => Ok(TiffFormat::Long),
49 5 => Ok(TiffFormat::Rational),
50 6 => Ok(TiffFormat::SByte),
51 7 => Ok(TiffFormat::Undefined),
52 8 => Ok(TiffFormat::SShort),
53 9 => Ok(TiffFormat::SLong),
54 10 => Ok(TiffFormat::SRational),
55 11 => Ok(TiffFormat::Float),
56 12 => Ok(TiffFormat::Double),
57 13 => Ok(TiffFormat::Ifd),
58 _ => Err(ExifError::ParseError(format!(
59 "Invalid TIFF format type: {format}"
60 ))),
61 }
62 }
63}
64
65#[derive(Debug, Clone, Copy, PartialEq, Eq)]
68pub enum ByteOrder {
69 LittleEndian, BigEndian, }
72
73impl ByteOrder {
74 pub fn read_u16(self, data: &[u8], offset: usize) -> Result<u16> {
76 if offset + 2 > data.len() {
77 return Err(ExifError::ParseError("Not enough data for u16".to_string()));
78 }
79 let bytes = &data[offset..offset + 2];
80 Ok(match self {
81 ByteOrder::LittleEndian => u16::from_le_bytes([bytes[0], bytes[1]]),
82 ByteOrder::BigEndian => u16::from_be_bytes([bytes[0], bytes[1]]),
83 })
84 }
85
86 pub fn read_u32(self, data: &[u8], offset: usize) -> Result<u32> {
88 if offset + 4 > data.len() {
89 return Err(ExifError::ParseError("Not enough data for u32".to_string()));
90 }
91 let bytes = &data[offset..offset + 4];
92 Ok(match self {
93 ByteOrder::LittleEndian => u32::from_le_bytes([bytes[0], bytes[1], bytes[2], bytes[3]]),
94 ByteOrder::BigEndian => u32::from_be_bytes([bytes[0], bytes[1], bytes[2], bytes[3]]),
95 })
96 }
97}
98
99#[derive(Debug, Clone)]
102pub struct TiffHeader {
103 pub byte_order: ByteOrder,
104 pub magic: u16, pub ifd0_offset: u32, }
107
108impl TiffHeader {
109 pub fn parse(data: &[u8]) -> Result<Self> {
112 if data.len() < 8 {
113 return Err(ExifError::ParseError(
114 "TIFF header too short (need 8 bytes)".to_string(),
115 ));
116 }
117
118 let byte_order = match &data[0..2] {
120 [0x49, 0x49] => ByteOrder::LittleEndian, [0x4D, 0x4D] => ByteOrder::BigEndian, _ => {
123 return Err(ExifError::ParseError(
124 "Invalid TIFF byte order marker".to_string(),
125 ))
126 }
127 };
128
129 let magic = byte_order.read_u16(data, 2)?;
132 if magic != 42 && magic != 85 {
133 return Err(ExifError::ParseError(format!(
134 "Invalid TIFF magic number: {magic} (expected 42 or 85 for RW2)"
135 )));
136 }
137
138 let ifd0_offset = byte_order.read_u32(data, 4)?;
140
141 Ok(TiffHeader {
142 byte_order,
143 magic,
144 ifd0_offset,
145 })
146 }
147}
148
149#[derive(Debug, Clone)]
152pub struct IfdEntry {
153 pub tag_id: u16,
154 pub format: TiffFormat,
155 pub count: u32,
156 pub value_or_offset: u32,
157}
158
159impl IfdEntry {
160 pub fn parse(data: &[u8], offset: usize, byte_order: ByteOrder) -> Result<Self> {
163 if offset + 12 > data.len() {
164 return Err(ExifError::ParseError(
165 "Not enough data for IFD entry".to_string(),
166 ));
167 }
168
169 let tag_id = byte_order.read_u16(data, offset)?;
170 let format_num = byte_order.read_u16(data, offset + 2)?;
171 let format = TiffFormat::from_u16(format_num)?;
172 let count = byte_order.read_u32(data, offset + 4)?;
173 let value_or_offset = byte_order.read_u32(data, offset + 8)?;
174
175 Ok(IfdEntry {
176 tag_id,
177 format,
178 count,
179 value_or_offset,
180 })
181 }
182
183 pub fn data_size(&self) -> u32 {
186 self.count.saturating_mul(self.format.byte_size() as u32)
188 }
189
190 pub fn is_inline(&self) -> bool {
193 self.data_size() <= 4
194 }
195}
196
197#[cfg(test)]
198mod tests {
199 use super::*;
200
201 #[test]
202 fn test_tiff_format_byte_size() {
203 assert_eq!(TiffFormat::Byte.byte_size(), 1);
204 assert_eq!(TiffFormat::Short.byte_size(), 2);
205 assert_eq!(TiffFormat::Long.byte_size(), 4);
206 assert_eq!(TiffFormat::Rational.byte_size(), 8);
207 }
208
209 #[test]
210 fn test_tiff_format_from_u16() {
211 assert_eq!(TiffFormat::from_u16(1).unwrap(), TiffFormat::Byte);
212 assert_eq!(TiffFormat::from_u16(2).unwrap(), TiffFormat::Ascii);
213 assert_eq!(TiffFormat::from_u16(3).unwrap(), TiffFormat::Short);
214 assert!(TiffFormat::from_u16(99).is_err());
215 }
216
217 #[test]
218 fn test_byte_order_read() {
219 let data = [0x12, 0x34, 0x56, 0x78];
220
221 let le = ByteOrder::LittleEndian;
223 assert_eq!(le.read_u16(&data, 0).unwrap(), 0x3412);
224 assert_eq!(le.read_u32(&data, 0).unwrap(), 0x78563412);
225
226 let be = ByteOrder::BigEndian;
228 assert_eq!(be.read_u16(&data, 0).unwrap(), 0x1234);
229 assert_eq!(be.read_u32(&data, 0).unwrap(), 0x12345678);
230 }
231
232 #[test]
233 fn test_tiff_header_parse() {
234 let le_data = [
236 0x49, 0x49, 0x2A, 0x00, 0x08, 0x00, 0x00, 0x00, ];
240
241 let header = TiffHeader::parse(&le_data).unwrap();
242 assert_eq!(header.byte_order, ByteOrder::LittleEndian);
243 assert_eq!(header.magic, 42);
244 assert_eq!(header.ifd0_offset, 8);
245
246 let be_data = [
248 0x4D, 0x4D, 0x00, 0x2A, 0x00, 0x00, 0x00, 0x08, ];
252
253 let header = TiffHeader::parse(&be_data).unwrap();
254 assert_eq!(header.byte_order, ByteOrder::BigEndian);
255 assert_eq!(header.magic, 42);
256 assert_eq!(header.ifd0_offset, 8);
257 }
258
259 #[test]
260 fn test_ifd_entry_parse() {
261 let data = [
262 0x01, 0x0F, 0x00, 0x02, 0x00, 0x00, 0x00, 0x06, 0x00, 0x00, 0x00, 0x26, ];
267
268 let entry = IfdEntry::parse(&data, 0, ByteOrder::BigEndian).unwrap();
269 assert_eq!(entry.tag_id, 0x010F);
270 assert_eq!(entry.format, TiffFormat::Ascii);
271 assert_eq!(entry.count, 6);
272 assert_eq!(entry.value_or_offset, 0x26);
273 assert!(!entry.is_inline()); }
275}