Skip to main content

ps_ecc/long/header/methods/
from_bytes.rs

1use ps_util::Array;
2
3use crate::{
4    long::{
5        header::{
6            magic::{LONG_ECC_HEADER_MAGIC, LONG_ECC_HEADER_VERSION},
7            utils::calculate_header_checksum,
8        },
9        LongEccHeader, HEADER_SIZE,
10    },
11    LongEccHeaderFromBytesError, ReedSolomon,
12};
13
14impl LongEccHeader {
15    /// Parses a header from its serialized form, correcting errors via the header parity.
16    ///
17    /// # Errors
18    /// Returns an error if header correction fails, or if the corrected header
19    /// carries an incorrect magic number, version, or checksum, a message
20    /// length that does not fit within the full length, an invalid
21    /// segment-to-parity ratio, or a full length that does not match the
22    /// derived codeword length.
23    pub fn from_bytes(mut bytes: [u8; 32]) -> Result<Self, LongEccHeaderFromBytesError> {
24        let (body, parity) = bytes.split_at_mut(24);
25
26        ReedSolomon::correct_detached_in_place(parity, body)?;
27
28        let magic = u16::from_be_bytes(*bytes.subarray(0));
29        let version = bytes[2];
30        let parity = bytes[3];
31        let full_length = u32::from_be_bytes(*bytes.subarray(4));
32        let message_length = u32::from_be_bytes(*bytes.subarray(8));
33        let header_checksum = u32::from_be_bytes(*bytes.subarray(12));
34        let checksum = u64::from_be_bytes(*bytes.subarray(16));
35        let header_parity = *bytes.subarray(24);
36
37        if magic != LONG_ECC_HEADER_MAGIC {
38            return Err(LongEccHeaderFromBytesError::IncorrectMagic(magic));
39        }
40
41        if version != LONG_ECC_HEADER_VERSION {
42            return Err(LongEccHeaderFromBytesError::InvalidVersion(version));
43        }
44
45        if header_checksum != {
46            calculate_header_checksum(version, parity, full_length, message_length)
47        } {
48            return Err(LongEccHeaderFromBytesError::IncorrectChecksum);
49        }
50
51        // The codeword must hold the header and the message; parity only adds
52        // to it. The exact-length check below subsumes this bound, but this
53        // check runs first and is kept for its more specific
54        // `InvalidMessageLength` diagnostic.
55        if u64::from(full_length) < u64::from(message_length) + HEADER_SIZE as u64 {
56            return Err(LongEccHeaderFromBytesError::InvalidMessageLength(
57                message_length,
58                full_length,
59            ));
60        }
61
62        let header = Self {
63            magic,
64            version,
65            parity,
66            full_length,
67            message_length,
68            header_checksum,
69            checksum,
70            header_parity,
71        };
72
73        // Reject headers whose parity leaves no room for new data within a segment;
74        // the derived segment methods assume `2 * parity < segment_distance`.
75        if header.parity_bytes() >= header.segment_distance() {
76            return Err(LongEccHeaderFromBytesError::InvalidSegmentParityRatio(
77                header.segment_distance(),
78                header.parity(),
79            ));
80        }
81
82        // Defensively require the full length to match the codeword geometry
83        // exactly: the header, the message, and the parity of every segment.
84        let expected_length = HEADER_SIZE as u64
85            + u64::from(message_length)
86            + u64::from(header.parity_bytes()) * u64::from(header.segment_count());
87
88        if u64::from(full_length) != expected_length {
89            return Err(LongEccHeaderFromBytesError::InvalidFullLength(
90                full_length,
91                expected_length,
92            ));
93        }
94
95        Ok(header)
96    }
97}
98
99#[cfg(test)]
100mod tests {
101    use crate::long::{
102        header::{
103            magic::{LONG_ECC_HEADER_MAGIC, LONG_ECC_HEADER_VERSION},
104            utils::calculate_header_checksum,
105            RS,
106        },
107        LongEccHeader, OverlapFactor,
108    };
109
110    use super::LongEccHeaderFromBytesError;
111
112    type TestError = Box<dyn std::error::Error>;
113
114    #[test]
115    fn test_from_bytes_rejects_invalid_segment_parity_ratio() -> Result<(), TestError> {
116        // Craft a header that is valid on the wire (magic, version, checksum, and
117        // RS parity all check out) but carries an invalid geometry: with parity 63
118        // and quadruple overlap, each segment holds 126 parity bytes, but
119        // consecutive segments start only (255 - 126) / 4 = 32 bytes apart.
120        let parity = 63 | OverlapFactor::Quadruple.to_u8();
121        let full_length = 1000u32;
122        let message_length = 500u32;
123        let header_checksum =
124            calculate_header_checksum(LONG_ECC_HEADER_VERSION, parity, full_length, message_length);
125
126        let mut bytes = [0u8; 32];
127
128        bytes[..2].copy_from_slice(&LONG_ECC_HEADER_MAGIC.to_be_bytes());
129        bytes[2] = LONG_ECC_HEADER_VERSION;
130        bytes[3] = parity;
131        bytes[4..8].copy_from_slice(&full_length.to_be_bytes());
132        bytes[8..12].copy_from_slice(&message_length.to_be_bytes());
133        bytes[12..16].copy_from_slice(&header_checksum.to_be_bytes());
134
135        let header_parity = RS.generate_parity(&bytes[..24])?;
136
137        bytes[24..].copy_from_slice(&header_parity);
138
139        let result = LongEccHeader::from_bytes(bytes);
140
141        assert_eq!(
142            result,
143            Err(LongEccHeaderFromBytesError::InvalidSegmentParityRatio(
144                32, 63
145            ))
146        );
147
148        Ok(())
149    }
150
151    #[test]
152    fn test_from_bytes_rejects_message_length_exceeding_full_length() -> Result<(), TestError> {
153        // Craft a header that is valid on the wire but whose message cannot fit:
154        // full length 100 leaves room for only 68 message bytes after the header.
155        let parity = 2 | OverlapFactor::Simple.to_u8();
156        let full_length = 100u32;
157        let message_length = 100u32;
158        let header_checksum =
159            calculate_header_checksum(LONG_ECC_HEADER_VERSION, parity, full_length, message_length);
160
161        let mut bytes = [0u8; 32];
162
163        bytes[..2].copy_from_slice(&LONG_ECC_HEADER_MAGIC.to_be_bytes());
164        bytes[2] = LONG_ECC_HEADER_VERSION;
165        bytes[3] = parity;
166        bytes[4..8].copy_from_slice(&full_length.to_be_bytes());
167        bytes[8..12].copy_from_slice(&message_length.to_be_bytes());
168        bytes[12..16].copy_from_slice(&header_checksum.to_be_bytes());
169
170        let header_parity = RS.generate_parity(&bytes[..24])?;
171
172        bytes[24..].copy_from_slice(&header_parity);
173
174        let result = LongEccHeader::from_bytes(bytes);
175
176        assert_eq!(
177            result,
178            Err(LongEccHeaderFromBytesError::InvalidMessageLength(100, 100))
179        );
180
181        Ok(())
182    }
183
184    #[test]
185    fn test_from_bytes_rejects_incorrect_full_length() -> Result<(), TestError> {
186        // Craft a header that is valid on the wire but inflated: with parity 1,
187        // the codeword spans the 32-byte header, 10 message bytes, and 2 parity
188        // bytes, so the full length must be 44, not 100.
189        let parity = 1 | OverlapFactor::Simple.to_u8();
190        let full_length = 100u32;
191        let message_length = 10u32;
192        let header_checksum =
193            calculate_header_checksum(LONG_ECC_HEADER_VERSION, parity, full_length, message_length);
194
195        let mut bytes = [0u8; 32];
196
197        bytes[..2].copy_from_slice(&LONG_ECC_HEADER_MAGIC.to_be_bytes());
198        bytes[2] = LONG_ECC_HEADER_VERSION;
199        bytes[3] = parity;
200        bytes[4..8].copy_from_slice(&full_length.to_be_bytes());
201        bytes[8..12].copy_from_slice(&message_length.to_be_bytes());
202        bytes[12..16].copy_from_slice(&header_checksum.to_be_bytes());
203
204        let header_parity = RS.generate_parity(&bytes[..24])?;
205
206        bytes[24..].copy_from_slice(&header_parity);
207
208        let result = LongEccHeader::from_bytes(bytes);
209
210        assert_eq!(
211            result,
212            Err(LongEccHeaderFromBytesError::InvalidFullLength(100, 44))
213        );
214
215        Ok(())
216    }
217
218    #[test]
219    fn test_from_bytes_rejects_zero_checksum_for_zero_parity() -> Result<(), TestError> {
220        // The former multiplicative checksum was 0 for every zero-parity header,
221        // validating arbitrary length fields; a stored 0 must now be rejected.
222        let parity = OverlapFactor::Simple.to_u8();
223        let full_length = 100u32;
224        let message_length = 50u32;
225
226        let mut bytes = [0u8; 32];
227
228        bytes[..2].copy_from_slice(&LONG_ECC_HEADER_MAGIC.to_be_bytes());
229        bytes[2] = LONG_ECC_HEADER_VERSION;
230        bytes[3] = parity;
231        bytes[4..8].copy_from_slice(&full_length.to_be_bytes());
232        bytes[8..12].copy_from_slice(&message_length.to_be_bytes());
233
234        let header_parity = RS.generate_parity(&bytes[..24])?;
235
236        bytes[24..].copy_from_slice(&header_parity);
237
238        let result = LongEccHeader::from_bytes(bytes);
239
240        assert_eq!(result, Err(LongEccHeaderFromBytesError::IncorrectChecksum));
241
242        Ok(())
243    }
244}