affs_read/
file.rs

1//! File reading functionality.
2
3use crate::block::{EntryBlock, FileExtBlock, OfsDataBlock};
4use crate::constants::*;
5use crate::error::{AffsError, Result};
6use crate::types::{BlockDevice, FsType};
7
8/// Streaming file reader.
9///
10/// Reads file data sequentially with zero heap allocation.
11/// Supports both OFS and FFS filesystems.
12///
13/// # Example
14///
15/// ```ignore
16/// let mut reader = FileReader::new(&device, FsType::Ffs, file_header_block)?;
17/// let mut buf = [0u8; 1024];
18/// loop {
19///     let n = reader.read(&mut buf)?;
20///     if n == 0 {
21///         break; // EOF
22///     }
23///     // Process buf[..n]
24/// }
25/// ```
26pub struct FileReader<'a, D: BlockDevice> {
27    device: &'a D,
28    fs_type: FsType,
29    /// Block number of file header (for reset/seek).
30    header_block: u32,
31    /// Total file size in bytes.
32    file_size: u32,
33    /// Bytes remaining to read.
34    remaining: u32,
35    /// Current block index within the file (0-based).
36    block_index: u32,
37    /// Initial number of data blocks in header (for reset).
38    initial_blocks_in_header: u32,
39    /// Total number of data blocks in header/ext block.
40    blocks_in_current: u32,
41    /// Index within current header/extension block.
42    index_in_current: u32,
43    /// Initial data block pointers from header (for reset).
44    initial_data_blocks: [u32; MAX_DATABLK],
45    /// Current data block pointers (from header or extension).
46    data_blocks: [u32; MAX_DATABLK],
47    /// Initial extension block (for reset).
48    initial_extension: u32,
49    /// Next extension block.
50    next_extension: u32,
51    /// Initial first data block for OFS (for reset).
52    initial_first_data: u32,
53    /// Current data block (for OFS linked list).
54    current_data_block: u32,
55    /// Offset within current data block.
56    offset_in_block: usize,
57    /// Block buffer.
58    buf: [u8; BLOCK_SIZE],
59}
60
61impl<'a, D: BlockDevice> FileReader<'a, D> {
62    /// Create a new file reader from a file header block.
63    ///
64    /// # Arguments
65    /// * `device` - Block device to read from
66    /// * `fs_type` - Filesystem type (OFS or FFS)
67    /// * `header_block` - Block number of the file header
68    pub fn new(device: &'a D, fs_type: FsType, header_block: u32) -> Result<Self> {
69        let mut buf = [0u8; BLOCK_SIZE];
70        device
71            .read_block(header_block, &mut buf)
72            .map_err(|()| AffsError::BlockReadError)?;
73
74        let entry = EntryBlock::parse(&buf)?;
75
76        if !entry.is_file() {
77            return Err(AffsError::NotAFile);
78        }
79
80        let file_size = entry.byte_size;
81        let blocks_in_current = entry.high_seq as u32;
82
83        // Copy data block pointers
84        let mut data_blocks = [0u32; MAX_DATABLK];
85        data_blocks.copy_from_slice(&entry.hash_table);
86
87        Ok(Self {
88            device,
89            fs_type,
90            header_block,
91            file_size,
92            remaining: file_size,
93            block_index: 0,
94            initial_blocks_in_header: blocks_in_current,
95            blocks_in_current,
96            index_in_current: 0,
97            initial_data_blocks: data_blocks,
98            data_blocks,
99            initial_extension: entry.extension,
100            next_extension: entry.extension,
101            initial_first_data: entry.first_data,
102            current_data_block: entry.first_data,
103            offset_in_block: 0,
104            buf,
105        })
106    }
107
108    /// Create a file reader from an already-parsed entry block.
109    ///
110    /// This avoids re-reading the header block if you already have it.
111    ///
112    /// # Arguments
113    /// * `device` - Block device to read from
114    /// * `fs_type` - Filesystem type (OFS or FFS)
115    /// * `header_block` - Block number of the file header
116    /// * `entry` - Already-parsed entry block
117    pub fn from_entry(
118        device: &'a D,
119        fs_type: FsType,
120        header_block: u32,
121        entry: &EntryBlock,
122    ) -> Result<Self> {
123        if !entry.is_file() {
124            return Err(AffsError::NotAFile);
125        }
126
127        let file_size = entry.byte_size;
128        let blocks_in_current = entry.high_seq as u32;
129
130        let mut data_blocks = [0u32; MAX_DATABLK];
131        data_blocks.copy_from_slice(&entry.hash_table);
132
133        Ok(Self {
134            device,
135            fs_type,
136            header_block,
137            file_size,
138            remaining: file_size,
139            block_index: 0,
140            initial_blocks_in_header: blocks_in_current,
141            blocks_in_current,
142            index_in_current: 0,
143            initial_data_blocks: data_blocks,
144            data_blocks,
145            initial_extension: entry.extension,
146            next_extension: entry.extension,
147            initial_first_data: entry.first_data,
148            current_data_block: entry.first_data,
149            offset_in_block: 0,
150            buf: [0u8; BLOCK_SIZE],
151        })
152    }
153
154    /// Get the total file size in bytes.
155    #[inline]
156    pub const fn size(&self) -> u32 {
157        self.file_size
158    }
159
160    /// Get the block number of the file header.
161    #[inline]
162    pub const fn header_block(&self) -> u32 {
163        self.header_block
164    }
165
166    /// Get the number of bytes remaining to read.
167    #[inline]
168    pub const fn remaining(&self) -> u32 {
169        self.remaining
170    }
171
172    /// Check if we've reached end of file.
173    #[inline]
174    pub const fn is_eof(&self) -> bool {
175        self.remaining == 0
176    }
177
178    /// Get current position in the file.
179    #[inline]
180    pub const fn position(&self) -> u32 {
181        self.file_size - self.remaining
182    }
183
184    /// Reset the reader to the beginning of the file.
185    ///
186    /// This restores all internal state to allow reading from the start.
187    pub fn reset(&mut self) {
188        self.remaining = self.file_size;
189        self.block_index = 0;
190        self.blocks_in_current = self.initial_blocks_in_header;
191        self.index_in_current = 0;
192        self.data_blocks = self.initial_data_blocks;
193        self.next_extension = self.initial_extension;
194        self.current_data_block = self.initial_first_data;
195        self.offset_in_block = 0;
196    }
197
198    /// Read data into a buffer.
199    ///
200    /// Returns the number of bytes read. Returns 0 at end of file.
201    pub fn read(&mut self, out: &mut [u8]) -> Result<usize> {
202        if self.remaining == 0 || out.is_empty() {
203            return Ok(0);
204        }
205
206        let mut total_read = 0;
207
208        while total_read < out.len() && self.remaining > 0 {
209            // If we need to read a new data block
210            if self.offset_in_block == 0 || self.offset_in_block >= self.data_block_size() {
211                self.read_next_data_block()?;
212            }
213
214            // Calculate how much we can read from current block
215            let data_size = self.current_block_data_size();
216            let available = data_size.saturating_sub(self.offset_in_block);
217            let to_read = available
218                .min(out.len() - total_read)
219                .min(self.remaining as usize);
220
221            if to_read == 0 {
222                break;
223            }
224
225            // Copy data
226            let data_start = self.data_offset();
227            let src = &self.buf
228                [data_start + self.offset_in_block..data_start + self.offset_in_block + to_read];
229            out[total_read..total_read + to_read].copy_from_slice(src);
230
231            total_read += to_read;
232            self.offset_in_block += to_read;
233            self.remaining -= to_read as u32;
234        }
235
236        Ok(total_read)
237    }
238
239    /// Read the entire file into a buffer.
240    ///
241    /// The buffer must be at least as large as the file size.
242    /// Returns the number of bytes read.
243    pub fn read_all(&mut self, out: &mut [u8]) -> Result<usize> {
244        if out.len() < self.remaining as usize {
245            return Err(AffsError::BufferTooSmall);
246        }
247
248        let mut total = 0;
249        while self.remaining > 0 {
250            let n = self.read(&mut out[total..])?;
251            if n == 0 {
252                break;
253            }
254            total += n;
255        }
256        Ok(total)
257    }
258
259    /// Get data block size for this filesystem type.
260    #[inline]
261    const fn data_block_size(&self) -> usize {
262        match self.fs_type {
263            FsType::Ofs => OFS_DATA_SIZE,
264            FsType::Ffs => FFS_DATA_SIZE,
265        }
266    }
267
268    /// Get the data offset within a block.
269    #[inline]
270    const fn data_offset(&self) -> usize {
271        match self.fs_type {
272            FsType::Ofs => OfsDataBlock::HEADER_SIZE,
273            FsType::Ffs => 0,
274        }
275    }
276
277    /// Get actual data size in current block.
278    fn current_block_data_size(&self) -> usize {
279        match self.fs_type {
280            FsType::Ofs => {
281                // OFS has explicit data size in header
282                // We need to parse it from current buffer
283                let header = OfsDataBlock::parse(&self.buf).ok();
284                header.map(|h| h.data_size as usize).unwrap_or(0)
285            }
286            FsType::Ffs => {
287                // FFS uses full block, but last block may be partial
288                let block_size = FFS_DATA_SIZE;
289                let remaining = self.remaining as usize + self.offset_in_block;
290                remaining.min(block_size)
291            }
292        }
293    }
294
295    /// Read the next data block.
296    fn read_next_data_block(&mut self) -> Result<()> {
297        let block = self.get_next_data_block()?;
298        if block == 0 {
299            return Err(AffsError::EndOfFile);
300        }
301
302        self.device
303            .read_block(block, &mut self.buf)
304            .map_err(|()| AffsError::BlockReadError)?;
305
306        // Validate OFS data block
307        if matches!(self.fs_type, FsType::Ofs) {
308            let _ = OfsDataBlock::parse(&self.buf)?;
309        }
310
311        self.offset_in_block = 0;
312        self.block_index += 1;
313        Ok(())
314    }
315
316    /// Get the next data block number.
317    fn get_next_data_block(&mut self) -> Result<u32> {
318        match self.fs_type {
319            FsType::Ofs => self.get_next_ofs_block(),
320            FsType::Ffs => self.get_next_ffs_block(),
321        }
322    }
323
324    /// Get next data block for OFS (follows linked list).
325    fn get_next_ofs_block(&mut self) -> Result<u32> {
326        if self.block_index == 0 {
327            // First block - use first_data from header
328            // current_data_block was set in new()
329            return Ok(self.current_data_block);
330        }
331
332        // Follow the linked list
333        // current buffer should have the previous data block
334        let header = OfsDataBlock::parse(&self.buf)?;
335        self.current_data_block = header.next_data;
336        Ok(self.current_data_block)
337    }
338
339    /// Get next data block for FFS (uses block pointer table).
340    fn get_next_ffs_block(&mut self) -> Result<u32> {
341        // Check if we need to load an extension block
342        if self.index_in_current >= self.blocks_in_current {
343            if self.next_extension == 0 {
344                return Ok(0); // No more blocks
345            }
346
347            // Load extension block
348            self.device
349                .read_block(self.next_extension, &mut self.buf)
350                .map_err(|()| AffsError::BlockReadError)?;
351
352            let ext = FileExtBlock::parse(&self.buf)?;
353
354            // Copy data block pointers
355            self.data_blocks.copy_from_slice(&ext.data_blocks);
356            self.blocks_in_current = ext.high_seq as u32;
357            self.next_extension = ext.extension;
358            self.index_in_current = 0;
359        }
360
361        if self.index_in_current >= self.blocks_in_current {
362            return Ok(0);
363        }
364
365        // Get block pointer (stored in reverse order)
366        let idx = self.index_in_current as usize;
367        let block = if idx < MAX_DATABLK {
368            self.data_blocks[MAX_DATABLK - 1 - idx]
369        } else {
370            0
371        };
372
373        self.index_in_current += 1;
374        Ok(block)
375    }
376
377    /// Seek to a specific position in the file.
378    ///
379    /// Note: Seeking backwards resets to the beginning and seeks forward,
380    /// which may need to re-read extension blocks for large files.
381    pub fn seek(&mut self, position: u32) -> Result<()> {
382        if position > self.file_size {
383            return Err(AffsError::EndOfFile);
384        }
385
386        if position == self.position() {
387            return Ok(());
388        }
389
390        // For backward seeks, reset to beginning first
391        if position < self.position() {
392            self.reset();
393        }
394
395        // Seek forward by reading and discarding
396        let mut discard = [0u8; 512];
397        let mut to_skip = position - self.position();
398        while to_skip > 0 {
399            let n = self.read(&mut discard[..to_skip.min(512) as usize])?;
400            if n == 0 {
401                return Err(AffsError::EndOfFile);
402            }
403            to_skip -= n as u32;
404        }
405
406        Ok(())
407    }
408}
409
410#[cfg(test)]
411mod tests {
412    use super::*;
413
414    struct DummyDevice;
415
416    impl BlockDevice for DummyDevice {
417        fn read_block(&self, _block: u32, _buf: &mut [u8; 512]) -> core::result::Result<(), ()> {
418            Err(())
419        }
420    }
421
422    #[test]
423    fn test_file_reader_error_on_bad_device() {
424        let device = DummyDevice;
425        let result = FileReader::new(&device, FsType::Ffs, 100);
426        assert!(result.is_err());
427    }
428}