1use crate::checksum;
2use crate::error::{Par2Error, Result};
3use crate::types::RecoverySetId;
4
5pub const MAGIC: &[u8; 8] = b"PAR2\x00PKT";
7
8pub const HEADER_SIZE: usize = 64;
10
11pub const TYPE_MAIN: &[u8; 16] = b"PAR 2.0\x00Main\x00\x00\x00\x00";
13pub const TYPE_FILE_DESC: &[u8; 16] = b"PAR 2.0\x00FileDesc";
14pub const TYPE_IFSC: &[u8; 16] = b"PAR 2.0\x00IFSC\x00\x00\x00\x00";
15pub const TYPE_RECOVERY: &[u8; 16] = b"PAR 2.0\x00RecvSlic";
16pub const TYPE_CREATOR: &[u8; 16] = b"PAR 2.0\x00Creator\x00";
17
18#[derive(Debug, Clone, Copy, PartialEq, Eq)]
20pub enum PacketType {
21 Main,
22 FileDescription,
23 InputFileSliceChecksum,
24 RecoverySlice,
25 Creator,
26 Unknown([u8; 16]),
27}
28
29impl PacketType {
30 pub fn from_signature(sig: &[u8; 16]) -> Self {
31 match sig {
32 s if s == TYPE_MAIN => PacketType::Main,
33 s if s == TYPE_FILE_DESC => PacketType::FileDescription,
34 s if s == TYPE_IFSC => PacketType::InputFileSliceChecksum,
35 s if s == TYPE_RECOVERY => PacketType::RecoverySlice,
36 s if s == TYPE_CREATOR => PacketType::Creator,
37 other => PacketType::Unknown(*other),
38 }
39 }
40}
41
42#[derive(Debug, Clone)]
44pub struct PacketHeader {
45 pub length: u64,
47 pub packet_hash: [u8; 16],
49 pub recovery_set_id: RecoverySetId,
51 pub packet_type: PacketType,
53}
54
55impl PacketHeader {
56 pub fn parse(data: &[u8], offset: u64) -> Result<Self> {
61 if data.len() < HEADER_SIZE {
62 return Err(Par2Error::PacketTooShort {
63 expected: HEADER_SIZE as u64,
64 actual: data.len() as u64,
65 });
66 }
67
68 if &data[0..8] != MAGIC {
70 return Err(Par2Error::InvalidMagic { offset });
71 }
72
73 let length = u64::from_le_bytes(data[8..16].try_into().unwrap());
75 if length < HEADER_SIZE as u64 {
76 return Err(Par2Error::PacketTooShort {
77 expected: HEADER_SIZE as u64,
78 actual: length,
79 });
80 }
81 if length % 4 != 0 {
82 return Err(Par2Error::InvalidPacketLength { length });
83 }
84 let packet_hash: [u8; 16] = data[16..32].try_into().unwrap();
86 let recovery_set_id = RecoverySetId::from_bytes(data[32..48].try_into().unwrap());
87 let type_sig: [u8; 16] = data[48..64].try_into().unwrap();
88 let packet_type = PacketType::from_signature(&type_sig);
89
90 Ok(PacketHeader {
91 length,
92 packet_hash,
93 recovery_set_id,
94 packet_type,
95 })
96 }
97
98 pub fn validate_hash(&self, full_packet: &[u8], offset: u64) -> Result<()> {
102 if (full_packet.len() as u64) < self.length {
103 return Err(Par2Error::PacketTooShort {
104 expected: self.length,
105 actual: full_packet.len() as u64,
106 });
107 }
108
109 let hash_input = &full_packet[32..self.length as usize];
110 let computed = checksum::md5(hash_input);
111
112 if computed != self.packet_hash {
113 return Err(Par2Error::PacketHashMismatch { offset });
114 }
115
116 Ok(())
117 }
118
119 pub fn body_length(&self) -> u64 {
121 self.length - HEADER_SIZE as u64
122 }
123}
124
125#[cfg(test)]
126mod tests {
127 use super::*;
128
129 fn make_header(packet_type: &[u8; 16], body: &[u8]) -> Vec<u8> {
131 let length = (HEADER_SIZE + body.len()) as u64;
132 let recovery_set_id = [0xAAu8; 16];
133 let type_sig = packet_type;
134
135 let mut hash_input = Vec::new();
137 hash_input.extend_from_slice(&recovery_set_id);
138 hash_input.extend_from_slice(type_sig);
139 hash_input.extend_from_slice(body);
140
141 let packet_hash = checksum::md5(&hash_input);
142
143 let mut data = Vec::new();
144 data.extend_from_slice(MAGIC);
145 data.extend_from_slice(&length.to_le_bytes());
146 data.extend_from_slice(&packet_hash);
147 data.extend_from_slice(&recovery_set_id);
148 data.extend_from_slice(type_sig);
149 data.extend_from_slice(body);
150 data
151 }
152
153 #[test]
154 fn parse_valid_header() {
155 let data = make_header(TYPE_MAIN, &[0u8; 0]);
156 let header = PacketHeader::parse(&data, 0).unwrap();
157 assert_eq!(header.length, HEADER_SIZE as u64);
158 assert_eq!(header.packet_type, PacketType::Main);
159 assert_eq!(
160 header.recovery_set_id,
161 RecoverySetId::from_bytes([0xAA; 16])
162 );
163 }
164
165 #[test]
166 fn parse_all_packet_types() {
167 for (sig, expected) in [
168 (TYPE_MAIN, PacketType::Main),
169 (TYPE_FILE_DESC, PacketType::FileDescription),
170 (TYPE_IFSC, PacketType::InputFileSliceChecksum),
171 (TYPE_RECOVERY, PacketType::RecoverySlice),
172 (TYPE_CREATOR, PacketType::Creator),
173 ] {
174 let data = make_header(sig, &[]);
175 let header = PacketHeader::parse(&data, 0).unwrap();
176 assert_eq!(header.packet_type, expected);
177 }
178 }
179
180 #[test]
181 fn unknown_packet_type() {
182 let custom = b"PAR 2.0\x00CustomXX";
183 let data = make_header(custom, &[]);
184 let header = PacketHeader::parse(&data, 0).unwrap();
185 assert_eq!(header.packet_type, PacketType::Unknown(*custom));
186 }
187
188 #[test]
189 fn validate_hash_succeeds() {
190 let data = make_header(TYPE_CREATOR, b"test body data!!");
191 let header = PacketHeader::parse(&data, 0).unwrap();
192 header.validate_hash(&data, 0).unwrap();
193 }
194
195 #[test]
196 fn validate_hash_fails_on_corruption() {
197 let mut data = make_header(TYPE_CREATOR, b"test body data!!");
198 let last = data.len() - 1;
200 data[last] ^= 0xFF;
201 let header = PacketHeader::parse(&data, 42).unwrap();
202 let err = header.validate_hash(&data, 42).unwrap_err();
203 assert!(matches!(err, Par2Error::PacketHashMismatch { offset: 42 }));
204 }
205
206 #[test]
207 fn reject_bad_magic() {
208 let mut data = make_header(TYPE_MAIN, &[]);
209 data[0] = b'X';
210 let err = PacketHeader::parse(&data, 100).unwrap_err();
211 assert!(matches!(err, Par2Error::InvalidMagic { offset: 100 }));
212 }
213
214 #[test]
215 fn reject_too_short() {
216 let data = [0u8; 32];
217 let err = PacketHeader::parse(&data, 0).unwrap_err();
218 assert!(matches!(err, Par2Error::PacketTooShort { .. }));
219 }
220
221 #[test]
222 fn reject_non_aligned_length() {
223 let mut data = make_header(TYPE_MAIN, &[]);
224 let bad_len = 65u64;
226 data[8..16].copy_from_slice(&bad_len.to_le_bytes());
227 let err = PacketHeader::parse(&data, 0).unwrap_err();
228 assert!(matches!(err, Par2Error::InvalidPacketLength { length: 65 }));
229 }
230
231 #[test]
232 fn reject_length_too_small() {
233 let mut data = make_header(TYPE_MAIN, &[]);
234 let bad_len = 60u64;
236 data[8..16].copy_from_slice(&bad_len.to_le_bytes());
237 let err = PacketHeader::parse(&data, 0).unwrap_err();
238 assert!(matches!(err, Par2Error::PacketTooShort { .. }));
239 }
240
241 #[test]
242 fn body_length_calculation() {
243 let body = [0u8; 32];
244 let data = make_header(TYPE_MAIN, &body);
245 let header = PacketHeader::parse(&data, 0).unwrap();
246 assert_eq!(header.body_length(), 32);
247 }
248
249 #[test]
250 fn accept_large_packet_length() {
251 let mut data = make_header(TYPE_MAIN, &[]);
252 let huge_len = (2u64 << 30) & !3; data[8..16].copy_from_slice(&huge_len.to_le_bytes());
254 let header = PacketHeader::parse(&data, 0).unwrap();
255 assert_eq!(header.length, huge_len);
256 }
257}