parser_robustness.rs (2332B)
1 use std::panic::{AssertUnwindSafe, catch_unwind}; 2 3 use radroots_event::envelope::{EventTag, EventTags}; 4 use radroots_event::id::{DTag, EventId, EventSignature, Nip01Coordinate}; 5 use radroots_event::tag::relay_hint::NostrRelayHint; 6 use radroots_event::wire::v1::Nip01EventWire; 7 8 const VALID_WIRE_VECTOR: &str = include_str!("fixtures/nip01_wire.v1.json"); 9 10 #[test] 11 fn wire_and_tag_parsers_are_total_over_deterministic_mutation_corpus() { 12 for case in 0_u64..256 { 13 let bytes = corpus_bytes(case, ((case * 37) % 2049) as usize); 14 assert_parser_totality(bytes.as_slice(), case); 15 } 16 17 let valid_bytes = VALID_WIRE_VECTOR.as_bytes(); 18 for case in 0_u64..256 { 19 let mut mutated = valid_bytes.to_vec(); 20 let index = ((case * 131) as usize) % mutated.len(); 21 mutated[index] ^= (case as u8).wrapping_mul(29).wrapping_add(1); 22 assert_parser_totality(mutated.as_slice(), case + 256); 23 } 24 } 25 26 fn assert_parser_totality(bytes: &[u8], case: u64) { 27 let result = catch_unwind(AssertUnwindSafe(|| { 28 if let Ok(text) = core::str::from_utf8(bytes) { 29 let _ = Nip01EventWire::parse_json(text); 30 let _ = DTag::parse(text); 31 let _ = EventId::parse(text); 32 let _ = EventSignature::parse(text); 33 let _ = Nip01Coordinate::parse(text); 34 let _ = NostrRelayHint::parse(text); 35 } 36 37 let tags = tags_from_bytes(bytes); 38 for (index, tag) in tags.iter().cloned().enumerate() { 39 let _ = EventTag::new(index, tag); 40 } 41 let _ = EventTags::new(tags); 42 })); 43 44 assert!(result.is_ok(), "parser mutation case {case} panicked"); 45 } 46 47 fn corpus_bytes(seed: u64, length: usize) -> Vec<u8> { 48 let mut state = seed ^ 0x9e37_79b9_7f4a_7c15; 49 (0..length) 50 .map(|_| { 51 state = state 52 .wrapping_mul(6_364_136_223_846_793_005) 53 .wrapping_add(1_442_695_040_888_963_407); 54 (state >> 32) as u8 55 }) 56 .collect() 57 } 58 59 fn tags_from_bytes(bytes: &[u8]) -> Vec<Vec<String>> { 60 bytes 61 .chunks(64) 62 .take(16) 63 .map(|tag| { 64 tag.chunks(8) 65 .take(8) 66 .map(|element| String::from_utf8_lossy(element).into_owned()) 67 .collect() 68 }) 69 .collect() 70 }