Skip to main content

tiff_reader/
source.rs

1//! Random-access source abstraction used by the TIFF decoder.
2
3use std::fs::File;
4use std::io;
5#[cfg(not(any(unix, windows)))]
6use std::io::{Read, Seek, SeekFrom};
7use std::ops::Range;
8use std::path::Path;
9use std::sync::Arc;
10#[cfg(not(any(unix, windows)))]
11use std::sync::Mutex;
12
13use memmap2::Mmap;
14
15use crate::error::{Error, Result};
16
17/// Random-access byte source for TIFF decoding.
18pub trait TiffSource: Send + Sync {
19    /// Total object length in bytes.
20    fn len(&self) -> u64;
21
22    /// Returns `true` when the source has no bytes.
23    fn is_empty(&self) -> bool {
24        self.len() == 0
25    }
26
27    /// Read exactly `len` bytes starting at `offset`.
28    fn read_exact_at(&self, offset: u64, len: usize) -> Result<Vec<u8>>;
29
30    /// Expose a whole-object slice when the source is fully resident in memory.
31    fn as_slice(&self) -> Option<&[u8]> {
32        None
33    }
34}
35
36/// Shared source handle used by `TiffFile`.
37pub type SharedSource = Arc<dyn TiffSource>;
38
39/// Safe file-backed source using random-access reads.
40pub struct FileSource {
41    #[cfg(any(unix, windows))]
42    file: File,
43    #[cfg(not(any(unix, windows)))]
44    file: Mutex<File>,
45    len: u64,
46    path: String,
47}
48
49impl FileSource {
50    pub fn open(path: &Path) -> Result<Self> {
51        let file = File::open(path).map_err(|e| Error::Io(e, path.display().to_string()))?;
52        let len = file
53            .metadata()
54            .map_err(|e| Error::Io(e, path.display().to_string()))?
55            .len();
56        let path = path.display().to_string();
57        #[cfg(any(unix, windows))]
58        {
59            Ok(Self { file, len, path })
60        }
61        #[cfg(not(any(unix, windows)))]
62        {
63            Ok(Self {
64                file: Mutex::new(file),
65                len,
66                path,
67            })
68        }
69    }
70}
71
72impl TiffSource for FileSource {
73    fn len(&self) -> u64 {
74        self.len
75    }
76
77    fn read_exact_at(&self, offset: u64, len: usize) -> Result<Vec<u8>> {
78        checked_read_range(offset, len, self.len)?;
79        let mut bytes = vec![0; len];
80        read_file_exact_at(self, offset, &mut bytes)
81            .map_err(|e| Error::Io(e, self.path.clone()))?;
82        Ok(bytes)
83    }
84}
85
86/// Memory-mapped file source.
87pub struct MmapSource {
88    mmap: Mmap,
89}
90
91impl MmapSource {
92    /// Open a file-backed source by memory-mapping the whole file.
93    ///
94    /// # Safety
95    ///
96    /// The caller must guarantee that the mapped file is not mutated or
97    /// truncated for the lifetime of the returned source. This includes writes
98    /// through other file handles and writes from other processes.
99    pub unsafe fn open(path: &Path) -> Result<Self> {
100        let file = File::open(path).map_err(|e| Error::Io(e, path.display().to_string()))?;
101        let mmap =
102            unsafe { Mmap::map(&file) }.map_err(|e| Error::Io(e, path.display().to_string()))?;
103        Ok(Self { mmap })
104    }
105}
106
107impl TiffSource for MmapSource {
108    fn len(&self) -> u64 {
109        self.mmap.len() as u64
110    }
111
112    fn read_exact_at(&self, offset: u64, len: usize) -> Result<Vec<u8>> {
113        let range = checked_slice_range(offset, len, self.len())?;
114        Ok(self.mmap[range].to_vec())
115    }
116
117    fn as_slice(&self) -> Option<&[u8]> {
118        Some(&self.mmap)
119    }
120}
121
122/// In-memory byte-vector source.
123pub struct BytesSource {
124    bytes: Vec<u8>,
125}
126
127impl BytesSource {
128    pub fn new(bytes: Vec<u8>) -> Self {
129        Self { bytes }
130    }
131}
132
133impl TiffSource for BytesSource {
134    fn len(&self) -> u64 {
135        self.bytes.len() as u64
136    }
137
138    fn read_exact_at(&self, offset: u64, len: usize) -> Result<Vec<u8>> {
139        let range = checked_slice_range(offset, len, self.len())?;
140        Ok(self.bytes[range].to_vec())
141    }
142
143    fn as_slice(&self) -> Option<&[u8]> {
144        Some(&self.bytes)
145    }
146}
147
148fn checked_read_range(offset: u64, len: usize, data_len: u64) -> Result<u64> {
149    let length = u64::try_from(len).map_err(|_| Error::OffsetOutOfBounds {
150        offset,
151        length: u64::MAX,
152        data_len,
153    })?;
154    let end = offset.checked_add(length).ok_or(Error::OffsetOutOfBounds {
155        offset,
156        length,
157        data_len,
158    })?;
159    if end > data_len {
160        return Err(Error::OffsetOutOfBounds {
161            offset,
162            length,
163            data_len,
164        });
165    }
166    Ok(end)
167}
168
169fn checked_slice_range(offset: u64, len: usize, data_len: u64) -> Result<Range<usize>> {
170    checked_read_range(offset, len, data_len)?;
171    let start = usize::try_from(offset).map_err(|_| Error::OffsetOutOfBounds {
172        offset,
173        length: len as u64,
174        data_len,
175    })?;
176    let end = start.checked_add(len).ok_or(Error::OffsetOutOfBounds {
177        offset,
178        length: len as u64,
179        data_len,
180    })?;
181    Ok(start..end)
182}
183
184#[cfg(unix)]
185fn read_file_exact_at(source: &FileSource, offset: u64, bytes: &mut [u8]) -> io::Result<()> {
186    use std::os::unix::fs::FileExt;
187
188    read_with_offset_loop(bytes, offset, |buf, current_offset| {
189        source.file.read_at(buf, current_offset)
190    })
191}
192
193#[cfg(windows)]
194fn read_file_exact_at(source: &FileSource, offset: u64, bytes: &mut [u8]) -> io::Result<()> {
195    use std::os::windows::fs::FileExt;
196
197    read_with_offset_loop(bytes, offset, |buf, current_offset| {
198        source.file.seek_read(buf, current_offset)
199    })
200}
201
202#[cfg(not(any(unix, windows)))]
203fn read_file_exact_at(source: &FileSource, offset: u64, bytes: &mut [u8]) -> io::Result<()> {
204    let mut file = source
205        .file
206        .lock()
207        .map_err(|_| io::Error::new(io::ErrorKind::Other, "file source lock poisoned"))?;
208    file.seek(SeekFrom::Start(offset))?;
209    file.read_exact(bytes)
210}
211
212#[cfg(any(unix, windows))]
213fn read_with_offset_loop(
214    bytes: &mut [u8],
215    offset: u64,
216    mut read_at: impl FnMut(&mut [u8], u64) -> io::Result<usize>,
217) -> io::Result<()> {
218    let mut total_read = 0usize;
219    while total_read < bytes.len() {
220        let current_offset = offset + total_read as u64;
221        match read_at(&mut bytes[total_read..], current_offset) {
222            Ok(0) => {
223                return Err(io::Error::new(
224                    io::ErrorKind::UnexpectedEof,
225                    "failed to fill whole buffer",
226                ));
227            }
228            Ok(read) => total_read += read,
229            Err(error) if error.kind() == io::ErrorKind::Interrupted => {}
230            Err(error) => return Err(error),
231        }
232    }
233    Ok(())
234}