Skip to main content

rns_net/common/link_manager/
resource_handling.rs

1use super::*;
2
3/// Incoming transfers a link keeps in progress at once. A Python sender
4/// advertises one resource at a time per link; the headroom covers concurrent
5/// requests and responses. Further advertisements are refused until one ends.
6pub(super) const MAX_ACTIVE_INCOMING_RESOURCES: usize = 16;
7
8pub(super) struct ResourceSendParams<'a> {
9    pub(super) data: &'a [u8],
10    pub(super) metadata: Option<&'a [u8]>,
11    pub(super) auto_compress: bool,
12    pub(super) compressor: &'a dyn rns_core::buffer::types::Compressor,
13    pub(super) is_response: bool,
14    pub(super) request_id: Option<Vec<u8>>,
15    pub(super) rng: &'a mut dyn Rng,
16    pub(super) now: f64,
17}
18
19impl LinkManager {
20    pub(super) fn request_response_deadline(link: &ManagedLink, now: f64) -> f64 {
21        now + link.engine.rtt().unwrap_or(1.0) * constants::LINK_TRAFFIC_TIMEOUT_FACTOR
22            + constants::RESOURCE_RESPONSE_MAX_GRACE_TIME * 1.125
23    }
24
25    pub(super) fn build_resource_senders(
26        link: &ManagedLink,
27        params: ResourceSendParams<'_>,
28    ) -> Result<Vec<ResourceSender>, rns_core::resource::ResourceError> {
29        let ResourceSendParams {
30            data,
31            metadata,
32            auto_compress,
33            compressor,
34            is_response,
35            request_id,
36            rng,
37            now,
38        } = params;
39        let link_rtt = link.engine.rtt().unwrap_or(1.0);
40        let resource_sdu = Self::resource_sdu_for_link(link);
41        let metadata_overhead = metadata.map(|m| 3 + m.len()).unwrap_or(0);
42        let logical_size = metadata_overhead + data.len();
43
44        if logical_size <= constants::RESOURCE_MAX_EFFICIENT_SIZE {
45            let enc_rng = std::cell::RefCell::new(rns_crypto::OsRng);
46            let encrypt_fn = |plaintext: &[u8]| -> Vec<u8> {
47                link.engine
48                    .encrypt(plaintext, &mut *enc_rng.borrow_mut())
49                    .unwrap_or_else(|_| plaintext.to_vec())
50            };
51            return ResourceSender::new(
52                data,
53                metadata,
54                resource_sdu,
55                &encrypt_fn,
56                compressor,
57                rng,
58                now,
59                auto_compress,
60                is_response,
61                request_id,
62                1,
63                1,
64                None,
65                link_rtt,
66                6.0,
67            )
68            .map(|sender| vec![sender]);
69        }
70
71        if metadata_overhead > constants::RESOURCE_MAX_EFFICIENT_SIZE {
72            return Err(rns_core::resource::ResourceError::TooLarge);
73        }
74
75        let first_payload_len = core::cmp::min(
76            data.len(),
77            constants::RESOURCE_MAX_EFFICIENT_SIZE - metadata_overhead,
78        );
79        let remaining = data.len().saturating_sub(first_payload_len);
80        let total_segments = 1 + remaining.div_ceil(constants::RESOURCE_MAX_EFFICIENT_SIZE) as u64;
81
82        let enc_rng = std::cell::RefCell::new(rns_crypto::OsRng);
83        let encrypt_fn = |plaintext: &[u8]| -> Vec<u8> {
84            link.engine
85                .encrypt(plaintext, &mut *enc_rng.borrow_mut())
86                .unwrap_or_else(|_| plaintext.to_vec())
87        };
88
89        let mut senders = Vec::new();
90        let mut first = ResourceSender::new(
91            &data[..first_payload_len],
92            metadata,
93            resource_sdu,
94            &encrypt_fn,
95            compressor,
96            rng,
97            now,
98            auto_compress,
99            is_response,
100            request_id.clone(),
101            1,
102            total_segments,
103            None,
104            link_rtt,
105            6.0,
106        )?;
107        first.data_size = logical_size;
108        let original_hash = first.original_hash;
109        let has_metadata = metadata.is_some();
110        senders.push(first);
111
112        let mut offset = first_payload_len;
113        let mut segment_index = 2;
114        while offset < data.len() {
115            let end = core::cmp::min(offset + constants::RESOURCE_MAX_EFFICIENT_SIZE, data.len());
116            let mut sender = ResourceSender::new(
117                &data[offset..end],
118                None,
119                resource_sdu,
120                &encrypt_fn,
121                compressor,
122                rng,
123                now,
124                auto_compress,
125                is_response,
126                request_id.clone(),
127                segment_index,
128                total_segments,
129                Some(original_hash),
130                link_rtt,
131                6.0,
132            )?;
133            sender.data_size = logical_size;
134            sender.flags.has_metadata = has_metadata;
135            senders.push(sender);
136            segment_index += 1;
137            offset = end;
138        }
139
140        Ok(senders)
141    }
142
143    fn read_stream_segment(
144        stream: &mut OutgoingStreamTransfer,
145        capacity: usize,
146    ) -> Result<Vec<u8>, ResourceTransferError> {
147        let length = stream.remaining.min(capacity as u64) as usize;
148        let mut data = vec![0; length];
149        stream
150            .reader
151            .read_exact(&mut data)
152            .map_err(|error| ResourceTransferError::Source(error.to_string()))?;
153        stream.remaining -= length as u64;
154        if stream.remaining == 0 {
155            let mut trailing = [0u8; 1];
156            if stream
157                .reader
158                .read(&mut trailing)
159                .map_err(|error| ResourceTransferError::Source(error.to_string()))?
160                != 0
161            {
162                return Err(ResourceTransferError::Source(
163                    "resource source exceeds declared length".into(),
164                ));
165            }
166        }
167        Ok(data)
168    }
169
170    #[allow(clippy::too_many_arguments)]
171    fn build_stream_segment(
172        link: &ManagedLink,
173        stream: &mut OutgoingStreamTransfer,
174        metadata: Option<&[u8]>,
175        segment_index: u64,
176        total_segments: u64,
177        original_hash: Option<[u8; 32]>,
178        rng: &mut dyn Rng,
179        now: f64,
180    ) -> Result<ResourceSender, ResourceTransferError> {
181        let metadata_overhead = metadata.map(|value| 3 + value.len()).unwrap_or(0);
182        if metadata_overhead > constants::RESOURCE_MAX_EFFICIENT_SIZE {
183            return Err(ResourceTransferError::Protocol(
184                "resource metadata exceeds maximum segment size".into(),
185            ));
186        }
187        let data = Self::read_stream_segment(
188            stream,
189            constants::RESOURCE_MAX_EFFICIENT_SIZE - metadata_overhead,
190        )?;
191        let enc_rng = std::cell::RefCell::new(rns_crypto::OsRng);
192        let encrypt_fn = |plaintext: &[u8]| -> Vec<u8> {
193            link.engine
194                .encrypt(plaintext, &mut *enc_rng.borrow_mut())
195                .unwrap_or_else(|_| plaintext.to_vec())
196        };
197        let mut sender = ResourceSender::new(
198            &data,
199            metadata,
200            Self::resource_sdu_for_link(link),
201            &encrypt_fn,
202            &Bzip2Compressor,
203            rng,
204            now,
205            stream.auto_compress,
206            false,
207            None,
208            segment_index,
209            total_segments,
210            original_hash,
211            link.engine.rtt().unwrap_or(1.0),
212            6.0,
213        )
214        .map_err(|error| ResourceTransferError::Protocol(error.to_string()))?;
215        sender.data_size = (stream.declared_length + stream.metadata_overhead) as usize;
216        sender.flags.has_metadata = stream.metadata_overhead != 0;
217        Ok(sender)
218    }
219
220    pub(super) fn start_resource_senders(
221        link: &mut ManagedLink,
222        mut senders: Vec<ResourceSender>,
223        now: f64,
224    ) -> Vec<ResourceAction> {
225        if senders.is_empty() {
226            return Vec::new();
227        }
228
229        let mut first = senders.remove(0);
230        let adv_actions = first.advertise(now);
231
232        if first.total_segments > 1 {
233            let original_hash = first.original_hash;
234            let split = OutgoingSplitTransfer {
235                total_segments: first.total_segments,
236                completed_segments: 0,
237                current_segment_index: first.segment_index,
238                current_sent_parts: 0,
239                current_total_parts: first.total_parts(),
240            };
241            link.outgoing_splits.insert(original_hash, split);
242        }
243
244        link.outgoing_resources.push(first);
245        link.outgoing_resources.extend(senders);
246        adv_actions
247    }
248
249    /// Handle resource advertisement (CONTEXT_RESOURCE_ADV).
250    pub(super) fn handle_resource_adv(
251        &mut self,
252        link_id: &LinkId,
253        adv_plaintext: &[u8],
254        rng: &mut dyn Rng,
255    ) -> Vec<LinkManagerAction> {
256        let has_request_handlers = !self.request_handlers.is_empty()
257            || !self.deferred_request_handlers.is_empty()
258            || !self.management_paths.is_empty();
259        let link = match self.links.get_mut(link_id) {
260            Some(l) => l,
261            None => return Vec::new(),
262        };
263
264        let link_rtt = link.engine.rtt().unwrap_or(1.0);
265        let resource_sdu = Self::resource_sdu_for_link(link);
266        let now = time::now();
267
268        let receiver = match ResourceReceiver::from_advertisement(
269            adv_plaintext,
270            resource_sdu,
271            link_rtt,
272            now,
273            None,
274            None,
275        ) {
276            Ok(r) => r,
277            Err(e) => {
278                log::debug!("Resource ADV rejected: {}", e);
279                let _ = link;
280                return self.teardown_link(link_id);
281            }
282        };
283
284        // Advertisement retries belong to the existing transfer. A second receiver
285        // would request the same parts and deliver the payload again. Preserve
286        // pending approval, progress and assembly; its normal retry timer handles
287        // lost requests. Completed receivers remain protected until tick cleanup.
288        if link
289            .incoming_resources
290            .iter()
291            .any(|existing| existing.resource_hash == receiver.resource_hash)
292        {
293            return Vec::new();
294        }
295
296        let active_incoming = link
297            .incoming_resources
298            .iter()
299            .filter(|existing| existing.status < rns_core::resource::ResourceStatus::Complete)
300            .count();
301        if active_incoming >= MAX_ACTIVE_INCOMING_RESOURCES {
302            log::debug!(
303                "refused resource advertisement on link {:02x?}: {} transfers in progress",
304                &link_id[..4],
305                active_incoming
306            );
307            let reject_actions = {
308                let mut receiver = receiver;
309                receiver.reject()
310            };
311            let _ = link;
312            return self.process_resource_actions(link_id, reject_actions, rng);
313        }
314
315        let strategy = link.resource_strategy;
316        let resource_hash = receiver.resource_hash.clone();
317        let transfer_size = receiver.transfer_size;
318        let has_metadata = receiver.has_metadata;
319        let is_request = receiver.flags.is_request;
320        let is_response = receiver.flags.is_response;
321        let is_split = receiver.flags.split;
322        let segment_index = receiver.segment_index;
323        let total_segments = receiver.total_segments;
324        let original_hash = match Self::resource_hash_key(&receiver.original_hash) {
325            Some(key) => key,
326            None => return Vec::new(),
327        };
328
329        if !is_request && !is_response {
330            let maximum = match &link.resource_receive_mode {
331                ResourceReceiveMode::Memory { max_bytes } => Some(*max_bytes),
332                ResourceReceiveMode::TemporaryFile { max_bytes, .. } => *max_bytes,
333            };
334            if maximum.is_some_and(|maximum| receiver.data_size > maximum) {
335                let reject_actions = {
336                    let mut receiver = receiver;
337                    receiver.reject()
338                };
339                let _ = link;
340                return self.process_resource_actions(link_id, reject_actions, rng);
341            }
342        }
343
344        if is_split && segment_index > 1 {
345            let should_accept = link
346                .incoming_splits
347                .get(&original_hash)
348                .is_some_and(|split| {
349                    split.completed_segments + 1 == segment_index
350                        && split.total_segments == total_segments
351                });
352
353            if !should_accept {
354                let reject_actions = {
355                    let mut r = receiver;
356                    r.reject()
357                };
358                let _ = link;
359                return self.process_resource_actions(link_id, reject_actions, rng);
360            }
361
362            let current_total_parts = receiver.total_parts;
363            link.incoming_resources.push(receiver);
364            let idx = link.incoming_resources.len() - 1;
365            if let Some(split) = link.incoming_splits.get_mut(&original_hash) {
366                split.current_segment_index = segment_index;
367                split.current_received_parts = 0;
368                split.current_total_parts = current_total_parts;
369            }
370            let resource_actions = link.incoming_resources[idx].accept(now);
371            let _ = link;
372            return self.process_resource_actions(link_id, resource_actions, rng);
373        }
374
375        if is_request {
376            // A request resource is only useful when this destination has a
377            // request handler, and request IDs are truncated hashes.
378            if !has_request_handlers || Self::response_request_id(&receiver.request_id).is_none() {
379                return Vec::new();
380            }
381            if link
382                .max_request_size
383                .is_some_and(|limit| receiver.data_size > limit as u64)
384            {
385                log::debug!(
386                    "ignored request with excessive size {} bytes on link {:02x?}",
387                    receiver.data_size,
388                    &link_id[..4]
389                );
390                let reject_actions = {
391                    let mut receiver = receiver;
392                    receiver.reject()
393                };
394                let _ = link;
395                return self.process_resource_actions(link_id, reject_actions, rng);
396            }
397            if is_split {
398                link.incoming_splits.insert(
399                    original_hash,
400                    IncomingSplitTransfer {
401                        total_segments,
402                        completed_segments: 0,
403                        current_segment_index: segment_index,
404                        current_received_parts: 0,
405                        current_total_parts: receiver.total_parts,
406                        storage: IncomingSplitStorage::Memory(Vec::new()),
407                        metadata: None,
408                        is_request: true,
409                        is_response: false,
410                    },
411                );
412            }
413            link.incoming_resources.push(receiver);
414            let idx = link.incoming_resources.len() - 1;
415            let resource_actions = link.incoming_resources[idx].accept(now);
416            let _ = link;
417            return self.process_resource_actions(link_id, resource_actions, rng);
418        }
419
420        if is_response {
421            // Response resources bypass the application acceptance strategy —
422            // they are answers to pending requests, not independent resources.
423            let Some(request_id) = Self::response_request_id(&receiver.request_id) else {
424                return Vec::new();
425            };
426            let Some(pending) = link.pending_requests.get(&request_id) else {
427                return Vec::new();
428            };
429            if let Some(maximum) = pending.max_response_size {
430                if receiver.data_size > maximum as u64 {
431                    let response_size = receiver.data_size;
432                    link.pending_requests.remove(&request_id);
433                    log::debug!(
434                        "rejected response with excessive size {} bytes on link {:02x?}",
435                        response_size,
436                        &link_id[..4]
437                    );
438                    let reject_actions = {
439                        let mut receiver = receiver;
440                        receiver.reject()
441                    };
442                    let _ = link;
443                    let mut actions = self.process_resource_actions(link_id, reject_actions, rng);
444                    actions.push(LinkManagerAction::RequestFailed {
445                        link_id: *link_id,
446                        request_id,
447                        reason: RequestFailure::ResponseTooLarge {
448                            size: response_size,
449                            maximum,
450                        },
451                    });
452                    return actions;
453                }
454            }
455            if is_split {
456                link.incoming_splits.insert(
457                    original_hash,
458                    IncomingSplitTransfer {
459                        total_segments,
460                        completed_segments: 0,
461                        current_segment_index: segment_index,
462                        current_received_parts: 0,
463                        current_total_parts: receiver.total_parts,
464                        storage: IncomingSplitStorage::Memory(Vec::new()),
465                        metadata: None,
466                        is_request: false,
467                        is_response,
468                    },
469                );
470            }
471            link.incoming_resources.push(receiver);
472            let idx = link.incoming_resources.len() - 1;
473            let resource_actions = link.incoming_resources[idx].accept(now);
474            let _ = link;
475            return self.process_resource_actions(link_id, resource_actions, rng);
476        }
477
478        match strategy {
479            ResourceStrategy::AcceptNone => {
480                // Reject: send RCL
481                let reject_actions = {
482                    let mut r = receiver;
483                    r.reject()
484                };
485                self.process_resource_actions(link_id, reject_actions, rng)
486            }
487            ResourceStrategy::AcceptAll => {
488                if is_split {
489                    let storage =
490                        match Self::incoming_storage(&link.resource_receive_mode, &original_hash) {
491                            Ok(storage) => storage,
492                            Err(error) => {
493                                let reject_actions = {
494                                    let mut receiver = receiver;
495                                    receiver.reject()
496                                };
497                                let _ = link;
498                                let mut actions =
499                                    self.process_resource_actions(link_id, reject_actions, rng);
500                                actions.push(LinkManagerAction::ResourceFailed {
501                                    link_id: *link_id,
502                                    error: format!("resource storage failed: {error}"),
503                                });
504                                return actions;
505                            }
506                        };
507                    link.incoming_splits.insert(
508                        original_hash,
509                        IncomingSplitTransfer {
510                            total_segments,
511                            completed_segments: 0,
512                            current_segment_index: segment_index,
513                            current_received_parts: 0,
514                            current_total_parts: receiver.total_parts,
515                            storage,
516                            metadata: None,
517                            is_request: false,
518                            is_response,
519                        },
520                    );
521                }
522                link.incoming_resources.push(receiver);
523                let idx = link.incoming_resources.len() - 1;
524                let resource_actions = link.incoming_resources[idx].accept(now);
525                let _ = link;
526                self.process_resource_actions(link_id, resource_actions, rng)
527            }
528            ResourceStrategy::AcceptApp => {
529                link.incoming_resources.push(receiver);
530                // Query application callback
531                vec![LinkManagerAction::ResourceAcceptQuery {
532                    link_id: *link_id,
533                    resource_hash,
534                    transfer_size,
535                    has_metadata,
536                }]
537            }
538        }
539    }
540
541    /// Accept or reject a pending resource (for AcceptApp strategy).
542    pub fn accept_resource(
543        &mut self,
544        link_id: &LinkId,
545        resource_hash: &[u8],
546        accept: bool,
547        rng: &mut dyn Rng,
548    ) -> Vec<LinkManagerAction> {
549        let link = match self.links.get_mut(link_id) {
550            Some(l) => l,
551            None => return Vec::new(),
552        };
553
554        let now = time::now();
555        let idx = link
556            .incoming_resources
557            .iter()
558            .position(|r| r.resource_hash == resource_hash);
559        let idx = match idx {
560            Some(i) => i,
561            None => return Vec::new(),
562        };
563
564        if accept && link.incoming_resources[idx].flags.split {
565            if let Some(original_hash) =
566                Self::resource_hash_key(&link.incoming_resources[idx].original_hash)
567            {
568                if !link.incoming_splits.contains_key(&original_hash) {
569                    let storage =
570                        match Self::incoming_storage(&link.resource_receive_mode, &original_hash) {
571                            Ok(storage) => storage,
572                            Err(error) => {
573                                let resource_actions = link.incoming_resources[idx].reject();
574                                let _ = link;
575                                let mut actions =
576                                    self.process_resource_actions(link_id, resource_actions, rng);
577                                actions.push(LinkManagerAction::ResourceFailed {
578                                    link_id: *link_id,
579                                    error: format!("resource storage failed: {error}"),
580                                });
581                                return actions;
582                            }
583                        };
584                    link.incoming_splits.insert(
585                        original_hash,
586                        IncomingSplitTransfer {
587                            total_segments: link.incoming_resources[idx].total_segments,
588                            completed_segments: 0,
589                            current_segment_index: link.incoming_resources[idx].segment_index,
590                            current_received_parts: 0,
591                            current_total_parts: link.incoming_resources[idx].total_parts,
592                            storage,
593                            metadata: None,
594                            is_request: link.incoming_resources[idx].flags.is_request,
595                            is_response: link.incoming_resources[idx].flags.is_response,
596                        },
597                    );
598                }
599            }
600        }
601
602        let resource_actions = if accept {
603            link.incoming_resources[idx].accept(now)
604        } else {
605            link.incoming_resources[idx].reject()
606        };
607
608        let _ = link;
609        self.process_resource_actions(link_id, resource_actions, rng)
610    }
611
612    /// Handle resource request (CONTEXT_RESOURCE_REQ) — feed to sender.
613    pub(super) fn handle_resource_req(
614        &mut self,
615        link_id: &LinkId,
616        plaintext: &[u8],
617        rng: &mut dyn Rng,
618    ) -> Vec<LinkManagerAction> {
619        let link = match self.links.get_mut(link_id) {
620            Some(l) => l,
621            None => return Vec::new(),
622        };
623
624        let now = time::now();
625        let mut all_actions = Vec::new();
626        let mut progress_update = None;
627        let mut progress_stream_hash = None;
628        for sender in &mut link.outgoing_resources {
629            if sender.flags.split && sender.status == rns_core::resource::ResourceStatus::Queued {
630                continue;
631            }
632            let before_sent = sender.sent_parts;
633            let resource_actions = sender.handle_request(plaintext, now);
634            if !resource_actions.is_empty() {
635                if sender.sent_parts != before_sent {
636                    progress_stream_hash = Some(sender.original_hash);
637                    if sender.flags.split {
638                        if let Some(split) = link.outgoing_splits.get_mut(&sender.original_hash) {
639                            split.current_segment_index = sender.segment_index;
640                            split.current_sent_parts = sender.sent_parts;
641                            split.current_total_parts = sender.total_parts();
642                            progress_update =
643                                Some(Self::outgoing_split_progress(split, sender.sdu));
644                        }
645                    } else {
646                        progress_update = Some((sender.sent_parts, sender.total_parts()));
647                    }
648                }
649                all_actions.extend(resource_actions);
650                break;
651            }
652        }
653
654        let _ = link;
655        let mut out = self.process_resource_actions(link_id, all_actions, rng);
656        if let Some((received, total)) = progress_update {
657            if let Some((stream, transferred)) = progress_stream_hash.and_then(|hash| {
658                self.links.get(link_id).and_then(|link| {
659                    link.outgoing_streams.get(&hash).map(|stream| {
660                        let transferred = if total == 0 {
661                            0
662                        } else {
663                            stream.declared_length.saturating_mul(received as u64) / total as u64
664                        };
665                        (stream, transferred.min(stream.declared_length))
666                    })
667                })
668            }) {
669                out.push(LinkManagerAction::ResourceStreamProgress {
670                    link_id: *link_id,
671                    transfer_id: stream.transfer_id,
672                    transferred,
673                    total: stream.declared_length,
674                });
675            } else {
676                out.push(LinkManagerAction::ResourceProgress {
677                    link_id: *link_id,
678                    received,
679                    total,
680                });
681            }
682        }
683        out
684    }
685
686    /// Handle resource HMU (CONTEXT_RESOURCE_HMU) — feed to receiver.
687    pub(super) fn handle_resource_hmu(
688        &mut self,
689        link_id: &LinkId,
690        plaintext: &[u8],
691        rng: &mut dyn Rng,
692    ) -> Vec<LinkManagerAction> {
693        let link = match self.links.get_mut(link_id) {
694            Some(l) => l,
695            None => return Vec::new(),
696        };
697
698        let now = time::now();
699        let mut all_actions = Vec::new();
700        for receiver in &mut link.incoming_resources {
701            let resource_actions = receiver.handle_hashmap_update(plaintext, now);
702            if !resource_actions.is_empty() {
703                all_actions.extend(resource_actions);
704                break;
705            }
706        }
707
708        let _ = link;
709        self.process_resource_actions(link_id, all_actions, rng)
710    }
711
712    /// Handle resource part (CONTEXT_RESOURCE) — feed raw to receiver.
713    pub(super) fn handle_resource_part(
714        &mut self,
715        link_id: &LinkId,
716        raw_data: &[u8],
717        rng: &mut dyn Rng,
718    ) -> Vec<LinkManagerAction> {
719        let link = match self.links.get_mut(link_id) {
720            Some(l) => l,
721            None => return Vec::new(),
722        };
723
724        let now = time::now();
725        let resource_sdu = Self::resource_sdu_for_link(link);
726        let mut all_actions = Vec::new();
727        let mut assemble_idx = None;
728        let mut assembled_is_request = false;
729        let mut assembled_is_response = false;
730        let mut request_request_id = None;
731        let mut response_request_id = None;
732        let mut completed_file = None;
733        let mut storage_failure = None;
734
735        for (idx, receiver) in link.incoming_resources.iter_mut().enumerate() {
736            if receiver.status >= rns_core::resource::ResourceStatus::Assembling {
737                continue;
738            }
739            let resource_actions = receiver.receive_part(raw_data, now);
740            if !resource_actions.is_empty() {
741                if receiver.received_count == receiver.total_parts {
742                    assemble_idx = Some(idx);
743                }
744                if receiver.flags.split {
745                    if let Some(key) = Self::resource_hash_key(&receiver.original_hash) {
746                        if let Some(split) = link.incoming_splits.get_mut(&key) {
747                            split.current_segment_index = receiver.segment_index;
748                            split.current_received_parts = receiver.received_count;
749                            split.current_total_parts = receiver.total_parts;
750                            let (received, total) =
751                                Self::incoming_split_progress(split, resource_sdu);
752                            for action in resource_actions {
753                                match action {
754                                    ResourceAction::ProgressUpdate { .. } => {
755                                        all_actions.push(ResourceAction::ProgressUpdate {
756                                            received,
757                                            total,
758                                        });
759                                    }
760                                    other => all_actions.push(other),
761                                }
762                            }
763                        } else {
764                            all_actions.extend(resource_actions);
765                        }
766                    } else {
767                        all_actions.extend(resource_actions);
768                    }
769                } else {
770                    all_actions.extend(resource_actions);
771                }
772                break;
773            }
774        }
775
776        let mut preceding = Vec::new();
777        if let Some(idx) = assemble_idx {
778            let eligible = Self::receive_offload_eligible(link, &link.incoming_resources[idx]);
779            let _ = link;
780            preceding = self.wait_receive_for(link_id, rng);
781            if eligible {
782                self.ensure_receive_worker();
783            }
784            // A preceding corrupt assembly may have torn down this link. Completion
785            // polling does not remove receivers, so indices remain stable otherwise.
786            let Some(link) = self
787                .links
788                .get_mut(link_id)
789                .filter(|link| link.engine.state() == LinkState::Active)
790            else {
791                return preceding;
792            };
793            if idx >= link.incoming_resources.len() {
794                return preceding;
795            }
796            if eligible && self.receive_worker.is_some() {
797                let receiver = &mut link.incoming_resources[idx];
798                let actions = match receiver
799                    .prepare_assembly(&|data| link.engine.decrypt(data).map_err(|_| ()))
800                {
801                    Ok(assembly) => match self.receive_worker.as_mut().unwrap().submit(
802                        *link_id,
803                        link.resource_generation.clone(),
804                        assembly,
805                    ) {
806                        Ok(()) => Vec::new(),
807                        // Admission/disconnection fallback uses the already-authenticated
808                        // handoff; no additional decryption or unbounded retry.
809                        Err(assembly) => receiver.complete_assembly(assembly.run(&Bzip2Compressor)),
810                    },
811                    Err(actions) => actions,
812                };
813                all_actions.extend(actions);
814                let _ = link;
815                preceding.extend(self.process_resource_actions(link_id, all_actions, rng));
816                return preceding;
817            }
818            let split_key = if link.incoming_resources[idx].flags.split {
819                Self::resource_hash_key(&link.incoming_resources[idx].original_hash)
820            } else {
821                None
822            };
823            let split_segment_index = link.incoming_resources[idx].segment_index;
824            let split_segment_total = link.incoming_resources[idx].total_segments;
825            let split_segment_parts = link.incoming_resources[idx].total_parts;
826            let split_is_request = link.incoming_resources[idx].flags.is_request;
827            let split_is_response = link.incoming_resources[idx].flags.is_response;
828            let resource_original_hash =
829                Self::resource_hash_key(&link.incoming_resources[idx].original_hash)
830                    .unwrap_or([0; 32]);
831            let resource_request_id =
832                Self::response_request_id(&link.incoming_resources[idx].request_id);
833            if split_is_request {
834                request_request_id = resource_request_id;
835            } else if split_is_response {
836                response_request_id = resource_request_id;
837            }
838            let decrypt_fn = |ciphertext: &[u8]| -> Result<Vec<u8>, ()> {
839                link.engine.decrypt(ciphertext).map_err(|_| ())
840            };
841            let mut assemble_actions =
842                link.incoming_resources[idx].assemble(&decrypt_fn, &Bzip2Compressor);
843            assembled_is_request = split_is_request;
844            assembled_is_response = split_is_response;
845
846            if let Some(key) = split_key {
847                let mut converted_actions = Vec::new();
848                let mut segment_data = None;
849                let mut segment_metadata = None;
850                for action in assemble_actions {
851                    match action {
852                        ResourceAction::DataReceived { data, metadata } => {
853                            segment_data = Some(data);
854                            segment_metadata = metadata;
855                        }
856                        ResourceAction::Completed => {}
857                        other => converted_actions.push(other),
858                    }
859                }
860
861                if let Some(data) = segment_data {
862                    if let Some(split) = link.incoming_splits.get_mut(&key) {
863                        let write_result = match &mut split.storage {
864                            IncomingSplitStorage::Memory(buffer) => {
865                                buffer.extend_from_slice(&data);
866                                Ok(())
867                            }
868                            IncomingSplitStorage::File { file, size, .. } => {
869                                file.write_all(&data).map(|()| *size += data.len() as u64)
870                            }
871                        };
872                        if let Err(error) = write_result {
873                            storage_failure = Some(error.to_string());
874                        }
875                        if segment_metadata.is_some() {
876                            split.metadata = segment_metadata;
877                        }
878                        split.completed_segments = split_segment_index;
879                        split.current_segment_index = split_segment_index;
880                        split.current_received_parts = split_segment_parts;
881                        split.current_total_parts = split_segment_parts;
882                    }
883
884                    if storage_failure.is_none() && split_segment_index == split_segment_total {
885                        if let Some(split) = link.incoming_splits.remove(&key) {
886                            assembled_is_request = split.is_request;
887                            assembled_is_response = split.is_response;
888                            match split.storage {
889                                IncomingSplitStorage::Memory(data) => {
890                                    converted_actions.push(ResourceAction::DataReceived {
891                                        data,
892                                        metadata: split.metadata,
893                                    });
894                                }
895                                IncomingSplitStorage::File { file, path, size } => {
896                                    match file.sync_all() {
897                                        Ok(()) => {
898                                            completed_file = Some(ReceivedResourceFile::new(
899                                                path,
900                                                key,
901                                                size,
902                                                split.metadata,
903                                            ));
904                                        }
905                                        Err(error) => storage_failure = Some(error.to_string()),
906                                    }
907                                }
908                            }
909                            converted_actions.push(ResourceAction::Completed);
910                        }
911                    }
912                    if storage_failure.is_some() {
913                        link.incoming_splits.remove(&key);
914                        converted_actions.clear();
915                        converted_actions.push(ResourceAction::SendCancelReceiver(
916                            link.incoming_resources[idx].resource_hash.clone(),
917                        ));
918                    }
919                }
920
921                assemble_actions = converted_actions;
922            } else if !split_is_request
923                && !split_is_response
924                && matches!(
925                    link.resource_receive_mode,
926                    ResourceReceiveMode::TemporaryFile { .. }
927                )
928            {
929                let directory = match &link.resource_receive_mode {
930                    ResourceReceiveMode::TemporaryFile { directory, .. } => directory.clone(),
931                    ResourceReceiveMode::Memory { .. } => unreachable!(),
932                };
933                let mut converted_actions = Vec::new();
934                for action in assemble_actions {
935                    match action {
936                        ResourceAction::DataReceived { data, metadata } => {
937                            match crate::resource::create_receive_file(
938                                &directory,
939                                &resource_original_hash,
940                            )
941                            .and_then(|(mut file, path)| {
942                                file.write_all(&data)?;
943                                file.sync_all()?;
944                                Ok(ReceivedResourceFile::new(
945                                    path,
946                                    resource_original_hash,
947                                    data.len() as u64,
948                                    metadata,
949                                ))
950                            }) {
951                                Ok(resource) => completed_file = Some(resource),
952                                Err(error) => storage_failure = Some(error.to_string()),
953                            }
954                        }
955                        other => converted_actions.push(other),
956                    }
957                }
958                if storage_failure.is_some() {
959                    converted_actions.retain(|action| {
960                        !matches!(
961                            action,
962                            ResourceAction::SendProof(_) | ResourceAction::Completed
963                        )
964                    });
965                    converted_actions.push(ResourceAction::SendCancelReceiver(
966                        link.incoming_resources[idx].resource_hash.clone(),
967                    ));
968                }
969                assemble_actions = converted_actions;
970            }
971            all_actions.extend(assemble_actions);
972        }
973
974        let mut out = self.process_resource_actions(link_id, all_actions, rng);
975        if let Some(resource) = completed_file {
976            out.push(LinkManagerAction::ResourceFileReceived {
977                link_id: *link_id,
978                resource,
979            });
980        }
981        if let Some(error) = storage_failure {
982            out.push(LinkManagerAction::ResourceFailed {
983                link_id: *link_id,
984                error: format!("resource storage failed: {error}"),
985            });
986        }
987
988        if assembled_is_request {
989            let mut converted = Vec::new();
990            for action in out {
991                match action {
992                    LinkManagerAction::ResourceReceived { data, .. } => {
993                        if let Some(request_id) = request_request_id {
994                            converted.extend(self.handle_request(link_id, &data, request_id, rng));
995                        }
996                    }
997                    LinkManagerAction::ResourceAcceptQuery { .. } => {
998                        // Request resources bypass application resource acceptance.
999                    }
1000                    other => converted.push(other),
1001                }
1002            }
1003            out = converted;
1004        } else if assembled_is_response {
1005            let mut converted = Vec::new();
1006            for action in out {
1007                match action {
1008                    LinkManagerAction::ResourceReceived { data, metadata, .. } => {
1009                        converted.extend(self.handle_response(
1010                            link_id,
1011                            &data,
1012                            metadata,
1013                            response_request_id,
1014                        ));
1015                    }
1016                    LinkManagerAction::ResourceAcceptQuery { .. } => {
1017                        // Response resources bypass application acceptance
1018                    }
1019                    other => converted.push(other),
1020                }
1021            }
1022            out = converted;
1023        }
1024
1025        // Earlier application completions already have their final action types.
1026        // Only this Resource's actions pass through request/response conversion.
1027        preceding.extend(out);
1028        preceding
1029    }
1030
1031    /// Handle resource proof (CONTEXT_RESOURCE_PRF) — feed to sender.
1032    pub(super) fn handle_resource_prf(
1033        &mut self,
1034        link_id: &LinkId,
1035        plaintext: &[u8],
1036        rng: &mut dyn Rng,
1037    ) -> Vec<LinkManagerAction> {
1038        let link = match self.links.get_mut(link_id) {
1039            Some(l) => l,
1040            None => return Vec::new(),
1041        };
1042
1043        let now = time::now();
1044        let mut result_actions = Vec::new();
1045        let mut completed_sender = None;
1046        let mut completed_request_id = None;
1047        let mut failed_split = None;
1048        let mut failed_request_id = None;
1049        let proof_hash = plaintext.get(..32);
1050        for sender in &mut link.outgoing_resources {
1051            if proof_hash.is_some_and(|hash| hash != sender.resource_hash.as_slice()) {
1052                continue;
1053            }
1054            let resource_actions = sender.handle_proof(plaintext, now);
1055            if !resource_actions.is_empty() {
1056                if resource_actions
1057                    .iter()
1058                    .any(|action| matches!(action, ResourceAction::Completed))
1059                {
1060                    completed_sender = Some((
1061                        sender.original_hash,
1062                        sender.segment_index,
1063                        sender.total_segments,
1064                        sender.total_parts(),
1065                    ));
1066                    if sender.flags.is_request && sender.segment_index == sender.total_segments {
1067                        completed_request_id = Self::response_request_id(&sender.request_id);
1068                    }
1069                }
1070                if resource_actions
1071                    .iter()
1072                    .any(|action| matches!(action, ResourceAction::Failed(_)))
1073                {
1074                    failed_split = Some(sender.original_hash);
1075                }
1076                if resource_actions
1077                    .iter()
1078                    .any(|action| matches!(action, ResourceAction::Failed(_)))
1079                    && sender.flags.is_request
1080                {
1081                    failed_request_id = Self::response_request_id(&sender.request_id);
1082                }
1083                result_actions.extend(resource_actions);
1084                break;
1085            }
1086        }
1087
1088        // Convert to LinkManagerActions
1089        let mut actions = Vec::new();
1090        let mut advertise_next = None;
1091        let mut stream_failure = None;
1092        for ra in result_actions {
1093            match ra {
1094                ResourceAction::Completed => {
1095                    if let Some((original_hash, segment_index, total_segments, total_parts)) =
1096                        completed_sender
1097                    {
1098                        if total_segments > 1 && segment_index < total_segments {
1099                            if let Some(split) = link.outgoing_splits.get_mut(&original_hash) {
1100                                split.completed_segments = segment_index;
1101                                split.current_segment_index = segment_index;
1102                                split.current_sent_parts = total_parts;
1103                                split.current_total_parts = total_parts;
1104                            }
1105                            if let Some(next) = link.outgoing_resources.iter_mut().find(|s| {
1106                                s.flags.split
1107                                    && s.original_hash == original_hash
1108                                    && s.segment_index == segment_index + 1
1109                            }) {
1110                                if let Some(split) = link.outgoing_splits.get_mut(&original_hash) {
1111                                    split.current_segment_index = next.segment_index;
1112                                    split.current_sent_parts = 0;
1113                                    split.current_total_parts = next.total_parts();
1114                                }
1115                                advertise_next = Some(next.advertise(now));
1116                            } else if let Some(mut stream) =
1117                                link.outgoing_streams.remove(&original_hash)
1118                            {
1119                                match Self::build_stream_segment(
1120                                    link,
1121                                    &mut stream,
1122                                    None,
1123                                    segment_index + 1,
1124                                    total_segments,
1125                                    Some(original_hash),
1126                                    rng,
1127                                    now,
1128                                ) {
1129                                    Ok(mut next) => {
1130                                        if let Some(split) =
1131                                            link.outgoing_splits.get_mut(&original_hash)
1132                                        {
1133                                            split.current_segment_index = next.segment_index;
1134                                            split.current_sent_parts = 0;
1135                                            split.current_total_parts = next.total_parts();
1136                                        }
1137                                        advertise_next = Some(next.advertise(now));
1138                                        link.outgoing_resources.push(next);
1139                                        link.outgoing_streams.insert(original_hash, stream);
1140                                    }
1141                                    Err(error) => {
1142                                        stream_failure = Some((stream.transfer_id, error));
1143                                        link.outgoing_splits.remove(&original_hash);
1144                                    }
1145                                }
1146                            }
1147                        } else {
1148                            link.outgoing_splits.remove(&original_hash);
1149                            if let Some(stream) = link.outgoing_streams.remove(&original_hash) {
1150                                actions.push(LinkManagerAction::ResourceStreamCompleted {
1151                                    link_id: *link_id,
1152                                    transfer_id: stream.transfer_id,
1153                                });
1154                            } else {
1155                                actions.push(LinkManagerAction::ResourceCompleted {
1156                                    link_id: *link_id,
1157                                });
1158                            }
1159                        }
1160                    } else {
1161                        actions.push(LinkManagerAction::ResourceCompleted { link_id: *link_id });
1162                    }
1163                }
1164                ResourceAction::Failed(e) => {
1165                    if let Some(original_hash) = failed_split {
1166                        link.outgoing_splits.remove(&original_hash);
1167                        if let Some(stream) = link.outgoing_streams.remove(&original_hash) {
1168                            actions.push(LinkManagerAction::ResourceStreamFailed {
1169                                link_id: *link_id,
1170                                transfer_id: stream.transfer_id,
1171                                error: ResourceTransferError::Protocol(e.to_string()),
1172                            });
1173                            continue;
1174                        }
1175                    }
1176                    actions.push(LinkManagerAction::ResourceFailed {
1177                        link_id: *link_id,
1178                        error: format!("{}", e),
1179                    });
1180                }
1181                _ => {}
1182            }
1183        }
1184
1185        if let Some((transfer_id, error)) = stream_failure {
1186            actions.push(LinkManagerAction::ResourceStreamFailed {
1187                link_id: *link_id,
1188                transfer_id,
1189                error,
1190            });
1191        }
1192
1193        if let Some(request_id) = completed_request_id {
1194            let deadline = Self::request_response_deadline(link, now);
1195            if let Some(entry) = link.pending_requests.get_mut(&request_id) {
1196                entry.deadline = Some(deadline);
1197            }
1198        }
1199        if let Some(request_id) = failed_request_id {
1200            link.pending_requests.remove(&request_id);
1201        }
1202
1203        // Clean up completed/failed senders
1204        link.outgoing_resources
1205            .retain(|s| s.status < rns_core::resource::ResourceStatus::Complete);
1206
1207        let _ = link;
1208        if let Some(next_actions) = advertise_next {
1209            actions.extend(self.process_resource_actions(link_id, next_actions, rng));
1210        }
1211
1212        actions
1213    }
1214
1215    /// Cancel only the incoming transfer named by an authenticated ICL packet.
1216    pub(super) fn handle_resource_icl(
1217        &mut self,
1218        link_id: &LinkId,
1219        resource_hash: &[u8],
1220    ) -> Vec<LinkManagerAction> {
1221        let Some(hash) = Self::resource_hash_key(resource_hash) else {
1222            return Vec::new();
1223        };
1224        let Some(link) = self.links.get_mut(link_id) else {
1225            return Vec::new();
1226        };
1227        let Some(idx) = link.incoming_resources.iter().position(|receiver| {
1228            receiver.resource_hash == hash
1229                && receiver.status < rns_core::resource::ResourceStatus::Complete
1230        }) else {
1231            return Vec::new();
1232        };
1233        let mut receiver = link.incoming_resources.remove(idx);
1234        if receiver.flags.split {
1235            if let Some(original) = Self::resource_hash_key(&receiver.original_hash) {
1236                link.incoming_resources
1237                    .retain(|r| !r.flags.split || r.original_hash != original);
1238                if let Some(IncomingSplitTransfer {
1239                    storage: IncomingSplitStorage::File { file, path, .. },
1240                    ..
1241                }) = link.incoming_splits.remove(&original)
1242                {
1243                    drop(file);
1244                    let _ = std::fs::remove_file(path);
1245                }
1246            }
1247        }
1248        if receiver.flags.is_response {
1249            if let Some(request) = Self::response_request_id(&receiver.request_id) {
1250                link.pending_requests.remove(&request);
1251            }
1252        }
1253        // Removing its assembly identity invalidates only this transfer's worker
1254        // result. Other transfers retain their link generation and pending work.
1255        receiver
1256            .handle_cancel()
1257            .into_iter()
1258            .filter_map(|action| match action {
1259                ResourceAction::Failed(error) => Some(LinkManagerAction::ResourceFailed {
1260                    link_id: *link_id,
1261                    error: error.to_string(),
1262                }),
1263                _ => None,
1264            })
1265            .collect()
1266    }
1267
1268    /// Reject only the outgoing transfer named by an authenticated RCL packet.
1269    pub(super) fn handle_resource_rcl(
1270        &mut self,
1271        link_id: &LinkId,
1272        resource_hash: &[u8],
1273    ) -> Vec<LinkManagerAction> {
1274        let Some(hash) = Self::resource_hash_key(resource_hash) else {
1275            return Vec::new();
1276        };
1277        let Some(link) = self.links.get_mut(link_id) else {
1278            return Vec::new();
1279        };
1280        let Some(idx) = link.outgoing_resources.iter().position(|sender| {
1281            sender.resource_hash == hash
1282                && sender.status < rns_core::resource::ResourceStatus::Complete
1283        }) else {
1284            return Vec::new();
1285        };
1286        let mut sender = link.outgoing_resources.remove(idx);
1287        if sender.flags.split {
1288            // Buffered senders may already hold later segments of this transfer.
1289            link.outgoing_resources
1290                .retain(|s| !s.flags.split || s.original_hash != sender.original_hash);
1291            link.outgoing_splits.remove(&sender.original_hash);
1292        }
1293        if sender.flags.is_request {
1294            if let Some(request) = Self::response_request_id(&sender.request_id) {
1295                link.pending_requests.remove(&request);
1296            }
1297        }
1298        let mut actions: Vec<_> = sender
1299            .handle_reject()
1300            .into_iter()
1301            .filter_map(|action| match action {
1302                ResourceAction::Failed(error) => Some(LinkManagerAction::ResourceFailed {
1303                    link_id: *link_id,
1304                    error: error.to_string(),
1305                }),
1306                _ => None,
1307            })
1308            .collect();
1309        if let Some(stream) = link.outgoing_streams.remove(&sender.original_hash) {
1310            actions.push(LinkManagerAction::ResourceStreamFailed {
1311                link_id: *link_id,
1312                transfer_id: stream.transfer_id,
1313                error: ResourceTransferError::Cancelled,
1314            });
1315        }
1316        actions
1317    }
1318
1319    fn resource_link_is_active(&self, link_id: &LinkId) -> bool {
1320        self.links
1321            .get(link_id)
1322            .is_some_and(|link| link.engine.state() == LinkState::Active)
1323    }
1324
1325    fn abort_resources_on_inactive_link(&mut self, link_id: &LinkId) -> Vec<LinkManagerAction> {
1326        let Some(link) = self.links.get_mut(link_id) else {
1327            return Vec::new();
1328        };
1329        for sender in &mut link.outgoing_resources {
1330            let _ = sender.cancel();
1331        }
1332        for receiver in &mut link.incoming_resources {
1333            let _ = receiver.cancel();
1334        }
1335        link.outgoing_resources.clear();
1336        link.incoming_resources.clear();
1337        link.outgoing_splits.clear();
1338        let actions = link
1339            .outgoing_streams
1340            .drain()
1341            .map(|(_, stream)| LinkManagerAction::ResourceStreamFailed {
1342                link_id: *link_id,
1343                transfer_id: stream.transfer_id,
1344                error: ResourceTransferError::Cancelled,
1345            })
1346            .collect();
1347        link.incoming_splits.clear();
1348        link.pending_requests.clear();
1349        actions
1350    }
1351
1352    /// Convert ResourceActions to LinkManagerActions.
1353    pub(super) fn process_resource_actions(
1354        &mut self,
1355        link_id: &LinkId,
1356        actions: Vec<ResourceAction>,
1357        rng: &mut dyn Rng,
1358    ) -> Vec<LinkManagerAction> {
1359        let mut result = Vec::new();
1360        for action in actions {
1361            let requires_active_link = matches!(
1362                &action,
1363                ResourceAction::SendAdvertisement(_)
1364                    | ResourceAction::SendPart(_)
1365                    | ResourceAction::SendRequest(_)
1366                    | ResourceAction::SendHmu(_)
1367                    | ResourceAction::SendProof(_)
1368            );
1369            if requires_active_link && !self.resource_link_is_active(link_id) {
1370                result.extend(self.abort_resources_on_inactive_link(link_id));
1371                continue;
1372            }
1373
1374            match action {
1375                ResourceAction::SendAdvertisement(data) => {
1376                    // Link-encrypt and send as CONTEXT_RESOURCE_ADV
1377                    let encrypted = self
1378                        .links
1379                        .get(link_id)
1380                        .and_then(|link| link.engine.encrypt(&data, rng).ok());
1381                    if let Some(encrypted) = encrypted {
1382                        result.extend(self.build_link_packet(
1383                            link_id,
1384                            constants::CONTEXT_RESOURCE_ADV,
1385                            &encrypted,
1386                        ));
1387                    }
1388                }
1389                ResourceAction::SendPart(data) => {
1390                    // Parts are NOT link-encrypted — send raw as CONTEXT_RESOURCE
1391                    result.extend(self.build_link_packet(
1392                        link_id,
1393                        constants::CONTEXT_RESOURCE,
1394                        &data,
1395                    ));
1396                }
1397                ResourceAction::SendRequest(data) => {
1398                    let encrypted = self
1399                        .links
1400                        .get(link_id)
1401                        .and_then(|link| link.engine.encrypt(&data, rng).ok());
1402                    if let Some(encrypted) = encrypted {
1403                        result.extend(self.build_link_packet(
1404                            link_id,
1405                            constants::CONTEXT_RESOURCE_REQ,
1406                            &encrypted,
1407                        ));
1408                    }
1409                }
1410                ResourceAction::SendHmu(data) => {
1411                    let encrypted = self
1412                        .links
1413                        .get(link_id)
1414                        .and_then(|link| link.engine.encrypt(&data, rng).ok());
1415                    if let Some(encrypted) = encrypted {
1416                        result.extend(self.build_link_packet(
1417                            link_id,
1418                            constants::CONTEXT_RESOURCE_HMU,
1419                            &encrypted,
1420                        ));
1421                    }
1422                }
1423                ResourceAction::SendProof(data) => {
1424                    let flags = PacketFlags {
1425                        header_type: constants::HEADER_1,
1426                        context_flag: constants::FLAG_UNSET,
1427                        transport_type: constants::TRANSPORT_BROADCAST,
1428                        destination_type: constants::DESTINATION_LINK,
1429                        packet_type: constants::PACKET_TYPE_PROOF,
1430                    };
1431                    if let Ok((raw, _packet_hash)) = RawPacket::pack_raw_with_hash(
1432                        flags,
1433                        0,
1434                        link_id,
1435                        None,
1436                        constants::CONTEXT_RESOURCE_PRF,
1437                        &data,
1438                    ) {
1439                        result.push(LinkManagerAction::SendPacket {
1440                            raw,
1441                            dest_type: constants::DESTINATION_LINK,
1442                            attached_interface: None,
1443                        });
1444                    }
1445                }
1446                ResourceAction::SendCancelInitiator(data) => {
1447                    let encrypted = self
1448                        .links
1449                        .get(link_id)
1450                        .filter(|link| link.engine.state() == LinkState::Active)
1451                        .and_then(|link| link.engine.encrypt(&data, rng).ok());
1452                    if let Some(encrypted) = encrypted {
1453                        result.extend(self.build_link_packet(
1454                            link_id,
1455                            constants::CONTEXT_RESOURCE_ICL,
1456                            &encrypted,
1457                        ));
1458                    }
1459                }
1460                ResourceAction::SendCancelReceiver(data) => {
1461                    let encrypted = self
1462                        .links
1463                        .get(link_id)
1464                        .filter(|link| link.engine.state() == LinkState::Active)
1465                        .and_then(|link| link.engine.encrypt(&data, rng).ok());
1466                    if let Some(encrypted) = encrypted {
1467                        result.extend(self.build_link_packet(
1468                            link_id,
1469                            constants::CONTEXT_RESOURCE_RCL,
1470                            &encrypted,
1471                        ));
1472                    }
1473                }
1474                ResourceAction::DataReceived { data, metadata } => {
1475                    result.push(LinkManagerAction::ResourceReceived {
1476                        link_id: *link_id,
1477                        data,
1478                        metadata,
1479                    });
1480                }
1481                ResourceAction::Completed => {
1482                    result.push(LinkManagerAction::ResourceCompleted { link_id: *link_id });
1483                }
1484                ResourceAction::Failed(e) => {
1485                    result.push(LinkManagerAction::ResourceFailed {
1486                        link_id: *link_id,
1487                        error: format!("{}", e),
1488                    });
1489                }
1490                ResourceAction::TeardownLink => {
1491                    let teardown_actions = match self.links.get_mut(link_id) {
1492                        Some(link) => link.engine.handle_teardown(),
1493                        None => Vec::new(),
1494                    };
1495                    result.extend(self.process_link_actions(link_id, &teardown_actions));
1496                }
1497                ResourceAction::ProgressUpdate { received, total } => {
1498                    result.push(LinkManagerAction::ResourceProgress {
1499                        link_id: *link_id,
1500                        received,
1501                        total,
1502                    });
1503                }
1504            }
1505        }
1506        result
1507    }
1508
1509    /// Build a link DATA packet with a given context and data.
1510    pub(super) fn build_link_packet(
1511        &self,
1512        link_id: &LinkId,
1513        context: u8,
1514        data: &[u8],
1515    ) -> Vec<LinkManagerAction> {
1516        let flags = PacketFlags {
1517            header_type: constants::HEADER_1,
1518            context_flag: constants::FLAG_UNSET,
1519            transport_type: constants::TRANSPORT_BROADCAST,
1520            destination_type: constants::DESTINATION_LINK,
1521            packet_type: constants::PACKET_TYPE_DATA,
1522        };
1523        let mut actions = Vec::new();
1524        let max_mtu = self
1525            .links
1526            .get(link_id)
1527            .map(|l| l.engine.mtu() as usize)
1528            .unwrap_or(constants::MTU);
1529        if let Ok(raw) =
1530            RawPacket::pack_raw_with_max_mtu(flags, 0, link_id, None, context, data, max_mtu)
1531        {
1532            actions.push(LinkManagerAction::SendPacket {
1533                raw,
1534                dest_type: constants::DESTINATION_LINK,
1535                attached_interface: None,
1536            });
1537        }
1538        actions
1539    }
1540
1541    /// Start sending a resource on a link.
1542    pub fn send_resource(
1543        &mut self,
1544        link_id: &LinkId,
1545        data: &[u8],
1546        metadata: Option<&[u8]>,
1547        rng: &mut dyn Rng,
1548    ) -> Vec<LinkManagerAction> {
1549        self.send_resource_with_auto_compress(link_id, data, metadata, true, rng)
1550    }
1551
1552    /// Start sending a resource on a link, controlling automatic compression.
1553    pub fn send_resource_with_auto_compress(
1554        &mut self,
1555        link_id: &LinkId,
1556        data: &[u8],
1557        metadata: Option<&[u8]>,
1558        auto_compress: bool,
1559        rng: &mut dyn Rng,
1560    ) -> Vec<LinkManagerAction> {
1561        self.send_resource_with_compressor(
1562            link_id,
1563            data,
1564            metadata,
1565            auto_compress,
1566            &Bzip2Compressor,
1567            rng,
1568        )
1569    }
1570
1571    pub(crate) fn send_resource_with_compressor(
1572        &mut self,
1573        link_id: &LinkId,
1574        data: &[u8],
1575        metadata: Option<&[u8]>,
1576        auto_compress: bool,
1577        compressor: &dyn rns_core::buffer::types::Compressor,
1578        rng: &mut dyn Rng,
1579    ) -> Vec<LinkManagerAction> {
1580        let link = match self.links.get_mut(link_id) {
1581            Some(l) => l,
1582            None => return Vec::new(),
1583        };
1584
1585        if link.engine.state() != LinkState::Active {
1586            return Vec::new();
1587        }
1588
1589        let now = time::now();
1590
1591        let senders = match Self::build_resource_senders(
1592            link,
1593            ResourceSendParams {
1594                data,
1595                metadata,
1596                auto_compress,
1597                compressor,
1598                is_response: false,
1599                request_id: None,
1600                rng,
1601                now,
1602            },
1603        ) {
1604            Ok(s) => s,
1605            Err(e) => {
1606                log::debug!("Failed to create ResourceSender: {}", e);
1607                return Vec::new();
1608            }
1609        };
1610
1611        let adv_actions = Self::start_resource_senders(link, senders, now);
1612
1613        let _ = link;
1614        self.process_resource_actions(link_id, adv_actions, rng)
1615    }
1616
1617    /// Start a bounded-memory Resource transfer from a sequential reader.
1618    ///
1619    /// The declared-length reader is consumed directly. Unlike the upstream
1620    /// zero-stat compatibility path, no temporary buffered proxy must be
1621    /// flushed and rewound before its size or first segment can be observed.
1622    #[allow(clippy::too_many_arguments)]
1623    pub fn send_resource_stream(
1624        &mut self,
1625        link_id: &LinkId,
1626        transfer_id: ResourceTransferId,
1627        reader: Box<dyn Read + Send>,
1628        declared_length: u64,
1629        metadata: Option<Vec<u8>>,
1630        auto_compress: bool,
1631        rng: &mut dyn Rng,
1632    ) -> Vec<LinkManagerAction> {
1633        let Some(link) = self.links.get_mut(link_id) else {
1634            return vec![LinkManagerAction::ResourceStreamFailed {
1635                link_id: *link_id,
1636                transfer_id,
1637                error: ResourceTransferError::Protocol("link does not exist".into()),
1638            }];
1639        };
1640        if link.engine.state() != LinkState::Active {
1641            return vec![LinkManagerAction::ResourceStreamFailed {
1642                link_id: *link_id,
1643                transfer_id,
1644                error: ResourceTransferError::Protocol("link is not active".into()),
1645            }];
1646        }
1647
1648        let metadata_overhead = metadata.as_ref().map(|value| 3 + value.len()).unwrap_or(0);
1649        let Some(logical_size) = declared_length.checked_add(metadata_overhead as u64) else {
1650            return vec![LinkManagerAction::ResourceStreamFailed {
1651                link_id: *link_id,
1652                transfer_id,
1653                error: ResourceTransferError::Protocol("resource length overflow".into()),
1654            }];
1655        };
1656        if usize::try_from(logical_size).is_err()
1657            || metadata_overhead > constants::RESOURCE_MAX_EFFICIENT_SIZE
1658        {
1659            return vec![LinkManagerAction::ResourceStreamFailed {
1660                link_id: *link_id,
1661                transfer_id,
1662                error: ResourceTransferError::Protocol(
1663                    "resource length cannot be represented by this platform".into(),
1664                ),
1665            }];
1666        }
1667
1668        let first_capacity = constants::RESOURCE_MAX_EFFICIENT_SIZE - metadata_overhead;
1669        let first_length = declared_length.min(first_capacity as u64);
1670        let remaining_after_first = declared_length - first_length;
1671        let total_segments =
1672            1 + remaining_after_first.div_ceil(constants::RESOURCE_MAX_EFFICIENT_SIZE as u64);
1673        let now = time::now();
1674        let mut stream = OutgoingStreamTransfer {
1675            transfer_id,
1676            reader,
1677            remaining: declared_length,
1678            declared_length,
1679            metadata_overhead: metadata_overhead as u64,
1680            auto_compress,
1681        };
1682        let mut first = match Self::build_stream_segment(
1683            link,
1684            &mut stream,
1685            metadata.as_deref(),
1686            1,
1687            total_segments,
1688            None,
1689            rng,
1690            now,
1691        ) {
1692            Ok(sender) => sender,
1693            Err(error) => {
1694                return vec![LinkManagerAction::ResourceStreamFailed {
1695                    link_id: *link_id,
1696                    transfer_id,
1697                    error,
1698                }]
1699            }
1700        };
1701        let original_hash = first.original_hash;
1702        let current_total_parts = first.total_parts();
1703        let adv_actions = first.advertise(now);
1704        if total_segments > 1 {
1705            link.outgoing_splits.insert(
1706                original_hash,
1707                OutgoingSplitTransfer {
1708                    total_segments,
1709                    completed_segments: 0,
1710                    current_segment_index: 1,
1711                    current_sent_parts: 0,
1712                    current_total_parts,
1713                },
1714            );
1715        }
1716        link.outgoing_streams.insert(original_hash, stream);
1717        link.outgoing_resources.push(first);
1718        let _ = link;
1719        self.process_resource_actions(link_id, adv_actions, rng)
1720    }
1721
1722    /// Set the resource acceptance strategy for a link.
1723    pub fn set_resource_strategy(&mut self, link_id: &LinkId, strategy: ResourceStrategy) {
1724        if let Some(link) = self.links.get_mut(link_id) {
1725            link.resource_strategy = strategy;
1726        }
1727    }
1728
1729    pub fn set_resource_receive_mode(&mut self, link_id: &LinkId, mode: ResourceReceiveMode) {
1730        if let Some(link) = self.links.get_mut(link_id) {
1731            link.resource_receive_mode = mode;
1732        }
1733    }
1734}