1use alloc::vec::Vec;
2
3use super::types::{AdvFlags, ResourceError};
4use crate::constants::{RESOURCE_HASHMAP_MAX_LEN, RESOURCE_MAPHASH_LEN};
5use crate::msgpack;
6
7#[derive(Debug, Clone)]
9pub struct ResourceAdvertisement {
10 pub transfer_size: u64,
12 pub data_size: u64,
14 pub num_parts: u64,
16 pub resource_hash: Vec<u8>,
18 pub random_hash: Vec<u8>,
20 pub original_hash: Vec<u8>,
22 pub hashmap: Vec<u8>,
24 pub flags: AdvFlags,
26 pub segment_index: u64,
28 pub total_segments: u64,
30 pub request_id: Option<Vec<u8>>,
32}
33
34#[derive(Clone, Copy)]
36pub(crate) struct AdvertisementFields<'a> {
37 pub transfer_size: u64,
38 pub data_size: u64,
39 pub num_parts: u64,
40 pub resource_hash: &'a [u8],
41 pub random_hash: &'a [u8],
42 pub original_hash: &'a [u8],
43 pub hashmap: &'a [u8],
44 pub flags: AdvFlags,
45 pub segment_index: u64,
46 pub total_segments: u64,
47 pub request_id: Option<&'a [u8]>,
48}
49
50impl AdvertisementFields<'_> {
51 pub fn pack(&self, segment: usize) -> Vec<u8> {
53 let hashmap_start = segment * RESOURCE_HASHMAP_MAX_LEN * RESOURCE_MAPHASH_LEN;
54 let max_end = (segment + 1) * RESOURCE_HASHMAP_MAX_LEN * RESOURCE_MAPHASH_LEN;
55 let hashmap_end = core::cmp::min(max_end, self.hashmap.len());
56 let hashmap_segment = if hashmap_start < self.hashmap.len() {
57 &self.hashmap[hashmap_start..hashmap_end]
58 } else {
59 &[]
60 };
61
62 let bins = self.resource_hash.len()
66 + self.random_hash.len()
67 + self.original_hash.len()
68 + self.request_id.map_or(0, <[u8]>::len)
69 + hashmap_segment.len();
70 let mut out = Vec::with_capacity(1 + 11 * 2 + 6 * 9 + 5 * 5 + bins);
71 out.push(0x80 | 11);
72 let uint = |out: &mut Vec<u8>, key: &str, value: u64| {
73 msgpack::pack_str(key, out);
74 msgpack::pack_uint(value, out);
75 };
76 uint(&mut out, "t", self.transfer_size);
77 uint(&mut out, "d", self.data_size);
78 uint(&mut out, "n", self.num_parts);
79 for (key, value) in [
80 ("h", self.resource_hash),
81 ("r", self.random_hash),
82 ("o", self.original_hash),
83 ] {
84 msgpack::pack_str(key, &mut out);
85 msgpack::pack_bin(value, &mut out);
86 }
87 uint(&mut out, "i", self.segment_index);
88 uint(&mut out, "l", self.total_segments);
89 msgpack::pack_str("q", &mut out);
90 match self.request_id {
91 Some(id) => msgpack::pack_bin(id, &mut out),
92 None => out.push(0xc0),
93 }
94 uint(&mut out, "f", self.flags.to_byte() as u64);
95 msgpack::pack_str("m", &mut out);
96 msgpack::pack_bin(hashmap_segment, &mut out);
97 out
98 }
99
100 pub fn pack_for_mdu(&self, segment: usize, mdu: usize) -> Vec<u8> {
102 let packed = self.pack(segment);
103 if packed.len() <= mdu {
104 return packed;
105 }
106 AdvertisementFields {
107 hashmap: &[],
108 ..*self
109 }
110 .pack(segment)
111 }
112}
113
114impl ResourceAdvertisement {
115 pub(crate) fn fields(&self) -> AdvertisementFields<'_> {
116 AdvertisementFields {
117 transfer_size: self.transfer_size,
118 data_size: self.data_size,
119 num_parts: self.num_parts,
120 resource_hash: &self.resource_hash,
121 random_hash: &self.random_hash,
122 original_hash: &self.original_hash,
123 hashmap: &self.hashmap,
124 flags: self.flags,
125 segment_index: self.segment_index,
126 total_segments: self.total_segments,
127 request_id: self.request_id.as_deref(),
128 }
129 }
130
131 pub fn pack(&self, segment: usize) -> Vec<u8> {
134 self.fields().pack(segment)
135 }
136
137 pub fn pack_for_mdu(&self, segment: usize, mdu: usize) -> Vec<u8> {
143 self.fields().pack_for_mdu(segment, mdu)
144 }
145
146 pub fn unpack(data: &[u8]) -> Result<Self, ResourceError> {
148 let invalid = ResourceError::InvalidAdvertisement;
151 let value = msgpack::unpack_exact_ref(data).map_err(|_| invalid.clone())?;
152 let mut fields: [Option<msgpack::ValueRef<'_>>; 11] = [None; 11];
153 for (key, field) in value.map_entries().ok_or(invalid.clone())? {
154 let Some(key) = key.as_str() else { continue };
155 let Some(index) = ["t", "d", "n", "h", "r", "o", "i", "l", "q", "f", "m"]
156 .iter()
157 .position(|k| *k == key)
158 else {
159 continue;
160 };
161 fields[index].get_or_insert(field);
163 }
164 let [t, d, n, h, r, o, i, l, q, f, m] = fields;
165 let uint =
166 |v: Option<msgpack::ValueRef<'_>>| v.and_then(|v| v.as_uint()).ok_or(invalid.clone());
167 let bin = |v: Option<msgpack::ValueRef<'_>>| {
168 v.and_then(|v| v.as_bin())
169 .map(<[u8]>::to_vec)
170 .ok_or(invalid.clone())
171 };
172 let (t, d, n) = (uint(t)?, uint(d)?, uint(n)?);
173 let (h, r, o, m) = (bin(h)?, bin(r)?, bin(o)?, bin(m)?);
174 let f = uint(f)? as u8;
175 let (i, l) = (uint(i)?, uint(l)?);
176 let q = q.ok_or(invalid.clone())?;
177 let request_id = if q.is_nil() {
178 None
179 } else {
180 Some(q.as_bin().ok_or(invalid.clone())?.to_vec())
181 };
182
183 if t > (crate::constants::RESOURCE_MAX_EFFICIENT_SIZE * 3) as u64 {
184 return Err(ResourceError::InvalidAdvertisement);
185 }
186
187 Ok(ResourceAdvertisement {
188 transfer_size: t,
189 data_size: d,
190 num_parts: n,
191 resource_hash: h,
192 random_hash: r,
193 original_hash: o,
194 hashmap: m,
195 flags: AdvFlags::from_byte(f),
196 segment_index: i,
197 total_segments: l,
198 request_id,
199 })
200 }
201
202 pub fn is_request(&self) -> bool {
204 self.request_id.is_some() && self.flags.is_request
205 }
206
207 pub fn is_response(&self) -> bool {
209 self.request_id.is_some() && self.flags.is_response
210 }
211
212 pub fn app_flag_set(&self) -> bool {
214 self.flags.app_flag
215 }
216
217 pub fn hashmap_segments(&self) -> usize {
219 let total_hashes = self.num_parts as usize;
220 if total_hashes == 0 {
221 return 1;
222 }
223 total_hashes.div_ceil(RESOURCE_HASHMAP_MAX_LEN)
224 }
225}
226
227#[cfg(test)]
228mod tests {
229 use super::*;
230 use crate::msgpack::Value;
231 use alloc::vec;
232
233 fn pack_reference(adv: &ResourceAdvertisement, segment: usize) -> Vec<u8> {
235 let hashmap_start = segment * RESOURCE_HASHMAP_MAX_LEN * RESOURCE_MAPHASH_LEN;
236 let max_end = (segment + 1) * RESOURCE_HASHMAP_MAX_LEN * RESOURCE_MAPHASH_LEN;
237 let hashmap_end = core::cmp::min(max_end, adv.hashmap.len());
238 let hashmap_segment = if hashmap_start < adv.hashmap.len() {
239 &adv.hashmap[hashmap_start..hashmap_end]
240 } else {
241 &[]
242 };
243 let q_value = match &adv.request_id {
244 Some(id) => Value::Bin(id.clone()),
245 None => Value::Nil,
246 };
247 let entries: Vec<(&str, Value)> = vec![
248 ("t", Value::UInt(adv.transfer_size)),
249 ("d", Value::UInt(adv.data_size)),
250 ("n", Value::UInt(adv.num_parts)),
251 ("h", Value::Bin(adv.resource_hash.clone())),
252 ("r", Value::Bin(adv.random_hash.clone())),
253 ("o", Value::Bin(adv.original_hash.clone())),
254 ("i", Value::UInt(adv.segment_index)),
255 ("l", Value::UInt(adv.total_segments)),
256 ("q", q_value),
257 ("f", Value::UInt(adv.flags.to_byte() as u64)),
258 ("m", Value::Bin(hashmap_segment.to_vec())),
259 ];
260 msgpack::pack_str_map(&entries)
261 }
262
263 fn unpack_reference(data: &[u8]) -> Option<ResourceAdvertisement> {
265 let value = msgpack::unpack_exact(data).ok()?;
266 let uint = |k| value.map_get(k).and_then(|v| v.as_uint());
267 let bin = |k| {
268 value
269 .map_get(k)
270 .and_then(|v| v.as_bin())
271 .map(<[u8]>::to_vec)
272 };
273 let (t, d, n) = (uint("t")?, uint("d")?, uint("n")?);
274 let (h, r, o, m) = (bin("h")?, bin("r")?, bin("o")?, bin("m")?);
275 let f = uint("f")? as u8;
276 let (i, l) = (uint("i")?, uint("l")?);
277 let q = value.map_get("q")?;
278 let request_id = if q.is_nil() {
279 None
280 } else {
281 Some(q.as_bin()?.to_vec())
282 };
283 if t > (crate::constants::RESOURCE_MAX_EFFICIENT_SIZE * 3) as u64 {
284 return None;
285 }
286 Some(ResourceAdvertisement {
287 transfer_size: t,
288 data_size: d,
289 num_parts: n,
290 resource_hash: h,
291 random_hash: r,
292 original_hash: o,
293 hashmap: m,
294 flags: AdvFlags::from_byte(f),
295 segment_index: i,
296 total_segments: l,
297 request_id,
298 })
299 }
300
301 struct XorShift(u64);
302 impl XorShift {
303 fn next(&mut self) -> u64 {
304 self.0 ^= self.0 << 13;
305 self.0 ^= self.0 >> 7;
306 self.0 ^= self.0 << 17;
307 self.0
308 }
309 fn bytes(&mut self, len: usize) -> Vec<u8> {
310 (0..len).map(|_| self.next() as u8).collect()
311 }
312 }
313
314 fn random_adv(rng: &mut XorShift) -> ResourceAdvertisement {
315 let widths = [
317 0u64,
318 1,
319 127,
320 128,
321 255,
322 256,
323 65_535,
324 65_536,
325 u32::MAX as u64,
326 u64::MAX,
327 ];
328 let pick = |rng: &mut XorShift| {
329 let w = widths[(rng.next() % widths.len() as u64) as usize];
330 w.saturating_sub(rng.next() % 3)
331 };
332 let hashmap_len = [0usize, 4, 300, 74 * 4 * 2][(rng.next() % 4) as usize];
333 let original_len = (rng.next() % 40) as usize;
334 ResourceAdvertisement {
335 transfer_size: pick(rng) % 1_000_000,
336 data_size: pick(rng),
337 num_parts: pick(rng),
338 resource_hash: rng.bytes(32),
339 random_hash: rng.bytes(4),
340 original_hash: rng.bytes(original_len),
341 hashmap: rng.bytes(hashmap_len),
342 flags: AdvFlags::from_byte(rng.next() as u8),
343 segment_index: pick(rng),
344 total_segments: pick(rng),
345 request_id: rng.next().is_multiple_of(2).then(|| rng.bytes(16)),
346 }
347 }
348
349 fn same(a: &Option<ResourceAdvertisement>, b: &Option<ResourceAdvertisement>) -> bool {
350 match (a, b) {
351 (None, None) => true,
352 (Some(a), Some(b)) => {
353 a.transfer_size == b.transfer_size
354 && a.data_size == b.data_size
355 && a.num_parts == b.num_parts
356 && a.resource_hash == b.resource_hash
357 && a.random_hash == b.random_hash
358 && a.original_hash == b.original_hash
359 && a.hashmap == b.hashmap
360 && a.flags.to_byte() == b.flags.to_byte()
361 && a.segment_index == b.segment_index
362 && a.total_segments == b.total_segments
363 && a.request_id == b.request_id
364 }
365 _ => false,
366 }
367 }
368
369 #[test]
370 fn direct_pack_matches_the_value_encoder_byte_for_byte() {
371 let mut rng = XorShift(0x5EED_0001);
372 for _ in 0..2_000 {
373 let adv = random_adv(&mut rng);
374 for segment in 0..3 {
375 assert_eq!(adv.pack(segment), pack_reference(&adv, segment));
376 }
377 }
378 }
379
380 #[test]
381 fn in_place_unpack_matches_the_value_decoder_on_valid_and_corrupt_input() {
382 let mut rng = XorShift(0x5EED_0002);
383 let mut cases = 0;
384 for round in 0..400u32 {
385 let adv = random_adv(&mut rng);
386 let packed = adv.pack(0);
387 let check = |data: &[u8]| {
388 let new = ResourceAdvertisement::unpack(data).ok();
389 assert!(
390 same(&new, &unpack_reference(data)),
391 "diverged on {data:02x?}"
392 );
393 };
394 check(&packed);
395 for len in 0..packed.len() {
398 check(&packed[..len]);
399 }
400 let mut extended = packed.clone();
401 extended.push(0xc0);
402 check(&extended);
403 if round.is_multiple_of(8) {
404 for at in 0..packed.len() {
405 for value in [
406 0x00, 0x7f, 0x80, 0xa1, 0xc0, 0xc1, 0xc4, 0xcc, 0xd0, 0xd9, 0xdc, 0xde,
407 0xff,
408 ] {
409 let mut mutated = packed.clone();
410 mutated[at] = value;
411 check(&mutated);
412 cases += 1;
413 }
414 }
415 }
416 for _ in 0..20 {
417 let mut mutated = packed.clone();
418 for _ in 0..(1 + rng.next() % 4) {
419 let at = (rng.next() as usize) % mutated.len();
420 mutated[at] = rng.next() as u8;
421 }
422 check(&mutated);
423 cases += 1;
424 }
425 }
426 let adv = random_adv(&mut rng);
428 let mut packed = adv.pack(0);
429 packed[0] = 0x80 | 12;
430 msgpack::pack_str("t", &mut packed);
431 msgpack::pack_uint(5, &mut packed);
432 assert!(same(
433 &ResourceAdvertisement::unpack(&packed).ok(),
434 &unpack_reference(&packed)
435 ));
436 assert!(cases > 10_000);
437 }
438
439 fn make_adv(flags: AdvFlags) -> ResourceAdvertisement {
440 ResourceAdvertisement {
441 transfer_size: 1000,
442 data_size: 950,
443 num_parts: 3,
444 resource_hash: vec![0x11; 32],
445 random_hash: vec![0xAA, 0xBB, 0xCC, 0xDD],
446 original_hash: vec![0x22; 32],
447 hashmap: vec![
448 0x01, 0x02, 0x03, 0x04, 0x05, 0x06, 0x07, 0x08, 0x09, 0x0A, 0x0B, 0x0C,
449 ],
450 flags,
451 segment_index: 1,
452 total_segments: 1,
453 request_id: None,
454 }
455 }
456
457 #[test]
458 fn test_pack_unpack_roundtrip() {
459 let flags = AdvFlags {
460 encrypted: true,
461 compressed: false,
462 split: false,
463 is_request: false,
464 is_response: false,
465 has_metadata: false,
466 app_flag: false,
467 };
468 let adv = make_adv(flags);
469 let packed = adv.pack(0);
470 let unpacked = ResourceAdvertisement::unpack(&packed).unwrap();
471
472 assert_eq!(unpacked.transfer_size, 1000);
473 assert_eq!(unpacked.data_size, 950);
474 assert_eq!(unpacked.num_parts, 3);
475 assert_eq!(unpacked.resource_hash, vec![0x11; 32]);
476 assert_eq!(unpacked.random_hash, vec![0xAA, 0xBB, 0xCC, 0xDD]);
477 assert_eq!(unpacked.original_hash, vec![0x22; 32]);
478 assert_eq!(unpacked.flags, flags);
479 assert_eq!(unpacked.segment_index, 1);
480 assert_eq!(unpacked.total_segments, 1);
481 assert!(unpacked.request_id.is_none());
482 }
483
484 #[test]
485 fn test_flags_encrypted_compressed() {
486 let flags = AdvFlags {
487 encrypted: true,
488 compressed: true,
489 split: false,
490 is_request: false,
491 is_response: false,
492 has_metadata: false,
493 app_flag: false,
494 };
495 let adv = make_adv(flags);
496 let packed = adv.pack(0);
497 let unpacked = ResourceAdvertisement::unpack(&packed).unwrap();
498 assert!(unpacked.flags.encrypted);
499 assert!(unpacked.flags.compressed);
500 assert!(!unpacked.flags.split);
501 }
502
503 #[test]
504 fn test_flags_with_metadata() {
505 let flags = AdvFlags {
506 encrypted: true,
507 compressed: false,
508 split: false,
509 is_request: false,
510 is_response: false,
511 has_metadata: true,
512 app_flag: false,
513 };
514 let adv = make_adv(flags);
515 let packed = adv.pack(0);
516 let unpacked = ResourceAdvertisement::unpack(&packed).unwrap();
517 assert!(unpacked.flags.has_metadata);
518 }
519
520 #[test]
521 fn test_multi_segment() {
522 let flags = AdvFlags {
523 encrypted: true,
524 compressed: false,
525 split: true,
526 is_request: false,
527 is_response: false,
528 has_metadata: false,
529 app_flag: false,
530 };
531 let mut adv = make_adv(flags);
532 adv.segment_index = 2;
533 adv.total_segments = 5;
534 let packed = adv.pack(0);
535 let unpacked = ResourceAdvertisement::unpack(&packed).unwrap();
536 assert!(unpacked.flags.split);
537 assert_eq!(unpacked.segment_index, 2);
538 assert_eq!(unpacked.total_segments, 5);
539 }
540
541 #[test]
542 fn test_with_request_id() {
543 let flags = AdvFlags {
544 encrypted: true,
545 compressed: false,
546 split: false,
547 is_request: true,
548 is_response: false,
549 has_metadata: false,
550 app_flag: false,
551 };
552 let mut adv = make_adv(flags);
553 adv.request_id = Some(vec![0xDE, 0xAD, 0xBE, 0xEF]);
554 let packed = adv.pack(0);
555 let unpacked = ResourceAdvertisement::unpack(&packed).unwrap();
556 assert!(unpacked.is_request());
557 assert!(!unpacked.is_response());
558 assert_eq!(unpacked.request_id, Some(vec![0xDE, 0xAD, 0xBE, 0xEF]));
559 }
560
561 #[test]
562 fn test_is_response() {
563 let flags = AdvFlags {
564 encrypted: true,
565 compressed: false,
566 split: false,
567 is_request: false,
568 is_response: true,
569 has_metadata: false,
570 app_flag: false,
571 };
572 let mut adv = make_adv(flags);
573 adv.request_id = Some(vec![0x42; 16]);
574 assert!(adv.is_response());
575 assert!(!adv.is_request());
576 }
577
578 #[test]
579 fn test_nil_request_id() {
580 let flags = AdvFlags {
581 encrypted: true,
582 compressed: false,
583 split: false,
584 is_request: false,
585 is_response: false,
586 has_metadata: false,
587 app_flag: false,
588 };
589 let adv = make_adv(flags);
590 let packed = adv.pack(0);
591 let unpacked = ResourceAdvertisement::unpack(&packed).unwrap();
592 assert!(unpacked.request_id.is_none());
593 assert!(!unpacked.is_request());
594 assert!(!unpacked.is_response());
595 }
596
597 #[test]
598 fn test_hashmap_segmentation() {
599 let num_hashes = 100;
601 let hashmap: Vec<u8> = (0..num_hashes).flat_map(|i| vec![i as u8; 4]).collect();
602
603 let flags = AdvFlags {
604 encrypted: true,
605 compressed: false,
606 split: false,
607 is_request: false,
608 is_response: false,
609 has_metadata: false,
610 app_flag: false,
611 };
612 let adv = ResourceAdvertisement {
613 transfer_size: 50000,
614 data_size: 48000,
615 num_parts: num_hashes,
616 resource_hash: vec![0x11; 32],
617 random_hash: vec![0xAA; 4],
618 original_hash: vec![0x22; 32],
619 hashmap: hashmap.clone(),
620 flags,
621 segment_index: 1,
622 total_segments: 1,
623 request_id: None,
624 };
625
626 let packed0 = adv.pack(0);
628 let unpacked0 = ResourceAdvertisement::unpack(&packed0).unwrap();
629 assert_eq!(unpacked0.hashmap.len(), 74 * 4);
630
631 let packed1 = adv.pack(1);
633 let unpacked1 = ResourceAdvertisement::unpack(&packed1).unwrap();
634 assert_eq!(unpacked1.hashmap.len(), 26 * 4);
635 }
636
637 #[test]
638 fn test_hashmap_segments_count() {
639 let flags = AdvFlags {
640 encrypted: true,
641 compressed: false,
642 split: false,
643 is_request: false,
644 is_response: false,
645 has_metadata: false,
646 app_flag: false,
647 };
648 let mut adv = make_adv(flags);
649
650 adv.num_parts = 74; assert_eq!(adv.hashmap_segments(), 1);
652
653 adv.num_parts = 75;
654 assert_eq!(adv.hashmap_segments(), 2);
655
656 adv.num_parts = 148;
657 assert_eq!(adv.hashmap_segments(), 2);
658
659 adv.num_parts = 149;
660 assert_eq!(adv.hashmap_segments(), 3);
661 }
662
663 #[test]
664 fn test_unpack_invalid_data() {
665 assert!(ResourceAdvertisement::unpack(&[]).is_err());
666 assert!(ResourceAdvertisement::unpack(&[0xc0]).is_err()); assert!(ResourceAdvertisement::unpack(&[0x01, 0x02]).is_err()); }
669
670 #[test]
671 fn test_unpack_accepts_exact_upstream_transfer_limit() {
672 let flags = AdvFlags {
673 encrypted: true,
674 compressed: false,
675 split: false,
676 is_request: false,
677 is_response: false,
678 has_metadata: false,
679 app_flag: false,
680 };
681 let mut adv = make_adv(flags);
682 adv.transfer_size = (crate::constants::RESOURCE_MAX_EFFICIENT_SIZE * 3) as u64;
683
684 let unpacked = ResourceAdvertisement::unpack(&adv.pack(0)).unwrap();
685 assert_eq!(unpacked.transfer_size, adv.transfer_size);
686 }
687
688 #[test]
689 fn test_unpack_rejects_transfer_above_upstream_limit() {
690 let flags = AdvFlags {
691 encrypted: true,
692 compressed: false,
693 split: false,
694 is_request: false,
695 is_response: false,
696 has_metadata: false,
697 app_flag: false,
698 };
699 let mut adv = make_adv(flags);
700 adv.transfer_size = (crate::constants::RESOURCE_MAX_EFFICIENT_SIZE * 3 + 1) as u64;
701
702 assert_eq!(
703 ResourceAdvertisement::unpack(&adv.pack(0)).unwrap_err(),
704 ResourceError::InvalidAdvertisement
705 );
706 }
707}