Skip to main content

ps_ecc/reed_solomon/methods/
validate_detached.rs

1use crate::{Polynomial, RSConstructorError, ReedSolomon};
2
3impl ReedSolomon {
4    /// Validates a segregated (parity, data) pair.
5    ///
6    /// Returns `Ok(None)` if valid, or `Ok(Some(syndromes))` if errors are
7    /// detected. An empty `parity` slice carries no parity information, so
8    /// a pair within the length bound validates trivially.
9    ///
10    /// A pair whose combined length exceeds
11    /// [`Polynomial::MAX_COEFFICIENTS`] (255) bytes can never be a valid
12    /// codeword and always yields `Ok(Some)`; for such inputs the contained
13    /// syndrome polynomial may be zero and is not usable for error
14    /// computation.
15    /// # Errors
16    /// - [`RSConstructorError::ParityTooHigh`] is returned if `parity` holds
17    ///   more than [`MAX_PARITY_BYTES`](crate::MAX_PARITY_BYTES) bytes.
18    /// - [`RSConstructorError::OddParityLength`] is returned if `parity`
19    ///   holds an odd number of bytes.
20    pub fn validate_detached(
21        parity: &[u8],
22        data: &[u8],
23    ) -> Result<Option<Polynomial>, RSConstructorError> {
24        let syndromes = Self::compute_syndromes_detached(parity, data)?;
25
26        let length_is_valid =
27            parity.len() + data.len() <= usize::from(Polynomial::MAX_COEFFICIENTS);
28
29        if length_is_valid && syndromes.is_zero() {
30            Ok(None)
31        } else {
32            Ok(Some(syndromes))
33        }
34    }
35}
36
37#[cfg(test)]
38mod tests {
39    use ps_buffer::{Buffer, ToBuffer};
40
41    use crate::{RSConstructorError, ReedSolomon};
42
43    type TestError = Box<dyn std::error::Error>;
44
45    #[test]
46    fn test_validate_detached() -> Result<(), TestError> {
47        let rs = ReedSolomon::new(4)?;
48        let message = b"Hello, World!".to_buffer()?;
49        let parity = rs.generate_parity(&message)?;
50
51        assert!(ReedSolomon::validate_detached(&parity, &message)?.is_none());
52
53        let mut corrupted = Buffer::with_capacity(message.len())?;
54
55        corrupted.extend_from_slice(&message)?;
56        corrupted[2] ^= 1;
57
58        assert!(ReedSolomon::validate_detached(&parity, &corrupted)?.is_some());
59
60        Ok(())
61    }
62
63    #[test]
64    fn test_validate_detached_no_errors_case_2() -> Result<(), TestError> {
65        let rs = ReedSolomon::new(2)?;
66        let message = b"Detached2".to_buffer()?;
67        let parity = rs.generate_parity(&message)?;
68
69        assert!(ReedSolomon::validate_detached(&parity, &message)?.is_none());
70
71        Ok(())
72    }
73
74    #[test]
75    fn test_validate_detached_with_errors_case_2() -> Result<(), TestError> {
76        let rs = ReedSolomon::new(2)?;
77        let message = b"Detached2".to_buffer()?;
78        let parity = rs.generate_parity(&message)?;
79
80        let mut corrupted = message.clone()?;
81
82        corrupted[1] ^= 8;
83
84        assert!(ReedSolomon::validate_detached(&parity, &corrupted)?.is_some());
85
86        Ok(())
87    }
88
89    #[test]
90    fn test_validate_detached_rejects_odd_parity_length() {
91        let parity = [0u8; 3];
92
93        assert_eq!(
94            ReedSolomon::validate_detached(&parity, &[]),
95            Err(RSConstructorError::OddParityLength(3))
96        );
97    }
98
99    #[test]
100    fn test_validate_detached_empty_parity_is_trivially_valid() -> Result<(), TestError> {
101        assert!(ReedSolomon::validate_detached(&[], b"anything")?.is_none());
102
103        Ok(())
104    }
105
106    #[test]
107    fn test_validate_detached_rejects_oversized_combined_length() -> Result<(), TestError> {
108        // 4 parity bytes plus 252 data bytes exceed the 255-byte codeword;
109        // without the upper bound the all-zero pair validated as pristine,
110        // although correct_detached rejects the identical input.
111        assert!(ReedSolomon::validate_detached(&[0u8; 4], &[0u8; 252])?.is_some());
112
113        Ok(())
114    }
115
116    #[test]
117    fn test_validate_detached_accepts_maximum_combined_length() -> Result<(), TestError> {
118        // 4 parity bytes plus 251 data bytes fill the codeword exactly.
119        assert!(ReedSolomon::validate_detached(&[0u8; 4], &[0u8; 251])?.is_none());
120
121        Ok(())
122    }
123
124    #[test]
125    fn test_validate_detached_rejects_oversized_parity() {
126        // Without the length bound, an oversized parity slice was silently
127        // accepted, and validation ran on silently truncated syndromes.
128        for parity_len in [127usize, 256, 300] {
129            let parity = vec![0u8; parity_len];
130
131            assert_eq!(
132                ReedSolomon::validate_detached(&parity, &[]),
133                Err(RSConstructorError::ParityTooHigh)
134            );
135        }
136    }
137}