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