Skip to main content

ps_ecc/long/header/methods/
new.rs

1use crate::{
2    long::{
3        header::{
4            magic::LONG_ECC_HEADER_VERSION, utils::calculate_header_checksum, LONG_ECC_HEADER_MAGIC,
5        },
6        LongEccHeader, OverlapFactor, HEADER_SIZE,
7    },
8    LongEccHeaderConstructorError, MAX_PARITY,
9};
10
11use super::super::RS;
12
13impl LongEccHeader {
14    /// Constructs a header, computing its checksum and parity.
15    ///
16    /// # Errors
17    /// Returns `InvalidParityCount` if `parity` exceeds [`MAX_PARITY`],
18    /// `InvalidMessageLength` if the header and message do not fit within
19    /// `full_length`, `InvalidSegmentParityRatio` if the parity bytes leave
20    /// no room for new data within a segment, `InvalidFullLength` if
21    /// `full_length` does not match the codeword length derived from the
22    /// message length and segment geometry, or `GenerateParity` if
23    /// generating the header parity fails.
24    pub fn new(
25        parity: u8,
26        overlap_factor: OverlapFactor,
27        full_length: u32,
28        message_length: u32,
29        checksum: u64,
30    ) -> Result<Self, LongEccHeaderConstructorError> {
31        let magic = LONG_ECC_HEADER_MAGIC;
32        let version = LONG_ECC_HEADER_VERSION;
33
34        if parity > MAX_PARITY {
35            return Err(LongEccHeaderConstructorError::InvalidParityCount(parity));
36        }
37
38        // The codeword must hold the header and the message; parity only adds
39        // to it. The exact-length check below subsumes this bound, but this
40        // check runs first and is kept for its more specific
41        // `InvalidMessageLength` diagnostic.
42        if u64::from(full_length) < u64::from(message_length) + HEADER_SIZE as u64 {
43            return Err(LongEccHeaderConstructorError::InvalidMessageLength(
44                message_length,
45                full_length,
46            ));
47        }
48
49        // Pack the overlap factor into the high two bits of the parity byte.
50        let parity = parity | overlap_factor.to_u8();
51
52        let mut header = Self {
53            magic,
54            version,
55            parity,
56            full_length,
57            message_length,
58            header_checksum: calculate_header_checksum(
59                version,
60                parity,
61                full_length,
62                message_length,
63            ),
64            checksum,
65            header_parity: [0; 8],
66        };
67
68        // Reject headers whose parity leaves no room for new data within a segment;
69        // the derived segment methods assume `2 * parity < segment_distance`.
70        if header.parity_bytes() >= header.segment_distance() {
71            return Err(LongEccHeaderConstructorError::InvalidSegmentParityRatio(
72                header.segment_distance(),
73                header.parity(),
74            ));
75        }
76
77        // Defensively require the full length to match the codeword geometry
78        // exactly: the header, the message, and the parity of every segment.
79        let expected_length = HEADER_SIZE as u64
80            + u64::from(message_length)
81            + u64::from(header.parity_bytes()) * u64::from(header.segment_count());
82
83        if u64::from(full_length) != expected_length {
84            return Err(LongEccHeaderConstructorError::InvalidFullLength(
85                full_length,
86                expected_length,
87            ));
88        }
89
90        let parity = RS.generate_parity(&header.to_bytes()[..24])?;
91
92        header.header_parity.copy_from_slice(&parity);
93
94        Ok(header)
95    }
96}
97
98#[cfg(test)]
99mod tests {
100    use crate::{
101        long::{LongEccHeader, OverlapFactor},
102        MAX_PARITY,
103    };
104
105    use super::LongEccHeaderConstructorError;
106
107    #[test]
108    fn test_new_rejects_invalid_segment_parity_ratio() {
109        // With parity 63 and quadruple overlap, each segment holds 126 parity bytes,
110        // but consecutive segments start only (255 - 126) / 4 = 32 bytes apart.
111        let result = LongEccHeader::new(63, OverlapFactor::Quadruple, 100, 50, 0);
112
113        assert_eq!(
114            result,
115            Err(LongEccHeaderConstructorError::InvalidSegmentParityRatio(
116                32, 63
117            ))
118        );
119    }
120
121    #[test]
122    fn test_new_quadruple_overlap_boundary() {
123        // With parity 25, the 50 parity bytes fit below the segment distance
124        // of (255 - 50) / 4 = 51; with parity 26, the 52 parity bytes exceed
125        // the segment distance of (255 - 52) / 4 = 50.
126        assert!(LongEccHeader::new(25, OverlapFactor::Quadruple, 132, 50, 0).is_ok());
127        assert_eq!(
128            LongEccHeader::new(26, OverlapFactor::Quadruple, 132, 50, 0),
129            Err(LongEccHeaderConstructorError::InvalidSegmentParityRatio(
130                50, 26
131            ))
132        );
133    }
134
135    #[test]
136    fn test_new_double_overlap_boundary() {
137        // With parity 42, the 84 parity bytes fit below the segment distance
138        // of (255 - 84) / 2 = 85; with parity 43, the 86 parity bytes exceed
139        // the segment distance of (255 - 86) / 2 = 84.
140        assert!(LongEccHeader::new(42, OverlapFactor::Double, 166, 50, 0).is_ok());
141        assert_eq!(
142            LongEccHeader::new(43, OverlapFactor::Double, 166, 50, 0),
143            Err(LongEccHeaderConstructorError::InvalidSegmentParityRatio(
144                84, 43
145            ))
146        );
147    }
148
149    #[test]
150    fn test_new_triple_overlap_boundary() {
151        // With parity 31, the 62 parity bytes fit below the segment distance
152        // of (255 - 62) / 3 = 64; with parity 32, the 64 parity bytes exceed
153        // the segment distance of (255 - 64) / 3 = 63.
154        assert!(LongEccHeader::new(31, OverlapFactor::Triple, 144, 50, 0).is_ok());
155        assert_eq!(
156            LongEccHeader::new(32, OverlapFactor::Triple, 144, 50, 0),
157            Err(LongEccHeaderConstructorError::InvalidSegmentParityRatio(
158                63, 32
159            ))
160        );
161    }
162
163    #[test]
164    fn test_new_rejects_message_length_exceeding_full_length() {
165        // Full length 41 is one byte short of the 32-byte header plus 10 message bytes.
166        let result = LongEccHeader::new(1, OverlapFactor::Simple, 41, 10, 0);
167
168        assert_eq!(
169            result,
170            Err(LongEccHeaderConstructorError::InvalidMessageLength(10, 41))
171        );
172    }
173
174    #[test]
175    fn test_new_rejects_incorrect_full_length() {
176        // With parity 1, the codeword spans the 32-byte header, 10 message
177        // bytes, and 2 parity bytes, so the full length must be exactly 44.
178        assert!(LongEccHeader::new(1, OverlapFactor::Simple, 44, 10, 0).is_ok());
179        assert_eq!(
180            LongEccHeader::new(1, OverlapFactor::Simple, 45, 10, 0),
181            Err(LongEccHeaderConstructorError::InvalidFullLength(45, 44))
182        );
183    }
184
185    #[test]
186    fn test_new_accepts_max_parity_without_overlap() {
187        // With maximum parity and no overlap, the 126 parity bytes fit below
188        // the segment distance of 255 - 126 = 129.
189        assert!(LongEccHeader::new(MAX_PARITY, OverlapFactor::Simple, 208, 50, 0).is_ok());
190    }
191}