1use crate::error::{Par2Error, Result};
2use crate::types::{FileId, MAX_FILES_PER_SET, RecoverySetId};
3
4#[derive(Debug, Clone)]
9pub struct MainPacket {
10 pub recovery_set_id: RecoverySetId,
12 pub slice_size: u64,
14 pub recovery_file_ids: Vec<FileId>,
16 pub non_recovery_file_ids: Vec<FileId>,
18}
19
20impl MainPacket {
21 pub fn parse(body: &[u8], recovery_set_id: RecoverySetId) -> Result<Self> {
23 if body.len() < 12 {
25 return Err(Par2Error::InvalidMainPacket {
26 reason: format!("body too short: {} bytes, need at least 12", body.len()),
27 });
28 }
29
30 let slice_size = u64::from_le_bytes(body[0..8].try_into().unwrap());
31 if slice_size == 0 {
32 return Err(Par2Error::InvalidMainPacket {
33 reason: "slice_size is 0".to_string(),
34 });
35 }
36 if slice_size % 4 != 0 {
37 return Err(Par2Error::InvalidMainPacket {
38 reason: format!("slice_size {slice_size} is not a multiple of 4"),
39 });
40 }
41
42 let recovery_file_count = u32::from_le_bytes(body[8..12].try_into().unwrap()) as usize;
43
44 let file_id_area = &body[12..];
45 if !file_id_area.len().is_multiple_of(16) {
46 return Err(Par2Error::InvalidMainPacket {
47 reason: format!(
48 "file ID area length {} is not a multiple of 16",
49 file_id_area.len()
50 ),
51 });
52 }
53
54 let total_ids = file_id_area.len() / 16;
55 if total_ids > MAX_FILES_PER_SET {
56 return Err(Par2Error::InvalidMainPacket {
57 reason: format!("file ID count {total_ids} exceeds maximum {MAX_FILES_PER_SET}"),
58 });
59 }
60 if recovery_file_count > total_ids {
61 return Err(Par2Error::InvalidMainPacket {
62 reason: format!(
63 "recovery_file_count ({recovery_file_count}) exceeds total file IDs ({total_ids})"
64 ),
65 });
66 }
67
68 let mut recovery_file_ids = Vec::with_capacity(recovery_file_count);
69 for i in 0..recovery_file_count {
70 let offset = i * 16;
71 let id: [u8; 16] = file_id_area[offset..offset + 16].try_into().unwrap();
72 recovery_file_ids.push(FileId::from_bytes(id));
73 }
74
75 let non_recovery_count = total_ids - recovery_file_count;
76 let mut non_recovery_file_ids = Vec::with_capacity(non_recovery_count);
77 for i in recovery_file_count..total_ids {
78 let offset = i * 16;
79 let id: [u8; 16] = file_id_area[offset..offset + 16].try_into().unwrap();
80 non_recovery_file_ids.push(FileId::from_bytes(id));
81 }
82
83 Ok(MainPacket {
84 recovery_set_id,
85 slice_size,
86 recovery_file_ids,
87 non_recovery_file_ids,
88 })
89 }
90}
91
92#[cfg(test)]
93mod tests {
94 use super::*;
95
96 fn make_main_body(
97 slice_size: u64,
98 recovery_ids: &[[u8; 16]],
99 non_recovery_ids: &[[u8; 16]],
100 ) -> Vec<u8> {
101 let mut body = Vec::new();
102 body.extend_from_slice(&slice_size.to_le_bytes());
103 body.extend_from_slice(&(recovery_ids.len() as u32).to_le_bytes());
104 for id in recovery_ids {
105 body.extend_from_slice(id);
106 }
107 for id in non_recovery_ids {
108 body.extend_from_slice(id);
109 }
110 body
111 }
112
113 #[test]
114 fn parse_valid_main() {
115 let file_a = [0x01u8; 16];
116 let file_b = [0x02u8; 16];
117 let file_c = [0x03u8; 16];
118 let body = make_main_body(65536, &[file_a, file_b], &[file_c]);
119 let rsid = RecoverySetId::from_bytes([0xFF; 16]);
120
121 let pkt = MainPacket::parse(&body, rsid).unwrap();
122 assert_eq!(pkt.slice_size, 65536);
123 assert_eq!(pkt.recovery_file_ids.len(), 2);
124 assert_eq!(pkt.non_recovery_file_ids.len(), 1);
125 assert_eq!(*pkt.recovery_file_ids[0].as_bytes(), file_a);
126 assert_eq!(*pkt.recovery_file_ids[1].as_bytes(), file_b);
127 assert_eq!(*pkt.non_recovery_file_ids[0].as_bytes(), file_c);
128 }
129
130 #[test]
131 fn parse_main_no_non_recovery() {
132 let file_a = [0x01u8; 16];
133 let body = make_main_body(1024, &[file_a], &[]);
134 let pkt = MainPacket::parse(&body, RecoverySetId::from_bytes([0; 16])).unwrap();
135 assert_eq!(pkt.recovery_file_ids.len(), 1);
136 assert_eq!(pkt.non_recovery_file_ids.len(), 0);
137 }
138
139 #[test]
140 fn parse_main_no_files() {
141 let body = make_main_body(4096, &[], &[]);
142 let pkt = MainPacket::parse(&body, RecoverySetId::from_bytes([0; 16])).unwrap();
143 assert_eq!(pkt.recovery_file_ids.len(), 0);
144 assert_eq!(pkt.non_recovery_file_ids.len(), 0);
145 }
146
147 #[test]
148 fn reject_zero_slice_size() {
149 let body = make_main_body(0, &[], &[]);
150 let err = MainPacket::parse(&body, RecoverySetId::from_bytes([0; 16])).unwrap_err();
151 assert!(matches!(err, Par2Error::InvalidMainPacket { .. }));
152 }
153
154 #[test]
155 fn reject_non_aligned_slice_size() {
156 let body = make_main_body(7, &[], &[]);
157 let err = MainPacket::parse(&body, RecoverySetId::from_bytes([0; 16])).unwrap_err();
158 assert!(matches!(err, Par2Error::InvalidMainPacket { .. }));
159 }
160
161 #[test]
162 fn reject_body_too_short() {
163 let body = [0u8; 8];
164 let err = MainPacket::parse(&body, RecoverySetId::from_bytes([0; 16])).unwrap_err();
165 assert!(matches!(err, Par2Error::InvalidMainPacket { .. }));
166 }
167
168 #[test]
169 fn reject_count_exceeds_ids() {
170 let mut body = Vec::new();
172 body.extend_from_slice(&1024u64.to_le_bytes());
173 body.extend_from_slice(&5u32.to_le_bytes());
174 body.extend_from_slice(&[0u8; 32]); let err = MainPacket::parse(&body, RecoverySetId::from_bytes([0; 16])).unwrap_err();
176 assert!(matches!(err, Par2Error::InvalidMainPacket { .. }));
177 }
178}