Skip to main content

sbf_tools/
reader.rs

1//! SBF stream/file reader
2//!
3//! Provides `SbfReader` for reading SBF blocks from any `Read` source.
4
5use std::collections::VecDeque;
6use std::io::Read;
7
8use crate::blocks::SbfBlock;
9use crate::error::{SbfError, SbfResult};
10use crate::header::{SbfHeader, MIN_BLOCK_LENGTH, SBF_SYNC};
11
12/// Default buffer capacity (64KB)
13const DEFAULT_BUFFER_CAPACITY: usize = 65536;
14
15/// Maximum buffer size before trimming (128KB)
16const MAX_BUFFER_SIZE: usize = 131072;
17
18/// Outcome of a single attempt to pull more bytes from the source.
19enum Fill {
20    /// At least one byte was appended to the buffer.
21    Filled,
22    /// The source reported end of stream (`read` returned `Ok(0)`).
23    Eof,
24    /// A non-blocking source has no data available right now (`WouldBlock`).
25    WouldBlock,
26}
27
28/// SBF block reader
29///
30/// Reads SBF blocks from any source implementing `Read`.
31///
32/// # Example
33///
34/// ```no_run
35/// use std::fs::File;
36/// use sbf_tools::SbfReader;
37///
38/// let file = File::open("data.sbf").unwrap();
39/// let mut reader = SbfReader::new(file);
40///
41/// while let Some(result) = reader.next() {
42///     match result {
43///         Ok(block) => println!("Got block: {}", block.name()),
44///         Err(e) => eprintln!("Error: {}", e),
45///     }
46/// }
47/// ```
48pub struct SbfReader<R: Read> {
49    inner: R,
50    buffer: VecDeque<u8>,
51    /// Whether to validate CRC
52    validate_crc: bool,
53    /// Statistics
54    stats: ReaderStats,
55}
56
57/// Reader statistics
58#[derive(Debug, Clone, Default)]
59pub struct ReaderStats {
60    /// Total bytes read from source
61    pub bytes_read: u64,
62    /// Number of blocks successfully parsed
63    pub blocks_parsed: u64,
64    /// Number of CRC errors
65    pub crc_errors: u64,
66    /// Number of parse errors
67    pub parse_errors: u64,
68    /// Bytes skipped looking for sync
69    pub bytes_skipped: u64,
70}
71
72impl<R: Read> SbfReader<R> {
73    /// Create a new SBF reader
74    pub fn new(reader: R) -> Self {
75        Self {
76            inner: reader,
77            buffer: VecDeque::with_capacity(DEFAULT_BUFFER_CAPACITY),
78            validate_crc: true,
79            stats: ReaderStats::default(),
80        }
81    }
82
83    /// Create reader with specific buffer capacity
84    pub fn with_capacity(reader: R, capacity: usize) -> Self {
85        Self {
86            inner: reader,
87            buffer: VecDeque::with_capacity(capacity),
88            validate_crc: true,
89            stats: ReaderStats::default(),
90        }
91    }
92
93    /// Enable or disable CRC validation (default: enabled)
94    pub fn validate_crc(mut self, validate: bool) -> Self {
95        self.validate_crc = validate;
96        self
97    }
98
99    /// Get reader statistics
100    pub fn stats(&self) -> &ReaderStats {
101        &self.stats
102    }
103
104    /// Reset statistics
105    pub fn reset_stats(&mut self) {
106        self.stats = ReaderStats::default();
107    }
108
109    /// Read the next SBF block.
110    ///
111    /// Returns:
112    /// - `Ok(Some(block))` - a block was parsed successfully.
113    /// - `Ok(None)` - end of stream: the source reported EOF and no partial
114    ///   block remains in the buffer.
115    /// - `Err(SbfError::WouldBlock)` - a non-blocking source has no data
116    ///   available yet; call again later. Blocking sources (files, `Cursor`)
117    ///   never return this and report EOF instead.
118    /// - `Err(SbfError::IncompleteBlock { .. })` - the stream ended in the
119    ///   middle of a block (a genuinely truncated final block).
120    /// - `Err(e)` - another parse or I/O error.
121    pub fn read_block(&mut self) -> SbfResult<Option<SbfBlock>> {
122        loop {
123            // Try to find sync bytes in buffer
124            if let Some(sync_pos) = self.find_sync() {
125                // Remove any bytes before sync
126                if sync_pos > 0 {
127                    self.stats.bytes_skipped += sync_pos as u64;
128                    self.buffer.drain(0..sync_pos);
129                }
130
131                // Try to parse block
132                match self.try_parse_block() {
133                    Ok(Some((block, consumed))) => {
134                        // Remove consumed bytes
135                        self.buffer.drain(0..consumed);
136                        self.stats.blocks_parsed += 1;
137                        return Ok(Some(block));
138                    }
139                    Ok(None) => {
140                        // Need more data to complete the block
141                        match self.fill_buffer()? {
142                            Fill::Filled => {}
143                            Fill::WouldBlock => return Err(SbfError::WouldBlock),
144                            Fill::Eof => {
145                                if !self.buffer.is_empty() {
146                                    // Genuinely truncated final block at real EOF
147                                    return Err(SbfError::IncompleteBlock {
148                                        needed: 8,
149                                        have: self.buffer.len(),
150                                    });
151                                }
152                                return Ok(None);
153                            }
154                        }
155                    }
156                    Err(SbfError::InvalidSync) => {
157                        // Skip one byte and try again
158                        self.buffer.remove(0);
159                        self.stats.bytes_skipped += 1;
160                    }
161                    Err(SbfError::CrcMismatch { .. }) => {
162                        // CRC error - skip sync and continue
163                        self.buffer.remove(0);
164                        self.stats.crc_errors += 1;
165                        self.stats.bytes_skipped += 1;
166                    }
167                    Err(_) => {
168                        // Other parse error - skip sync and continue
169                        self.buffer.remove(0);
170                        self.stats.parse_errors += 1;
171                        self.stats.bytes_skipped += 1;
172                        // Continue to next potential sync
173                    }
174                }
175            } else {
176                // No full sync in the buffer: discard the scanned bytes, but keep
177                // the last byte in case it is the first half of a sync split
178                // across reads (find_sync only scans 0..len-1). This bounds the
179                // buffer on garbage/no-sync streams and keeps find_sync O(n).
180                let len = self.buffer.len();
181                if len > 1 {
182                    self.stats.bytes_skipped += (len - 1) as u64;
183                    self.buffer.drain(0..len - 1);
184                }
185                match self.fill_buffer()? {
186                    Fill::Filled => {}
187                    Fill::WouldBlock => return Err(SbfError::WouldBlock),
188                    Fill::Eof => return Ok(None),
189                }
190            }
191
192            // Prevent buffer from growing too large
193            self.trim_buffer();
194        }
195    }
196
197    /// Find sync bytes in buffer
198    fn find_sync(&self) -> Option<usize> {
199        if self.buffer.len() < 2 {
200            return None;
201        }
202
203        (0..(self.buffer.len() - 1))
204            .find(|&i| self.buffer[i] == SBF_SYNC[0] && self.buffer[i + 1] == SBF_SYNC[1])
205    }
206
207    /// Try to parse a block from the current buffer position
208    fn try_parse_block(&mut self) -> SbfResult<Option<(SbfBlock, usize)>> {
209        if self.buffer.len() < 8 {
210            return Ok(None);
211        }
212
213        // Peek the declared block length (bytes 6..7) via O(1) indexing; this
214        // works regardless of where the ring boundary falls.
215        let peek_len = u16::from_le_bytes([self.buffer[6], self.buffer[7]]) as usize;
216        if peek_len >= MIN_BLOCK_LENGTH as usize && self.buffer.len() < peek_len {
217            return Ok(None); // wait for the rest of the block
218        }
219
220        // Fast path: if the whole block already lives in the contiguous front
221        // slice, parse it in place with no rotation. Only rotate the ring when
222        // the block straddles the boundary (or the length is invalid and needs
223        // the normal parse path to produce InvalidLength).
224        let front_len = self.buffer.as_slices().0.len();
225        let contiguous_front = peek_len >= MIN_BLOCK_LENGTH as usize && front_len >= peek_len;
226        if !contiguous_front {
227            self.buffer.make_contiguous();
228        }
229        let buffer = self.buffer.as_slices().0;
230
231        // Parse header
232        let header = SbfHeader::parse(&buffer[2..])?;
233        let total_len = header.length as usize;
234
235        if buffer.len() < total_len {
236            return Ok(None);
237        }
238
239        // Validate CRC if enabled. `validate_crc` computes the CRC and returns
240        // it in the error, so `CrcMismatch.actual` carries the real value.
241        if self.validate_crc {
242            header.validate_crc(&buffer[..total_len])?;
243        }
244
245        // Parse block
246        let (block, consumed) = SbfBlock::parse(&buffer[..total_len])?;
247
248        Ok(Some((block, consumed)))
249    }
250
251    /// Fill buffer from source.
252    ///
253    /// Distinguishes end of stream (`Fill::Eof`) from a non-blocking source with
254    /// no data available right now (`Fill::WouldBlock`).
255    fn fill_buffer(&mut self) -> SbfResult<Fill> {
256        let mut temp = [0u8; 4096];
257        match self.inner.read(&mut temp) {
258            Ok(0) => Ok(Fill::Eof),
259            Ok(n) => {
260                self.buffer.extend(&temp[..n]);
261                self.stats.bytes_read += n as u64;
262                Ok(Fill::Filled)
263            }
264            Err(e) if e.kind() == std::io::ErrorKind::WouldBlock => Ok(Fill::WouldBlock),
265            Err(e) if e.kind() == std::io::ErrorKind::Interrupted => self.fill_buffer(),
266            Err(e) => Err(SbfError::Io(e)),
267        }
268    }
269
270    /// Trim buffer if too large
271    fn trim_buffer(&mut self) {
272        if self.buffer.capacity() > MAX_BUFFER_SIZE && self.buffer.len() < MAX_BUFFER_SIZE / 2 {
273            self.buffer.shrink_to_fit();
274        }
275    }
276}
277
278/// Iterator implementation for SbfReader
279impl<R: Read> Iterator for SbfReader<R> {
280    type Item = SbfResult<SbfBlock>;
281
282    fn next(&mut self) -> Option<Self::Item> {
283        match self.read_block() {
284            Ok(Some(block)) => Some(Ok(block)),
285            Ok(None) => None,
286            Err(e) => Some(Err(e)),
287        }
288    }
289}
290
291/// Extension trait for creating SbfReader from Read types
292pub trait SbfReadExt: Read + Sized {
293    /// Create an SbfReader from this Read source
294    fn sbf_blocks(self) -> SbfReader<Self> {
295        SbfReader::new(self)
296    }
297}
298
299impl<R: Read> SbfReadExt for R {}
300
301#[cfg(test)]
302mod tests {
303    use super::*;
304    use std::io::Cursor;
305
306    #[test]
307    fn test_reader_empty() {
308        let data: &[u8] = &[];
309        let mut reader = SbfReader::new(Cursor::new(data));
310
311        assert!(reader.read_block().unwrap().is_none());
312    }
313
314    #[test]
315    fn test_reader_no_sync() {
316        let data = [0x00, 0x00, 0x00, 0x00];
317        let mut reader = SbfReader::new(Cursor::new(&data[..]));
318
319        assert!(reader.read_block().unwrap().is_none());
320    }
321
322    #[test]
323    fn test_reader_stats() {
324        let data: &[u8] = &[0x00, 0x00];
325        let mut reader = SbfReader::new(Cursor::new(data));
326
327        let _ = reader.read_block();
328
329        assert_eq!(reader.stats().bytes_read, 2);
330    }
331
332    #[test]
333    fn test_sbf_read_ext() {
334        let data: &[u8] = &[];
335        let reader = Cursor::new(data).sbf_blocks();
336
337        assert!(reader.validate_crc);
338    }
339
340    #[test]
341    fn crc_mismatch_reports_computed_actual() {
342        // 16-byte block; corrupt a body byte so the stored CRC no longer matches
343        // the recomputed one. Guards that CrcMismatch.actual is the real value.
344        let mut block = vec![0u8; 16];
345        block[0] = SBF_SYNC[0];
346        block[1] = SBF_SYNC[1];
347        block[4..6].copy_from_slice(&5922u16.to_le_bytes()); // EndOfMeas id, rev 0
348        block[6..8].copy_from_slice(&16u16.to_le_bytes()); // length
349        let stored = crate::crc::crc16_ccitt(&block[4..16]);
350        block[2..4].copy_from_slice(&stored.to_le_bytes());
351        block[12] ^= 0xFF; // corrupt body: computed CRC changes, stored stays
352        let computed = crate::crc::crc16_ccitt(&block[4..16]);
353        assert_ne!(stored, computed);
354
355        let mut reader = SbfReader::new(Cursor::new(block.clone()));
356        while reader.buffer.len() < block.len() {
357            match reader.fill_buffer().unwrap() {
358                Fill::Filled => {}
359                _ => break,
360            }
361        }
362
363        match reader.try_parse_block() {
364            Err(SbfError::CrcMismatch { expected, actual }) => {
365                assert_eq!(expected, stored);
366                assert_eq!(actual, computed);
367                assert_ne!(actual, 0);
368            }
369            other => panic!("expected CrcMismatch, got {other:?}"),
370        }
371    }
372}