Skip to main content

ps_ecc/reed_solomon/methods/
correct_detached.rs

1use ps_buffer::Buffer;
2
3use crate::cow::Cow;
4use crate::{Codeword, RSDecodeError, ReedSolomon};
5
6impl ReedSolomon {
7    /// Corrects a message based on detached parity bytes.
8    /// # Errors
9    /// - [`RSConstructorError`](crate::RSConstructorError) is returned if
10    ///   `parity` holds more than [`MAX_PARITY_BYTES`](crate::MAX_PARITY_BYTES)
11    ///   bytes, or an odd number of bytes.
12    /// - [`std::num::TryFromIntError`] is returned if `parity` and `data`
13    ///   together hold more than 255 bytes.
14    /// - [`ps_buffer::BufferError`] is returned if memory allocation fails.
15    /// - [`RSComputeErrorsError`](crate::RSComputeErrorsError) is propagated
16    ///   from [`ReedSolomon::compute_errors`].
17    /// - [`RSDecodeError::TooManyErrors`] is returned if the corrected bytes
18    ///   fail validation.
19    pub fn correct_detached<'lt>(
20        parity: &[u8],
21        data: &'lt [u8],
22    ) -> Result<Codeword<'lt>, RSDecodeError> {
23        // The parity-slice checks run first so that a structurally invalid
24        // parity slice is diagnosed before the combined length can fail the
25        // u8 conversion, without computing syndromes on oversized input.
26        Self::check_detached_parity(parity)?;
27
28        let parity_bytes = parity.len();
29        let num_parity = u8::try_from(parity_bytes >> 1)?;
30        let length = u8::try_from(parity_bytes + data.len())?;
31        let rs = Self::new(num_parity)?;
32
33        let syndromes = Self::compute_syndromes_detached(parity, data)?;
34
35        let Some(errors) = Self::compute_errors_detached(num_parity, length, &syndromes)? else {
36            return Ok(data.into());
37        };
38
39        // Correct the received codeword
40        let mut corrected = Buffer::with_capacity(parity.len() + data.len())?;
41
42        corrected.extend_from_slice(parity)?;
43        corrected.extend_from_slice(data)?;
44
45        Self::apply_corrections(&mut corrected, errors.first_n_coefficients(length.into()));
46
47        if rs.validate(&corrected).is_some() {
48            return Err(RSDecodeError::TooManyErrors);
49        }
50
51        let range = parity.len()..corrected.len();
52        let codeword = Cow::Owned(corrected.share());
53        let codeword = Codeword { codeword, range };
54
55        Ok(codeword)
56    }
57}
58
59#[cfg(test)]
60mod tests {
61    use ps_buffer::{Buffer, ToBuffer};
62
63    use crate::{RSComputeErrorsError, RSConstructorError, RSDecodeError, ReedSolomon};
64
65    type TestError = Box<dyn std::error::Error>;
66
67    #[test]
68    fn test_correct_detached_no_errors() -> Result<(), TestError> {
69        let rs = ReedSolomon::new(2)?;
70        let message = b"DetachOk".to_buffer()?;
71        let parity = rs.generate_parity(&message)?;
72
73        let corrected = ReedSolomon::correct_detached(&parity, &message)?;
74
75        assert_eq!(&corrected[..], &message[..]);
76
77        Ok(())
78    }
79
80    #[test]
81    fn test_correct_detached_one_error() -> Result<(), TestError> {
82        let rs = ReedSolomon::new(3)?;
83        let message = b"DetachErr".to_buffer()?;
84        let parity = rs.generate_parity(&message)?;
85
86        let mut corrupted = message.clone()?;
87
88        corrupted[1] ^= 64;
89
90        let corrected = ReedSolomon::correct_detached(&parity, &corrupted)?;
91
92        assert_eq!(&corrected[..], &message[..]);
93
94        Ok(())
95    }
96
97    #[test]
98    fn test_correct_detached_too_many_errors() -> Result<(), TestError> {
99        let rs = ReedSolomon::new(1)?;
100        let message = b"DetachMany".to_buffer()?;
101        let parity = rs.generate_parity(&message)?;
102
103        let mut corrupted = message.clone()?;
104
105        corrupted[0] ^= 1;
106        corrupted[2] ^= 2;
107
108        assert_eq!(
109            ReedSolomon::correct_detached(&parity, &corrupted),
110            Err(RSDecodeError::RSComputeErrorsError(
111                RSComputeErrorsError::TooManyErrors
112            ))
113        );
114
115        Ok(())
116    }
117
118    #[test]
119    fn test_correct_detached_rejects_oversized_parity() {
120        // A 127-byte parity slice was previously accepted and silently
121        // truncated to a parity count of 63; it is now rejected, matching
122        // the in-place variants.
123        let parity = [0u8; 127];
124
125        assert_eq!(
126            ReedSolomon::correct_detached(&parity, &[]),
127            Err(RSDecodeError::RSConstructorError(
128                RSConstructorError::ParityTooHigh
129            ))
130        );
131    }
132
133    #[test]
134    fn test_correct_detached_parity_checks_precede_length_conversion() {
135        // One parity byte plus 255 data bytes overflows the u8 combined
136        // length, but the odd parity length is diagnosed first.
137        let parity = [0u8; 1];
138        let data = [0u8; 255];
139
140        assert_eq!(
141            ReedSolomon::correct_detached(&parity, &data),
142            Err(RSDecodeError::RSConstructorError(
143                RSConstructorError::OddParityLength(1)
144            ))
145        );
146    }
147
148    #[test]
149    fn test_correct_detached_rejects_odd_parity_length() {
150        // An odd length previously dropped the last parity byte from
151        // num_parity while the syndromes covered the full slice.
152        let parity = [0u8; 5];
153
154        assert_eq!(
155            ReedSolomon::correct_detached(&parity, b"data"),
156            Err(RSDecodeError::RSConstructorError(
157                RSConstructorError::OddParityLength(5)
158            ))
159        );
160    }
161
162    #[test]
163    fn test_correct_detached_with_errors_in_parity_only() -> Result<(), TestError> {
164        let rs = ReedSolomon::new(3)?;
165        let message = b"ParityOnly".to_buffer()?;
166        let parity_poly = rs.generate_parity(&message)?;
167        let mut parity = Buffer::from_slice(parity_poly)?;
168
169        parity[0] ^= 2;
170        parity[2] ^= 4;
171
172        let corrected = ReedSolomon::correct_detached(&parity, &message)?;
173
174        assert_eq!(&corrected[..], &message[..]);
175
176        Ok(())
177    }
178}