// Truncation suite: every truncated or single-byte-mutated input must come
// back as `Ok` or a structured `Err` — the parser must never crash. This is
// the defensive-parsing guarantee for untrusted input.
///|
/// Bytes of an input as integers, for deterministic prefix/mutation work.
fn ints_of_bytes(bytes : Bytes) -> Array[Int] {
let out : Array[Int] = []
for b in bytes {
out.push(b.to_int())
}
out
}
///|
/// Parse must not crash; an Err must remain a well-formed structured error.
fn check_safe(input : Bytes) -> Unit raise {
match parse_security_txt_bytes(input, Limits::default()) {
Ok(_) => ()
Err(err) => {
let rendered = err.to_string()
assert_true(rendered.length() > 0)
}
}
}
///|
test "truncation: every prefix of a valid document yields Ok or structured Err" {
let full = ints_of_bytes(@utf8.encode(valid_document()))
let mut cases = 0
let mut len = 0
while len <= full.length() {
let prefix : Array[Int] = []
let mut k = 0
while k < len {
prefix.push(full[k])
k += 1
}
check_safe(bytes_of(prefix))
cases += 1
len += 1
}
assert_int_eq(cases, full.length() + 1)
}
///|
test "truncation: byte-level fuzzing never crashes the parser" {
let full = ints_of_bytes(@utf8.encode(valid_document()))
// Replace a sampled byte with an interesting value (NUL, a UTF-8
// continuation byte, a lead byte, 0xFF, or an ASCII byte) and parse.
let mutations = [0x00, 0x80, 0xC0, 0xFF]
let mut cases = 0
let mut pos = 0
while pos < full.length() {
for m in mutations {
let copy : Array[Int] = []
let mut k = 0
while k < full.length() {
if k == pos {
copy.push(m)
} else {
copy.push(full[k])
}
k += 1
}
check_safe(bytes_of(copy))
cases += 1
}
pos += 8
}
let positions = (full.length() + 7) / 8
assert_int_eq(cases, positions * 4)
// For each cut position, append one of the five interesting trailing
// bytes directly after the cut. This targets the strict decoder's
// boundary handling.
let tails = [0x00, 0x80, 0xC2, 0xF5, 0x41]
let mut grid_cases = 0
let mut cut = 0
while cut < full.length() {
for tail in tails {
let candidate : Array[Int] = []
let mut k = 0
while k <= cut {
candidate.push(full[k])
k += 1
}
candidate.push(tail)
check_safe(bytes_of(candidate))
grid_cases += 1
}
cut += 8
}
let cuts = (full.length() + 7) / 8
assert_int_eq(grid_cases, cuts * 5)
}
///|
test "truncation: sampled prefixes of a signed envelope are safe" {
let full = ints_of_bytes(@utf8.encode(signed_fixture()))
let mut cases = 0
let mut len = 0
while len < full.length() {
let prefix : Array[Int] = []
let mut k = 0
while k < len {
prefix.push(full[k])
k += 1
}
check_safe(bytes_of(prefix))
cases += 1
len += 2
}
// The complete envelope itself.
check_safe(bytes_of(full))
cases += 1
assert_int_eq(cases, (full.length() + 1) / 2 + 1)
}