1use crate::error::{Error, Result};
2
3#[derive(Debug, Clone, Copy, PartialEq, Eq)]
5pub enum ByteOrder {
6 LittleEndian,
7 BigEndian,
8}
9
10#[derive(Debug, Clone)]
12pub struct TiffHeader {
13 pub byte_order: ByteOrder,
14 pub version: u16,
16 pub first_ifd_offset: u64,
18}
19
20impl TiffHeader {
21 pub fn is_bigtiff(&self) -> bool {
23 self.version == 43
24 }
25
26 pub fn parse(data: &[u8]) -> Result<Self> {
28 if data.len() < 8 {
29 return Err(Error::InvalidMagic);
30 }
31
32 let byte_order = match &data[0..2] {
33 b"II" => ByteOrder::LittleEndian,
34 b"MM" => ByteOrder::BigEndian,
35 _ => return Err(Error::InvalidMagic),
36 };
37
38 let read_u16 = |offset: usize| -> u16 {
39 let bytes = [data[offset], data[offset + 1]];
40 match byte_order {
41 ByteOrder::LittleEndian => u16::from_le_bytes(bytes),
42 ByteOrder::BigEndian => u16::from_be_bytes(bytes),
43 }
44 };
45
46 let version = read_u16(2);
47
48 match version {
49 42 => {
50 let read_u32 = |offset: usize| -> u32 {
52 let bytes: [u8; 4] = data[offset..offset + 4].try_into().unwrap();
53 match byte_order {
54 ByteOrder::LittleEndian => u32::from_le_bytes(bytes),
55 ByteOrder::BigEndian => u32::from_be_bytes(bytes),
56 }
57 };
58 let first_ifd_offset = read_u32(4) as u64;
59 Ok(Self {
60 byte_order,
61 version,
62 first_ifd_offset,
63 })
64 }
65 43 => {
66 if data.len() < 16 {
68 return Err(Error::InvalidMagic);
69 }
70 let offset_size = read_u16(4);
71 if offset_size != 8 {
72 return Err(Error::UnsupportedVersion(version));
73 }
74 let read_u64 = |offset: usize| -> u64 {
76 let bytes: [u8; 8] = data[offset..offset + 8].try_into().unwrap();
77 match byte_order {
78 ByteOrder::LittleEndian => u64::from_le_bytes(bytes),
79 ByteOrder::BigEndian => u64::from_be_bytes(bytes),
80 }
81 };
82 let first_ifd_offset = read_u64(8);
83 Ok(Self {
84 byte_order,
85 version,
86 first_ifd_offset,
87 })
88 }
89 _ => Err(Error::UnsupportedVersion(version)),
90 }
91 }
92}
93
94#[cfg(test)]
95mod tests {
96 use super::*;
97
98 #[test]
99 fn parse_little_endian_classic() {
100 let data = b"II\x2a\x00\x08\x00\x00\x00";
102 let header = TiffHeader::parse(data).unwrap();
103 assert_eq!(header.byte_order, ByteOrder::LittleEndian);
104 assert_eq!(header.version, 42);
105 assert_eq!(header.first_ifd_offset, 8);
106 assert!(!header.is_bigtiff());
107 }
108
109 #[test]
110 fn parse_big_endian_classic() {
111 let data = b"MM\x00\x2a\x00\x00\x00\x08";
113 let header = TiffHeader::parse(data).unwrap();
114 assert_eq!(header.byte_order, ByteOrder::BigEndian);
115 assert_eq!(header.version, 42);
116 assert_eq!(header.first_ifd_offset, 8);
117 }
118
119 #[test]
120 fn parse_bigtiff() {
121 let mut data = Vec::new();
123 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();
129 assert!(header.is_bigtiff());
130 assert_eq!(header.first_ifd_offset, 16);
131 }
132
133 #[test]
134 fn reject_invalid_magic() {
135 let data = b"XX\x2a\x00\x08\x00\x00\x00";
136 assert!(TiffHeader::parse(data).is_err());
137 }
138}