1mod block_decode;
31pub mod cache;
32pub mod error;
33pub mod filters;
34pub mod header;
35pub mod ifd;
36pub mod io;
37mod pixel;
38pub mod source;
39pub mod strip;
40pub mod tag;
41pub mod tile;
42
43use std::path::Path;
44use std::sync::Arc;
45
46use cache::BlockCache;
47use error::{Error, Result};
48use ndarray::{ArrayD, IxDyn};
49use source::{BytesSource, FileSource, MmapSource, SharedSource, TiffSource};
50
51pub use error::Error as TiffError;
52pub use header::ByteOrder;
53pub use ifd::{Ifd, ParseBudgets, RasterLayout};
54pub use tag::{Tag, TagValue};
55pub use tiff_core::constants;
56pub use tiff_core::sample::TiffSample;
57pub use tiff_core::TagType;
58pub use tiff_core::{
59 ColorMap, ColorModel, ExtraSample, InkSet, PhotometricInterpretation, YCbCrPositioning,
60};
61
62const DEFAULT_DECODE_OUTPUT_BYTES: usize = 1024 * 1024 * 1024;
63
64#[derive(Debug, Clone, Copy)]
66pub struct OpenOptions {
67 pub block_cache_bytes: usize,
69 pub block_cache_slots: usize,
71 pub parse_budgets: ParseBudgets,
73 pub decode_output_bytes: usize,
75}
76
77impl Default for OpenOptions {
78 fn default() -> Self {
79 Self {
80 block_cache_bytes: 64 * 1024 * 1024,
81 block_cache_slots: 257,
82 parse_budgets: ParseBudgets::default(),
83 decode_output_bytes: DEFAULT_DECODE_OUTPUT_BYTES,
84 }
85 }
86}
87
88pub struct TiffFile {
90 source: SharedSource,
91 header: header::TiffHeader,
92 ifds: Vec<ifd::Ifd>,
93 parse_budgets: ParseBudgets,
94 decode_output_bytes: usize,
95 block_cache: Arc<BlockCache>,
96 gdal_structural_metadata: Option<GdalStructuralMetadata>,
97}
98
99#[derive(Debug, Clone, Copy)]
100pub(crate) struct GdalStructuralMetadata {
101 block_leader_size_as_u32: bool,
102 block_trailer_repeats_last_4_bytes: bool,
103}
104
105#[derive(Debug, Clone, Copy, PartialEq, Eq)]
106pub(crate) struct Window {
107 pub row_off: usize,
108 pub col_off: usize,
109 pub rows: usize,
110 pub cols: usize,
111}
112
113#[derive(Debug, Clone, Copy)]
114pub(crate) struct DecodeReadOptions<'a> {
115 pub decode_output_bytes: usize,
116 pub gdal_structural_metadata: Option<&'a GdalStructuralMetadata>,
117}
118
119impl Window {
120 pub(crate) fn is_empty(self) -> bool {
121 self.rows == 0 || self.cols == 0
122 }
123
124 pub(crate) fn row_end(self) -> usize {
125 self.row_off + self.rows
126 }
127
128 pub(crate) fn col_end(self) -> usize {
129 self.col_off + self.cols
130 }
131
132 pub(crate) fn output_len(self, layout: &RasterLayout) -> Result<usize> {
133 let pixel_stride = layout.checked_pixel_stride_bytes()?;
134 self.cols
135 .checked_mul(self.rows)
136 .and_then(|pixels| pixels.checked_mul(pixel_stride))
137 .ok_or_else(|| Error::InvalidImageLayout("window size overflows usize".into()))
138 }
139
140 pub(crate) fn band_output_len(self, layout: &RasterLayout) -> Result<usize> {
141 self.cols
142 .checked_mul(self.rows)
143 .and_then(|pixels| pixels.checked_mul(layout.bytes_per_sample))
144 .ok_or_else(|| Error::InvalidImageLayout("window band size overflows usize".into()))
145 }
146}
147
148pub(crate) fn checked_layout_add(lhs: usize, rhs: usize, context: &'static str) -> Result<usize> {
149 lhs.checked_add(rhs)
150 .ok_or_else(|| Error::InvalidImageLayout(format!("{context} overflows usize")))
151}
152
153pub(crate) fn checked_layout_mul(lhs: usize, rhs: usize, context: &'static str) -> Result<usize> {
154 lhs.checked_mul(rhs)
155 .ok_or_else(|| Error::InvalidImageLayout(format!("{context} overflows usize")))
156}
157
158pub(crate) fn allocate_decode_output(output_len: usize, budget: usize) -> Result<Vec<u8>> {
159 let mut output = allocate_decode_output_capacity(output_len, budget)?;
160 output.resize(output_len, 0);
161 Ok(output)
162}
163
164pub(crate) fn allocate_decode_output_capacity(output_len: usize, budget: usize) -> Result<Vec<u8>> {
165 validate_decode_output_len(output_len, budget)?;
166 let mut output = Vec::new();
167 output
168 .try_reserve_exact(output_len)
169 .map_err(|error| Error::DecodeOutputAllocationFailed {
170 requested: output_len,
171 reason: error.to_string(),
172 })?;
173 Ok(output)
174}
175
176pub(crate) fn copy_decode_output(bytes: &[u8], budget: usize) -> Result<Vec<u8>> {
177 let mut output = allocate_decode_output_capacity(bytes.len(), budget)?;
178 output.extend_from_slice(bytes);
179 Ok(output)
180}
181
182pub(crate) fn validate_decode_output_len(output_len: usize, budget: usize) -> Result<()> {
183 if output_len > budget {
184 return Err(Error::DecodeOutputTooLarge {
185 requested: output_len,
186 limit: budget,
187 });
188 }
189 Ok(())
190}
191
192impl GdalStructuralMetadata {
193 fn from_prefix(bytes: &[u8]) -> Option<Self> {
194 let text = std::str::from_utf8(bytes).ok()?;
195 if !text.contains("GDAL_STRUCTURAL_METADATA_SIZE=") {
196 return None;
197 }
198
199 Some(Self {
200 block_leader_size_as_u32: text.contains("BLOCK_LEADER=SIZE_AS_UINT4"),
201 block_trailer_repeats_last_4_bytes: text
202 .contains("BLOCK_TRAILER=LAST_4_BYTES_REPEATED"),
203 })
204 }
205
206 pub(crate) fn unwrap_block<'a>(
207 &self,
208 raw: &'a [u8],
209 byte_order: ByteOrder,
210 offset: u64,
211 ) -> Result<&'a [u8]> {
212 if self.block_leader_size_as_u32 {
213 if raw.len() < 4 {
214 return Ok(raw);
215 }
216 let declared_len = match byte_order {
217 ByteOrder::LittleEndian => u32::from_le_bytes(raw[..4].try_into().unwrap()),
218 ByteOrder::BigEndian => u32::from_be_bytes(raw[..4].try_into().unwrap()),
219 } as usize;
220 if let Some(payload_end) = 4usize.checked_add(declared_len) {
221 if payload_end <= raw.len() {
222 if self.block_trailer_repeats_last_4_bytes {
223 let trailer_end = payload_end.checked_add(4).ok_or_else(|| {
224 Error::InvalidImageLayout("GDAL block trailer overflows usize".into())
225 })?;
226 if trailer_end <= raw.len() {
227 let expected = &raw[payload_end - 4..payload_end];
228 let trailer = &raw[payload_end..trailer_end];
229 if expected != trailer {
230 return Err(Error::InvalidImageLayout(format!(
231 "GDAL block trailer mismatch at offset {offset}"
232 )));
233 }
234 }
235 }
236 return Ok(&raw[4..payload_end]);
237 }
238 }
239 }
240
241 if self.block_trailer_repeats_last_4_bytes && raw.len() >= 8 {
242 let split = raw.len() - 4;
243 if raw[split - 4..split] == raw[split..] {
244 return Ok(&raw[..split]);
245 }
246 }
247
248 Ok(raw)
249 }
250}
251
252pub(crate) fn read_block_payload(
253 source: &dyn TiffSource,
254 offset: u64,
255 byte_count: u64,
256 byte_count_limit: usize,
257 index: usize,
258) -> Result<Vec<u8>> {
259 let len = validate_block_byte_count(index, byte_count, byte_count_limit)?;
260 if let Some(bytes) = source.as_slice() {
261 let start = usize::try_from(offset).map_err(|_| Error::OffsetOutOfBounds {
262 offset,
263 length: byte_count,
264 data_len: bytes.len() as u64,
265 })?;
266 let end = start.checked_add(len).ok_or(Error::OffsetOutOfBounds {
267 offset,
268 length: byte_count,
269 data_len: bytes.len() as u64,
270 })?;
271 if end > bytes.len() {
272 return Err(Error::OffsetOutOfBounds {
273 offset,
274 length: byte_count,
275 data_len: bytes.len() as u64,
276 });
277 }
278 Ok(bytes[start..end].to_vec())
279 } else {
280 source.read_exact_at(offset, len)
281 }
282}
283
284pub(crate) fn read_gdal_block_payload(
285 source: &dyn TiffSource,
286 metadata: &GdalStructuralMetadata,
287 byte_order: ByteOrder,
288 offset: u64,
289 byte_count: u64,
290 byte_count_limit: usize,
291 index: usize,
292) -> Result<Vec<u8>> {
293 let payload_len = validate_block_byte_count(index, byte_count, byte_count_limit)?;
294
295 let wrapped_result = (metadata.block_leader_size_as_u32 && offset >= 4).then(|| {
300 read_wrapped_gdal_block(
301 source,
302 metadata,
303 byte_order,
304 offset,
305 byte_count,
306 byte_count_limit,
307 index,
308 )
309 });
310 if let Some(Ok(payload)) = &wrapped_result {
311 if payload.len() == payload_len {
312 return Ok(payload.clone());
313 }
314 }
315
316 let direct_result = source
319 .read_exact_at(offset, payload_len)
320 .and_then(|raw| Ok(metadata.unwrap_block(&raw, byte_order, offset)?.to_vec()));
321 match direct_result {
322 Ok(payload) => {
323 if payload.len() > byte_count_limit {
324 return Err(block_byte_count_too_large(
325 index,
326 payload.len() as u64,
327 byte_count_limit,
328 ));
329 }
330 Ok(payload)
331 }
332 Err(direct_error) => match wrapped_result {
333 Some(Ok(payload)) => Ok(payload),
334 Some(Err(wrapped_error)) => Err(wrapped_error),
335 None => Err(direct_error),
336 },
337 }
338}
339
340fn read_wrapped_gdal_block(
342 source: &dyn TiffSource,
343 metadata: &GdalStructuralMetadata,
344 byte_order: ByteOrder,
345 offset: u64,
346 byte_count: u64,
347 byte_count_limit: usize,
348 index: usize,
349) -> Result<Vec<u8>> {
350 let wrapper_extra = if metadata.block_trailer_repeats_last_4_bytes {
351 8u64
352 } else {
353 4u64
354 };
355 let wrapped_offset = offset - 4;
356 let wrapped_len = byte_count.checked_add(wrapper_extra).ok_or_else(|| {
357 Error::InvalidImageLayout("GDAL wrapped block length overflows u64".into())
358 })?;
359 let len = usize::try_from(wrapped_len).map_err(|_| Error::OffsetOutOfBounds {
360 offset: wrapped_offset,
361 length: wrapped_len,
362 data_len: source.len(),
363 })?;
364 let raw = source.read_exact_at(wrapped_offset, len)?;
365 let payload = metadata.unwrap_block(&raw, byte_order, wrapped_offset)?;
366 if payload.len() > byte_count_limit {
367 return Err(block_byte_count_too_large(
368 index,
369 payload.len() as u64,
370 byte_count_limit,
371 ));
372 }
373 Ok(payload.to_vec())
374}
375
376fn validate_block_byte_count(
377 index: usize,
378 byte_count: u64,
379 byte_count_limit: usize,
380) -> Result<usize> {
381 let len = usize::try_from(byte_count)
382 .map_err(|_| block_byte_count_too_large(index, byte_count, byte_count_limit))?;
383 if len > byte_count_limit {
384 return Err(block_byte_count_too_large(
385 index,
386 byte_count,
387 byte_count_limit,
388 ));
389 }
390 Ok(len)
391}
392
393fn block_byte_count_too_large(index: usize, byte_count: u64, byte_count_limit: usize) -> Error {
394 Error::DecompressionFailed {
395 index,
396 reason: format!(
397 "encoded block byte count {byte_count} exceeds TIFF block read budget {byte_count_limit}"
398 ),
399 }
400}
401
402const GDAL_STRUCTURAL_METADATA_PREFIX: &str = "GDAL_STRUCTURAL_METADATA_SIZE=";
403
404impl TiffFile {
407 pub fn open<P: AsRef<Path>>(path: P) -> Result<Self> {
409 Self::open_with_options(path, OpenOptions::default())
410 }
411
412 pub fn open_with_options<P: AsRef<Path>>(path: P, options: OpenOptions) -> Result<Self> {
414 let source: SharedSource = Arc::new(FileSource::open(path.as_ref())?);
415 Self::from_source_with_options(source, options)
416 }
417
418 pub unsafe fn open_mmap<P: AsRef<Path>>(path: P) -> Result<Self> {
426 unsafe { Self::open_mmap_with_options(path, OpenOptions::default()) }
427 }
428
429 pub unsafe fn open_mmap_with_options<P: AsRef<Path>>(
437 path: P,
438 options: OpenOptions,
439 ) -> Result<Self> {
440 let source: SharedSource = Arc::new(unsafe { MmapSource::open(path.as_ref())? });
441 Self::from_source_with_options(source, options)
442 }
443
444 pub fn from_bytes(data: Vec<u8>) -> Result<Self> {
446 Self::from_bytes_with_options(data, OpenOptions::default())
447 }
448
449 pub fn from_bytes_with_options(data: Vec<u8>, options: OpenOptions) -> Result<Self> {
451 let source: SharedSource = Arc::new(BytesSource::new(data));
452 Self::from_source_with_options(source, options)
453 }
454
455 pub fn from_source(source: SharedSource) -> Result<Self> {
457 Self::from_source_with_options(source, OpenOptions::default())
458 }
459
460 pub fn from_source_with_options(source: SharedSource, options: OpenOptions) -> Result<Self> {
462 let header_len = usize::try_from(source.len().min(16)).unwrap_or(16);
463 let header_bytes = source.read_exact_at(0, header_len)?;
464 let header = header::TiffHeader::parse(&header_bytes)?;
465 let gdal_structural_metadata = parse_gdal_structural_metadata(source.as_ref());
466 let ifds =
467 ifd::parse_ifd_chain_with_budgets(source.as_ref(), &header, options.parse_budgets)?;
468 Ok(Self {
469 source,
470 header,
471 ifds,
472 parse_budgets: options.parse_budgets,
473 decode_output_bytes: options.decode_output_bytes,
474 block_cache: Arc::new(BlockCache::new(
475 options.block_cache_bytes,
476 options.block_cache_slots,
477 )),
478 gdal_structural_metadata,
479 })
480 }
481
482 pub fn byte_order(&self) -> ByteOrder {
484 self.header.byte_order
485 }
486
487 pub fn is_bigtiff(&self) -> bool {
489 self.header.is_bigtiff()
490 }
491
492 pub fn ifd_count(&self) -> usize {
494 self.ifds.len()
495 }
496
497 pub fn ifd(&self, index: usize) -> Result<&Ifd> {
499 self.ifds.get(index).ok_or(Error::IfdNotFound(index))
500 }
501
502 pub fn ifds(&self) -> &[Ifd] {
504 &self.ifds
505 }
506
507 pub fn raw_bytes(&self) -> Option<&[u8]> {
512 self.source.as_slice()
513 }
514
515 pub fn source(&self) -> &dyn TiffSource {
517 self.source.as_ref()
518 }
519
520 fn decode_read_options(&self) -> DecodeReadOptions<'_> {
521 DecodeReadOptions {
522 decode_output_bytes: self.decode_output_bytes,
523 gdal_structural_metadata: self.gdal_structural_metadata.as_ref(),
524 }
525 }
526
527 pub fn read_ifd_at_offset(&self, offset: u64) -> Result<Ifd> {
529 ifd::parse_ifd_at_with_budgets(
530 self.source.as_ref(),
531 &self.header,
532 offset,
533 self.parse_budgets,
534 )
535 }
536
537 pub fn read_image_bytes(&self, ifd_index: usize) -> Result<Vec<u8>> {
539 let ifd = self.ifd(ifd_index)?;
540 self.read_image_bytes_from_ifd(ifd)
541 }
542
543 pub fn read_image_bytes_from_ifd(&self, ifd: &Ifd) -> Result<Vec<u8>> {
545 let layout = ifd.raster_layout()?;
546 self.decode_window_sample_bytes(
547 ifd,
548 Window {
549 row_off: 0,
550 col_off: 0,
551 rows: layout.height,
552 cols: layout.width,
553 },
554 )
555 }
556
557 pub fn read_decoded_image_bytes(&self, ifd_index: usize) -> Result<Vec<u8>> {
559 let ifd = self.ifd(ifd_index)?;
560 self.read_decoded_image_bytes_from_ifd(ifd)
561 }
562
563 pub fn read_decoded_image_bytes_from_ifd(&self, ifd: &Ifd) -> Result<Vec<u8>> {
566 let layout = ifd.decoded_raster_layout()?;
567 self.decode_window_pixel_bytes(
568 ifd,
569 Window {
570 row_off: 0,
571 col_off: 0,
572 rows: layout.height,
573 cols: layout.width,
574 },
575 )
576 }
577
578 pub fn read_window_bytes(
581 &self,
582 ifd_index: usize,
583 row_off: usize,
584 col_off: usize,
585 rows: usize,
586 cols: usize,
587 ) -> Result<Vec<u8>> {
588 let ifd = self.ifd(ifd_index)?;
589 self.read_window_bytes_from_ifd(ifd, row_off, col_off, rows, cols)
590 }
591
592 pub fn read_decoded_window_bytes(
595 &self,
596 ifd_index: usize,
597 row_off: usize,
598 col_off: usize,
599 rows: usize,
600 cols: usize,
601 ) -> Result<Vec<u8>> {
602 let ifd = self.ifd(ifd_index)?;
603 self.read_decoded_window_bytes_from_ifd(ifd, row_off, col_off, rows, cols)
604 }
605
606 pub fn read_window_bytes_from_ifd(
609 &self,
610 ifd: &Ifd,
611 row_off: usize,
612 col_off: usize,
613 rows: usize,
614 cols: usize,
615 ) -> Result<Vec<u8>> {
616 let layout = ifd.raster_layout()?;
617 let window = validate_window(&layout, row_off, col_off, rows, cols)?;
618 self.decode_window_sample_bytes(ifd, window)
619 }
620
621 pub fn read_decoded_window_bytes_from_ifd(
624 &self,
625 ifd: &Ifd,
626 row_off: usize,
627 col_off: usize,
628 rows: usize,
629 cols: usize,
630 ) -> Result<Vec<u8>> {
631 let layout = ifd.decoded_raster_layout()?;
632 let window = validate_window(&layout, row_off, col_off, rows, cols)?;
633 self.decode_window_pixel_bytes(ifd, window)
634 }
635
636 pub fn read_band_bytes(&self, ifd_index: usize, band_index: usize) -> Result<Vec<u8>> {
638 let ifd = self.ifd(ifd_index)?;
639 self.read_band_bytes_from_ifd(ifd, band_index)
640 }
641
642 pub fn read_band_bytes_from_ifd(&self, ifd: &Ifd, band_index: usize) -> Result<Vec<u8>> {
645 let layout = ifd.raster_layout()?;
646 self.read_band_window_bytes_from_ifd(ifd, band_index, 0, 0, layout.height, layout.width)
647 }
648
649 pub fn read_band_window_bytes(
652 &self,
653 ifd_index: usize,
654 band_index: usize,
655 row_off: usize,
656 col_off: usize,
657 rows: usize,
658 cols: usize,
659 ) -> Result<Vec<u8>> {
660 let ifd = self.ifd(ifd_index)?;
661 self.read_band_window_bytes_from_ifd(ifd, band_index, row_off, col_off, rows, cols)
662 }
663
664 pub fn read_band_window_bytes_from_ifd(
667 &self,
668 ifd: &Ifd,
669 band_index: usize,
670 row_off: usize,
671 col_off: usize,
672 rows: usize,
673 cols: usize,
674 ) -> Result<Vec<u8>> {
675 let layout = ifd.raster_layout()?;
676 validate_band_index(&layout, band_index)?;
677 let window = validate_window(&layout, row_off, col_off, rows, cols)?;
678 self.decode_window_sample_band_bytes(ifd, window, band_index)
679 }
680
681 fn decode_window_sample_bytes(&self, ifd: &Ifd, window: Window) -> Result<Vec<u8>> {
682 if window.is_empty() {
683 return Ok(Vec::new());
684 }
685
686 if ifd.is_tiled() {
687 tile::read_window(
688 self.source.as_ref(),
689 ifd,
690 self.byte_order(),
691 &self.block_cache,
692 window,
693 self.decode_read_options(),
694 )
695 } else {
696 strip::read_window(
697 self.source.as_ref(),
698 ifd,
699 self.byte_order(),
700 &self.block_cache,
701 window,
702 self.decode_read_options(),
703 )
704 }
705 }
706
707 fn decode_window_sample_band_bytes(
708 &self,
709 ifd: &Ifd,
710 window: Window,
711 band_index: usize,
712 ) -> Result<Vec<u8>> {
713 if window.is_empty() {
714 return Ok(Vec::new());
715 }
716
717 let layout = ifd.raster_layout()?;
718 validate_band_index(&layout, band_index)?;
719 if ifd.is_tiled() {
720 tile::read_window_band(
721 self.source.as_ref(),
722 ifd,
723 self.byte_order(),
724 &self.block_cache,
725 window,
726 band_index,
727 self.decode_read_options(),
728 )
729 } else {
730 strip::read_window_band(
731 self.source.as_ref(),
732 ifd,
733 self.byte_order(),
734 &self.block_cache,
735 window,
736 band_index,
737 self.decode_read_options(),
738 )
739 }
740 }
741
742 fn decode_window_pixel_bytes(&self, ifd: &Ifd, window: Window) -> Result<Vec<u8>> {
743 let storage_layout = ifd.raster_layout()?;
744 let sample_bytes = self.decode_window_sample_bytes(ifd, window)?;
745 let (_, pixels) = pixel::decode_pixels(
746 ifd,
747 &storage_layout,
748 window.cols,
749 window.rows,
750 &sample_bytes,
751 self.decode_output_bytes,
752 )?;
753 Ok(pixels)
754 }
755
756 pub fn read_window<T: TiffSample>(
761 &self,
762 ifd_index: usize,
763 row_off: usize,
764 col_off: usize,
765 rows: usize,
766 cols: usize,
767 ) -> Result<ArrayD<T>> {
768 let ifd = self.ifd(ifd_index)?;
769 self.read_window_from_ifd(ifd, row_off, col_off, rows, cols)
770 }
771
772 pub fn read_window_from_ifd<T: TiffSample>(
775 &self,
776 ifd: &Ifd,
777 row_off: usize,
778 col_off: usize,
779 rows: usize,
780 cols: usize,
781 ) -> Result<ArrayD<T>> {
782 let layout = ifd.raster_layout()?;
783 let window = validate_window(&layout, row_off, col_off, rows, cols)?;
784 if !T::matches_layout(&layout) {
785 return Err(Error::TypeMismatch {
786 expected: T::type_name(),
787 actual: format!(
788 "sample_format={} bits_per_sample={}",
789 layout.sample_format, layout.bits_per_sample
790 ),
791 });
792 }
793
794 let decoded = self.decode_window_sample_bytes(ifd, window)?;
795 let values = T::decode_many(&decoded);
796 let shape = if layout.samples_per_pixel == 1 {
797 vec![window.rows, window.cols]
798 } else {
799 vec![window.rows, window.cols, layout.samples_per_pixel]
800 };
801 ArrayD::from_shape_vec(IxDyn(&shape), values).map_err(|e| {
802 Error::InvalidImageLayout(format!("failed to build ndarray from storage raster: {e}"))
803 })
804 }
805
806 pub fn read_decoded_window<T: TiffSample>(
811 &self,
812 ifd_index: usize,
813 row_off: usize,
814 col_off: usize,
815 rows: usize,
816 cols: usize,
817 ) -> Result<ArrayD<T>> {
818 let ifd = self.ifd(ifd_index)?;
819 self.read_decoded_window_from_ifd(ifd, row_off, col_off, rows, cols)
820 }
821
822 pub fn read_decoded_window_from_ifd<T: TiffSample>(
825 &self,
826 ifd: &Ifd,
827 row_off: usize,
828 col_off: usize,
829 rows: usize,
830 cols: usize,
831 ) -> Result<ArrayD<T>> {
832 let layout = ifd.decoded_raster_layout()?;
833 let window = validate_window(&layout, row_off, col_off, rows, cols)?;
834 if !T::matches_layout(&layout) {
835 return Err(Error::TypeMismatch {
836 expected: T::type_name(),
837 actual: format!(
838 "sample_format={} bits_per_sample={}",
839 layout.sample_format, layout.bits_per_sample
840 ),
841 });
842 }
843
844 let decoded = self.decode_window_pixel_bytes(ifd, window)?;
845 let values = T::decode_many(&decoded);
846 let shape = if layout.samples_per_pixel == 1 {
847 vec![window.rows, window.cols]
848 } else {
849 vec![window.rows, window.cols, layout.samples_per_pixel]
850 };
851 ArrayD::from_shape_vec(IxDyn(&shape), values).map_err(|e| {
852 Error::InvalidImageLayout(format!("failed to build ndarray from decoded raster: {e}"))
853 })
854 }
855
856 pub fn read_band<T: TiffSample>(
858 &self,
859 ifd_index: usize,
860 band_index: usize,
861 ) -> Result<ArrayD<T>> {
862 let ifd = self.ifd(ifd_index)?;
863 self.read_band_from_ifd(ifd, band_index)
864 }
865
866 pub fn read_band_from_ifd<T: TiffSample>(
869 &self,
870 ifd: &Ifd,
871 band_index: usize,
872 ) -> Result<ArrayD<T>> {
873 let layout = ifd.raster_layout()?;
874 self.read_band_window_from_ifd(ifd, band_index, 0, 0, layout.height, layout.width)
875 }
876
877 pub fn read_band_window<T: TiffSample>(
880 &self,
881 ifd_index: usize,
882 band_index: usize,
883 row_off: usize,
884 col_off: usize,
885 rows: usize,
886 cols: usize,
887 ) -> Result<ArrayD<T>> {
888 let ifd = self.ifd(ifd_index)?;
889 self.read_band_window_from_ifd(ifd, band_index, row_off, col_off, rows, cols)
890 }
891
892 pub fn read_band_window_from_ifd<T: TiffSample>(
895 &self,
896 ifd: &Ifd,
897 band_index: usize,
898 row_off: usize,
899 col_off: usize,
900 rows: usize,
901 cols: usize,
902 ) -> Result<ArrayD<T>> {
903 let layout = ifd.raster_layout()?;
904 validate_band_index(&layout, band_index)?;
905 let window = validate_window(&layout, row_off, col_off, rows, cols)?;
906 if !T::matches_layout(&layout) {
907 return Err(Error::TypeMismatch {
908 expected: T::type_name(),
909 actual: format!(
910 "sample_format={} bits_per_sample={}",
911 layout.sample_format, layout.bits_per_sample
912 ),
913 });
914 }
915
916 let decoded = self.decode_window_sample_band_bytes(ifd, window, band_index)?;
917 let values = T::decode_many(&decoded);
918 ArrayD::from_shape_vec(IxDyn(&[window.rows, window.cols]), values).map_err(|e| {
919 Error::InvalidImageLayout(format!("failed to build ndarray from band raster: {e}"))
920 })
921 }
922
923 pub fn read_image<T: TiffSample>(&self, ifd_index: usize) -> Result<ArrayD<T>> {
928 let ifd = self.ifd(ifd_index)?;
929 self.read_image_from_ifd(ifd)
930 }
931
932 pub fn read_image_from_ifd<T: TiffSample>(&self, ifd: &Ifd) -> Result<ArrayD<T>> {
934 let layout = ifd.raster_layout()?;
935 if !T::matches_layout(&layout) {
936 return Err(Error::TypeMismatch {
937 expected: T::type_name(),
938 actual: format!(
939 "sample_format={} bits_per_sample={}",
940 layout.sample_format, layout.bits_per_sample
941 ),
942 });
943 }
944
945 self.read_window_from_ifd(ifd, 0, 0, layout.height, layout.width)
946 }
947
948 pub fn read_decoded_image<T: TiffSample>(&self, ifd_index: usize) -> Result<ArrayD<T>> {
954 let ifd = self.ifd(ifd_index)?;
955 self.read_decoded_image_from_ifd(ifd)
956 }
957
958 pub fn read_decoded_image_from_ifd<T: TiffSample>(&self, ifd: &Ifd) -> Result<ArrayD<T>> {
960 let layout = ifd.decoded_raster_layout()?;
961 if !T::matches_layout(&layout) {
962 return Err(Error::TypeMismatch {
963 expected: T::type_name(),
964 actual: format!(
965 "sample_format={} bits_per_sample={}",
966 layout.sample_format, layout.bits_per_sample
967 ),
968 });
969 }
970
971 self.read_decoded_window_from_ifd(ifd, 0, 0, layout.height, layout.width)
972 }
973}
974
975fn validate_window(
976 layout: &RasterLayout,
977 row_off: usize,
978 col_off: usize,
979 rows: usize,
980 cols: usize,
981) -> Result<Window> {
982 let row_end = row_off
983 .checked_add(rows)
984 .ok_or_else(|| Error::InvalidImageLayout("window row range overflows usize".into()))?;
985 let col_end = col_off
986 .checked_add(cols)
987 .ok_or_else(|| Error::InvalidImageLayout("window column range overflows usize".into()))?;
988 if row_end > layout.height || col_end > layout.width {
989 return Err(Error::InvalidImageLayout(format!(
990 "window [{row_off}..{row_end}, {col_off}..{col_end}) exceeds raster bounds {}x{}",
991 layout.height, layout.width
992 )));
993 }
994 Ok(Window {
995 row_off,
996 col_off,
997 rows,
998 cols,
999 })
1000}
1001
1002fn validate_band_index(layout: &RasterLayout, band_index: usize) -> Result<()> {
1003 if band_index >= layout.samples_per_pixel {
1004 return Err(Error::BandIndexOutOfBounds {
1005 index: band_index,
1006 band_count: layout.samples_per_pixel,
1007 });
1008 }
1009 Ok(())
1010}
1011
1012fn parse_gdal_structural_metadata(source: &dyn TiffSource) -> Option<GdalStructuralMetadata> {
1013 let available_len = usize::try_from(source.len().checked_sub(8)?).ok()?;
1014 if available_len == 0 {
1015 return None;
1016 }
1017
1018 let probe_len = available_len.min(64);
1019 let probe = source.read_exact_at(8, probe_len).ok()?;
1020 let total_len = parse_gdal_structural_metadata_len(&probe)?;
1021 if total_len == 0 || total_len > available_len {
1022 return None;
1023 }
1024
1025 let bytes = source.read_exact_at(8, total_len).ok()?;
1026 GdalStructuralMetadata::from_prefix(&bytes)
1027}
1028
1029fn parse_gdal_structural_metadata_len(bytes: &[u8]) -> Option<usize> {
1030 let text = std::str::from_utf8(bytes).ok()?;
1031 let newline_index = text.find('\n')?;
1032 let header = &text[..newline_index];
1033 let value = header.strip_prefix(GDAL_STRUCTURAL_METADATA_PREFIX)?;
1034 let digits: String = value.chars().take_while(|ch| ch.is_ascii_digit()).collect();
1035 if digits.is_empty() {
1036 return None;
1037 }
1038 let payload_len: usize = digits.parse().ok()?;
1039 newline_index.checked_add(1)?.checked_add(payload_len)
1040}
1041
1042#[cfg(test)]
1043mod tests {
1044 use std::collections::BTreeMap;
1045 use std::fs;
1046 use std::path::PathBuf;
1047 use std::sync::atomic::{AtomicUsize, Ordering};
1048 use std::sync::Arc;
1049 use std::time::{SystemTime, UNIX_EPOCH};
1050
1051 use super::{
1052 parse_gdal_structural_metadata, parse_gdal_structural_metadata_len, Error,
1053 GdalStructuralMetadata, OpenOptions, ParseBudgets, TiffFile,
1054 GDAL_STRUCTURAL_METADATA_PREFIX,
1055 };
1056 use crate::source::{BytesSource, TiffSource};
1057 use flate2::{write::ZlibEncoder, Compression as FlateCompression};
1058
1059 fn le_u16(value: u16) -> [u8; 2] {
1060 value.to_le_bytes()
1061 }
1062
1063 fn le_u32(value: u32) -> [u8; 4] {
1064 value.to_le_bytes()
1065 }
1066
1067 fn le_u64(value: u64) -> [u8; 8] {
1068 value.to_le_bytes()
1069 }
1070
1071 fn temp_tiff_path(test_name: &str) -> PathBuf {
1072 let nanos = SystemTime::now()
1073 .duration_since(UNIX_EPOCH)
1074 .unwrap()
1075 .as_nanos();
1076 std::env::temp_dir().join(format!(
1077 "geotiff-rust-{test_name}-{}-{nanos}.tif",
1078 std::process::id()
1079 ))
1080 }
1081
1082 fn bigtiff_header(first_ifd_offset: u64) -> Vec<u8> {
1083 let mut bytes = Vec::new();
1084 bytes.extend_from_slice(b"II");
1085 bytes.extend_from_slice(&le_u16(43));
1086 bytes.extend_from_slice(&le_u16(8));
1087 bytes.extend_from_slice(&le_u16(0));
1088 bytes.extend_from_slice(&le_u64(first_ifd_offset));
1089 bytes
1090 }
1091
1092 fn inline_short(value: u16) -> Vec<u8> {
1093 let mut bytes = [0u8; 4];
1094 bytes[..2].copy_from_slice(&le_u16(value));
1095 bytes.to_vec()
1096 }
1097
1098 fn build_stripped_tiff(
1099 width: u32,
1100 height: u32,
1101 image_data: &[u8],
1102 overrides: &[(u16, u16, u32, Vec<u8>)],
1103 ) -> Vec<u8> {
1104 let mut entries = BTreeMap::new();
1105 entries.insert(256, (4, 1, le_u32(width).to_vec()));
1106 entries.insert(257, (4, 1, le_u32(height).to_vec()));
1107 entries.insert(258, (3, 1, [8, 0, 0, 0].to_vec()));
1108 entries.insert(259, (3, 1, [1, 0, 0, 0].to_vec()));
1109 entries.insert(273, (4, 1, Vec::new()));
1110 entries.insert(277, (3, 1, [1, 0, 0, 0].to_vec()));
1111 entries.insert(278, (4, 1, le_u32(height).to_vec()));
1112 entries.insert(279, (4, 1, le_u32(image_data.len() as u32).to_vec()));
1113 for &(tag, ty, count, ref value) in overrides {
1114 entries.insert(tag, (ty, count, value.clone()));
1115 }
1116
1117 let ifd_offset = 8u32;
1118 let ifd_size = 2 + entries.len() * 12 + 4;
1119 let mut next_data_offset = ifd_offset as usize + ifd_size;
1120 let image_offset = next_data_offset as u32;
1121 next_data_offset += image_data.len();
1122
1123 let mut data = Vec::with_capacity(next_data_offset);
1124 data.extend_from_slice(b"II");
1125 data.extend_from_slice(&le_u16(42));
1126 data.extend_from_slice(&le_u32(ifd_offset));
1127 data.extend_from_slice(&le_u16(entries.len() as u16));
1128
1129 let mut deferred = Vec::new();
1130 for (tag, (ty, count, value)) in entries {
1131 data.extend_from_slice(&le_u16(tag));
1132 data.extend_from_slice(&le_u16(ty));
1133 data.extend_from_slice(&le_u32(count));
1134 if tag == 273 {
1135 data.extend_from_slice(&le_u32(image_offset));
1136 } else if value.len() <= 4 {
1137 let mut inline = [0u8; 4];
1138 inline[..value.len()].copy_from_slice(&value);
1139 data.extend_from_slice(&inline);
1140 } else {
1141 let offset = next_data_offset as u32;
1142 data.extend_from_slice(&le_u32(offset));
1143 next_data_offset += value.len();
1144 deferred.push(value);
1145 }
1146 }
1147 data.extend_from_slice(&le_u32(0));
1148 data.extend_from_slice(image_data);
1149 for value in deferred {
1150 data.extend_from_slice(&value);
1151 }
1152 data
1153 }
1154
1155 fn build_multi_ifd_tiff(pixel_values: &[u8]) -> Vec<u8> {
1158 const IFD_ENTRY_COUNT: usize = 8;
1159 const IFD_SIZE: usize = 2 + IFD_ENTRY_COUNT * 12 + 4;
1160 const IFD_STRIDE: usize = IFD_SIZE + 1; let mut data = Vec::with_capacity(8 + pixel_values.len() * IFD_STRIDE);
1163 data.extend_from_slice(b"II");
1164 data.extend_from_slice(&le_u16(42));
1165 data.extend_from_slice(&le_u32(8));
1166
1167 for (index, &pixel) in pixel_values.iter().enumerate() {
1168 let ifd_offset = 8 + index * IFD_STRIDE;
1169 debug_assert_eq!(data.len(), ifd_offset);
1170 let image_offset = (ifd_offset + IFD_SIZE) as u32;
1171 let next_ifd_offset = if index + 1 < pixel_values.len() {
1172 (ifd_offset + IFD_STRIDE) as u32
1173 } else {
1174 0
1175 };
1176
1177 data.extend_from_slice(&le_u16(IFD_ENTRY_COUNT as u16));
1178 for (tag, ty, value) in [
1179 (256u16, 4u16, le_u32(1).to_vec()),
1180 (257, 4, le_u32(1).to_vec()),
1181 (258, 3, inline_short(8)),
1182 (259, 3, inline_short(1)),
1183 (273, 4, le_u32(image_offset).to_vec()),
1184 (277, 3, inline_short(1)),
1185 (278, 4, le_u32(1).to_vec()),
1186 (279, 4, le_u32(1).to_vec()),
1187 ] {
1188 data.extend_from_slice(&le_u16(tag));
1189 data.extend_from_slice(&le_u16(ty));
1190 data.extend_from_slice(&le_u32(1));
1191 let mut inline = [0u8; 4];
1192 inline[..value.len()].copy_from_slice(&value);
1193 data.extend_from_slice(&inline);
1194 }
1195 data.extend_from_slice(&le_u32(next_ifd_offset));
1196 data.push(pixel);
1197 }
1198 data
1199 }
1200
1201 #[test]
1202 fn block_cache_does_not_collide_between_chain_index_and_ifd_offset() {
1203 let pixel_values: Vec<u8> = (0..10u8).map(|index| 100 + index).collect();
1206 let file = TiffFile::from_bytes(build_multi_ifd_tiff(&pixel_values)).unwrap();
1207 assert_eq!(file.ifd_count(), 10);
1208
1209 assert_eq!(file.read_image_bytes(8).unwrap(), vec![pixel_values[8]]);
1211
1212 let first_ifd = file.read_ifd_at_offset(8).unwrap();
1215 assert_eq!(
1216 file.read_image_bytes_from_ifd(&first_ifd).unwrap(),
1217 vec![pixel_values[0]]
1218 );
1219 }
1220
1221 #[allow(clippy::too_many_arguments)]
1222 fn build_lerc2_header_v2(
1223 width: u32,
1224 height: u32,
1225 valid_pixel_count: u32,
1226 image_type: i32,
1227 max_z_error: f64,
1228 z_min: f64,
1229 z_max: f64,
1230 payload_len: usize,
1231 ) -> Vec<u8> {
1232 let blob_size = 58 + 4 + payload_len;
1233 let mut bytes = Vec::with_capacity(blob_size);
1234 bytes.extend_from_slice(b"Lerc2 ");
1235 bytes.extend_from_slice(&2i32.to_le_bytes());
1236 bytes.extend_from_slice(&height.to_le_bytes());
1237 bytes.extend_from_slice(&width.to_le_bytes());
1238 bytes.extend_from_slice(&valid_pixel_count.to_le_bytes());
1239 bytes.extend_from_slice(&8i32.to_le_bytes());
1240 bytes.extend_from_slice(&(blob_size as i32).to_le_bytes());
1241 bytes.extend_from_slice(&image_type.to_le_bytes());
1242 bytes.extend_from_slice(&max_z_error.to_le_bytes());
1243 bytes.extend_from_slice(&z_min.to_le_bytes());
1244 bytes.extend_from_slice(&z_max.to_le_bytes());
1245 bytes
1246 }
1247
1248 #[allow(clippy::too_many_arguments)]
1249 fn build_lerc2_header_v4(
1250 width: u32,
1251 height: u32,
1252 depth: u32,
1253 valid_pixel_count: u32,
1254 image_type: i32,
1255 max_z_error: f64,
1256 z_min: f64,
1257 z_max: f64,
1258 payload_len: usize,
1259 ) -> Vec<u8> {
1260 let blob_size = 66 + 4 + payload_len;
1261 let mut bytes = Vec::with_capacity(blob_size);
1262 bytes.extend_from_slice(b"Lerc2 ");
1263 bytes.extend_from_slice(&4i32.to_le_bytes());
1264 bytes.extend_from_slice(&0u32.to_le_bytes());
1265 bytes.extend_from_slice(&height.to_le_bytes());
1266 bytes.extend_from_slice(&width.to_le_bytes());
1267 bytes.extend_from_slice(&depth.to_le_bytes());
1268 bytes.extend_from_slice(&valid_pixel_count.to_le_bytes());
1269 bytes.extend_from_slice(&8i32.to_le_bytes());
1270 bytes.extend_from_slice(&(blob_size as i32).to_le_bytes());
1271 bytes.extend_from_slice(&image_type.to_le_bytes());
1272 bytes.extend_from_slice(&max_z_error.to_le_bytes());
1273 bytes.extend_from_slice(&z_min.to_le_bytes());
1274 bytes.extend_from_slice(&z_max.to_le_bytes());
1275 bytes
1276 }
1277
1278 fn finalize_lerc2_v4_with_checksum(mut bytes: Vec<u8>) -> Vec<u8> {
1279 let blob_size = bytes.len() as i32;
1280 bytes[34..38].copy_from_slice(&blob_size.to_le_bytes());
1281 let checksum = fletcher32(&bytes[14..blob_size as usize]);
1282 bytes[10..14].copy_from_slice(&checksum.to_le_bytes());
1283 bytes
1284 }
1285
1286 fn fletcher32(bytes: &[u8]) -> u32 {
1287 let mut sum1 = 0xffffu32;
1288 let mut sum2 = 0xffffu32;
1289 let mut words = bytes.len() / 2;
1290 let mut index = 0usize;
1291
1292 while words > 0 {
1293 let chunk = words.min(359);
1294 words -= chunk;
1295 for _ in 0..chunk {
1296 sum1 += (bytes[index] as u32) << 8;
1297 index += 1;
1298 sum2 += sum1 + bytes[index] as u32;
1299 sum1 += bytes[index] as u32;
1300 index += 1;
1301 }
1302 sum1 = (sum1 & 0xffff) + (sum1 >> 16);
1303 sum2 = (sum2 & 0xffff) + (sum2 >> 16);
1304 }
1305
1306 if bytes.len() & 1 != 0 {
1307 sum1 += (bytes[index] as u32) << 8;
1308 sum2 += sum1;
1309 }
1310
1311 sum1 = (sum1 & 0xffff) + (sum1 >> 16);
1312 sum2 = (sum2 & 0xffff) + (sum2 >> 16);
1313 (sum2 << 16) | (sum1 & 0xffff)
1314 }
1315
1316 fn encode_mask_rle(mask: &[u8]) -> Vec<u8> {
1317 let bitset_len = mask.len().div_ceil(8);
1318 let mut bitset = vec![0u8; bitset_len];
1319 for (index, &value) in mask.iter().enumerate() {
1320 if value != 0 {
1321 bitset[index >> 3] |= 1 << (7 - (index & 7));
1322 }
1323 }
1324
1325 let mut encoded = Vec::with_capacity(bitset_len + 4);
1326 encoded.extend_from_slice(&(bitset_len as i16).to_le_bytes());
1327 encoded.extend_from_slice(&bitset);
1328 encoded.extend_from_slice(&i16::MIN.to_le_bytes());
1329 encoded
1330 }
1331
1332 fn build_lerc_tiff(
1333 width: u32,
1334 height: u32,
1335 image_data: &[u8],
1336 bits_per_sample: u16,
1337 sample_format: u16,
1338 samples_per_pixel: u16,
1339 lerc_parameters: Option<[u32; 2]>,
1340 ) -> Vec<u8> {
1341 let mut overrides = vec![
1342 (258u16, 3u16, 1u32, inline_short(bits_per_sample)),
1343 (259u16, 3u16, 1u32, inline_short(34887)),
1344 (277u16, 3u16, 1u32, inline_short(samples_per_pixel)),
1345 (279u16, 4u16, 1u32, le_u32(image_data.len() as u32).to_vec()),
1346 ];
1347 if sample_format != 1 {
1348 overrides.push((339u16, 3u16, 1u32, inline_short(sample_format)));
1349 }
1350 if let Some([version, additional_compression]) = lerc_parameters {
1351 overrides.push((
1352 50674u16,
1353 4u16,
1354 2u32,
1355 [version, additional_compression]
1356 .into_iter()
1357 .flat_map(le_u32)
1358 .collect(),
1359 ));
1360 }
1361 build_stripped_tiff(width, height, image_data, &overrides)
1362 }
1363
1364 fn build_tiled_tiff(
1365 width: u32,
1366 height: u32,
1367 tile_width: u32,
1368 tile_height: u32,
1369 tiles: &[&[u8]],
1370 ) -> Vec<u8> {
1371 build_tiled_tiff_with_overrides(width, height, tile_width, tile_height, tiles, &[])
1372 }
1373
1374 fn build_tiled_tiff_with_overrides(
1375 width: u32,
1376 height: u32,
1377 tile_width: u32,
1378 tile_height: u32,
1379 tiles: &[&[u8]],
1380 overrides: &[(u16, u16, u32, Vec<u8>)],
1381 ) -> Vec<u8> {
1382 let mut entries = BTreeMap::new();
1383 entries.insert(256, (4, 1, le_u32(width).to_vec()));
1384 entries.insert(257, (4, 1, le_u32(height).to_vec()));
1385 entries.insert(258, (3, 1, [8, 0, 0, 0].to_vec()));
1386 entries.insert(259, (3, 1, [1, 0, 0, 0].to_vec()));
1387 entries.insert(277, (3, 1, [1, 0, 0, 0].to_vec()));
1388 entries.insert(322, (4, 1, le_u32(tile_width).to_vec()));
1389 entries.insert(323, (4, 1, le_u32(tile_height).to_vec()));
1390 entries.insert(
1391 325,
1392 (
1393 4,
1394 tiles.len() as u32,
1395 tiles
1396 .iter()
1397 .flat_map(|tile| le_u32(tile.len() as u32))
1398 .collect(),
1399 ),
1400 );
1401 for &(tag, ty, count, ref value) in overrides {
1402 entries.insert(tag, (ty, count, value.clone()));
1403 }
1404
1405 let ifd_offset = 8u32;
1406 let ifd_size = 2 + (entries.len() + 1) * 12 + 4;
1407 let mut tile_data_offset = ifd_offset as usize + ifd_size;
1408 let tile_offsets: Vec<u32> = tiles
1409 .iter()
1410 .map(|tile| {
1411 let offset = tile_data_offset as u32;
1412 tile_data_offset += tile.len();
1413 offset
1414 })
1415 .collect();
1416 entries.insert(
1417 324,
1418 (
1419 4,
1420 tile_offsets.len() as u32,
1421 tile_offsets
1422 .iter()
1423 .flat_map(|offset| le_u32(*offset))
1424 .collect(),
1425 ),
1426 );
1427
1428 let mut next_data_offset = tile_data_offset;
1429 let mut data = Vec::with_capacity(next_data_offset);
1430 data.extend_from_slice(b"II");
1431 data.extend_from_slice(&le_u16(42));
1432 data.extend_from_slice(&le_u32(ifd_offset));
1433 data.extend_from_slice(&le_u16(entries.len() as u16));
1434
1435 let mut deferred = Vec::new();
1436 for (tag, (ty, count, value)) in entries {
1437 data.extend_from_slice(&le_u16(tag));
1438 data.extend_from_slice(&le_u16(ty));
1439 data.extend_from_slice(&le_u32(count));
1440 if value.len() <= 4 {
1441 let mut inline = [0u8; 4];
1442 inline[..value.len()].copy_from_slice(&value);
1443 data.extend_from_slice(&inline);
1444 } else {
1445 let offset = next_data_offset as u32;
1446 data.extend_from_slice(&le_u32(offset));
1447 next_data_offset += value.len();
1448 deferred.push(value);
1449 }
1450 }
1451 data.extend_from_slice(&le_u32(0));
1452 for tile in tiles {
1453 data.extend_from_slice(tile);
1454 }
1455 for value in deferred {
1456 data.extend_from_slice(&value);
1457 }
1458 data
1459 }
1460
1461 fn build_multi_strip_tiff(width: u32, rows: &[&[u8]]) -> Vec<u8> {
1462 let mut entries = BTreeMap::new();
1463 entries.insert(256, (4, 1, le_u32(width).to_vec()));
1464 entries.insert(257, (4, 1, le_u32(rows.len() as u32).to_vec()));
1465 entries.insert(258, (3, 1, [8, 0, 0, 0].to_vec()));
1466 entries.insert(259, (3, 1, [1, 0, 0, 0].to_vec()));
1467 entries.insert(277, (3, 1, [1, 0, 0, 0].to_vec()));
1468 entries.insert(278, (4, 1, le_u32(1).to_vec()));
1469 entries.insert(
1470 279,
1471 (
1472 4,
1473 rows.len() as u32,
1474 rows.iter()
1475 .flat_map(|row| le_u32(row.len() as u32))
1476 .collect(),
1477 ),
1478 );
1479
1480 let ifd_offset = 8u32;
1481 let ifd_size = 2 + (entries.len() + 1) * 12 + 4;
1482 let mut strip_data_offset = ifd_offset as usize + ifd_size;
1483 let strip_offsets: Vec<u32> = rows
1484 .iter()
1485 .map(|row| {
1486 let offset = strip_data_offset as u32;
1487 strip_data_offset += row.len();
1488 offset
1489 })
1490 .collect();
1491 entries.insert(
1492 273,
1493 (
1494 4,
1495 strip_offsets.len() as u32,
1496 strip_offsets
1497 .iter()
1498 .flat_map(|offset| le_u32(*offset))
1499 .collect(),
1500 ),
1501 );
1502
1503 let mut next_data_offset = strip_data_offset;
1504 let mut data = Vec::with_capacity(next_data_offset);
1505 data.extend_from_slice(b"II");
1506 data.extend_from_slice(&le_u16(42));
1507 data.extend_from_slice(&le_u32(ifd_offset));
1508 data.extend_from_slice(&le_u16(entries.len() as u16));
1509
1510 let mut deferred = Vec::new();
1511 for (tag, (ty, count, value)) in entries {
1512 data.extend_from_slice(&le_u16(tag));
1513 data.extend_from_slice(&le_u16(ty));
1514 data.extend_from_slice(&le_u32(count));
1515 if value.len() <= 4 {
1516 let mut inline = [0u8; 4];
1517 inline[..value.len()].copy_from_slice(&value);
1518 data.extend_from_slice(&inline);
1519 } else {
1520 let offset = next_data_offset as u32;
1521 data.extend_from_slice(&le_u32(offset));
1522 next_data_offset += value.len();
1523 deferred.push(value);
1524 }
1525 }
1526 data.extend_from_slice(&le_u32(0));
1527 for row in rows {
1528 data.extend_from_slice(row);
1529 }
1530 for value in deferred {
1531 data.extend_from_slice(&value);
1532 }
1533 data
1534 }
1535
1536 fn build_planar_stripped_tiff(width: u32, height: u32, planes: &[&[u8]]) -> Vec<u8> {
1537 let mut entries = BTreeMap::new();
1538 entries.insert(256, (4, 1, le_u32(width).to_vec()));
1539 entries.insert(257, (4, 1, le_u32(height).to_vec()));
1540 entries.insert(258, (3, 1, [8, 0, 0, 0].to_vec()));
1541 entries.insert(259, (3, 1, [1, 0, 0, 0].to_vec()));
1542 entries.insert(262, (3, 1, [2, 0, 0, 0].to_vec()));
1543 entries.insert(277, (3, 1, inline_short(planes.len() as u16)));
1544 entries.insert(278, (4, 1, le_u32(height).to_vec()));
1545 entries.insert(284, (3, 1, [2, 0, 0, 0].to_vec()));
1546 entries.insert(
1547 279,
1548 (
1549 4,
1550 planes.len() as u32,
1551 planes
1552 .iter()
1553 .flat_map(|plane| le_u32(plane.len() as u32))
1554 .collect(),
1555 ),
1556 );
1557
1558 let ifd_offset = 8u32;
1559 let ifd_size = 2 + (entries.len() + 1) * 12 + 4;
1560 let mut strip_data_offset = ifd_offset as usize + ifd_size;
1561 let strip_offsets: Vec<u32> = planes
1562 .iter()
1563 .map(|plane| {
1564 let offset = strip_data_offset as u32;
1565 strip_data_offset += plane.len();
1566 offset
1567 })
1568 .collect();
1569 entries.insert(
1570 273,
1571 (
1572 4,
1573 strip_offsets.len() as u32,
1574 strip_offsets
1575 .iter()
1576 .flat_map(|offset| le_u32(*offset))
1577 .collect(),
1578 ),
1579 );
1580
1581 let mut next_data_offset = strip_data_offset;
1582 let mut data = Vec::with_capacity(next_data_offset);
1583 data.extend_from_slice(b"II");
1584 data.extend_from_slice(&le_u16(42));
1585 data.extend_from_slice(&le_u32(ifd_offset));
1586 data.extend_from_slice(&le_u16(entries.len() as u16));
1587
1588 let mut deferred = Vec::new();
1589 for (tag, (ty, count, value)) in entries {
1590 data.extend_from_slice(&le_u16(tag));
1591 data.extend_from_slice(&le_u16(ty));
1592 data.extend_from_slice(&le_u32(count));
1593 if value.len() <= 4 {
1594 let mut inline = [0u8; 4];
1595 inline[..value.len()].copy_from_slice(&value);
1596 data.extend_from_slice(&inline);
1597 } else {
1598 let offset = next_data_offset as u32;
1599 data.extend_from_slice(&le_u32(offset));
1600 next_data_offset += value.len();
1601 deferred.push(value);
1602 }
1603 }
1604 data.extend_from_slice(&le_u32(0));
1605 for plane in planes {
1606 data.extend_from_slice(plane);
1607 }
1608 for value in deferred {
1609 data.extend_from_slice(&value);
1610 }
1611 data
1612 }
1613
1614 struct CountingSource {
1615 bytes: Vec<u8>,
1616 reads: AtomicUsize,
1617 }
1618
1619 impl CountingSource {
1620 fn new(bytes: Vec<u8>) -> Self {
1621 Self {
1622 bytes,
1623 reads: AtomicUsize::new(0),
1624 }
1625 }
1626
1627 fn reset_reads(&self) {
1628 self.reads.store(0, Ordering::SeqCst);
1629 }
1630
1631 fn reads(&self) -> usize {
1632 self.reads.load(Ordering::SeqCst)
1633 }
1634 }
1635
1636 impl TiffSource for CountingSource {
1637 fn len(&self) -> u64 {
1638 self.bytes.len() as u64
1639 }
1640
1641 fn read_exact_at(&self, offset: u64, len: usize) -> crate::error::Result<Vec<u8>> {
1642 self.reads.fetch_add(1, Ordering::SeqCst);
1643 let start =
1644 usize::try_from(offset).map_err(|_| crate::error::Error::OffsetOutOfBounds {
1645 offset,
1646 length: len as u64,
1647 data_len: self.len(),
1648 })?;
1649 let end = start
1650 .checked_add(len)
1651 .ok_or(crate::error::Error::OffsetOutOfBounds {
1652 offset,
1653 length: len as u64,
1654 data_len: self.len(),
1655 })?;
1656 if end > self.bytes.len() {
1657 return Err(crate::error::Error::OffsetOutOfBounds {
1658 offset,
1659 length: len as u64,
1660 data_len: self.len(),
1661 });
1662 }
1663 Ok(self.bytes[start..end].to_vec())
1664 }
1665 }
1666
1667 fn overwrite_classic_inline_long_tag(data: &mut [u8], tag: u16, value: u32) {
1668 let ifd_offset = u32::from_le_bytes(data[4..8].try_into().unwrap()) as usize;
1669 let entry_count = u16::from_le_bytes(data[ifd_offset..ifd_offset + 2].try_into().unwrap());
1670 for entry_index in 0..usize::from(entry_count) {
1671 let entry = ifd_offset + 2 + entry_index * 12;
1672 let entry_tag = u16::from_le_bytes(data[entry..entry + 2].try_into().unwrap());
1673 if entry_tag == tag {
1674 data[entry + 8..entry + 12].copy_from_slice(&le_u32(value));
1675 return;
1676 }
1677 }
1678 panic!("tag {tag} not found");
1679 }
1680
1681 fn zero_classic_long_array_element(data: &mut [u8], tag: u16, element_index: usize) {
1683 let ifd_offset = u32::from_le_bytes(data[4..8].try_into().unwrap()) as usize;
1684 let entry_count = u16::from_le_bytes(data[ifd_offset..ifd_offset + 2].try_into().unwrap());
1685 for entry_index in 0..usize::from(entry_count) {
1686 let entry = ifd_offset + 2 + entry_index * 12;
1687 let entry_tag = u16::from_le_bytes(data[entry..entry + 2].try_into().unwrap());
1688 if entry_tag != tag {
1689 continue;
1690 }
1691 let count = u32::from_le_bytes(data[entry + 4..entry + 8].try_into().unwrap()) as usize;
1692 assert!(element_index < count, "element index out of range");
1693 if count == 1 {
1694 data[entry + 8..entry + 12].copy_from_slice(&[0; 4]);
1695 } else {
1696 let value_offset =
1697 u32::from_le_bytes(data[entry + 8..entry + 12].try_into().unwrap()) as usize;
1698 let element = value_offset + element_index * 4;
1699 data[element..element + 4].copy_from_slice(&[0; 4]);
1700 }
1701 return;
1702 }
1703 panic!("tag {tag} not found");
1704 }
1705
1706 fn ghost_metadata(trailer: bool) -> GdalStructuralMetadata {
1707 GdalStructuralMetadata {
1708 block_leader_size_as_u32: true,
1709 block_trailer_repeats_last_4_bytes: trailer,
1710 }
1711 }
1712
1713 fn read_ghost_payload(
1714 bytes: Vec<u8>,
1715 metadata: &GdalStructuralMetadata,
1716 offset: u64,
1717 byte_count: u64,
1718 ) -> crate::error::Result<Vec<u8>> {
1719 let source = BytesSource::new(bytes);
1720 super::read_gdal_block_payload(
1721 &source,
1722 metadata,
1723 crate::ByteOrder::LittleEndian,
1724 offset,
1725 byte_count,
1726 1024,
1727 0,
1728 )
1729 }
1730
1731 #[test]
1732 fn ghost_block_prefers_wrapped_copy_with_matching_length() {
1733 let payload = [10u8, 20, 30, 40];
1735 let mut bytes = 4u32.to_le_bytes().to_vec();
1736 bytes.extend_from_slice(&payload);
1737 bytes.extend_from_slice(&payload);
1738
1739 let result = read_ghost_payload(bytes, &ghost_metadata(true), 4, 4).unwrap();
1740 assert_eq!(result, payload);
1741 }
1742
1743 #[test]
1744 fn ghost_block_falls_back_to_direct_read_without_leader() {
1745 let bytes = vec![9u8, 9, 9, 9, 1, 2, 3, 4];
1748 let result = read_ghost_payload(bytes, &ghost_metadata(false), 4, 4).unwrap();
1749 assert_eq!(result, vec![1, 2, 3, 4]);
1750 }
1751
1752 #[test]
1753 fn ghost_block_falls_back_when_wrapped_read_is_out_of_bounds() {
1754 let mut bytes = vec![0u8; 4];
1757 bytes.extend_from_slice(&[5, 6, 7, 8]);
1758 let result = read_ghost_payload(bytes, &ghost_metadata(true), 4, 4).unwrap();
1759 assert_eq!(result, vec![5, 6, 7, 8]);
1760 }
1761
1762 #[test]
1763 fn ghost_block_skips_wrapped_read_for_small_offsets() {
1764 let bytes = vec![11u8, 12, 13, 14];
1765 let result = read_ghost_payload(bytes, &ghost_metadata(true), 0, 4).unwrap();
1766 assert_eq!(result, vec![11, 12, 13, 14]);
1767 }
1768
1769 #[test]
1770 fn ghost_block_errors_when_both_reads_fail() {
1771 let bytes = vec![0u8; 4];
1772 let error = read_ghost_payload(bytes, &ghost_metadata(false), 8, 4).unwrap_err();
1773 assert!(matches!(error, Error::OffsetOutOfBounds { .. }), "{error}");
1774 }
1775
1776 #[test]
1777 fn sparse_strip_reads_as_zero_fill() {
1778 let rows: [&[u8]; 2] = [&[1, 2, 3, 4], &[5, 6, 7, 8]];
1779 let mut data = build_multi_strip_tiff(4, &rows);
1780 zero_classic_long_array_element(&mut data, 273, 1); zero_classic_long_array_element(&mut data, 279, 1); let file = TiffFile::from_bytes(data).unwrap();
1784 assert_eq!(
1785 file.read_image_bytes(0).unwrap(),
1786 vec![1, 2, 3, 4, 0, 0, 0, 0]
1787 );
1788 }
1789
1790 #[test]
1791 fn zero_byte_count_block_reads_as_zero_fill() {
1792 let rows: [&[u8]; 2] = [&[1, 2], &[3, 4]];
1793 let mut data = build_multi_strip_tiff(2, &rows);
1794 zero_classic_long_array_element(&mut data, 279, 0); let file = TiffFile::from_bytes(data).unwrap();
1797 assert_eq!(file.read_image_bytes(0).unwrap(), vec![0, 0, 3, 4]);
1798 }
1799
1800 #[test]
1801 fn sparse_tile_reads_as_zero_fill() {
1802 let tile0 = vec![9u8; 256];
1803 let tile1 = vec![7u8; 256];
1804 let mut data = build_tiled_tiff(32, 16, 16, 16, &[&tile0, &tile1]);
1805 zero_classic_long_array_element(&mut data, 324, 1); zero_classic_long_array_element(&mut data, 325, 1); let file = TiffFile::from_bytes(data).unwrap();
1809 let image = file.read_image_bytes(0).unwrap();
1810 assert_eq!(image.len(), 32 * 16);
1811 assert!(image.chunks(32).all(|row| {
1812 row[..16].iter().all(|&value| value == 9) && row[16..].iter().all(|&value| value == 0)
1813 }));
1814 }
1815
1816 #[test]
1817 fn open_uses_safe_file_source_without_raw_slice() {
1818 let path = temp_tiff_path("open_uses_safe_file_source_without_raw_slice");
1819 fs::write(&path, build_stripped_tiff(1, 1, &[7], &[])).unwrap();
1820
1821 let file = TiffFile::open(&path).unwrap();
1822 assert!(file.raw_bytes().is_none());
1823 assert_eq!(file.read_image_bytes(0).unwrap(), vec![7]);
1824
1825 drop(file);
1826 let _ = fs::remove_file(path);
1827 }
1828
1829 #[test]
1830 fn open_mmap_exposes_raw_slice() {
1831 let bytes = build_stripped_tiff(1, 1, &[7], &[]);
1832 let path = temp_tiff_path("open_mmap_exposes_raw_slice");
1833 fs::write(&path, &bytes).unwrap();
1834
1835 let file = unsafe { TiffFile::open_mmap(&path).unwrap() };
1836 assert_eq!(file.raw_bytes(), Some(bytes.as_slice()));
1837 assert_eq!(file.read_image_bytes(0).unwrap(), vec![7]);
1838
1839 drop(file);
1840 let _ = fs::remove_file(path);
1841 }
1842
1843 #[test]
1844 fn decode_output_budget_rejects_large_storage_window_before_allocation() {
1845 let file = TiffFile::from_bytes_with_options(
1846 build_stripped_tiff(4, 4, &[0], &[]),
1847 OpenOptions {
1848 decode_output_bytes: 8,
1849 ..OpenOptions::default()
1850 },
1851 )
1852 .unwrap();
1853
1854 let err = file.read_image_bytes(0).unwrap_err();
1855 assert!(matches!(
1856 err,
1857 Error::DecodeOutputTooLarge {
1858 requested: 16,
1859 limit: 8
1860 }
1861 ));
1862 }
1863
1864 #[test]
1865 fn decode_output_budget_also_bounds_intersecting_storage_blocks() {
1866 let tile = vec![7u8; 16 * 16];
1867 let file = TiffFile::from_bytes_with_options(
1868 build_tiled_tiff(16, 16, 16, 16, &[&tile]),
1869 OpenOptions {
1870 decode_output_bytes: 16,
1871 ..OpenOptions::default()
1872 },
1873 )
1874 .unwrap();
1875
1876 let err = file.read_window_bytes(0, 0, 0, 1, 1).unwrap_err();
1879 assert!(matches!(
1880 err,
1881 Error::DecodeOutputTooLarge {
1882 requested: 256,
1883 limit: 16
1884 }
1885 ));
1886 }
1887
1888 #[test]
1889 fn decode_output_budget_rejects_large_color_decoded_output() {
1890 let mut color_map = Vec::new();
1891 color_map.extend((0u16..16).map(|value| value * 17 * 257));
1892 color_map.extend((0u16..16).map(|value| (15 - value) * 17 * 257));
1893 color_map.extend((0u16..16).map(|value| value * 8 * 257));
1894 let file = TiffFile::from_bytes_with_options(
1895 build_stripped_tiff(
1896 1,
1897 1,
1898 &[0x00],
1899 &[
1900 (258, 3, 1, inline_short(4)),
1901 (262, 3, 1, inline_short(3)),
1902 (
1903 320,
1904 3,
1905 color_map.len() as u32,
1906 color_map.iter().flat_map(|value| le_u16(*value)).collect(),
1907 ),
1908 ],
1909 ),
1910 OpenOptions {
1911 decode_output_bytes: 2,
1912 ..OpenOptions::default()
1913 },
1914 )
1915 .unwrap();
1916
1917 let err = file.read_decoded_image_bytes(0).unwrap_err();
1918 assert!(matches!(
1919 err,
1920 Error::DecodeOutputTooLarge {
1921 requested: 3,
1922 limit: 2
1923 }
1924 ));
1925 }
1926
1927 #[test]
1928 fn bigtiff_ifd_entry_count_respects_parse_budget_before_body_read() {
1929 let mut data = bigtiff_header(16);
1930 data.extend_from_slice(&le_u64(2));
1931
1932 let err = match TiffFile::from_bytes_with_options(
1933 data,
1934 OpenOptions {
1935 parse_budgets: ParseBudgets {
1936 max_ifd_entries: 1,
1937 ..ParseBudgets::default()
1938 },
1939 ..OpenOptions::default()
1940 },
1941 ) {
1942 Ok(_) => panic!("expected parse budget error"),
1943 Err(err) => err,
1944 };
1945 assert!(
1946 matches!(err, Error::InvalidImageLayout(message) if message.contains("entry count"))
1947 );
1948 }
1949
1950 #[test]
1951 fn bigtiff_tag_value_bytes_respect_parse_budget_before_value_read() {
1952 let mut data = bigtiff_header(16);
1953 data.extend_from_slice(&le_u64(1));
1954 data.extend_from_slice(&le_u16(256));
1955 data.extend_from_slice(&le_u16(1));
1956 data.extend_from_slice(&le_u64(9));
1957 data.extend_from_slice(&le_u64(1024));
1958 data.extend_from_slice(&le_u64(0));
1959
1960 let err = match TiffFile::from_bytes_with_options(
1961 data,
1962 OpenOptions {
1963 parse_budgets: ParseBudgets {
1964 max_tag_value_bytes: 8,
1965 ..ParseBudgets::default()
1966 },
1967 ..OpenOptions::default()
1968 },
1969 ) {
1970 Ok(_) => panic!("expected parse budget error"),
1971 Err(err) => err,
1972 };
1973 assert!(
1974 matches!(err, Error::InvalidTagValue { tag: 256, reason } if reason.contains("parse budget"))
1975 );
1976 }
1977
1978 #[test]
1979 fn bigtiff_tag_value_bytes_respect_aggregate_parse_budget() {
1980 let mut data = bigtiff_header(16);
1981 data.extend_from_slice(&le_u64(2));
1982 data.extend_from_slice(&le_u16(65000));
1983 data.extend_from_slice(&le_u16(1));
1984 data.extend_from_slice(&le_u64(8));
1985 data.extend_from_slice(&[0x11; 8]);
1986 data.extend_from_slice(&le_u16(65001));
1987 data.extend_from_slice(&le_u16(1));
1988 data.extend_from_slice(&le_u64(8));
1989 data.extend_from_slice(&[0x22; 8]);
1990 data.extend_from_slice(&le_u64(0));
1991
1992 let err = match TiffFile::from_bytes_with_options(
1993 data,
1994 OpenOptions {
1995 parse_budgets: ParseBudgets {
1996 max_tag_value_bytes: 8,
1997 max_metadata_value_bytes: 8,
1998 ..ParseBudgets::default()
1999 },
2000 ..OpenOptions::default()
2001 },
2002 ) {
2003 Ok(_) => panic!("expected aggregate parse budget error"),
2004 Err(err) => err,
2005 };
2006 assert!(
2007 matches!(err, Error::InvalidTagValue { tag: 65001, reason } if reason.contains("aggregate metadata"))
2008 );
2009 }
2010
2011 #[test]
2012 fn bigtiff_ifd_chain_respects_parse_budget() {
2013 let mut data = bigtiff_header(16);
2014 data.extend_from_slice(&le_u64(0));
2015 data.extend_from_slice(&le_u64(32));
2016 data.extend_from_slice(&le_u64(0));
2017 data.extend_from_slice(&le_u64(0));
2018
2019 let err = match TiffFile::from_bytes_with_options(
2020 data,
2021 OpenOptions {
2022 parse_budgets: ParseBudgets {
2023 max_ifds: 1,
2024 ..ParseBudgets::default()
2025 },
2026 ..OpenOptions::default()
2027 },
2028 ) {
2029 Ok(_) => panic!("expected parse budget error"),
2030 Err(err) => err,
2031 };
2032 assert!(matches!(err, Error::Other(message) if message.contains("parse budget")));
2033 }
2034
2035 #[test]
2036 fn rejects_bigtiff_long8_dimension_that_exceeds_u32() {
2037 let mut data = bigtiff_header(16);
2038 data.extend_from_slice(&le_u64(2));
2039 data.extend_from_slice(&le_u16(256));
2040 data.extend_from_slice(&le_u16(16));
2041 data.extend_from_slice(&le_u64(1));
2042 data.extend_from_slice(&le_u64(u64::from(u32::MAX) + 2));
2043 data.extend_from_slice(&le_u16(257));
2044 data.extend_from_slice(&le_u16(16));
2045 data.extend_from_slice(&le_u64(1));
2046 data.extend_from_slice(&le_u64(1));
2047 data.extend_from_slice(&le_u64(0));
2048
2049 let file = TiffFile::from_bytes(data).unwrap();
2050 let err = file.ifd(0).unwrap().raster_layout().unwrap_err();
2051 assert!(
2052 matches!(err, Error::InvalidImageLayout(message) if message.contains("dimensions"))
2053 );
2054 }
2055
2056 #[test]
2057 fn oversized_strip_byte_count_is_rejected_before_payload_read() {
2058 let data = build_stripped_tiff(
2059 2,
2060 2,
2061 &[1, 2, 3, 4],
2062 &[(279, 4, 1, le_u32(u32::MAX).to_vec())],
2063 );
2064 let source = Arc::new(CountingSource::new(data));
2065 let file = TiffFile::from_source(source.clone()).unwrap();
2066 source.reset_reads();
2067
2068 let err = file.read_image_bytes(0).unwrap_err();
2069 assert!(err.to_string().contains("block read budget"));
2070 assert_eq!(source.reads(), 0);
2071 }
2072
2073 #[test]
2074 fn oversized_tile_byte_count_is_rejected_before_payload_read() {
2075 let mut data = build_tiled_tiff(2, 2, 2, 2, &[&[1, 2, 3, 4]]);
2076 overwrite_classic_inline_long_tag(&mut data, 325, u32::MAX);
2077 let source = Arc::new(CountingSource::new(data));
2078 let file = TiffFile::from_source(source.clone()).unwrap();
2079 source.reset_reads();
2080
2081 let err = file.read_image_bytes(0).unwrap_err();
2082 assert!(err.to_string().contains("block read budget"));
2083 assert_eq!(source.reads(), 0);
2084 }
2085
2086 #[test]
2087 fn huge_planar_tile_count_overflow_is_rejected_without_panicking() {
2088 let data = build_tiled_tiff_with_overrides(
2089 u32::MAX,
2090 u32::MAX,
2091 1,
2092 1,
2093 &[&[0]],
2094 &[(277, 3, 1, inline_short(2)), (284, 3, 1, inline_short(2))],
2095 );
2096 let file = TiffFile::from_bytes(data).unwrap();
2097
2098 let err = file.read_window_bytes(0, 0, 0, 1, 1).unwrap_err();
2099 assert!(
2100 matches!(err, Error::InvalidImageLayout(message) if message.contains("tile count"))
2101 );
2102 }
2103
2104 #[test]
2105 fn reads_stripped_u8_image() {
2106 let data = build_stripped_tiff(2, 2, &[1, 2, 3, 4], &[]);
2107 let file = TiffFile::from_bytes(data).unwrap();
2108 let image = file.read_image::<u8>(0).unwrap();
2109 assert_eq!(image.shape(), &[2, 2]);
2110 let (values, offset) = image.into_raw_vec_and_offset();
2111 assert_eq!(offset, Some(0));
2112 assert_eq!(values, vec![1, 2, 3, 4]);
2113 }
2114
2115 #[test]
2116 fn reads_single_chunky_band_and_window() {
2117 let data = build_stripped_tiff(
2118 2,
2119 2,
2120 &[
2121 1, 10, 100, 2, 20, 110, 3, 30, 120, 4, 40, 130,
2125 ],
2126 &[
2127 (262, 3, 1, inline_short(2)),
2128 (277, 3, 1, inline_short(3)),
2129 (279, 4, 1, le_u32(12).to_vec()),
2130 ],
2131 );
2132 let file = TiffFile::from_bytes(data).unwrap();
2133
2134 let green = file.read_band::<u8>(0, 1).unwrap();
2135 assert_eq!(green.shape(), &[2, 2]);
2136 let (green_values, offset) = green.into_raw_vec_and_offset();
2137 assert_eq!(offset, Some(0));
2138 assert_eq!(green_values, vec![10, 20, 30, 40]);
2139
2140 let blue_window = file.read_band_window::<u8>(0, 2, 0, 1, 2, 1).unwrap();
2141 assert_eq!(blue_window.shape(), &[2, 1]);
2142 let (blue_values, offset) = blue_window.into_raw_vec_and_offset();
2143 assert_eq!(offset, Some(0));
2144 assert_eq!(blue_values, vec![110, 130]);
2145
2146 let err = file.read_band::<u8>(0, 3).unwrap_err();
2147 assert!(matches!(
2148 err,
2149 Error::BandIndexOutOfBounds {
2150 index: 3,
2151 band_count: 3
2152 }
2153 ));
2154 }
2155
2156 #[test]
2157 fn planar_band_reads_only_requested_plane() {
2158 let data = build_planar_stripped_tiff(
2159 2,
2160 2,
2161 &[&[1, 2, 3, 4], &[10, 20, 30, 40], &[100, 110, 120, 130]],
2162 );
2163 let source = Arc::new(CountingSource::new(data));
2164 let file = TiffFile::from_source(source.clone()).unwrap();
2165 source.reset_reads();
2166
2167 let blue = file.read_band::<u8>(0, 2).unwrap();
2168 assert_eq!(blue.shape(), &[2, 2]);
2169 let (values, offset) = blue.into_raw_vec_and_offset();
2170 assert_eq!(offset, Some(0));
2171 assert_eq!(values, vec![100, 110, 120, 130]);
2172 assert_eq!(source.reads(), 1);
2173 }
2174
2175 #[test]
2176 fn keeps_subbyte_palette_reads_raw_and_offers_explicit_decoded_pixels() {
2177 let mut color_map = Vec::new();
2178 color_map.extend((0u16..16).map(|value| value * 17 * 257));
2179 color_map.extend((0u16..16).map(|value| (15 - value) * 17 * 257));
2180 color_map.extend((0u16..16).map(|value| value * 8 * 257));
2181 let data = build_stripped_tiff(
2182 4,
2183 1,
2184 &[0x01, 0x23],
2185 &[
2186 (258, 3, 1, inline_short(4)),
2187 (262, 3, 1, inline_short(3)),
2188 (
2189 320,
2190 3,
2191 color_map.len() as u32,
2192 color_map.iter().flat_map(|value| le_u16(*value)).collect(),
2193 ),
2194 ],
2195 );
2196 let file = TiffFile::from_bytes(data).unwrap();
2197
2198 let image = file.read_image::<u8>(0).unwrap();
2199 assert_eq!(image.shape(), &[1, 4]);
2200 let (values, offset) = image.into_raw_vec_and_offset();
2201 assert_eq!(offset, Some(0));
2202 assert_eq!(values, vec![0, 1, 2, 3]);
2203
2204 let image = file.read_decoded_image::<u8>(0).unwrap();
2205 assert_eq!(image.shape(), &[1, 4, 3]);
2206 let (values, offset) = image.into_raw_vec_and_offset();
2207 assert_eq!(offset, Some(0));
2208 assert_eq!(
2209 values,
2210 vec![
2211 0, 255, 0, 17, 238, 8, 34, 221, 16, 51, 204, 24
2215 ]
2216 );
2217
2218 let sample_bytes = file.read_image_bytes(0).unwrap();
2219 assert_eq!(sample_bytes, vec![0, 1, 2, 3]);
2220 }
2221
2222 #[test]
2223 fn keeps_subsampled_ycbcr_reads_raw_and_offers_explicit_decoded_pixels() {
2224 let data = build_stripped_tiff(
2225 2,
2226 2,
2227 &[10u8, 20, 30, 40, 128, 128],
2228 &[
2229 (
2230 258,
2231 3,
2232 3,
2233 [8u16, 8, 8].into_iter().flat_map(le_u16).collect(),
2234 ),
2235 (262, 3, 1, inline_short(6)),
2236 (277, 3, 1, inline_short(3)),
2237 (530, 3, 2, [2u16, 2].into_iter().flat_map(le_u16).collect()),
2238 ],
2239 );
2240 let file = TiffFile::from_bytes(data).unwrap();
2241
2242 let image = file.read_image::<u8>(0).unwrap();
2243 assert_eq!(image.shape(), &[2, 2, 3]);
2244 let (values, offset) = image.into_raw_vec_and_offset();
2245 assert_eq!(offset, Some(0));
2246 assert_eq!(
2247 values,
2248 vec![
2249 10, 128, 128, 20, 128, 128, 30, 128, 128, 40, 128, 128
2253 ]
2254 );
2255
2256 let image = file.read_decoded_image::<u8>(0).unwrap();
2257 assert_eq!(image.shape(), &[2, 2, 3]);
2258 let (rgb, offset) = image.into_raw_vec_and_offset();
2259 assert_eq!(offset, Some(0));
2260 assert_eq!(
2261 rgb,
2262 vec![
2263 10, 10, 10, 20, 20, 20, 30, 30, 30, 40, 40, 40
2267 ]
2268 );
2269
2270 let samples = file.read_image::<u8>(0).unwrap();
2271 let (values, offset) = samples.into_raw_vec_and_offset();
2272 assert_eq!(offset, Some(0));
2273 assert_eq!(
2274 values,
2275 vec![
2276 10, 128, 128, 20, 128, 128, 30, 128, 128, 40, 128, 128
2280 ]
2281 );
2282 }
2283
2284 #[test]
2285 fn accepts_long_typed_bits_per_sample_and_sample_format() {
2286 let data = build_stripped_tiff(
2288 1,
2289 1,
2290 &[0x34, 0x12],
2291 &[
2292 (258, 4, 1, le_u32(16).to_vec()),
2293 (339, 4, 1, le_u32(1).to_vec()),
2294 ],
2295 );
2296 let file = TiffFile::from_bytes(data).unwrap();
2297 let image = file.read_image::<u16>(0).unwrap();
2298 assert_eq!(image[[0, 0]], 0x1234);
2299 }
2300
2301 #[test]
2302 fn rejects_unexpected_bits_per_sample_tag_type() {
2303 let data = build_stripped_tiff(1, 1, &[7], &[(258, 11, 1, 16f32.to_le_bytes().to_vec())]);
2304 let file = TiffFile::from_bytes(data).unwrap();
2305 let error = file.read_image_bytes(0).unwrap_err();
2306 assert!(
2307 matches!(error, Error::UnexpectedTagType { tag: 258, .. }),
2308 "{error}"
2309 );
2310 }
2311
2312 #[cfg(feature = "webp")]
2313 #[test]
2314 fn decodes_webp_compressed_rgb_tiles() {
2315 let (tile_w, tile_h) = (16usize, 16usize);
2316 let mut rgb = vec![0u8; tile_w * tile_h * 3];
2317 for row in 0..tile_h {
2318 for col in 0..tile_w {
2319 let base = (row * tile_w + col) * 3;
2320 rgb[base] = (row * 16) as u8;
2321 rgb[base + 1] = (col * 16) as u8;
2322 rgb[base + 2] = ((row + col) * 8) as u8;
2323 }
2324 }
2325
2326 let mut webp = Vec::new();
2328 image_webp::WebPEncoder::new(&mut webp)
2329 .encode(
2330 &rgb,
2331 tile_w as u32,
2332 tile_h as u32,
2333 image_webp::ColorType::Rgb8,
2334 )
2335 .unwrap();
2336
2337 let data = build_tiled_tiff_with_overrides(
2338 16,
2339 16,
2340 16,
2341 16,
2342 &[&webp],
2343 &[
2344 (
2345 258,
2346 3,
2347 3,
2348 [8u16, 8, 8].into_iter().flat_map(le_u16).collect(),
2349 ),
2350 (259, 3, 1, inline_short(50001)),
2351 (262, 3, 1, inline_short(2)),
2352 (277, 3, 1, inline_short(3)),
2353 ],
2354 );
2355 let file = TiffFile::from_bytes(data).unwrap();
2356 let image = file.read_image::<u8>(0).unwrap();
2357 assert_eq!(image.shape(), &[16, 16, 3]);
2358 let (values, offset) = image.into_raw_vec_and_offset();
2359 assert_eq!(offset, Some(0));
2360 assert_eq!(values, rgb);
2361 }
2362
2363 #[cfg(feature = "webp")]
2364 #[test]
2365 fn rejects_webp_payload_dimensions_that_do_not_match_tile() {
2366 let rgb = vec![17u8; 8 * 32 * 3];
2367 let mut webp = Vec::new();
2368 image_webp::WebPEncoder::new(&mut webp)
2369 .encode(&rgb, 8, 32, image_webp::ColorType::Rgb8)
2370 .unwrap();
2371
2372 let data = build_tiled_tiff_with_overrides(
2373 16,
2374 16,
2375 16,
2376 16,
2377 &[&webp],
2378 &[
2379 (
2380 258,
2381 3,
2382 3,
2383 [8u16, 8, 8].into_iter().flat_map(le_u16).collect(),
2384 ),
2385 (259, 3, 1, inline_short(50001)),
2386 (262, 3, 1, inline_short(2)),
2387 (277, 3, 1, inline_short(3)),
2388 ],
2389 );
2390 let file = TiffFile::from_bytes(data).unwrap();
2391 let error = file.read_image::<u8>(0).unwrap_err();
2392 assert!(error.to_string().contains("dimensions 8x32"), "{error}");
2393 }
2394
2395 #[cfg(feature = "jpeg")]
2396 #[test]
2397 fn rejects_jpeg_payload_dimensions_that_do_not_match_tile() {
2398 let pixels = vec![17u8; 8 * 32];
2399 let mut jpeg = Vec::new();
2400 jpeg_encoder::Encoder::new(&mut jpeg, 80)
2401 .encode(&pixels, 8, 32, jpeg_encoder::ColorType::Luma)
2402 .unwrap();
2403
2404 let data = build_tiled_tiff_with_overrides(
2407 16,
2408 16,
2409 16,
2410 16,
2411 &[&jpeg],
2412 &[(259, 3, 1, inline_short(7))],
2413 );
2414 let file = TiffFile::from_bytes(data).unwrap();
2415 let error = file.read_image::<u8>(0).unwrap_err();
2416 assert!(error.to_string().contains("dimensions 8x32"), "{error}");
2417 }
2418
2419 #[test]
2420 fn ycbcr_decode_honors_reference_black_white_ranges() {
2421 let reference: [u32; 12] = [16, 1, 235, 1, 128, 1, 240, 1, 128, 1, 240, 1];
2423 let data = build_stripped_tiff(
2424 1,
2425 1,
2426 &[126u8, 201, 190],
2427 &[
2428 (
2429 258,
2430 3,
2431 3,
2432 [8u16, 8, 8].into_iter().flat_map(le_u16).collect(),
2433 ),
2434 (262, 3, 1, inline_short(6)),
2435 (277, 3, 1, inline_short(3)),
2436 (532, 5, 6, reference.into_iter().flat_map(le_u32).collect()),
2437 ],
2438 );
2439 let file = TiffFile::from_bytes(data).unwrap();
2440
2441 let image = file.read_decoded_image::<u8>(0).unwrap();
2442 let (rgb, offset) = image.into_raw_vec_and_offset();
2443 assert_eq!(offset, Some(0));
2444 assert_eq!(rgb, vec![227, 49, 255]);
2448 }
2449
2450 #[test]
2451 fn reads_horizontal_predictor_u16_strip() {
2452 let encoded = [1, 0, 1, 0, 2, 0];
2453 let data = build_stripped_tiff(
2454 3,
2455 1,
2456 &encoded,
2457 &[
2458 (258, 3, 1, [16, 0, 0, 0].to_vec()),
2459 (317, 3, 1, [2, 0, 0, 0].to_vec()),
2460 ],
2461 );
2462 let file = TiffFile::from_bytes(data).unwrap();
2463 let image = file.read_image::<u16>(0).unwrap();
2464 assert_eq!(image.shape(), &[1, 3]);
2465 let (values, offset) = image.into_raw_vec_and_offset();
2466 assert_eq!(offset, Some(0));
2467 assert_eq!(values, vec![1, 2, 4]);
2468 }
2469
2470 #[test]
2471 fn reads_lerc_f32_strip() {
2472 let mut blob = build_lerc2_header_v2(2, 2, 4, 6, 0.0, 1.0, 4.0, 1 + 16);
2473 blob.extend_from_slice(&0u32.to_le_bytes());
2474 blob.push(1);
2475 for value in [1.0f32, 2.0, 3.0, 4.0] {
2476 blob.extend_from_slice(&value.to_le_bytes());
2477 }
2478
2479 let data = build_lerc_tiff(2, 2, &blob, 32, 3, 1, None);
2480 let file = TiffFile::from_bytes(data).unwrap();
2481 let image = file.read_image::<f32>(0).unwrap();
2482 let (values, offset) = image.into_raw_vec_and_offset();
2483 assert_eq!(offset, Some(0));
2484 assert_eq!(values, vec![1.0, 2.0, 3.0, 4.0]);
2485 }
2486
2487 #[test]
2488 fn non_lerc_read_ignores_irrelevant_lerc_parameters() {
2489 let data = build_stripped_tiff(1, 1, &[9], &[(50674, 3, 1, inline_short(1))]);
2490 let file = TiffFile::from_bytes(data).unwrap();
2491 let image = file.read_image::<u8>(0).unwrap();
2492 assert_eq!(image.into_raw_vec_and_offset().0, vec![9]);
2493 }
2494
2495 #[test]
2496 fn reads_lerc_masked_f32_strip_as_nan() {
2497 let mask = [1u8, 0, 1, 1];
2498 let encoded_mask = encode_mask_rle(&mask);
2499 let mut blob =
2500 build_lerc2_header_v2(2, 2, 3, 6, 0.0, 1.0, 4.0, encoded_mask.len() + 1 + 12);
2501 blob.extend_from_slice(&(encoded_mask.len() as u32).to_le_bytes());
2502 blob.extend_from_slice(&encoded_mask);
2503 blob.push(1);
2504 for value in [1.0f32, 3.0, 4.0] {
2505 blob.extend_from_slice(&value.to_le_bytes());
2506 }
2507
2508 let data = build_lerc_tiff(2, 2, &blob, 32, 3, 1, None);
2509 let file = TiffFile::from_bytes(data).unwrap();
2510 let image = file.read_image::<f32>(0).unwrap();
2511 let (values, offset) = image.into_raw_vec_and_offset();
2512 assert_eq!(offset, Some(0));
2513 assert_eq!(values[0], 1.0);
2514 assert!(values[1].is_nan());
2515 assert_eq!(values[2], 3.0);
2516 assert_eq!(values[3], 4.0);
2517 }
2518
2519 #[test]
2520 fn reads_lerc_chunky_rgb_band_set_strip() {
2521 let mut red = build_lerc2_header_v2(2, 1, 2, 1, 0.0, 1.0, 1.0, 0);
2522 red.extend_from_slice(&0u32.to_le_bytes());
2523 let mut green = build_lerc2_header_v2(2, 1, 2, 1, 0.0, 2.0, 2.0, 0);
2524 green.extend_from_slice(&0u32.to_le_bytes());
2525 let mut blue = build_lerc2_header_v2(2, 1, 2, 1, 0.0, 3.0, 3.0, 0);
2526 blue.extend_from_slice(&0u32.to_le_bytes());
2527
2528 let mut blob = red;
2529 blob.extend_from_slice(&green);
2530 blob.extend_from_slice(&blue);
2531
2532 let data = build_lerc_tiff(2, 1, &blob, 8, 1, 3, None);
2533 let file = TiffFile::from_bytes(data).unwrap();
2534 let image = file.read_image::<u8>(0).unwrap();
2535 assert_eq!(image.shape(), &[1, 2, 3]);
2536 let (values, offset) = image.into_raw_vec_and_offset();
2537 assert_eq!(offset, Some(0));
2538 assert_eq!(values, vec![1, 2, 3, 1, 2, 3]);
2539 }
2540
2541 #[test]
2542 fn reads_lerc_chunky_rgb_depth_blob_strip() {
2543 let mut blob = build_lerc2_header_v4(2, 1, 3, 2, 1, 0.0, 1.0, 6.0, 6 + 6 + 1 + 6);
2544 blob.extend_from_slice(&0u32.to_le_bytes());
2545 for value in [1u8, 2, 3] {
2546 blob.extend_from_slice(&value.to_le_bytes());
2547 }
2548 for value in [4u8, 5, 6] {
2549 blob.extend_from_slice(&value.to_le_bytes());
2550 }
2551 blob.push(1);
2552 blob.extend_from_slice(&[1, 2, 3, 4, 5, 6]);
2553 let blob = finalize_lerc2_v4_with_checksum(blob);
2554
2555 let data = build_lerc_tiff(2, 1, &blob, 8, 1, 3, Some([4, 0]));
2556 let file = TiffFile::from_bytes(data).unwrap();
2557 let image = file.read_image::<u8>(0).unwrap();
2558 assert_eq!(image.shape(), &[1, 2, 3]);
2559 let (values, offset) = image.into_raw_vec_and_offset();
2560 assert_eq!(offset, Some(0));
2561 assert_eq!(values, vec![1, 2, 3, 4, 5, 6]);
2562 }
2563
2564 #[test]
2565 fn rejects_lerc2_blob_size_before_checksum_range_without_panicking() {
2566 let mut blob = build_lerc2_header_v4(1, 1, 1, 1, 1, 0.0, 1.0, 1.0, 0);
2567 blob[34..38].copy_from_slice(&8i32.to_le_bytes());
2568
2569 let data = build_lerc_tiff(1, 1, &blob, 8, 1, 1, Some([4, 0]));
2570 let file = TiffFile::from_bytes(data).unwrap();
2571 let error = file.read_image_bytes(0).unwrap_err();
2572 assert!(error.to_string().contains("invalid Lerc2 v4 blob size 8"));
2573 }
2574
2575 #[test]
2576 fn rejects_lerc2_header_dimensions_before_allocating_mask() {
2577 let mut blob = build_lerc2_header_v2(u32::MAX, u32::MAX, 1, 1, 0.0, 0.0, 1.0, 4);
2578 blob.extend_from_slice(&4u32.to_le_bytes());
2579 blob.extend_from_slice(&[0, 0, 0, 0]);
2580
2581 let data = build_lerc_tiff(1, 1, &blob, 8, 1, 1, None);
2582 let file = TiffFile::from_bytes(data).unwrap();
2583 let error = file.read_image_bytes(0).unwrap_err();
2584 assert!(error.to_string().contains("LERC raster dimensions"));
2585 }
2586
2587 #[test]
2588 fn rejects_truncated_lerc2_header_dimensions_before_decoder() {
2589 let blob = build_lerc2_header_v2(u32::MAX, u32::MAX, 1, 1, 0.0, 0.0, 1.0, 64);
2590
2591 let data = build_lerc_tiff(1, 1, &blob, 8, 1, 1, None);
2592 let file = TiffFile::from_bytes(data).unwrap();
2593 let error = file.read_image_bytes(0).unwrap_err();
2594 assert!(error.to_string().contains("LERC raster dimensions"));
2595 }
2596
2597 #[test]
2598 fn reads_lerc_deflate_f32_strip() {
2599 let mut blob = build_lerc2_header_v2(2, 2, 4, 6, 0.0, 1.0, 4.0, 1 + 16);
2600 blob.extend_from_slice(&0u32.to_le_bytes());
2601 blob.push(1);
2602 for value in [1.0f32, 2.0, 3.0, 4.0] {
2603 blob.extend_from_slice(&value.to_le_bytes());
2604 }
2605
2606 let mut encoder = ZlibEncoder::new(Vec::new(), FlateCompression::default());
2607 std::io::Write::write_all(&mut encoder, &blob).unwrap();
2608 let compressed = encoder.finish().unwrap();
2609
2610 let data = build_lerc_tiff(2, 2, &compressed, 32, 3, 1, Some([2, 1]));
2611 let file = TiffFile::from_bytes(data).unwrap();
2612 let image = file.read_image::<f32>(0).unwrap();
2613 let (values, offset) = image.into_raw_vec_and_offset();
2614 assert_eq!(offset, Some(0));
2615 assert_eq!(values, vec![1.0, 2.0, 3.0, 4.0]);
2616 }
2617
2618 #[cfg(feature = "zstd")]
2619 #[test]
2620 fn reads_lerc_zstd_f32_strip() {
2621 let mut blob = build_lerc2_header_v2(2, 2, 4, 6, 0.0, 1.0, 4.0, 1 + 16);
2622 blob.extend_from_slice(&0u32.to_le_bytes());
2623 blob.push(1);
2624 for value in [1.0f32, 2.0, 3.0, 4.0] {
2625 blob.extend_from_slice(&value.to_le_bytes());
2626 }
2627
2628 let compressed = ruzstd::encoding::compress_to_vec(
2629 &blob[..],
2630 ruzstd::encoding::CompressionLevel::Fastest,
2631 );
2632 let data = build_lerc_tiff(2, 2, &compressed, 32, 3, 1, Some([2, 2]));
2633 let file = TiffFile::from_bytes(data).unwrap();
2634 let image = file.read_image::<f32>(0).unwrap();
2635 let (values, offset) = image.into_raw_vec_and_offset();
2636 assert_eq!(offset, Some(0));
2637 assert_eq!(values, vec![1.0, 2.0, 3.0, 4.0]);
2638 }
2639
2640 #[test]
2641 fn reads_stripped_u8_window() {
2642 let data = build_multi_strip_tiff(
2643 4,
2644 &[
2645 &[1, 2, 3, 4],
2646 &[5, 6, 7, 8],
2647 &[9, 10, 11, 12],
2648 &[13, 14, 15, 16],
2649 ],
2650 );
2651 let file = TiffFile::from_bytes(data).unwrap();
2652 let window = file.read_window::<u8>(0, 1, 1, 2, 2).unwrap();
2653 assert_eq!(window.shape(), &[2, 2]);
2654 let (values, offset) = window.into_raw_vec_and_offset();
2655 assert_eq!(offset, Some(0));
2656 assert_eq!(values, vec![6, 7, 10, 11]);
2657 }
2658
2659 #[test]
2660 fn reads_tiled_u8_window() {
2661 let data = build_tiled_tiff(
2662 4,
2663 4,
2664 2,
2665 2,
2666 &[
2667 &[1, 2, 5, 6],
2668 &[3, 4, 7, 8],
2669 &[9, 10, 13, 14],
2670 &[11, 12, 15, 16],
2671 ],
2672 );
2673 let file = TiffFile::from_bytes(data).unwrap();
2674 let window = file.read_window::<u8>(0, 1, 1, 2, 2).unwrap();
2675 assert_eq!(window.shape(), &[2, 2]);
2676 let (values, offset) = window.into_raw_vec_and_offset();
2677 assert_eq!(offset, Some(0));
2678 assert_eq!(values, vec![6, 7, 10, 11]);
2679 }
2680
2681 #[test]
2682 fn windowed_tiled_reads_only_intersecting_blocks() {
2683 let data = build_tiled_tiff(
2684 4,
2685 4,
2686 2,
2687 2,
2688 &[
2689 &[1, 2, 5, 6],
2690 &[3, 4, 7, 8],
2691 &[9, 10, 13, 14],
2692 &[11, 12, 15, 16],
2693 ],
2694 );
2695 let source = Arc::new(CountingSource::new(data));
2696 let file = TiffFile::from_source(source.clone()).unwrap();
2697 source.reset_reads();
2698
2699 let window = file.read_window::<u8>(0, 0, 0, 2, 2).unwrap();
2700 let (values, offset) = window.into_raw_vec_and_offset();
2701 assert_eq!(offset, Some(0));
2702 assert_eq!(values, vec![1, 2, 5, 6]);
2703 assert_eq!(source.reads(), 1);
2704 }
2705
2706 #[test]
2707 fn unwraps_gdal_structural_metadata_block() {
2708 let metadata = GdalStructuralMetadata::from_prefix(
2709 b"GDAL_STRUCTURAL_METADATA_SIZE=000174 bytes\nBLOCK_LEADER=SIZE_AS_UINT4\nBLOCK_TRAILER=LAST_4_BYTES_REPEATED\n",
2710 )
2711 .unwrap();
2712
2713 let payload = [1u8, 2, 3, 4];
2714 let mut block = Vec::new();
2715 block.extend_from_slice(&(payload.len() as u32).to_le_bytes());
2716 block.extend_from_slice(&payload);
2717 block.extend_from_slice(&payload[payload.len() - 4..]);
2718
2719 let unwrapped = metadata
2720 .unwrap_block(&block, crate::ByteOrder::LittleEndian, 256)
2721 .unwrap();
2722 assert_eq!(unwrapped, payload);
2723 }
2724
2725 #[test]
2726 fn rejects_gdal_structural_metadata_trailer_mismatch() {
2727 let metadata = GdalStructuralMetadata::from_prefix(
2728 b"GDAL_STRUCTURAL_METADATA_SIZE=000174 bytes\nBLOCK_LEADER=SIZE_AS_UINT4\nBLOCK_TRAILER=LAST_4_BYTES_REPEATED\n",
2729 )
2730 .unwrap();
2731
2732 let block = [
2733 4u8, 0, 0, 0, 1, 2, 3, 4, 4, 3, 2, 1,
2736 ];
2737
2738 let error = metadata
2739 .unwrap_block(&block, crate::ByteOrder::LittleEndian, 512)
2740 .unwrap_err();
2741 assert!(error.to_string().contains("GDAL block trailer mismatch"));
2742 }
2743
2744 #[test]
2745 fn parses_gdal_structural_metadata_before_binary_prefix_data() {
2746 let rest = "LAYOUT=IFDS_BEFORE_DATA\nBLOCK_ORDER=ROW_MAJOR\nBLOCK_LEADER=SIZE_AS_UINT4\nBLOCK_TRAILER=LAST_4_BYTES_REPEATED\nKNOWN_INCOMPATIBLE_EDITION=NO\n";
2747 let prefix = format!(
2748 "{GDAL_STRUCTURAL_METADATA_PREFIX}{:06} bytes\n{rest}",
2749 rest.len()
2750 );
2751
2752 let mut bytes = vec![0u8; 8];
2753 bytes.extend_from_slice(prefix.as_bytes());
2754 bytes.extend_from_slice(&[0xff, 0x00, 0x80, 0x7f]);
2755
2756 let source = BytesSource::new(bytes);
2757 let metadata = parse_gdal_structural_metadata(&source).unwrap();
2758 assert!(metadata.block_leader_size_as_u32);
2759 assert!(metadata.block_trailer_repeats_last_4_bytes);
2760 }
2761
2762 #[test]
2763 fn parses_gdal_structural_metadata_declared_length_as_header_plus_payload() {
2764 let rest = "LAYOUT=IFDS_BEFORE_DATA\nBLOCK_ORDER=ROW_MAJOR\n";
2765 let prefix = format!(
2766 "{GDAL_STRUCTURAL_METADATA_PREFIX}{:06} bytes\n{rest}",
2767 rest.len()
2768 );
2769 assert_eq!(
2770 parse_gdal_structural_metadata_len(prefix.as_bytes()),
2771 Some(prefix.len())
2772 );
2773 }
2774
2775 #[test]
2776 fn leaves_payload_only_gdal_block_unchanged() {
2777 let metadata = GdalStructuralMetadata {
2778 block_leader_size_as_u32: true,
2779 block_trailer_repeats_last_4_bytes: true,
2780 };
2781 let payload = [0x80u8, 0x1a, 0xcf, 0x68, 0x43, 0x9a, 0x11, 0x08];
2782 let unwrapped = metadata
2783 .unwrap_block(&payload, crate::ByteOrder::LittleEndian, 570)
2784 .unwrap();
2785 assert_eq!(unwrapped, payload);
2786 }
2787
2788 #[test]
2789 fn rejects_zero_rows_per_strip_without_panicking() {
2790 let data = build_stripped_tiff(2, 2, &[1, 2, 3, 4], &[(278, 4, 1, le_u32(0).to_vec())]);
2791 let file = TiffFile::from_bytes(data).unwrap();
2792 let error = file.read_image_bytes(0).unwrap_err();
2793 assert!(error.to_string().contains("RowsPerStrip"));
2794 }
2795}