test(adr-115): property-based fuzzing for Matter commissioning encoder (424 lib tests)
4 new proptest cases in matter::commissioning::tests. Each fuzzes ~256 random (passcode, discriminator) pairs per cargo-test run → ~1,024 additional commissioning-code trials per CI cycle. ## Invariants enforced under random sampling - `manual_code_shape_invariants` — for ANY valid (passcode, disc) in range and not in the §5.1.6.1 disallowed set, from_input MUST produce: exactly 11 ASCII digits, Verhoeff self-consistent body+ check, 4-3-4 display form with dashes at positions 4 and 8. - `disallowed_passcodes_always_rejected` — every passcode in the §5.1.6.1 list MUST be rejected regardless of discriminator. - `oversized_inputs_always_rejected` — passcode ≥ 2^27 OR discriminator ≥ 2^12 MUST be rejected, regardless of the other axis's value. - `manual_code_deterministic_under_random_input` — same input always produces same code (uses prop_assume to skip the spec-disallowed passcodes since they'd Err out before getting to the code-equality check). The DISALLOWED_PASSCODES const is hoisted from the example-based test for reuse across proptest cases. Lib test count: 420 → 424. Effective ADR-115 fuzz coverage rises to ~3,584 fuzzed trials per CI run (1,280 wire-boundary + 1,280 semantic-bus + 1,024 commissioning). Refs #776, PR #778. Co-Authored-By: claude-flow <ruv@ruv.net>
This commit is contained in:
parent
f40b811bee
commit
cb3ea9fbd2
|
|
@ -295,4 +295,89 @@ mod tests {
|
|||
let s = SetupCodeInput::dev(11111111, 3840);
|
||||
assert!(ManualPairingCode::from_input(&s).is_err());
|
||||
}
|
||||
|
||||
// ─── Property-based invariants for the commissioning encoder ─────
|
||||
|
||||
use proptest::prelude::*;
|
||||
|
||||
/// The §5.1.6.1 disallowed-passcodes set, hoisted to a const for
|
||||
/// reuse in property tests.
|
||||
const DISALLOWED_PASSCODES: &[u32] = &[
|
||||
0u32, 11111111, 22222222, 33333333, 44444444, 55555555,
|
||||
66666666, 77777777, 88888888, 99999999, 12345678, 87654321,
|
||||
];
|
||||
|
||||
proptest! {
|
||||
/// For ANY (passcode, discriminator) in the valid range that
|
||||
/// is not in the §5.1.6.1 disallowed set, from_input MUST
|
||||
/// produce a code with the same shape:
|
||||
/// - exactly 11 ASCII digits
|
||||
/// - Verhoeff-self-consistent
|
||||
/// - 4-3-4 display form is 13 chars with dashes at positions 4 and 8
|
||||
#[test]
|
||||
fn manual_code_shape_invariants(
|
||||
passcode in 1u32..((1 << 27) - 1),
|
||||
disc in 0u16..4095,
|
||||
) {
|
||||
// Reject the disallowed-by-spec set inside the proptest body
|
||||
// so the input strategy stays simple.
|
||||
prop_assume!(!DISALLOWED_PASSCODES.contains(&passcode));
|
||||
|
||||
let s = SetupCodeInput::dev(passcode, disc);
|
||||
let code = ManualPairingCode::from_input(&s);
|
||||
prop_assert!(code.is_ok(), "valid input rejected: {:?}", code.err());
|
||||
let code = code.unwrap();
|
||||
|
||||
// 11 ASCII digits.
|
||||
prop_assert_eq!(code.0.len(), 11);
|
||||
prop_assert!(code.0.chars().all(|c| c.is_ascii_digit()));
|
||||
|
||||
// Verhoeff self-consistency.
|
||||
let body = &code.0[0..10];
|
||||
let body_digit = code.0[10..11].parse::<u8>().unwrap();
|
||||
prop_assert_eq!(verhoeff_check_digit(body), body_digit);
|
||||
|
||||
// 4-3-4 form.
|
||||
let pretty = code.display_4_3_4();
|
||||
prop_assert_eq!(pretty.len(), 13);
|
||||
prop_assert_eq!(&pretty[4..5], "-");
|
||||
prop_assert_eq!(&pretty[8..9], "-");
|
||||
}
|
||||
|
||||
/// Every disallowed passcode in the §5.1.6.1 list MUST be
|
||||
/// rejected by validate(), regardless of discriminator.
|
||||
#[test]
|
||||
fn disallowed_passcodes_always_rejected(
|
||||
disc in 0u16..4095,
|
||||
bad_idx in 0usize..DISALLOWED_PASSCODES.len(),
|
||||
) {
|
||||
let bad = DISALLOWED_PASSCODES[bad_idx];
|
||||
let s = SetupCodeInput::dev(bad, disc);
|
||||
prop_assert!(s.validate().is_err(), "passcode {} must be rejected", bad);
|
||||
}
|
||||
|
||||
/// Oversized inputs always rejected, regardless of the
|
||||
/// allowed dim.
|
||||
#[test]
|
||||
fn oversized_inputs_always_rejected(
|
||||
big_pin in (1u32 << 27)..u32::MAX,
|
||||
big_disc in 4096u16..,
|
||||
) {
|
||||
prop_assert!(SetupCodeInput::dev(big_pin, 100).validate().is_err());
|
||||
prop_assert!(SetupCodeInput::dev(20202021, big_disc).validate().is_err());
|
||||
}
|
||||
|
||||
/// Same input → same code (determinism property under random sampling).
|
||||
#[test]
|
||||
fn manual_code_deterministic_under_random_input(
|
||||
passcode in 1u32..((1 << 27) - 1),
|
||||
disc in 0u16..4095,
|
||||
) {
|
||||
prop_assume!(!DISALLOWED_PASSCODES.contains(&passcode));
|
||||
let s = SetupCodeInput::dev(passcode, disc);
|
||||
let a = ManualPairingCode::from_input(&s).unwrap();
|
||||
let b = ManualPairingCode::from_input(&s).unwrap();
|
||||
prop_assert_eq!(a, b);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
|
|||
Loading…
Reference in New Issue