Skip to main content

iso9660_forensic/
file_reader.rs

1//! Streaming file reader (`IsoFileReader`) implementing `std::io::Read`.
2//!
3//! Reads one sector (2048 bytes) at a time to avoid loading the entire file
4//! into memory. Supports multi-extent files via `extra_extents`.
5
6use std::io::{self, Read, Seek, SeekFrom};
7
8use crate::sector::{read_sector_data, SectorMode};
9
10/// A single extent: `(lba, total_bytes)`.
11type Extent = (u32, u32);
12
13/// Streaming reader for a single ISO 9660 file entry.
14///
15/// Obtained from [`crate::IsoReader::open_file`]. Implements [`Read`].
16pub struct IsoFileReader<R> {
17    inner: R,
18    mode: SectorMode,
19    extents: Vec<Extent>, // [primary] ++ extra_extents
20    total: u32,           // sum of all extent sizes
21
22    // read cursor state
23    ext_idx: usize, // which extent we're reading
24    ext_pos: u32,   // bytes consumed within the current extent
25    sector_buf: [u8; 2048],
26    buf_start: u32,   // byte offset in current extent where sector_buf starts
27    buf_valid: usize, // bytes in sector_buf that are valid (can be < 2048 at last sector)
28    buf_pos: usize,   // read cursor within sector_buf
29}
30
31impl<R: Read + Seek> IsoFileReader<R> {
32    pub(crate) fn new(
33        inner: R,
34        mode: SectorMode,
35        primary_lba: u32,
36        primary_size: u32,
37        extra_extents: Vec<Extent>,
38    ) -> Self {
39        let total = primary_size + extra_extents.iter().map(|e| e.1).sum::<u32>();
40        let mut extents = Vec::with_capacity(1 + extra_extents.len());
41        extents.push((primary_lba, primary_size));
42        extents.extend_from_slice(&extra_extents);
43
44        Self {
45            inner,
46            mode,
47            extents,
48            total,
49            ext_idx: 0,
50            ext_pos: 0,
51            sector_buf: [0u8; 2048],
52            buf_start: u32::MAX, // sentinel: no sector loaded yet
53            buf_valid: 0,
54            buf_pos: 0,
55        }
56    }
57
58    /// Total file size in bytes across all extents.
59    pub fn size(&self) -> u32 {
60        self.total
61    }
62
63    /// Ensure `sector_buf` is loaded for `ext_pos` within the current extent.
64    fn ensure_buf(&mut self) -> io::Result<()> {
65        if self.ext_idx >= self.extents.len() {
66            return Ok(());
67        }
68        let (lba, size) = self.extents[self.ext_idx];
69        if size == 0 {
70            return Ok(());
71        }
72        let sector_start = (self.ext_pos / 2048) * 2048;
73        if self.buf_start == sector_start {
74            return Ok(()); // already loaded
75        }
76        let sector_idx = sector_start / 2048;
77        read_sector_data(
78            &mut self.inner,
79            self.mode,
80            lba as u64 + sector_idx as u64,
81            &mut self.sector_buf,
82        )
83        .map_err(|e| io::Error::other(e.to_string()))?;
84
85        let remaining_in_extent = size - sector_start;
86        self.buf_valid = remaining_in_extent.min(2048) as usize;
87        self.buf_start = sector_start;
88        self.buf_pos = (self.ext_pos - sector_start) as usize;
89        Ok(())
90    }
91}
92
93impl<R: Read + Seek> Seek for IsoFileReader<R> {
94    fn seek(&mut self, pos: SeekFrom) -> io::Result<u64> {
95        let current_abs: i64 = {
96            let before: u32 = self.extents[..self.ext_idx].iter().map(|e| e.1).sum();
97            before as i64 + self.ext_pos as i64
98        };
99        let new_abs = match pos {
100            SeekFrom::Start(p) => p as i64,
101            SeekFrom::End(p) => self.total as i64 + p,
102            SeekFrom::Current(p) => current_abs + p,
103        };
104        let new_abs = new_abs.clamp(0, self.total as i64) as u32;
105
106        // Walk extents to find the new (ext_idx, ext_pos).
107        let mut remaining = new_abs;
108        let mut new_idx = self.extents.len(); // sentinel: at/past end
109        let mut new_pos = 0u32;
110        for (i, &(_, size)) in self.extents.iter().enumerate() {
111            if remaining < size || (remaining == size && i + 1 == self.extents.len()) {
112                new_idx = i;
113                new_pos = remaining;
114                break;
115            }
116            remaining -= size;
117        }
118
119        self.ext_idx = new_idx;
120        self.ext_pos = new_pos;
121        self.buf_start = u32::MAX; // invalidate buffer
122        self.buf_valid = 0;
123        self.buf_pos = 0;
124
125        Ok(new_abs as u64)
126    }
127}
128
129impl<R: Read + Seek> Read for IsoFileReader<R> {
130    fn read(&mut self, buf: &mut [u8]) -> io::Result<usize> {
131        if buf.is_empty() {
132            return Ok(0);
133        }
134        let mut written = 0;
135        while written < buf.len() {
136            if self.ext_idx >= self.extents.len() {
137                break;
138            }
139            let (_, size) = self.extents[self.ext_idx];
140            if self.ext_pos >= size {
141                // Advance to next extent.
142                self.ext_idx += 1;
143                self.ext_pos = 0;
144                self.buf_start = u32::MAX;
145                continue;
146            }
147            self.ensure_buf()?;
148            let available = self.buf_valid - self.buf_pos;
149            if available == 0 {
150                break;
151            }
152            let to_copy = available.min(buf.len() - written);
153            buf[written..written + to_copy]
154                .copy_from_slice(&self.sector_buf[self.buf_pos..self.buf_pos + to_copy]);
155            written += to_copy;
156            self.buf_pos += to_copy;
157            self.ext_pos += to_copy as u32;
158        }
159        Ok(written)
160    }
161}