Files
libfreemkv/src/labels/text.rs
T
Matthew Jackson 71686f1407 Lint the test code, and fix the 74 findings it had been hiding
Every other repo's CI now runs clippy with --all-targets. libfreemkv,
the crate the other seven build against and the one held up as the
reference workflow, was the last one still linting the library only — so
its ~3,000 tests, by far the largest body of test code in the project,
had never been linted at all. Turning the flag on surfaced 74 findings.

Most were mechanical and applied with clippy --fix. The rest, by hand:

- Four discarded Results in decrypt.rs. css::descramble_region returns a
  Result and four CSS tests threw it away, so a descramble that FAILED
  would have surfaced as a confusing buffer-comparison mismatch instead
  of the actual error. They expect() now.
- A dead `kp` field on the PlantedWalk fixture. The test deliberately
  asserts Kp as the explicit AES-G3(dk, 1) relation from [C] §3.2.4
  rather than against a stored value — its doc comment says so — which
  makes the field not just unused but a trap: the obvious "fix" of
  asserting against it would quietly weaken the test to comparing the
  fixture with itself. Removed.
- Two hand-rolled ICB counters in the HD-DVD fixtures, a needless mut,
  three vec!s that only ever needed arrays, a filter_map whose every arm
  was Some, and a Vec::new()+push chain.
- Doc list indentation in mkv.rs and mp4/read.rs, which was mis-rendering
  in the generated docs.
- A five-[u8; 16]-tuple return type named FourLevelParts.

Three lints are allowed at the specific sites, with reasons, because
they are wrong for this domain: the underscores in the bitstream-header
literals mark BITFIELD boundaries, not digit groups, so regrouping them
uniformly would satisfy the lint by destroying the only thing they
encode; and in three table-validation loops the loop variable is the
domain value under test (a DTS SFREQ code, an AMODE value, a palette
entry number), which is what the assertion messages name.
2026-07-31 15:08:37 -07:00

208 lines
7.6 KiB
Rust

