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::UnsupportedVersion(version));
68 }
69 let read_u64 = |offset: usize| -> u64 {
71 let bytes: [u8; 8] = data[offset..offset + 8].try_into().unwrap();
72 match byte_order {
73 ByteOrder::LittleEndian => u64::from_le_bytes(bytes),
74 ByteOrder::BigEndian => u64::from_be_bytes(bytes),
75 }
76 };
77 let first_ifd_offset = read_u64(8);
78 Ok(Self {
79 byte_order,
80 version,
81 first_ifd_offset,
82 })
83 }
84 _ => Err(Error::UnsupportedVersion(version)),
85 }
86 }
87}
88
89#[cfg(test)]
90mod tests {
91 use super::*;
92
93 #[test]
94 fn parse_little_endian_classic() {
95 let data = b"II\x2a\x00\x08\x00\x00\x00";
97 let header = TiffHeader::parse(data).unwrap();
98 assert_eq!(header.byte_order, ByteOrder::LittleEndian);
99 assert_eq!(header.version, 42);
100 assert_eq!(header.first_ifd_offset, 8);
101 assert!(!header.is_bigtiff());
102 }
103
104 #[test]
105 fn parse_big_endian_classic() {
106 let data = b"MM\x00\x2a\x00\x00\x00\x08";
108 let header = TiffHeader::parse(data).unwrap();
109 assert_eq!(header.byte_order, ByteOrder::BigEndian);
110 assert_eq!(header.version, 42);
111 assert_eq!(header.first_ifd_offset, 8);
112 }
113
114 #[test]
115 fn parse_bigtiff() {
116 let mut data = Vec::new();
118 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();
124 assert!(header.is_bigtiff());
125 assert_eq!(header.first_ifd_offset, 16);
126 }
127
128 #[test]
129 fn reject_invalid_magic() {
130 let data = b"XX\x2a\x00\x08\x00\x00\x00";
131 assert!(TiffHeader::parse(data).is_err());
132 }
133}