copybook_rdw_predicates/
lib.rs1#![cfg_attr(not(test), deny(clippy::unwrap_used, clippy::expect_used))]
2pub const RDW_HEADER_LEN: usize = 4;
10
11#[inline]
13#[must_use]
14pub const fn rdw_is_suspect_ascii_corruption(rdw_header: [u8; RDW_HEADER_LEN]) -> bool {
15 let b0 = rdw_header[0];
16 let b1 = rdw_header[1];
17
18 is_ascii_digit(b0) && is_ascii_digit(b1)
19}
20
21#[inline]
23#[must_use]
24pub fn rdw_is_suspect_ascii_corruption_slice(rdw_bytes: &[u8]) -> bool {
25 rdw_bytes.len() >= RDW_HEADER_LEN
26 && rdw_is_suspect_ascii_corruption([rdw_bytes[0], rdw_bytes[1], rdw_bytes[2], rdw_bytes[3]])
27}
28
29#[inline]
30#[must_use]
31const fn is_ascii_digit(byte: u8) -> bool {
32 byte >= b'0' && byte <= b'9'
33}
34
35#[cfg(test)]
36#[allow(clippy::expect_used, clippy::unwrap_used)]
37mod tests {
38 use super::*;
39
40 #[test]
41 fn ascii_digit_bytes_are_suspect() {
42 assert!(rdw_is_suspect_ascii_corruption([b'1', b'2', 0x00, 0x00]));
43 assert!(rdw_is_suspect_ascii_corruption([b'0', b'9', 0xFF, 0xEE]));
44 }
45
46 #[test]
47 fn non_ascii_digit_length_bytes_are_not_suspect() {
48 assert!(!rdw_is_suspect_ascii_corruption([b'1', b'G', 0x00, 0x00]));
49 assert!(!rdw_is_suspect_ascii_corruption([0x00, 0x01, 0x00, 0x00]));
50 assert!(!rdw_is_suspect_ascii_corruption([0x31, 0x00, 0x30, 0x30]));
51 }
52
53 #[test]
54 fn short_headers_are_not_suspect() {
55 assert!(!rdw_is_suspect_ascii_corruption_slice(b"12"));
56 }
57
58 #[test]
59 fn slice_uses_same_rule_as_array() {
60 let header = [b'7', b'8', 0x10, 0x20];
61 assert!(rdw_is_suspect_ascii_corruption_slice(&header));
62 }
63
64 #[test]
67 fn rdw_header_len_is_four() {
68 assert_eq!(RDW_HEADER_LEN, 4);
69 }
70
71 #[test]
72 fn all_ascii_digit_pairs_suspect() {
73 for b0 in b'0'..=b'9' {
74 for b1 in b'0'..=b'9' {
75 assert!(
76 rdw_is_suspect_ascii_corruption([b0, b1, 0x00, 0x00]),
77 "expected suspect for ({b0}, {b1})"
78 );
79 }
80 }
81 }
82
83 #[test]
84 fn first_byte_non_digit_not_suspect() {
85 assert!(!rdw_is_suspect_ascii_corruption([0x2F, b'5', 0x00, 0x00]));
87 assert!(!rdw_is_suspect_ascii_corruption([0x3A, b'5', 0x00, 0x00]));
89 }
90
91 #[test]
92 fn second_byte_non_digit_not_suspect() {
93 assert!(!rdw_is_suspect_ascii_corruption([b'5', 0x2F, 0x00, 0x00]));
94 assert!(!rdw_is_suspect_ascii_corruption([b'5', 0x3A, 0x00, 0x00]));
95 }
96
97 #[test]
98 fn reserved_bytes_do_not_affect_detection() {
99 assert!(rdw_is_suspect_ascii_corruption([b'0', b'0', 0xFF, 0xFF]));
101 assert!(rdw_is_suspect_ascii_corruption([b'9', b'9', b'A', b'B']));
102 }
103
104 #[test]
105 fn all_zeros_not_suspect() {
106 assert!(!rdw_is_suspect_ascii_corruption([0x00, 0x00, 0x00, 0x00]));
107 }
108
109 #[test]
110 fn all_0xff_not_suspect() {
111 assert!(!rdw_is_suspect_ascii_corruption([0xFF, 0xFF, 0xFF, 0xFF]));
112 }
113
114 #[test]
115 fn slice_empty_not_suspect() {
116 assert!(!rdw_is_suspect_ascii_corruption_slice(&[]));
117 }
118
119 #[test]
120 fn slice_exactly_4_bytes() {
121 assert!(rdw_is_suspect_ascii_corruption_slice(&[
122 b'3', b'4', 0x00, 0x00
123 ]));
124 assert!(!rdw_is_suspect_ascii_corruption_slice(&[
125 0x00, 0x50, 0x00, 0x00
126 ]));
127 }
128
129 #[test]
130 fn slice_longer_than_4_uses_first_4() {
131 let data = [b'1', b'2', 0x00, 0x00, 0xFF, 0xFF, 0xFF];
132 assert!(rdw_is_suspect_ascii_corruption_slice(&data));
133 }
134
135 #[test]
136 fn slice_3_bytes_not_suspect() {
137 assert!(!rdw_is_suspect_ascii_corruption_slice(&[b'1', b'2', 0x00]));
138 }
139}