identifier_properties.rs (2886B)
1 use radroots_event::id::{CandidateId, EventId, EventPointer, EventSignature, MutationId, TradeId}; 2 3 macro_rules! assert_hex_identifier_properties { 4 ($type:ty, $byte_length:expr, $seed:expr) => {{ 5 let mut state = $seed; 6 for case in 0..256 { 7 let mut bytes = [0_u8; $byte_length]; 8 for byte in &mut bytes { 9 state = xorshift64(state); 10 *byte = state as u8; 11 } 12 13 let expected = encode_hex(&bytes); 14 let mut mixed_case = expected.clone().into_bytes(); 15 for (index, byte) in mixed_case.iter_mut().enumerate() { 16 if index % 3 == case % 3 && matches!(*byte, b'a'..=b'f') { 17 *byte = byte.to_ascii_uppercase(); 18 } 19 } 20 let mixed_case = String::from_utf8(mixed_case).expect("ASCII hex"); 21 22 let parsed = <$type>::parse(&mixed_case).expect("generated valid identifier"); 23 assert_eq!(parsed.as_bytes(), &bytes); 24 assert_eq!(parsed.to_hex(), expected); 25 assert_eq!(<$type>::parse(parsed.to_hex()).expect("round trip"), parsed); 26 } 27 28 for length in 0..=($byte_length * 2 + 2) { 29 if length == $byte_length * 2 { 30 continue; 31 } 32 assert!( 33 <$type>::parse("a".repeat(length)).is_err(), 34 "length {length} must be rejected" 35 ); 36 } 37 38 for invalid in [b'g', b'/', b' ', b'\n', 0, 0x7f, 0xff] { 39 let mut candidate = vec![b'a'; $byte_length * 2]; 40 let invalid_index = usize::from(invalid) % candidate.len(); 41 candidate[invalid_index] = invalid; 42 assert!( 43 <$type>::parse(String::from_utf8_lossy(&candidate)).is_err(), 44 "byte {invalid:#04x} must be rejected" 45 ); 46 } 47 }}; 48 } 49 50 #[test] 51 fn canonical_hex_identifier_parsers_hold_under_generated_inputs() { 52 assert_hex_identifier_properties!(EventId, 32, 0x9e37_79b9_7f4a_7c15); 53 assert_hex_identifier_properties!(EventSignature, 64, 0xd1b5_4a32_d192_ed03); 54 assert_hex_identifier_properties!(TradeId, 16, 0x94d0_49bb_1331_11eb); 55 assert_hex_identifier_properties!(CandidateId, 32, 0x8538_eb3d_64dc_8717); 56 assert_hex_identifier_properties!(MutationId, 32, 0xda94_2042_e4dd_58b5); 57 assert_hex_identifier_properties!(EventPointer, 32, 0xa409_3822_299f_31d0); 58 } 59 60 fn xorshift64(mut value: u64) -> u64 { 61 value ^= value << 13; 62 value ^= value >> 7; 63 value ^ (value << 17) 64 } 65 66 fn encode_hex(bytes: &[u8]) -> String { 67 const LOWER_HEX: &[u8; 16] = b"0123456789abcdef"; 68 let mut encoded = String::with_capacity(bytes.len() * 2); 69 for byte in bytes { 70 encoded.push(LOWER_HEX[usize::from(byte >> 4)] as char); 71 encoded.push(LOWER_HEX[usize::from(byte & 0x0f)] as char); 72 } 73 encoded 74 }