1use otf_pixels_core::{PixelsError, Result};
23
24#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash)]
26pub enum ByteOrder {
27 Little,
29 Big,
31}
32
33impl ByteOrder {
34 #[must_use]
36 pub fn u16(self, data: &[u8], at: usize) -> u16 {
37 let a = data.get(at).copied().unwrap_or(0);
38 let b = data.get(at + 1).copied().unwrap_or(0);
39 match self {
40 Self::Little => u16::from_le_bytes([a, b]),
41 Self::Big => u16::from_be_bytes([a, b]),
42 }
43 }
44
45 #[must_use]
47 pub fn u32(self, data: &[u8], at: usize) -> u32 {
48 let mut bytes = [0_u8; 4];
49 for (slot, offset) in bytes.iter_mut().zip(0..4) {
50 *slot = data.get(at + offset).copied().unwrap_or(0);
51 }
52 match self {
53 Self::Little => u32::from_le_bytes(bytes),
54 Self::Big => u32::from_be_bytes(bytes),
55 }
56 }
57
58 #[must_use]
60 pub const fn write_u16(self, value: u16) -> [u8; 2] {
61 match self {
62 Self::Little => value.to_le_bytes(),
63 Self::Big => value.to_be_bytes(),
64 }
65 }
66
67 #[must_use]
69 pub const fn write_u32(self, value: u32) -> [u8; 4] {
70 match self {
71 Self::Little => value.to_le_bytes(),
72 Self::Big => value.to_be_bytes(),
73 }
74 }
75}
76
77pub mod tag {
79 pub const IMAGE_WIDTH: u16 = 256;
81 pub const IMAGE_LENGTH: u16 = 257;
83 pub const BITS_PER_SAMPLE: u16 = 258;
85 pub const COMPRESSION: u16 = 259;
87 pub const PHOTOMETRIC: u16 = 262;
89 pub const STRIP_OFFSETS: u16 = 273;
91 pub const ORIENTATION: u16 = 274;
93 pub const ICC_PROFILE: u16 = 34_675;
95 pub const SAMPLES_PER_PIXEL: u16 = 277;
97 pub const ROWS_PER_STRIP: u16 = 278;
99 pub const STRIP_BYTE_COUNTS: u16 = 279;
101 pub const COLOR_MAP: u16 = 320;
103 pub const PLANAR_CONFIG: u16 = 284;
105 pub const PREDICTOR: u16 = 317;
107 pub const TILE_WIDTH: u16 = 322;
109 pub const TILE_LENGTH: u16 = 323;
111 pub const TILE_OFFSETS: u16 = 324;
113 pub const TILE_BYTE_COUNTS: u16 = 325;
115 pub const EXTRA_SAMPLES: u16 = 338;
117 pub const SAMPLE_FORMAT: u16 = 339;
119}
120
121#[derive(Debug, Clone, Copy, PartialEq, Eq)]
123pub enum FieldType {
124 Byte,
126 Ascii,
128 Short,
130 Long,
132 Rational,
134 Other(u16, usize),
136}
137
138impl FieldType {
139 #[must_use]
141 pub const fn from_code(code: u16) -> Self {
142 match code {
143 1 => Self::Byte,
144 2 => Self::Ascii,
145 3 => Self::Short,
146 4 => Self::Long,
147 5 => Self::Rational,
148 6 => Self::Other(code, 1),
152 7 => Self::Other(code, 1),
153 8 => Self::Other(code, 2),
154 9 => Self::Other(code, 4),
155 10 => Self::Other(code, 8),
156 11 => Self::Other(code, 4),
157 12 => Self::Other(code, 8),
158 other => Self::Other(other, 0),
159 }
160 }
161
162 #[must_use]
164 pub const fn size(self) -> usize {
165 match self {
166 Self::Byte | Self::Ascii => 1,
167 Self::Short => 2,
168 Self::Long => 4,
169 Self::Rational => 8,
170 Self::Other(_, size) => size,
171 }
172 }
173}
174
175#[derive(Debug, Clone)]
177pub struct Entry {
178 pub tag: u16,
180 pub field_type: FieldType,
182 pub values: Vec<u32>,
185}
186
187impl Entry {
188 #[must_use]
190 pub fn first(&self) -> Option<u32> {
191 self.values.first().copied()
192 }
193}
194
195#[derive(Debug, Clone)]
197pub struct Directory {
198 entries: Vec<Entry>,
199 next: u32,
201}
202
203const MAX_VALUES: usize = 16 * 1024 * 1024;
209
210impl Directory {
211 pub fn parse(data: &[u8], order: ByteOrder, offset: usize) -> Result<Self> {
218 let count = order.u16(data, offset) as usize;
219 if data.len() < offset + 2 + count * 12 + 4 {
220 return Err(PixelsError::malformed(
221 "tiff",
222 format!("directory of {count} entries runs past the end of the file"),
223 ));
224 }
225
226 let mut entries = Vec::with_capacity(count);
227 for index in 0..count {
228 let at = offset + 2 + index * 12;
229 let tag = order.u16(data, at);
230 let field_type = FieldType::from_code(order.u16(data, at + 2));
231 let value_count = order.u32(data, at + 4) as usize;
232 let size = field_type.size();
233
234 if size == 0 || value_count > MAX_VALUES {
235 entries.push(Entry {
239 tag,
240 field_type,
241 values: Vec::new(),
242 });
243 continue;
244 }
245
246 let total = value_count.saturating_mul(size);
247 let values_at = if total <= 4 {
251 at + 8
252 } else {
253 order.u32(data, at + 8) as usize
254 };
255
256 let mut values = Vec::with_capacity(value_count.min(4096));
257 for value_index in 0..value_count {
258 let value_at = values_at + value_index * size;
259 if value_at + size > data.len() {
260 break;
261 }
262 let value = match field_type {
263 FieldType::Byte | FieldType::Ascii | FieldType::Other(7, _) => {
266 u32::from(data.get(value_at).copied().unwrap_or(0))
267 }
268 FieldType::Short => u32::from(order.u16(data, value_at)),
269 FieldType::Long => order.u32(data, value_at),
270 FieldType::Rational => order.u32(data, value_at),
274 FieldType::Other(..) => break,
275 };
276 values.push(value);
277 }
278
279 entries.push(Entry {
280 tag,
281 field_type,
282 values,
283 });
284 }
285
286 let next = order.u32(data, offset + 2 + count * 12);
287 Ok(Self { entries, next })
288 }
289
290 #[must_use]
292 pub fn get(&self, tag: u16) -> Option<&Entry> {
293 self.entries.iter().find(|entry| entry.tag == tag)
294 }
295
296 #[must_use]
298 pub fn value(&self, tag: u16) -> Option<u32> {
299 self.get(tag).and_then(Entry::first)
300 }
301
302 #[must_use]
304 pub fn value_or(&self, tag: u16, default: u32) -> u32 {
305 self.value(tag).unwrap_or(default)
306 }
307
308 #[must_use]
310 pub fn values(&self, tag: u16) -> &[u32] {
311 self.get(tag).map_or(&[], |entry| &entry.values)
312 }
313
314 pub fn require(&self, tag: u16, name: &str) -> Result<u32> {
320 self.value(tag)
321 .ok_or_else(|| PixelsError::malformed("tiff", format!("missing required tag {name}")))
322 }
323
324 #[must_use]
326 pub const fn next_offset(&self) -> Option<u32> {
327 if self.next == 0 {
328 None
329 } else {
330 Some(self.next)
331 }
332 }
333
334 #[must_use]
336 pub fn len(&self) -> usize {
337 self.entries.len()
338 }
339
340 #[must_use]
342 pub fn is_empty(&self) -> bool {
343 self.entries.is_empty()
344 }
345}
346
347pub fn parse_header(data: &[u8]) -> Result<(ByteOrder, usize)> {
354 let order = match data.get(..2) {
355 Some(b"II") => ByteOrder::Little,
356 Some(b"MM") => ByteOrder::Big,
357 _ => {
358 return Err(PixelsError::malformed(
359 "tiff",
360 "byte-order mark is neither II nor MM",
361 ));
362 }
363 };
364 let magic = order.u16(data, 2);
367 if magic != 42 {
368 return Err(PixelsError::malformed(
369 "tiff",
370 format!("magic number is {magic}, not 42"),
371 ));
372 }
373 Ok((order, order.u32(data, 4) as usize))
374}
375
376#[must_use]
378pub fn probe(prefix: &[u8]) -> bool {
379 parse_header(prefix).is_ok()
380}
381
382#[cfg(test)]
383#[allow(
384 clippy::unwrap_used,
385 clippy::expect_used,
386 clippy::indexing_slicing,
387 clippy::panic,
388 reason = "tests operate on known-good values and assert shapes directly"
389)]
390mod tests {
391 use super::*;
392
393 fn build(order: ByteOrder, entries: &[(u16, FieldType, Vec<u32>)]) -> Vec<u8> {
395 let mut out = Vec::new();
396 out.extend_from_slice(if order == ByteOrder::Little {
397 b"II"
398 } else {
399 b"MM"
400 });
401 out.extend_from_slice(&order.write_u16(42));
402 out.extend_from_slice(&order.write_u32(8));
403
404 let mut directory = Vec::new();
406 directory.extend_from_slice(&order.write_u16(entries.len() as u16));
407 let heap_start = 8 + 2 + entries.len() * 12 + 4;
408 let mut heap = Vec::new();
409
410 for (tag, field_type, values) in entries {
411 directory.extend_from_slice(&order.write_u16(*tag));
412 let code = match field_type {
413 FieldType::Byte => 1,
414 FieldType::Ascii => 2,
415 FieldType::Short => 3,
416 FieldType::Long => 4,
417 FieldType::Rational => 5,
418 FieldType::Other(code, _) => *code,
419 };
420 directory.extend_from_slice(&order.write_u16(code));
421 directory.extend_from_slice(&order.write_u32(values.len() as u32));
422
423 let size = field_type.size();
424 let total = values.len() * size;
425 let mut encoded = Vec::new();
426 for &value in values {
427 match size {
428 1 => encoded.push(value as u8),
429 2 => encoded.extend_from_slice(&order.write_u16(value as u16)),
430 _ => encoded.extend_from_slice(&order.write_u32(value)),
431 }
432 }
433 if total <= 4 {
434 encoded.resize(4, 0);
435 directory.extend_from_slice(&encoded);
436 } else {
437 directory.extend_from_slice(&order.write_u32((heap_start + heap.len()) as u32));
438 heap.extend_from_slice(&encoded);
439 }
440 }
441 directory.extend_from_slice(&order.write_u32(0));
442
443 out.extend_from_slice(&directory);
444 out.extend_from_slice(&heap);
445 out
446 }
447
448 #[test]
449 fn both_byte_orders_parse() {
450 for order in [ByteOrder::Little, ByteOrder::Big] {
453 let bytes = build(order, &[(tag::IMAGE_WIDTH, FieldType::Long, vec![640])]);
454 let (parsed_order, offset) = parse_header(&bytes).unwrap();
455 assert_eq!(parsed_order, order);
456 let directory = Directory::parse(&bytes, order, offset).unwrap();
457 assert_eq!(directory.value(tag::IMAGE_WIDTH), Some(640), "{order:?}");
458 }
459 }
460
461 #[test]
462 fn a_wrong_magic_number_is_rejected() {
463 let mut bytes = build(ByteOrder::Little, &[]);
464 bytes[2] = 43;
465 assert!(parse_header(&bytes).is_err());
466 }
467
468 #[test]
469 fn a_bad_byte_order_mark_is_rejected() {
470 let mut bytes = build(ByteOrder::Little, &[]);
471 bytes[0] = b'X';
472 assert!(parse_header(&bytes).is_err());
473 }
474
475 #[test]
476 fn small_values_live_inline_and_large_ones_are_offsets() {
477 for order in [ByteOrder::Little, ByteOrder::Big] {
480 let inline = build(
482 order,
483 &[(tag::BITS_PER_SAMPLE, FieldType::Short, vec![8, 8])],
484 );
485 let (o, at) = parse_header(&inline).unwrap();
486 let directory = Directory::parse(&inline, o, at).unwrap();
487 assert_eq!(directory.values(tag::BITS_PER_SAMPLE), &[8, 8], "{order:?}");
488
489 let offset = build(
490 order,
491 &[(tag::BITS_PER_SAMPLE, FieldType::Short, vec![8, 8, 8])],
492 );
493 let (o, at) = parse_header(&offset).unwrap();
494 let directory = Directory::parse(&offset, o, at).unwrap();
495 assert_eq!(
496 directory.values(tag::BITS_PER_SAMPLE),
497 &[8, 8, 8],
498 "{order:?}"
499 );
500 }
501 }
502
503 #[test]
504 fn every_field_type_width_reads_correctly() {
505 let bytes = build(
506 ByteOrder::Little,
507 &[
508 (100, FieldType::Byte, vec![1, 2, 3]),
509 (101, FieldType::Short, vec![1000, 2000]),
510 (102, FieldType::Long, vec![100_000]),
511 ],
512 );
513 let (order, at) = parse_header(&bytes).unwrap();
514 let directory = Directory::parse(&bytes, order, at).unwrap();
515 assert_eq!(directory.values(100), &[1, 2, 3]);
516 assert_eq!(directory.values(101), &[1000, 2000]);
517 assert_eq!(directory.values(102), &[100_000]);
518 }
519
520 #[test]
521 fn an_unknown_tag_is_kept_but_not_fatal() {
522 let bytes = build(
525 ByteOrder::Little,
526 &[
527 (60_000, FieldType::Long, vec![7]),
528 (tag::IMAGE_WIDTH, FieldType::Long, vec![32]),
529 ],
530 );
531 let (order, at) = parse_header(&bytes).unwrap();
532 let directory = Directory::parse(&bytes, order, at).unwrap();
533 assert_eq!(directory.value(60_000), Some(7));
534 assert_eq!(
535 directory.value(tag::IMAGE_WIDTH),
536 Some(32),
537 "an unknown tag desynchronised the directory"
538 );
539 }
540
541 #[test]
542 fn an_unknown_field_type_does_not_desynchronise_the_directory() {
543 let bytes = build(
546 ByteOrder::Little,
547 &[
548 (60_001, FieldType::Other(31_000, 0), vec![]),
549 (tag::IMAGE_LENGTH, FieldType::Long, vec![48]),
550 ],
551 );
552 let (order, at) = parse_header(&bytes).unwrap();
553 let directory = Directory::parse(&bytes, order, at).unwrap();
554 assert_eq!(directory.value(tag::IMAGE_LENGTH), Some(48));
555 }
556
557 #[test]
558 fn a_missing_required_tag_names_itself() {
559 let bytes = build(ByteOrder::Little, &[]);
560 let (order, at) = parse_header(&bytes).unwrap();
561 let directory = Directory::parse(&bytes, order, at).unwrap();
562 let error = directory
563 .require(tag::IMAGE_WIDTH, "ImageWidth")
564 .unwrap_err();
565 assert!(error.to_string().contains("ImageWidth"), "{error}");
566 }
567
568 #[test]
569 fn a_directory_running_past_the_file_is_an_error() {
570 let mut bytes = build(
571 ByteOrder::Little,
572 &[(tag::IMAGE_WIDTH, FieldType::Long, vec![1])],
573 );
574 bytes[8] = 100;
576 let (order, at) = parse_header(&bytes).unwrap();
577 assert!(Directory::parse(&bytes, order, at).is_err());
578 }
579
580 #[test]
581 fn a_value_offset_past_the_file_truncates_rather_than_panicking() {
582 let mut bytes = build(
583 ByteOrder::Little,
584 &[(tag::STRIP_OFFSETS, FieldType::Long, vec![1, 2, 3])],
585 );
586 let at = 8 + 2 + 8;
588 bytes[at..at + 4].copy_from_slice(&0xFFFF_FF00_u32.to_le_bytes());
589 let (order, start) = parse_header(&bytes).unwrap();
590 let directory = Directory::parse(&bytes, order, start).unwrap();
591 assert!(
592 directory.values(tag::STRIP_OFFSETS).is_empty(),
593 "an out-of-range offset should yield no values"
594 );
595 }
596
597 #[test]
598 fn arbitrary_bytes_never_panic() {
599 let mut seed = 0x2468_ACE0_u32;
600 for _ in 0..2000 {
601 let len = (seed % 300) as usize + 8;
602 let data: Vec<u8> = (0..len)
603 .map(|_| {
604 seed = seed.wrapping_mul(1_664_525).wrapping_add(1_013_904_223);
605 (seed >> 16) as u8
606 })
607 .collect();
608 if let Ok((order, offset)) = parse_header(&data) {
609 let _ = Directory::parse(&data, order, offset);
610 }
611 }
612 }
613
614 #[test]
615 fn probe_accepts_only_a_real_header() {
616 assert!(probe(b"II\x2a\x00\x08\x00\x00\x00"));
617 assert!(probe(b"MM\x00\x2a\x00\x00\x00\x08"));
618 assert!(!probe(b"II\x2b\x00\x08\x00\x00\x00"), "BigTIFF is not v1");
619 assert!(!probe(b"GIF89a"));
620 assert!(!probe(b""));
621 }
622}