1use alloc::collections::BTreeMap;
6use alloc::vec;
7use alloc::vec::Vec;
8
9use super::messages::{Dii, DownloadDataBlock};
10
11#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash)]
13#[cfg_attr(feature = "serde", derive(serde::Serialize))]
14pub struct ModuleKey {
15 pub download_id: u32,
17 pub module_id: u16,
19 pub module_version: u8,
21}
22
23#[derive(Debug, Clone, PartialEq, Eq)]
25#[cfg_attr(feature = "serde", derive(serde::Serialize))]
26pub struct Module {
27 pub key: ModuleKey,
29 pub data: Vec<u8>,
31}
32
33type SlotKey = (u32, u16); struct Slot {
42 module_version: u8,
43 block_size: usize,
44 data: Vec<u8>,
45 received: Vec<u64>,
46 n_blocks: usize,
47 remaining: usize,
48}
49
50impl Slot {
51 fn is_received(&self, n: usize) -> bool {
52 (self.received[n >> 6] >> (n & 63)) & 1 != 0
53 }
54 fn mark_received(&mut self, n: usize) {
55 self.received[n >> 6] |= 1 << (n & 63);
56 }
57}
58
59pub const DEFAULT_MAX_MODULE_SIZE: u32 = 64 * 1024 * 1024;
61pub const DEFAULT_MAX_TOTAL_BYTES: usize = 256 * 1024 * 1024;
65pub const DEFAULT_MAX_SLOTS: usize = 16 * 1024;
72
73pub struct ModuleReassembler {
97 slots: BTreeMap<SlotKey, Slot>,
98 max_module_size: u32,
99 max_total_bytes: usize,
100 max_slots: usize,
101 total_bytes: usize,
102}
103
104impl Default for ModuleReassembler {
105 fn default() -> Self {
106 Self::new()
107 }
108}
109
110impl ModuleReassembler {
111 #[must_use]
114 pub fn new() -> Self {
115 Self::with_limits(DEFAULT_MAX_MODULE_SIZE, DEFAULT_MAX_TOTAL_BYTES)
116 }
117
118 #[must_use]
121 pub fn with_max_module_size(max_module_size: u32) -> Self {
122 Self::with_limits(max_module_size, DEFAULT_MAX_TOTAL_BYTES)
123 }
124
125 #[must_use]
128 pub fn with_limits(max_module_size: u32, max_total_bytes: usize) -> Self {
129 Self {
130 slots: BTreeMap::new(),
131 max_module_size,
132 max_total_bytes,
133 max_slots: DEFAULT_MAX_SLOTS,
134 total_bytes: 0,
135 }
136 }
137
138 #[must_use]
143 pub fn with_max_slots(mut self, max_slots: usize) -> Self {
144 self.max_slots = max_slots;
145 self
146 }
147
148 pub fn note_dii(&mut self, dii: &Dii<'_>) {
155 for m in &dii.modules {
156 if m.module_size == 0 || m.module_size > self.max_module_size || dii.block_size == 0 {
157 continue;
158 }
159 let key: SlotKey = (dii.download_id, m.module_id);
160 if let Some(existing) = self.slots.get(&key) {
161 if existing.module_version == m.module_version {
162 continue; }
164 let s = self.slots.remove(&key).expect("just found");
166 self.total_bytes -= s.data.len();
167 }
168 let size = m.module_size as usize;
169 if self.total_bytes + size > self.max_total_bytes || self.slots.len() >= self.max_slots
170 {
171 continue; }
173 let block_size = dii.block_size as usize;
174 let n_blocks = size.div_ceil(block_size).max(1);
175 self.total_bytes += size;
176 self.slots.insert(
177 key,
178 Slot {
179 module_version: m.module_version,
180 block_size,
181 data: vec![0u8; size],
182 received: vec![0u64; n_blocks.div_ceil(64)],
183 n_blocks,
184 remaining: n_blocks,
185 },
186 );
187 }
188 }
189
190 pub fn feed_ddb(&mut self, ddb: &DownloadDataBlock<'_>) -> Option<Module> {
195 let key: SlotKey = (ddb.download_id, ddb.module_id);
196 let slot = self.slots.get_mut(&key)?;
197 let n = ddb.block_number as usize;
198 if slot.module_version != ddb.module_version || n >= slot.n_blocks || slot.is_received(n) {
199 return None;
200 }
201 let offset = n * slot.block_size;
202 let expected = (slot.data.len() - offset).min(slot.block_size);
203 if ddb.block_data.len() != expected {
204 return None; }
206 slot.data[offset..offset + expected].copy_from_slice(ddb.block_data);
207 slot.mark_received(n);
208 slot.remaining -= 1;
209 if slot.remaining > 0 {
210 return None;
211 }
212 let slot = self.slots.remove(&key).expect("slot exists");
213 self.total_bytes -= slot.data.len();
214 Some(Module {
215 key: ModuleKey {
216 download_id: ddb.download_id,
217 module_id: ddb.module_id,
218 module_version: slot.module_version,
219 },
220 data: slot.data,
221 })
222 }
223
224 #[must_use]
226 pub fn pending(&self) -> usize {
227 self.slots.len()
228 }
229
230 #[must_use]
239 pub fn pending_bytes(&self) -> usize {
240 self.total_bytes
241 }
242}
243
244#[cfg(test)]
245mod tests {
246 use super::super::messages::DiiModule;
247 use super::*;
248 use crate::compatibility::CompatibilityDescriptor;
249
250 fn dii(download_id: u32, block_size: u16, modules: Vec<DiiModule<'static>>) -> Dii<'static> {
251 Dii {
252 transaction_id: 0x8000_0002,
253 adaptation: &[],
254 download_id,
255 block_size,
256 window_size: 0,
257 ack_period: 0,
258 t_c_download_window: 0,
259 t_c_download_scenario: 0,
260 compatibility_descriptor: CompatibilityDescriptor {
261 descriptors: vec![],
262 },
263 modules,
264 private_data: &[],
265 }
266 }
267
268 fn module(module_id: u16, module_size: u32, module_version: u8) -> DiiModule<'static> {
269 DiiModule {
270 module_id,
271 module_size,
272 module_version,
273 module_info: &[],
274 }
275 }
276
277 fn ddb(
278 download_id: u32,
279 module_id: u16,
280 module_version: u8,
281 block_number: u16,
282 block_data: &[u8],
283 ) -> DownloadDataBlock<'_> {
284 DownloadDataBlock {
285 download_id,
286 adaptation: &[],
287 module_id,
288 module_version,
289 block_number,
290 block_data,
291 }
292 }
293
294 #[test]
295 fn two_block_module_completes() {
296 let mut r = ModuleReassembler::new();
297 r.note_dii(&dii(1, 4, vec![module(7, 6, 0)]));
298 assert!(r.feed_ddb(&ddb(1, 7, 0, 0, &[1, 2, 3, 4])).is_none());
299 let m = r.feed_ddb(&ddb(1, 7, 0, 1, &[5, 6])).expect("complete");
300 assert_eq!(m.key.module_id, 7);
301 assert_eq!(m.data, vec![1, 2, 3, 4, 5, 6]);
302 assert_eq!(r.pending(), 0);
303 }
304
305 #[test]
306 fn out_of_order_blocks_complete() {
307 let mut r = ModuleReassembler::new();
308 r.note_dii(&dii(1, 2, vec![module(1, 4, 0)]));
309 assert!(r.feed_ddb(&ddb(1, 1, 0, 1, &[3, 4])).is_none());
310 let m = r.feed_ddb(&ddb(1, 1, 0, 0, &[1, 2])).expect("complete");
311 assert_eq!(m.data, vec![1, 2, 3, 4]);
312 }
313
314 #[test]
315 fn ddb_before_dii_is_ignored() {
316 let mut r = ModuleReassembler::new();
317 assert!(r.feed_ddb(&ddb(1, 1, 0, 0, &[1, 2])).is_none());
318 r.note_dii(&dii(1, 2, vec![module(1, 2, 0)]));
320 assert!(r.feed_ddb(&ddb(1, 1, 0, 0, &[1, 2])).is_some());
321 }
322
323 #[test]
324 fn version_mismatch_ignored_and_new_version_restarts() {
325 let mut r = ModuleReassembler::new();
326 r.note_dii(&dii(1, 2, vec![module(1, 4, 0)]));
327 assert!(r.feed_ddb(&ddb(1, 1, 0, 0, &[1, 2])).is_none());
328 assert!(r.feed_ddb(&ddb(1, 1, 3, 1, &[9, 9])).is_none());
330 r.note_dii(&dii(1, 2, vec![module(1, 4, 3)]));
332 assert_eq!(r.pending(), 1);
333 assert!(r.feed_ddb(&ddb(1, 1, 3, 0, &[5, 6])).is_none());
334 let m = r.feed_ddb(&ddb(1, 1, 3, 1, &[7, 8])).expect("complete");
335 assert_eq!(m.key.module_version, 3);
336 assert_eq!(m.data, vec![5, 6, 7, 8]);
337 }
338
339 #[test]
340 fn repeated_dii_keeps_progress() {
341 let mut r = ModuleReassembler::new();
342 let d = dii(1, 2, vec![module(1, 4, 0)]);
343 r.note_dii(&d);
344 assert!(r.feed_ddb(&ddb(1, 1, 0, 0, &[1, 2])).is_none());
345 r.note_dii(&d); let m = r.feed_ddb(&ddb(1, 1, 0, 1, &[3, 4])).expect("complete");
347 assert_eq!(m.data, vec![1, 2, 3, 4]);
348 }
349
350 #[test]
351 fn duplicate_and_out_of_range_blocks_ignored() {
352 let mut r = ModuleReassembler::new();
353 r.note_dii(&dii(1, 2, vec![module(1, 4, 0)]));
354 assert!(r.feed_ddb(&ddb(1, 1, 0, 0, &[1, 2])).is_none());
355 assert!(r.feed_ddb(&ddb(1, 1, 0, 0, &[1, 2])).is_none()); assert!(r.feed_ddb(&ddb(1, 1, 0, 9, &[9, 9])).is_none()); assert_eq!(r.pending(), 1);
358 }
359
360 #[test]
361 fn wrong_block_length_ignored() {
362 let mut r = ModuleReassembler::new();
363 r.note_dii(&dii(1, 4, vec![module(1, 6, 0)]));
364 assert!(r.feed_ddb(&ddb(1, 1, 0, 0, &[1, 2, 3])).is_none());
366 assert!(r.feed_ddb(&ddb(1, 1, 0, 1, &[5, 6, 7])).is_none());
367 assert_eq!(r.pending(), 1);
368 assert!(r.feed_ddb(&ddb(1, 1, 0, 0, &[1, 2, 3, 4])).is_none());
369 assert!(r.feed_ddb(&ddb(1, 1, 0, 1, &[5, 6])).is_some());
370 }
371
372 #[test]
373 fn oversize_module_skipped() {
374 let mut r = ModuleReassembler::with_max_module_size(8);
375 r.note_dii(&dii(1, 4, vec![module(1, 9, 0), module(2, 8, 0)]));
376 assert_eq!(r.pending(), 1); }
378
379 #[test]
380 fn zero_block_size_skipped() {
381 let mut r = ModuleReassembler::new();
382 r.note_dii(&dii(1, 0, vec![module(1, 4, 0)]));
383 assert_eq!(r.pending(), 0);
384 }
385
386 #[test]
390 fn zero_size_module_announcement_ignored() {
391 let mut r = ModuleReassembler::new();
392 r.note_dii(&dii(1, 4, vec![module(1, 0, 0)]));
393 assert_eq!(r.pending(), 0);
394 assert!(r.feed_ddb(&ddb(1, 1, 0, 0, &[])).is_none());
395 }
396
397 #[test]
402 fn slot_count_capped() {
403 let mut r = ModuleReassembler::new().with_max_slots(3);
404 let modules: Vec<_> = (0..5).map(|i| module(i, 1, 0)).collect();
405 r.note_dii(&dii(1, 4, modules));
406 assert_eq!(r.pending(), 3); assert!(r.feed_ddb(&ddb(1, 0, 0, 0, &[0xAA])).is_some());
409 assert_eq!(r.pending(), 2);
410 r.note_dii(&dii(1, 4, vec![module(4, 1, 0)]));
412 assert_eq!(r.pending(), 3);
413 }
414
415 #[test]
419 fn aggregate_budget_bounds_total_memory() {
420 let mut r = ModuleReassembler::with_limits(8, 10);
421 r.note_dii(&dii(1, 4, vec![module(1, 8, 0)]));
422 assert_eq!(r.pending_bytes(), 8);
423 r.note_dii(&dii(2, 4, vec![module(1, 8, 0)]));
426 assert_eq!(r.pending(), 1);
427 assert_eq!(r.pending_bytes(), 8);
428 assert!(r.feed_ddb(&ddb(1, 1, 0, 0, &[0; 4])).is_none());
430 assert!(r.feed_ddb(&ddb(1, 1, 0, 1, &[0; 4])).is_some());
431 assert_eq!(r.pending_bytes(), 0);
432 r.note_dii(&dii(2, 4, vec![module(1, 8, 0)]));
434 assert_eq!(r.pending(), 1);
435 assert_eq!(r.pending_bytes(), 8);
436 }
437
438 #[test]
440 fn version_replacement_releases_budget() {
441 let mut r = ModuleReassembler::with_limits(8, 8);
442 r.note_dii(&dii(1, 4, vec![module(1, 8, 0)]));
443 assert_eq!(r.pending_bytes(), 8);
444 r.note_dii(&dii(1, 4, vec![module(1, 8, 1)]));
445 assert_eq!(r.pending(), 1);
446 assert_eq!(r.pending_bytes(), 8); }
448
449 #[test]
452 fn block_size_one_uses_bitset() {
453 let mut r = ModuleReassembler::new();
454 r.note_dii(&dii(1, 1, vec![module(1, 130, 0)]));
455 for i in 0..129u16 {
456 assert!(r.feed_ddb(&ddb(1, 1, 0, i, &[i as u8])).is_none());
457 assert!(r.feed_ddb(&ddb(1, 1, 0, i, &[i as u8])).is_none());
459 }
460 let m = r.feed_ddb(&ddb(1, 1, 0, 129, &[0x81])).expect("complete");
461 assert_eq!(m.data.len(), 130);
462 assert_eq!(m.data[129], 0x81);
463 }
464}