Skip to main content

rns_net/common/link_manager/
resource_handling.rs

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