lib

Core libraries for Radroots
git clone https://radroots.dev/git/lib.git
Log | Files | Refs | README

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 }