diff --git a/src/decoder/ifd.rs b/src/decoder/ifd.rs index 217ad26..a17d645 100644 --- a/src/decoder/ifd.rs +++ b/src/decoder/ifd.rs @@ -393,7 +393,7 @@ impl Entry { self.count } - pub(crate) fn offset(&self) -> &[u8] { + pub(crate) fn offset_raw(&self) -> &[u8; 8] { &self.offset } diff --git a/src/decoder/mod.rs b/src/decoder/mod.rs index 53a64ba..0240126 100644 --- a/src/decoder/mod.rs +++ b/src/decoder/mod.rs @@ -20,8 +20,10 @@ mod image; mod logluv; mod stream; mod tag_reader; +mod value; pub use self::buffer::{DecodingSampleBuffer, DecodingSampleSlice, DecodingSampleType}; +pub use self::value::EntryBytesReader; #[deprecated = "Renamed to `DecodingSampleBuffer`"] pub type DecodingResult = DecodingSampleBuffer; @@ -943,6 +945,10 @@ impl Decoder { Ok(val) } + pub fn read_entry(&mut self, entry: ifd::Entry) -> TiffResult> { + EntryBytesReader::from_entry(&mut self.value_reader, entry) + } + /// Reads a TIFF IFA offset/value field #[inline] pub fn read_offset_u64(&mut self) -> Result<[u8; 8], io::Error> { diff --git a/src/decoder/value.rs b/src/decoder/value.rs new file mode 100644 index 0000000..0673f2b --- /dev/null +++ b/src/decoder/value.rs @@ -0,0 +1,196 @@ +use std::io::{self, Cursor, Read, Seek}; + +use crate::{ + decoder::{ifd::Entry, stream::EndianReader, ValueReader}, + tags::{ByteOrder, IfdPointer, Type}, + TiffResult, +}; + +pub struct EntryBytesReader<'lt, R> { + inner: &'lt mut EndianReader, + variant: Variant, +} + +const OFFSET_BUFFER_LEN: usize = 8; + +enum Variant { + Immediate { + /// An in-value reader if the offset field contains the data. Note that we rotate it to the + /// back of this buffer, not the front, so that `Cursor` already contains the right end. + /// We do not let you seek back beyond our start. + data: Cursor<[u8; OFFSET_BUFFER_LEN]>, + value_bytes: u8, + }, + InFile { + // the length relative to the base so we can seek relative without querying the position. + value_bytes: u64, + remainder: u64, + base: IfdPointer, + }, +} + +impl<'lt, R> EntryBytesReader<'lt, R> { + pub(super) fn from_entry(inner: &'lt mut ValueReader, entry: Entry) -> TiffResult + where + R: Read + Seek, + { + let offset: [_; OFFSET_BUFFER_LEN] = *entry.offset_raw(); + let limit = if inner.bigtiff { 8 } else { 4 }; + assert!(limit as usize <= OFFSET_BUFFER_LEN); + + let value_bytes = entry.field_type().value_bytes(entry.count())?; + + if value_bytes <= limit { + // We move the data to the back of the array whereas it is in the front right now. + let mut value = offset; + value.rotate_left(value_bytes as usize); + let mut cursor = Cursor::new(value); + cursor.set_position(OFFSET_BUFFER_LEN as u64 - value_bytes); + + Ok(EntryBytesReader { + inner: &mut inner.reader, + variant: Variant::Immediate { + data: cursor, + value_bytes: value_bytes as u8, + }, + }) + } else { + // remainder > limit >= 0 so this is an unreachable panic. + let pointer = if inner.bigtiff { + let mut offset = offset; + + inner + .reader + .byte_order + .convert(Type::IFD8, &mut offset[..], ByteOrder::native()); + + IfdPointer(u64::from_ne_bytes(offset)) + } else { + let mut offset = *offset[..4].as_array::<4>().unwrap(); + + inner + .reader + .byte_order + .convert(Type::IFD, &mut offset[..], ByteOrder::native()); + + let offset = u32::from_ne_bytes(offset); + IfdPointer(u64::from(offset)) + }; + + inner.reader.goto_offset(pointer.0)?; + + Ok(EntryBytesReader { + inner: &mut inner.reader, + variant: Variant::InFile { + value_bytes, + remainder: value_bytes, + base: pointer, + }, + }) + } + } +} + +impl Read for EntryBytesReader<'_, R> { + fn read(&mut self, buf: &mut [u8]) -> io::Result { + match &mut self.variant { + Variant::Immediate { data, .. } => { + return data.read(buf); + } + Variant::InFile { remainder, .. } => { + let bound = usize::try_from(*remainder).unwrap_or(usize::MAX); + let avail = buf.len().min(bound); + let read = self.inner.inner().read(&mut buf[..avail])?; + + // On a well-behaved `Read`: read <= avail <= bound <=(mathematical) remainder + let consume = read as u64; + *remainder -= consume; + Ok(read) + } + } + } + + fn read_exact(&mut self, buf: &mut [u8]) -> io::Result<()> { + match &mut self.variant { + Variant::Immediate { data, .. } => { + return data.read_exact(buf); + } + Variant::InFile { remainder, .. } => { + let bound = usize::try_from(*remainder).unwrap_or(usize::MAX); + + // Fail early if we would be sure to over-read. + if buf.len() > bound { + return Err(io::Error::new( + io::ErrorKind::UnexpectedEof, + "Tiff value shorter than the requested buffer size", + )); + } + + // since buf.len() <= bound <=(mathematical) remainder + let consume = buf.len() as u64; + self.inner.inner().read_exact(buf)?; + *remainder -= consume; + + Ok(()) + } + } + } +} + +impl Seek for EntryBytesReader<'_, R> { + fn seek(&mut self, offset: io::SeekFrom) -> io::Result { + let (value_bytes, remainder) = match &self.variant { + Variant::Immediate { data, value_bytes } => { + let remainder = data.get_ref().len() as u64 - data.position(); + (*value_bytes as u64, remainder) + } + &Variant::InFile { + value_bytes, + remainder, + base: _, + } => (value_bytes, remainder), + }; + + // Normalize to an offset from the start + let offset_from_start = match offset { + io::SeekFrom::Start(n) => Some(n), + io::SeekFrom::Current(n) => { + let front = value_bytes - remainder; + front.checked_add_signed(n) + } + io::SeekFrom::End(n) => value_bytes.checked_add_signed(n), + }; + + let Some((offset, remainder)) = offset_from_start.and_then(|n| { + let remainder = value_bytes.checked_sub(n)?; + Some((n, remainder)) + }) else { + return Err(io::Error::new( + io::ErrorKind::InvalidInput, + "seek out of bounds of entry value", + )); + }; + + match &mut self.variant { + Variant::Immediate { data, .. } => { + let start = OFFSET_BUFFER_LEN as u64 - value_bytes; + data.set_position(start + offset); + Ok(offset) + } + &mut Variant::InFile { base, .. } => { + // We checked this against overflow on construction. Ignore the offset we get. + self.inner + .inner() + .seek(io::SeekFrom::Start(base.0 + offset))?; + + self.variant = Variant::InFile { + value_bytes, + remainder, + base, + }; + + Ok(offset) + } + } + } +} diff --git a/src/encoder/mod.rs b/src/encoder/mod.rs index ed28b8e..48412e0 100644 --- a/src/encoder/mod.rs +++ b/src/encoder/mod.rs @@ -488,7 +488,8 @@ impl<'a, W: 'a + Write + Seek, K: TiffKind> DirectoryEncoder<'a, W, K> { self.writer.write_u16(entry.field_type().to_u16())?; let count = K::convert_offset(entry.count())?; count.write(self.writer)?; - self.writer.write_bytes(&entry.offset()[..offset_bytes])?; + self.writer + .write_bytes(&entry.offset_raw()[..offset_bytes])?; } Ok(offset)