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