1use alloc::borrow::Cow;
18use alloc::collections::{BTreeMap, BTreeSet, VecDeque};
19use alloc::format;
20use alloc::string::String;
21use alloc::vec::Vec;
22use core::cell::Cell;
23use core::ops::Range;
24
25use zencodec::inventory::{
26 Disposition, Inventory, InventoryError, Part, PartId, PartKind, PartTag,
27};
28
29use super::tags::tag_name;
30
31const MAX_IFDS: usize = 4096;
36pub const BUDGET_GAP: &str = "the walk stopped at a budget; the decoder may read these bytes";
39const MAX_NOTES: usize = 4;
41const MAX_PIECES: usize = 64;
43const MAX_ROLES: usize = 8;
45
46pub const STRIP_OFFSETS: u16 = 273;
49pub const STRIP_BYTE_COUNTS: u16 = 279;
50pub const FREE_OFFSETS: u16 = 288;
51pub const FREE_BYTE_COUNTS: u16 = 289;
52pub const TILE_OFFSETS: u16 = 324;
53pub const TILE_BYTE_COUNTS: u16 = 325;
54pub const SUB_IFDS: u16 = 330;
55pub const JPEG_IF_OFFSET: u16 = 513;
56pub const JPEG_IF_LENGTH: u16 = 514;
57pub const JPEG_Q_TABLES: u16 = 519;
58pub const JPEG_DC_TABLES: u16 = 520;
59pub const JPEG_AC_TABLES: u16 = 521;
60pub const EXIF_IFD: u16 = 34665;
61pub const GPS_IFD: u16 = 34853;
62pub const INTEROP_IFD: u16 = 40965;
63
64pub fn get(d: &[u8], at: u64, n: u64) -> Option<&[u8]> {
67 let s = usize::try_from(at).ok()?;
68 let e = s.checked_add(usize::try_from(n).ok()?)?;
69 d.get(s..e)
70}
71
72#[derive(Clone, Copy, Debug)]
74pub struct Layout {
75 pub le: bool,
76 pub big: bool,
77}
78
79impl Layout {
80 pub fn u16(self, d: &[u8], at: u64) -> Option<u16> {
81 let b: [u8; 2] = get(d, at, 2)?.try_into().ok()?;
82 Some(if self.le {
83 u16::from_le_bytes(b)
84 } else {
85 u16::from_be_bytes(b)
86 })
87 }
88
89 pub fn u32(self, d: &[u8], at: u64) -> Option<u32> {
90 let b: [u8; 4] = get(d, at, 4)?.try_into().ok()?;
91 Some(if self.le {
92 u32::from_le_bytes(b)
93 } else {
94 u32::from_be_bytes(b)
95 })
96 }
97
98 pub fn u64(self, d: &[u8], at: u64) -> Option<u64> {
99 let b: [u8; 8] = get(d, at, 8)?.try_into().ok()?;
100 Some(if self.le {
101 u64::from_le_bytes(b)
102 } else {
103 u64::from_be_bytes(b)
104 })
105 }
106
107 pub fn offset(self, d: &[u8], at: u64) -> Option<u64> {
109 if self.big {
110 self.u64(d, at)
111 } else {
112 self.u32(d, at).map(u64::from)
113 }
114 }
115
116 pub fn header_len(self) -> u64 {
117 if self.big { 16 } else { 8 }
118 }
119
120 pub fn count_len(self) -> u64 {
121 if self.big { 8 } else { 2 }
122 }
123
124 pub fn entry_len(self) -> u64 {
125 if self.big { 20 } else { 12 }
126 }
127
128 pub fn inline_cap(self) -> u64 {
130 if self.big { 8 } else { 4 }
131 }
132}
133
134pub fn type_size(typ: u16) -> Option<u64> {
137 Some(match typ {
138 1 | 2 | 6 | 7 => 1,
139 3 | 8 => 2,
140 4 | 9 | 11 | 13 => 4,
141 5 | 10 | 12 | 16 | 17 | 18 => 8,
142 _ => return None,
143 })
144}
145
146pub fn type_name(typ: u16) -> Cow<'static, str> {
147 Cow::Borrowed(match typ {
148 1 => "BYTE",
149 2 => "ASCII",
150 3 => "SHORT",
151 4 => "LONG",
152 5 => "RATIONAL",
153 6 => "SBYTE",
154 7 => "UNDEFINED",
155 8 => "SSHORT",
156 9 => "SLONG",
157 10 => "SRATIONAL",
158 11 => "FLOAT",
159 12 => "DOUBLE",
160 13 => "IFD",
161 16 => "LONG8",
162 17 => "SLONG8",
163 18 => "IFD8",
164 other => return Cow::Owned(format!("type {other}")),
165 })
166}
167
168#[derive(Clone, Copy, Debug, PartialEq, Eq)]
172pub enum Kind {
173 Page(u32),
175 Sub,
177 Exif,
179 Gps,
181 Interop,
183 #[allow(dead_code)] MakerNote,
187}
188
189#[derive(Clone, Debug)]
191pub struct Entry {
192 pub at: u64,
194 pub tag: u16,
195 pub typ: u16,
196 pub count: u64,
197 pub field: u64,
199 pub notes: Vec<String>,
201}
202
203pub enum Loc {
205 UnknownType,
207 Overflow,
209 Empty,
211 Inline,
213 At(u64, u64),
215}
216
217#[derive(Debug)]
219pub struct Ifd {
220 pub at: u64,
221 pub end: u64,
223 pub kind: Kind,
224 pub name: String,
225 pub followed: bool,
227 pub entries: Vec<Entry>,
228 pub next: u64,
230 pub problem: Option<String>,
232 pub notes: Vec<String>,
234 pub parent: Option<(usize, usize)>,
236 pub roles: Vec<(Kind, bool)>,
240 pub last: BTreeMap<u16, usize>,
243 pub first_unknown: Option<usize>,
245 pub depth: u8,
248}
249
250impl Ifd {
251 #[allow(dead_code)] pub fn is(&self, kind: Kind) -> bool {
254 self.roles.iter().any(|&(k, _)| k == kind)
255 }
256
257 pub fn followed_as(&self, kind: Kind) -> bool {
259 self.roles.iter().any(|&(k, f)| k == kind && f)
260 }
261
262 pub fn page(&self) -> Option<u32> {
264 self.roles.iter().find_map(|&(k, _)| match k {
265 Kind::Page(n) => Some(n),
266 _ => None,
267 })
268 }
269
270 pub fn holds_image(&self) -> bool {
273 self.roles
274 .iter()
275 .any(|&(k, _)| matches!(k, Kind::Page(_) | Kind::Sub))
276 }
277}
278
279pub fn add_note(notes: &mut Vec<String>, text: String) {
281 if notes.len() < MAX_NOTES {
282 notes.push(text);
283 } else if notes.len() == MAX_NOTES {
284 notes.push("further notes not listed".into());
285 }
286}
287
288pub struct Walk<'a> {
290 pub d: &'a [u8],
292 pub base: u64,
294 pub lay: Layout,
295 pub magic: u16,
297 pub header: Range<u64>,
298 pub header_ok: bool,
300 pub header_ext: Option<(Range<u64>, Disposition, String)>,
303 pub fatal: Option<String>,
305 pub ifds: Vec<Ifd>,
306 pub entries_left: u64,
310 tables: BTreeMap<u64, u64>,
313 by_at: BTreeMap<u64, usize>,
315 pub cancelled: bool,
317 pub budget_hit: bool,
320 #[allow(dead_code)] pub pointer_reads: u64,
324 scan_left: Cell<u64>,
327}
328
329fn entry_budget(limit: u64) -> u64 {
331 limit / 12 + 4096
332}
333
334fn scan_budget(limit: u64) -> Cell<u64> {
336 Cell::new(limit.saturating_mul(2).saturating_add(1 << 20))
337}
338
339impl Walk<'_> {
340 pub fn limit(&self) -> u64 {
341 self.d.len() as u64
342 }
343
344 pub fn take_scan(&self, n: u64) -> bool {
347 let left = self.scan_left.get();
348 if n > left {
349 return false;
350 }
351 self.scan_left.set(left - n);
352 true
353 }
354
355 fn table_overlap(&self, at: u64) -> Option<u64> {
358 use core::ops::Bound::Excluded;
359 if let Some((&s, &e)) = self.tables.range(..=at).next_back()
360 && e > at
361 {
362 return Some(s);
363 }
364 let count = if self.lay.big {
365 self.lay.u64(self.d, at)
366 } else {
367 self.lay.u16(self.d, at).map(u64::from)
368 }
369 .unwrap_or(0);
370 let end = count
371 .saturating_mul(self.lay.entry_len())
372 .saturating_add(at)
373 .saturating_add(self.lay.count_len() + self.lay.inline_cap())
374 .min(self.limit());
375 if end <= at {
378 return None;
379 }
380 self.tables
381 .range((Excluded(at), Excluded(end)))
382 .next()
383 .map(|(&s, _)| s)
384 }
385
386 pub fn abs(&self, off: u64) -> Option<u64> {
388 self.base.checked_add(off)
389 }
390
391 pub fn loc(&self, e: &Entry) -> Loc {
392 let Some(size) = type_size(e.typ) else {
393 return Loc::UnknownType;
394 };
395 let Some(total) = size.checked_mul(e.count) else {
396 return Loc::Overflow;
397 };
398 if total == 0 {
399 Loc::Empty
400 } else if total <= self.lay.inline_cap() {
401 Loc::Inline
402 } else {
403 match self.lay.offset(self.d, e.field).and_then(|o| self.abs(o)) {
404 Some(off) => Loc::At(off, total),
405 None => Loc::Overflow,
406 }
407 }
408 }
409
410 pub fn bytes(&self, e: &Entry) -> Option<&[u8]> {
412 match self.loc(e) {
413 Loc::Inline => {
414 let total = type_size(e.typ)?.checked_mul(e.count)?;
415 get(self.d, e.field, total)
416 }
417 Loc::At(off, total) => get(self.d, off, total),
418 Loc::Empty => Some(&[]),
419 _ => None,
420 }
421 }
422
423 pub fn uints(&self, e: &Entry, max: usize) -> Vec<u64> {
426 let mut out = Vec::new();
427 let size = match e.typ {
428 1 | 7 => 1,
429 3 => 2,
430 4 | 13 => 4,
431 16 | 18 => 8,
432 _ => return out,
433 };
434 let Some(bytes) = self.bytes(e) else {
435 return out;
436 };
437 for chunk in bytes.chunks_exact(size).take(max) {
438 let v = match size {
439 1 => u64::from(chunk[0]),
440 2 => u64::from(self.lay.u16(chunk, 0).unwrap_or(0)),
441 4 => u64::from(self.lay.u32(chunk, 0).unwrap_or(0)),
442 _ => self.lay.u64(chunk, 0).unwrap_or(0),
443 };
444 out.push(v);
445 }
446 out
447 }
448
449 #[allow(dead_code)] pub fn single_uint(&self, e: &Entry) -> Option<u64> {
452 if e.count != 1 {
453 return None;
454 }
455 self.uints(e, 1).first().copied()
456 }
457
458 pub fn find<'i>(&self, ifd: &'i Ifd, tag: u16) -> Option<&'i Entry> {
461 ifd.last.get(&tag).and_then(|&i| ifd.entries.get(i))
462 }
463}
464
465fn parse_ifd(
468 d: &[u8],
469 lay: Layout,
470 at: u64,
471 kind: Kind,
472 name: String,
473 followed: bool,
474 budget: &mut u64,
475) -> Option<Ifd> {
476 let len = d.len() as u64;
477 if at >= len {
478 return None;
479 }
480 let mut ifd = Ifd {
481 at,
482 end: len,
483 kind,
484 name,
485 followed,
486 entries: Vec::new(),
487 next: 0,
488 problem: None,
489 notes: Vec::new(),
490 parent: None,
491 roles: alloc::vec![(kind, followed)],
492 last: BTreeMap::new(),
493 first_unknown: None,
494 depth: 0,
495 };
496 let count = if lay.big {
497 lay.u64(d, at)
498 } else {
499 lay.u16(d, at).map(u64::from)
500 };
501 let Some(count) = count else {
502 ifd.problem = Some("entry count runs past the end of the data".into());
503 return Some(ifd);
504 };
505 let declared_end = count
506 .checked_mul(lay.entry_len())
507 .and_then(|n| n.checked_add(at))
508 .and_then(|n| n.checked_add(lay.count_len()))
509 .and_then(|n| n.checked_add(lay.inline_cap()));
510 let mut e_at = at + lay.count_len();
511 let mut i = 0u64;
512 while i < count {
513 if *budget == 0 {
514 ifd.problem = Some(
515 "not read further: the file's entry budget is spent (overlapping or repeated IFDs)"
516 .into(),
517 );
518 break;
519 }
520 let Some(e_end) = e_at.checked_add(lay.entry_len()) else {
521 break;
522 };
523 if e_end > len {
524 break;
525 }
526 let (Some(tag), Some(typ)) = (lay.u16(d, e_at), lay.u16(d, e_at + 2)) else {
527 break;
528 };
529 let (count_v, field) = if lay.big {
530 (lay.u64(d, e_at + 4), e_at + 12)
531 } else {
532 (lay.u32(d, e_at + 4).map(u64::from), e_at + 8)
533 };
534 let Some(count_v) = count_v else { break };
535 ifd.entries.push(Entry {
536 at: e_at,
537 tag,
538 typ,
539 count: count_v,
540 field,
541 notes: Vec::new(),
542 });
543 *budget -= 1;
544 e_at = e_end;
545 i += 1;
546 }
547 match declared_end {
548 Some(end) if end <= len => {
549 ifd.end = end;
550 ifd.next = lay.offset(d, end - lay.inline_cap()).unwrap_or(0);
551 }
552 _ if ifd.problem.is_none() => {
553 ifd.problem = Some(format!(
554 "declares {count} entries but runs past the end of the data"
555 ));
556 }
557 _ => {}
558 }
559 for (i, e) in ifd.entries.iter().enumerate() {
560 ifd.last.insert(e.tag, i);
561 if ifd.first_unknown.is_none() && type_size(e.typ).is_none() {
562 ifd.first_unknown = Some(i);
563 }
564 }
565 Some(ifd)
566}
567
568pub struct Fate {
572 pub d: Disposition,
573 pub note: Option<Cow<'static, str>>,
574}
575
576impl Fate {
577 pub fn new(d: Disposition) -> Self {
578 Self { d, note: None }
579 }
580
581 pub fn because(d: Disposition, note: impl Into<Cow<'static, str>>) -> Self {
582 Self {
583 d,
584 note: Some(note.into()),
585 }
586 }
587}
588
589fn rank(d: Disposition) -> u8 {
592 match d {
593 Disposition::Metadata(_) => 6,
594 Disposition::Structure | Disposition::ImageData => 5,
595 Disposition::Malformed => 4,
596 Disposition::Dropped => 3,
597 Disposition::Padding => 2,
598 _ => 1,
599 }
600}
601
602pub fn union_fate(ifd: &Ifd, fate: impl Fn(Kind, bool) -> Fate) -> Fate {
606 let mut best: Option<Fate> = None;
607 for &(kind, followed) in &ifd.roles {
608 let f = fate(kind, followed);
609 if best.as_ref().is_none_or(|b| rank(f.d) > rank(b.d)) {
610 best = Some(f);
611 }
612 }
613 best.unwrap_or_else(|| Fate::new(Disposition::Unknown))
614}
615
616pub fn kind_name(kind: Kind) -> String {
618 match kind {
619 Kind::Page(n) => format!("page {n}"),
620 Kind::Sub => "SubIFD".into(),
621 Kind::Exif => "EXIF IFD".into(),
622 Kind::Gps => "GPS IFD".into(),
623 Kind::Interop => "Interop IFD".into(),
624 Kind::MakerNote => "maker-note IFD".into(),
625 }
626}
627
628#[allow(dead_code)] pub enum Count {
631 Tag(u16),
633 ToEnd,
635 Bytes(u64),
637 JpegQuantization,
640 JpegHuffman,
644}
645
646pub struct ExtentRule {
648 pub offsets: u16,
649 pub counts: Count,
650 pub what: &'static str,
651 pub kind: PartKind,
652 pub fate: Fate,
653}
654
655pub trait Rules {
657 fn magic(&self, _magic: u16) -> bool {
659 false
660 }
661
662 fn header_extension(&self, _w: &Walk<'_>) -> Option<(u64, Fate)> {
665 None
666 }
667
668 fn value_tail(&self, _w: &Walk<'_>, _ifd: &Ifd, _index: usize) -> Option<(u64, Fate)> {
672 None
673 }
674
675 fn chunk_use(
679 &self,
680 w: &Walk<'_>,
681 ifd: &Ifd,
682 tiles: bool,
683 offs: &[u64],
684 cnts: &[u64],
685 ) -> Vec<ChunkUse> {
686 default_chunk_use(w, ifd, tiles, offs, cnts)
687 }
688
689 fn pointer(&self, kind: Kind, e: &Entry) -> Option<(Kind, &'static str)> {
691 match (e.tag, kind) {
692 (SUB_IFDS, Kind::Page(_) | Kind::Sub) => Some((Kind::Sub, "SubIFD")),
693 (EXIF_IFD, Kind::Page(_) | Kind::Sub) => Some((Kind::Exif, "EXIF IFD")),
694 (GPS_IFD, Kind::Page(_) | Kind::Sub) => Some((Kind::Gps, "GPS IFD")),
695 (INTEROP_IFD, Kind::Exif) => Some((Kind::Interop, "Interop IFD")),
696 _ => None,
697 }
698 }
699
700 fn pointer_offsets(&self, w: &Walk<'_>, e: &Entry, max: usize) -> Vec<u64> {
703 w.uints(e, max)
704 }
705
706 fn follows(&self, w: &Walk<'_>, parent: &Ifd, kind: Kind, e: &Entry) -> bool;
709
710 fn follows_next(&self, w: &Walk<'_>, ifd: &Ifd, kind: Kind) -> bool;
713
714 fn ifd(&self, w: &Walk<'_>, ifd: &Ifd) -> Fate;
715
716 fn entry(&self, w: &Walk<'_>, ifd: &Ifd, index: usize) -> Fate;
719
720 fn value(&self, w: &Walk<'_>, ifd: &Ifd, index: usize) -> Fate {
722 self.entry(w, ifd, index)
723 }
724
725 fn image_data(&self, w: &Walk<'_>, ifd: &Ifd) -> Fate;
727
728 fn jpeg_stream(&self, w: &Walk<'_>, ifd: &Ifd) -> Fate;
730
731 fn jpeg_table(&self, _w: &Walk<'_>, _ifd: &Ifd, _tag: u16) -> Fate {
739 Fate::because(
740 Disposition::Unknown,
741 "referenced JPEG table; decoder consumption is not established",
742 )
743 }
744
745 fn cancelled(&self) -> bool {
747 false
748 }
749
750 fn rejects(&self, _w: &Walk<'_>, _at: u64, _kind: Kind) -> Option<&'static str> {
754 None
755 }
756
757 fn extents(&self, w: &Walk<'_>, ifd: &Ifd) -> Vec<ExtentRule> {
760 if !ifd.holds_image() {
761 return Vec::new();
762 }
763 alloc::vec![
764 ExtentRule {
765 offsets: STRIP_OFFSETS,
766 counts: Count::Tag(STRIP_BYTE_COUNTS),
767 what: "strip",
768 kind: PartKind::Extent,
769 fate: self.image_data(w, ifd),
770 },
771 ExtentRule {
772 offsets: TILE_OFFSETS,
773 counts: Count::Tag(TILE_BYTE_COUNTS),
774 what: "tile",
775 kind: PartKind::Extent,
776 fate: self.image_data(w, ifd),
777 },
778 ExtentRule {
779 offsets: FREE_OFFSETS,
780 counts: Count::Tag(FREE_BYTE_COUNTS),
781 what: "free space",
782 kind: PartKind::Extent,
783 fate: Fate::because(Disposition::Padding, "declared free space"),
784 },
785 ExtentRule {
786 offsets: JPEG_IF_OFFSET,
787 counts: Count::Tag(JPEG_IF_LENGTH),
788 what: "JPEG stream",
789 kind: PartKind::EmbeddedImage,
790 fate: self.jpeg_stream(w, ifd),
791 },
792 ExtentRule {
793 offsets: JPEG_Q_TABLES,
794 counts: Count::JpegQuantization,
795 what: "JPEG quantization table",
796 kind: PartKind::Extent,
797 fate: self.jpeg_table(w, ifd, JPEG_Q_TABLES),
798 },
799 ExtentRule {
800 offsets: JPEG_DC_TABLES,
801 counts: Count::JpegHuffman,
802 what: "JPEG DC Huffman table",
803 kind: PartKind::Extent,
804 fate: self.jpeg_table(w, ifd, JPEG_DC_TABLES),
805 },
806 ExtentRule {
807 offsets: JPEG_AC_TABLES,
808 counts: Count::JpegHuffman,
809 what: "JPEG AC Huffman table",
810 kind: PartKind::Extent,
811 fate: self.jpeg_table(w, ifd, JPEG_AC_TABLES),
812 },
813 ]
814 }
815}
816
817fn jpeg_table_counts(
821 w: &Walk<'_>,
822 offsets: &[u64],
823 huffman: bool,
824 rules: &dyn Rules,
825) -> (Vec<u64>, bool, bool) {
826 let mut lengths = Vec::new();
827 for &offset in offsets {
828 if rules.cancelled() {
829 return (lengths, false, true);
830 }
831 if offset == 0 {
832 lengths.push(0);
833 } else if !huffman {
834 lengths.push(64);
835 } else {
836 if !w.take_scan(16) {
837 return (lengths, true, false);
838 }
839 let counts = w.abs(offset).and_then(|at| get(w.d, at, 16));
840 lengths.push(16 + counts.map_or(0, |v| v.iter().map(|&n| u64::from(n)).sum()));
841 }
842 }
843 (lengths, false, false)
844}
845
846#[derive(Clone, Debug, Default)]
848pub struct ChunkUse {
849 pub used: Option<u64>,
853 pub tail: &'static str,
855 pub note: Option<String>,
857 pub past_count: bool,
861 pub covered: bool,
864 pub unread: Option<&'static str>,
866 pub unknown: Option<&'static str>,
869}
870
871#[derive(Clone, Copy, Debug)]
873pub struct ChunkGeom {
874 pub row: u64,
876 pub rows_in: u64,
878 pub rows: u64,
880 pub pad_per_row: u64,
882}
883
884impl ChunkGeom {
885 pub fn read(&self) -> Option<u64> {
887 self.row.checked_mul(self.rows_in)
888 }
889}
890
891pub fn chunk_geometry(
895 w: &Walk<'_>,
896 ifd: &Ifd,
897 tiles: bool,
898 chunks: usize,
899) -> Option<Vec<ChunkGeom>> {
900 let one = |tag: u16| {
901 w.find(ifd, tag)
902 .and_then(|e| w.uints(e, 1).first().copied())
903 };
904 if one(262) == Some(6) {
905 return None;
906 }
907 let width = one(256)?;
908 let height = one(257)?;
909 let spp = one(277).unwrap_or(1).max(1);
910 let bps = w
911 .find(ifd, 258)
912 .map(|e| w.uints(e, 64))
913 .unwrap_or_else(|| alloc::vec![1]);
914 let planar = one(284) == Some(2);
915 let bits = if planar {
916 *bps.first()?
917 } else if bps.len() as u64 == spp {
918 bps.iter().try_fold(0u64, |a, &b| a.checked_add(b))?
919 } else {
920 bps.first()?.checked_mul(spp)?
921 };
922 let row_bytes = |px: u64| px.checked_mul(bits).map(|b| b.div_ceil(8));
923 let mut out = Vec::with_capacity(chunks.min(1 << 20));
924 if tiles {
925 let (tw, tl) = (one(322)?.max(1), one(323)?.max(1));
926 let across = width.div_ceil(tw).max(1);
927 let per_plane = across.checked_mul(height.div_ceil(tl).max(1))?;
928 let row = row_bytes(tw)?;
929 for k in 0..chunks as u64 {
930 let i = k % per_plane;
931 let (col, down) = (i % across, i / across);
932 let cols_in = tw.min(width.saturating_sub(col.saturating_mul(tw)));
933 out.push(ChunkGeom {
934 row,
935 rows_in: tl.min(height.saturating_sub(down.saturating_mul(tl))),
936 rows: tl,
937 pad_per_row: row.saturating_sub(row_bytes(cols_in)?),
938 });
939 }
940 return Some(out);
941 }
942 let rps = one(278).unwrap_or(height).clamp(1, height.max(1));
943 let per_plane = height.div_ceil(rps).max(1);
944 let row = row_bytes(width)?;
945 for k in 0..chunks as u64 {
946 let rows_in = rps.min(height.saturating_sub((k % per_plane).saturating_mul(rps)));
947 out.push(ChunkGeom {
948 row,
949 rows_in,
950 rows: rows_in,
951 pad_per_row: 0,
952 });
953 }
954 Some(out)
955}
956
957fn packbits_end(data: &[u8], need: u64) -> Option<u64> {
961 let (mut pos, mut out) = (0usize, 0u64);
962 while out < need {
963 let h = *data.get(pos)? as i8;
964 pos += 1;
965 match h {
966 -128 => {}
967 -127..=-1 => {
968 data.get(pos)?;
969 pos += 1;
970 out += (1 - i64::from(h)) as u64;
971 }
972 _ => {
973 let take = (h as u64 + 1).min(need - out);
974 pos = pos.checked_add(usize::try_from(take).ok()?)?;
975 if pos > data.len() {
976 return None;
977 }
978 out += take;
979 }
980 }
981 }
982 Some(pos as u64)
983}
984
985pub fn default_chunk_use(
991 w: &Walk<'_>,
992 ifd: &Ifd,
993 tiles: bool,
994 offs: &[u64],
995 cnts: &[u64],
996) -> Vec<ChunkUse> {
997 let compression = w
998 .find(ifd, 259)
999 .and_then(|e| w.uints(e, 1).first().copied())
1000 .unwrap_or(1);
1001 let geom = chunk_geometry(w, ifd, tiles, cnts.len());
1002 let mut out = Vec::with_capacity(cnts.len().min(1 << 20));
1003 for (k, &n) in cnts.iter().enumerate() {
1004 let g = geom.as_ref().and_then(|g| g.get(k).copied());
1005 let mut u = ChunkUse::default();
1006 let Some(g) = g else {
1007 out.push(u);
1008 continue;
1009 };
1010 let below = g.rows_in < g.rows;
1011 u.tail = if below {
1012 "rows below the image (edge-tile padding) and any bytes after them; never read"
1013 } else {
1014 "after the rows the decoder reads; never read"
1015 };
1016 if g.pad_per_row > 0 {
1017 u.note = Some(format!(
1018 "{} padding bytes per decoded row past the image's right edge are read and discarded",
1019 g.pad_per_row
1020 ));
1021 }
1022 match compression {
1023 1 => {
1026 u.used = g.read();
1027 u.past_count = true;
1028 }
1029 32773 => {
1030 let start = offs.get(k).and_then(|&o| w.abs(o));
1033 let avail = start.map_or(0, |s| n.min(w.limit().saturating_sub(s)));
1034 let budget = start.is_some() && g.read().is_some() && w.take_scan(avail);
1035 if !budget {
1036 u.unknown = Some(
1037 "the count-only scan budget is spent; the decoder may read some or all of these bytes",
1038 );
1039 }
1040 if let (true, Some(s), Some(need)) = (budget, start, g.read())
1041 && let Some(d) = get(w.d, s, avail)
1042 {
1043 match packbits_end(d, need) {
1044 Some(end) => u.used = Some(end),
1045 None if avail == n => {
1046 u.used = Some(n);
1047 u.note = Some(
1048 "the PackBits data ends before the chunk's rows are complete"
1049 .into(),
1050 );
1051 }
1052 None => {}
1053 }
1054 }
1055 }
1056 8 | 32946 | 50000 => {
1059 u.note = Some(
1060 "the stream is read to its end, which may lie past the declared count".into(),
1061 );
1062 }
1063 _ => {}
1064 }
1065 if u.used.is_none() && (below || g.pad_per_row > 0) {
1066 let edge = "edge tile: its padding pixels are decoded and discarded";
1067 u.note = Some(match u.note.take() {
1068 Some(n) => format!("{n}; {edge}"),
1069 None => edge.into(),
1070 });
1071 }
1072 out.push(u);
1073 }
1074 out
1075}
1076
1077fn inline_children(w: &Walk<'_>, rules: &dyn Rules, ifd: &Ifd, ei: usize, entry: &mut Cand) {
1081 let Some(e) = ifd.entries.get(ei) else { return };
1082 let size = match w.loc(e) {
1083 Loc::Inline => type_size(e.typ)
1084 .and_then(|s| s.checked_mul(e.count))
1085 .unwrap_or(0),
1086 Loc::Empty => 0,
1087 _ => return,
1088 };
1089 let cap = w.lay.inline_cap();
1090 let field = e.field..e.field + cap;
1091 if size > 0
1092 && let Some((keep, tail)) = rules.value_tail(w, ifd, ei)
1093 && keep < size
1094 {
1095 let mut detail = format!("{} value bytes {keep}..{size}", tag_name(ifd.kind, e.tag));
1096 if let Some(n) = &tail.note {
1097 push_note(&mut detail, n);
1098 }
1099 entry.children.push(Cand::new(
1100 field.start + keep..field.start + size,
1101 PartKind::Field,
1102 PartTag::Code(u32::from(e.tag)),
1103 tail.d,
1104 detail,
1105 ));
1106 }
1107 let unused = field.start + size..field.end;
1108 if !unused.is_empty()
1109 && get(w.d, unused.start, unused.end - unused.start)
1110 .is_some_and(|b| b.iter().any(|&x| x != 0))
1111 {
1112 entry.children.push(Cand::new(
1113 unused,
1114 PartKind::Gap,
1115 PartTag::None,
1116 Disposition::Padding,
1117 "unused bytes of the inline value field, not zero; nothing reads them".into(),
1118 ));
1119 }
1120}
1121
1122pub fn walk<'a>(data: &'a [u8], base: u64, limit: u64, rules: &dyn Rules) -> Walk<'a> {
1125 let limit = limit.min(data.len() as u64);
1126 let d = &data[..usize::try_from(limit).unwrap_or(data.len())];
1127 let mut w = Walk {
1128 d,
1129 base,
1130 lay: Layout {
1131 le: true,
1132 big: false,
1133 },
1134 magic: 0,
1135 header: base..base,
1136 header_ok: false,
1137 header_ext: None,
1138 fatal: None,
1139 ifds: Vec::new(),
1140 entries_left: entry_budget(limit),
1141 scan_left: scan_budget(limit),
1142 tables: BTreeMap::new(),
1143 by_at: BTreeMap::new(),
1144 cancelled: false,
1145 budget_hit: false,
1146 pointer_reads: 0,
1147 };
1148 let rest = limit.saturating_sub(base);
1149 let lay = match get(d, base, 2) {
1150 Some(b"II") => Layout {
1151 le: true,
1152 big: false,
1153 },
1154 Some(b"MM") => Layout {
1155 le: false,
1156 big: false,
1157 },
1158 _ => {
1159 w.header = base..base + rest.min(2);
1160 w.fatal = Some("not a TIFF byte-order mark".into());
1161 return w;
1162 }
1163 };
1164 let (lay, ifd0) = match lay.u16(d, base + 2) {
1165 Some(43) => {
1166 let big = Layout { big: true, ..lay };
1167 if big.u16(d, base + 4).is_some_and(|v| v != 8)
1169 || big.u16(d, base + 6).is_some_and(|v| v != 0)
1170 {
1171 w.header = base..base + rest.min(8);
1172 w.fatal = Some("BigTIFF header with an offset size other than 8".into());
1173 return w;
1174 }
1175 w.magic = 43;
1176 (big, big.u64(d, base + 8))
1177 }
1178 Some(m) if m == 42 || rules.magic(m) => {
1179 w.magic = m;
1180 (lay, lay.u32(d, base + 4).map(u64::from))
1181 }
1182 Some(m) => {
1183 w.header = base..base + rest.min(4);
1184 w.fatal = Some(format!("not a TIFF version number ({m})"));
1185 return w;
1186 }
1187 None => {
1188 w.header = base..limit;
1189 w.fatal = Some("truncated TIFF header".into());
1190 return w;
1191 }
1192 };
1193 w.lay = lay;
1194 let Some(ifd0) = ifd0 else {
1195 w.header = base..limit;
1196 w.fatal = Some("truncated TIFF header".into());
1197 return w;
1198 };
1199 w.header = base..base + lay.header_len();
1200 w.header_ok = true;
1201 if let Some((extra, fate)) = rules.header_extension(&w)
1202 && w.header.end + extra <= limit
1203 {
1204 let r = w.header.end..w.header.end + extra;
1205 let detail = fate.note.map(|n| n.into_owned()).unwrap_or_default();
1206 w.header_ext = Some((r, fate.d, detail));
1207 }
1208
1209 let first = w.abs(ifd0);
1210 run_queue(&mut w, first, Kind::Page(0), "IFD0".into(), 0, rules);
1211 w
1212}
1213
1214#[allow(clippy::too_many_arguments, dead_code)] pub fn walk_ifd<'a>(
1220 data: &'a [u8],
1221 base: u64,
1222 limit: u64,
1223 lay: Layout,
1224 at: u64,
1225 kind: Kind,
1226 name: String,
1227 depth: u8,
1228 rules: &dyn Rules,
1229) -> Walk<'a> {
1230 let limit = limit.min(data.len() as u64);
1231 let d = &data[..usize::try_from(limit).unwrap_or(data.len())];
1232 let mut w = Walk {
1233 d,
1234 base,
1235 lay,
1236 magic: 0,
1237 header: at..at,
1238 header_ok: true,
1239 header_ext: None,
1240 fatal: None,
1241 ifds: Vec::new(),
1242 entries_left: entry_budget(limit),
1243 scan_left: scan_budget(limit),
1244 tables: BTreeMap::new(),
1245 by_at: BTreeMap::new(),
1246 cancelled: false,
1247 budget_hit: false,
1248 pointer_reads: 0,
1249 };
1250 run_queue(&mut w, Some(at), kind, name, depth, rules);
1251 w
1252}
1253
1254#[derive(Clone, Copy)]
1256enum From {
1257 Header,
1258 Entry(usize, usize),
1259 Next(usize),
1260}
1261
1262struct Pending {
1263 at: Option<u64>,
1264 kind: Kind,
1265 name: String,
1266 followed: bool,
1267 from: From,
1268 depth: u8,
1269}
1270
1271fn run_queue(
1275 w: &mut Walk<'_>,
1276 first: Option<u64>,
1277 kind: Kind,
1278 name: String,
1279 depth: u8,
1280 rules: &dyn Rules,
1281) {
1282 let d = w.d;
1283 let lay = w.lay;
1284 let mut queue = VecDeque::new();
1285 queue.push_back(Pending {
1286 at: first,
1287 kind,
1288 name,
1289 followed: true,
1290 from: From::Header,
1291 depth,
1292 });
1293 while let Some(p) = queue.pop_front() {
1294 let note = |w: &mut Walk<'_>, text: String| match p.from {
1295 From::Header => w.fatal = Some(text),
1296 From::Entry(i, e) => add_note(&mut w.ifds[i].entries[e].notes, text),
1297 From::Next(i) => add_note(&mut w.ifds[i].notes, text),
1298 };
1299 let Some(at) = p.at else {
1300 note(w, format!("{} offset overflows", p.name));
1301 continue;
1302 };
1303 if rules.cancelled() {
1304 w.cancelled = true;
1305 break;
1306 }
1307 if let Some(&idx) = w.by_at.get(&at) {
1308 let ifd = &mut w.ifds[idx];
1311 let looped = matches!(p.kind, Kind::Page(_)) && ifd.page().is_some();
1314 let role = ifd.roles.iter().position(|&(k, _)| k == p.kind);
1315 let new_kind = !looped && role.is_none() && ifd.roles.len() < MAX_ROLES;
1316 let upgraded = !looped && p.followed && role.is_some_and(|r| !ifd.roles[r].1);
1317 match role {
1318 Some(r) if !looped => ifd.roles[r].1 |= p.followed,
1319 None if new_kind => ifd.roles.push((p.kind, p.followed)),
1320 _ => {}
1321 }
1322 if new_kind || upgraded {
1323 ifd.followed |= p.followed;
1324 }
1325 let text = if new_kind {
1326 format!(
1327 "{} at {at} is also reached as {}",
1328 p.name,
1329 kind_name(p.kind)
1330 )
1331 } else {
1332 format!(
1333 "{} at {at} was already walked (cycle or shared IFD)",
1334 p.name
1335 )
1336 };
1337 note(w, text);
1338 if (new_kind || upgraded) && w.ifds[idx].problem.is_none() {
1339 enqueue(w, &mut queue, idx, p.kind, rules);
1340 }
1341 continue;
1342 }
1343 if w.ifds.len() >= MAX_IFDS {
1344 w.budget_hit = true;
1345 note(w, format!("not walked: more than {MAX_IFDS} IFDs"));
1346 continue;
1347 }
1348 if let Some(other) = w.table_overlap(at) {
1354 if !p.followed {
1355 note(
1356 w,
1357 format!(
1358 "{} at {at} overlaps the IFD table at {other}; not walked",
1359 p.name
1360 ),
1361 );
1362 continue;
1363 }
1364 note(
1365 w,
1366 format!(
1367 "{} at {at} overlaps the IFD table at {other}; walked, because the decoder reads it",
1368 p.name
1369 ),
1370 );
1371 }
1372 let mut followed = p.followed;
1375 if followed && let Some(why) = rules.rejects(w, at, p.kind) {
1376 followed = false;
1377 note(w, format!("{} at {at}: {why}", p.name));
1378 }
1379 let mut budget = w.entries_left;
1380 let parsed = parse_ifd(d, lay, at, p.kind, p.name.clone(), followed, &mut budget);
1381 w.entries_left = budget;
1382 if budget == 0 {
1383 w.budget_hit = true;
1384 }
1385 let Some(mut ifd) = parsed else {
1386 note(
1387 w,
1388 format!("{} offset {at} is past the end of the data", p.name),
1389 );
1390 continue;
1391 };
1392 if let From::Entry(i, e) = p.from {
1393 ifd.parent = Some((i, e));
1394 }
1395 ifd.depth = p.depth;
1396 if p.kind == Kind::Page(0) && ifd.problem.is_some() {
1397 w.fatal = Some(format!(
1398 "IFD0 {}",
1399 ifd.problem.as_deref().unwrap_or_default()
1400 ));
1401 }
1402 w.tables.insert(ifd.at, ifd.end);
1403 let idx = w.ifds.len();
1404 w.by_at.insert(at, idx);
1405 let readable = ifd.problem.is_none();
1406 w.ifds.push(ifd);
1407 if readable {
1408 enqueue(w, &mut queue, idx, p.kind, rules);
1409 }
1410 }
1411}
1412
1413fn enqueue(
1416 w: &mut Walk<'_>,
1417 queue: &mut VecDeque<Pending>,
1418 idx: usize,
1419 kind: Kind,
1420 rules: &dyn Rules,
1421) {
1422 let base_name = w.ifds[idx].name.clone();
1423 let followed_here = w.ifds[idx].followed_as(kind);
1424 for ei in 0..w.ifds[idx].entries.len() {
1425 let e = &w.ifds[idx].entries[ei];
1426 let Some((child_kind, label)) = rules.pointer(kind, e) else {
1427 continue;
1428 };
1429 let followed = followed_here && rules.follows(w, &w.ifds[idx], kind, e);
1430 let room = MAX_IFDS.saturating_sub(queue.len() + w.ifds.len());
1433 if room == 0 {
1434 w.budget_hit = true;
1435 add_note(
1436 &mut w.ifds[idx].entries[ei].notes,
1437 format!("{label} pointers not walked: more than {MAX_IFDS} IFDs"),
1438 );
1439 continue;
1440 }
1441 let ptrs = rules.pointer_offsets(w, e, room + 1);
1442 w.pointer_reads += ptrs.len() as u64;
1443 if ptrs.is_empty() {
1444 let text = format!("{label} pointer of type {} not read", type_name(e.typ));
1445 add_note(&mut w.ifds[idx].entries[ei].notes, text);
1446 }
1447 let numbered = ptrs.len() > 1 || child_kind == Kind::Sub;
1448 for (k, off) in ptrs.into_iter().enumerate() {
1449 if queue.len() + w.ifds.len() >= MAX_IFDS {
1450 w.budget_hit = true;
1451 add_note(
1452 &mut w.ifds[idx].entries[ei].notes,
1453 format!("further {label} pointers not walked: more than {MAX_IFDS} IFDs"),
1454 );
1455 break;
1456 }
1457 let name = match (numbered, base_name.len() > 48) {
1458 (true, false) => format!("{label} {k} of {base_name}"),
1459 (true, true) => format!("{label} {k} (nested)"),
1460 (false, false) => format!("{label} of {base_name}"),
1461 (false, true) => format!("{label} (nested)"),
1462 };
1463 queue.push_back(Pending {
1464 at: w.abs(off),
1465 kind: child_kind,
1466 name,
1467 followed,
1468 from: From::Entry(idx, ei),
1469 depth: w.ifds[idx].depth.saturating_add(1),
1470 });
1471 }
1472 }
1473 let next = w.ifds[idx].next;
1474 if next != 0 && queue.len() + w.ifds.len() >= MAX_IFDS {
1475 w.budget_hit = true;
1476 add_note(
1477 &mut w.ifds[idx].notes,
1478 format!("not walked: more than {MAX_IFDS} IFDs"),
1479 );
1480 } else if next != 0 {
1481 let followed_next = followed_here && rules.follows_next(w, &w.ifds[idx], kind);
1482 let (kind, name) = match kind {
1483 Kind::Page(n) => (
1484 Kind::Page(n.saturating_add(1)),
1485 format!("IFD{}", n.saturating_add(1)),
1486 ),
1487 other if base_name.len() > 48 => (other, "IFD chained after a sub-IFD".into()),
1488 other => (other, format!("IFD chained after {base_name}")),
1489 };
1490 queue.push_back(Pending {
1491 at: w.abs(next),
1492 kind,
1493 name,
1494 followed: followed_next,
1495 from: From::Next(idx),
1496 depth: w.ifds[idx].depth,
1497 });
1498 }
1499}
1500
1501pub struct Cand {
1505 pub range: Range<u64>,
1506 pub kind: PartKind,
1507 pub tag: PartTag,
1508 pub label: Option<String>,
1509 pub disp: Disposition,
1510 pub detail: String,
1511 pub children: Vec<Cand>,
1512 pub body: Option<Range<u64>>,
1515 pub gap: Option<(Disposition, String)>,
1518 notes: usize,
1519}
1520
1521impl Cand {
1522 pub fn new(
1523 range: Range<u64>,
1524 kind: PartKind,
1525 tag: PartTag,
1526 disp: Disposition,
1527 detail: String,
1528 ) -> Self {
1529 Self {
1530 range,
1531 kind,
1532 tag,
1533 label: None,
1534 disp,
1535 detail,
1536 children: Vec::new(),
1537 body: None,
1538 gap: None,
1539 notes: 0,
1540 }
1541 }
1542
1543 pub fn note(&mut self, text: &str) {
1544 self.notes += 1;
1545 if self.notes <= MAX_NOTES {
1546 push_note(&mut self.detail, text);
1547 } else if self.notes == MAX_NOTES + 1 {
1548 push_note(&mut self.detail, "further overlaps not listed");
1549 }
1550 }
1551}
1552
1553pub fn push_note(detail: &mut String, text: &str) {
1554 if !detail.is_empty() {
1555 detail.push_str("; ");
1556 }
1557 detail.push_str(text);
1558}
1559
1560fn describe(c: &Cand) -> String {
1561 let head = c.detail.split(';').next().unwrap_or("");
1562 format!(
1563 "{} {}..{} ({head})",
1564 c.kind.name(),
1565 c.range.start,
1566 c.range.end
1567 )
1568}
1569
1570fn clip(c: &Cand, r: &Range<u64>, mark: bool) -> Option<Cand> {
1573 let range = c.range.start.max(r.start)..c.range.end.min(r.end);
1574 if range.start >= range.end {
1575 return None;
1576 }
1577 let mut out = Cand::new(
1578 range.clone(),
1579 c.kind,
1580 c.tag.clone(),
1581 c.disp,
1582 c.detail.clone(),
1583 );
1584 out.label = c.label.clone();
1585 out.notes = c.notes;
1586 out.gap = c.gap.clone();
1587 out.body = c
1588 .body
1589 .as_ref()
1590 .map(|b| b.start.max(range.start)..b.end.min(range.end))
1591 .filter(|b| b.start < b.end);
1592 out.children = c
1593 .children
1594 .iter()
1595 .filter_map(|ch| clip(ch, &range, true))
1596 .collect();
1597 if mark && range != c.range {
1598 out.note("split by an overlapping part");
1599 }
1600 Some(out)
1601}
1602
1603#[derive(Default)]
1609pub struct Placed {
1610 cands: Vec<Cand>,
1611}
1612
1613struct Seg {
1615 range: Range<u64>,
1616 rank: u8,
1617 cand: usize,
1618}
1619
1620fn gap_rank(c: &Cand) -> u8 {
1622 rank(c.gap.as_ref().map_or(Disposition::Unreferenced, |g| g.0))
1623}
1624
1625fn segments(c: &Cand, within: &Range<u64>, cand: usize, out: &mut Vec<Seg>) {
1627 let range = c.range.start.max(within.start)..c.range.end.min(within.end);
1628 if range.start >= range.end {
1629 return;
1630 }
1631 let body = c
1632 .body
1633 .as_ref()
1634 .filter(|b| b.start >= c.range.start && b.end <= c.range.end);
1635 let own = |r: Range<u64>, out: &mut Vec<Seg>| {
1636 if r.start >= r.end {
1637 return;
1638 }
1639 let Some(b) = body else {
1640 out.push(Seg {
1641 range: r,
1642 rank: rank(c.disp),
1643 cand,
1644 });
1645 return;
1646 };
1647 let cuts = [
1648 (r.start..r.end.min(b.start), rank(c.disp)),
1649 (r.start.max(b.start)..r.end.min(b.end), gap_rank(c)),
1650 (r.start.max(b.end)..r.end, rank(c.disp)),
1651 ];
1652 for (r, rank) in cuts {
1653 if r.start < r.end {
1654 out.push(Seg {
1655 range: r,
1656 rank,
1657 cand,
1658 });
1659 }
1660 }
1661 };
1662 let mut kids: Vec<&Cand> = c.children.iter().collect();
1663 kids.sort_by_key(|ch| ch.range.start);
1664 let mut cursor = range.start;
1665 for ch in kids {
1666 let start = ch.range.start.clamp(range.start, range.end);
1667 if start > cursor {
1668 own(cursor..start, out);
1669 }
1670 segments(ch, &range, cand, out);
1671 cursor = cursor.max(ch.range.end.min(range.end));
1672 }
1673 own(cursor..range.end, out);
1674}
1675
1676fn beaten(by: &mut [Vec<usize>], loser: usize, winner: usize) {
1678 let list = &mut by[loser];
1679 if list.len() <= MAX_NOTES && !list.contains(&winner) {
1680 list.push(winner);
1681 }
1682}
1683
1684impl Placed {
1685 pub fn insert(&mut self, c: Cand) {
1687 if c.range.start < c.range.end {
1688 self.cands.push(c);
1689 }
1690 }
1691
1692 pub fn into_sorted(self) -> Vec<Cand> {
1696 use core::cmp::Reverse;
1697 let mut cands = self.cands;
1698 let n = cands.len();
1699 let mut segs = Vec::new();
1700 for (i, c) in cands.iter().enumerate() {
1701 segments(c, &c.range, i, &mut segs);
1702 }
1703 let mut events: Vec<(u64, bool, usize)> = Vec::with_capacity(segs.len() * 2);
1706 for (k, sg) in segs.iter().enumerate() {
1707 events.push((sg.range.start, true, k));
1708 events.push((sg.range.end, false, k));
1709 }
1710 events.sort_unstable_by_key(|&(x, start, k)| (x, start, k));
1711 let mut active: BTreeSet<(Reverse<u8>, usize, usize)> = BTreeSet::new();
1712 let mut live = alloc::vec![0u32; n];
1713 let mut won: Vec<Vec<Range<u64>>> = (0..n).map(|_| Vec::new()).collect();
1714 let mut by: Vec<Vec<usize>> = (0..n).map(|_| Vec::new()).collect();
1715 let mut top: Option<usize> = None;
1716 let mut added = Vec::new();
1717 let mut i = 0;
1718 while i < events.len() {
1719 let x = events[i].0;
1720 added.clear();
1721 while i < events.len() && events[i].0 == x {
1722 let (_, start, k) = events[i];
1723 let sg = &segs[k];
1724 let key = (Reverse(sg.rank), sg.cand, k);
1725 if start {
1726 active.insert(key);
1727 live[sg.cand] += 1;
1728 added.push(sg.cand);
1729 } else {
1730 active.remove(&key);
1731 live[sg.cand] -= 1;
1732 }
1733 i += 1;
1734 }
1735 let now = active.first().map(|&(_, c, _)| c);
1736 if let Some(wc) = now {
1737 for &c in &added {
1738 if c != wc {
1739 beaten(&mut by, c, wc);
1740 }
1741 }
1742 if let Some(p) = top
1743 && p != wc
1744 && live[p] > 0
1745 {
1746 beaten(&mut by, p, wc);
1747 }
1748 if let Some(&(next, _, _)) = events.get(i) {
1749 let r = &mut won[wc];
1750 match r.last_mut() {
1751 Some(last) if last.end == x => last.end = next,
1752 _ => r.push(x..next),
1753 }
1754 }
1755 }
1756 top = now;
1757 }
1758 let descs: Vec<Option<String>> = (0..n)
1761 .map(|k| (!by[k].is_empty()).then(|| describe(&cands[k])))
1762 .collect();
1763 for loser in 0..n {
1764 let Some(desc) = &descs[loser] else { continue };
1765 for &wc in &by[loser] {
1766 cands[wc].note(&format!("overlaps {desc}"));
1767 }
1768 let mine = &won[loser];
1769 let lost = cands[loser]
1770 .children
1771 .iter()
1772 .filter(|ch| {
1773 let k = mine.partition_point(|r| r.end <= ch.range.start);
1774 mine.get(k).is_none_or(|r| r.start >= ch.range.end)
1775 })
1776 .count();
1777 if lost > 0 {
1778 let wc = by[loser][0];
1779 cands[wc].note(&format!(
1780 "{lost} part(s) of {desc} lie in bytes other parts take and are not listed"
1781 ));
1782 }
1783 }
1784 let mut parts = Vec::new();
1785 for (k, c) in cands.into_iter().enumerate() {
1786 let pieces = core::mem::take(&mut won[k]);
1787 if pieces.len() == 1 && pieces[0] == c.range {
1788 parts.push(c);
1789 continue;
1790 }
1791 let others = by[k].len();
1792 let more = if others > MAX_NOTES { "+" } else { "" };
1793 let total = pieces.len();
1794 let listed = total.min(MAX_PIECES);
1795 let desc = descs[k].clone().unwrap_or_else(|| describe(&c));
1796 for (j, r) in pieces.into_iter().enumerate() {
1797 if j >= MAX_PIECES {
1798 parts.push(Cand::new(
1801 r,
1802 PartKind::Gap,
1803 PartTag::None,
1804 Disposition::Unknown,
1805 format!(
1806 "part of {desc}; not listed past {MAX_PIECES} overlaps; the decoder may read it"
1807 ),
1808 ));
1809 continue;
1810 }
1811 let Some(mut piece) = clip(&c, &r, false) else {
1812 continue;
1813 };
1814 piece.note(&format!(
1815 "overlaps {}{more} other part(s); piece {} of {listed}{}",
1816 others.max(1),
1817 j + 1,
1818 if total > listed {
1819 "; the rest is not listed"
1820 } else {
1821 ""
1822 }
1823 ));
1824 parts.push(piece);
1825 }
1826 }
1827 parts.sort_by_key(|c| c.range.start);
1828 parts
1829 }
1830}
1831
1832pub struct Placement {
1834 pub parts: Vec<Cand>,
1836 pub logical_end: u64,
1838 pub ifd0_ok: bool,
1840 pub budget_hit: bool,
1843}
1844
1845pub fn place(w: &mut Walk<'_>, rules: &dyn Rules, first: Vec<Cand>) -> Placement {
1848 let limit = w.limit();
1849 let mut placed = Placed::default();
1850 let mut logical_end = w.header.end;
1851 let ifd0_ok = w
1852 .ifds
1853 .first()
1854 .is_some_and(|i| i.kind == Kind::Page(0) && i.problem.is_none());
1855 if !ifd0_ok {
1856 logical_end = limit;
1857 }
1858 if w.header.end > w.header.start {
1859 let mut c = if w.header_ok {
1860 let what = if w.lay.big {
1861 "BigTIFF header"
1862 } else {
1863 "TIFF header"
1864 };
1865 Cand::new(
1866 w.header.clone(),
1867 PartKind::Header,
1868 PartTag::None,
1869 Disposition::Structure,
1870 what.into(),
1871 )
1872 } else {
1873 let why = w.fatal.clone().unwrap_or_default();
1874 Cand::new(
1875 w.header.clone(),
1876 PartKind::Header,
1877 PartTag::None,
1878 Disposition::Malformed,
1879 why,
1880 )
1881 };
1882 if w.header_ok
1883 && let Some(why) = &w.fatal
1884 {
1885 c.note(why);
1886 }
1887 placed.insert(c);
1888 }
1889 if let Some((r, d, detail)) = w.header_ext.clone() {
1890 placed.insert(Cand::new(r, PartKind::Header, PartTag::None, d, detail));
1891 }
1892 for c in first {
1893 logical_end = logical_end.max(c.range.end);
1894 placed.insert(c);
1895 }
1896
1897 let mut values: Vec<Cand> = Vec::new();
1900 let mut extents_left = limit.saturating_add(4096);
1903 for i in 0..w.ifds.len() {
1904 if rules.cancelled() {
1905 w.cancelled = true;
1906 break;
1907 }
1908 let ifd = &w.ifds[i];
1909 if ifd.problem.is_some() {
1910 continue;
1911 }
1912 let mut notes: Vec<(usize, String)> = Vec::new();
1913 for (ei, e) in ifd.entries.iter().enumerate() {
1914 let Loc::At(off, size) = w.loc(e) else {
1915 continue;
1916 };
1917 let fate = rules.value(w, ifd, ei);
1918 let mut detail = format!("{} value, {}", tag_name(ifd.kind, e.tag), ifd.name);
1919 if let Some(n) = &fate.note {
1920 push_note(&mut detail, n);
1921 }
1922 let end = off.saturating_add(size);
1923 if off >= limit {
1924 notes.push((ei, format!("value at {off} is past the end of the data")));
1925 logical_end = limit;
1926 continue;
1927 }
1928 let mut disp = fate.d;
1929 if end > limit {
1930 push_note(&mut detail, "truncated by the end of the data");
1931 disp = Disposition::Malformed;
1932 logical_end = limit;
1933 }
1934 logical_end = logical_end.max(end.min(limit));
1935 let mut c = Cand::new(
1936 off..end.min(limit),
1937 PartKind::Field,
1938 PartTag::Code(u32::from(e.tag)),
1939 disp,
1940 detail,
1941 );
1942 if end <= limit
1943 && let Some((keep, tail)) = rules.value_tail(w, ifd, ei)
1944 && keep < size
1945 {
1946 let mut t = String::new();
1947 if let Some(n) = &tail.note {
1948 push_note(&mut t, n);
1949 }
1950 c.children.push(Cand::new(
1951 off + keep..end,
1952 PartKind::Field,
1953 PartTag::Code(u32::from(e.tag)),
1954 tail.d,
1955 t,
1956 ));
1957 }
1958 values.push(c);
1959 }
1960 for rule in rules.extents(w, ifd) {
1961 let Some(oe) = w.find(ifd, rule.offsets) else {
1962 continue;
1963 };
1964 if extents_left == 0 {
1965 w.budget_hit = true;
1966 notes.push((
1967 ifd.entries
1968 .iter()
1969 .rposition(|e| e.tag == rule.offsets)
1970 .unwrap_or(0),
1971 "further extents not listed: the file's extent budget is spent".into(),
1972 ));
1973 continue;
1974 }
1975 let max = usize::try_from((limit / 4 + 1).min(extents_left)).unwrap_or(usize::MAX);
1976 let offs = w.uints(oe, max);
1977 let cnts = match rule.counts {
1978 Count::Tag(ct) => match w.find(ifd, ct) {
1979 Some(ce) => w.uints(ce, max),
1980 None => continue,
1981 },
1982 Count::ToEnd => offs
1983 .first()
1984 .and_then(|&o| w.abs(o))
1985 .map(|a| alloc::vec![limit.saturating_sub(a)])
1986 .unwrap_or_default(),
1987 Count::Bytes(n) => alloc::vec![n],
1988 Count::JpegQuantization | Count::JpegHuffman => {
1989 let huffman = matches!(rule.counts, Count::JpegHuffman);
1990 let (lengths, exhausted, cancelled) =
1991 jpeg_table_counts(w, &offs, huffman, rules);
1992 w.budget_hit |= exhausted || oe.count > max as u64;
1993 w.cancelled |= cancelled;
1994 lengths
1995 }
1996 };
1997 let oi = ifd
1998 .entries
1999 .iter()
2000 .rposition(|e| e.tag == rule.offsets)
2001 .unwrap_or(0);
2002 let pixels = rule.fate.d == Disposition::ImageData
2003 && matches!(rule.offsets, STRIP_OFFSETS | TILE_OFFSETS);
2004 let uses = if pixels {
2005 let n = offs.len().min(cnts.len());
2006 rules.chunk_use(w, ifd, rule.offsets == TILE_OFFSETS, &offs[..n], &cnts[..n])
2007 } else {
2008 Vec::new()
2009 };
2010 let emit = |r: Range<u64>,
2011 first: usize,
2012 last: usize,
2013 values: &mut Vec<Cand>,
2014 notes: &mut Vec<(usize, String)>| {
2015 let which = if first == last {
2016 format!("{} {first}", rule.what)
2017 } else {
2018 format!("{}s {first}..={last}", rule.what)
2019 };
2020 if r.start >= limit {
2021 notes.push((
2022 oi,
2023 format!("{which} at {} is past the end of the data", r.start),
2024 ));
2025 return;
2026 }
2027 let mut detail = format!("{}, {which}", ifd.name);
2028 if let Some(n) = &rule.fate.note {
2029 push_note(&mut detail, n);
2030 }
2031 if pixels
2032 && (first..=last).any(|k| {
2033 uses.get(k).is_none_or(|u| {
2034 u.used.is_none() && u.unread.is_none() && u.unknown.is_none()
2035 })
2036 })
2037 {
2038 push_note(
2039 &mut detail,
2040 "bytes after the end of the coded data are not split out",
2041 );
2042 }
2043 if first == last
2044 && let Some(n) = uses.get(first).and_then(|u| u.note.as_deref())
2045 {
2046 push_note(&mut detail, n);
2047 }
2048 let mut disp = rule.fate.d;
2049 if r.end > limit {
2050 push_note(&mut detail, "truncated by the end of the data");
2051 disp = Disposition::Malformed;
2052 }
2053 values.push(Cand::new(
2054 r.start..r.end.min(limit),
2055 rule.kind,
2056 PartTag::Code(u32::from(rule.offsets)),
2057 disp,
2058 detail,
2059 ));
2060 };
2061 let mut run: Option<(Range<u64>, usize, usize)> = None;
2063 for (k, (&o, &n)) in offs.iter().zip(cnts.iter()).enumerate() {
2064 let Some(start) = w.abs(o) else {
2065 notes.push((
2066 oi,
2067 format!("{} {k}: offset overflows the TIFF base", rule.what),
2068 ));
2069 continue;
2070 };
2071 if n == 0 || uses.get(k).is_some_and(|u| u.covered) {
2072 continue;
2073 }
2074 if extents_left == 0 {
2075 w.budget_hit = true;
2076 break;
2077 }
2078 extents_left -= 1;
2079 let alone = uses.get(k).and_then(|u| {
2081 u.unread
2082 .map(|why| (Disposition::Skipped, why))
2083 .or(u.unknown.map(|why| (Disposition::Unknown, why)))
2084 });
2085 if let Some((fate, why)) = alone {
2086 if let Some((r, first, last)) = run.take() {
2087 emit(r, first, last, &mut values, &mut notes);
2088 }
2089 let before = values.len();
2090 let end = start.saturating_add(n);
2091 logical_end = logical_end.max(end.min(limit));
2092 emit(start..end, k, k, &mut values, &mut notes);
2093 if values.len() > before
2094 && let Some(c) = values.last_mut()
2095 {
2096 if c.disp != Disposition::Malformed {
2097 c.disp = fate;
2098 }
2099 push_note(&mut c.detail, why);
2100 }
2101 continue;
2102 }
2103 let read = uses
2105 .get(k)
2106 .filter(|u| u.past_count)
2107 .and_then(|u| u.used)
2108 .filter(|&used| used > n);
2109 let end = start.saturating_add(read.unwrap_or(n));
2110 logical_end = logical_end.max(end.min(limit));
2111 if end > limit {
2112 logical_end = limit;
2113 }
2114 let u = uses.get(k);
2118 let tail = u
2119 .and_then(|u| u.used)
2120 .filter(|&used| used < n && end <= limit);
2121 if tail.is_some() || read.is_some() || u.is_some_and(|u| u.note.is_some()) {
2122 if let Some((r, first, last)) = run.take() {
2123 emit(r, first, last, &mut values, &mut notes);
2124 }
2125 let before = values.len();
2126 emit(start..end, k, k, &mut values, &mut notes);
2127 if let Some(used) = read
2128 && values.len() > before
2129 && let Some(c) = values.last_mut()
2130 {
2131 push_note(
2132 &mut c.detail,
2133 &format!("declared {n} bytes; the decoder reads {used}, by row size"),
2134 );
2135 }
2136 if let Some(used) = tail
2137 && values.len() > before
2138 && let Some(c) = values.last_mut()
2139 {
2140 let what = u.map_or("", |u| u.tail);
2141 c.children.push(Cand::new(
2142 start + used..end,
2143 rule.kind,
2144 PartTag::Code(u32::from(rule.offsets)),
2145 Disposition::Dropped,
2146 format!("{what} (the decoder reads the first {used} bytes)"),
2147 ));
2148 }
2149 continue;
2150 }
2151 run = match run {
2152 Some((r, first, last)) if r.end == start && last + 1 == k => {
2153 Some((r.start..end, first, k))
2154 }
2155 other => {
2156 if let Some((r, first, last)) = other {
2157 emit(r, first, last, &mut values, &mut notes);
2158 }
2159 Some((start..end, k, k))
2160 }
2161 };
2162 }
2163 if let Some((r, first, last)) = run {
2164 emit(r, first, last, &mut values, &mut notes);
2165 }
2166 }
2167 for (ei, text) in notes {
2168 if let Some(e) = w.ifds[i].entries.get_mut(ei) {
2169 add_note(&mut e.notes, text);
2170 }
2171 }
2172 }
2173
2174 for ifd in &w.ifds {
2176 let fate = match &ifd.problem {
2177 Some(p) => Fate::because(Disposition::Malformed, p.clone()),
2178 None => rules.ifd(w, ifd),
2179 };
2180 let mut detail = format!("{}, {} entries", ifd.name, ifd.entries.len());
2181 if let Some(n) = &fate.note {
2182 push_note(&mut detail, n);
2183 }
2184 if ifd.roles.len() > 1 {
2185 let others: Vec<String> = ifd.roles[1..].iter().map(|&(k, _)| kind_name(k)).collect();
2186 push_note(
2187 &mut detail,
2188 &format!("also reached as {}", others.join(", ")),
2189 );
2190 }
2191 for n in &ifd.notes {
2192 push_note(&mut detail, &format!("next IFD: {n}"));
2193 }
2194 let mut c = Cand::new(
2195 ifd.at..ifd.end,
2196 PartKind::Ifd,
2197 PartTag::None,
2198 fate.d,
2199 detail,
2200 );
2201 logical_end = logical_end.max(ifd.end);
2202 if ifd.problem.is_some() {
2203 logical_end = limit;
2204 }
2205 for (ei, e) in ifd.entries.iter().enumerate() {
2206 let fate = if ifd.problem.is_some() {
2207 Fate::because(Disposition::Malformed, "in an unreadable IFD")
2208 } else {
2209 rules.entry(w, ifd, ei)
2210 };
2211 let mut detail = format!(
2212 "{} {}[{}]",
2213 tag_name(ifd.kind, e.tag),
2214 type_name(e.typ),
2215 e.count
2216 );
2217 if let Some(n) = &fate.note {
2218 push_note(&mut detail, n);
2219 }
2220 for n in &e.notes {
2221 push_note(&mut detail, n);
2222 }
2223 let mut entry = Cand::new(
2224 e.at..e.at + w.lay.entry_len(),
2225 PartKind::Field,
2226 PartTag::Code(u32::from(e.tag)),
2227 fate.d,
2228 detail,
2229 );
2230 if ifd.problem.is_none() {
2231 inline_children(w, rules, ifd, ei, &mut entry);
2232 }
2233 c.children.push(entry);
2234 }
2235 placed.insert(c);
2236 }
2237 for v in values {
2240 placed.insert(v);
2241 }
2242 Placement {
2243 parts: placed.into_sorted(),
2244 logical_end,
2245 ifd0_ok,
2246 budget_hit: w.budget_hit,
2247 }
2248}
2249
2250pub fn emit(inv: &mut Inventory, parent: Option<PartId>, c: Cand) -> Result<(), InventoryError> {
2254 let body = c
2255 .body
2256 .clone()
2257 .filter(|b| b.start >= c.range.start && b.end <= c.range.end);
2258 let gap = c.gap.clone();
2259 let mut p = Part::new(c.kind, c.tag, c.range, c.disp);
2260 if let Some(l) = c.label {
2261 p = p.with_label(l);
2262 }
2263 if !c.detail.is_empty() {
2264 p = p.with_detail(c.detail);
2265 }
2266 if let Some(b) = body.clone() {
2267 p = p.with_body(b);
2268 }
2269 let id = inv.push(parent, p)?;
2270 let mut children = c.children;
2271 children.sort_by_key(|ch| ch.range.start);
2272 for ch in children {
2273 emit(inv, Some(id), ch)?;
2274 }
2275 if body.is_some() {
2276 match gap {
2277 Some((d, detail)) => {
2278 let before = inv.parts().len();
2279 inv.fill_gaps(Some(id), d)?;
2280 for gap in inv.children(Some(id)) {
2281 if gap.index() >= before {
2282 inv.set_detail(gap, detail.clone());
2283 }
2284 }
2285 }
2286 None => inv.fill_gaps(Some(id), Disposition::Unreferenced)?,
2287 }
2288 }
2289 Ok(())
2290}
2291
2292pub fn emit_top(
2297 inv: &mut Inventory,
2298 data: &[u8],
2299 parts: Vec<Cand>,
2300 logical_end: u64,
2301 gap: (Disposition, Option<String>),
2302) -> Result<(), InventoryError> {
2303 let len = data.len() as u64;
2304 let mut cursor = 0u64;
2305 let push_gap = |inv: &mut Inventory, r: Range<u64>| -> Result<(), InventoryError> {
2306 let end = r.end.min(logical_end);
2307 if r.start >= end {
2308 return Ok(());
2309 }
2310 let mut part = Part::new(PartKind::Gap, PartTag::None, r.start..end, gap.0);
2311 if let Some(d) = &gap.1 {
2312 part = part.with_detail(d.clone());
2313 } else if gap.0 == Disposition::Unreferenced
2314 && end - r.start == 1
2315 && r.start % 2 == 1
2316 && end < logical_end
2317 {
2318 let byte = get(data, r.start, 1).map_or(0, |b| b[0]);
2319 part = Part::new(
2320 PartKind::Gap,
2321 PartTag::None,
2322 r.start..end,
2323 Disposition::Padding,
2324 )
2325 .with_detail(if byte == 0 {
2326 "word-alignment pad byte"
2327 } else {
2328 "word-alignment pad byte (non-zero)"
2329 });
2330 }
2331 inv.push(None, part)?;
2332 Ok(())
2333 };
2334 for c in parts {
2335 if c.range.start > cursor {
2336 push_gap(inv, cursor..c.range.start)?;
2337 }
2338 cursor = cursor.max(c.range.end);
2339 emit(inv, None, c)?;
2340 }
2341 if cursor < len {
2342 push_gap(inv, cursor..len)?;
2343 }
2344 Ok(())
2345}
2346
2347#[cfg(test)]
2348mod tests {
2349 use super::*;
2350 use zencodec::inventory::MetadataKind;
2351
2352 struct Rng(u64);
2354
2355 impl Rng {
2356 fn next(&mut self) -> u64 {
2357 self.0 ^= self.0 >> 12;
2358 self.0 ^= self.0 << 25;
2359 self.0 ^= self.0 >> 27;
2360 self.0.wrapping_mul(0x2545_f491_4f6c_dd1d)
2361 }
2362
2363 fn below(&mut self, n: u64) -> u64 {
2364 self.next() % n
2365 }
2366 }
2367
2368 const FATES: [Disposition; 10] = [
2369 Disposition::Metadata(MetadataKind::Xmp),
2370 Disposition::Metadata(MetadataKind::Exif),
2371 Disposition::Structure,
2372 Disposition::ImageData,
2373 Disposition::Malformed,
2374 Disposition::Dropped,
2375 Disposition::Padding,
2376 Disposition::Skipped,
2377 Disposition::Unknown,
2378 Disposition::Unreferenced,
2379 ];
2380
2381 fn random_cand(rng: &mut Rng, r: Range<u64>, depth: u32) -> Cand {
2385 let disp = FATES[rng.below(FATES.len() as u64) as usize];
2386 let mut c = Cand::new(
2387 r.clone(),
2388 PartKind::Field,
2389 PartTag::None,
2390 disp,
2391 String::new(),
2392 );
2393 let len = r.end - r.start;
2394 if depth == 0 || len < 2 {
2395 return c;
2396 }
2397 let inner = match rng.below(4) {
2398 0 => {
2400 let keep = rng.below(len);
2401 c.children
2402 .push(random_cand(rng, r.start + keep..r.end, depth - 1));
2403 return c;
2404 }
2405 1 => {
2407 let a = r.start + rng.below(len);
2408 let b = a + 1 + rng.below(r.end - a);
2409 c.body = Some(a..b);
2410 if rng.below(2) == 0 {
2411 c.gap = Some((Disposition::Unknown, "budget".into()));
2412 }
2413 a..b
2414 }
2415 2 => r.clone(),
2417 _ => return c,
2418 };
2419 let mut at = inner.start;
2420 while at < inner.end && c.children.len() < 4 {
2421 let start = at + rng.below(3);
2422 if start >= inner.end {
2423 break;
2424 }
2425 let end = start + 1 + rng.below((inner.end - start).min(12));
2426 c.children.push(random_cand(rng, start..end, depth - 1));
2427 at = end;
2428 }
2429 c
2430 }
2431
2432 fn at(c: &Cand, b: u64) -> Option<u8> {
2435 if !c.range.contains(&b) {
2436 return None;
2437 }
2438 for ch in &c.children {
2439 if let Some(r) = at(ch, b) {
2440 return Some(r);
2441 }
2442 }
2443 match &c.body {
2444 Some(body) if body.contains(&b) => Some(gap_rank(c)),
2445 _ => Some(rank(c.disp)),
2446 }
2447 }
2448
2449 fn well_formed(c: &Cand) {
2451 let mut kids: Vec<&Cand> = c.children.iter().collect();
2452 kids.sort_by_key(|ch| ch.range.start);
2453 let mut end = c.range.start;
2454 for ch in kids {
2455 assert!(ch.range.start >= end && ch.range.end <= c.range.end);
2456 assert!(ch.range.start < ch.range.end);
2457 end = ch.range.end;
2458 well_formed(ch);
2459 }
2460 if let Some(b) = &c.body {
2461 assert!(b.start >= c.range.start && b.end <= c.range.end);
2462 }
2463 }
2464
2465 #[test]
2466 fn every_byte_takes_the_strongest_fate() {
2467 const SPACE: u64 = 48;
2468 let mut rng = Rng(0x9e37_79b9_7f4a_7c15);
2469 for round in 0..4000 {
2470 let n = 1 + rng.below(8) as usize;
2471 let cands: Vec<Cand> = (0..n)
2472 .map(|_| {
2473 let a = rng.below(SPACE);
2474 let b = a + 1 + rng.below(SPACE - a);
2475 random_cand(&mut rng, a..b, 3)
2476 })
2477 .collect();
2478 let expect: Vec<Option<u8>> = (0..SPACE)
2479 .map(|b| cands.iter().filter_map(|c| at(c, b)).max())
2480 .collect();
2481 let mut placed = Placed::default();
2482 for c in cands {
2483 placed.insert(c);
2484 }
2485 let parts = placed.into_sorted();
2486 let mut end = 0;
2487 for p in &parts {
2488 assert!(p.range.start >= end, "round {round}: siblings overlap");
2489 assert!(!p.detail.contains("not listed past"), "round {round}");
2490 end = p.range.end;
2491 well_formed(p);
2492 }
2493 for b in 0..SPACE {
2494 let got = parts.iter().find_map(|p| at(p, b));
2495 assert_eq!(got, expect[b as usize], "round {round}, byte {b}");
2496 }
2497 }
2498 }
2499}