//! Text-extraction helpers used by parsers that scan binary blobs for
//! embedded label strings.
//!
//! Promoted from a byte-scanning helper (`bluray_project.bin`,
//! min_len=4). Single implementation, threshold passed in.
//!
//! `dbp` no longer uses a byte-scanning helper — it iterates
//! `class_reader::CpInfo::Utf8` constant-pool entries directly. Callers
//! that have a more structured parse path (e.g. `class_reader` for
//! `.class`) should prefer that; this helper is for genuinely
//! unstructured input.
/// Walk `data`, emit every maximal run of printable-ASCII bytes
/// (`0x20..=0x7E`) whose length is at least `min_len`.
///
/// Non-printable bytes (including `\t`, `\n`, NUL) terminate the
/// current run. Output strings are guaranteed valid UTF-8 (they're
/// pure 7-bit ASCII). Strings shorter than `min_len` are dropped.
pub fn extract_ascii_strings(data: &[u8], min_len: usize) -> Vec<String> {
let mut out = Vec::new();
let mut current = String::new();
for &b in data {
if (0x20..=0x7E).contains(&b) {
current.push(b as char);
} else if !current.is_empty() && current.len() >= min_len {
out.push(std::mem::take(&mut current));
} else {
current.clear();
}
}
if !current.is_empty() && current.len() >= min_len {
out.push(current);
}
out
}
#[cfg(test)]
mod tests {
use super::*;
#[test]
fn extracts_simple_runs() {
let got = extract_ascii_strings(b"hello\0world\0", 3);
assert_eq!(got, vec!["hello", "world"]);
}
#[test]
fn applies_minimum_length() {
let got = extract_ascii_strings(b"hi\0ok\0longer\0", 5);
assert_eq!(got, vec!["longer"]);
}
#[test]
fn treats_tab_and_newline_as_separators() {
// \t (0x09) and \n (0x0A) are below 0x20, so they break runs.
let got = extract_ascii_strings(b"alpha\tbeta\ngamma", 3);
assert_eq!(got, vec!["alpha", "beta", "gamma"]);
}
#[test]
fn handles_trailing_run_without_terminator() {
// Run at the very end of the buffer should still be emitted.
let got = extract_ascii_strings(b"prefix\0tail", 3);
assert_eq!(got, vec!["prefix", "tail"]);
}
#[test]
fn rejects_high_bit_bytes() {
// 0x80+ is non-printable per this helper's definition.
let mut buf = b"good".to_vec();
buf.push(0xC3);
buf.push(0xA9);
buf.extend_from_slice(b"more");
let got = extract_ascii_strings(&buf, 3);
assert_eq!(got, vec!["good", "more"]);
}
#[test]
fn empty_input_returns_empty() {
let got = extract_ascii_strings(&[], 1);
assert!(got.is_empty());
}
#[test]
fn min_len_zero_emits_singletons() {
// Pathological but well-defined.
let got = extract_ascii_strings(b"a\0b", 0);
assert_eq!(got, vec!["a", "b"]);
}
#[test]
fn min_len_zero_skips_empty_runs_on_consecutive_separators() {
// Consecutive separators must NOT emit empty strings even at
// min_len=0 — an empty string is not a "run of printable bytes".
let got = extract_ascii_strings(b"\0\0abc", 0);
assert_eq!(got, vec!["abc"]);
let got = extract_ascii_strings(b"ab\0\0\0cd\0\0", 0);
assert_eq!(got, vec!["ab", "cd"]);
}
// ── Additional hardening tests ─────────────────────────────────────────
/// Spec: printable ASCII is 0x20..=0x7E inclusive. 0x1F (US) and 0x7F (DEL)
/// are NOT printable and must terminate a run.
/// Mutation: change the range to 0x20..=0x7F → DEL included.
#[test]
fn del_character_0x7f_terminates_run() {
// 0x7F is DEL — not printable per our definition.
let mut buf = b"hello".to_vec();
buf.push(0x7F);
buf.extend_from_slice(b"world");
let got = extract_ascii_strings(&buf, 3);
assert_eq!(got, vec!["hello", "world"]);
}
/// Spec: 0x1F (unit separator) is below 0x20 — must terminate a run.
/// Mutation: change range to start at 0x00 → control chars included.
#[test]
fn unit_separator_0x1f_terminates_run() {
let mut buf = b"abc".to_vec();
buf.push(0x1F);
buf.extend_from_slice(b"defg");
let got = extract_ascii_strings(&buf, 3);
assert_eq!(got, vec!["abc", "defg"]);
}
/// Spec: 0x20 (space) is the lower bound — MUST be included in runs.
/// Mutation: change range to start at 0x21 → spaces excluded, "hello world" splits.
#[test]
fn space_0x20_included_in_run() {
let got = extract_ascii_strings(b"hello world\0", 5);
assert_eq!(got, vec!["hello world"]);
}
/// Spec: 0x7E (tilde) is the upper bound — MUST be included.
/// Mutation: change range to 0x20..0x7E (exclusive) → tilde excluded.
#[test]
fn tilde_0x7e_included_in_run() {
let got = extract_ascii_strings(b"hello~world\0", 3);
assert_eq!(got, vec!["hello~world"]);
}
/// Spec: min_len=4 (Pixelogic's minimum). Token "abc" (length 3) must be dropped.
/// Mutation: use `>` instead of `>=` for the length check → "abcd" (len 4) dropped.
#[test]
fn min_len_4_boundary() {
let got = extract_ascii_strings(b"abc\0abcd\0abcde\0", 4);
assert_eq!(got, vec!["abcd", "abcde"]);
}
/// Spec: output strings are guaranteed valid UTF-8 (pure 7-bit ASCII).
/// This test verifies the invariant: no string contains non-ASCII bytes.
/// Mutation: skip the 0x80..=0xFF filter → high bytes appear in output.
#[test]
fn output_strings_are_pure_ascii() {
let mut buf = Vec::new();
for b in 0x20u8..=0x7Eu8 {
buf.push(b);
}
buf.push(0u8);
let got = extract_ascii_strings(&buf, 1);
assert_eq!(got.len(), 1);
for s in &got {
assert!(s.is_ascii(), "output must be pure ASCII: {:?}", s);
}
}
/// Large all-printable buffer: verify the tail run is emitted.
/// Mutation: skip the final `if !current.is_empty()` emit → trailing run lost.
#[test]
fn large_buffer_trailing_run_emitted() {
let buf: Vec<u8> = (0..1000u32).map(|i| 0x41u8 + (i % 26) as u8).collect();
let got = extract_ascii_strings(&buf, 1);
// All printable, so one big run at the end.
assert!(!got.is_empty());
let total: usize = got.iter().map(|s| s.len()).sum();
assert_eq!(total, 1000);
}
/// Consecutive non-printable bytes must not produce empty strings.
/// Mutation: remove the `!current.is_empty()` guard on the emit → empty strings pushed.
#[test]
fn no_empty_strings_in_output() {
let got = extract_ascii_strings(b"\x00\x00\x00hello\x00\x00\x00world\x00\x00", 3);
for s in &got {
assert!(!s.is_empty(), "output must contain no empty strings");
}
assert_eq!(got, vec!["hello", "world"]);
}
/// The Pixelogic token grammar starts at length 4 (`{lang3}_{…}`).
/// Verify that a token of exactly 4 chars `eng_` is emitted when min_len=4.
/// Mutation: use `>` instead of `>=` → len-4 token dropped.
#[test]
fn exact_min_len_token_emitted() {
let got = extract_ascii_strings(b"\x00eng_\x00", 4);
assert_eq!(got, vec!["eng_"]);
}
/// Single printable byte with min_len=1 must be emitted.
/// Mutation: use `> 1` → single-char tokens dropped.
#[test]
fn single_byte_at_min_len_1() {
let got = extract_ascii_strings(b"A\x00B\x00C", 1);
assert_eq!(got, vec!["A", "B", "C"]);
}
}