Files
benilla-octo/crates/benilla-bytes/src/lib.rs
T
Sam SungandClaude Opus 5.5 bf8a15de02 Comments in the small crates: one line of fact each, no history, no record numbers (#93)
Part of #41: the comments of the thirteen small crates (`benilla-adt`, `-blp`, `-buildstamp`, `-bytes`, `-dbc`, `-m2`, `-mpq`, `-srp`, `-visual`, `-wdt`, `-wmo`, `-worldview` and `benilla`) rewritten to the rule in `docs/METHOD.md`: what the code does and the reference fact behind it, in a line. 2,176 comment lines become 1,029.

- The citations that were only a private record number go first, mechanically, each edit shown to be a pure removal.
- The rewrite corrects the comments that stated wrong facts: the M2 header's selection-ring claim, the texture-transform and MCNK impassable-flag notes, a DBC reader note that said short records zero-pad (they do not, #17), and about twenty more found on the way. The SRP handshake's redraw and redial are marked as a deviation, with the measured reason.
- Verified: every file strips to identical code, and `cargo fmt --check` is clean. A second reader compared every file old against new and restored the facts the rewrite had dropped.

String literals are untouched; they are #92.

🤖 Generated with [Claude Code](https://claude.com/claude-code)

Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com>
2026-09-24 15:51:18 +07:00

137 lines
4.9 KiB
Rust

//! Bounds-checked byte readers and IFF chunk iteration for the format parsers.
/// Little-endian reads at an offset, `None` past the end. There is no panicking variant on
/// purpose: a parser maps `None` to its own truncation error.
pub trait ByteExt {
fn u8_at(&self, o: usize) -> Option<u8>;
fn u16_at(&self, o: usize) -> Option<u16>;
fn u32_at(&self, o: usize) -> Option<u32>;
fn i32_at(&self, o: usize) -> Option<i32>;
fn f32_at(&self, o: usize) -> Option<f32>;
/// A length-checked sub-slice: `n` bytes starting at `o`.
fn bytes_at(&self, o: usize, n: usize) -> Option<&[u8]>;
}
impl ByteExt for [u8] {
#[inline]
fn u8_at(&self, o: usize) -> Option<u8> {
self.get(o).copied()
}
#[inline]
fn u16_at(&self, o: usize) -> Option<u16> {
Some(u16::from_le_bytes(
self.get(o..o.checked_add(2)?)?.try_into().ok()?,
))
}
#[inline]
fn u32_at(&self, o: usize) -> Option<u32> {
Some(u32::from_le_bytes(
self.get(o..o.checked_add(4)?)?.try_into().ok()?,
))
}
#[inline]
fn i32_at(&self, o: usize) -> Option<i32> {
self.u32_at(o).map(|v| v as i32)
}
#[inline]
fn f32_at(&self, o: usize) -> Option<f32> {
self.u32_at(o).map(f32::from_bits)
}
#[inline]
fn bytes_at(&self, o: usize, n: usize) -> Option<&[u8]> {
self.get(o..o.checked_add(n)?)
}
}
/// Iterate IFF chunks as `(magic, payload)`, the magic as stored: reversed on disk, so MOHD is
/// `b"DHOM"`. A truncated header ends iteration and an overrunning payload is clamped to the
/// buffer end, a tolerance real 1.12 art needs.
pub fn chunks(b: &[u8]) -> impl Iterator<Item = ([u8; 4], &[u8])> {
let mut pos = 0usize;
std::iter::from_fn(move || {
if pos.checked_add(8)? > b.len() {
return None;
}
let magic = [b[pos], b[pos + 1], b[pos + 2], b[pos + 3]];
let size = b.u32_at(pos + 4)? as usize;
let start = pos + 8;
// Saturating, so a 32-bit target cannot wrap either.
let end = start.saturating_add(size).min(b.len());
pos = end;
Some((magic, &b[start..end]))
})
}
/// Cap a count-driven reservation at what `avail` bytes could hold, so a corrupt count reserves at
/// most the input's size; the caller still iterates its own count and fails at the checked read.
/// An `elem_size` of 0 counts as 1.
pub fn capped(count: usize, elem_size: usize, avail: usize) -> usize {
count.min(avail / elem_size.max(1))
}
#[cfg(test)]
mod tests {
use super::*;
#[test]
fn reads_in_bounds() {
let b: &[u8] = &[0x01, 0x02, 0x03, 0x04, 0x05];
assert_eq!(b.u8_at(4), Some(0x05));
assert_eq!(b.u16_at(0), Some(0x0201));
assert_eq!(b.u32_at(1), Some(0x05040302));
assert_eq!(b.i32_at(0), Some(0x04030201));
assert_eq!(b.f32_at(0), Some(f32::from_bits(0x04030201)));
assert_eq!(b.bytes_at(2, 3), Some(&b[2..5]));
}
#[test]
fn reads_out_of_bounds_are_none() {
let b: &[u8] = &[0x01, 0x02, 0x03];
assert_eq!(b.u8_at(3), None);
assert_eq!(b.u16_at(2), None);
assert_eq!(b.u32_at(0), None);
assert_eq!(b.bytes_at(1, 3), None);
// Offsets near usize::MAX must not wrap into a "valid" range.
assert_eq!(b.u32_at(usize::MAX - 1), None);
assert_eq!(b.bytes_at(usize::MAX, 8), None);
}
#[test]
fn chunk_iteration_walks_well_formed_input() {
// Two chunks: "ABCD" (2 bytes) then "EFGH" (0 bytes).
let mut b = Vec::new();
b.extend_from_slice(b"ABCD");
b.extend_from_slice(&2u32.to_le_bytes());
b.extend_from_slice(&[0xAA, 0xBB]);
b.extend_from_slice(b"EFGH");
b.extend_from_slice(&0u32.to_le_bytes());
let got: Vec<_> = chunks(&b).collect();
assert_eq!(got.len(), 2);
assert_eq!((got[0].0, got[0].1), (*b"ABCD", &[0xAA, 0xBB][..]));
assert_eq!((got[1].0, got[1].1), (*b"EFGH", &[][..]));
}
#[test]
fn chunk_iteration_is_lenient() {
// Over-declared size clamps to the buffer end.
let mut b = Vec::new();
b.extend_from_slice(b"ABCD");
b.extend_from_slice(&u32::MAX.to_le_bytes());
b.extend_from_slice(&[0x01]);
let got: Vec<_> = chunks(&b).collect();
assert_eq!(got.len(), 1);
assert_eq!(got[0].1, &[0x01][..]);
// A truncated header (< 8 bytes remaining) stops iteration cleanly.
assert_eq!(chunks(&b[..7]).count(), 0);
assert_eq!(chunks(&[]).count(), 0);
}
#[test]
fn capped_bounds_reservations() {
assert_eq!(capped(10, 4, 1000), 10); // plausible count passes through
assert_eq!(capped(u32::MAX as usize, 48, 96), 2); // corrupt count → what fits
assert_eq!(capped(5, 0, 16), 5); // elem_size 0 treated as 1, no div-by-zero
assert_eq!(capped(5, 4, 0), 0); // empty input reserves nothing
}
}