1use alloc::vec;
2use alloc::vec::Vec;
3
4use super::advertisement::ResourceAdvertisement;
5use super::assembly::{AssemblyData, AssemblyId, AssemblyResult, ResourceAssembly};
6use super::parts::map_hash;
7use super::types::*;
8use super::window::WindowState;
9use crate::buffer::types::Compressor;
10use crate::constants::*;
11
12pub type PartsDecryptFn<'a> = dyn Fn(&[&[u8]], usize) -> Result<Vec<u8>, ()> + 'a;
16
17pub struct ResourceReceiver {
22 assembly_id: Option<AssemblyId>,
23 pub status: ResourceStatus,
25 pub resource_hash: Vec<u8>,
27 pub random_hash: Vec<u8>,
29 pub original_hash: Vec<u8>,
31 pub flags: AdvFlags,
33 pub transfer_size: u64,
35 pub data_size: u64,
37 pub total_parts: usize,
39 parts: Vec<Option<Vec<u8>>>,
41 hashmap: Vec<Option<[u8; RESOURCE_MAPHASH_LEN]>>,
43 hashmap_height: usize,
45 pub waiting_for_hmu: bool,
47 pub received_count: usize,
49 pub outstanding_parts: usize,
51 consecutive_completed_height: isize,
53 sdu: usize,
55 link_rtt: f64,
57 pub retries_left: usize,
59 max_retries: usize,
61 pub rtt: Option<f64>,
63 part_timeout_factor: f64,
65 pub last_activity: f64,
67 pub req_sent: f64,
69 req_sent_bytes: usize,
71 req_resp: Option<f64>,
73 rtt_rxd_bytes: usize,
75 rtt_rxd_bytes_at_part_req: usize,
77 req_resp_rtt_rate: f64,
79 req_data_rtt_rate: f64,
81 pub eifr: Option<f64>,
83 previous_eifr: Option<f64>,
85 pub segment_index: u64,
87 pub total_segments: u64,
89 pub has_metadata: bool,
91 pub request_id: Option<Vec<u8>>,
93 pub window: WindowState,
95 pub advertisement_packet: Vec<u8>,
97 pub max_decompressed_size: usize,
99}
100
101impl ResourceReceiver {
102 pub fn from_advertisement(
104 adv_data: &[u8],
105 sdu: usize,
106 link_rtt: f64,
107 now: f64,
108 previous_window: Option<usize>,
109 previous_eifr: Option<f64>,
110 ) -> Result<Self, ResourceError> {
111 let adv = ResourceAdvertisement::unpack(adv_data)?;
112
113 if adv.resource_hash.len() != 32 {
115 return Err(ResourceError::InvalidAdvertisement);
116 }
117 if adv.random_hash.len() != RESOURCE_RANDOM_HASH_SIZE || adv.original_hash.len() != 32 {
118 return Err(ResourceError::InvalidAdvertisement);
119 }
120 if adv.transfer_size > RESOURCE_AUTO_COMPRESS_MAX_SIZE as u64
124 || (!adv.flags.split && adv.data_size > RESOURCE_AUTO_COMPRESS_MAX_SIZE as u64)
125 {
126 return Err(ResourceError::TooLarge);
127 }
128
129 let total_parts = usize::try_from(adv.num_parts).map_err(|_| ResourceError::TooLarge)?;
136 if total_parts == 0 {
137 return Err(ResourceError::InvalidAdvertisement);
138 }
139 let transfer_size =
140 usize::try_from(adv.transfer_size).map_err(|_| ResourceError::TooLarge)?;
141 let expected_parts = transfer_size.div_ceil(sdu.max(1));
142 if total_parts != expected_parts {
143 return Err(ResourceError::PartCountMismatch);
144 }
145
146 let initial_hashes = core::cmp::min(adv.hashmap.len() / RESOURCE_MAPHASH_LEN, total_parts);
150 let parts_vec: Vec<Option<Vec<u8>>> = Vec::new();
151 let mut hashmap_vec: Vec<Option<[u8; RESOURCE_MAPHASH_LEN]>> =
152 Vec::with_capacity(initial_hashes);
153
154 for chunk in adv
156 .hashmap
157 .chunks_exact(RESOURCE_MAPHASH_LEN)
158 .take(initial_hashes)
159 {
160 let mut h = [0u8; RESOURCE_MAPHASH_LEN];
161 h.copy_from_slice(chunk);
162 hashmap_vec.push(Some(h));
163 }
164 let hashmap_height = hashmap_vec.len();
165
166 let mut window_state = WindowState::new();
167 if let Some(prev_w) = previous_window {
168 window_state.restore(prev_w);
169 }
170
171 Ok(ResourceReceiver {
172 assembly_id: None,
173 status: ResourceStatus::None,
174 resource_hash: adv.resource_hash,
175 random_hash: adv.random_hash,
176 original_hash: adv.original_hash,
177 flags: adv.flags,
178 transfer_size: adv.transfer_size,
179 data_size: adv.data_size,
180 total_parts,
181 parts: parts_vec,
182 hashmap: hashmap_vec,
183 hashmap_height,
184 waiting_for_hmu: false,
185 received_count: 0,
186 outstanding_parts: 0,
187 consecutive_completed_height: -1,
188 sdu,
189 link_rtt,
190 retries_left: RESOURCE_MAX_RETRIES,
191 max_retries: RESOURCE_MAX_RETRIES,
192 rtt: None,
193 part_timeout_factor: RESOURCE_PART_TIMEOUT_FACTOR,
194 last_activity: now,
195 req_sent: 0.0,
196 req_sent_bytes: 0,
197 req_resp: None,
198 rtt_rxd_bytes: 0,
199 rtt_rxd_bytes_at_part_req: 0,
200 req_resp_rtt_rate: 0.0,
201 req_data_rtt_rate: 0.0,
202 eifr: None,
203 previous_eifr,
204 segment_index: adv.segment_index,
205 total_segments: adv.total_segments,
206 has_metadata: adv.flags.has_metadata,
207 request_id: adv.request_id,
208 window: window_state,
209 advertisement_packet: adv_data.to_vec(),
210 max_decompressed_size: RESOURCE_AUTO_COMPRESS_MAX_SIZE,
211 })
212 }
213
214 fn has_part(&self, index: usize) -> bool {
215 self.parts.get(index).is_some_and(Option::is_some)
216 }
217
218 fn store_part(&mut self, index: usize, data: Vec<u8>) {
219 if self.parts.len() <= index {
220 self.parts.resize(index + 1, None);
221 }
222 self.parts[index] = Some(data);
223 }
224
225 fn hash_at(&self, index: usize) -> Option<&[u8; RESOURCE_MAPHASH_LEN]> {
226 self.hashmap.get(index).and_then(Option::as_ref)
227 }
228
229 fn store_hash(&mut self, index: usize, hash: [u8; RESOURCE_MAPHASH_LEN]) {
230 if self.hashmap.len() <= index {
231 self.hashmap.resize(index + 1, None);
232 }
233 self.hashmap[index] = Some(hash);
234 }
235
236 pub fn accept(&mut self, now: f64) -> Vec<ResourceAction> {
238 if self.status >= ResourceStatus::Assembling {
239 return vec![];
240 }
241 self.status = ResourceStatus::Transferring;
242 self.last_activity = now;
243 self.request_next(now)
244 }
245
246 pub fn reject(&mut self) -> Vec<ResourceAction> {
248 self.status = ResourceStatus::Rejected;
249 vec![ResourceAction::SendCancelReceiver(
250 self.resource_hash.clone(),
251 )]
252 }
253
254 pub fn cancel(&mut self) -> Vec<ResourceAction> {
256 if self.status < ResourceStatus::Complete {
257 self.status = ResourceStatus::Failed;
258 vec![ResourceAction::SendCancelReceiver(
259 self.resource_hash.clone(),
260 )]
261 } else {
262 Vec::new()
263 }
264 }
265
266 fn corrupt_actions(&mut self, error: ResourceError) -> Vec<ResourceAction> {
267 self.status = ResourceStatus::Corrupt;
268 vec![
269 ResourceAction::SendCancelReceiver(self.resource_hash.clone()),
270 ResourceAction::Failed(error),
271 ResourceAction::TeardownLink,
272 ]
273 }
274
275 pub fn receive_part(&mut self, part_data: &[u8], now: f64) -> Vec<ResourceAction> {
277 if self.status >= ResourceStatus::Assembling {
278 return vec![];
279 }
280
281 self.last_activity = now;
282 self.retries_left = self.max_retries;
283
284 if self.req_resp.is_none() {
286 self.req_resp = Some(now);
287 let rtt = now - self.req_sent;
288 self.part_timeout_factor = RESOURCE_PART_TIMEOUT_FACTOR_AFTER_RTT;
289
290 if self.rtt.is_none() {
291 self.rtt = Some(rtt);
292 } else if let Some(current_rtt) = self.rtt {
293 if rtt < current_rtt {
294 self.rtt = Some(f64::max(current_rtt - current_rtt * 0.05, rtt));
295 } else {
296 self.rtt = Some(f64::min(current_rtt + current_rtt * 0.05, rtt));
297 }
298 }
299
300 if rtt > 0.0 {
301 let req_resp_cost = part_data.len() + self.req_sent_bytes;
302 self.req_resp_rtt_rate = req_resp_cost as f64 / rtt;
303 self.window.update_req_resp_rate(self.req_resp_rtt_rate);
304 }
305 }
306
307 self.status = ResourceStatus::Transferring;
308
309 let part_hash = map_hash(part_data, &self.random_hash);
311
312 let search_start = (self.consecutive_completed_height + 1) as usize;
315 let mut matched = false;
316 let search_end = core::cmp::min(search_start + self.window.window, self.total_parts);
317 for i in search_start..search_end {
318 if let Some(h) = self.hash_at(i) {
319 if *h == part_hash {
320 if !self.has_part(i) {
321 self.store_part(i, part_data.to_vec());
322 self.rtt_rxd_bytes += part_data.len();
323 self.received_count += 1;
324 self.outstanding_parts = self.outstanding_parts.saturating_sub(1);
325
326 let mut cp = (self.consecutive_completed_height + 1) as usize;
328 while cp < self.total_parts && self.has_part(cp) {
329 self.consecutive_completed_height = cp as isize;
330 cp += 1;
331 }
332
333 matched = true;
334 }
335 break;
336 }
337 }
338 }
339
340 let mut actions = Vec::new();
341
342 if self.received_count == self.total_parts {
344 actions.push(ResourceAction::ProgressUpdate {
345 received: self.received_count,
346 total: self.total_parts,
347 });
348 return actions;
350 }
351
352 if matched {
353 actions.push(ResourceAction::ProgressUpdate {
354 received: self.received_count,
355 total: self.total_parts,
356 });
357 }
358
359 if self.outstanding_parts == 0 && self.received_count < self.total_parts {
361 self.window.on_window_complete();
363
364 if self.req_sent > 0.0 {
366 let rtt = now - self.req_sent;
367 let req_transferred = self.rtt_rxd_bytes - self.rtt_rxd_bytes_at_part_req;
368 if rtt > 0.0 {
369 self.req_data_rtt_rate = req_transferred as f64 / rtt;
370 self.rtt_rxd_bytes_at_part_req = self.rtt_rxd_bytes;
371 self.window.update_data_rate(self.req_data_rtt_rate);
372 }
373 }
374
375 let next_actions = self.request_next(now);
376 actions.extend(next_actions);
377 }
378
379 actions
380 }
381
382 pub fn handle_hashmap_update(&mut self, hmu_data: &[u8], now: f64) -> Vec<ResourceAction> {
386 if self.status >= ResourceStatus::Assembling || !self.waiting_for_hmu {
387 return vec![];
388 }
389
390 if hmu_data.len() < 32 || hmu_data[..32] != self.resource_hash {
391 return vec![];
392 }
393 if hmu_data.len() == 32 {
394 return self.cancel();
395 }
396
397 let payload = &hmu_data[32..];
398 let value = match crate::msgpack::unpack_exact(payload) {
399 Ok(v) => v,
400 Err(_) => return self.cancel(),
401 };
402
403 let arr = match value.as_array() {
404 Some(a) if a.len() == 2 => a,
405 _ => return self.cancel(),
406 };
407
408 let segment = match arr[0].as_uint() {
409 Some(s) => match usize::try_from(s) {
410 Ok(segment) => segment,
411 Err(_) => return self.cancel(),
412 },
413 None => return self.cancel(),
414 };
415
416 let hashmap_bytes = match arr[1].as_bin() {
417 Some(b) => b,
418 None => return self.cancel(),
419 };
420
421 if hashmap_bytes.is_empty() || !hashmap_bytes.len().is_multiple_of(RESOURCE_MAPHASH_LEN) {
422 return self.cancel();
423 }
424
425 let seg_len = RESOURCE_HASHMAP_MAX_LEN;
426 let segment_start = match segment.checked_mul(seg_len) {
427 Some(start) if start < self.total_parts => start,
428 _ => return self.cancel(),
429 };
430 let num_hashes = hashmap_bytes.len() / RESOURCE_MAPHASH_LEN;
431 if num_hashes > seg_len || segment_start.saturating_add(num_hashes) > self.total_parts {
432 return self.cancel();
433 }
434
435 self.last_activity = now;
436 self.retries_left = self.max_retries;
437 self.status = ResourceStatus::Transferring;
438
439 for i in 0..num_hashes {
441 let idx = segment_start + i;
442 let start = i * RESOURCE_MAPHASH_LEN;
443 let end = start + RESOURCE_MAPHASH_LEN;
444 if self.hash_at(idx).is_none() {
445 self.hashmap_height += 1;
446 }
447 let mut h = [0u8; RESOURCE_MAPHASH_LEN];
448 h.copy_from_slice(&hashmap_bytes[start..end]);
449 self.store_hash(idx, h);
450 }
451
452 self.waiting_for_hmu = false;
453 self.request_next(now)
454 }
455
456 pub fn request_next(&mut self, now: f64) -> Vec<ResourceAction> {
458 if self.status >= ResourceStatus::Assembling {
459 return vec![];
460 }
461 if self.status == ResourceStatus::Failed || self.waiting_for_hmu {
462 return vec![];
463 }
464
465 self.outstanding_parts = 0;
466 let mut hashmap_exhausted = RESOURCE_HASHMAP_IS_NOT_EXHAUSTED;
467 let mut requested_hashes = Vec::new();
468
469 let pn_start = (self.consecutive_completed_height + 1) as usize;
470 let search_end = core::cmp::min(pn_start + self.window.window, self.total_parts);
471 let mut i = 0;
472
473 for pn in pn_start..search_end {
474 if !self.has_part(pn) {
475 match self.hash_at(pn) {
476 Some(h) => {
477 requested_hashes.extend_from_slice(h);
478 self.outstanding_parts += 1;
479 i += 1;
480 }
481 None => {
482 hashmap_exhausted = RESOURCE_HASHMAP_IS_EXHAUSTED;
483 }
484 }
485 }
486 if i >= self.window.window || hashmap_exhausted == RESOURCE_HASHMAP_IS_EXHAUSTED {
487 break;
488 }
489 }
490
491 let mut request_data = Vec::new();
492 request_data.push(hashmap_exhausted);
493 if hashmap_exhausted == RESOURCE_HASHMAP_IS_EXHAUSTED {
494 if self.hashmap_height > 0 {
496 if let Some(last_hash) = self.hash_at(self.hashmap_height - 1) {
497 request_data.extend_from_slice(last_hash);
498 } else {
499 request_data.extend_from_slice(&[0u8; RESOURCE_MAPHASH_LEN]);
500 }
501 } else {
502 request_data.extend_from_slice(&[0u8; RESOURCE_MAPHASH_LEN]);
503 }
504 self.waiting_for_hmu = true;
505 }
506
507 request_data.extend_from_slice(&self.resource_hash);
508 request_data.extend_from_slice(&requested_hashes);
509
510 self.last_activity = now;
511 self.req_sent = now;
512 self.req_sent_bytes = request_data.len();
513 self.req_resp = None;
514
515 vec![ResourceAction::SendRequest(request_data)]
516 }
517
518 pub fn assemble(
520 &mut self,
521 decrypt_fn: &PartsDecryptFn<'_>,
522 compressor: &dyn Compressor,
523 ) -> Vec<ResourceAction> {
524 match self.prepare_data(decrypt_fn) {
525 Ok(data) => self.finish_data(data.run(compressor)),
526 Err(actions) => actions,
527 }
528 }
529
530 pub fn prepare_assembly(
534 &mut self,
535 decrypt_fn: &PartsDecryptFn<'_>,
536 ) -> Result<ResourceAssembly, Vec<ResourceAction>> {
537 let data = self.prepare_data(decrypt_fn)?;
538 let id = AssemblyId::new();
539 self.assembly_id = Some(id.clone());
540 Ok(ResourceAssembly { id, data })
541 }
542
543 pub fn assembly_id(&self) -> Option<&AssemblyId> {
544 (self.status == ResourceStatus::Assembling)
545 .then_some(self.assembly_id.as_ref())
546 .flatten()
547 }
548
549 pub fn complete_assembly(&mut self, result: AssemblyResult) -> Vec<ResourceAction> {
552 if self.assembly_id() != Some(&result.id) {
553 return Vec::new();
554 }
555 self.assembly_id = None;
556 self.finish_data(result.result)
557 }
558
559 pub fn fail_assembly(&mut self, id: &AssemblyId) -> Vec<ResourceAction> {
561 if self.assembly_id() != Some(id) {
562 return Vec::new();
563 }
564 self.assembly_id = None;
565 let mut actions = self.cancel();
566 actions.push(ResourceAction::Failed(ResourceError::InvalidState));
567 actions
568 }
569
570 fn finish_data(
571 &mut self,
572 result: Result<Vec<ResourceAction>, ResourceError>,
573 ) -> Vec<ResourceAction> {
574 match result {
575 Ok(actions) => {
576 self.status = ResourceStatus::Complete;
577 actions
578 }
579 Err(error) => self.corrupt_actions(error),
580 }
581 }
582
583 fn prepare_data(
584 &mut self,
585 decrypt_fn: &PartsDecryptFn<'_>,
586 ) -> Result<AssemblyData, Vec<ResourceAction>> {
587 if self.status >= ResourceStatus::Assembling || self.received_count != self.total_parts {
588 return Err(vec![ResourceAction::Failed(ResourceError::InvalidState)]);
589 }
590
591 self.status = ResourceStatus::Assembling;
592
593 if self.parts.len() != self.total_parts {
595 self.status = ResourceStatus::Failed;
596 return Err(vec![ResourceAction::Failed(ResourceError::InvalidState)]);
597 }
598
599 let mut stream_len = 0usize;
602 for part in &self.parts {
603 match part {
604 Some(data) => match stream_len.checked_add(data.len()) {
605 Some(len) => stream_len = len,
606 None => {
607 self.status = ResourceStatus::Failed;
608 return Err(vec![ResourceAction::Failed(ResourceError::TooLarge)]);
609 }
610 },
611 None => {
612 self.status = ResourceStatus::Failed;
613 return Err(vec![ResourceAction::Failed(ResourceError::InvalidState)]);
614 }
615 }
616 }
617 let parts = core::mem::take(&mut self.parts);
620 self.hashmap = Vec::new();
621
622 let plaintext = if self.flags.encrypted {
624 let chunks: Vec<&[u8]> = parts.iter().flatten().map(Vec::as_slice).collect();
627 let decrypted = decrypt_fn(&chunks, RESOURCE_RANDOM_HASH_SIZE);
628 drop(chunks);
629 drop(parts);
630 match decrypted {
631 Ok(d) => d,
632 Err(_) => {
633 self.status = ResourceStatus::Failed;
634 return Err(vec![ResourceAction::Failed(
635 ResourceError::DecryptionFailed,
636 )]);
637 }
638 }
639 } else {
640 let Some(len) = stream_len.checked_sub(RESOURCE_RANDOM_HASH_SIZE) else {
641 return Err(self.corrupt_actions(ResourceError::InvalidPart));
642 };
643 let mut plaintext = Vec::with_capacity(len);
644 let mut skip = RESOURCE_RANDOM_HASH_SIZE;
645 for data in parts.iter().flatten() {
646 let from = skip.min(data.len());
647 plaintext.extend_from_slice(&data[from..]);
648 skip -= from;
649 }
650 plaintext
651 };
652
653 let (Ok(resource_hash), Ok(random_hash)) = (
654 self.resource_hash.as_slice().try_into(),
655 self.random_hash.as_slice().try_into(),
656 ) else {
657 return Err(self.corrupt_actions(ResourceError::InvalidAdvertisement));
658 };
659 Ok(AssemblyData {
660 plaintext,
661 resource_hash,
662 random_hash,
663 compressed: self.flags.compressed,
664 metadata: self.has_metadata && self.segment_index == 1,
665 limit: self.max_decompressed_size,
666 })
667 }
668
669 pub fn handle_cancel(&mut self) -> Vec<ResourceAction> {
671 if self.status < ResourceStatus::Complete {
672 self.status = ResourceStatus::Failed;
673 return vec![ResourceAction::Failed(ResourceError::Rejected)];
674 }
675 vec![]
676 }
677
678 pub fn tick(
680 &mut self,
681 now: f64,
682 decrypt_fn: &PartsDecryptFn<'_>,
683 compressor: &dyn Compressor,
684 ) -> Vec<ResourceAction> {
685 if self.status >= ResourceStatus::Assembling {
686 return vec![];
687 }
688
689 if self.status == ResourceStatus::Transferring {
690 if self.received_count == self.total_parts {
692 return self.assemble(decrypt_fn, compressor);
693 }
694
695 let eifr = self.compute_eifr();
697 let retries_used = self.max_retries - self.retries_left;
698 let extra_wait = retries_used as f64 * RESOURCE_PER_RETRY_DELAY;
699 let expected_hmu_wait =
700 if eifr > 0.0 && (self.waiting_for_hmu || self.outstanding_parts == 0) {
701 (self.sdu as f64 * 8.0 * RESOURCE_HMU_WAIT_FACTOR) / eifr
702 } else {
703 0.0
704 };
705 let expected_tof = if self.outstanding_parts > 0 && eifr > 0.0 {
706 (self.outstanding_parts as f64 * self.sdu as f64 * 8.0) / eifr
707 } else if eifr > 0.0 {
708 (3.0 * self.sdu as f64) / eifr
709 } else {
710 10.0 };
712
713 let sleep_time = self.last_activity
714 + self.part_timeout_factor * expected_tof
715 + expected_hmu_wait
716 + RESOURCE_RETRY_GRACE_TIME
717 + extra_wait;
718
719 if now > sleep_time {
720 if self.retries_left > 0 {
721 self.window.on_timeout();
723 self.retries_left -= 1;
724 self.waiting_for_hmu = false;
725 return self.request_next(now);
726 } else {
727 self.status = ResourceStatus::Failed;
728 return vec![ResourceAction::Failed(ResourceError::MaxRetriesExceeded)];
729 }
730 }
731 }
732
733 vec![]
734 }
735
736 fn compute_eifr(&mut self) -> f64 {
738 let eifr = if self.req_data_rtt_rate > 0.0 {
739 self.req_data_rtt_rate * 8.0
740 } else if let Some(prev) = self.previous_eifr {
741 prev
742 } else {
743 let rtt = self.rtt.unwrap_or(self.link_rtt);
745 if rtt > 0.0 {
746 (self.sdu as f64 * 8.0) / rtt
747 } else {
748 10000.0
749 }
750 };
751 self.eifr = Some(eifr);
752 eifr
753 }
754
755 pub fn progress(&self) -> (usize, usize) {
757 (self.received_count, self.total_parts)
758 }
759
760 pub fn get_transfer_state(&self) -> (usize, Option<f64>) {
762 (self.window.window, self.eifr)
763 }
764}
765
766#[cfg(test)]
767mod tests {
768 use super::*;
769 use crate::buffer::types::{Compressor, DecompressError, NoopCompressor};
770 use crate::resource::advertisement::ResourceAdvertisement;
771 use crate::resource::sender::ResourceSender;
772
773 fn identity_encrypt(data: &[u8]) -> Vec<u8> {
774 data.to_vec()
775 }
776
777 fn identity_decrypt(parts: &[&[u8]], skip: usize) -> Result<Vec<u8>, ()> {
778 let joined = parts.concat();
779 joined.get(skip..).map(<[u8]>::to_vec).ok_or(())
780 }
781
782 struct ExpandingCompressor;
783
784 impl Compressor for ExpandingCompressor {
785 fn compress(&self, data: &[u8]) -> Option<Vec<u8>> {
786 Some(data[..data.len() / 2].to_vec())
787 }
788
789 fn decompress(&self, data: &[u8]) -> Option<Vec<u8>> {
790 self.decompress_bounded(data, usize::MAX).ok()
791 }
792
793 fn decompress_bounded(
794 &self,
795 data: &[u8],
796 max_output_size: usize,
797 ) -> Result<Vec<u8>, DecompressError> {
798 let mut out = data.to_vec();
799 out.extend_from_slice(data);
800 if out.len() > max_output_size {
801 return Err(DecompressError::TooLarge);
802 }
803 Ok(out)
804 }
805 }
806
807 fn base_timeout(receiver: &ResourceReceiver, eifr: f64) -> f64 {
808 let expected_tof = if receiver.outstanding_parts > 0 {
809 (receiver.outstanding_parts as f64 * receiver.sdu as f64 * 8.0) / eifr
810 } else {
811 (3.0 * receiver.sdu as f64) / eifr
812 };
813
814 receiver.last_activity
815 + receiver.part_timeout_factor * expected_tof
816 + RESOURCE_RETRY_GRACE_TIME
817 }
818
819 fn hmu_timeout(receiver: &ResourceReceiver, eifr: f64) -> f64 {
820 let expected_hmu_wait = (receiver.sdu as f64 * 8.0 * RESOURCE_HMU_WAIT_FACTOR) / eifr;
821 base_timeout(receiver, eifr) + expected_hmu_wait
822 }
823
824 fn make_sender_receiver() -> (ResourceSender, ResourceReceiver) {
825 let mut rng = rns_crypto::FixedRng::new(&[0x42; 64]);
826 let data = b"Hello, Resource Transfer!";
827
828 let sender = ResourceSender::new(
829 data,
830 None,
831 RESOURCE_SDU,
832 &identity_encrypt,
833 &NoopCompressor,
834 &mut rng,
835 1000.0,
836 false,
837 false,
838 None,
839 1,
840 1,
841 None,
842 0.5,
843 6.0,
844 )
845 .unwrap();
846
847 let adv_data = sender.get_advertisement(0);
848 let receiver =
849 ResourceReceiver::from_advertisement(&adv_data, RESOURCE_SDU, 0.5, 1000.0, None, None)
850 .unwrap();
851
852 (sender, receiver)
853 }
854
855 fn ready_receiver() -> ResourceReceiver {
856 let (mut sender, mut receiver) = make_sender_receiver();
857 for action in receiver.accept(1001.0) {
858 if let ResourceAction::SendRequest(request) = action {
859 for action in sender.handle_request(&request, 1002.0) {
860 if let ResourceAction::SendPart(part) = action {
861 receiver.receive_part(&part, 1003.0);
862 }
863 }
864 }
865 }
866 assert_eq!(receiver.received_count, receiver.total_parts);
867 receiver
868 }
869
870 #[test]
871 fn assembly_handoff_matches_synchronous_actions_and_releases_parts() {
872 let mut receiver = ready_receiver();
873 let job = receiver.prepare_assembly(&identity_decrypt).unwrap();
874 assert!(receiver.parts.is_empty() && receiver.hashmap.is_empty());
875 receiver.waiting_for_hmu = true;
876 assert!(receiver
877 .handle_hashmap_update(&receiver.resource_hash.clone(), 1004.0)
878 .is_empty());
879 assert!(receiver.accept(1004.0).is_empty());
880 assert!(receiver.request_next(1004.0).is_empty());
881 assert_eq!(receiver.assembly_id(), Some(job.id()));
882 assert!(receiver.receive_part(b"duplicate", 1004.0).is_empty());
883 assert!(receiver
884 .tick(
885 100000.0,
886 &|_, _| panic!("pending assembly decrypted again"),
887 &NoopCompressor
888 )
889 .is_empty());
890 assert!(receiver.prepare_assembly(&identity_decrypt).is_err());
891 let actions = receiver.complete_assembly(job.run(&NoopCompressor));
892 let expected = ready_receiver().assemble(&identity_decrypt, &NoopCompressor);
893 assert_eq!(format!("{actions:?}"), format!("{expected:?}"));
894 assert_eq!(receiver.status, ResourceStatus::Complete);
895 assert!(receiver.assembly_id().is_none());
896 }
897
898 #[test]
899 fn assembly_result_requires_original_live_attempt_even_for_same_hash() {
900 let mut first = ready_receiver();
901 let mut second = ready_receiver();
902 assert_eq!(first.resource_hash, second.resource_hash);
903 let first_job = first.prepare_assembly(&identity_decrypt).unwrap();
904 let second_job = second.prepare_assembly(&identity_decrypt).unwrap();
905 assert_ne!(first_job.id(), second_job.id());
906 assert!(first
907 .complete_assembly(second_job.run(&NoopCompressor))
908 .is_empty());
909 first.cancel();
910 assert!(first
911 .complete_assembly(first_job.run(&NoopCompressor))
912 .is_empty());
913 assert_eq!(first.status, ResourceStatus::Failed);
914 }
915
916 #[test]
917 fn assembly_local_failure_and_authentication_failure_cannot_publish_success() {
918 let mut receiver = ready_receiver();
919 assert!(receiver.prepare_assembly(&|_, _| Err(())).is_err());
920 assert!(receiver.assembly_id().is_none());
921 assert!(receiver.parts.is_empty() && receiver.hashmap.is_empty());
922 assert!(receiver
923 .prepare_assembly(&|_, _| panic!("failed assembly retried"))
924 .is_err());
925 let mut receiver = ready_receiver();
926 let job = receiver.prepare_assembly(&identity_decrypt).unwrap();
927 let actions = receiver.fail_assembly(job.id());
928 assert!(matches!(
929 actions.as_slice(),
930 [
931 ResourceAction::SendCancelReceiver(_),
932 ResourceAction::Failed(ResourceError::InvalidState)
933 ]
934 ));
935 assert!(receiver.fail_assembly(job.id()).is_empty());
936 assert!(receiver
937 .complete_assembly(job.run(&NoopCompressor))
938 .is_empty());
939 }
940
941 #[test]
942 fn test_assembly_joins_actual_parts_independent_of_advertised_size() {
943 let (mut sender, mut receiver) = make_sender_receiver();
944 let mut stream = Vec::new();
945 for action in receiver.accept(1001.0) {
946 if let ResourceAction::SendRequest(request) = action {
947 for response in sender.handle_request(&request, 1002.0) {
948 if let ResourceAction::SendPart(part) = response {
949 stream.extend_from_slice(&part);
950 }
951 }
952 }
953 }
954 receiver.parts = stream.chunks(7).map(|part| Some(part.to_vec())).collect();
956 receiver.total_parts = receiver.parts.len();
957 receiver.received_count = receiver.total_parts;
958 receiver.transfer_size = u64::MAX;
959 let actions = receiver.assemble(
960 &|parts, skip| {
961 assert_eq!(parts.len(), stream.len().div_ceil(7));
963 assert!(parts.iter().all(|part| part.len() <= 7));
964 assert_eq!(parts.concat(), stream);
965 identity_decrypt(parts, skip)
966 },
967 &NoopCompressor,
968 );
969 assert!(actions.iter().any(|action| matches!(action,
970 ResourceAction::DataReceived { data, .. } if data == b"Hello, Resource Transfer!")));
971 assert_eq!(receiver.status, ResourceStatus::Complete);
972 }
973
974 #[test]
975 fn test_assembly_rejects_missing_part_before_decryption() {
976 let (_, mut receiver) = make_sender_receiver();
977 receiver.received_count = receiver.total_parts;
978 let actions = receiver.assemble(
979 &|_, _| panic!("missing part reached decryption"),
980 &NoopCompressor,
981 );
982 assert!(matches!(
983 actions.as_slice(),
984 [ResourceAction::Failed(ResourceError::InvalidState)]
985 ));
986 assert_eq!(receiver.status, ResourceStatus::Failed);
987 }
988
989 #[test]
990 fn test_from_advertisement() {
991 let (sender, receiver) = make_sender_receiver();
992 assert_eq!(receiver.total_parts, sender.total_parts());
993 assert_eq!(receiver.transfer_size, sender.transfer_size as u64);
994 assert_eq!(receiver.resource_hash, sender.resource_hash.to_vec());
995 assert!(!receiver.advertisement_packet.is_empty());
996 assert_eq!(
997 receiver.max_decompressed_size,
998 RESOURCE_AUTO_COMPRESS_MAX_SIZE
999 );
1000 }
1001
1002 #[test]
1003 fn test_from_advertisement_rejects_huge_part_count() {
1004 let adv = ResourceAdvertisement {
1005 transfer_size: 1024,
1006 data_size: 1024,
1007 num_parts: u64::MAX,
1008 resource_hash: vec![0x11; 32],
1009 random_hash: vec![0x22; RESOURCE_RANDOM_HASH_SIZE],
1010 original_hash: vec![0x33; 32],
1011 hashmap: vec![0x44; RESOURCE_MAPHASH_LEN],
1012 flags: AdvFlags {
1013 encrypted: false,
1014 compressed: false,
1015 split: false,
1016 is_request: false,
1017 is_response: false,
1018 has_metadata: false,
1019 },
1020 segment_index: 1,
1021 total_segments: 1,
1022 request_id: None,
1023 };
1024
1025 let err = match ResourceReceiver::from_advertisement(
1026 &adv.pack(0),
1027 RESOURCE_SDU,
1028 0.5,
1029 1000.0,
1030 None,
1031 None,
1032 ) {
1033 Ok(_) => panic!("huge advertisement should be rejected"),
1034 Err(err) => err,
1035 };
1036
1037 assert_eq!(err, ResourceError::PartCountMismatch);
1038 }
1039
1040 fn sized_advertisement(transfer_size: u64, num_parts: u64) -> ResourceAdvertisement {
1041 ResourceAdvertisement {
1042 transfer_size,
1043 data_size: transfer_size,
1044 num_parts,
1045 resource_hash: vec![0x11; 32],
1046 random_hash: vec![0x22; RESOURCE_RANDOM_HASH_SIZE],
1047 original_hash: vec![0x33; 32],
1048 hashmap: vec![0x44; RESOURCE_MAPHASH_LEN * num_parts.min(512) as usize],
1049 flags: AdvFlags {
1050 encrypted: true,
1051 compressed: false,
1052 split: false,
1053 is_request: false,
1054 is_response: false,
1055 has_metadata: false,
1056 },
1057 segment_index: 1,
1058 total_segments: 1,
1059 request_id: None,
1060 }
1061 }
1062
1063 fn receive_advertisement(
1064 adv: &ResourceAdvertisement,
1065 sdu: usize,
1066 ) -> Result<ResourceReceiver, ResourceError> {
1067 ResourceReceiver::from_advertisement(&adv.pack(0), sdu, 0.5, 1000.0, None, None)
1068 }
1069
1070 #[test]
1071 fn part_count_above_transfer_size_is_refused() {
1072 let adv = sized_advertisement(1, 140_000);
1073 assert_eq!(
1074 receive_advertisement(&adv, RESOURCE_SDU).err(),
1075 Some(ResourceError::PartCountMismatch)
1076 );
1077 assert_eq!(
1078 receive_advertisement(&adv, 1).err(),
1079 Some(ResourceError::PartCountMismatch)
1080 );
1081 }
1082
1083 #[test]
1084 fn part_count_below_transfer_size_is_refused() {
1085 let sdu = RESOURCE_SDU;
1086 let adv = sized_advertisement(10 * sdu as u64 + 1, 10);
1087 assert_eq!(
1088 receive_advertisement(&adv, sdu).err(),
1089 Some(ResourceError::PartCountMismatch)
1090 );
1091 }
1092
1093 #[test]
1094 fn part_count_matching_transfer_size_is_accepted() {
1095 let sdu = RESOURCE_SDU;
1096 let receiver = receive_advertisement(&sized_advertisement(1, 1), sdu).unwrap();
1097 assert_eq!(receiver.total_parts, 1);
1098
1099 let receiver = receive_advertisement(&sized_advertisement(sdu as u64 + 1, 2), sdu).unwrap();
1100 assert_eq!(receiver.total_parts, 2);
1101 }
1102
1103 #[test]
1104 fn transfer_size_on_sdu_boundary_needs_no_extra_part() {
1105 let sdu = RESOURCE_SDU;
1106 let exact = 3 * sdu as u64;
1107 let receiver = receive_advertisement(&sized_advertisement(exact, 3), sdu).unwrap();
1108 assert_eq!(receiver.total_parts, 3);
1109 assert_eq!(
1110 receive_advertisement(&sized_advertisement(exact, 4), sdu).err(),
1111 Some(ResourceError::PartCountMismatch)
1112 );
1113 assert_eq!(
1114 receive_advertisement(&sized_advertisement(exact, 2), sdu).err(),
1115 Some(ResourceError::PartCountMismatch)
1116 );
1117 }
1118
1119 #[test]
1120 fn split_segment_part_count_follows_segment_transfer_size() {
1121 let sdu = RESOURCE_SDU;
1122 let segment_size = RESOURCE_MAX_EFFICIENT_SIZE as u64 + 61;
1123 let segment_parts = segment_size.div_ceil(sdu as u64);
1124 let mut adv = sized_advertisement(segment_size, segment_parts);
1125 adv.data_size = 3 * RESOURCE_MAX_EFFICIENT_SIZE as u64;
1126 adv.flags.split = true;
1127 adv.segment_index = 2;
1128 adv.total_segments = 3;
1129
1130 let receiver = receive_advertisement(&adv, sdu).unwrap();
1131 assert_eq!(receiver.total_parts as u64, segment_parts);
1132 assert_eq!(receiver.segment_index, 2);
1133
1134 adv.num_parts = adv.data_size.div_ceil(sdu as u64);
1136 assert_eq!(
1137 receive_advertisement(&adv, sdu).err(),
1138 Some(ResourceError::PartCountMismatch)
1139 );
1140 }
1141
1142 #[test]
1143 fn transfer_size_above_segment_limit_is_refused_up_front() {
1144 let limit = 3 * RESOURCE_MAX_EFFICIENT_SIZE as u64;
1145 let adv = sized_advertisement(limit + 1, (limit + 1).div_ceil(RESOURCE_SDU as u64));
1146 assert_eq!(
1147 receive_advertisement(&adv, RESOURCE_SDU).err(),
1148 Some(ResourceError::InvalidAdvertisement)
1149 );
1150 }
1151
1152 #[test]
1153 fn idle_accepted_advertisement_holds_no_part_storage() {
1154 let sdu = 48;
1155 let limit = 3 * RESOURCE_MAX_EFFICIENT_SIZE as u64;
1156 let parts = limit.div_ceil(sdu as u64);
1157 let mut receiver = receive_advertisement(&sized_advertisement(limit, parts), sdu).unwrap();
1158 assert_eq!(receiver.total_parts as u64, parts);
1159
1160 let actions = receiver.accept(1000.0);
1161 assert!(actions
1162 .iter()
1163 .any(|action| matches!(action, ResourceAction::SendRequest(_))));
1164 assert_eq!(receiver.parts.capacity(), 0);
1165 assert!(receiver.hashmap.capacity() <= RESOURCE_HASHMAP_MAX_LEN);
1166 assert_eq!(receiver.hashmap.len(), receiver.hashmap_height);
1167 }
1168
1169 #[test]
1170 fn part_storage_grows_with_received_parts() {
1171 let mut rng = rns_crypto::FixedRng::new(&[0x42; 64]);
1172 let sdu = 8;
1173 let data: Vec<u8> = (0..100u8).collect();
1174 let mut sender = ResourceSender::new(
1175 &data,
1176 None,
1177 sdu,
1178 &identity_encrypt,
1179 &NoopCompressor,
1180 &mut rng,
1181 1000.0,
1182 false,
1183 false,
1184 None,
1185 1,
1186 1,
1187 None,
1188 0.5,
1189 6.0,
1190 )
1191 .unwrap();
1192 let mut receiver = ResourceReceiver::from_advertisement(
1193 &sender.get_advertisement(0),
1194 sdu,
1195 0.5,
1196 1000.0,
1197 None,
1198 None,
1199 )
1200 .unwrap();
1201 assert!(receiver.total_parts > 1);
1202 assert!(receiver.parts.is_empty());
1203
1204 let request = receiver
1205 .accept(1000.0)
1206 .into_iter()
1207 .find_map(|action| match action {
1208 ResourceAction::SendRequest(request) => Some(request),
1209 _ => None,
1210 })
1211 .unwrap();
1212 let first_part = sender
1213 .handle_request(&request, 1001.0)
1214 .into_iter()
1215 .find_map(|action| match action {
1216 ResourceAction::SendPart(part) => Some(part),
1217 _ => None,
1218 })
1219 .unwrap();
1220 receiver.receive_part(&first_part, 1002.0);
1221 assert_eq!(receiver.received_count, 1);
1222 assert_eq!(receiver.parts.len(), 1);
1223 }
1224
1225 #[test]
1226 fn test_from_advertisement_rejects_oversized_transfer() {
1227 let adv = ResourceAdvertisement {
1228 transfer_size: RESOURCE_AUTO_COMPRESS_MAX_SIZE as u64 + 1,
1229 data_size: 1024,
1230 num_parts: 1,
1231 resource_hash: vec![0x11; 32],
1232 random_hash: vec![0x22; RESOURCE_RANDOM_HASH_SIZE],
1233 original_hash: vec![0x33; 32],
1234 hashmap: vec![0x44; RESOURCE_MAPHASH_LEN],
1235 flags: AdvFlags {
1236 encrypted: false,
1237 compressed: false,
1238 split: false,
1239 is_request: false,
1240 is_response: false,
1241 has_metadata: false,
1242 },
1243 segment_index: 1,
1244 total_segments: 1,
1245 request_id: None,
1246 };
1247
1248 let err = match ResourceReceiver::from_advertisement(
1249 &adv.pack(0),
1250 RESOURCE_SDU,
1251 0.5,
1252 1000.0,
1253 None,
1254 None,
1255 ) {
1256 Ok(_) => panic!("oversized advertisement should be rejected"),
1257 Err(err) => err,
1258 };
1259
1260 assert_eq!(err, ResourceError::InvalidAdvertisement);
1261 }
1262
1263 #[test]
1264 fn test_accept() {
1265 let (_, mut receiver) = make_sender_receiver();
1266 let actions = receiver.accept(1000.0);
1267 assert_eq!(receiver.status, ResourceStatus::Transferring);
1268 assert!(!actions.is_empty());
1269 assert!(actions
1270 .iter()
1271 .any(|a| matches!(a, ResourceAction::SendRequest(_))));
1272 }
1273
1274 #[test]
1275 fn test_reject() {
1276 let (_, mut receiver) = make_sender_receiver();
1277 let actions = receiver.reject();
1278 assert_eq!(receiver.status, ResourceStatus::Rejected);
1279 assert!(actions
1280 .iter()
1281 .any(|a| matches!(a, ResourceAction::SendCancelReceiver(_))));
1282 }
1283
1284 #[test]
1285 fn test_cancel_is_distinct_from_advertisement_rejection() {
1286 let (_, mut receiver) = make_sender_receiver();
1287 receiver.accept(1000.0);
1288 let actions = receiver.cancel();
1289 assert_eq!(receiver.status, ResourceStatus::Failed);
1290 assert!(actions
1291 .iter()
1292 .any(|a| matches!(a, ResourceAction::SendCancelReceiver(_))));
1293 assert!(receiver.cancel().is_empty());
1294 }
1295
1296 fn hmu(resource_hash: &[u8], segment: u64, hashmap: Vec<u8>) -> Vec<u8> {
1297 let payload = crate::msgpack::pack(&crate::msgpack::Value::Array(vec![
1298 crate::msgpack::Value::UInt(segment),
1299 crate::msgpack::Value::Bin(hashmap),
1300 ]));
1301 let mut data = resource_hash.to_vec();
1302 data.extend_from_slice(&payload);
1303 data
1304 }
1305
1306 #[test]
1307 fn test_unsolicited_hmu_is_ignored_without_refreshing_timeout_state() {
1308 let (_, mut receiver) = make_sender_receiver();
1309 receiver.status = ResourceStatus::Transferring;
1310 receiver.waiting_for_hmu = false;
1311 receiver.last_activity = 1000.0;
1312 receiver.retries_left = 1;
1313 let update = hmu(&receiver.resource_hash, 0, vec![0x99; RESOURCE_MAPHASH_LEN]);
1314
1315 let actions = receiver.handle_hashmap_update(&update, 2000.0);
1316
1317 assert!(actions.is_empty());
1318 assert_eq!(receiver.status, ResourceStatus::Transferring);
1319 assert!(!receiver.waiting_for_hmu);
1320 assert_eq!(receiver.last_activity, 1000.0);
1321 assert_eq!(receiver.retries_left, 1);
1322 }
1323
1324 #[test]
1325 fn test_empty_hmu_cancels_waiting_receiver() {
1326 let (_, mut receiver) = make_sender_receiver();
1327 receiver.status = ResourceStatus::Transferring;
1328 receiver.waiting_for_hmu = true;
1329 let update = hmu(&receiver.resource_hash, 1, Vec::new());
1330
1331 let actions = receiver.handle_hashmap_update(&update, 1001.0);
1332
1333 assert_eq!(receiver.status, ResourceStatus::Failed);
1334 assert!(actions
1335 .iter()
1336 .any(|action| matches!(action, ResourceAction::SendCancelReceiver(_))));
1337 assert!(!actions
1338 .iter()
1339 .any(|action| matches!(action, ResourceAction::SendRequest(_))));
1340 }
1341
1342 #[test]
1343 fn test_malformed_hmu_cancels_waiting_receiver() {
1344 let (_, mut receiver) = make_sender_receiver();
1345 receiver.status = ResourceStatus::Transferring;
1346 receiver.waiting_for_hmu = true;
1347 let mut malformed = receiver.resource_hash.clone();
1348 malformed.extend_from_slice(&[0xc1]);
1349
1350 let actions = receiver.handle_hashmap_update(&malformed, 1001.0);
1351
1352 assert_eq!(receiver.status, ResourceStatus::Failed);
1353 assert!(actions
1354 .iter()
1355 .any(|action| matches!(action, ResourceAction::SendCancelReceiver(_))));
1356 }
1357
1358 #[test]
1359 fn test_hmu_without_payload_cancels_waiting_receiver() {
1360 let (_, mut receiver) = make_sender_receiver();
1361 receiver.status = ResourceStatus::Transferring;
1362 receiver.waiting_for_hmu = true;
1363 let update = receiver.resource_hash.clone();
1364
1365 let actions = receiver.handle_hashmap_update(&update, 1001.0);
1366
1367 assert_eq!(receiver.status, ResourceStatus::Failed);
1368 assert!(actions
1369 .iter()
1370 .any(|action| matches!(action, ResourceAction::SendCancelReceiver(_))));
1371 }
1372
1373 #[test]
1374 fn test_hmu_for_another_resource_is_ignored() {
1375 let (_, mut receiver) = make_sender_receiver();
1376 receiver.status = ResourceStatus::Transferring;
1377 receiver.waiting_for_hmu = true;
1378 receiver.last_activity = 1000.0;
1379 let update = hmu(&[0xEE; 32], 0, vec![0x99; RESOURCE_MAPHASH_LEN]);
1380
1381 let actions = receiver.handle_hashmap_update(&update, 2000.0);
1382
1383 assert!(actions.is_empty());
1384 assert!(receiver.waiting_for_hmu);
1385 assert_eq!(receiver.last_activity, 1000.0);
1386 }
1387
1388 #[test]
1389 fn test_misaligned_hmu_hashmap_cancels_waiting_receiver() {
1390 let (_, mut receiver) = make_sender_receiver();
1391 receiver.status = ResourceStatus::Transferring;
1392 receiver.waiting_for_hmu = true;
1393 let update = hmu(
1394 &receiver.resource_hash,
1395 0,
1396 vec![0x99; RESOURCE_MAPHASH_LEN + 1],
1397 );
1398
1399 let actions = receiver.handle_hashmap_update(&update, 1001.0);
1400
1401 assert_eq!(receiver.status, ResourceStatus::Failed);
1402 assert!(actions
1403 .iter()
1404 .any(|action| matches!(action, ResourceAction::SendCancelReceiver(_))));
1405 }
1406
1407 #[test]
1408 fn test_out_of_range_hmu_segment_cancels_waiting_receiver() {
1409 let (_, mut receiver) = make_sender_receiver();
1410 receiver.status = ResourceStatus::Transferring;
1411 receiver.waiting_for_hmu = true;
1412 let update = hmu(
1413 &receiver.resource_hash,
1414 u64::MAX,
1415 vec![0x99; RESOURCE_MAPHASH_LEN],
1416 );
1417
1418 let actions = receiver.handle_hashmap_update(&update, 1001.0);
1419
1420 assert_eq!(receiver.status, ResourceStatus::Failed);
1421 assert!(actions
1422 .iter()
1423 .any(|action| matches!(action, ResourceAction::SendCancelReceiver(_))));
1424 }
1425
1426 #[test]
1427 fn test_tick_is_inert_once_receiver_starts_assembling() {
1428 for status in [
1429 ResourceStatus::Assembling,
1430 ResourceStatus::Complete,
1431 ResourceStatus::Failed,
1432 ResourceStatus::Corrupt,
1433 ResourceStatus::Rejected,
1434 ] {
1435 let (_, mut receiver) = make_sender_receiver();
1436 receiver.status = status;
1437 receiver.last_activity = 1.0;
1438 receiver.retries_left = 0;
1439
1440 let actions = receiver.tick(10_000.0, &identity_decrypt, &NoopCompressor);
1441
1442 assert!(actions.is_empty(), "tick emitted an action for {status:?}");
1443 assert_eq!(receiver.status, status);
1444 assert_eq!(receiver.last_activity, 1.0);
1445 }
1446 }
1447
1448 #[test]
1449 fn test_receive_part_stores() {
1450 let (mut sender, mut receiver) = make_sender_receiver();
1451 receiver.accept(1000.0);
1452
1453 let mut request = Vec::new();
1456 request.push(RESOURCE_HASHMAP_IS_NOT_EXHAUSTED);
1457 request.extend_from_slice(&sender.resource_hash);
1458 request.extend_from_slice(&sender.part_hashes[0]);
1459
1460 let send_actions = sender.handle_request(&request, 1001.0);
1461 let part_data = send_actions
1462 .iter()
1463 .find_map(|a| match a {
1464 ResourceAction::SendPart(d) => Some(d.clone()),
1465 _ => None,
1466 })
1467 .unwrap();
1468
1469 receiver.req_sent = 1000.5;
1471 let _actions = receiver.receive_part(&part_data, 1001.0);
1472 assert_eq!(receiver.received_count, 1);
1473 }
1474
1475 #[test]
1476 fn test_consecutive_completed_height() {
1477 let (sender, mut receiver) = make_sender_receiver();
1478 receiver.accept(1000.0);
1479
1480 if sender.total_parts() > 1 {
1482 assert_eq!(receiver.consecutive_completed_height, -1);
1484 }
1485 }
1486
1487 #[test]
1488 fn receive_part_search_window_matches_request_after_completed_height() {
1489 let mut rng = rns_crypto::FixedRng::new(&[0x5a; 64]);
1490 let data: Vec<u8> = (0..48).collect();
1491 let mut sender = ResourceSender::new(
1492 &data,
1493 None,
1494 8,
1495 &identity_encrypt,
1496 &NoopCompressor,
1497 &mut rng,
1498 1000.0,
1499 false,
1500 false,
1501 None,
1502 1,
1503 1,
1504 None,
1505 0.5,
1506 6.0,
1507 )
1508 .unwrap();
1509 assert!(sender.total_parts() > 5);
1510
1511 let advertisement = sender.get_advertisement(0);
1512 let mut receiver =
1513 ResourceReceiver::from_advertisement(&advertisement, 8, 0.5, 1000.0, None, None)
1514 .unwrap();
1515 let first_request = receiver
1516 .accept(1000.0)
1517 .into_iter()
1518 .find_map(|action| match action {
1519 ResourceAction::SendRequest(data) => Some(data),
1520 _ => None,
1521 })
1522 .unwrap();
1523 let first_parts = sender.handle_request(&first_request, 1001.0);
1524 let first_part = first_parts
1525 .iter()
1526 .find_map(|action| match action {
1527 ResourceAction::SendPart(data)
1528 if map_hash(data, &receiver.random_hash) == sender.part_hashes[0] =>
1529 {
1530 Some(data.clone())
1531 }
1532 _ => None,
1533 })
1534 .unwrap();
1535 receiver.receive_part(&first_part, 1002.0);
1536 assert_eq!(receiver.consecutive_completed_height, 0);
1537
1538 let next_request = receiver
1539 .request_next(1003.0)
1540 .into_iter()
1541 .find_map(|action| match action {
1542 ResourceAction::SendRequest(data) => Some(data),
1543 _ => None,
1544 })
1545 .unwrap();
1546 let next_parts = sender.handle_request(&next_request, 1004.0);
1547 let window_tail_index = receiver.window.window;
1548 let window_tail = next_parts
1549 .iter()
1550 .find_map(|action| match action {
1551 ResourceAction::SendPart(data)
1552 if map_hash(data, &receiver.random_hash)
1553 == sender.part_hashes[window_tail_index] =>
1554 {
1555 Some(data.clone())
1556 }
1557 _ => None,
1558 })
1559 .expect("request must include the inclusive tail of the next window");
1560
1561 let actions = receiver.receive_part(&window_tail, 1005.0);
1562 assert!(receiver.parts[window_tail_index].is_some());
1563 assert_eq!(receiver.received_count, 2);
1564 assert_eq!(receiver.consecutive_completed_height, 0);
1565 assert!(actions.iter().any(|action| matches!(
1566 action,
1567 ResourceAction::ProgressUpdate {
1568 received: 2,
1569 total,
1570 } if *total == receiver.total_parts
1571 )));
1572 }
1573
1574 #[test]
1575 fn test_handle_cancel() {
1576 let (_, mut receiver) = make_sender_receiver();
1577 receiver.accept(1000.0);
1578 let _actions = receiver.handle_cancel();
1579 assert_eq!(receiver.status, ResourceStatus::Failed);
1580 }
1581
1582 #[test]
1583 fn test_assemble_compressed_resource_rejects_oversized_decompression() {
1584 let data = b"oversized!";
1585 let mut rng = rns_crypto::FixedRng::new(&[0x93; 64]);
1586
1587 let mut sender = ResourceSender::new(
1588 data,
1589 None,
1590 RESOURCE_SDU,
1591 &identity_encrypt,
1592 &ExpandingCompressor,
1593 &mut rng,
1594 1000.0,
1595 true,
1596 false,
1597 None,
1598 1,
1599 1,
1600 None,
1601 0.5,
1602 6.0,
1603 )
1604 .unwrap();
1605
1606 let adv = sender.get_advertisement(0);
1607 let mut receiver =
1608 ResourceReceiver::from_advertisement(&adv, RESOURCE_SDU, 0.5, 1000.0, None, None)
1609 .unwrap();
1610 receiver.max_decompressed_size = data.len() - 1;
1611
1612 let request_data = receiver
1613 .accept(1001.0)
1614 .into_iter()
1615 .find_map(|a| match a {
1616 ResourceAction::SendRequest(d) => Some(d),
1617 _ => None,
1618 })
1619 .unwrap();
1620
1621 let send_actions = sender.handle_request(&request_data, 1002.0);
1622 receiver.req_sent = 1001.0;
1623 for action in &send_actions {
1624 if let ResourceAction::SendPart(part_data) = action {
1625 receiver.receive_part(part_data, 1003.0);
1626 }
1627 }
1628
1629 let assemble_actions = receiver.assemble(&identity_decrypt, &ExpandingCompressor);
1630 assert_eq!(receiver.status, ResourceStatus::Corrupt);
1631 assert!(assemble_actions
1632 .iter()
1633 .any(|a| matches!(a, ResourceAction::SendCancelReceiver(_))));
1634 assert!(assemble_actions
1635 .iter()
1636 .any(|a| matches!(a, ResourceAction::Failed(ResourceError::TooLarge))));
1637 assert!(assemble_actions
1638 .iter()
1639 .any(|a| matches!(a, ResourceAction::TeardownLink)));
1640 }
1641
1642 #[test]
1643 fn test_full_transfer_small_data() {
1644 let data = b"small data";
1646 let mut rng = rns_crypto::FixedRng::new(&[0x77; 64]);
1647
1648 let mut sender = ResourceSender::new(
1649 data,
1650 None,
1651 RESOURCE_SDU,
1652 &identity_encrypt,
1653 &NoopCompressor,
1654 &mut rng,
1655 1000.0,
1656 false,
1657 false,
1658 None,
1659 1,
1660 1,
1661 None,
1662 0.5,
1663 6.0,
1664 )
1665 .unwrap();
1666
1667 let adv = sender.get_advertisement(0);
1668 let mut receiver =
1669 ResourceReceiver::from_advertisement(&adv, RESOURCE_SDU, 0.5, 1000.0, None, None)
1670 .unwrap();
1671
1672 let req_actions = receiver.accept(1001.0);
1674 assert_eq!(receiver.status, ResourceStatus::Transferring);
1675
1676 let request_data = req_actions
1678 .iter()
1679 .find_map(|a| match a {
1680 ResourceAction::SendRequest(d) => Some(d.clone()),
1681 _ => None,
1682 })
1683 .unwrap();
1684
1685 let send_actions = sender.handle_request(&request_data, 1002.0);
1687
1688 receiver.req_sent = 1001.0;
1690 for action in &send_actions {
1691 if let ResourceAction::SendPart(part_data) = action {
1692 receiver.receive_part(part_data, 1003.0);
1693 }
1694 }
1695
1696 assert_eq!(receiver.received_count, receiver.total_parts);
1697
1698 let assemble_actions = receiver.assemble(&identity_decrypt, &NoopCompressor);
1700
1701 let has_proof = assemble_actions
1703 .iter()
1704 .any(|a| matches!(a, ResourceAction::SendProof(_)));
1705 let has_data = assemble_actions
1706 .iter()
1707 .any(|a| matches!(a, ResourceAction::DataReceived { .. }));
1708 let has_complete = assemble_actions
1709 .iter()
1710 .any(|a| matches!(a, ResourceAction::Completed));
1711
1712 assert!(has_proof, "Should send proof");
1713 assert!(has_data, "Should return data");
1714 assert!(has_complete, "Should be completed");
1715
1716 let received_data = assemble_actions
1718 .iter()
1719 .find_map(|a| match a {
1720 ResourceAction::DataReceived { data, .. } => Some(data.clone()),
1721 _ => None,
1722 })
1723 .unwrap();
1724 assert_eq!(received_data, data);
1725
1726 let proof_data = assemble_actions
1728 .iter()
1729 .find_map(|a| match a {
1730 ResourceAction::SendProof(d) => Some(d.clone()),
1731 _ => None,
1732 })
1733 .unwrap();
1734
1735 let _proof_actions = sender.handle_proof(&proof_data, 1004.0);
1736 assert_eq!(sender.status, ResourceStatus::Complete);
1737 }
1738
1739 #[test]
1740 fn test_full_transfer_with_metadata() {
1741 let data = b"data with metadata";
1742 let metadata = b"some metadata";
1743 let mut rng = rns_crypto::FixedRng::new(&[0x88; 64]);
1744
1745 let mut sender = ResourceSender::new(
1746 data,
1747 Some(metadata),
1748 RESOURCE_SDU,
1749 &identity_encrypt,
1750 &NoopCompressor,
1751 &mut rng,
1752 1000.0,
1753 false,
1754 false,
1755 None,
1756 1,
1757 1,
1758 None,
1759 0.5,
1760 6.0,
1761 )
1762 .unwrap();
1763
1764 assert!(sender.flags.has_metadata);
1765
1766 let adv = sender.get_advertisement(0);
1767 let mut receiver =
1768 ResourceReceiver::from_advertisement(&adv, RESOURCE_SDU, 0.5, 1000.0, None, None)
1769 .unwrap();
1770
1771 assert!(receiver.has_metadata);
1772
1773 let req_actions = receiver.accept(1001.0);
1775 let request_data = req_actions
1776 .iter()
1777 .find_map(|a| match a {
1778 ResourceAction::SendRequest(d) => Some(d.clone()),
1779 _ => None,
1780 })
1781 .unwrap();
1782
1783 let send_actions = sender.handle_request(&request_data, 1002.0);
1784 receiver.req_sent = 1001.0;
1785 for action in &send_actions {
1786 if let ResourceAction::SendPart(part_data) = action {
1787 receiver.receive_part(part_data, 1003.0);
1788 }
1789 }
1790
1791 let assemble_actions = receiver.assemble(&identity_decrypt, &NoopCompressor);
1792
1793 let (recv_data, recv_meta) = assemble_actions
1794 .iter()
1795 .find_map(|a| match a {
1796 ResourceAction::DataReceived { data, metadata } => {
1797 Some((data.clone(), metadata.clone()))
1798 }
1799 _ => None,
1800 })
1801 .unwrap();
1802
1803 assert_eq!(recv_data, data);
1804 assert_eq!(recv_meta.unwrap(), metadata);
1805 }
1806
1807 #[test]
1808 fn test_previous_window_restore() {
1809 let (_, _receiver) = make_sender_receiver();
1810 let adv_data = {
1812 let mut rng = rns_crypto::FixedRng::new(&[0x42; 64]);
1813 let sender = ResourceSender::new(
1814 b"test",
1815 None,
1816 RESOURCE_SDU,
1817 &identity_encrypt,
1818 &NoopCompressor,
1819 &mut rng,
1820 1000.0,
1821 false,
1822 false,
1823 None,
1824 1,
1825 1,
1826 None,
1827 0.5,
1828 6.0,
1829 )
1830 .unwrap();
1831 sender.get_advertisement(0)
1832 };
1833
1834 let receiver = ResourceReceiver::from_advertisement(
1835 &adv_data,
1836 RESOURCE_SDU,
1837 0.5,
1838 1000.0,
1839 Some(8),
1840 Some(50000.0),
1841 )
1842 .unwrap();
1843 assert_eq!(receiver.window.window, 8);
1844 }
1845
1846 #[test]
1847 fn test_tick_timeout_retry() {
1848 let (_, mut receiver) = make_sender_receiver();
1849 receiver.accept(1000.0);
1850 receiver.rtt = Some(0.1);
1851
1852 let actions = receiver.tick(9999.0, &identity_decrypt, &NoopCompressor);
1854 assert!(!actions.is_empty() || receiver.retries_left < RESOURCE_MAX_RETRIES);
1856 }
1857
1858 #[test]
1859 fn test_tick_waiting_for_hmu_gets_extra_timeout() {
1860 let (_, mut receiver) = make_sender_receiver();
1861 receiver.accept(1000.0);
1862 receiver.waiting_for_hmu = true;
1863 receiver.outstanding_parts = 0;
1864 let eifr = 10_000.0;
1865 receiver.previous_eifr = Some(eifr);
1866 receiver.last_activity = 1000.0;
1867
1868 let old_timeout = base_timeout(&receiver, eifr);
1869 let now = old_timeout + 0.01;
1870
1871 let actions = receiver.tick(now, &identity_decrypt, &NoopCompressor);
1872
1873 assert!(actions.is_empty(), "receiver should keep waiting for HMU");
1874 assert_eq!(receiver.retries_left, RESOURCE_MAX_RETRIES);
1875 assert_eq!(receiver.status, ResourceStatus::Transferring);
1876 }
1877
1878 #[test]
1879 fn test_tick_zero_outstanding_parts_gets_extra_timeout_without_hmu_flag() {
1880 let (_, mut receiver) = make_sender_receiver();
1881 receiver.accept(1000.0);
1882 receiver.waiting_for_hmu = false;
1883 receiver.outstanding_parts = 0;
1884 let eifr = 10_000.0;
1885 receiver.previous_eifr = Some(eifr);
1886 receiver.last_activity = 1000.0;
1887
1888 let old_timeout = base_timeout(&receiver, eifr);
1889 let now = old_timeout + 0.01;
1890
1891 let actions = receiver.tick(now, &identity_decrypt, &NoopCompressor);
1892
1893 assert!(
1894 actions.is_empty(),
1895 "receiver should keep waiting for follow-up hashmap data"
1896 );
1897 assert_eq!(receiver.retries_left, RESOURCE_MAX_RETRIES);
1898 assert_eq!(receiver.status, ResourceStatus::Transferring);
1899 }
1900
1901 #[test]
1902 fn test_tick_waiting_for_hmu_retries_after_extended_timeout() {
1903 let (_, mut receiver) = make_sender_receiver();
1904 receiver.accept(1000.0);
1905 receiver.waiting_for_hmu = true;
1906 receiver.outstanding_parts = 0;
1907 let eifr = 10_000.0;
1908 receiver.previous_eifr = Some(eifr);
1909 receiver.last_activity = 1000.0;
1910
1911 let now = hmu_timeout(&receiver, eifr) + 0.01;
1912 let _actions = receiver.tick(now, &identity_decrypt, &NoopCompressor);
1913
1914 assert_eq!(receiver.retries_left, RESOURCE_MAX_RETRIES - 1);
1915 assert!(!receiver.waiting_for_hmu);
1916 }
1917
1918 #[test]
1919 fn test_tick_inflight_parts_do_not_get_hmu_timeout_extension() {
1920 let (_, mut receiver) = make_sender_receiver();
1921 receiver.accept(1000.0);
1922 receiver.waiting_for_hmu = false;
1923 receiver.outstanding_parts = 2;
1924 let eifr = 10_000.0;
1925 receiver.previous_eifr = Some(eifr);
1926 receiver.last_activity = 1000.0;
1927
1928 let now = base_timeout(&receiver, eifr) + 0.01;
1929 let _actions = receiver.tick(now, &identity_decrypt, &NoopCompressor);
1930
1931 assert_eq!(receiver.retries_left, RESOURCE_MAX_RETRIES - 1);
1932 }
1933
1934 #[test]
1935 fn test_tick_max_retries_exceeded() {
1936 let (_, mut receiver) = make_sender_receiver();
1937 receiver.accept(1000.0);
1938 receiver.retries_left = 0;
1939 receiver.rtt = Some(0.001);
1940 receiver.eifr = Some(100000.0);
1941
1942 let _actions = receiver.tick(9999.0, &identity_decrypt, &NoopCompressor);
1943 assert_eq!(receiver.status, ResourceStatus::Failed);
1944 }
1945}