Skip to main content

ps_ecc/reed_solomon/methods/
validate.rs

1use crate::{Polynomial, ReedSolomon};
2
3impl ReedSolomon {
4    /// Validates a received codeword.
5    ///
6    /// Returns `None` if `received` is a valid codeword, or `Some(syndromes)`
7    /// if errors are detected.
8    ///
9    /// An input holding fewer bytes than [`ReedSolomon::parity_bytes`], or
10    /// more than [`Polynomial::MAX_COEFFICIENTS`] (255) bytes, can never be
11    /// a valid codeword and always yields `Some`; for such inputs the
12    /// contained syndrome polynomial may be zero and is not usable for
13    /// error computation.
14    #[must_use]
15    pub fn validate(&self, received: &[u8]) -> Option<Polynomial> {
16        let parity_bytes = self.parity_bytes();
17        let syndromes = Self::compute_syndromes(parity_bytes, received);
18
19        let length_is_valid = received.len() >= usize::from(parity_bytes)
20            && received.len() <= usize::from(Polynomial::MAX_COEFFICIENTS);
21
22        if length_is_valid && syndromes.is_zero() {
23            None
24        } else {
25            Some(syndromes)
26        }
27    }
28}
29
30#[cfg(test)]
31mod tests {
32    use ps_buffer::{Buffer, ToBuffer};
33
34    use crate::ReedSolomon;
35
36    type TestError = Box<dyn std::error::Error>;
37
38    #[test]
39    fn test_validate() -> Result<(), TestError> {
40        let rs = ReedSolomon::new(4)?;
41        let message = b"Hello, World!".to_buffer()?;
42        let encoded = rs.encode(&message)?;
43
44        assert!(rs.validate(&encoded).is_none());
45
46        let mut corrupted = Buffer::with_capacity(encoded.len())?;
47
48        corrupted.extend_from_slice(&encoded)?;
49        corrupted[2] ^= 1;
50
51        assert!(rs.validate(&corrupted).is_some());
52
53        Ok(())
54    }
55
56    #[test]
57    fn test_validate_no_errors() -> Result<(), TestError> {
58        let rs = ReedSolomon::new(1)?;
59        let message = b"Valid".to_buffer()?;
60        let encoded = rs.encode(&message)?;
61
62        assert!(rs.validate(&encoded).is_none());
63
64        Ok(())
65    }
66
67    #[test]
68    fn test_validate_with_errors() -> Result<(), TestError> {
69        let rs = ReedSolomon::new(1)?;
70        let message = b"Valid".to_buffer()?;
71        let encoded = rs.encode(&message)?;
72
73        let mut corrupted = encoded.clone()?;
74
75        corrupted[0] ^= 4;
76
77        assert!(rs.validate(&corrupted).is_some());
78
79        Ok(())
80    }
81
82    #[test]
83    fn test_validate_rejects_input_shorter_than_parity() -> Result<(), TestError> {
84        // An all-zero truncated slice yields zero syndromes; without the
85        // length check it validated as pristine, although no codeword is
86        // shorter than the parity.
87        let rs = ReedSolomon::new(4)?;
88
89        assert!(rs.validate(&[]).is_some());
90        assert!(rs.validate(&[0u8; 7]).is_some());
91
92        Ok(())
93    }
94
95    #[test]
96    fn test_validate_rejects_input_longer_than_max_codeword() -> Result<(), TestError> {
97        // A valid codeword zero-padded past 255 bytes yields the same zero
98        // syndromes; without the upper bound it validated as pristine,
99        // although decode rejects the identical bytes.
100        let rs = ReedSolomon::new(4)?;
101
102        assert!(rs.validate(&[0u8; 256]).is_some());
103        assert!(rs.validate(&[0u8; 300]).is_some());
104
105        Ok(())
106    }
107
108    #[test]
109    fn test_validate_accepts_maximum_length_codeword() -> Result<(), TestError> {
110        // 255 zero bytes form the codeword of the all-zero 247-byte message.
111        let rs = ReedSolomon::new(4)?;
112
113        assert!(rs.validate(&[0u8; 255]).is_none());
114
115        Ok(())
116    }
117
118    #[test]
119    fn test_validate_accepts_parity_only_codeword() -> Result<(), TestError> {
120        // The encoding of the empty message is all-zero parity; a slice of
121        // exactly parity_bytes zeros is that codeword and remains valid.
122        let rs = ReedSolomon::new(4)?;
123
124        assert!(rs.validate(&[0u8; 8]).is_none());
125
126        Ok(())
127    }
128
129    #[test]
130    fn test_validate_zero_parity_empty_input() -> Result<(), TestError> {
131        let rs = ReedSolomon::new(0)?;
132
133        assert!(rs.validate(&[]).is_none());
134
135        Ok(())
136    }
137
138    #[test]
139    fn test_validate_large_parity() -> Result<(), TestError> {
140        let rs = ReedSolomon::new(16)?;
141        let message = b"LargeParity".to_buffer()?;
142        let encoded = rs.encode(&message)?;
143
144        assert!(rs.validate(&encoded).is_none());
145
146        let mut corrupted = encoded.clone()?;
147
148        corrupted[10] ^= 1;
149
150        assert!(rs.validate(&corrupted).is_some());
151
152        Ok(())
153    }
154}