Skip to main content

ps_ecc/methods/
validate.rs

1use crate::{long, ReedSolomon};
2
3/// Validates that a received codeword is pristine.
4///
5/// Returns `true` only if the codeword is entirely uncorrupted; for codewords
6/// longer than 255 bytes, this includes carrying no bytes beyond the full
7/// length recorded in the header, and the `parity` argument is ignored,
8/// since the header records the parity. A `true` result implies that [`decode`](crate::decode)
9/// succeeds. The converse does not hold: [`decode`](crate::decode) repairs correctable
10/// corruption and discards trailing bytes, so it accepts input that this
11/// function rejects.
12#[must_use]
13pub fn validate(received: &[u8], parity: u8) -> bool {
14    if let Ok(length) = u8::try_from(received.len()) {
15        if parity > length >> 1 {
16            return false;
17        }
18
19        let Ok(rs) = ReedSolomon::new(parity) else {
20            return false;
21        };
22
23        rs.validate(received).is_none()
24    } else {
25        matches!(long::fast_validate(received), Ok((_, true)))
26    }
27}
28
29#[cfg(test)]
30mod tests {
31    use crate::{
32        long, validate, LongEccDecodeError, LongEccEncodeError, RSEncodeError, ReedSolomon,
33    };
34
35    use ps_buffer::ToBuffer;
36
37    #[derive(thiserror::Error, Debug)]
38    enum TestError {
39        #[error(transparent)]
40        LongEccEncode(#[from] LongEccEncodeError),
41        #[error(transparent)]
42        LongEccDecode(#[from] LongEccDecodeError),
43        #[error(transparent)]
44        Buffer(#[from] ps_buffer::BufferError),
45        #[error(transparent)]
46        RSConstructorError(#[from] crate::RSConstructorError),
47        #[error(transparent)]
48        RSEncodeError(#[from] RSEncodeError),
49    }
50
51    #[test]
52    fn test_validate_short_data_valid_no_errors() -> Result<(), TestError> {
53        let data = b"test";
54        let parity = 2;
55        let rs = ReedSolomon::new(parity)?;
56        let codeword = rs.encode(data)?;
57
58        assert!(validate(&codeword, parity));
59
60        Ok(())
61    }
62
63    #[test]
64    fn test_validate_short_data_invalid_with_errors() -> Result<(), TestError> {
65        let data = b"test";
66        let parity = 2;
67        let rs = ReedSolomon::new(parity)?;
68        let mut codeword = rs.encode(data)?;
69
70        // Introduce errors
71        codeword[0] ^= 1;
72        codeword[1] ^= 1;
73        codeword[2] ^= 1;
74
75        assert!(!validate(&codeword, parity));
76
77        Ok(())
78    }
79
80    #[test]
81    fn test_validate_short_data_parity_too_large() {
82        let data = b"test"; // 4 bytes
83        let parity = 3; // 3 > 4/2 (2) - parity is too large
84
85        // Should return false when parity is too large
86        assert!(!validate(data, parity));
87    }
88
89    #[test]
90    fn test_validate_short_data_rs_constructor_error() {
91        let data = b"test";
92        let parity = 255; // Invalid parity value that should cause RS constructor to fail
93
94        // Should return false when RS constructor fails
95        assert!(!validate(data, parity));
96    }
97
98    #[test]
99    fn test_validate_short_data_correctable_errors() -> Result<(), TestError> {
100        let data = b"test";
101        let parity = 2;
102        let rs = ReedSolomon::new(parity)?;
103        let mut codeword = rs.encode(data)?;
104
105        // Introduce correctable errors (1 error with parity=2)
106        codeword[0] ^= 1;
107
108        // Validation should return false because there are errors (even if correctable)
109        assert!(!validate(&codeword, parity));
110
111        Ok(())
112    }
113
114    #[test]
115    fn test_validate_long_data_valid_no_errors() -> Result<(), TestError> {
116        let message = b"This is a longer message that will use long ECC".repeat(7);
117        let parity = 2;
118
119        let encoded = long::encode(&message, parity, long::OverlapFactor::Simple)?;
120
121        // Valid data should pass validation
122        assert!(validate(&encoded, parity));
123
124        Ok(())
125    }
126
127    #[test]
128    fn test_validate_long_data_invalid_with_errors() -> Result<(), TestError> {
129        let message = b"This is a longer message that will use long ECC".to_buffer()?;
130        let parity = 2;
131
132        let mut encoded = long::encode(&message, parity, long::OverlapFactor::Simple)?;
133
134        // Introduce errors in the message
135        encoded[32] ^= 1;
136        encoded[37] ^= 1;
137
138        // Invalid data should fail validation
139        assert!(!validate(&encoded, parity));
140
141        Ok(())
142    }
143
144    #[test]
145    fn test_validate_long_data_fast_path_valid() -> Result<(), TestError> {
146        let message = b"Fast path validation test".repeat(12);
147        let parity = 1;
148
149        let encoded = long::encode(&message, parity, long::OverlapFactor::Simple)?;
150
151        // Valid data should pass fast validation
152        assert!(validate(&encoded, parity));
153
154        Ok(())
155    }
156
157    #[test]
158    fn test_validate_empty_data() {
159        let data = b"";
160        let parity = 0;
161
162        // Empty data should be handled gracefully
163        assert!(validate(data, parity));
164    }
165
166    #[test]
167    fn test_validate_single_byte() -> Result<(), TestError> {
168        let data = b"A";
169        let parity = 1;
170        let rs = ReedSolomon::new(parity)?;
171        let codeword = rs.encode(data)?;
172
173        assert!(validate(&codeword, parity));
174
175        Ok(())
176    }
177
178    #[test]
179    fn test_validate_large_short_data() -> Result<(), TestError> {
180        let data = b"This is exactly 32 bytes of test data!!";
181        let parity = 4;
182        let rs = ReedSolomon::new(parity)?;
183        let codeword = rs.encode(data)?;
184
185        assert!(validate(&codeword, parity));
186
187        Ok(())
188    }
189
190    #[test]
191    fn test_validate_edge_case_parity_equals_length_div_2() -> Result<(), TestError> {
192        let data = b"test"; // 4 bytes
193        let parity = 2; // 2 == 4/2 - edge case
194
195        let rs = ReedSolomon::new(parity)?;
196        let codeword = rs.encode(data)?;
197
198        assert!(validate(&codeword, parity));
199
200        Ok(())
201    }
202
203    #[test]
204    fn test_validate_edge_case_parity_just_over_length_div_2() {
205        let data = b"test"; // 4 bytes
206        let parity = 3; // 3 > 4/2 (2) - parity is too large
207
208        // Should return false when parity is too large
209        assert!(!validate(data, parity));
210    }
211
212    #[test]
213    fn test_validate_long_data_with_zero_parity() -> Result<(), TestError> {
214        let message = b"Zero parity test".to_buffer()?;
215        let parity = 0;
216
217        let encoded = long::encode(&message, parity, long::OverlapFactor::Simple)?;
218
219        // Zero parity should still validate correctly
220        assert!(validate(&encoded, parity));
221
222        Ok(())
223    }
224
225    #[test]
226    fn test_validate_long_data_header_corrupted() -> Result<(), TestError> {
227        let message = b"Header corruption test".to_buffer()?;
228        let parity = 2;
229
230        let mut encoded = long::encode(&message, parity, long::OverlapFactor::Simple)?;
231
232        // Corrupt header data
233        encoded[0] ^= 1;
234        encoded[5] ^= 1;
235
236        // Corrupted header should fail validation
237        assert!(!validate(&encoded, parity));
238
239        Ok(())
240    }
241
242    #[test]
243    fn test_validate_short_data_length_conversion_error() {
244        let data: Vec<u8> = vec![0x42; 300]; // 300 bytes > 255
245        let parity = 2;
246
247        // This should fall back to long::fast_validate
248        assert!(!validate(&data, parity));
249    }
250
251    #[test]
252    fn test_validate_short_data_unrecoverable_errors() -> Result<(), TestError> {
253        let data = b"test data";
254        let parity = 1;
255        let rs = ReedSolomon::new(parity)?;
256        let mut codeword = rs.encode(data)?;
257
258        // Introduce unrecoverable errors (more errors than parity can correct)
259        codeword[0] ^= 1;
260        codeword[1] ^= 1; // 2 errors with parity=1 - unrecoverable
261
262        assert!(!validate(&codeword, parity));
263
264        Ok(())
265    }
266
267    #[test]
268    fn test_validate_long_data_corrupted_parity() -> Result<(), TestError> {
269        // The message must exceed 255 bytes so that `validate` takes the long path.
270        let message = b"Corrupted parity test".repeat(13).to_buffer()?;
271        let parity = 2;
272
273        let mut encoded = long::encode(&message, parity, long::OverlapFactor::Simple)?;
274
275        // Corrupt parity bytes
276        let parity_start = 32 + message.len();
277
278        if parity_start < encoded.len() {
279            encoded[parity_start] ^= 1;
280        }
281
282        // The checksum covers both the message and the parity, so corrupted
283        // parity bytes invalidate the codeword.
284        assert!(!validate(&encoded, parity));
285
286        Ok(())
287    }
288}