1use alloc::boxed::Box;
2use alloc::vec;
3use alloc::vec::Vec;
4
5use super::advertisement::ResourceAdvertisement;
6use super::assembly::{AssemblyData, AssemblyId, AssemblyResult, ResourceAssembly};
7use super::parts::map_hash;
8use super::types::*;
9use super::window::WindowState;
10use crate::buffer::types::Compressor;
11use crate::constants::*;
12use rns_crypto::token::TokenDecryptStream;
13
14const PART_RESERVE_PARTS: usize = 64;
16
17pub type PartsDecryptFn<'a> = dyn Fn(&[&[u8]], usize) -> Result<Vec<u8>, ()> + 'a;
21
22pub struct ResourceReceiver {
27 assembly_id: Option<AssemblyId>,
28 decrypt: Option<Box<TokenDecryptStream>>,
31 decrypted_parts: usize,
32 pub status: ResourceStatus,
34 pub resource_hash: Vec<u8>,
36 pub random_hash: Vec<u8>,
38 pub original_hash: Vec<u8>,
40 pub flags: AdvFlags,
42 pub transfer_size: u64,
44 pub data_size: u64,
46 pub total_parts: usize,
48 part_data: Vec<u8>,
50 part_spans: Vec<Option<(usize, usize)>>,
52 hashmap: Vec<Option<[u8; RESOURCE_MAPHASH_LEN]>>,
54 hashmap_height: usize,
56 pub waiting_for_hmu: bool,
58 pub received_count: usize,
60 pub outstanding_parts: usize,
62 consecutive_completed_height: isize,
64 sdu: usize,
66 link_rtt: f64,
68 pub retries_left: usize,
70 max_retries: usize,
72 pub rtt: Option<f64>,
74 part_timeout_factor: f64,
76 pub last_activity: f64,
78 pub req_sent: f64,
80 req_sent_bytes: usize,
82 req_resp: Option<f64>,
84 rtt_rxd_bytes: usize,
86 rtt_rxd_bytes_at_part_req: usize,
88 req_resp_rtt_rate: f64,
90 req_data_rtt_rate: f64,
92 pub eifr: Option<f64>,
94 previous_eifr: Option<f64>,
96 pub segment_index: u64,
98 pub total_segments: u64,
100 pub has_metadata: bool,
102 pub request_id: Option<Vec<u8>>,
104 pub window: WindowState,
106 pub advertisement_packet: Vec<u8>,
108 pub max_decompressed_size: usize,
110}
111
112impl ResourceReceiver {
113 pub fn from_advertisement(
115 adv_data: &[u8],
116 sdu: usize,
117 link_rtt: f64,
118 now: f64,
119 previous_window: Option<usize>,
120 previous_eifr: Option<f64>,
121 ) -> Result<Self, ResourceError> {
122 let adv = ResourceAdvertisement::unpack(adv_data)?;
123
124 if adv.resource_hash.len() != 32 {
126 return Err(ResourceError::InvalidAdvertisement);
127 }
128 if adv.random_hash.len() != RESOURCE_RANDOM_HASH_SIZE || adv.original_hash.len() != 32 {
129 return Err(ResourceError::InvalidAdvertisement);
130 }
131 if adv.transfer_size > RESOURCE_AUTO_COMPRESS_MAX_SIZE as u64
135 || (!adv.flags.split && adv.data_size > RESOURCE_AUTO_COMPRESS_MAX_SIZE as u64)
136 {
137 return Err(ResourceError::TooLarge);
138 }
139
140 let total_parts = usize::try_from(adv.num_parts).map_err(|_| ResourceError::TooLarge)?;
147 if total_parts == 0 {
148 return Err(ResourceError::InvalidAdvertisement);
149 }
150 let transfer_size =
151 usize::try_from(adv.transfer_size).map_err(|_| ResourceError::TooLarge)?;
152 let expected_parts = transfer_size.div_ceil(sdu.max(1));
153 if total_parts != expected_parts {
154 return Err(ResourceError::PartCountMismatch);
155 }
156
157 let initial_hashes = core::cmp::min(adv.hashmap.len() / RESOURCE_MAPHASH_LEN, total_parts);
161 let mut hashmap_vec: Vec<Option<[u8; RESOURCE_MAPHASH_LEN]>> =
162 Vec::with_capacity(initial_hashes);
163
164 for chunk in adv
166 .hashmap
167 .chunks_exact(RESOURCE_MAPHASH_LEN)
168 .take(initial_hashes)
169 {
170 let mut h = [0u8; RESOURCE_MAPHASH_LEN];
171 h.copy_from_slice(chunk);
172 hashmap_vec.push(Some(h));
173 }
174 let hashmap_height = hashmap_vec.len();
175
176 let mut window_state = WindowState::new();
177 if let Some(prev_w) = previous_window {
178 window_state.restore(prev_w);
179 }
180
181 Ok(ResourceReceiver {
182 assembly_id: None,
183 decrypt: None,
184 decrypted_parts: 0,
185 status: ResourceStatus::None,
186 resource_hash: adv.resource_hash,
187 random_hash: adv.random_hash,
188 original_hash: adv.original_hash,
189 flags: adv.flags,
190 transfer_size: adv.transfer_size,
191 data_size: adv.data_size,
192 total_parts,
193 part_data: Vec::new(),
194 part_spans: Vec::new(),
195 hashmap: hashmap_vec,
196 hashmap_height,
197 waiting_for_hmu: false,
198 received_count: 0,
199 outstanding_parts: 0,
200 consecutive_completed_height: -1,
201 sdu,
202 link_rtt,
203 retries_left: RESOURCE_MAX_RETRIES,
204 max_retries: RESOURCE_MAX_RETRIES,
205 rtt: None,
206 part_timeout_factor: RESOURCE_PART_TIMEOUT_FACTOR,
207 last_activity: now,
208 req_sent: 0.0,
209 req_sent_bytes: 0,
210 req_resp: None,
211 rtt_rxd_bytes: 0,
212 rtt_rxd_bytes_at_part_req: 0,
213 req_resp_rtt_rate: 0.0,
214 req_data_rtt_rate: 0.0,
215 eifr: None,
216 previous_eifr,
217 segment_index: adv.segment_index,
218 total_segments: adv.total_segments,
219 has_metadata: adv.flags.has_metadata,
220 request_id: adv.request_id,
221 window: window_state,
222 advertisement_packet: adv_data.to_vec(),
223 max_decompressed_size: RESOURCE_AUTO_COMPRESS_MAX_SIZE,
224 })
225 }
226
227 fn has_part(&self, index: usize) -> bool {
228 self.part_spans.get(index).is_some_and(Option::is_some)
229 }
230
231 fn store_part(&mut self, index: usize, data: &[u8]) {
232 if self.part_spans.len() <= index {
233 let wanted = (index + 1).max(self.total_parts.min(PART_RESERVE_PARTS));
237 self.part_spans.reserve(wanted - self.part_spans.len());
238 self.part_spans.resize(index + 1, None);
239 }
240 if self.part_data.capacity() - self.part_data.len() < data.len() {
241 let remaining = self.total_parts - self.received_count;
242 let wanted = remaining.min(PART_RESERVE_PARTS).saturating_mul(self.sdu);
243 self.part_data.reserve(wanted.max(data.len()));
244 }
245 self.part_spans[index] = Some((self.part_data.len(), data.len()));
246 self.part_data.extend_from_slice(data);
247 }
248
249 fn hash_at(&self, index: usize) -> Option<&[u8; RESOURCE_MAPHASH_LEN]> {
250 self.hashmap.get(index).and_then(Option::as_ref)
251 }
252
253 fn store_hash(&mut self, index: usize, hash: [u8; RESOURCE_MAPHASH_LEN]) {
254 if self.hashmap.len() <= index {
255 self.hashmap.resize(index + 1, None);
256 }
257 self.hashmap[index] = Some(hash);
258 }
259
260 pub fn accept(&mut self, now: f64) -> Vec<ResourceAction> {
262 if self.status >= ResourceStatus::Assembling {
263 return vec![];
264 }
265 self.status = ResourceStatus::Transferring;
266 self.last_activity = now;
267 self.request_next(now)
268 }
269
270 pub fn reject(&mut self) -> Vec<ResourceAction> {
272 self.status = ResourceStatus::Rejected;
273 vec![ResourceAction::SendCancelReceiver(
274 self.resource_hash.clone(),
275 )]
276 }
277
278 pub fn cancel(&mut self) -> Vec<ResourceAction> {
280 if self.status < ResourceStatus::Complete {
281 self.status = ResourceStatus::Failed;
282 vec![ResourceAction::SendCancelReceiver(
283 self.resource_hash.clone(),
284 )]
285 } else {
286 Vec::new()
287 }
288 }
289
290 fn corrupt_actions(&mut self, error: ResourceError) -> Vec<ResourceAction> {
291 self.status = ResourceStatus::Corrupt;
292 vec![
293 ResourceAction::SendCancelReceiver(self.resource_hash.clone()),
294 ResourceAction::Failed(error),
295 ResourceAction::TeardownLink,
296 ]
297 }
298
299 pub fn continue_from(&mut self, window: WindowState, eifr: Option<f64>) {
302 self.window = window;
303 if eifr.is_some() {
304 self.previous_eifr = eifr;
305 }
306 }
307
308 pub fn has_parts_to_decrypt(&self) -> bool {
310 self.flags.encrypted
311 && self.status < ResourceStatus::Assembling
312 && (self.decrypted_parts as isize) <= self.consecutive_completed_height
313 }
314
315 pub fn decrypt_next_part(
321 &mut self,
322 start: impl FnOnce() -> Option<TokenDecryptStream>,
323 ) -> bool {
324 if !self.has_parts_to_decrypt() {
325 return false;
326 }
327 if self.decrypt.is_none() {
328 let Some(mut stream) = start() else {
329 return false;
330 };
331 if !self.flags.compressed {
334 stream.hash_plaintext();
335 }
336 self.decrypt = Some(Box::new(stream));
337 }
338 let Some(stream) = self.decrypt.as_mut() else {
339 return false;
340 };
341 let Some(Some((at, len))) = self.part_spans.get(self.decrypted_parts).copied() else {
342 return false;
343 };
344 stream.update(&self.part_data[at..at + len]);
345 self.decrypted_parts += 1;
346 self.has_parts_to_decrypt()
347 }
348
349 pub fn decrypts(&self) -> bool {
352 self.flags.encrypted && self.status < ResourceStatus::Assembling
353 }
354
355 pub fn prepare_decrypt_stream(&self, stream: &mut TokenDecryptStream) {
358 if !self.flags.compressed {
361 stream.hash_plaintext();
362 }
363 }
364
365 pub fn parts_in_order_from(&self, from: usize) -> impl Iterator<Item = &[u8]> + '_ {
368 let end = (self.consecutive_completed_height + 1).max(0) as usize;
369 self.part_spans
370 .get(from..end.max(from))
371 .unwrap_or(&[])
372 .iter()
373 .map_while(|span| span.map(|(at, len)| &self.part_data[at..at + len]))
374 }
375
376 pub fn resume_decrypt(&mut self, stream: Box<TokenDecryptStream>, parts: usize) {
381 if self.decrypt.is_none() && self.decrypted_parts == 0 && parts <= self.part_spans.len() {
382 self.decrypt = Some(stream);
383 self.decrypted_parts = parts;
384 }
385 }
386
387 pub fn decrypt_held_parts(
390 &mut self,
391 start: impl FnOnce() -> Option<TokenDecryptStream>,
392 budget: usize,
393 ) -> bool {
394 let mut done = self.next_part_len();
395 if !self.decrypt_next_part(start) {
396 return false;
397 }
398 while done < budget {
399 done += self.next_part_len();
400 if !self.decrypt_next_part(|| None) {
402 return false;
403 }
404 }
405 self.has_parts_to_decrypt()
406 }
407
408 fn next_part_len(&self) -> usize {
409 match self.part_spans.get(self.decrypted_parts) {
410 Some(Some((_, len))) => *len,
411 _ => 0,
412 }
413 }
414
415 pub fn receive_part(&mut self, part_data: &[u8], now: f64) -> Vec<ResourceAction> {
417 let mut actions = Vec::new();
418 self.receive_part_into(part_data, now, &mut actions);
419 actions
420 }
421
422 pub fn receive_part_into(&mut self, part_data: &[u8], now: f64, out: &mut Vec<ResourceAction>) {
424 if self.status >= ResourceStatus::Assembling {
425 return;
426 }
427
428 self.last_activity = now;
429 self.retries_left = self.max_retries;
430
431 if self.req_resp.is_none() {
433 self.req_resp = Some(now);
434 let rtt = now - self.req_sent;
435 self.part_timeout_factor = RESOURCE_PART_TIMEOUT_FACTOR_AFTER_RTT;
436
437 if self.rtt.is_none() {
438 self.rtt = Some(rtt);
439 } else if let Some(current_rtt) = self.rtt {
440 if rtt < current_rtt {
441 self.rtt = Some(f64::max(current_rtt - current_rtt * 0.05, rtt));
442 } else {
443 self.rtt = Some(f64::min(current_rtt + current_rtt * 0.05, rtt));
444 }
445 }
446
447 if rtt > 0.0 {
448 let req_resp_cost = part_data.len() + self.req_sent_bytes;
449 self.req_resp_rtt_rate = req_resp_cost as f64 / rtt;
450 self.window.update_req_resp_rate(self.req_resp_rtt_rate);
451 }
452 }
453
454 self.status = ResourceStatus::Transferring;
455
456 let part_hash = map_hash(part_data, &self.random_hash);
458
459 let search_start = (self.consecutive_completed_height + 1) as usize;
462 let mut matched = false;
463 let search_end = core::cmp::min(search_start + self.window.window, self.total_parts);
464 for i in search_start..search_end {
465 if let Some(h) = self.hash_at(i) {
466 if *h == part_hash {
467 if !self.has_part(i) {
468 self.store_part(i, part_data);
469 self.rtt_rxd_bytes += part_data.len();
470 self.received_count += 1;
471 self.outstanding_parts = self.outstanding_parts.saturating_sub(1);
472
473 let mut cp = (self.consecutive_completed_height + 1) as usize;
475 while cp < self.total_parts && self.has_part(cp) {
476 self.consecutive_completed_height = cp as isize;
477 cp += 1;
478 }
479
480 matched = true;
481 }
482 break;
483 }
484 }
485 }
486
487 if self.received_count == self.total_parts {
489 out.push(ResourceAction::ProgressUpdate {
490 received: self.received_count,
491 total: self.total_parts,
492 });
493 return;
495 }
496
497 if matched {
498 out.push(ResourceAction::ProgressUpdate {
499 received: self.received_count,
500 total: self.total_parts,
501 });
502 }
503
504 if self.outstanding_parts == 0 && self.received_count < self.total_parts {
506 self.window.on_window_complete();
508
509 if self.req_sent > 0.0 {
511 let rtt = now - self.req_sent;
512 let req_transferred = self.rtt_rxd_bytes - self.rtt_rxd_bytes_at_part_req;
513 if rtt > 0.0 {
514 self.req_data_rtt_rate = req_transferred as f64 / rtt;
515 self.rtt_rxd_bytes_at_part_req = self.rtt_rxd_bytes;
516 self.window.update_data_rate(self.req_data_rtt_rate);
517 }
518 }
519
520 self.request_next_into(now, out);
521 }
522 }
523
524 pub fn handle_hashmap_update(&mut self, hmu_data: &[u8], now: f64) -> Vec<ResourceAction> {
528 if self.status >= ResourceStatus::Assembling || !self.waiting_for_hmu {
529 return vec![];
530 }
531
532 if hmu_data.len() < 32 || hmu_data[..32] != self.resource_hash {
533 return vec![];
534 }
535 if hmu_data.len() == 32 {
536 return self.cancel();
537 }
538
539 let payload = &hmu_data[32..];
540 let value = match crate::msgpack::unpack_exact(payload) {
541 Ok(v) => v,
542 Err(_) => return self.cancel(),
543 };
544
545 let arr = match value.as_array() {
546 Some(a) if a.len() == 2 => a,
547 _ => return self.cancel(),
548 };
549
550 let segment = match arr[0].as_uint() {
551 Some(s) => match usize::try_from(s) {
552 Ok(segment) => segment,
553 Err(_) => return self.cancel(),
554 },
555 None => return self.cancel(),
556 };
557
558 let hashmap_bytes = match arr[1].as_bin() {
559 Some(b) => b,
560 None => return self.cancel(),
561 };
562
563 if hashmap_bytes.is_empty() || !hashmap_bytes.len().is_multiple_of(RESOURCE_MAPHASH_LEN) {
564 return self.cancel();
565 }
566
567 let seg_len = RESOURCE_HASHMAP_MAX_LEN;
568 let segment_start = match segment.checked_mul(seg_len) {
569 Some(start) if start < self.total_parts => start,
570 _ => return self.cancel(),
571 };
572 let num_hashes = hashmap_bytes.len() / RESOURCE_MAPHASH_LEN;
573 if num_hashes > seg_len || segment_start.saturating_add(num_hashes) > self.total_parts {
574 return self.cancel();
575 }
576
577 self.last_activity = now;
578 self.retries_left = self.max_retries;
579 self.status = ResourceStatus::Transferring;
580
581 for i in 0..num_hashes {
583 let idx = segment_start + i;
584 let start = i * RESOURCE_MAPHASH_LEN;
585 let end = start + RESOURCE_MAPHASH_LEN;
586 if self.hash_at(idx).is_none() {
587 self.hashmap_height += 1;
588 }
589 let mut h = [0u8; RESOURCE_MAPHASH_LEN];
590 h.copy_from_slice(&hashmap_bytes[start..end]);
591 self.store_hash(idx, h);
592 }
593
594 self.waiting_for_hmu = false;
595 self.request_next(now)
596 }
597
598 pub fn request_next(&mut self, now: f64) -> Vec<ResourceAction> {
600 let mut actions = Vec::new();
601 self.request_next_into(now, &mut actions);
602 actions
603 }
604
605 pub fn request_next_into(&mut self, now: f64, out: &mut Vec<ResourceAction>) {
608 if self.status >= ResourceStatus::Assembling {
609 return;
610 }
611 if self.status == ResourceStatus::Failed || self.waiting_for_hmu {
612 return;
613 }
614
615 self.outstanding_parts = 0;
616 let mut hashmap_exhausted = RESOURCE_HASHMAP_IS_NOT_EXHAUSTED;
617 let mut request_data = Vec::with_capacity(
619 1 + RESOURCE_MAPHASH_LEN
620 + self.resource_hash.len()
621 + self.window.window * RESOURCE_MAPHASH_LEN,
622 );
623 request_data.push(hashmap_exhausted);
624 let hashes_at = request_data.len();
625
626 if self.hash_at(0).is_none() {
627 hashmap_exhausted = RESOURCE_HASHMAP_IS_EMPTY;
631 } else {
632 let pn_start = (self.consecutive_completed_height + 1) as usize;
633 let search_end = core::cmp::min(pn_start + self.window.window, self.total_parts);
634 let mut i = 0;
635
636 for pn in pn_start..search_end {
637 if !self.has_part(pn) {
638 match self.hash_at(pn) {
639 Some(h) => {
640 request_data.extend_from_slice(h);
641 self.outstanding_parts += 1;
642 i += 1;
643 }
644 None => {
645 hashmap_exhausted = RESOURCE_HASHMAP_IS_EXHAUSTED;
646 }
647 }
648 }
649 if i >= self.window.window || hashmap_exhausted == RESOURCE_HASHMAP_IS_EXHAUSTED {
650 break;
651 }
652 }
653 }
654
655 request_data[0] = hashmap_exhausted;
656 let mut prefix = [0u8; RESOURCE_MAPHASH_LEN + 32];
659 let mut prefix_len = 0;
660 if hashmap_exhausted == RESOURCE_HASHMAP_IS_EXHAUSTED {
661 let last_hash = if self.hashmap_height > 0 {
663 self.hash_at(self.hashmap_height - 1).copied()
664 } else {
665 None
666 };
667 prefix[..RESOURCE_MAPHASH_LEN]
668 .copy_from_slice(&last_hash.unwrap_or([0u8; RESOURCE_MAPHASH_LEN]));
669 prefix_len = RESOURCE_MAPHASH_LEN;
670 self.waiting_for_hmu = true;
671 }
672 if hashmap_exhausted == RESOURCE_HASHMAP_IS_EMPTY {
673 self.waiting_for_hmu = true;
674 }
675 request_data.splice(
676 hashes_at..hashes_at,
677 prefix[..prefix_len]
678 .iter()
679 .chain(self.resource_hash.iter())
680 .copied(),
681 );
682
683 self.last_activity = now;
684 self.req_sent = now;
685 self.req_sent_bytes = request_data.len();
686 self.req_resp = None;
687
688 out.push(ResourceAction::SendRequest(request_data));
689 }
690
691 pub fn assemble(
693 &mut self,
694 decrypt_fn: &PartsDecryptFn<'_>,
695 compressor: &dyn Compressor,
696 ) -> Vec<ResourceAction> {
697 match self.prepare_data(decrypt_fn) {
698 Ok(data) => self.finish_data(data.run(compressor)),
699 Err(actions) => actions,
700 }
701 }
702
703 pub fn prepare_assembly(
707 &mut self,
708 decrypt_fn: &PartsDecryptFn<'_>,
709 ) -> Result<ResourceAssembly, Vec<ResourceAction>> {
710 let data = self.prepare_data(decrypt_fn)?;
711 let id = AssemblyId::new();
712 self.assembly_id = Some(id.clone());
713 Ok(ResourceAssembly { id, data })
714 }
715
716 pub fn assembly_id(&self) -> Option<&AssemblyId> {
717 (self.status == ResourceStatus::Assembling)
718 .then_some(self.assembly_id.as_ref())
719 .flatten()
720 }
721
722 pub fn complete_assembly(&mut self, result: AssemblyResult) -> Vec<ResourceAction> {
725 if self.assembly_id() != Some(&result.id) {
726 return Vec::new();
727 }
728 self.assembly_id = None;
729 self.finish_data(result.result)
730 }
731
732 pub fn fail_assembly(&mut self, id: &AssemblyId) -> Vec<ResourceAction> {
734 if self.assembly_id() != Some(id) {
735 return Vec::new();
736 }
737 self.assembly_id = None;
738 let mut actions = self.cancel();
739 actions.push(ResourceAction::Failed(ResourceError::InvalidState));
740 actions
741 }
742
743 fn finish_data(
744 &mut self,
745 result: Result<Vec<ResourceAction>, ResourceError>,
746 ) -> Vec<ResourceAction> {
747 match result {
748 Ok(actions) => {
749 self.status = ResourceStatus::Complete;
750 actions
751 }
752 Err(error) => self.corrupt_actions(error),
753 }
754 }
755
756 fn prepare_data(
757 &mut self,
758 decrypt_fn: &PartsDecryptFn<'_>,
759 ) -> Result<AssemblyData, Vec<ResourceAction>> {
760 if self.status >= ResourceStatus::Assembling || self.received_count != self.total_parts {
761 return Err(vec![ResourceAction::Failed(ResourceError::InvalidState)]);
762 }
763
764 self.status = ResourceStatus::Assembling;
765
766 if self.part_spans.len() != self.total_parts {
768 self.status = ResourceStatus::Failed;
769 return Err(vec![ResourceAction::Failed(ResourceError::InvalidState)]);
770 }
771
772 let mut stream_len = 0usize;
775 for span in &self.part_spans {
776 match span {
777 Some((_, len)) => match stream_len.checked_add(*len) {
778 Some(len) => stream_len = len,
779 None => {
780 self.status = ResourceStatus::Failed;
781 return Err(vec![ResourceAction::Failed(ResourceError::TooLarge)]);
782 }
783 },
784 None => {
785 self.status = ResourceStatus::Failed;
786 return Err(vec![ResourceAction::Failed(ResourceError::InvalidState)]);
787 }
788 }
789 }
790 let part_data = core::mem::take(&mut self.part_data);
793 let part_spans = core::mem::take(&mut self.part_spans);
794 let parts = || {
795 part_spans
796 .iter()
797 .flatten()
798 .map(|&(start, len)| &part_data[start..start + len])
799 };
800 self.hashmap = Vec::new();
801 let mut data_state = None;
802
803 let plaintext = if self.flags.encrypted {
805 let decrypted = if let Some(mut stream) = self.decrypt.take() {
806 for data in parts().skip(self.decrypted_parts) {
808 stream.update(data);
809 }
810 stream
811 .finish_hashed()
812 .map_err(|_| ())
813 .map(|(plaintext, hash)| {
814 data_state = hash.map(Box::new);
815 plaintext
816 })
817 } else {
818 let chunks: Vec<&[u8]> = parts().collect();
821 decrypt_fn(&chunks, RESOURCE_RANDOM_HASH_SIZE)
822 };
823 drop(part_spans);
824 drop(part_data);
825 match decrypted {
826 Ok(d) => d,
827 Err(_) => {
828 self.status = ResourceStatus::Failed;
829 return Err(vec![ResourceAction::Failed(
830 ResourceError::DecryptionFailed,
831 )]);
832 }
833 }
834 } else {
835 let Some(len) = stream_len.checked_sub(RESOURCE_RANDOM_HASH_SIZE) else {
836 return Err(self.corrupt_actions(ResourceError::InvalidPart));
837 };
838 let mut plaintext = Vec::with_capacity(len);
839 let mut skip = RESOURCE_RANDOM_HASH_SIZE;
840 for data in parts() {
841 let from = skip.min(data.len());
842 plaintext.extend_from_slice(&data[from..]);
843 skip -= from;
844 }
845 plaintext
846 };
847
848 let (Ok(resource_hash), Ok(random_hash)) = (
849 self.resource_hash.as_slice().try_into(),
850 self.random_hash.as_slice().try_into(),
851 ) else {
852 return Err(self.corrupt_actions(ResourceError::InvalidAdvertisement));
853 };
854 Ok(AssemblyData {
855 plaintext,
856 data_state,
857 resource_hash,
858 random_hash,
859 compressed: self.flags.compressed,
860 metadata: self.has_metadata && self.segment_index == 1,
861 app_flag: self.flags.app_flag,
862 limit: self.max_decompressed_size,
863 })
864 }
865
866 pub fn handle_cancel(&mut self) -> Vec<ResourceAction> {
868 if self.status < ResourceStatus::Complete {
869 self.status = ResourceStatus::Failed;
870 return vec![ResourceAction::Failed(ResourceError::Rejected)];
871 }
872 vec![]
873 }
874
875 pub fn tick(
877 &mut self,
878 now: f64,
879 decrypt_fn: &PartsDecryptFn<'_>,
880 compressor: &dyn Compressor,
881 ) -> Vec<ResourceAction> {
882 if self.status >= ResourceStatus::Assembling {
883 return vec![];
884 }
885
886 if self.status == ResourceStatus::Transferring {
887 if self.received_count == self.total_parts {
889 return self.assemble(decrypt_fn, compressor);
890 }
891
892 let eifr = self.compute_eifr();
894 let retries_used = self.max_retries - self.retries_left;
895 let extra_wait = retries_used as f64 * RESOURCE_PER_RETRY_DELAY;
896 let expected_hmu_wait =
897 if eifr > 0.0 && (self.waiting_for_hmu || self.outstanding_parts == 0) {
898 (self.sdu as f64 * 8.0 * RESOURCE_HMU_WAIT_FACTOR) / eifr
899 } else {
900 0.0
901 };
902 let expected_tof = if self.outstanding_parts > 0 && eifr > 0.0 {
903 (self.outstanding_parts as f64 * self.sdu as f64 * 8.0) / eifr
904 } else if eifr > 0.0 {
905 (3.0 * self.sdu as f64) / eifr
906 } else {
907 10.0 };
909
910 let sleep_time = self.last_activity
911 + self.part_timeout_factor * expected_tof
912 + expected_hmu_wait
913 + RESOURCE_RETRY_GRACE_TIME
914 + extra_wait;
915
916 if now > sleep_time {
917 if self.retries_left > 0 {
918 self.window.on_timeout();
920 self.retries_left -= 1;
921 self.waiting_for_hmu = false;
922 return self.request_next(now);
923 } else {
924 self.status = ResourceStatus::Failed;
925 return vec![ResourceAction::Failed(ResourceError::MaxRetriesExceeded)];
926 }
927 }
928 }
929
930 vec![]
931 }
932
933 fn compute_eifr(&mut self) -> f64 {
935 let eifr = if self.req_data_rtt_rate > 0.0 {
936 self.req_data_rtt_rate * 8.0
937 } else if let Some(prev) = self.previous_eifr {
938 prev
939 } else {
940 let rtt = self.rtt.unwrap_or(self.link_rtt);
942 if rtt > 0.0 {
943 (self.sdu as f64 * 8.0) / rtt
944 } else {
945 10000.0
946 }
947 };
948 self.eifr = Some(eifr);
949 eifr
950 }
951
952 pub fn progress(&self) -> (usize, usize) {
954 (self.received_count, self.total_parts)
955 }
956
957 pub fn get_transfer_state(&self) -> (usize, Option<f64>) {
959 (self.window.window, self.eifr)
960 }
961}
962
963#[cfg(test)]
964mod tests {
965 use super::*;
966 use crate::buffer::types::{Compressor, DecompressError, NoopCompressor};
967 use crate::resource::advertisement::ResourceAdvertisement;
968 use crate::resource::sender::ResourceSender;
969
970 fn identity_encrypt(data: &[u8]) -> Vec<u8> {
971 data.to_vec()
972 }
973
974 fn identity_decrypt(parts: &[&[u8]], skip: usize) -> Result<Vec<u8>, ()> {
975 let joined = parts.concat();
976 joined.get(skip..).map(<[u8]>::to_vec).ok_or(())
977 }
978
979 struct ExpandingCompressor;
980
981 impl Compressor for ExpandingCompressor {
982 fn compress(&self, data: &[u8]) -> Option<Vec<u8>> {
983 Some(data[..data.len() / 2].to_vec())
984 }
985
986 fn decompress(&self, data: &[u8]) -> Option<Vec<u8>> {
987 self.decompress_bounded(data, usize::MAX).ok()
988 }
989
990 fn decompress_bounded(
991 &self,
992 data: &[u8],
993 max_output_size: usize,
994 ) -> Result<Vec<u8>, DecompressError> {
995 let mut out = data.to_vec();
996 out.extend_from_slice(data);
997 if out.len() > max_output_size {
998 return Err(DecompressError::TooLarge);
999 }
1000 Ok(out)
1001 }
1002 }
1003
1004 fn base_timeout(receiver: &ResourceReceiver, eifr: f64) -> f64 {
1005 let expected_tof = if receiver.outstanding_parts > 0 {
1006 (receiver.outstanding_parts as f64 * receiver.sdu as f64 * 8.0) / eifr
1007 } else {
1008 (3.0 * receiver.sdu as f64) / eifr
1009 };
1010
1011 receiver.last_activity
1012 + receiver.part_timeout_factor * expected_tof
1013 + RESOURCE_RETRY_GRACE_TIME
1014 }
1015
1016 fn hmu_timeout(receiver: &ResourceReceiver, eifr: f64) -> f64 {
1017 let expected_hmu_wait = (receiver.sdu as f64 * 8.0 * RESOURCE_HMU_WAIT_FACTOR) / eifr;
1018 base_timeout(receiver, eifr) + expected_hmu_wait
1019 }
1020
1021 fn make_sender_receiver() -> (ResourceSender, ResourceReceiver) {
1022 let mut rng = rns_crypto::FixedRng::new(&[0x42; 64]);
1023 let data = b"Hello, Resource Transfer!";
1024
1025 let sender = ResourceSender::new(
1026 data,
1027 None,
1028 RESOURCE_SDU,
1029 &identity_encrypt,
1030 &NoopCompressor,
1031 &mut rng,
1032 1000.0,
1033 false,
1034 false,
1035 None,
1036 1,
1037 1,
1038 None,
1039 0.5,
1040 6.0,
1041 )
1042 .unwrap();
1043
1044 let adv_data = sender.get_advertisement(0);
1045 let receiver =
1046 ResourceReceiver::from_advertisement(&adv_data, RESOURCE_SDU, 0.5, 1000.0, None, None)
1047 .unwrap();
1048
1049 (sender, receiver)
1050 }
1051
1052 fn ready_receiver() -> ResourceReceiver {
1053 let (mut sender, mut receiver) = make_sender_receiver();
1054 for action in receiver.accept(1001.0) {
1055 if let ResourceAction::SendRequest(request) = action {
1056 for action in sender.handle_request(&request, 1002.0) {
1057 if let ResourceAction::SendPart(part) = action {
1058 receiver.receive_part(&part, 1003.0);
1059 }
1060 }
1061 }
1062 }
1063 assert_eq!(receiver.received_count, receiver.total_parts);
1064 receiver
1065 }
1066
1067 fn encrypted_receiver(
1070 token: &rns_crypto::token::Token,
1071 len: usize,
1072 sdu: usize,
1073 ) -> ResourceReceiver {
1074 let mut rng = rns_crypto::FixedRng::new(&[0x42; 64]);
1075 let data: Vec<u8> = (0..len).map(|i| (i * 31 + 7) as u8).collect();
1076 let encrypt = |plaintext: &[u8]| token.encrypt_with_iv(plaintext, &[0x17; 16]);
1077 let mut sender = ResourceSender::new(
1078 &data,
1079 None,
1080 sdu,
1081 &encrypt,
1082 &NoopCompressor,
1083 &mut rng,
1084 1000.0,
1085 false,
1086 false,
1087 None,
1088 1,
1089 1,
1090 None,
1091 0.5,
1092 6.0,
1093 )
1094 .unwrap();
1095 let adv = sender.get_advertisement(0);
1096 let mut receiver =
1097 ResourceReceiver::from_advertisement(&adv, sdu, 0.5, 1000.0, None, None).unwrap();
1098 let mut actions = receiver.accept(1001.0);
1099 while let Some(action) = actions.pop() {
1100 if let ResourceAction::SendRequest(request) = action {
1101 for action in sender.handle_request(&request, 1002.0) {
1102 if let ResourceAction::SendPart(part) = action {
1103 actions.extend(receiver.receive_part(&part, 1003.0));
1104 }
1105 }
1106 }
1107 }
1108 assert_eq!(receiver.received_count, receiver.total_parts);
1109 receiver
1110 }
1111
1112 #[test]
1113 fn parts_decrypted_ahead_assemble_as_the_parts_decrypted_at_once() {
1114 let token = rns_crypto::token::Token::new(&[0x5A; 64]).unwrap();
1115 let decrypt =
1116 |parts: &[&[u8]], skip: usize| token.decrypt_chunks(parts, skip).map_err(|_| ());
1117 for (len, sdu) in [(10usize, 100usize), (5000, 100), (5000, 97), (20_000, 464)] {
1118 let expected = encrypted_receiver(&token, len, sdu).assemble(&decrypt, &NoopCompressor);
1119 assert!(matches!(expected[1], ResourceAction::DataReceived { .. }));
1120 let total = encrypted_receiver(&token, len, sdu).total_parts;
1121 for ahead in [1, total / 2, total.saturating_sub(1), total] {
1122 if ahead == 0 {
1123 continue;
1124 }
1125 let mut receiver = encrypted_receiver(&token, len, sdu);
1126 for _ in 0..ahead {
1127 receiver.decrypt_next_part(|| {
1128 Some(token.decrypt_stream(RESOURCE_RANDOM_HASH_SIZE))
1129 });
1130 }
1131 assert_eq!(receiver.has_parts_to_decrypt(), ahead < total);
1132 let actions = receiver.assemble(
1133 &|_, _| panic!("parts decrypted ahead were decrypted again"),
1134 &NoopCompressor,
1135 );
1136 assert_eq!(actions, expected, "len {len} sdu {sdu} ahead {ahead}");
1137 }
1138 for budget in [1, 250, 1000, usize::MAX] {
1140 let mut receiver = encrypted_receiver(&token, len, sdu);
1141 let mut jobs = 0;
1142 while receiver.decrypt_held_parts(
1143 || Some(token.decrypt_stream(RESOURCE_RANDOM_HASH_SIZE)),
1144 budget,
1145 ) {
1146 jobs += 1;
1147 }
1148 assert!(!receiver.has_parts_to_decrypt());
1149 assert!(jobs < total.max(1));
1150 let actions = receiver.assemble(
1151 &|_, _| panic!("parts decrypted ahead were decrypted again"),
1152 &NoopCompressor,
1153 );
1154 assert_eq!(actions, expected, "len {len} sdu {sdu} budget {budget}");
1155 }
1156 }
1157 }
1158
1159 #[test]
1160 fn parts_fed_to_a_decryption_elsewhere_assemble_as_at_once() {
1161 let token = rns_crypto::token::Token::new(&[0x5A; 64]).unwrap();
1162 let decrypt =
1163 |parts: &[&[u8]], skip: usize| token.decrypt_chunks(parts, skip).map_err(|_| ());
1164 for (len, sdu) in [(10usize, 100usize), (5000, 100), (5000, 97), (20_000, 464)] {
1165 let expected = encrypted_receiver(&token, len, sdu).assemble(&decrypt, &NoopCompressor);
1166 let total = encrypted_receiver(&token, len, sdu).total_parts;
1167 for fed in [0, 1, total / 2, total] {
1168 let mut receiver = encrypted_receiver(&token, len, sdu);
1169 assert!(receiver.decrypts());
1170 let mut stream = token.decrypt_stream(RESOURCE_RANDOM_HASH_SIZE);
1171 receiver.prepare_decrypt_stream(&mut stream);
1172 let parts: Vec<Vec<u8>> = receiver
1174 .parts_in_order_from(0)
1175 .map(<[u8]>::to_vec)
1176 .collect();
1177 assert_eq!(parts.len(), total);
1178 assert_eq!(receiver.parts_in_order_from(total).count(), 0);
1179 for part in &parts[..fed] {
1180 stream.update(part);
1181 }
1182 receiver.resume_decrypt(Box::new(stream), fed);
1183 let actions = receiver.assemble(
1184 &|_, _| panic!("a decryption handed back is finished, not redone"),
1185 &NoopCompressor,
1186 );
1187 assert_eq!(actions, expected, "len {len} sdu {sdu} fed {fed}");
1188 }
1189 }
1190 }
1191
1192 #[test]
1193 fn parts_decrypted_ahead_under_another_key_fail_at_assembly() {
1194 let token = rns_crypto::token::Token::new(&[0x5A; 64]).unwrap();
1195 let other = rns_crypto::token::Token::new(&[0x5B; 64]).unwrap();
1196 let mut receiver = encrypted_receiver(&token, 5000, 100);
1197 receiver.decrypt_next_part(|| Some(other.decrypt_stream(RESOURCE_RANDOM_HASH_SIZE)));
1198 let actions = receiver.assemble(&|_, _| panic!("decrypted again"), &NoopCompressor);
1199 assert!(actions.contains(&ResourceAction::Failed(ResourceError::DecryptionFailed)));
1200 assert!(!actions
1201 .iter()
1202 .any(|a| matches!(a, ResourceAction::DataReceived { .. })));
1203 }
1204
1205 #[test]
1206 fn assembly_handoff_matches_synchronous_actions_and_releases_parts() {
1207 let mut receiver = ready_receiver();
1208 let job = receiver.prepare_assembly(&identity_decrypt).unwrap();
1209 assert!(receiver.part_spans.is_empty() && receiver.hashmap.is_empty());
1210 receiver.waiting_for_hmu = true;
1211 assert!(receiver
1212 .handle_hashmap_update(&receiver.resource_hash.clone(), 1004.0)
1213 .is_empty());
1214 assert!(receiver.accept(1004.0).is_empty());
1215 assert!(receiver.request_next(1004.0).is_empty());
1216 assert_eq!(receiver.assembly_id(), Some(job.id()));
1217 assert!(receiver.receive_part(b"duplicate", 1004.0).is_empty());
1218 assert!(receiver
1219 .tick(
1220 100000.0,
1221 &|_, _| panic!("pending assembly decrypted again"),
1222 &NoopCompressor
1223 )
1224 .is_empty());
1225 assert!(receiver.prepare_assembly(&identity_decrypt).is_err());
1226 let actions = receiver.complete_assembly(job.run(&NoopCompressor));
1227 let expected = ready_receiver().assemble(&identity_decrypt, &NoopCompressor);
1228 assert_eq!(format!("{actions:?}"), format!("{expected:?}"));
1229 assert_eq!(receiver.status, ResourceStatus::Complete);
1230 assert!(receiver.assembly_id().is_none());
1231 }
1232
1233 #[test]
1234 fn assembly_result_requires_original_live_attempt_even_for_same_hash() {
1235 let mut first = ready_receiver();
1236 let mut second = ready_receiver();
1237 assert_eq!(first.resource_hash, second.resource_hash);
1238 let first_job = first.prepare_assembly(&identity_decrypt).unwrap();
1239 let second_job = second.prepare_assembly(&identity_decrypt).unwrap();
1240 assert_ne!(first_job.id(), second_job.id());
1241 assert!(first
1242 .complete_assembly(second_job.run(&NoopCompressor))
1243 .is_empty());
1244 first.cancel();
1245 assert!(first
1246 .complete_assembly(first_job.run(&NoopCompressor))
1247 .is_empty());
1248 assert_eq!(first.status, ResourceStatus::Failed);
1249 }
1250
1251 #[test]
1252 fn assembly_local_failure_and_authentication_failure_cannot_publish_success() {
1253 let mut receiver = ready_receiver();
1254 assert!(receiver.prepare_assembly(&|_, _| Err(())).is_err());
1255 assert!(receiver.assembly_id().is_none());
1256 assert!(receiver.part_spans.is_empty() && receiver.hashmap.is_empty());
1257 assert!(receiver
1258 .prepare_assembly(&|_, _| panic!("failed assembly retried"))
1259 .is_err());
1260 let mut receiver = ready_receiver();
1261 let job = receiver.prepare_assembly(&identity_decrypt).unwrap();
1262 let actions = receiver.fail_assembly(job.id());
1263 assert!(matches!(
1264 actions.as_slice(),
1265 [
1266 ResourceAction::SendCancelReceiver(_),
1267 ResourceAction::Failed(ResourceError::InvalidState)
1268 ]
1269 ));
1270 assert!(receiver.fail_assembly(job.id()).is_empty());
1271 assert!(receiver
1272 .complete_assembly(job.run(&NoopCompressor))
1273 .is_empty());
1274 }
1275
1276 #[test]
1277 fn test_assembly_joins_actual_parts_independent_of_advertised_size() {
1278 let (mut sender, mut receiver) = make_sender_receiver();
1279 let mut stream = Vec::new();
1280 for action in receiver.accept(1001.0) {
1281 if let ResourceAction::SendRequest(request) = action {
1282 for response in sender.handle_request(&request, 1002.0) {
1283 if let ResourceAction::SendPart(part) = response {
1284 stream.extend_from_slice(&part);
1285 }
1286 }
1287 }
1288 }
1289 receiver.part_data = stream.to_vec();
1291 receiver.part_spans = (0..stream.len())
1292 .step_by(7)
1293 .map(|start| Some((start, (stream.len() - start).min(7))))
1294 .collect();
1295 receiver.total_parts = receiver.part_spans.len();
1296 receiver.received_count = receiver.total_parts;
1297 receiver.transfer_size = u64::MAX;
1298 let actions = receiver.assemble(
1299 &|parts, skip| {
1300 assert_eq!(parts.len(), stream.len().div_ceil(7));
1302 assert!(parts.iter().all(|part| part.len() <= 7));
1303 assert_eq!(parts.concat(), stream);
1304 identity_decrypt(parts, skip)
1305 },
1306 &NoopCompressor,
1307 );
1308 assert!(actions.iter().any(|action| matches!(action,
1309 ResourceAction::DataReceived { data, .. } if data == b"Hello, Resource Transfer!")));
1310 assert_eq!(receiver.status, ResourceStatus::Complete);
1311 }
1312
1313 #[test]
1314 fn test_assembly_rejects_missing_part_before_decryption() {
1315 let (_, mut receiver) = make_sender_receiver();
1316 receiver.received_count = receiver.total_parts;
1317 let actions = receiver.assemble(
1318 &|_, _| panic!("missing part reached decryption"),
1319 &NoopCompressor,
1320 );
1321 assert!(matches!(
1322 actions.as_slice(),
1323 [ResourceAction::Failed(ResourceError::InvalidState)]
1324 ));
1325 assert_eq!(receiver.status, ResourceStatus::Failed);
1326 }
1327
1328 #[test]
1329 fn test_from_advertisement() {
1330 let (sender, receiver) = make_sender_receiver();
1331 assert_eq!(receiver.total_parts, sender.total_parts());
1332 assert_eq!(receiver.transfer_size, sender.transfer_size as u64);
1333 assert_eq!(receiver.resource_hash, sender.resource_hash.to_vec());
1334 assert!(!receiver.advertisement_packet.is_empty());
1335 assert_eq!(
1336 receiver.max_decompressed_size,
1337 RESOURCE_AUTO_COMPRESS_MAX_SIZE
1338 );
1339 }
1340
1341 #[test]
1342 fn test_from_advertisement_rejects_huge_part_count() {
1343 let adv = ResourceAdvertisement {
1344 transfer_size: 1024,
1345 data_size: 1024,
1346 num_parts: u64::MAX,
1347 resource_hash: vec![0x11; 32],
1348 random_hash: vec![0x22; RESOURCE_RANDOM_HASH_SIZE],
1349 original_hash: vec![0x33; 32],
1350 hashmap: vec![0x44; RESOURCE_MAPHASH_LEN],
1351 flags: AdvFlags {
1352 encrypted: false,
1353 compressed: false,
1354 split: false,
1355 is_request: false,
1356 is_response: false,
1357 has_metadata: false,
1358 app_flag: false,
1359 },
1360 segment_index: 1,
1361 total_segments: 1,
1362 request_id: None,
1363 };
1364
1365 let err = match ResourceReceiver::from_advertisement(
1366 &adv.pack(0),
1367 RESOURCE_SDU,
1368 0.5,
1369 1000.0,
1370 None,
1371 None,
1372 ) {
1373 Ok(_) => panic!("huge advertisement should be rejected"),
1374 Err(err) => err,
1375 };
1376
1377 assert_eq!(err, ResourceError::PartCountMismatch);
1378 }
1379
1380 fn sized_advertisement(transfer_size: u64, num_parts: u64) -> ResourceAdvertisement {
1381 ResourceAdvertisement {
1382 transfer_size,
1383 data_size: transfer_size,
1384 num_parts,
1385 resource_hash: vec![0x11; 32],
1386 random_hash: vec![0x22; RESOURCE_RANDOM_HASH_SIZE],
1387 original_hash: vec![0x33; 32],
1388 hashmap: vec![0x44; RESOURCE_MAPHASH_LEN * num_parts.min(512) as usize],
1389 flags: AdvFlags {
1390 encrypted: true,
1391 compressed: false,
1392 split: false,
1393 is_request: false,
1394 is_response: false,
1395 has_metadata: false,
1396 app_flag: false,
1397 },
1398 segment_index: 1,
1399 total_segments: 1,
1400 request_id: None,
1401 }
1402 }
1403
1404 fn receive_advertisement(
1405 adv: &ResourceAdvertisement,
1406 sdu: usize,
1407 ) -> Result<ResourceReceiver, ResourceError> {
1408 ResourceReceiver::from_advertisement(&adv.pack(0), sdu, 0.5, 1000.0, None, None)
1409 }
1410
1411 #[test]
1412 fn part_count_above_transfer_size_is_refused() {
1413 let adv = sized_advertisement(1, 140_000);
1414 assert_eq!(
1415 receive_advertisement(&adv, RESOURCE_SDU).err(),
1416 Some(ResourceError::PartCountMismatch)
1417 );
1418 assert_eq!(
1419 receive_advertisement(&adv, 1).err(),
1420 Some(ResourceError::PartCountMismatch)
1421 );
1422 }
1423
1424 #[test]
1425 fn part_count_below_transfer_size_is_refused() {
1426 let sdu = RESOURCE_SDU;
1427 let adv = sized_advertisement(10 * sdu as u64 + 1, 10);
1428 assert_eq!(
1429 receive_advertisement(&adv, sdu).err(),
1430 Some(ResourceError::PartCountMismatch)
1431 );
1432 }
1433
1434 #[test]
1435 fn part_count_matching_transfer_size_is_accepted() {
1436 let sdu = RESOURCE_SDU;
1437 let receiver = receive_advertisement(&sized_advertisement(1, 1), sdu).unwrap();
1438 assert_eq!(receiver.total_parts, 1);
1439
1440 let receiver = receive_advertisement(&sized_advertisement(sdu as u64 + 1, 2), sdu).unwrap();
1441 assert_eq!(receiver.total_parts, 2);
1442 }
1443
1444 #[test]
1445 fn transfer_size_on_sdu_boundary_needs_no_extra_part() {
1446 let sdu = RESOURCE_SDU;
1447 let exact = 3 * sdu as u64;
1448 let receiver = receive_advertisement(&sized_advertisement(exact, 3), sdu).unwrap();
1449 assert_eq!(receiver.total_parts, 3);
1450 assert_eq!(
1451 receive_advertisement(&sized_advertisement(exact, 4), sdu).err(),
1452 Some(ResourceError::PartCountMismatch)
1453 );
1454 assert_eq!(
1455 receive_advertisement(&sized_advertisement(exact, 2), sdu).err(),
1456 Some(ResourceError::PartCountMismatch)
1457 );
1458 }
1459
1460 #[test]
1461 fn split_segment_part_count_follows_segment_transfer_size() {
1462 let sdu = RESOURCE_SDU;
1463 let segment_size = RESOURCE_MAX_EFFICIENT_SIZE as u64 + 61;
1464 let segment_parts = segment_size.div_ceil(sdu as u64);
1465 let mut adv = sized_advertisement(segment_size, segment_parts);
1466 adv.data_size = 3 * RESOURCE_MAX_EFFICIENT_SIZE as u64;
1467 adv.flags.split = true;
1468 adv.segment_index = 2;
1469 adv.total_segments = 3;
1470
1471 let receiver = receive_advertisement(&adv, sdu).unwrap();
1472 assert_eq!(receiver.total_parts as u64, segment_parts);
1473 assert_eq!(receiver.segment_index, 2);
1474
1475 adv.num_parts = adv.data_size.div_ceil(sdu as u64);
1477 assert_eq!(
1478 receive_advertisement(&adv, sdu).err(),
1479 Some(ResourceError::PartCountMismatch)
1480 );
1481 }
1482
1483 #[test]
1484 fn transfer_size_above_segment_limit_is_refused_up_front() {
1485 let limit = 3 * RESOURCE_MAX_EFFICIENT_SIZE as u64;
1486 let adv = sized_advertisement(limit + 1, (limit + 1).div_ceil(RESOURCE_SDU as u64));
1487 assert_eq!(
1488 receive_advertisement(&adv, RESOURCE_SDU).err(),
1489 Some(ResourceError::InvalidAdvertisement)
1490 );
1491 }
1492
1493 #[test]
1494 fn idle_accepted_advertisement_holds_no_part_storage() {
1495 let sdu = 48;
1496 let limit = 3 * RESOURCE_MAX_EFFICIENT_SIZE as u64;
1497 let parts = limit.div_ceil(sdu as u64);
1498 let mut receiver = receive_advertisement(&sized_advertisement(limit, parts), sdu).unwrap();
1499 assert_eq!(receiver.total_parts as u64, parts);
1500
1501 let actions = receiver.accept(1000.0);
1502 assert!(actions
1503 .iter()
1504 .any(|action| matches!(action, ResourceAction::SendRequest(_))));
1505 assert_eq!(receiver.part_spans.capacity(), 0);
1506 assert_eq!(receiver.part_data.capacity(), 0);
1507 assert!(receiver.hashmap.capacity() <= RESOURCE_HASHMAP_MAX_LEN);
1508 assert_eq!(receiver.hashmap.len(), receiver.hashmap_height);
1509 }
1510
1511 #[test]
1512 fn part_storage_grows_with_received_parts() {
1513 let mut rng = rns_crypto::FixedRng::new(&[0x42; 64]);
1514 let sdu = 8;
1515 let data: Vec<u8> = (0..100u8).collect();
1516 let mut sender = ResourceSender::new(
1517 &data,
1518 None,
1519 sdu,
1520 &identity_encrypt,
1521 &NoopCompressor,
1522 &mut rng,
1523 1000.0,
1524 false,
1525 false,
1526 None,
1527 1,
1528 1,
1529 None,
1530 0.5,
1531 6.0,
1532 )
1533 .unwrap();
1534 let mut receiver = ResourceReceiver::from_advertisement(
1535 &sender.get_advertisement(0),
1536 sdu,
1537 0.5,
1538 1000.0,
1539 None,
1540 None,
1541 )
1542 .unwrap();
1543 assert!(receiver.total_parts > 1);
1544 assert!(receiver.part_spans.is_empty());
1545
1546 let request = receiver
1547 .accept(1000.0)
1548 .into_iter()
1549 .find_map(|action| match action {
1550 ResourceAction::SendRequest(request) => Some(request),
1551 _ => None,
1552 })
1553 .unwrap();
1554 let first_part = sender
1555 .handle_request(&request, 1001.0)
1556 .into_iter()
1557 .find_map(|action| match action {
1558 ResourceAction::SendPart(part) => Some(part),
1559 _ => None,
1560 })
1561 .unwrap();
1562 receiver.receive_part(&first_part, 1002.0);
1563 assert_eq!(receiver.received_count, 1);
1564 assert_eq!(receiver.part_spans.len(), 1);
1565 }
1566
1567 #[test]
1568 fn test_from_advertisement_rejects_oversized_transfer() {
1569 let adv = ResourceAdvertisement {
1570 transfer_size: RESOURCE_AUTO_COMPRESS_MAX_SIZE as u64 + 1,
1571 data_size: 1024,
1572 num_parts: 1,
1573 resource_hash: vec![0x11; 32],
1574 random_hash: vec![0x22; RESOURCE_RANDOM_HASH_SIZE],
1575 original_hash: vec![0x33; 32],
1576 hashmap: vec![0x44; RESOURCE_MAPHASH_LEN],
1577 flags: AdvFlags {
1578 encrypted: false,
1579 compressed: false,
1580 split: false,
1581 is_request: false,
1582 is_response: false,
1583 has_metadata: false,
1584 app_flag: false,
1585 },
1586 segment_index: 1,
1587 total_segments: 1,
1588 request_id: None,
1589 };
1590
1591 let err = match ResourceReceiver::from_advertisement(
1592 &adv.pack(0),
1593 RESOURCE_SDU,
1594 0.5,
1595 1000.0,
1596 None,
1597 None,
1598 ) {
1599 Ok(_) => panic!("oversized advertisement should be rejected"),
1600 Err(err) => err,
1601 };
1602
1603 assert_eq!(err, ResourceError::InvalidAdvertisement);
1604 }
1605
1606 #[test]
1607 fn test_accept() {
1608 let (_, mut receiver) = make_sender_receiver();
1609 let actions = receiver.accept(1000.0);
1610 assert_eq!(receiver.status, ResourceStatus::Transferring);
1611 assert!(!actions.is_empty());
1612 assert!(actions
1613 .iter()
1614 .any(|a| matches!(a, ResourceAction::SendRequest(_))));
1615 }
1616
1617 #[test]
1618 fn test_reject() {
1619 let (_, mut receiver) = make_sender_receiver();
1620 let actions = receiver.reject();
1621 assert_eq!(receiver.status, ResourceStatus::Rejected);
1622 assert!(actions
1623 .iter()
1624 .any(|a| matches!(a, ResourceAction::SendCancelReceiver(_))));
1625 }
1626
1627 #[test]
1628 fn test_cancel_is_distinct_from_advertisement_rejection() {
1629 let (_, mut receiver) = make_sender_receiver();
1630 receiver.accept(1000.0);
1631 let actions = receiver.cancel();
1632 assert_eq!(receiver.status, ResourceStatus::Failed);
1633 assert!(actions
1634 .iter()
1635 .any(|a| matches!(a, ResourceAction::SendCancelReceiver(_))));
1636 assert!(receiver.cancel().is_empty());
1637 }
1638
1639 fn hmu(resource_hash: &[u8], segment: u64, hashmap: Vec<u8>) -> Vec<u8> {
1640 let payload = crate::msgpack::pack(&crate::msgpack::Value::Array(vec![
1641 crate::msgpack::Value::UInt(segment),
1642 crate::msgpack::Value::Bin(hashmap),
1643 ]));
1644 let mut data = resource_hash.to_vec();
1645 data.extend_from_slice(&payload);
1646 data
1647 }
1648
1649 #[test]
1650 fn test_unsolicited_hmu_is_ignored_without_refreshing_timeout_state() {
1651 let (_, mut receiver) = make_sender_receiver();
1652 receiver.status = ResourceStatus::Transferring;
1653 receiver.waiting_for_hmu = false;
1654 receiver.last_activity = 1000.0;
1655 receiver.retries_left = 1;
1656 let update = hmu(&receiver.resource_hash, 0, vec![0x99; RESOURCE_MAPHASH_LEN]);
1657
1658 let actions = receiver.handle_hashmap_update(&update, 2000.0);
1659
1660 assert!(actions.is_empty());
1661 assert_eq!(receiver.status, ResourceStatus::Transferring);
1662 assert!(!receiver.waiting_for_hmu);
1663 assert_eq!(receiver.last_activity, 1000.0);
1664 assert_eq!(receiver.retries_left, 1);
1665 }
1666
1667 #[test]
1668 fn test_empty_hmu_cancels_waiting_receiver() {
1669 let (_, mut receiver) = make_sender_receiver();
1670 receiver.status = ResourceStatus::Transferring;
1671 receiver.waiting_for_hmu = true;
1672 let update = hmu(&receiver.resource_hash, 1, Vec::new());
1673
1674 let actions = receiver.handle_hashmap_update(&update, 1001.0);
1675
1676 assert_eq!(receiver.status, ResourceStatus::Failed);
1677 assert!(actions
1678 .iter()
1679 .any(|action| matches!(action, ResourceAction::SendCancelReceiver(_))));
1680 assert!(!actions
1681 .iter()
1682 .any(|action| matches!(action, ResourceAction::SendRequest(_))));
1683 }
1684
1685 #[test]
1686 fn test_malformed_hmu_cancels_waiting_receiver() {
1687 let (_, mut receiver) = make_sender_receiver();
1688 receiver.status = ResourceStatus::Transferring;
1689 receiver.waiting_for_hmu = true;
1690 let mut malformed = receiver.resource_hash.clone();
1691 malformed.extend_from_slice(&[0xc1]);
1692
1693 let actions = receiver.handle_hashmap_update(&malformed, 1001.0);
1694
1695 assert_eq!(receiver.status, ResourceStatus::Failed);
1696 assert!(actions
1697 .iter()
1698 .any(|action| matches!(action, ResourceAction::SendCancelReceiver(_))));
1699 }
1700
1701 #[test]
1702 fn test_hmu_without_payload_cancels_waiting_receiver() {
1703 let (_, mut receiver) = make_sender_receiver();
1704 receiver.status = ResourceStatus::Transferring;
1705 receiver.waiting_for_hmu = true;
1706 let update = receiver.resource_hash.clone();
1707
1708 let actions = receiver.handle_hashmap_update(&update, 1001.0);
1709
1710 assert_eq!(receiver.status, ResourceStatus::Failed);
1711 assert!(actions
1712 .iter()
1713 .any(|action| matches!(action, ResourceAction::SendCancelReceiver(_))));
1714 }
1715
1716 #[test]
1717 fn test_hmu_for_another_resource_is_ignored() {
1718 let (_, mut receiver) = make_sender_receiver();
1719 receiver.status = ResourceStatus::Transferring;
1720 receiver.waiting_for_hmu = true;
1721 receiver.last_activity = 1000.0;
1722 let update = hmu(&[0xEE; 32], 0, vec![0x99; RESOURCE_MAPHASH_LEN]);
1723
1724 let actions = receiver.handle_hashmap_update(&update, 2000.0);
1725
1726 assert!(actions.is_empty());
1727 assert!(receiver.waiting_for_hmu);
1728 assert_eq!(receiver.last_activity, 1000.0);
1729 }
1730
1731 #[test]
1732 fn test_misaligned_hmu_hashmap_cancels_waiting_receiver() {
1733 let (_, mut receiver) = make_sender_receiver();
1734 receiver.status = ResourceStatus::Transferring;
1735 receiver.waiting_for_hmu = true;
1736 let update = hmu(
1737 &receiver.resource_hash,
1738 0,
1739 vec![0x99; RESOURCE_MAPHASH_LEN + 1],
1740 );
1741
1742 let actions = receiver.handle_hashmap_update(&update, 1001.0);
1743
1744 assert_eq!(receiver.status, ResourceStatus::Failed);
1745 assert!(actions
1746 .iter()
1747 .any(|action| matches!(action, ResourceAction::SendCancelReceiver(_))));
1748 }
1749
1750 #[test]
1751 fn test_out_of_range_hmu_segment_cancels_waiting_receiver() {
1752 let (_, mut receiver) = make_sender_receiver();
1753 receiver.status = ResourceStatus::Transferring;
1754 receiver.waiting_for_hmu = true;
1755 let update = hmu(
1756 &receiver.resource_hash,
1757 u64::MAX,
1758 vec![0x99; RESOURCE_MAPHASH_LEN],
1759 );
1760
1761 let actions = receiver.handle_hashmap_update(&update, 1001.0);
1762
1763 assert_eq!(receiver.status, ResourceStatus::Failed);
1764 assert!(actions
1765 .iter()
1766 .any(|action| matches!(action, ResourceAction::SendCancelReceiver(_))));
1767 }
1768
1769 #[test]
1770 fn test_tick_is_inert_once_receiver_starts_assembling() {
1771 for status in [
1772 ResourceStatus::Assembling,
1773 ResourceStatus::Complete,
1774 ResourceStatus::Failed,
1775 ResourceStatus::Corrupt,
1776 ResourceStatus::Rejected,
1777 ] {
1778 let (_, mut receiver) = make_sender_receiver();
1779 receiver.status = status;
1780 receiver.last_activity = 1.0;
1781 receiver.retries_left = 0;
1782
1783 let actions = receiver.tick(10_000.0, &identity_decrypt, &NoopCompressor);
1784
1785 assert!(actions.is_empty(), "tick emitted an action for {status:?}");
1786 assert_eq!(receiver.status, status);
1787 assert_eq!(receiver.last_activity, 1.0);
1788 }
1789 }
1790
1791 #[test]
1792 fn test_receive_part_stores() {
1793 let (mut sender, mut receiver) = make_sender_receiver();
1794 receiver.accept(1000.0);
1795
1796 let mut request = Vec::new();
1799 request.push(RESOURCE_HASHMAP_IS_NOT_EXHAUSTED);
1800 request.extend_from_slice(&sender.resource_hash);
1801 request.extend_from_slice(&sender.part_hashes[0]);
1802
1803 let send_actions = sender.handle_request(&request, 1001.0);
1804 let part_data = send_actions
1805 .iter()
1806 .find_map(|a| match a {
1807 ResourceAction::SendPart(d) => Some(d.clone()),
1808 _ => None,
1809 })
1810 .unwrap();
1811
1812 receiver.req_sent = 1000.5;
1814 let _actions = receiver.receive_part(&part_data, 1001.0);
1815 assert_eq!(receiver.received_count, 1);
1816 }
1817
1818 #[test]
1819 fn test_consecutive_completed_height() {
1820 let (sender, mut receiver) = make_sender_receiver();
1821 receiver.accept(1000.0);
1822
1823 if sender.total_parts() > 1 {
1825 assert_eq!(receiver.consecutive_completed_height, -1);
1827 }
1828 }
1829
1830 #[test]
1831 fn receive_part_search_window_matches_request_after_completed_height() {
1832 let mut rng = rns_crypto::FixedRng::new(&[0x5a; 64]);
1833 let data: Vec<u8> = (0..48).collect();
1834 let mut sender = ResourceSender::new(
1835 &data,
1836 None,
1837 8,
1838 &identity_encrypt,
1839 &NoopCompressor,
1840 &mut rng,
1841 1000.0,
1842 false,
1843 false,
1844 None,
1845 1,
1846 1,
1847 None,
1848 0.5,
1849 6.0,
1850 )
1851 .unwrap();
1852 assert!(sender.total_parts() > 5);
1853
1854 let advertisement = sender.get_advertisement(0);
1855 let mut receiver =
1856 ResourceReceiver::from_advertisement(&advertisement, 8, 0.5, 1000.0, None, None)
1857 .unwrap();
1858 let first_request = receiver
1859 .accept(1000.0)
1860 .into_iter()
1861 .find_map(|action| match action {
1862 ResourceAction::SendRequest(data) => Some(data),
1863 _ => None,
1864 })
1865 .unwrap();
1866 let first_parts = sender.handle_request(&first_request, 1001.0);
1867 let first_part = first_parts
1868 .iter()
1869 .find_map(|action| match action {
1870 ResourceAction::SendPart(data)
1871 if map_hash(data, &receiver.random_hash) == sender.part_hashes[0] =>
1872 {
1873 Some(data.clone())
1874 }
1875 _ => None,
1876 })
1877 .unwrap();
1878 receiver.receive_part(&first_part, 1002.0);
1879 assert_eq!(receiver.consecutive_completed_height, 0);
1880
1881 let next_request = receiver
1882 .request_next(1003.0)
1883 .into_iter()
1884 .find_map(|action| match action {
1885 ResourceAction::SendRequest(data) => Some(data),
1886 _ => None,
1887 })
1888 .unwrap();
1889 let next_parts = sender.handle_request(&next_request, 1004.0);
1890 let window_tail_index = receiver.window.window;
1891 let window_tail = next_parts
1892 .iter()
1893 .find_map(|action| match action {
1894 ResourceAction::SendPart(data)
1895 if map_hash(data, &receiver.random_hash)
1896 == sender.part_hashes[window_tail_index] =>
1897 {
1898 Some(data.clone())
1899 }
1900 _ => None,
1901 })
1902 .expect("request must include the inclusive tail of the next window");
1903
1904 let actions = receiver.receive_part(&window_tail, 1005.0);
1905 assert!(receiver.part_spans[window_tail_index].is_some());
1906 assert_eq!(receiver.received_count, 2);
1907 assert_eq!(receiver.consecutive_completed_height, 0);
1908 assert!(actions.iter().any(|action| matches!(
1909 action,
1910 ResourceAction::ProgressUpdate {
1911 received: 2,
1912 total,
1913 } if *total == receiver.total_parts
1914 )));
1915 }
1916
1917 #[test]
1918 fn test_handle_cancel() {
1919 let (_, mut receiver) = make_sender_receiver();
1920 receiver.accept(1000.0);
1921 let _actions = receiver.handle_cancel();
1922 assert_eq!(receiver.status, ResourceStatus::Failed);
1923 }
1924
1925 #[test]
1926 fn test_assemble_compressed_resource_rejects_oversized_decompression() {
1927 let data = b"oversized!";
1928 let mut rng = rns_crypto::FixedRng::new(&[0x93; 64]);
1929
1930 let mut sender = ResourceSender::new(
1931 data,
1932 None,
1933 RESOURCE_SDU,
1934 &identity_encrypt,
1935 &ExpandingCompressor,
1936 &mut rng,
1937 1000.0,
1938 true,
1939 false,
1940 None,
1941 1,
1942 1,
1943 None,
1944 0.5,
1945 6.0,
1946 )
1947 .unwrap();
1948
1949 let adv = sender.get_advertisement(0);
1950 let mut receiver =
1951 ResourceReceiver::from_advertisement(&adv, RESOURCE_SDU, 0.5, 1000.0, None, None)
1952 .unwrap();
1953 receiver.max_decompressed_size = data.len() - 1;
1954
1955 let request_data = receiver
1956 .accept(1001.0)
1957 .into_iter()
1958 .find_map(|a| match a {
1959 ResourceAction::SendRequest(d) => Some(d),
1960 _ => None,
1961 })
1962 .unwrap();
1963
1964 let send_actions = sender.handle_request(&request_data, 1002.0);
1965 receiver.req_sent = 1001.0;
1966 for action in &send_actions {
1967 if let ResourceAction::SendPart(part_data) = action {
1968 receiver.receive_part(part_data, 1003.0);
1969 }
1970 }
1971
1972 let assemble_actions = receiver.assemble(&identity_decrypt, &ExpandingCompressor);
1973 assert_eq!(receiver.status, ResourceStatus::Corrupt);
1974 assert!(assemble_actions
1975 .iter()
1976 .any(|a| matches!(a, ResourceAction::SendCancelReceiver(_))));
1977 assert!(assemble_actions
1978 .iter()
1979 .any(|a| matches!(a, ResourceAction::Failed(ResourceError::TooLarge))));
1980 assert!(assemble_actions
1981 .iter()
1982 .any(|a| matches!(a, ResourceAction::TeardownLink)));
1983 }
1984
1985 #[test]
1986 fn test_full_transfer_small_data() {
1987 let data = b"small data";
1989 let mut rng = rns_crypto::FixedRng::new(&[0x77; 64]);
1990
1991 let mut sender = ResourceSender::new(
1992 data,
1993 None,
1994 RESOURCE_SDU,
1995 &identity_encrypt,
1996 &NoopCompressor,
1997 &mut rng,
1998 1000.0,
1999 false,
2000 false,
2001 None,
2002 1,
2003 1,
2004 None,
2005 0.5,
2006 6.0,
2007 )
2008 .unwrap();
2009
2010 let adv = sender.get_advertisement(0);
2011 let mut receiver =
2012 ResourceReceiver::from_advertisement(&adv, RESOURCE_SDU, 0.5, 1000.0, None, None)
2013 .unwrap();
2014
2015 let req_actions = receiver.accept(1001.0);
2017 assert_eq!(receiver.status, ResourceStatus::Transferring);
2018
2019 let request_data = req_actions
2021 .iter()
2022 .find_map(|a| match a {
2023 ResourceAction::SendRequest(d) => Some(d.clone()),
2024 _ => None,
2025 })
2026 .unwrap();
2027
2028 let send_actions = sender.handle_request(&request_data, 1002.0);
2030
2031 receiver.req_sent = 1001.0;
2033 for action in &send_actions {
2034 if let ResourceAction::SendPart(part_data) = action {
2035 receiver.receive_part(part_data, 1003.0);
2036 }
2037 }
2038
2039 assert_eq!(receiver.received_count, receiver.total_parts);
2040
2041 let assemble_actions = receiver.assemble(&identity_decrypt, &NoopCompressor);
2043
2044 let has_proof = assemble_actions
2046 .iter()
2047 .any(|a| matches!(a, ResourceAction::SendProof(_)));
2048 let has_data = assemble_actions
2049 .iter()
2050 .any(|a| matches!(a, ResourceAction::DataReceived { .. }));
2051 let has_complete = assemble_actions
2052 .iter()
2053 .any(|a| matches!(a, ResourceAction::Completed));
2054
2055 assert!(has_proof, "Should send proof");
2056 assert!(has_data, "Should return data");
2057 assert!(has_complete, "Should be completed");
2058
2059 let received_data = assemble_actions
2061 .iter()
2062 .find_map(|a| match a {
2063 ResourceAction::DataReceived { data, .. } => Some(data.clone()),
2064 _ => None,
2065 })
2066 .unwrap();
2067 assert_eq!(received_data, data);
2068
2069 let proof_data = assemble_actions
2071 .iter()
2072 .find_map(|a| match a {
2073 ResourceAction::SendProof(d) => Some(d.clone()),
2074 _ => None,
2075 })
2076 .unwrap();
2077
2078 let _proof_actions = sender.handle_proof(&proof_data, 1004.0);
2079 assert_eq!(sender.status, ResourceStatus::Complete);
2080 }
2081
2082 fn run_transfer(
2085 sender: &mut ResourceSender,
2086 receiver: &mut ResourceReceiver,
2087 mut pending: Vec<ResourceAction>,
2088 ) -> (Vec<u8>, Vec<ResourceAction>) {
2089 let mut now = 1001.0;
2090 let mut request_flags = Vec::new();
2091 for _ in 0..1000 {
2092 if receiver.received_count == receiver.total_parts {
2093 break;
2094 }
2095 now += 0.01;
2096 let mut next = Vec::new();
2097 for action in pending.drain(..) {
2098 if let ResourceAction::SendRequest(request) = action {
2099 request_flags.push(request[0]);
2100 for reply in sender.handle_request(&request, now) {
2101 match reply {
2102 ResourceAction::SendPart(part) => {
2103 next.extend(receiver.receive_part(&part, now))
2104 }
2105 ResourceAction::SendHmu(hmu) => {
2106 next.extend(receiver.handle_hashmap_update(&hmu, now))
2107 }
2108 other => panic!("unexpected sender action {other:?}"),
2109 }
2110 }
2111 }
2112 }
2113 pending = next;
2114 }
2115 assert_eq!(receiver.received_count, receiver.total_parts);
2116 (
2117 request_flags,
2118 receiver.assemble(&identity_decrypt, &NoopCompressor),
2119 )
2120 }
2121
2122 fn multi_hashmap_sender(advertisement_mdu: usize) -> (Vec<u8>, ResourceSender) {
2123 let mut rng = rns_crypto::FixedRng::new(&[0x5a; 64]);
2124 let data: Vec<u8> = (0..(RESOURCE_HASHMAP_MAX_LEN + 26) * RESOURCE_SDU)
2126 .map(|i| (i * 7 + i / 251) as u8)
2127 .collect();
2128 let mut sender = ResourceSender::new(
2129 &data,
2130 None,
2131 RESOURCE_SDU,
2132 &identity_encrypt,
2133 &NoopCompressor,
2134 &mut rng,
2135 1000.0,
2136 false,
2137 false,
2138 None,
2139 1,
2140 1,
2141 None,
2142 0.5,
2143 6.0,
2144 )
2145 .unwrap();
2146 assert!(sender.total_parts() > RESOURCE_HASHMAP_MAX_LEN);
2147 sender.advertisement_mdu = advertisement_mdu;
2148 (data, sender)
2149 }
2150
2151 #[test]
2152 fn advertisement_too_large_for_link_mdu_defers_the_initial_hashmap() {
2153 let (data, mut sender) = multi_hashmap_sender(200);
2157 let adv = sender.advertise(1000.0);
2158 let ResourceAction::SendAdvertisement(adv_data) = &adv[0] else {
2159 panic!("expected an advertisement");
2160 };
2161 assert!(adv_data.len() <= 200);
2162 assert!(ResourceAdvertisement::unpack(adv_data)
2163 .unwrap()
2164 .hashmap
2165 .is_empty());
2166
2167 let mut receiver =
2168 ResourceReceiver::from_advertisement(adv_data, RESOURCE_SDU, 0.5, 1000.0, None, None)
2169 .unwrap();
2170 let first = receiver.accept(1000.5);
2171 let [ResourceAction::SendRequest(request)] = first.as_slice() else {
2172 panic!("expected one request, got {first:?}");
2173 };
2174 assert_eq!(request[0], RESOURCE_HASHMAP_IS_EMPTY);
2176 assert_eq!(request.len(), 1 + 32);
2177 assert!(receiver.waiting_for_hmu);
2178
2179 let (flags, actions) = run_transfer(&mut sender, &mut receiver, first);
2180 assert_eq!(flags[0], RESOURCE_HASHMAP_IS_EMPTY);
2181 assert!(
2182 flags.contains(&RESOURCE_HASHMAP_IS_EXHAUSTED),
2183 "the second hashmap segment still uses the exhausted path: {flags:?}"
2184 );
2185 let received = actions.iter().find_map(|a| match a {
2186 ResourceAction::DataReceived { data, .. } => Some(data.clone()),
2187 _ => None,
2188 });
2189 assert_eq!(received.as_deref(), Some(data.as_slice()));
2190 let proof = actions
2191 .iter()
2192 .find_map(|a| match a {
2193 ResourceAction::SendProof(proof) => Some(proof.clone()),
2194 _ => None,
2195 })
2196 .unwrap();
2197 sender.handle_proof(&proof, 1100.0);
2198 assert_eq!(sender.status, ResourceStatus::Complete);
2199 }
2200
2201 #[test]
2202 fn advertisement_within_link_mdu_keeps_its_hashmap() {
2203 let (data, mut sender) = multi_hashmap_sender(LINK_MDU);
2204 let adv = sender.get_advertisement(0);
2205 assert!(adv.len() <= LINK_MDU);
2206 assert_eq!(
2207 ResourceAdvertisement::unpack(&adv).unwrap().hashmap.len(),
2208 RESOURCE_HASHMAP_MAX_LEN * RESOURCE_MAPHASH_LEN
2209 );
2210 let mut receiver =
2211 ResourceReceiver::from_advertisement(&adv, RESOURCE_SDU, 0.5, 1000.0, None, None)
2212 .unwrap();
2213 let first = receiver.accept(1001.0);
2214 let (flags, actions) = run_transfer(&mut sender, &mut receiver, first);
2215 assert_eq!(flags[0], RESOURCE_HASHMAP_IS_NOT_EXHAUSTED);
2216 assert!(!flags.contains(&RESOURCE_HASHMAP_IS_EMPTY));
2217 assert!(actions.iter().any(
2218 |a| matches!(a, ResourceAction::DataReceived { data: received, .. } if *received == data)
2219 ));
2220 }
2221
2222 #[test]
2223 fn app_flag_reaches_the_receiver() {
2224 let (mut sender, _) = make_sender_receiver();
2225 sender.flags.app_flag = true;
2226 let adv = sender.get_advertisement(0);
2227 let unpacked = ResourceAdvertisement::unpack(&adv).unwrap();
2228 assert_eq!(unpacked.flags.to_byte() & 0x40, 0x40);
2229 let mut receiver =
2230 ResourceReceiver::from_advertisement(&adv, RESOURCE_SDU, 0.5, 1000.0, None, None)
2231 .unwrap();
2232 assert!(receiver.flags.app_flag);
2233 let first = receiver.accept(1001.0);
2234 let (_, actions) = run_transfer(&mut sender, &mut receiver, first);
2235 assert!(actions
2236 .iter()
2237 .any(|a| matches!(a, ResourceAction::DataReceived { app_flag: true, .. })));
2238
2239 let (mut sender, mut receiver) = make_sender_receiver();
2240 let first = receiver.accept(1001.0);
2241 let (_, actions) = run_transfer(&mut sender, &mut receiver, first);
2242 assert!(actions.iter().any(|a| matches!(
2243 a,
2244 ResourceAction::DataReceived {
2245 app_flag: false,
2246 ..
2247 }
2248 )));
2249 }
2250
2251 #[test]
2252 fn test_full_transfer_with_metadata() {
2253 let data = b"data with metadata";
2254 let metadata = b"some metadata";
2255 let mut rng = rns_crypto::FixedRng::new(&[0x88; 64]);
2256
2257 let mut sender = ResourceSender::new(
2258 data,
2259 Some(metadata),
2260 RESOURCE_SDU,
2261 &identity_encrypt,
2262 &NoopCompressor,
2263 &mut rng,
2264 1000.0,
2265 false,
2266 false,
2267 None,
2268 1,
2269 1,
2270 None,
2271 0.5,
2272 6.0,
2273 )
2274 .unwrap();
2275
2276 assert!(sender.flags.has_metadata);
2277
2278 let adv = sender.get_advertisement(0);
2279 let mut receiver =
2280 ResourceReceiver::from_advertisement(&adv, RESOURCE_SDU, 0.5, 1000.0, None, None)
2281 .unwrap();
2282
2283 assert!(receiver.has_metadata);
2284
2285 let req_actions = receiver.accept(1001.0);
2287 let request_data = req_actions
2288 .iter()
2289 .find_map(|a| match a {
2290 ResourceAction::SendRequest(d) => Some(d.clone()),
2291 _ => None,
2292 })
2293 .unwrap();
2294
2295 let send_actions = sender.handle_request(&request_data, 1002.0);
2296 receiver.req_sent = 1001.0;
2297 for action in &send_actions {
2298 if let ResourceAction::SendPart(part_data) = action {
2299 receiver.receive_part(part_data, 1003.0);
2300 }
2301 }
2302
2303 let assemble_actions = receiver.assemble(&identity_decrypt, &NoopCompressor);
2304
2305 let (recv_data, recv_meta) = assemble_actions
2306 .iter()
2307 .find_map(|a| match a {
2308 ResourceAction::DataReceived { data, metadata, .. } => {
2309 Some((data.clone(), metadata.clone()))
2310 }
2311 _ => None,
2312 })
2313 .unwrap();
2314
2315 assert_eq!(recv_data, data);
2316 assert_eq!(recv_meta.unwrap(), metadata);
2317 }
2318
2319 #[test]
2320 fn test_previous_window_restore() {
2321 let (_, _receiver) = make_sender_receiver();
2322 let adv_data = {
2324 let mut rng = rns_crypto::FixedRng::new(&[0x42; 64]);
2325 let sender = ResourceSender::new(
2326 b"test",
2327 None,
2328 RESOURCE_SDU,
2329 &identity_encrypt,
2330 &NoopCompressor,
2331 &mut rng,
2332 1000.0,
2333 false,
2334 false,
2335 None,
2336 1,
2337 1,
2338 None,
2339 0.5,
2340 6.0,
2341 )
2342 .unwrap();
2343 sender.get_advertisement(0)
2344 };
2345
2346 let receiver = ResourceReceiver::from_advertisement(
2347 &adv_data,
2348 RESOURCE_SDU,
2349 0.5,
2350 1000.0,
2351 Some(8),
2352 Some(50000.0),
2353 )
2354 .unwrap();
2355 assert_eq!(receiver.window.window, 8);
2356 }
2357
2358 #[test]
2359 fn test_tick_timeout_retry() {
2360 let (_, mut receiver) = make_sender_receiver();
2361 receiver.accept(1000.0);
2362 receiver.rtt = Some(0.1);
2363
2364 let actions = receiver.tick(9999.0, &identity_decrypt, &NoopCompressor);
2366 assert!(!actions.is_empty() || receiver.retries_left < RESOURCE_MAX_RETRIES);
2368 }
2369
2370 #[test]
2371 fn test_tick_waiting_for_hmu_gets_extra_timeout() {
2372 let (_, mut receiver) = make_sender_receiver();
2373 receiver.accept(1000.0);
2374 receiver.waiting_for_hmu = true;
2375 receiver.outstanding_parts = 0;
2376 let eifr = 10_000.0;
2377 receiver.previous_eifr = Some(eifr);
2378 receiver.last_activity = 1000.0;
2379
2380 let old_timeout = base_timeout(&receiver, eifr);
2381 let now = old_timeout + 0.01;
2382
2383 let actions = receiver.tick(now, &identity_decrypt, &NoopCompressor);
2384
2385 assert!(actions.is_empty(), "receiver should keep waiting for HMU");
2386 assert_eq!(receiver.retries_left, RESOURCE_MAX_RETRIES);
2387 assert_eq!(receiver.status, ResourceStatus::Transferring);
2388 }
2389
2390 #[test]
2391 fn test_tick_zero_outstanding_parts_gets_extra_timeout_without_hmu_flag() {
2392 let (_, mut receiver) = make_sender_receiver();
2393 receiver.accept(1000.0);
2394 receiver.waiting_for_hmu = false;
2395 receiver.outstanding_parts = 0;
2396 let eifr = 10_000.0;
2397 receiver.previous_eifr = Some(eifr);
2398 receiver.last_activity = 1000.0;
2399
2400 let old_timeout = base_timeout(&receiver, eifr);
2401 let now = old_timeout + 0.01;
2402
2403 let actions = receiver.tick(now, &identity_decrypt, &NoopCompressor);
2404
2405 assert!(
2406 actions.is_empty(),
2407 "receiver should keep waiting for follow-up hashmap data"
2408 );
2409 assert_eq!(receiver.retries_left, RESOURCE_MAX_RETRIES);
2410 assert_eq!(receiver.status, ResourceStatus::Transferring);
2411 }
2412
2413 #[test]
2414 fn test_tick_waiting_for_hmu_retries_after_extended_timeout() {
2415 let (_, mut receiver) = make_sender_receiver();
2416 receiver.accept(1000.0);
2417 receiver.waiting_for_hmu = true;
2418 receiver.outstanding_parts = 0;
2419 let eifr = 10_000.0;
2420 receiver.previous_eifr = Some(eifr);
2421 receiver.last_activity = 1000.0;
2422
2423 let now = hmu_timeout(&receiver, eifr) + 0.01;
2424 let _actions = receiver.tick(now, &identity_decrypt, &NoopCompressor);
2425
2426 assert_eq!(receiver.retries_left, RESOURCE_MAX_RETRIES - 1);
2427 assert!(!receiver.waiting_for_hmu);
2428 }
2429
2430 #[test]
2431 fn test_tick_inflight_parts_do_not_get_hmu_timeout_extension() {
2432 let (_, mut receiver) = make_sender_receiver();
2433 receiver.accept(1000.0);
2434 receiver.waiting_for_hmu = false;
2435 receiver.outstanding_parts = 2;
2436 let eifr = 10_000.0;
2437 receiver.previous_eifr = Some(eifr);
2438 receiver.last_activity = 1000.0;
2439
2440 let now = base_timeout(&receiver, eifr) + 0.01;
2441 let _actions = receiver.tick(now, &identity_decrypt, &NoopCompressor);
2442
2443 assert_eq!(receiver.retries_left, RESOURCE_MAX_RETRIES - 1);
2444 }
2445
2446 #[test]
2447 fn test_tick_max_retries_exceeded() {
2448 let (_, mut receiver) = make_sender_receiver();
2449 receiver.accept(1000.0);
2450 receiver.retries_left = 0;
2451 receiver.rtt = Some(0.001);
2452 receiver.eifr = Some(100000.0);
2453
2454 let _actions = receiver.tick(9999.0, &identity_decrypt, &NoopCompressor);
2455 assert_eq!(receiver.status, ResourceStatus::Failed);
2456 }
2457}