Skip to main content

ps_ecc/reed_solomon/methods/
decode.rs

1use crate::{Codeword, RSDecodeError, ReedSolomon};
2
3impl ReedSolomon {
4    /// Decodes a received codeword, correcting errors if possible.
5    /// # Errors
6    /// - [`RSDecodeError::InsufficientLength`] is returned if `received` holds
7    ///   fewer bytes than [`ReedSolomon::parity_bytes`].
8    /// - [`std::num::TryFromIntError`] is returned if `received` holds more
9    ///   than 255 bytes.
10    /// - [`ps_buffer::BufferError`] is returned if memory allocation fails.
11    /// - [`RSComputeErrorsError`](crate::RSComputeErrorsError) is propagated
12    ///   from [`ReedSolomon::compute_errors`].
13    /// - [`RSDecodeError::TooManyErrors`] is returned if the corrected bytes
14    ///   fail validation.
15    pub fn decode<'lt>(&self, received: &'lt [u8]) -> Result<Codeword<'lt>, RSDecodeError> {
16        let parity_bytes = self.parity_bytes();
17
18        if received.len() < usize::from(parity_bytes) {
19            return Err(RSDecodeError::InsufficientLength {
20                parity_bytes,
21                received: received.len(),
22            });
23        }
24
25        let corrected = self.correct(received)?;
26        let codeword = Codeword {
27            codeword: corrected,
28            range: usize::from(parity_bytes)..received.len(),
29        };
30
31        Ok(codeword)
32    }
33}
34
35#[cfg(test)]
36#[allow(clippy::decimal_bitwise_operands)]
37mod tests {
38    use ps_buffer::{Buffer, ToBuffer};
39
40    use crate::{RSComputeErrorsError, RSDecodeError, ReedSolomon};
41
42    type TestError = Box<dyn std::error::Error>;
43
44    #[test]
45    fn test_encode_decode() -> Result<(), TestError> {
46        let rs = ReedSolomon::new(4)?;
47        let message = b"Hello, World!".to_buffer()?;
48        let encoded = rs.encode(&message)?;
49
50        let mut corrupted = Buffer::with_capacity(encoded.len())?;
51
52        corrupted.extend_from_slice(&encoded)?;
53        corrupted[2] ^= 1;
54
55        let decoded = rs.decode(&corrupted)?;
56
57        assert_eq!(&decoded[..], &message[..]);
58
59        Ok(())
60    }
61
62    #[test]
63    fn test_too_many_errors() -> Result<(), TestError> {
64        let rs = ReedSolomon::new(2)?;
65        let message = b"Hello, World!".to_buffer()?;
66        let encoded = rs.encode(&message)?;
67
68        let mut corrupted = Buffer::with_capacity(encoded.len())?;
69
70        corrupted.extend_from_slice(&encoded)?;
71        corrupted[5] ^= 113;
72        corrupted[6] ^= 59;
73        corrupted[7] ^= 3;
74
75        assert_eq!(
76            rs.decode(&corrupted),
77            Err(RSDecodeError::RSComputeErrorsError(
78                RSComputeErrorsError::TooManyErrors
79            ))
80        );
81
82        Ok(())
83    }
84
85    #[test]
86    fn test_encode_correct_no_errors() -> Result<(), TestError> {
87        let rs = ReedSolomon::new(2)?;
88        let message = b"Data".to_buffer()?;
89        let encoded = rs.encode(&message)?;
90
91        let decoded = rs.decode(&encoded)?;
92
93        assert_eq!(&decoded[..], &message[..]);
94
95        Ok(())
96    }
97
98    #[test]
99    fn test_encode_correct_one_error() -> Result<(), TestError> {
100        let rs = ReedSolomon::new(3)?;
101        let message = b"Example".to_buffer()?;
102        let encoded = rs.encode(&message)?;
103
104        let mut corrupted = encoded.clone()?;
105
106        corrupted[1] ^= 0b1010_1010;
107
108        let decoded = rs.decode(&corrupted)?;
109
110        assert_eq!(&decoded[..], &message[..]);
111
112        Ok(())
113    }
114
115    #[test]
116    fn test_encode_correct_multiple_recoverable_errors() -> Result<(), TestError> {
117        let rs = ReedSolomon::new(4)?;
118        let message = b"Multiple".to_buffer()?;
119        let encoded = rs.encode(&message)?;
120
121        let mut corrupted = encoded.clone()?;
122
123        corrupted[3] ^= 0b0011_0011;
124        corrupted[7] ^= 0b1100_1100;
125
126        let decoded = rs.decode(&corrupted)?;
127
128        assert_eq!(&decoded[..], &message[..]);
129
130        Ok(())
131    }
132
133    #[test]
134    fn test_decode_no_errors() -> Result<(), TestError> {
135        let rs = ReedSolomon::new(2)?;
136        let message = b"DecodeOk".to_buffer()?;
137        let encoded = rs.encode(&message)?;
138
139        let decoded = rs.decode(&encoded)?;
140
141        assert_eq!(&decoded[..], &message[..]);
142
143        Ok(())
144    }
145
146    #[test]
147    fn test_decode_one_error() -> Result<(), TestError> {
148        let rs = ReedSolomon::new(3)?;
149        let message = b"DecodeErr".to_buffer()?;
150        let encoded = rs.encode(&message)?;
151
152        let mut corrupted = encoded.clone()?;
153
154        corrupted[5] ^= 32;
155
156        let decoded = rs.decode(&corrupted)?;
157
158        assert_eq!(&decoded[..], &message[..]);
159
160        Ok(())
161    }
162
163    #[test]
164    fn test_decode_too_many_errors() -> Result<(), TestError> {
165        let rs = ReedSolomon::new(1)?;
166        let message = b"DecodeMany".to_buffer()?;
167        let encoded = rs.encode(&message)?;
168
169        let mut corrupted = encoded.clone()?;
170
171        corrupted[0] ^= 1;
172        corrupted[2] ^= 2;
173
174        assert_eq!(
175            rs.decode(&corrupted),
176            Err(RSDecodeError::RSComputeErrorsError(
177                RSComputeErrorsError::TooManyErrors
178            ))
179        );
180
181        Ok(())
182    }
183
184    #[test]
185    fn test_decode_with_errors_in_parity_only() -> Result<(), TestError> {
186        let rs = ReedSolomon::new(3)?;
187        let message = b"ParityErr".to_buffer()?;
188        let encoded = rs.encode(&message)?;
189
190        let mut corrupted = encoded.clone()?;
191
192        corrupted[1] ^= 4; // Error in parity
193        corrupted[3] ^= 8; // Error in parity
194
195        let decoded = rs.decode(&corrupted)?;
196
197        assert_eq!(&decoded[..], &message[..]);
198
199        Ok(())
200    }
201
202    #[test]
203    fn test_decode_empty_input_rejected() -> Result<(), TestError> {
204        let rs = ReedSolomon::new(4)?;
205
206        // Without the length guard, decode returns Ok with the inverted
207        // range 8..0, and the first deref panics.
208        assert_eq!(
209            rs.decode(&[]),
210            Err(RSDecodeError::InsufficientLength {
211                parity_bytes: 8,
212                received: 0,
213            })
214        );
215
216        Ok(())
217    }
218
219    #[test]
220    fn test_decode_input_shorter_than_parity_rejected() -> Result<(), TestError> {
221        let rs = ReedSolomon::new(4)?;
222
223        assert_eq!(
224            rs.decode(&[0u8; 7]),
225            Err(RSDecodeError::InsufficientLength {
226                parity_bytes: 8,
227                received: 7,
228            })
229        );
230
231        Ok(())
232    }
233
234    #[test]
235    fn test_decode_parity_only_codeword() -> Result<(), TestError> {
236        let rs = ReedSolomon::new(4)?;
237        let encoded = rs.encode(&[])?;
238
239        assert_eq!(encoded.len(), usize::from(rs.parity_bytes()));
240
241        let decoded = rs.decode(&encoded)?;
242
243        assert!(decoded.is_empty());
244
245        Ok(())
246    }
247
248    #[test]
249    fn test_decode_zero_parity_empty_input() -> Result<(), TestError> {
250        let rs = ReedSolomon::new(0)?;
251        let decoded = rs.decode(&[])?;
252
253        assert!(decoded.is_empty());
254
255        Ok(())
256    }
257
258    #[test]
259    fn test_correct_maximum_correctable_errors() -> Result<(), TestError> {
260        let rs = ReedSolomon::new(4)?;
261        let message = b"MaxErrors".to_buffer()?;
262        let encoded = rs.encode(&message)?;
263
264        let mut corrupted = encoded.clone()?;
265
266        corrupted[0] ^= 1;
267        corrupted[2] ^= 2;
268        corrupted[4] ^= 4;
269        corrupted[6] ^= 8;
270
271        let decoded = rs.decode(&corrupted)?;
272
273        assert_eq!(&decoded[..], &message[..]);
274
275        Ok(())
276    }
277}