Skip to main content

rns_net/common/
link_manager.rs

1//! Link manager: wires rns-core LinkEngine + Channel + Resource into the driver.
2//!
3//! Manages multiple concurrent links, link destination registration,
4//! request/response handling, resource transfers, and full lifecycle
5//! (handshake → active → teardown).
6//!
7//! Python reference: Link.py, RequestReceipt.py, Resource.py
8
9use std::collections::HashMap;
10use std::fs::File;
11use std::io::{Read, Write};
12use std::path::PathBuf;
13
14use super::compressor::Bzip2Compressor;
15use rns_core::channel::{Channel, Sequence};
16use rns_core::constants;
17use rns_core::link::types::{LinkId, LinkState, TeardownReason};
18use rns_core::link::{LinkAction, LinkEngine, LinkMode};
19use rns_core::packet::{PacketFlags, RawPacket};
20use rns_core::resource::{ResourceAction, ResourceReceiver, ResourceSender};
21use rns_crypto::ed25519::Ed25519PrivateKey;
22use rns_crypto::{OsRng, Rng};
23
24use super::time;
25use crate::resource::{
26    ReceivedResourceFile, ResourceReceiveMode, ResourceTransferError, ResourceTransferId,
27};
28
29/// Resource acceptance strategy.
30#[derive(Debug, Clone, Copy, PartialEq, Eq)]
31pub enum ResourceStrategy {
32    /// Reject all incoming resources.
33    AcceptNone,
34    /// Accept all incoming resources automatically.
35    AcceptAll,
36    /// Query the application callback for each resource.
37    AcceptApp,
38}
39
40impl Default for ResourceStrategy {
41    fn default() -> Self {
42        ResourceStrategy::AcceptNone
43    }
44}
45
46/// A managed link wrapping LinkEngine + optional Channel + resources.
47struct ManagedLink {
48    engine: LinkEngine,
49    channel: Option<Channel>,
50    pending_channel_packets: HashMap<[u8; 32], Sequence>,
51    channel_send_ok: u64,
52    channel_send_not_ready: u64,
53    channel_send_too_big: u64,
54    channel_send_other_error: u64,
55    channel_messages_received: u64,
56    channel_proofs_sent: u64,
57    channel_proofs_received: u64,
58    /// Destination hash this link belongs to.
59    dest_hash: [u8; 16],
60    /// Remote identity (hash, public_key) once identified.
61    remote_identity: Option<([u8; 16], [u8; 64])>,
62    /// Destination's Ed25519 signing public key (for initiator to verify LRPROOF).
63    dest_sig_pub_bytes: Option<[u8; 32]>,
64    /// Active incoming resource transfers.
65    incoming_resources: Vec<ResourceReceiver>,
66    /// Active outgoing resource transfers.
67    outgoing_resources: Vec<ResourceSender>,
68    /// Request IDs awaiting a packet or resource response.
69    pending_requests: HashMap<[u8; 16], PendingRequest>,
70    /// Logical incoming split transfers, keyed by original resource hash.
71    incoming_splits: HashMap<[u8; 32], IncomingSplitTransfer>,
72    /// Logical outgoing split transfers, keyed by original resource hash.
73    outgoing_splits: HashMap<[u8; 32], OutgoingSplitTransfer>,
74    /// Reader state for bounded-memory outgoing transfers.
75    outgoing_streams: HashMap<[u8; 32], OutgoingStreamTransfer>,
76    /// Resource acceptance strategy.
77    resource_strategy: ResourceStrategy,
78    /// Delivery policy for independent incoming Resources.
79    resource_receive_mode: ResourceReceiveMode,
80    /// Maximum accepted request size inherited from the local destination.
81    max_request_size: Option<usize>,
82    /// Interface this link's packets should be sent on when known.
83    route_interface: Option<rns_core::transport::types::InterfaceId>,
84    /// Next-hop transport ID seen on inbound HEADER_2 link traffic.
85    ///
86    /// When present, outbound link packets can be rewritten to HEADER_2 using
87    /// this transport ID to preserve multi-hop routing.
88    route_transport_id: Option<[u8; 16]>,
89}
90
91struct IncomingSplitTransfer {
92    total_segments: u64,
93    completed_segments: u64,
94    current_segment_index: u64,
95    current_received_parts: usize,
96    current_total_parts: usize,
97    storage: IncomingSplitStorage,
98    metadata: Option<Vec<u8>>,
99    is_request: bool,
100    is_response: bool,
101}
102
103enum IncomingSplitStorage {
104    Memory(Vec<u8>),
105    File {
106        file: File,
107        path: PathBuf,
108        size: u64,
109    },
110}
111
112struct OutgoingSplitTransfer {
113    total_segments: u64,
114    completed_segments: u64,
115    current_segment_index: u64,
116    current_sent_parts: usize,
117    current_total_parts: usize,
118}
119
120struct OutgoingStreamTransfer {
121    transfer_id: ResourceTransferId,
122    reader: Box<dyn Read + Send>,
123    remaining: u64,
124    declared_length: u64,
125    metadata_overhead: u64,
126    auto_compress: bool,
127}
128
129struct PendingRequest {
130    deadline: Option<f64>,
131    max_response_size: Option<usize>,
132}
133
134/// A registered link destination that can accept incoming LINKREQUEST.
135struct LinkDestination {
136    sig_prv: Ed25519PrivateKey,
137    sig_pub_bytes: [u8; 32],
138    resource_strategy: ResourceStrategy,
139    max_request_size: Option<usize>,
140}
141
142/// Response produced by an application request handler.
143#[derive(Debug, Clone, PartialEq, Eq)]
144pub enum RequestResponse {
145    /// Send the response as the normal request response value.
146    Bytes(Vec<u8>),
147    /// Send the response as a resource response with optional metadata.
148    Resource {
149        data: Vec<u8>,
150        metadata: Option<Vec<u8>>,
151        auto_compress: bool,
152    },
153}
154
155/// Reason an outbound request failed before receiving an accepted response.
156#[derive(Debug, Clone, Copy, PartialEq, Eq)]
157pub enum RequestFailure {
158    /// The peer advertised or sent a response larger than the configured limit.
159    ResponseTooLarge { size: u64, maximum: usize },
160}
161
162impl From<Vec<u8>> for RequestResponse {
163    fn from(data: Vec<u8>) -> Self {
164        RequestResponse::Bytes(data)
165    }
166}
167
168/// A registered request handler for a path.
169struct RequestHandlerEntry {
170    /// The path this handler serves (e.g. "/status").
171    path: String,
172    /// The truncated hash of the path (first 16 bytes of SHA-256).
173    path_hash: [u8; 16],
174    /// Access control: None means allow all, Some(list) means allow only listed identities.
175    allowed_list: Option<Vec<[u8; 16]>>,
176    /// Handler function: (link_id, path, request_id, data, remote_identity) -> Option<response>.
177    handler: Box<
178        dyn Fn(LinkId, &str, &[u8], Option<&([u8; 16], [u8; 64])>) -> Option<RequestResponse>
179            + Send,
180    >,
181}
182
183struct DeferredRequestHandlerEntry {
184    path: String,
185    path_hash: [u8; 16],
186    allowed_list: Option<Vec<[u8; 16]>>,
187    handler: Box<dyn Fn(LinkId, &str, [u8; 16], &[u8], Option<&([u8; 16], [u8; 64])>) + Send>,
188}
189
190/// Actions produced by LinkManager for the driver to dispatch.
191#[derive(Debug)]
192pub enum LinkManagerAction {
193    /// Send a packet via the transport engine outbound path.
194    SendPacket {
195        raw: Vec<u8>,
196        dest_type: u8,
197        attached_interface: Option<rns_core::transport::types::InterfaceId>,
198    },
199    /// Link established — notify callbacks.
200    LinkEstablished {
201        link_id: LinkId,
202        dest_hash: [u8; 16],
203        hops: u8,
204        rebalanced_at: Option<f64>,
205        rtt: f64,
206        is_initiator: bool,
207    },
208    /// Link closed — notify callbacks.
209    LinkClosed {
210        link_id: LinkId,
211        reason: Option<TeardownReason>,
212    },
213    /// Remote peer identified — notify callbacks.
214    RemoteIdentified {
215        link_id: LinkId,
216        identity_hash: [u8; 16],
217        public_key: [u8; 64],
218    },
219    /// Register a link_id as local destination in transport (for receiving link data).
220    RegisterLinkDest { link_id: LinkId },
221    /// Deregister a link_id from transport local destinations.
222    DeregisterLinkDest { link_id: LinkId },
223    /// A management request that needs to be handled by the driver.
224    /// The driver has access to engine state needed to build the response.
225    ManagementRequest {
226        link_id: LinkId,
227        path_hash: [u8; 16],
228        /// The request data (msgpack-encoded Value from the request array).
229        data: Vec<u8>,
230        /// The request_id (truncated hash of the packed request).
231        request_id: [u8; 16],
232        remote_identity: Option<([u8; 16], [u8; 64])>,
233    },
234    /// Resource data fully received and assembled.
235    ResourceReceived {
236        link_id: LinkId,
237        data: Vec<u8>,
238        metadata: Option<Vec<u8>>,
239    },
240    /// A disk-backed Resource is ready for application ownership.
241    ResourceFileReceived {
242        link_id: LinkId,
243        resource: ReceivedResourceFile,
244    },
245    /// Resource transfer completed (proof validated on sender side).
246    ResourceCompleted { link_id: LinkId },
247    /// Resource transfer failed.
248    ResourceFailed { link_id: LinkId, error: String },
249    /// Resource transfer progress update.
250    ResourceProgress {
251        link_id: LinkId,
252        received: usize,
253        total: usize,
254    },
255    ResourceStreamCompleted {
256        link_id: LinkId,
257        transfer_id: ResourceTransferId,
258    },
259    ResourceStreamFailed {
260        link_id: LinkId,
261        transfer_id: ResourceTransferId,
262        error: ResourceTransferError,
263    },
264    ResourceStreamProgress {
265        link_id: LinkId,
266        transfer_id: ResourceTransferId,
267        transferred: u64,
268        total: u64,
269    },
270    /// Query application whether to accept an incoming resource (for AcceptApp strategy).
271    ResourceAcceptQuery {
272        link_id: LinkId,
273        resource_hash: Vec<u8>,
274        transfer_size: u64,
275        has_metadata: bool,
276    },
277    /// Channel message received on a link.
278    ChannelMessageReceived {
279        link_id: LinkId,
280        msgtype: u16,
281        payload: Vec<u8>,
282    },
283    /// Generic link data received (CONTEXT_NONE).
284    LinkDataReceived {
285        link_id: LinkId,
286        context: u8,
287        data: Vec<u8>,
288    },
289    /// Response received on a link.
290    ResponseReceived {
291        link_id: LinkId,
292        request_id: [u8; 16],
293        data: Vec<u8>,
294        metadata: Option<Vec<u8>>,
295    },
296    /// An outbound request failed.
297    RequestFailed {
298        link_id: LinkId,
299        request_id: [u8; 16],
300        reason: RequestFailure,
301    },
302    /// A link request was received (for hook notification).
303    LinkRequestReceived {
304        link_id: LinkId,
305        receiving_interface: rns_core::transport::types::InterfaceId,
306    },
307}
308
309/// Manages multiple links, link destinations, and request/response.
310pub struct LinkManager {
311    links: HashMap<LinkId, ManagedLink>,
312    link_destinations: HashMap<[u8; 16], LinkDestination>,
313    request_handlers: Vec<RequestHandlerEntry>,
314    deferred_request_handlers: Vec<DeferredRequestHandlerEntry>,
315    /// Path hashes that should be handled externally (by the driver) rather than
316    /// by registered handler closures. Used for management destinations.
317    management_paths: Vec<[u8; 16]>,
318}
319
320#[derive(Debug, Clone, Copy, PartialEq, Eq)]
321pub struct LinkRouteHint {
322    pub interface: rns_core::transport::types::InterfaceId,
323    pub transport_id: Option<[u8; 16]>,
324}
325
326impl LinkManager {
327    fn resource_sdu_for_link(link: &ManagedLink) -> usize {
328        // Python parity: Resource.sdu = link.mtu - HEADER_MAXSIZE - IFAC_MIN_SIZE
329        // when MTU signalling is available on the link.
330        let mtu = link.engine.mtu() as usize;
331        let derived = mtu.saturating_sub(constants::HEADER_MAXSIZE + constants::IFAC_MIN_SIZE);
332        if derived > 0 {
333            derived
334        } else {
335            constants::RESOURCE_SDU
336        }
337    }
338
339    fn split_progress_parts(
340        segment_index: u64,
341        total_segments: u64,
342        current_done: usize,
343        current_total: usize,
344        sdu: usize,
345    ) -> (usize, usize) {
346        let max_parts_per_segment = constants::RESOURCE_MAX_EFFICIENT_SIZE.div_ceil(sdu.max(1));
347        let total = (total_segments as usize).saturating_mul(max_parts_per_segment);
348        let completed_segments = segment_index.saturating_sub(1) as usize;
349        let completed = completed_segments.saturating_mul(max_parts_per_segment);
350        let current = if current_total == 0 {
351            0
352        } else if current_total < max_parts_per_segment {
353            let scaled =
354                (current_done as f64) * (max_parts_per_segment as f64 / current_total as f64);
355            scaled.floor() as usize
356        } else {
357            current_done
358        };
359        (completed.saturating_add(current).min(total), total)
360    }
361
362    fn resource_hash_key(hash: &[u8]) -> Option<[u8; 32]> {
363        let mut key = [0u8; 32];
364        if hash.len() != key.len() {
365            return None;
366        }
367        key.copy_from_slice(hash);
368        Some(key)
369    }
370
371    fn incoming_storage(
372        mode: &ResourceReceiveMode,
373        original_hash: &[u8; 32],
374    ) -> std::io::Result<IncomingSplitStorage> {
375        match mode {
376            ResourceReceiveMode::Memory { .. } => Ok(IncomingSplitStorage::Memory(Vec::new())),
377            ResourceReceiveMode::TemporaryFile { directory, .. } => {
378                let (file, path) = crate::resource::create_receive_file(directory, original_hash)?;
379                Ok(IncomingSplitStorage::File {
380                    file,
381                    path,
382                    size: 0,
383                })
384            }
385        }
386    }
387
388    fn incoming_split_progress(split: &IncomingSplitTransfer, sdu: usize) -> (usize, usize) {
389        Self::split_progress_parts(
390            split.current_segment_index,
391            split.total_segments,
392            split.current_received_parts,
393            split.current_total_parts,
394            sdu,
395        )
396    }
397
398    fn outgoing_split_progress(split: &OutgoingSplitTransfer, sdu: usize) -> (usize, usize) {
399        Self::split_progress_parts(
400            split.current_segment_index,
401            split.total_segments,
402            split.current_sent_parts,
403            split.current_total_parts,
404            sdu,
405        )
406    }
407
408    /// Create a new empty link manager.
409    pub fn new() -> Self {
410        LinkManager {
411            links: HashMap::new(),
412            link_destinations: HashMap::new(),
413            request_handlers: Vec::new(),
414            deferred_request_handlers: Vec::new(),
415            management_paths: Vec::new(),
416        }
417    }
418
419    /// Register a path hash as a management path.
420    /// Management requests are returned as ManagementRequest actions
421    /// for the driver to handle (since they need access to engine state).
422    pub fn register_management_path(&mut self, path_hash: [u8; 16]) {
423        if !self.management_paths.contains(&path_hash) {
424            self.management_paths.push(path_hash);
425        }
426    }
427
428    /// Get the derived session key for a link (needed for hole-punch token derivation).
429    pub fn get_derived_key(&self, link_id: &LinkId) -> Option<Vec<u8>> {
430        self.links
431            .get(link_id)
432            .and_then(|link| link.engine.derived_key().map(|dk| dk.to_vec()))
433    }
434
435    /// Return best-known routing hint for link packets.
436    pub fn get_link_route_hint(&self, link_id: &LinkId) -> Option<LinkRouteHint> {
437        self.links.get(link_id).and_then(|link| {
438            link.route_interface.map(|interface| LinkRouteHint {
439                interface,
440                transport_id: link.route_transport_id,
441            })
442        })
443    }
444
445    /// Set the best-known outbound route for a link.
446    pub fn set_link_route_hint(
447        &mut self,
448        link_id: &LinkId,
449        interface: rns_core::transport::types::InterfaceId,
450        transport_id: Option<[u8; 16]>,
451    ) -> bool {
452        let Some(link) = self.links.get_mut(link_id) else {
453            return false;
454        };
455        link.route_interface = Some(interface);
456        link.route_transport_id = transport_id;
457        true
458    }
459
460    /// Register a destination that can accept incoming links.
461    pub fn register_link_destination(
462        &mut self,
463        dest_hash: [u8; 16],
464        sig_prv: Ed25519PrivateKey,
465        sig_pub_bytes: [u8; 32],
466        resource_strategy: ResourceStrategy,
467    ) {
468        self.link_destinations.insert(
469            dest_hash,
470            LinkDestination {
471                sig_prv,
472                sig_pub_bytes,
473                resource_strategy,
474                max_request_size: None,
475            },
476        );
477    }
478
479    /// Configure the maximum request size for this destination.
480    ///
481    /// The new limit also applies to responder links that are already active.
482    /// Returns false when the destination is not registered.
483    pub fn set_link_destination_max_request_size(
484        &mut self,
485        dest_hash: &[u8; 16],
486        max_request_size: Option<usize>,
487    ) -> bool {
488        let Some(destination) = self.link_destinations.get_mut(dest_hash) else {
489            return false;
490        };
491        destination.max_request_size = max_request_size;
492        for link in self.links.values_mut() {
493            if link.dest_hash == *dest_hash && link.dest_sig_pub_bytes.is_none() {
494                link.max_request_size = max_request_size;
495            }
496        }
497        true
498    }
499
500    /// Deregister a link destination.
501    pub fn deregister_link_destination(&mut self, dest_hash: &[u8; 16]) {
502        self.link_destinations.remove(dest_hash);
503    }
504
505    /// Register a request handler for a given path.
506    ///
507    /// `path`: the request path string (e.g. "/status")
508    /// `allowed_list`: None = allow all, Some(list) = restrict to these identity hashes
509    /// `handler`: called with (link_id, path, request_data, remote_identity) -> Option<response>
510    pub fn register_request_handler<F>(
511        &mut self,
512        path: &str,
513        allowed_list: Option<Vec<[u8; 16]>>,
514        handler: F,
515    ) where
516        F: Fn(LinkId, &str, &[u8], Option<&([u8; 16], [u8; 64])>) -> Option<Vec<u8>>
517            + Send
518            + 'static,
519    {
520        let path_hash = compute_path_hash(path);
521        self.request_handlers.push(RequestHandlerEntry {
522            path: path.to_string(),
523            path_hash,
524            allowed_list,
525            handler: Box::new(move |link_id, p, data, remote| {
526                handler(link_id, p, data, remote).map(RequestResponse::Bytes)
527            }),
528        });
529    }
530
531    /// Register a request handler that can return resource responses with metadata.
532    pub fn register_request_handler_response<F>(
533        &mut self,
534        path: &str,
535        allowed_list: Option<Vec<[u8; 16]>>,
536        handler: F,
537    ) where
538        F: Fn(LinkId, &str, &[u8], Option<&([u8; 16], [u8; 64])>) -> Option<RequestResponse>
539            + Send
540            + 'static,
541    {
542        let path_hash = compute_path_hash(path);
543        self.request_handlers.push(RequestHandlerEntry {
544            path: path.to_string(),
545            path_hash,
546            allowed_list,
547            handler: Box::new(handler),
548        });
549    }
550
551    /// Register a request handler that produces its response asynchronously.
552    pub fn register_deferred_request_handler<F>(
553        &mut self,
554        path: &str,
555        allowed_list: Option<Vec<[u8; 16]>>,
556        handler: F,
557    ) where
558        F: Fn(LinkId, &str, [u8; 16], &[u8], Option<&([u8; 16], [u8; 64])>) + Send + 'static,
559    {
560        self.deferred_request_handlers
561            .push(DeferredRequestHandlerEntry {
562                path: path.to_string(),
563                path_hash: compute_path_hash(path),
564                allowed_list,
565                handler: Box::new(handler),
566            });
567    }
568
569    /// Create an outbound link to a destination.
570    ///
571    /// `dest_sig_pub_bytes` is the destination's Ed25519 signing public key
572    /// (needed to verify LRPROOF). In Python this comes from the Destination's Identity.
573    ///
574    /// Returns `(link_id, actions)`. The first action will be a SendPacket with
575    /// the LINKREQUEST.
576    pub fn create_link(
577        &mut self,
578        dest_hash: &[u8; 16],
579        dest_sig_pub_bytes: &[u8; 32],
580        hops: u8,
581        mtu: u32,
582        rng: &mut dyn Rng,
583    ) -> (LinkId, Vec<LinkManagerAction>) {
584        let mode = LinkMode::Aes256Cbc;
585        let (mut engine, request_data) =
586            LinkEngine::new_initiator(dest_hash, hops, mode, Some(mtu), time::now(), rng);
587
588        // Build the LINKREQUEST packet to compute link_id
589        let flags = PacketFlags {
590            header_type: constants::HEADER_1,
591            context_flag: constants::FLAG_UNSET,
592            transport_type: constants::TRANSPORT_BROADCAST,
593            destination_type: constants::DESTINATION_SINGLE,
594            packet_type: constants::PACKET_TYPE_LINKREQUEST,
595        };
596
597        let packet = match RawPacket::pack(
598            flags,
599            0,
600            dest_hash,
601            None,
602            constants::CONTEXT_NONE,
603            &request_data,
604        ) {
605            Ok(p) => p,
606            Err(_) => {
607                // Should not happen with valid data
608                return ([0u8; 16], Vec::new());
609            }
610        };
611
612        engine.set_link_id_from_hashable(&packet.get_hashable_part(), request_data.len());
613        let link_id = *engine.link_id();
614
615        engine.record_outbound_traffic(packet.data.len());
616        let managed = ManagedLink {
617            engine,
618            channel: None,
619            pending_channel_packets: HashMap::new(),
620            channel_send_ok: 0,
621            channel_send_not_ready: 0,
622            channel_send_too_big: 0,
623            channel_send_other_error: 0,
624            channel_messages_received: 0,
625            channel_proofs_sent: 0,
626            channel_proofs_received: 0,
627            dest_hash: *dest_hash,
628            remote_identity: None,
629            dest_sig_pub_bytes: Some(*dest_sig_pub_bytes),
630            incoming_resources: Vec::new(),
631            outgoing_resources: Vec::new(),
632            pending_requests: HashMap::new(),
633            incoming_splits: HashMap::new(),
634            outgoing_splits: HashMap::new(),
635            outgoing_streams: HashMap::new(),
636            resource_strategy: ResourceStrategy::default(),
637            resource_receive_mode: ResourceReceiveMode::default(),
638            max_request_size: None,
639            route_interface: None,
640            route_transport_id: None,
641        };
642        self.links.insert(link_id, managed);
643
644        let mut actions = Vec::new();
645        // Register the link_id as a local destination so we can receive LRPROOF
646        actions.push(LinkManagerAction::RegisterLinkDest { link_id });
647        // Send the LINKREQUEST packet
648        actions.push(LinkManagerAction::SendPacket {
649            raw: packet.raw,
650            dest_type: constants::DESTINATION_LINK,
651            attached_interface: None,
652        });
653
654        (link_id, actions)
655    }
656
657    /// Handle a packet delivered locally (via DeliverLocal).
658    ///
659    /// Returns actions for the driver to dispatch. The `dest_hash` is the
660    /// packet's destination_hash field. `raw` is the full packet bytes.
661    /// `packet_hash` is the SHA-256 hash.
662    pub fn handle_local_delivery(
663        &mut self,
664        dest_hash: [u8; 16],
665        raw: &[u8],
666        packet_hash: [u8; 32],
667        receiving_interface: rns_core::transport::types::InterfaceId,
668        rng: &mut dyn Rng,
669    ) -> Vec<LinkManagerAction> {
670        let packet = match RawPacket::unpack(raw) {
671            Ok(p) => p,
672            Err(_) => return Vec::new(),
673        };
674
675        // Python accounts link traffic once the packet is accepted on its
676        // attached interface, before dispatch/decryption of its context.
677        if packet.flags.packet_type != constants::PACKET_TYPE_LINKREQUEST {
678            if let Some(link) = self.links.get_mut(&dest_hash) {
679                link.engine.record_inbound_traffic(packet.data.len());
680            }
681        }
682
683        match packet.flags.packet_type {
684            constants::PACKET_TYPE_LINKREQUEST => {
685                self.handle_linkrequest(&dest_hash, &packet, receiving_interface, rng)
686            }
687            constants::PACKET_TYPE_PROOF if packet.context == constants::CONTEXT_LRPROOF => {
688                // LRPROOF: dest_hash is the link_id
689                self.handle_lrproof(&dest_hash, &packet, receiving_interface, rng)
690            }
691            constants::PACKET_TYPE_PROOF if packet.context == constants::CONTEXT_RESOURCE_PRF => {
692                // Resource proofs are PROOF packets with raw proof data, not
693                // encrypted DATA packets.
694                self.handle_resource_prf(&dest_hash, &packet.data, rng)
695            }
696            constants::PACKET_TYPE_PROOF => self.handle_link_proof(&dest_hash, &packet, rng),
697            constants::PACKET_TYPE_DATA => {
698                self.handle_link_data(&dest_hash, &packet, packet_hash, receiving_interface, rng)
699            }
700            _ => Vec::new(),
701        }
702    }
703
704    /// Handle an incoming LINKREQUEST packet.
705    fn handle_linkrequest(
706        &mut self,
707        dest_hash: &[u8; 16],
708        packet: &RawPacket,
709        receiving_interface: rns_core::transport::types::InterfaceId,
710        rng: &mut dyn Rng,
711    ) -> Vec<LinkManagerAction> {
712        // Look up the link destination
713        let ld = match self.link_destinations.get(dest_hash) {
714            Some(ld) => ld,
715            None => return Vec::new(),
716        };
717
718        let hashable = packet.get_hashable_part();
719        let now = time::now();
720
721        // Create responder engine
722        let (engine, lrproof_data) = match LinkEngine::new_responder(
723            &ld.sig_prv,
724            &ld.sig_pub_bytes,
725            &packet.data,
726            &hashable,
727            dest_hash,
728            packet.hops,
729            now,
730            rng,
731        ) {
732            Ok(r) => r,
733            Err(e) => {
734                log::debug!("LINKREQUEST rejected: {}", e);
735                return Vec::new();
736            }
737        };
738
739        let link_id = *engine.link_id();
740        log::debug!(
741            "LINKREQUEST accepted: link={:02x?} iface={} header_type={} transport_id_present={} hops={}",
742            &link_id[..4],
743            receiving_interface.0,
744            packet.flags.header_type,
745            packet.transport_id.is_some(),
746            packet.hops
747        );
748
749        let managed = ManagedLink {
750            engine,
751            channel: None,
752            pending_channel_packets: HashMap::new(),
753            channel_send_ok: 0,
754            channel_send_not_ready: 0,
755            channel_send_too_big: 0,
756            channel_send_other_error: 0,
757            channel_messages_received: 0,
758            channel_proofs_sent: 0,
759            channel_proofs_received: 0,
760            dest_hash: *dest_hash,
761            remote_identity: None,
762            dest_sig_pub_bytes: None,
763            incoming_resources: Vec::new(),
764            outgoing_resources: Vec::new(),
765            pending_requests: HashMap::new(),
766            incoming_splits: HashMap::new(),
767            outgoing_splits: HashMap::new(),
768            outgoing_streams: HashMap::new(),
769            resource_strategy: ld.resource_strategy,
770            resource_receive_mode: ResourceReceiveMode::default(),
771            max_request_size: ld.max_request_size,
772            route_interface: Some(receiving_interface),
773            route_transport_id: if packet.flags.header_type == constants::HEADER_2 {
774                packet.transport_id
775            } else {
776                None
777            },
778        };
779        self.links.insert(link_id, managed);
780
781        // Build LRPROOF packet: type=PROOF, context=LRPROOF, dest=link_id
782        let flags = PacketFlags {
783            header_type: constants::HEADER_1,
784            context_flag: constants::FLAG_UNSET,
785            transport_type: constants::TRANSPORT_BROADCAST,
786            destination_type: constants::DESTINATION_LINK,
787            packet_type: constants::PACKET_TYPE_PROOF,
788        };
789
790        let mut actions = Vec::new();
791
792        // Register link_id as local destination so we receive link data
793        actions.push(LinkManagerAction::RegisterLinkDest { link_id });
794
795        if let Ok((raw, _packet_hash)) = RawPacket::pack_raw_with_hash(
796            flags,
797            0,
798            &link_id,
799            None,
800            constants::CONTEXT_LRPROOF,
801            &lrproof_data,
802        ) {
803            log::debug!(
804                "LRPROOF queued: link={:02x?} route_iface={} route_tid_present={} hops=0",
805                &link_id[..4],
806                receiving_interface.0,
807                packet.transport_id.is_some()
808            );
809            actions.push(LinkManagerAction::SendPacket {
810                raw,
811                dest_type: constants::DESTINATION_LINK,
812                attached_interface: None,
813            });
814        }
815
816        // Notify hook system about the incoming link request
817        actions.push(LinkManagerAction::LinkRequestReceived {
818            link_id,
819            receiving_interface,
820        });
821
822        actions
823    }
824
825    fn handle_link_proof(
826        &mut self,
827        link_id: &LinkId,
828        packet: &RawPacket,
829        rng: &mut dyn Rng,
830    ) -> Vec<LinkManagerAction> {
831        if packet.data.len() < 32 {
832            return Vec::new();
833        }
834
835        let mut tracked_hash = [0u8; 32];
836        tracked_hash.copy_from_slice(&packet.data[..32]);
837
838        let Some(link) = self.links.get_mut(link_id) else {
839            return Vec::new();
840        };
841        let Some(sequence) = link.pending_channel_packets.remove(&tracked_hash) else {
842            return Vec::new();
843        };
844        link.channel_proofs_received += 1;
845        let Some(channel) = link.channel.as_mut() else {
846            return Vec::new();
847        };
848
849        let chan_actions = channel.packet_delivered(sequence);
850        let _ = channel;
851        let _ = link;
852        self.process_channel_actions(link_id, chan_actions, rng)
853    }
854
855    fn build_link_packet_proof(
856        &mut self,
857        link_id: &LinkId,
858        packet_hash: &[u8; 32],
859    ) -> Vec<LinkManagerAction> {
860        let dest_hash = match self.links.get(link_id) {
861            Some(link) => link.dest_hash,
862            None => return Vec::new(),
863        };
864        let Some(ld) = self.link_destinations.get(&dest_hash) else {
865            return Vec::new();
866        };
867        if let Some(link) = self.links.get_mut(link_id) {
868            link.channel_proofs_sent += 1;
869        }
870
871        let signature = ld.sig_prv.sign(packet_hash);
872        let mut proof_data = Vec::with_capacity(96);
873        proof_data.extend_from_slice(packet_hash);
874        proof_data.extend_from_slice(&signature);
875
876        let flags = PacketFlags {
877            header_type: constants::HEADER_1,
878            context_flag: constants::FLAG_UNSET,
879            transport_type: constants::TRANSPORT_BROADCAST,
880            destination_type: constants::DESTINATION_LINK,
881            packet_type: constants::PACKET_TYPE_PROOF,
882        };
883        if let Ok((raw, _packet_hash)) = RawPacket::pack_raw_with_hash(
884            flags,
885            0,
886            link_id,
887            None,
888            constants::CONTEXT_NONE,
889            &proof_data,
890        ) {
891            vec![LinkManagerAction::SendPacket {
892                raw,
893                dest_type: constants::DESTINATION_LINK,
894                attached_interface: None,
895            }]
896        } else {
897            Vec::new()
898        }
899    }
900
901    /// Handle an incoming LRPROOF packet (initiator side).
902    fn handle_lrproof(
903        &mut self,
904        link_id_bytes: &[u8; 16],
905        packet: &RawPacket,
906        receiving_interface: rns_core::transport::types::InterfaceId,
907        rng: &mut dyn Rng,
908    ) -> Vec<LinkManagerAction> {
909        let link = match self.links.get_mut(link_id_bytes) {
910            Some(l) => l,
911            None => return Vec::new(),
912        };
913
914        link.route_interface = Some(receiving_interface);
915        if packet.flags.header_type == constants::HEADER_2 {
916            if let Some(transport_id) = packet.transport_id {
917                link.route_transport_id = Some(transport_id);
918            }
919        }
920        log::debug!(
921            "LRPROOF received: link={:02x?} iface={} header_type={} transport_id_present={}",
922            &link_id_bytes[..4],
923            receiving_interface.0,
924            packet.flags.header_type,
925            packet.transport_id.is_some()
926        );
927
928        if link.engine.state() != LinkState::Pending || !link.engine.is_initiator() {
929            return Vec::new();
930        }
931
932        // The destination's signing pub key was stored when create_link was called
933        let dest_sig_pub_bytes = match link.dest_sig_pub_bytes {
934            Some(b) => b,
935            None => {
936                log::debug!("LRPROOF: no destination signing key available");
937                return Vec::new();
938            }
939        };
940
941        let now = time::now();
942        let (lrrtt_encrypted, link_actions) = match link.engine.handle_lrproof_with_hops(
943            &packet.data,
944            &dest_sig_pub_bytes,
945            Some(packet.hops),
946            now,
947            rng,
948        ) {
949            Ok(r) => r,
950            Err(e) => {
951                log::debug!("LRPROOF validation failed: {}", e);
952                return Vec::new();
953            }
954        };
955
956        let link_id = *link.engine.link_id();
957        let mut actions = Vec::new();
958
959        // Process link actions (StateChanged, LinkEstablished)
960        actions.extend(self.process_link_actions(&link_id, &link_actions));
961
962        // Send LRRTT: type=DATA, context=LRRTT, dest=link_id
963        let flags = PacketFlags {
964            header_type: constants::HEADER_1,
965            context_flag: constants::FLAG_UNSET,
966            transport_type: constants::TRANSPORT_BROADCAST,
967            destination_type: constants::DESTINATION_LINK,
968            packet_type: constants::PACKET_TYPE_DATA,
969        };
970
971        if let Ok((raw, _packet_hash)) = RawPacket::pack_raw_with_hash(
972            flags,
973            0,
974            &link_id,
975            None,
976            constants::CONTEXT_LRRTT,
977            &lrrtt_encrypted,
978        ) {
979            actions.push(LinkManagerAction::SendPacket {
980                raw,
981                dest_type: constants::DESTINATION_LINK,
982                attached_interface: None,
983            });
984        }
985
986        // Initialize channel now that link is active
987        if let Some(link) = self.links.get_mut(&link_id) {
988            if link.engine.state() == LinkState::Active {
989                let rtt = link.engine.rtt().unwrap_or(1.0);
990                link.channel = Some(Channel::new(rtt));
991            }
992        }
993
994        actions
995    }
996
997    /// Handle DATA packets on an established link.
998    ///
999    /// Structured to avoid borrow checker issues: we perform engine operations
1000    /// on the link, collect intermediate results, drop the mutable borrow, then
1001    /// call self methods that need immutable access.
1002    fn handle_link_data(
1003        &mut self,
1004        link_id_bytes: &[u8; 16],
1005        packet: &RawPacket,
1006        packet_hash: [u8; 32],
1007        receiving_interface: rns_core::transport::types::InterfaceId,
1008        rng: &mut dyn Rng,
1009    ) -> Vec<LinkManagerAction> {
1010        // First pass: perform engine operations, collect results
1011        enum LinkDataResult<'a> {
1012            Lrrtt {
1013                link_id: LinkId,
1014                link_actions: Vec<LinkAction>,
1015            },
1016            Identify {
1017                link_id: LinkId,
1018                link_actions: Vec<LinkAction>,
1019            },
1020            Keepalive {
1021                link_id: LinkId,
1022                inbound_actions: Vec<LinkAction>,
1023                reply: bool,
1024                received_at: f64,
1025            },
1026            LinkClose {
1027                link_id: LinkId,
1028                teardown_actions: Vec<LinkAction>,
1029            },
1030            Channel {
1031                link_id: LinkId,
1032                inbound_actions: Vec<LinkAction>,
1033                plaintext: Vec<u8>,
1034                packet_hash: [u8; 32],
1035            },
1036            Request {
1037                link_id: LinkId,
1038                inbound_actions: Vec<LinkAction>,
1039                plaintext: Vec<u8>,
1040                request_id: [u8; 16],
1041            },
1042            Response {
1043                link_id: LinkId,
1044                inbound_actions: Vec<LinkAction>,
1045                plaintext: Vec<u8>,
1046            },
1047            Generic {
1048                link_id: LinkId,
1049                inbound_actions: Vec<LinkAction>,
1050                plaintext: Vec<u8>,
1051                context: u8,
1052                packet_hash: [u8; 32],
1053            },
1054            /// Resource advertisement (link-decrypted).
1055            ResourceAdv {
1056                link_id: LinkId,
1057                inbound_actions: Vec<LinkAction>,
1058                plaintext: Vec<u8>,
1059            },
1060            /// Resource part request (link-decrypted).
1061            ResourceReq {
1062                link_id: LinkId,
1063                inbound_actions: Vec<LinkAction>,
1064                plaintext: Vec<u8>,
1065            },
1066            /// Resource hashmap update (link-decrypted).
1067            ResourceHmu {
1068                link_id: LinkId,
1069                inbound_actions: Vec<LinkAction>,
1070                plaintext: Vec<u8>,
1071            },
1072            /// Resource part data (NOT link-decrypted; parts are pre-encrypted by ResourceSender).
1073            ResourcePart {
1074                link_id: LinkId,
1075                inbound_actions: Vec<LinkAction>,
1076                raw_data: &'a [u8],
1077            },
1078            /// Resource proof (feed to sender).
1079            ResourcePrf {
1080                link_id: LinkId,
1081                inbound_actions: Vec<LinkAction>,
1082                plaintext: Vec<u8>,
1083            },
1084            /// Resource cancel from initiator (link-decrypted).
1085            ResourceIcl {
1086                link_id: LinkId,
1087                inbound_actions: Vec<LinkAction>,
1088            },
1089            /// Resource cancel from receiver (link-decrypted).
1090            ResourceRcl {
1091                link_id: LinkId,
1092                inbound_actions: Vec<LinkAction>,
1093            },
1094            Error,
1095        }
1096
1097        let result = {
1098            let link = match self.links.get_mut(link_id_bytes) {
1099                Some(l) => l,
1100                None => return Vec::new(),
1101            };
1102
1103            link.route_interface = Some(receiving_interface);
1104            if packet.flags.header_type == constants::HEADER_2 {
1105                if let Some(transport_id) = packet.transport_id {
1106                    link.route_transport_id = Some(transport_id);
1107                }
1108            } else {
1109                link.route_transport_id = None;
1110            }
1111
1112            match packet.context {
1113                constants::CONTEXT_LRRTT => {
1114                    match link.engine.handle_lrrtt_with_hops(
1115                        &packet.data,
1116                        Some(packet.hops),
1117                        time::now(),
1118                    ) {
1119                        Ok(link_actions) => {
1120                            let link_id = *link.engine.link_id();
1121                            LinkDataResult::Lrrtt {
1122                                link_id,
1123                                link_actions,
1124                            }
1125                        }
1126                        Err(e) => {
1127                            log::debug!("LRRTT handling failed: {}", e);
1128                            LinkDataResult::Error
1129                        }
1130                    }
1131                }
1132                constants::CONTEXT_LINKIDENTIFY => {
1133                    match link.engine.handle_identify(&packet.data) {
1134                        Ok(link_actions) => {
1135                            let link_id = *link.engine.link_id();
1136                            link.remote_identity = link.engine.remote_identity().cloned();
1137                            LinkDataResult::Identify {
1138                                link_id,
1139                                link_actions,
1140                            }
1141                        }
1142                        Err(e) => {
1143                            log::debug!("LINKIDENTIFY failed: {}", e);
1144                            LinkDataResult::Error
1145                        }
1146                    }
1147                }
1148                constants::CONTEXT_KEEPALIVE => {
1149                    let received_at = time::now();
1150                    let reply = link
1151                        .engine
1152                        .should_reply_keepalive(&packet.data, received_at);
1153                    let inbound_actions = link.engine.record_inbound(received_at);
1154                    let link_id = *link.engine.link_id();
1155                    LinkDataResult::Keepalive {
1156                        link_id,
1157                        inbound_actions,
1158                        reply,
1159                        received_at,
1160                    }
1161                }
1162                constants::CONTEXT_LINKCLOSE => match link.engine.decrypt(&packet.data) {
1163                    Ok(plaintext) if plaintext.as_slice() == link_id_bytes => {
1164                        let teardown_actions = link.engine.handle_teardown();
1165                        let link_id = *link.engine.link_id();
1166                        LinkDataResult::LinkClose {
1167                            link_id,
1168                            teardown_actions,
1169                        }
1170                    }
1171                    _ => LinkDataResult::Error,
1172                },
1173                constants::CONTEXT_CHANNEL => match link.engine.decrypt(&packet.data) {
1174                    Ok(plaintext) => {
1175                        let inbound_actions = link.engine.record_inbound(time::now());
1176                        let link_id = *link.engine.link_id();
1177                        LinkDataResult::Channel {
1178                            link_id,
1179                            inbound_actions,
1180                            plaintext,
1181                            packet_hash,
1182                        }
1183                    }
1184                    Err(_) => LinkDataResult::Error,
1185                },
1186                constants::CONTEXT_REQUEST => match link.engine.decrypt(&packet.data) {
1187                    Ok(plaintext) => {
1188                        let inbound_actions = link.engine.record_inbound(time::now());
1189                        let link_id = *link.engine.link_id();
1190                        let request_id = packet.get_truncated_hash();
1191                        LinkDataResult::Request {
1192                            link_id,
1193                            inbound_actions,
1194                            plaintext,
1195                            request_id,
1196                        }
1197                    }
1198                    Err(_) => LinkDataResult::Error,
1199                },
1200                constants::CONTEXT_RESPONSE => match link.engine.decrypt(&packet.data) {
1201                    Ok(plaintext) => {
1202                        let inbound_actions = link.engine.record_inbound(time::now());
1203                        let link_id = *link.engine.link_id();
1204                        LinkDataResult::Response {
1205                            link_id,
1206                            inbound_actions,
1207                            plaintext,
1208                        }
1209                    }
1210                    Err(_) => LinkDataResult::Error,
1211                },
1212                // --- Resource contexts ---
1213                constants::CONTEXT_RESOURCE_ADV => match link.engine.decrypt(&packet.data) {
1214                    Ok(plaintext) => {
1215                        let inbound_actions = link.engine.record_inbound(time::now());
1216                        let link_id = *link.engine.link_id();
1217                        LinkDataResult::ResourceAdv {
1218                            link_id,
1219                            inbound_actions,
1220                            plaintext,
1221                        }
1222                    }
1223                    Err(_) => LinkDataResult::Error,
1224                },
1225                constants::CONTEXT_RESOURCE_REQ => match link.engine.decrypt(&packet.data) {
1226                    Ok(plaintext) => {
1227                        let inbound_actions = link.engine.record_inbound(time::now());
1228                        let link_id = *link.engine.link_id();
1229                        LinkDataResult::ResourceReq {
1230                            link_id,
1231                            inbound_actions,
1232                            plaintext,
1233                        }
1234                    }
1235                    Err(_) => LinkDataResult::Error,
1236                },
1237                constants::CONTEXT_RESOURCE_HMU => match link.engine.decrypt(&packet.data) {
1238                    Ok(plaintext) => {
1239                        let inbound_actions = link.engine.record_inbound(time::now());
1240                        let link_id = *link.engine.link_id();
1241                        LinkDataResult::ResourceHmu {
1242                            link_id,
1243                            inbound_actions,
1244                            plaintext,
1245                        }
1246                    }
1247                    Err(_) => LinkDataResult::Error,
1248                },
1249                constants::CONTEXT_RESOURCE => {
1250                    // Resource parts are NOT link-decrypted — they're pre-encrypted by ResourceSender
1251                    let inbound_actions = link.engine.record_inbound(time::now());
1252                    let link_id = *link.engine.link_id();
1253                    LinkDataResult::ResourcePart {
1254                        link_id,
1255                        inbound_actions,
1256                        raw_data: &packet.data,
1257                    }
1258                }
1259                constants::CONTEXT_RESOURCE_PRF => match link.engine.decrypt(&packet.data) {
1260                    Ok(plaintext) => {
1261                        let inbound_actions = link.engine.record_inbound(time::now());
1262                        let link_id = *link.engine.link_id();
1263                        LinkDataResult::ResourcePrf {
1264                            link_id,
1265                            inbound_actions,
1266                            plaintext,
1267                        }
1268                    }
1269                    Err(_) => LinkDataResult::Error,
1270                },
1271                constants::CONTEXT_RESOURCE_ICL => {
1272                    let _ = link.engine.decrypt(&packet.data); // decrypt to validate
1273                    let inbound_actions = link.engine.record_inbound(time::now());
1274                    let link_id = *link.engine.link_id();
1275                    LinkDataResult::ResourceIcl {
1276                        link_id,
1277                        inbound_actions,
1278                    }
1279                }
1280                constants::CONTEXT_RESOURCE_RCL => {
1281                    let _ = link.engine.decrypt(&packet.data); // decrypt to validate
1282                    let inbound_actions = link.engine.record_inbound(time::now());
1283                    let link_id = *link.engine.link_id();
1284                    LinkDataResult::ResourceRcl {
1285                        link_id,
1286                        inbound_actions,
1287                    }
1288                }
1289                _ => match link.engine.decrypt(&packet.data) {
1290                    Ok(plaintext) => {
1291                        let inbound_actions = link.engine.record_inbound(time::now());
1292                        let link_id = *link.engine.link_id();
1293                        LinkDataResult::Generic {
1294                            link_id,
1295                            inbound_actions,
1296                            plaintext,
1297                            context: packet.context,
1298                            packet_hash,
1299                        }
1300                    }
1301                    Err(_) => LinkDataResult::Error,
1302                },
1303            }
1304        }; // mutable borrow of self.links dropped here
1305
1306        // Second pass: process results using self methods
1307        let mut actions = Vec::new();
1308        match result {
1309            LinkDataResult::Lrrtt {
1310                link_id,
1311                link_actions,
1312            } => {
1313                actions.extend(self.process_link_actions(&link_id, &link_actions));
1314                // Initialize channel
1315                if let Some(link) = self.links.get_mut(&link_id) {
1316                    if link.engine.state() == LinkState::Active {
1317                        let rtt = link.engine.rtt().unwrap_or(1.0);
1318                        link.channel = Some(Channel::new(rtt));
1319                    }
1320                }
1321            }
1322            LinkDataResult::Identify {
1323                link_id,
1324                link_actions,
1325            } => {
1326                actions.extend(self.process_link_actions(&link_id, &link_actions));
1327            }
1328            LinkDataResult::Keepalive {
1329                link_id,
1330                inbound_actions,
1331                reply,
1332                received_at,
1333            } => {
1334                actions.extend(self.process_link_actions(&link_id, &inbound_actions));
1335                if reply {
1336                    let flags = PacketFlags {
1337                        header_type: constants::HEADER_1,
1338                        context_flag: constants::FLAG_UNSET,
1339                        transport_type: constants::TRANSPORT_BROADCAST,
1340                        destination_type: constants::DESTINATION_LINK,
1341                        packet_type: constants::PACKET_TYPE_DATA,
1342                    };
1343                    if let Ok((raw, _)) = RawPacket::pack_raw_with_hash(
1344                        flags,
1345                        0,
1346                        &link_id,
1347                        None,
1348                        constants::CONTEXT_KEEPALIVE,
1349                        &[0xfe],
1350                    ) {
1351                        actions.push(LinkManagerAction::SendPacket {
1352                            raw,
1353                            dest_type: constants::DESTINATION_LINK,
1354                            attached_interface: None,
1355                        });
1356                        if let Some(link) = self.links.get_mut(&link_id) {
1357                            link.engine.record_outbound(received_at, true);
1358                        }
1359                    }
1360                }
1361            }
1362            LinkDataResult::LinkClose {
1363                link_id,
1364                teardown_actions,
1365            } => {
1366                actions.extend(self.process_link_actions(&link_id, &teardown_actions));
1367            }
1368            LinkDataResult::Channel {
1369                link_id,
1370                inbound_actions,
1371                plaintext,
1372                packet_hash,
1373            } => {
1374                actions.extend(self.process_link_actions(&link_id, &inbound_actions));
1375                // Feed plaintext to channel
1376                if let Some(link) = self.links.get_mut(&link_id) {
1377                    if let Some(ref mut channel) = link.channel {
1378                        let chan_actions = channel.receive(&plaintext, time::now());
1379                        link.channel_messages_received += chan_actions
1380                            .iter()
1381                            .filter(|action| {
1382                                matches!(
1383                                    action,
1384                                    rns_core::channel::ChannelAction::MessageReceived { .. }
1385                                )
1386                            })
1387                            .count()
1388                            as u64;
1389                        // process_channel_actions needs immutable self, so collect first
1390                        let _ = link;
1391                        actions.extend(self.process_channel_actions(&link_id, chan_actions, rng));
1392                    }
1393                }
1394                actions.extend(self.build_link_packet_proof(&link_id, &packet_hash));
1395            }
1396            LinkDataResult::Request {
1397                link_id,
1398                inbound_actions,
1399                plaintext,
1400                request_id,
1401            } => {
1402                actions.extend(self.process_link_actions(&link_id, &inbound_actions));
1403                let size_ok = self
1404                    .links
1405                    .get(&link_id)
1406                    .and_then(|link| link.max_request_size)
1407                    .is_none_or(|limit| plaintext.len() <= limit);
1408                if size_ok {
1409                    actions.extend(self.handle_request(&link_id, &plaintext, request_id, rng));
1410                } else {
1411                    log::debug!(
1412                        "ignored request with excessive size {} bytes on link {:02x?}",
1413                        plaintext.len(),
1414                        &link_id[..4]
1415                    );
1416                }
1417            }
1418            LinkDataResult::Response {
1419                link_id,
1420                inbound_actions,
1421                plaintext,
1422            } => {
1423                actions.extend(self.process_link_actions(&link_id, &inbound_actions));
1424                // Unpack msgpack response: [Bin(request_id), response_value]
1425                actions.extend(self.handle_response(&link_id, &plaintext, None, None));
1426            }
1427            LinkDataResult::Generic {
1428                link_id,
1429                inbound_actions,
1430                plaintext,
1431                context,
1432                packet_hash,
1433            } => {
1434                actions.extend(self.process_link_actions(&link_id, &inbound_actions));
1435                actions.push(LinkManagerAction::LinkDataReceived {
1436                    link_id,
1437                    context,
1438                    data: plaintext,
1439                });
1440
1441                actions.extend(self.build_link_packet_proof(&link_id, &packet_hash));
1442            }
1443            LinkDataResult::ResourceAdv {
1444                link_id,
1445                inbound_actions,
1446                plaintext,
1447            } => {
1448                actions.extend(self.process_link_actions(&link_id, &inbound_actions));
1449                actions.extend(self.handle_resource_adv(&link_id, &plaintext, rng));
1450            }
1451            LinkDataResult::ResourceReq {
1452                link_id,
1453                inbound_actions,
1454                plaintext,
1455            } => {
1456                actions.extend(self.process_link_actions(&link_id, &inbound_actions));
1457                actions.extend(self.handle_resource_req(&link_id, &plaintext, rng));
1458            }
1459            LinkDataResult::ResourceHmu {
1460                link_id,
1461                inbound_actions,
1462                plaintext,
1463            } => {
1464                actions.extend(self.process_link_actions(&link_id, &inbound_actions));
1465                actions.extend(self.handle_resource_hmu(&link_id, &plaintext, rng));
1466            }
1467            LinkDataResult::ResourcePart {
1468                link_id,
1469                inbound_actions,
1470                raw_data,
1471            } => {
1472                actions.extend(self.process_link_actions(&link_id, &inbound_actions));
1473                actions.extend(self.handle_resource_part(&link_id, &raw_data, rng));
1474            }
1475            LinkDataResult::ResourcePrf {
1476                link_id,
1477                inbound_actions,
1478                plaintext,
1479            } => {
1480                actions.extend(self.process_link_actions(&link_id, &inbound_actions));
1481                actions.extend(self.handle_resource_prf(&link_id, &plaintext, rng));
1482            }
1483            LinkDataResult::ResourceIcl {
1484                link_id,
1485                inbound_actions,
1486            } => {
1487                actions.extend(self.process_link_actions(&link_id, &inbound_actions));
1488                actions.extend(self.handle_resource_icl(&link_id));
1489            }
1490            LinkDataResult::ResourceRcl {
1491                link_id,
1492                inbound_actions,
1493            } => {
1494                actions.extend(self.process_link_actions(&link_id, &inbound_actions));
1495                actions.extend(self.handle_resource_rcl(&link_id));
1496            }
1497            LinkDataResult::Error => {}
1498        }
1499
1500        actions
1501    }
1502
1503    /// Handle a request on a link.
1504    fn handle_request(
1505        &mut self,
1506        link_id: &LinkId,
1507        plaintext: &[u8],
1508        request_id: [u8; 16],
1509        rng: &mut dyn Rng,
1510    ) -> Vec<LinkManagerAction> {
1511        use rns_core::msgpack::{self, Value};
1512
1513        // Python-compatible format: msgpack([timestamp, Bin(path_hash), data_value])
1514        let arr = match msgpack::unpack_exact(plaintext) {
1515            Ok(Value::Array(arr)) if arr.len() >= 3 => arr,
1516            _ => return Vec::new(),
1517        };
1518
1519        let path_hash_bytes = match &arr[1] {
1520            Value::Bin(b) if b.len() == 16 => b,
1521            _ => return Vec::new(),
1522        };
1523        let mut path_hash = [0u8; 16];
1524        path_hash.copy_from_slice(path_hash_bytes);
1525
1526        // Re-encode the data element for the handler
1527        let request_data = msgpack::pack(&arr[2]);
1528
1529        // Check if this is a management path (handled by the driver)
1530        if self.management_paths.contains(&path_hash) {
1531            let remote_identity = self
1532                .links
1533                .get(link_id)
1534                .and_then(|l| l.remote_identity)
1535                .map(|(h, k)| (h, k));
1536            return vec![LinkManagerAction::ManagementRequest {
1537                link_id: *link_id,
1538                path_hash,
1539                data: request_data,
1540                request_id,
1541                remote_identity,
1542            }];
1543        }
1544
1545        if let Some(handler) = self
1546            .deferred_request_handlers
1547            .iter()
1548            .find(|handler| handler.path_hash == path_hash)
1549        {
1550            let remote_identity = self
1551                .links
1552                .get(link_id)
1553                .and_then(|link| link.remote_identity.as_ref());
1554            if let Some(allowed) = &handler.allowed_list {
1555                let Some((identity_hash, _)) = remote_identity else {
1556                    log::debug!("Deferred request denied: peer not identified");
1557                    return Vec::new();
1558                };
1559                if !allowed.contains(identity_hash) {
1560                    log::debug!("Deferred request denied: identity not in allowed list");
1561                    return Vec::new();
1562                }
1563            }
1564            (handler.handler)(
1565                *link_id,
1566                &handler.path,
1567                request_id,
1568                &request_data,
1569                remote_identity,
1570            );
1571            return Vec::new();
1572        }
1573
1574        // Look up handler by path_hash
1575        let handler_idx = self
1576            .request_handlers
1577            .iter()
1578            .position(|h| h.path_hash == path_hash);
1579        let handler_idx = match handler_idx {
1580            Some(i) => i,
1581            None => return Vec::new(),
1582        };
1583
1584        // Check ACL
1585        let remote_identity = self
1586            .links
1587            .get(link_id)
1588            .and_then(|l| l.remote_identity.as_ref());
1589        let handler = &self.request_handlers[handler_idx];
1590        if let Some(ref allowed) = handler.allowed_list {
1591            match remote_identity {
1592                Some((identity_hash, _)) => {
1593                    if !allowed.contains(identity_hash) {
1594                        log::debug!("Request denied: identity not in allowed list");
1595                        return Vec::new();
1596                    }
1597                }
1598                None => {
1599                    log::debug!("Request denied: peer not identified");
1600                    return Vec::new();
1601                }
1602            }
1603        }
1604
1605        // Call handler
1606        let path = handler.path.clone();
1607        let response = (handler.handler)(*link_id, &path, &request_data, remote_identity);
1608
1609        let mut actions = Vec::new();
1610        if let Some(response) = response {
1611            match response {
1612                RequestResponse::Bytes(response_data) => {
1613                    let mut response_actions =
1614                        self.build_response_packet(link_id, &request_id, &response_data, rng);
1615                    if response_actions.is_empty() {
1616                        response_actions.extend(self.send_response_resource(
1617                            link_id,
1618                            &request_id,
1619                            &response_data,
1620                            None,
1621                            true,
1622                            rng,
1623                        ));
1624                    }
1625                    actions.extend(response_actions);
1626                }
1627                RequestResponse::Resource {
1628                    data,
1629                    metadata,
1630                    auto_compress,
1631                } => {
1632                    actions.extend(self.send_response_resource(
1633                        link_id,
1634                        &request_id,
1635                        &data,
1636                        metadata.as_deref(),
1637                        auto_compress,
1638                        rng,
1639                    ));
1640                }
1641            }
1642        }
1643
1644        actions
1645    }
1646
1647    /// Build a response packet for a request.
1648    /// `response_data` is the msgpack-encoded response value.
1649    fn build_response_packet(
1650        &self,
1651        link_id: &LinkId,
1652        request_id: &[u8; 16],
1653        response_data: &[u8],
1654        rng: &mut dyn Rng,
1655    ) -> Vec<LinkManagerAction> {
1656        use rns_core::msgpack::{self, Value};
1657
1658        let response_value = msgpack::unpack_exact(response_data)
1659            .unwrap_or_else(|_| Value::Bin(response_data.to_vec()));
1660
1661        let response_array = Value::Array(vec![Value::Bin(request_id.to_vec()), response_value]);
1662        let response_plaintext = msgpack::pack(&response_array);
1663
1664        let mut actions = Vec::new();
1665        if let Some(link) = self.links.get(link_id) {
1666            if let Ok(encrypted) = link.engine.encrypt(&response_plaintext, rng) {
1667                let flags = PacketFlags {
1668                    header_type: constants::HEADER_1,
1669                    context_flag: constants::FLAG_UNSET,
1670                    transport_type: constants::TRANSPORT_BROADCAST,
1671                    destination_type: constants::DESTINATION_LINK,
1672                    packet_type: constants::PACKET_TYPE_DATA,
1673                };
1674                let max_mtu = link.engine.mtu() as usize;
1675                if let Ok((raw, _packet_hash)) = RawPacket::pack_raw_with_hash_with_max_mtu(
1676                    flags,
1677                    0,
1678                    link_id,
1679                    None,
1680                    constants::CONTEXT_RESPONSE,
1681                    &encrypted,
1682                    max_mtu,
1683                ) {
1684                    actions.push(LinkManagerAction::SendPacket {
1685                        raw,
1686                        dest_type: constants::DESTINATION_LINK,
1687                        attached_interface: None,
1688                    });
1689                }
1690            }
1691        }
1692        actions
1693    }
1694
1695    fn send_response_resource(
1696        &mut self,
1697        link_id: &LinkId,
1698        request_id: &[u8; 16],
1699        response_data: &[u8],
1700        metadata: Option<&[u8]>,
1701        auto_compress: bool,
1702        rng: &mut dyn Rng,
1703    ) -> Vec<LinkManagerAction> {
1704        use rns_core::msgpack::{self, Value};
1705
1706        let link = match self.links.get_mut(link_id) {
1707            Some(l) => l,
1708            None => return Vec::new(),
1709        };
1710
1711        if link.engine.state() != LinkState::Active {
1712            return Vec::new();
1713        }
1714
1715        let now = time::now();
1716
1717        // Match Python resource response format from Link.handle_request:
1718        // packed_response = msgpack([request_id, response_value])
1719        // where response_value is decoded msgpack value, or Bin(raw bytes).
1720        let response_value = msgpack::unpack_exact(response_data)
1721            .unwrap_or_else(|_| Value::Bin(response_data.to_vec()));
1722        let response_array = Value::Array(vec![Value::Bin(request_id.to_vec()), response_value]);
1723        let resource_payload = msgpack::pack(&response_array);
1724
1725        let senders = match Self::build_resource_senders(
1726            link,
1727            &resource_payload,
1728            metadata,
1729            auto_compress,
1730            true, // is_response
1731            Some(request_id.to_vec()),
1732            rng,
1733            now,
1734        ) {
1735            Ok(s) => s,
1736            Err(e) => {
1737                log::debug!("Failed to create response ResourceSender: {}", e);
1738                return Vec::new();
1739            }
1740        };
1741
1742        let adv_actions = Self::start_resource_senders(link, senders, now);
1743
1744        let _ = link;
1745        self.process_resource_actions(link_id, adv_actions, rng)
1746    }
1747
1748    /// Send the value for a previously accepted deferred request.
1749    pub fn send_deferred_response(
1750        &mut self,
1751        link_id: &LinkId,
1752        request_id: &[u8; 16],
1753        response_data: &[u8],
1754        rng: &mut dyn Rng,
1755    ) -> Vec<LinkManagerAction> {
1756        let packet_actions = self.build_response_packet(link_id, request_id, response_data, rng);
1757        if !packet_actions.is_empty() {
1758            packet_actions
1759        } else {
1760            self.send_response_resource(link_id, request_id, response_data, None, true, rng)
1761        }
1762    }
1763
1764    /// Send a management response on a link.
1765    /// Called by the driver after building the response for a ManagementRequest.
1766    pub fn send_management_response(
1767        &mut self,
1768        link_id: &LinkId,
1769        request_id: &[u8; 16],
1770        response_data: &[u8],
1771        rng: &mut dyn Rng,
1772    ) -> Vec<LinkManagerAction> {
1773        let mut actions = self.build_response_packet(link_id, request_id, response_data, rng);
1774        if actions.is_empty() {
1775            actions.extend(self.send_response_resource(
1776                link_id,
1777                request_id,
1778                response_data,
1779                None,
1780                true,
1781                rng,
1782            ));
1783        }
1784        actions
1785    }
1786
1787    /// Send a request on a link.
1788    ///
1789    /// `data` is the msgpack-encoded request data value (e.g. msgpack([True]) for /status).
1790    ///
1791    /// Uses Python-compatible format: plaintext = msgpack([timestamp, path_hash_bytes, data_value]).
1792    /// Returns actions (the encrypted request packet). The response will arrive
1793    /// later via handle_local_delivery with CONTEXT_RESPONSE.
1794    pub fn send_request(
1795        &mut self,
1796        link_id: &LinkId,
1797        path: &str,
1798        data: &[u8],
1799        rng: &mut dyn Rng,
1800    ) -> Vec<LinkManagerAction> {
1801        self.send_request_with_max_response_size(link_id, path, data, None, rng)
1802    }
1803
1804    /// Send a request with an optional maximum accepted response size.
1805    pub fn send_request_with_max_response_size(
1806        &mut self,
1807        link_id: &LinkId,
1808        path: &str,
1809        data: &[u8],
1810        max_response_size: Option<usize>,
1811        rng: &mut dyn Rng,
1812    ) -> Vec<LinkManagerAction> {
1813        use rns_core::msgpack::{self, Value};
1814
1815        let link = match self.links.get_mut(link_id) {
1816            Some(l) => l,
1817            None => return Vec::new(),
1818        };
1819
1820        if link.engine.state() != LinkState::Active {
1821            return Vec::new();
1822        }
1823
1824        let path_hash = compute_path_hash(path);
1825
1826        // Decode data bytes to msgpack Value (or use Bin if can't decode)
1827        let data_value = msgpack::unpack_exact(data).unwrap_or_else(|_| Value::Bin(data.to_vec()));
1828
1829        // Python-compatible format: msgpack([timestamp, Bin(path_hash), data_value])
1830        let request_array = Value::Array(vec![
1831            Value::Float(time::now()),
1832            Value::Bin(path_hash.to_vec()),
1833            data_value,
1834        ]);
1835        let plaintext = msgpack::pack(&request_array);
1836
1837        if plaintext.len() > link.engine.mdu() {
1838            let request_id = rns_core::hash::truncated_hash(&plaintext);
1839            let now = time::now();
1840            let senders = match Self::build_resource_senders(
1841                link,
1842                &plaintext,
1843                None,
1844                true,
1845                false,
1846                Some(request_id.to_vec()),
1847                rng,
1848                now,
1849            ) {
1850                Ok(senders) => senders,
1851                Err(e) => {
1852                    log::debug!("Failed to create request ResourceSender: {}", e);
1853                    return Vec::new();
1854                }
1855            };
1856            // Resource request response timing starts once the remote proves
1857            // receipt of the final request-resource segment.
1858            link.pending_requests.insert(
1859                request_id,
1860                PendingRequest {
1861                    deadline: None,
1862                    max_response_size,
1863                },
1864            );
1865            let adv_actions = Self::start_resource_senders(link, senders, now);
1866            let _ = link;
1867            return self.process_resource_actions(link_id, adv_actions, rng);
1868        }
1869
1870        let encrypted = match link.engine.encrypt(&plaintext, rng) {
1871            Ok(e) => e,
1872            Err(_) => return Vec::new(),
1873        };
1874
1875        let flags = PacketFlags {
1876            header_type: constants::HEADER_1,
1877            context_flag: constants::FLAG_UNSET,
1878            transport_type: constants::TRANSPORT_BROADCAST,
1879            destination_type: constants::DESTINATION_LINK,
1880            packet_type: constants::PACKET_TYPE_DATA,
1881        };
1882
1883        let mut actions = Vec::new();
1884        let max_mtu = link.engine.mtu() as usize;
1885        if let Ok((raw, packet_hash)) = RawPacket::pack_raw_with_hash_with_max_mtu(
1886            flags,
1887            0,
1888            link_id,
1889            None,
1890            constants::CONTEXT_REQUEST,
1891            &encrypted,
1892            max_mtu,
1893        ) {
1894            let mut request_id = [0u8; 16];
1895            request_id.copy_from_slice(&packet_hash[..16]);
1896            let deadline = Self::request_response_deadline(link, time::now());
1897            link.pending_requests.insert(
1898                request_id,
1899                PendingRequest {
1900                    deadline: Some(deadline),
1901                    max_response_size,
1902                },
1903            );
1904            actions.push(LinkManagerAction::SendPacket {
1905                raw,
1906                dest_type: constants::DESTINATION_LINK,
1907                attached_interface: None,
1908            });
1909        }
1910        actions
1911    }
1912
1913    /// Send encrypted data on a link with a given context.
1914    pub fn send_on_link(
1915        &self,
1916        link_id: &LinkId,
1917        plaintext: &[u8],
1918        context: u8,
1919        rng: &mut dyn Rng,
1920    ) -> Vec<LinkManagerAction> {
1921        let link = match self.links.get(link_id) {
1922            Some(l) => l,
1923            None => return Vec::new(),
1924        };
1925
1926        if link.engine.state() != LinkState::Active {
1927            return Vec::new();
1928        }
1929
1930        let encrypted = match link.engine.encrypt(plaintext, rng) {
1931            Ok(e) => e,
1932            Err(_) => return Vec::new(),
1933        };
1934
1935        let flags = PacketFlags {
1936            header_type: constants::HEADER_1,
1937            context_flag: constants::FLAG_UNSET,
1938            transport_type: constants::TRANSPORT_BROADCAST,
1939            destination_type: constants::DESTINATION_LINK,
1940            packet_type: constants::PACKET_TYPE_DATA,
1941        };
1942
1943        let mut actions = Vec::new();
1944        if let Ok((raw, _packet_hash)) =
1945            RawPacket::pack_raw_with_hash(flags, 0, link_id, None, context, &encrypted)
1946        {
1947            actions.push(LinkManagerAction::SendPacket {
1948                raw,
1949                dest_type: constants::DESTINATION_LINK,
1950                attached_interface: None,
1951            });
1952        }
1953        actions
1954    }
1955
1956    /// Send an identify message on a link (initiator reveals identity to responder).
1957    pub fn identify(
1958        &self,
1959        link_id: &LinkId,
1960        identity: &rns_crypto::identity::Identity,
1961        rng: &mut dyn Rng,
1962    ) -> Vec<LinkManagerAction> {
1963        let link = match self.links.get(link_id) {
1964            Some(l) => l,
1965            None => return Vec::new(),
1966        };
1967
1968        let encrypted = match link.engine.build_identify(identity, rng) {
1969            Ok(e) => e,
1970            Err(_) => return Vec::new(),
1971        };
1972
1973        let flags = PacketFlags {
1974            header_type: constants::HEADER_1,
1975            context_flag: constants::FLAG_UNSET,
1976            transport_type: constants::TRANSPORT_BROADCAST,
1977            destination_type: constants::DESTINATION_LINK,
1978            packet_type: constants::PACKET_TYPE_DATA,
1979        };
1980
1981        let mut actions = Vec::new();
1982        if let Ok((raw, _packet_hash)) = RawPacket::pack_raw_with_hash(
1983            flags,
1984            0,
1985            link_id,
1986            None,
1987            constants::CONTEXT_LINKIDENTIFY,
1988            &encrypted,
1989        ) {
1990            actions.push(LinkManagerAction::SendPacket {
1991                raw,
1992                dest_type: constants::DESTINATION_LINK,
1993                attached_interface: None,
1994            });
1995        }
1996        actions
1997    }
1998
1999    /// Tear down a link.
2000    pub fn teardown_link(&mut self, link_id: &LinkId) -> Vec<LinkManagerAction> {
2001        let link = match self.links.get_mut(link_id) {
2002            Some(l) => l,
2003            None => return Vec::new(),
2004        };
2005
2006        let previous_state = link.engine.state();
2007        if previous_state == LinkState::Closed {
2008            return Vec::new();
2009        }
2010
2011        let encrypted_close = if previous_state == LinkState::Pending {
2012            None
2013        } else {
2014            let mut rng = OsRng;
2015            link.engine.encrypt(link_id, &mut rng).ok()
2016        };
2017        let teardown_actions = link.engine.teardown();
2018        if let Some(ref mut channel) = link.channel {
2019            channel.shutdown();
2020        }
2021
2022        let mut actions = self.process_link_actions(link_id, &teardown_actions);
2023
2024        // Pending links have not established link encryption and are closed
2025        // locally without transmitting LINKCLOSE.
2026        if previous_state == LinkState::Pending {
2027            return actions;
2028        }
2029
2030        // Send LINKCLOSE packet
2031        let flags = PacketFlags {
2032            header_type: constants::HEADER_1,
2033            context_flag: constants::FLAG_UNSET,
2034            transport_type: constants::TRANSPORT_BROADCAST,
2035            destination_type: constants::DESTINATION_LINK,
2036            packet_type: constants::PACKET_TYPE_DATA,
2037        };
2038        if let Some(encrypted_close) = encrypted_close {
2039            if let Ok((raw, _packet_hash)) = RawPacket::pack_raw_with_hash(
2040                flags,
2041                0,
2042                link_id,
2043                None,
2044                constants::CONTEXT_LINKCLOSE,
2045                &encrypted_close,
2046            ) {
2047                actions.push(LinkManagerAction::SendPacket {
2048                    raw,
2049                    dest_type: constants::DESTINATION_LINK,
2050                    attached_interface: None,
2051                });
2052            }
2053        }
2054
2055        actions
2056    }
2057
2058    /// Tear down all managed links.
2059    pub fn teardown_all_links(&mut self) -> Vec<LinkManagerAction> {
2060        let link_ids: Vec<LinkId> = self.links.keys().copied().collect();
2061        let mut actions = Vec::new();
2062        for link_id in link_ids {
2063            actions.extend(self.teardown_link(&link_id));
2064        }
2065        actions
2066    }
2067
2068    /// Handle a response on a link.
2069    fn handle_response(
2070        &mut self,
2071        link_id: &LinkId,
2072        plaintext: &[u8],
2073        metadata: Option<Vec<u8>>,
2074        resource_request_id: Option<[u8; 16]>,
2075    ) -> Vec<LinkManagerAction> {
2076        use rns_core::msgpack;
2077
2078        // Python-compatible packet response: msgpack([Bin(request_id), response_value]).
2079        let packet_response = msgpack::unpack_exact(plaintext).ok().and_then(|value| {
2080            let msgpack::Value::Array(arr) = value else {
2081                return None;
2082            };
2083            if arr.len() < 2 {
2084                return None;
2085            }
2086            let msgpack::Value::Bin(request_id_bytes) = &arr[0] else {
2087                return None;
2088            };
2089            if request_id_bytes.len() != 16 {
2090                return None;
2091            }
2092            let mut request_id = [0u8; 16];
2093            request_id.copy_from_slice(request_id_bytes);
2094            Some((request_id, msgpack::pack(&arr[1])))
2095        });
2096
2097        let (request_id, response_data, check_size) = match packet_response {
2098            Some((request_id, response_data)) => (request_id, response_data, true),
2099            None => {
2100                let Some(request_id) = resource_request_id else {
2101                    return Vec::new();
2102                };
2103                (
2104                    request_id,
2105                    msgpack::pack(&msgpack::Value::Bin(plaintext.to_vec())),
2106                    false,
2107                )
2108            }
2109        };
2110
2111        let Some(link) = self.links.get_mut(link_id) else {
2112            return Vec::new();
2113        };
2114        let Some(pending) = link.pending_requests.get(&request_id) else {
2115            return Vec::new();
2116        };
2117        if check_size {
2118            // Upstream excludes the two-byte MessagePack binary wrapper from
2119            // packet response sizes. Resource responses are checked from the
2120            // advertisement before their transfer is accepted.
2121            let response_size = response_data.len().saturating_sub(2) as u64;
2122            if let Some(maximum) = pending.max_response_size {
2123                if response_size > maximum as u64 {
2124                    link.pending_requests.remove(&request_id);
2125                    log::debug!(
2126                        "rejected response with excessive size {} bytes on link {:02x?}",
2127                        response_size,
2128                        &link_id[..4]
2129                    );
2130                    return vec![LinkManagerAction::RequestFailed {
2131                        link_id: *link_id,
2132                        request_id,
2133                        reason: RequestFailure::ResponseTooLarge {
2134                            size: response_size,
2135                            maximum,
2136                        },
2137                    }];
2138                }
2139            }
2140        }
2141        link.pending_requests.remove(&request_id);
2142
2143        vec![LinkManagerAction::ResponseReceived {
2144            link_id: *link_id,
2145            request_id,
2146            data: response_data,
2147            metadata,
2148        }]
2149    }
2150
2151    fn response_request_id(request_id: &Option<Vec<u8>>) -> Option<[u8; 16]> {
2152        let bytes = request_id.as_deref()?;
2153        if bytes.len() != 16 {
2154            return None;
2155        }
2156        let mut out = [0u8; 16];
2157        out.copy_from_slice(bytes);
2158        Some(out)
2159    }
2160
2161    fn request_response_deadline(link: &ManagedLink, now: f64) -> f64 {
2162        now + link.engine.rtt().unwrap_or(1.0) * constants::LINK_TRAFFIC_TIMEOUT_FACTOR
2163            + constants::RESOURCE_RESPONSE_MAX_GRACE_TIME * 1.125
2164    }
2165
2166    fn build_resource_senders(
2167        link: &ManagedLink,
2168        data: &[u8],
2169        metadata: Option<&[u8]>,
2170        auto_compress: bool,
2171        is_response: bool,
2172        request_id: Option<Vec<u8>>,
2173        rng: &mut dyn Rng,
2174        now: f64,
2175    ) -> Result<Vec<ResourceSender>, rns_core::resource::ResourceError> {
2176        let link_rtt = link.engine.rtt().unwrap_or(1.0);
2177        let resource_sdu = Self::resource_sdu_for_link(link);
2178        let metadata_overhead = metadata.map(|m| 3 + m.len()).unwrap_or(0);
2179        let logical_size = metadata_overhead + data.len();
2180
2181        if logical_size <= constants::RESOURCE_MAX_EFFICIENT_SIZE {
2182            let enc_rng = std::cell::RefCell::new(rns_crypto::OsRng);
2183            let encrypt_fn = |plaintext: &[u8]| -> Vec<u8> {
2184                link.engine
2185                    .encrypt(plaintext, &mut *enc_rng.borrow_mut())
2186                    .unwrap_or_else(|_| plaintext.to_vec())
2187            };
2188            return ResourceSender::new(
2189                data,
2190                metadata,
2191                resource_sdu,
2192                &encrypt_fn,
2193                &Bzip2Compressor,
2194                rng,
2195                now,
2196                auto_compress,
2197                is_response,
2198                request_id,
2199                1,
2200                1,
2201                None,
2202                link_rtt,
2203                6.0,
2204            )
2205            .map(|sender| vec![sender]);
2206        }
2207
2208        if metadata_overhead > constants::RESOURCE_MAX_EFFICIENT_SIZE {
2209            return Err(rns_core::resource::ResourceError::TooLarge);
2210        }
2211
2212        let first_payload_len = core::cmp::min(
2213            data.len(),
2214            constants::RESOURCE_MAX_EFFICIENT_SIZE - metadata_overhead,
2215        );
2216        let remaining = data.len().saturating_sub(first_payload_len);
2217        let total_segments = 1 + remaining.div_ceil(constants::RESOURCE_MAX_EFFICIENT_SIZE) as u64;
2218
2219        let enc_rng = std::cell::RefCell::new(rns_crypto::OsRng);
2220        let encrypt_fn = |plaintext: &[u8]| -> Vec<u8> {
2221            link.engine
2222                .encrypt(plaintext, &mut *enc_rng.borrow_mut())
2223                .unwrap_or_else(|_| plaintext.to_vec())
2224        };
2225
2226        let mut senders = Vec::new();
2227        let mut first = ResourceSender::new(
2228            &data[..first_payload_len],
2229            metadata,
2230            resource_sdu,
2231            &encrypt_fn,
2232            &Bzip2Compressor,
2233            rng,
2234            now,
2235            auto_compress,
2236            is_response,
2237            request_id.clone(),
2238            1,
2239            total_segments,
2240            None,
2241            link_rtt,
2242            6.0,
2243        )?;
2244        first.data_size = logical_size;
2245        let original_hash = first.original_hash;
2246        let has_metadata = metadata.is_some();
2247        senders.push(first);
2248
2249        let mut offset = first_payload_len;
2250        let mut segment_index = 2;
2251        while offset < data.len() {
2252            let end = core::cmp::min(offset + constants::RESOURCE_MAX_EFFICIENT_SIZE, data.len());
2253            let mut sender = ResourceSender::new(
2254                &data[offset..end],
2255                None,
2256                resource_sdu,
2257                &encrypt_fn,
2258                &Bzip2Compressor,
2259                rng,
2260                now,
2261                auto_compress,
2262                is_response,
2263                request_id.clone(),
2264                segment_index,
2265                total_segments,
2266                Some(original_hash),
2267                link_rtt,
2268                6.0,
2269            )?;
2270            sender.data_size = logical_size;
2271            sender.flags.has_metadata = has_metadata;
2272            senders.push(sender);
2273            segment_index += 1;
2274            offset = end;
2275        }
2276
2277        Ok(senders)
2278    }
2279
2280    fn read_stream_segment(
2281        stream: &mut OutgoingStreamTransfer,
2282        capacity: usize,
2283    ) -> Result<Vec<u8>, ResourceTransferError> {
2284        let length = stream.remaining.min(capacity as u64) as usize;
2285        let mut data = vec![0; length];
2286        stream
2287            .reader
2288            .read_exact(&mut data)
2289            .map_err(|error| ResourceTransferError::Source(error.to_string()))?;
2290        stream.remaining -= length as u64;
2291        if stream.remaining == 0 {
2292            let mut trailing = [0u8; 1];
2293            if stream
2294                .reader
2295                .read(&mut trailing)
2296                .map_err(|error| ResourceTransferError::Source(error.to_string()))?
2297                != 0
2298            {
2299                return Err(ResourceTransferError::Source(
2300                    "resource source exceeds declared length".into(),
2301                ));
2302            }
2303        }
2304        Ok(data)
2305    }
2306
2307    #[allow(clippy::too_many_arguments)]
2308    fn build_stream_segment(
2309        link: &ManagedLink,
2310        stream: &mut OutgoingStreamTransfer,
2311        metadata: Option<&[u8]>,
2312        segment_index: u64,
2313        total_segments: u64,
2314        original_hash: Option<[u8; 32]>,
2315        rng: &mut dyn Rng,
2316        now: f64,
2317    ) -> Result<ResourceSender, ResourceTransferError> {
2318        let metadata_overhead = metadata.map(|value| 3 + value.len()).unwrap_or(0);
2319        if metadata_overhead > constants::RESOURCE_MAX_EFFICIENT_SIZE {
2320            return Err(ResourceTransferError::Protocol(
2321                "resource metadata exceeds maximum segment size".into(),
2322            ));
2323        }
2324        let data = Self::read_stream_segment(
2325            stream,
2326            constants::RESOURCE_MAX_EFFICIENT_SIZE - metadata_overhead,
2327        )?;
2328        let enc_rng = std::cell::RefCell::new(rns_crypto::OsRng);
2329        let encrypt_fn = |plaintext: &[u8]| -> Vec<u8> {
2330            link.engine
2331                .encrypt(plaintext, &mut *enc_rng.borrow_mut())
2332                .unwrap_or_else(|_| plaintext.to_vec())
2333        };
2334        let mut sender = ResourceSender::new(
2335            &data,
2336            metadata,
2337            Self::resource_sdu_for_link(link),
2338            &encrypt_fn,
2339            &Bzip2Compressor,
2340            rng,
2341            now,
2342            stream.auto_compress,
2343            false,
2344            None,
2345            segment_index,
2346            total_segments,
2347            original_hash,
2348            link.engine.rtt().unwrap_or(1.0),
2349            6.0,
2350        )
2351        .map_err(|error| ResourceTransferError::Protocol(error.to_string()))?;
2352        sender.data_size = (stream.declared_length + stream.metadata_overhead) as usize;
2353        sender.flags.has_metadata = stream.metadata_overhead != 0;
2354        Ok(sender)
2355    }
2356
2357    fn start_resource_senders(
2358        link: &mut ManagedLink,
2359        mut senders: Vec<ResourceSender>,
2360        now: f64,
2361    ) -> Vec<ResourceAction> {
2362        if senders.is_empty() {
2363            return Vec::new();
2364        }
2365
2366        let mut first = senders.remove(0);
2367        let adv_actions = first.advertise(now);
2368
2369        if first.total_segments > 1 {
2370            let original_hash = first.original_hash;
2371            let split = OutgoingSplitTransfer {
2372                total_segments: first.total_segments,
2373                completed_segments: 0,
2374                current_segment_index: first.segment_index,
2375                current_sent_parts: 0,
2376                current_total_parts: first.total_parts(),
2377            };
2378            link.outgoing_splits.insert(original_hash, split);
2379        }
2380
2381        link.outgoing_resources.push(first);
2382        link.outgoing_resources.extend(senders);
2383        adv_actions
2384    }
2385
2386    /// Handle resource advertisement (CONTEXT_RESOURCE_ADV).
2387    fn handle_resource_adv(
2388        &mut self,
2389        link_id: &LinkId,
2390        adv_plaintext: &[u8],
2391        rng: &mut dyn Rng,
2392    ) -> Vec<LinkManagerAction> {
2393        let has_request_handlers = !self.request_handlers.is_empty()
2394            || !self.deferred_request_handlers.is_empty()
2395            || !self.management_paths.is_empty();
2396        let link = match self.links.get_mut(link_id) {
2397            Some(l) => l,
2398            None => return Vec::new(),
2399        };
2400
2401        let link_rtt = link.engine.rtt().unwrap_or(1.0);
2402        let resource_sdu = Self::resource_sdu_for_link(link);
2403        let now = time::now();
2404
2405        let receiver = match ResourceReceiver::from_advertisement(
2406            adv_plaintext,
2407            resource_sdu,
2408            link_rtt,
2409            now,
2410            None,
2411            None,
2412        ) {
2413            Ok(r) => r,
2414            Err(e) => {
2415                log::debug!("Resource ADV rejected: {}", e);
2416                let _ = link;
2417                return self.teardown_link(link_id);
2418            }
2419        };
2420
2421        let strategy = link.resource_strategy;
2422        let resource_hash = receiver.resource_hash.clone();
2423        let transfer_size = receiver.transfer_size;
2424        let has_metadata = receiver.has_metadata;
2425        let is_request = receiver.flags.is_request;
2426        let is_response = receiver.flags.is_response;
2427        let is_split = receiver.flags.split;
2428        let segment_index = receiver.segment_index;
2429        let total_segments = receiver.total_segments;
2430        let original_hash = match Self::resource_hash_key(&receiver.original_hash) {
2431            Some(key) => key,
2432            None => return Vec::new(),
2433        };
2434
2435        if !is_request && !is_response {
2436            let maximum = match &link.resource_receive_mode {
2437                ResourceReceiveMode::Memory { max_bytes } => Some(*max_bytes),
2438                ResourceReceiveMode::TemporaryFile { max_bytes, .. } => *max_bytes,
2439            };
2440            if maximum.is_some_and(|maximum| receiver.data_size > maximum) {
2441                let reject_actions = {
2442                    let mut receiver = receiver;
2443                    receiver.reject()
2444                };
2445                let _ = link;
2446                return self.process_resource_actions(link_id, reject_actions, rng);
2447            }
2448        }
2449
2450        if is_split && segment_index > 1 {
2451            let should_accept = link
2452                .incoming_splits
2453                .get(&original_hash)
2454                .is_some_and(|split| {
2455                    split.completed_segments + 1 == segment_index
2456                        && split.total_segments == total_segments
2457                });
2458
2459            if !should_accept {
2460                let reject_actions = {
2461                    let mut r = receiver;
2462                    r.reject()
2463                };
2464                let _ = link;
2465                return self.process_resource_actions(link_id, reject_actions, rng);
2466            }
2467
2468            let current_total_parts = receiver.total_parts;
2469            link.incoming_resources.push(receiver);
2470            let idx = link.incoming_resources.len() - 1;
2471            if let Some(split) = link.incoming_splits.get_mut(&original_hash) {
2472                split.current_segment_index = segment_index;
2473                split.current_received_parts = 0;
2474                split.current_total_parts = current_total_parts;
2475            }
2476            let resource_actions = link.incoming_resources[idx].accept(now);
2477            let _ = link;
2478            return self.process_resource_actions(link_id, resource_actions, rng);
2479        }
2480
2481        if is_request {
2482            // A request resource is only useful when this destination has a
2483            // request handler, and request IDs are truncated hashes.
2484            if !has_request_handlers || Self::response_request_id(&receiver.request_id).is_none() {
2485                return Vec::new();
2486            }
2487            if link
2488                .max_request_size
2489                .is_some_and(|limit| receiver.data_size > limit as u64)
2490            {
2491                log::debug!(
2492                    "ignored request with excessive size {} bytes on link {:02x?}",
2493                    receiver.data_size,
2494                    &link_id[..4]
2495                );
2496                let reject_actions = {
2497                    let mut receiver = receiver;
2498                    receiver.reject()
2499                };
2500                let _ = link;
2501                return self.process_resource_actions(link_id, reject_actions, rng);
2502            }
2503            if is_split {
2504                link.incoming_splits.insert(
2505                    original_hash,
2506                    IncomingSplitTransfer {
2507                        total_segments,
2508                        completed_segments: 0,
2509                        current_segment_index: segment_index,
2510                        current_received_parts: 0,
2511                        current_total_parts: receiver.total_parts,
2512                        storage: IncomingSplitStorage::Memory(Vec::new()),
2513                        metadata: None,
2514                        is_request: true,
2515                        is_response: false,
2516                    },
2517                );
2518            }
2519            link.incoming_resources.push(receiver);
2520            let idx = link.incoming_resources.len() - 1;
2521            let resource_actions = link.incoming_resources[idx].accept(now);
2522            let _ = link;
2523            return self.process_resource_actions(link_id, resource_actions, rng);
2524        }
2525
2526        if is_response {
2527            // Response resources bypass the application acceptance strategy —
2528            // they are answers to pending requests, not independent resources.
2529            let Some(request_id) = Self::response_request_id(&receiver.request_id) else {
2530                return Vec::new();
2531            };
2532            let Some(pending) = link.pending_requests.get(&request_id) else {
2533                return Vec::new();
2534            };
2535            if let Some(maximum) = pending.max_response_size {
2536                if receiver.data_size > maximum as u64 {
2537                    let response_size = receiver.data_size;
2538                    link.pending_requests.remove(&request_id);
2539                    log::debug!(
2540                        "rejected response with excessive size {} bytes on link {:02x?}",
2541                        response_size,
2542                        &link_id[..4]
2543                    );
2544                    let reject_actions = {
2545                        let mut receiver = receiver;
2546                        receiver.reject()
2547                    };
2548                    let _ = link;
2549                    let mut actions = self.process_resource_actions(link_id, reject_actions, rng);
2550                    actions.push(LinkManagerAction::RequestFailed {
2551                        link_id: *link_id,
2552                        request_id,
2553                        reason: RequestFailure::ResponseTooLarge {
2554                            size: response_size,
2555                            maximum,
2556                        },
2557                    });
2558                    return actions;
2559                }
2560            }
2561            if is_split {
2562                link.incoming_splits.insert(
2563                    original_hash,
2564                    IncomingSplitTransfer {
2565                        total_segments,
2566                        completed_segments: 0,
2567                        current_segment_index: segment_index,
2568                        current_received_parts: 0,
2569                        current_total_parts: receiver.total_parts,
2570                        storage: IncomingSplitStorage::Memory(Vec::new()),
2571                        metadata: None,
2572                        is_request: false,
2573                        is_response,
2574                    },
2575                );
2576            }
2577            link.incoming_resources.push(receiver);
2578            let idx = link.incoming_resources.len() - 1;
2579            let resource_actions = link.incoming_resources[idx].accept(now);
2580            let _ = link;
2581            return self.process_resource_actions(link_id, resource_actions, rng);
2582        }
2583
2584        match strategy {
2585            ResourceStrategy::AcceptNone => {
2586                // Reject: send RCL
2587                let reject_actions = {
2588                    let mut r = receiver;
2589                    r.reject()
2590                };
2591                self.process_resource_actions(link_id, reject_actions, rng)
2592            }
2593            ResourceStrategy::AcceptAll => {
2594                if is_split {
2595                    let storage =
2596                        match Self::incoming_storage(&link.resource_receive_mode, &original_hash) {
2597                            Ok(storage) => storage,
2598                            Err(error) => {
2599                                let reject_actions = {
2600                                    let mut receiver = receiver;
2601                                    receiver.reject()
2602                                };
2603                                let _ = link;
2604                                let mut actions =
2605                                    self.process_resource_actions(link_id, reject_actions, rng);
2606                                actions.push(LinkManagerAction::ResourceFailed {
2607                                    link_id: *link_id,
2608                                    error: format!("resource storage failed: {error}"),
2609                                });
2610                                return actions;
2611                            }
2612                        };
2613                    link.incoming_splits.insert(
2614                        original_hash,
2615                        IncomingSplitTransfer {
2616                            total_segments,
2617                            completed_segments: 0,
2618                            current_segment_index: segment_index,
2619                            current_received_parts: 0,
2620                            current_total_parts: receiver.total_parts,
2621                            storage,
2622                            metadata: None,
2623                            is_request: false,
2624                            is_response,
2625                        },
2626                    );
2627                }
2628                link.incoming_resources.push(receiver);
2629                let idx = link.incoming_resources.len() - 1;
2630                let resource_actions = link.incoming_resources[idx].accept(now);
2631                let _ = link;
2632                self.process_resource_actions(link_id, resource_actions, rng)
2633            }
2634            ResourceStrategy::AcceptApp => {
2635                link.incoming_resources.push(receiver);
2636                // Query application callback
2637                vec![LinkManagerAction::ResourceAcceptQuery {
2638                    link_id: *link_id,
2639                    resource_hash,
2640                    transfer_size,
2641                    has_metadata,
2642                }]
2643            }
2644        }
2645    }
2646
2647    /// Accept or reject a pending resource (for AcceptApp strategy).
2648    pub fn accept_resource(
2649        &mut self,
2650        link_id: &LinkId,
2651        resource_hash: &[u8],
2652        accept: bool,
2653        rng: &mut dyn Rng,
2654    ) -> Vec<LinkManagerAction> {
2655        let link = match self.links.get_mut(link_id) {
2656            Some(l) => l,
2657            None => return Vec::new(),
2658        };
2659
2660        let now = time::now();
2661        let idx = link
2662            .incoming_resources
2663            .iter()
2664            .position(|r| r.resource_hash == resource_hash);
2665        let idx = match idx {
2666            Some(i) => i,
2667            None => return Vec::new(),
2668        };
2669
2670        if accept && link.incoming_resources[idx].flags.split {
2671            if let Some(original_hash) =
2672                Self::resource_hash_key(&link.incoming_resources[idx].original_hash)
2673            {
2674                if !link.incoming_splits.contains_key(&original_hash) {
2675                    let storage =
2676                        match Self::incoming_storage(&link.resource_receive_mode, &original_hash) {
2677                            Ok(storage) => storage,
2678                            Err(error) => {
2679                                let resource_actions = link.incoming_resources[idx].reject();
2680                                let _ = link;
2681                                let mut actions =
2682                                    self.process_resource_actions(link_id, resource_actions, rng);
2683                                actions.push(LinkManagerAction::ResourceFailed {
2684                                    link_id: *link_id,
2685                                    error: format!("resource storage failed: {error}"),
2686                                });
2687                                return actions;
2688                            }
2689                        };
2690                    link.incoming_splits.insert(
2691                        original_hash,
2692                        IncomingSplitTransfer {
2693                            total_segments: link.incoming_resources[idx].total_segments,
2694                            completed_segments: 0,
2695                            current_segment_index: link.incoming_resources[idx].segment_index,
2696                            current_received_parts: 0,
2697                            current_total_parts: link.incoming_resources[idx].total_parts,
2698                            storage,
2699                            metadata: None,
2700                            is_request: link.incoming_resources[idx].flags.is_request,
2701                            is_response: link.incoming_resources[idx].flags.is_response,
2702                        },
2703                    );
2704                }
2705            }
2706        }
2707
2708        let resource_actions = if accept {
2709            link.incoming_resources[idx].accept(now)
2710        } else {
2711            link.incoming_resources[idx].reject()
2712        };
2713
2714        let _ = link;
2715        self.process_resource_actions(link_id, resource_actions, rng)
2716    }
2717
2718    /// Handle resource request (CONTEXT_RESOURCE_REQ) — feed to sender.
2719    fn handle_resource_req(
2720        &mut self,
2721        link_id: &LinkId,
2722        plaintext: &[u8],
2723        rng: &mut dyn Rng,
2724    ) -> Vec<LinkManagerAction> {
2725        let link = match self.links.get_mut(link_id) {
2726            Some(l) => l,
2727            None => return Vec::new(),
2728        };
2729
2730        let now = time::now();
2731        let mut all_actions = Vec::new();
2732        let mut progress_update = None;
2733        let mut progress_stream_hash = None;
2734        for sender in &mut link.outgoing_resources {
2735            if sender.flags.split && sender.status == rns_core::resource::ResourceStatus::Queued {
2736                continue;
2737            }
2738            let before_sent = sender.sent_parts;
2739            let resource_actions = sender.handle_request(plaintext, now);
2740            if !resource_actions.is_empty() {
2741                if sender.sent_parts != before_sent {
2742                    progress_stream_hash = Some(sender.original_hash);
2743                    if sender.flags.split {
2744                        if let Some(split) = link.outgoing_splits.get_mut(&sender.original_hash) {
2745                            split.current_segment_index = sender.segment_index;
2746                            split.current_sent_parts = sender.sent_parts;
2747                            split.current_total_parts = sender.total_parts();
2748                            progress_update =
2749                                Some(Self::outgoing_split_progress(split, sender.sdu));
2750                        }
2751                    } else {
2752                        progress_update = Some((sender.sent_parts, sender.total_parts()));
2753                    }
2754                }
2755                all_actions.extend(resource_actions);
2756                break;
2757            }
2758        }
2759
2760        let _ = link;
2761        let mut out = self.process_resource_actions(link_id, all_actions, rng);
2762        if let Some((received, total)) = progress_update {
2763            if let Some((stream, transferred)) = progress_stream_hash.and_then(|hash| {
2764                self.links.get(link_id).and_then(|link| {
2765                    link.outgoing_streams.get(&hash).map(|stream| {
2766                        let transferred = if total == 0 {
2767                            0
2768                        } else {
2769                            stream.declared_length.saturating_mul(received as u64) / total as u64
2770                        };
2771                        (stream, transferred.min(stream.declared_length))
2772                    })
2773                })
2774            }) {
2775                out.push(LinkManagerAction::ResourceStreamProgress {
2776                    link_id: *link_id,
2777                    transfer_id: stream.transfer_id,
2778                    transferred,
2779                    total: stream.declared_length,
2780                });
2781            } else {
2782                out.push(LinkManagerAction::ResourceProgress {
2783                    link_id: *link_id,
2784                    received,
2785                    total,
2786                });
2787            }
2788        }
2789        out
2790    }
2791
2792    /// Handle resource HMU (CONTEXT_RESOURCE_HMU) — feed to receiver.
2793    fn handle_resource_hmu(
2794        &mut self,
2795        link_id: &LinkId,
2796        plaintext: &[u8],
2797        rng: &mut dyn Rng,
2798    ) -> Vec<LinkManagerAction> {
2799        let link = match self.links.get_mut(link_id) {
2800            Some(l) => l,
2801            None => return Vec::new(),
2802        };
2803
2804        let now = time::now();
2805        let mut all_actions = Vec::new();
2806        for receiver in &mut link.incoming_resources {
2807            let resource_actions = receiver.handle_hashmap_update(plaintext, now);
2808            if !resource_actions.is_empty() {
2809                all_actions.extend(resource_actions);
2810                break;
2811            }
2812        }
2813
2814        let _ = link;
2815        self.process_resource_actions(link_id, all_actions, rng)
2816    }
2817
2818    /// Handle resource part (CONTEXT_RESOURCE) — feed raw to receiver.
2819    fn handle_resource_part(
2820        &mut self,
2821        link_id: &LinkId,
2822        raw_data: &[u8],
2823        rng: &mut dyn Rng,
2824    ) -> Vec<LinkManagerAction> {
2825        let link = match self.links.get_mut(link_id) {
2826            Some(l) => l,
2827            None => return Vec::new(),
2828        };
2829
2830        let now = time::now();
2831        let resource_sdu = Self::resource_sdu_for_link(link);
2832        let mut all_actions = Vec::new();
2833        let mut assemble_idx = None;
2834        let mut assembled_is_request = false;
2835        let mut assembled_is_response = false;
2836        let mut request_request_id = None;
2837        let mut response_request_id = None;
2838        let mut completed_file = None;
2839        let mut storage_failure = None;
2840
2841        for (idx, receiver) in link.incoming_resources.iter_mut().enumerate() {
2842            if receiver.status >= rns_core::resource::ResourceStatus::Complete {
2843                continue;
2844            }
2845            let resource_actions = receiver.receive_part(raw_data, now);
2846            if !resource_actions.is_empty() {
2847                if receiver.received_count == receiver.total_parts {
2848                    assemble_idx = Some(idx);
2849                }
2850                if receiver.flags.split {
2851                    if let Some(key) = Self::resource_hash_key(&receiver.original_hash) {
2852                        if let Some(split) = link.incoming_splits.get_mut(&key) {
2853                            split.current_segment_index = receiver.segment_index;
2854                            split.current_received_parts = receiver.received_count;
2855                            split.current_total_parts = receiver.total_parts;
2856                            let (received, total) =
2857                                Self::incoming_split_progress(split, resource_sdu);
2858                            for action in resource_actions {
2859                                match action {
2860                                    ResourceAction::ProgressUpdate { .. } => {
2861                                        all_actions.push(ResourceAction::ProgressUpdate {
2862                                            received,
2863                                            total,
2864                                        });
2865                                    }
2866                                    other => all_actions.push(other),
2867                                }
2868                            }
2869                        } else {
2870                            all_actions.extend(resource_actions);
2871                        }
2872                    } else {
2873                        all_actions.extend(resource_actions);
2874                    }
2875                } else {
2876                    all_actions.extend(resource_actions);
2877                }
2878                break;
2879            }
2880        }
2881
2882        if let Some(idx) = assemble_idx {
2883            let split_key = if link.incoming_resources[idx].flags.split {
2884                Self::resource_hash_key(&link.incoming_resources[idx].original_hash)
2885            } else {
2886                None
2887            };
2888            let split_segment_index = link.incoming_resources[idx].segment_index;
2889            let split_segment_total = link.incoming_resources[idx].total_segments;
2890            let split_segment_parts = link.incoming_resources[idx].total_parts;
2891            let split_is_request = link.incoming_resources[idx].flags.is_request;
2892            let split_is_response = link.incoming_resources[idx].flags.is_response;
2893            let resource_original_hash =
2894                Self::resource_hash_key(&link.incoming_resources[idx].original_hash)
2895                    .unwrap_or([0; 32]);
2896            let resource_request_id =
2897                Self::response_request_id(&link.incoming_resources[idx].request_id);
2898            if split_is_request {
2899                request_request_id = resource_request_id;
2900            } else if split_is_response {
2901                response_request_id = resource_request_id;
2902            }
2903            let decrypt_fn = |ciphertext: &[u8]| -> Result<Vec<u8>, ()> {
2904                link.engine.decrypt(ciphertext).map_err(|_| ())
2905            };
2906            let mut assemble_actions =
2907                link.incoming_resources[idx].assemble(&decrypt_fn, &Bzip2Compressor);
2908            assembled_is_request = split_is_request;
2909            assembled_is_response = split_is_response;
2910
2911            if let Some(key) = split_key {
2912                let mut converted_actions = Vec::new();
2913                let mut segment_data = None;
2914                let mut segment_metadata = None;
2915                for action in assemble_actions {
2916                    match action {
2917                        ResourceAction::DataReceived { data, metadata } => {
2918                            segment_data = Some(data);
2919                            segment_metadata = metadata;
2920                        }
2921                        ResourceAction::Completed => {}
2922                        other => converted_actions.push(other),
2923                    }
2924                }
2925
2926                if let Some(data) = segment_data {
2927                    if let Some(split) = link.incoming_splits.get_mut(&key) {
2928                        let write_result = match &mut split.storage {
2929                            IncomingSplitStorage::Memory(buffer) => {
2930                                buffer.extend_from_slice(&data);
2931                                Ok(())
2932                            }
2933                            IncomingSplitStorage::File { file, size, .. } => {
2934                                file.write_all(&data).map(|()| *size += data.len() as u64)
2935                            }
2936                        };
2937                        if let Err(error) = write_result {
2938                            storage_failure = Some(error.to_string());
2939                        }
2940                        if segment_metadata.is_some() {
2941                            split.metadata = segment_metadata;
2942                        }
2943                        split.completed_segments = split_segment_index;
2944                        split.current_segment_index = split_segment_index;
2945                        split.current_received_parts = split_segment_parts;
2946                        split.current_total_parts = split_segment_parts;
2947                    }
2948
2949                    if storage_failure.is_none() && split_segment_index == split_segment_total {
2950                        if let Some(split) = link.incoming_splits.remove(&key) {
2951                            assembled_is_request = split.is_request;
2952                            assembled_is_response = split.is_response;
2953                            match split.storage {
2954                                IncomingSplitStorage::Memory(data) => {
2955                                    converted_actions.push(ResourceAction::DataReceived {
2956                                        data,
2957                                        metadata: split.metadata,
2958                                    });
2959                                }
2960                                IncomingSplitStorage::File { file, path, size } => {
2961                                    match file.sync_all() {
2962                                        Ok(()) => {
2963                                            completed_file = Some(ReceivedResourceFile::new(
2964                                                path,
2965                                                key,
2966                                                size,
2967                                                split.metadata,
2968                                            ));
2969                                        }
2970                                        Err(error) => storage_failure = Some(error.to_string()),
2971                                    }
2972                                }
2973                            }
2974                            converted_actions.push(ResourceAction::Completed);
2975                        }
2976                    }
2977                    if storage_failure.is_some() {
2978                        link.incoming_splits.remove(&key);
2979                        converted_actions.clear();
2980                        converted_actions.push(ResourceAction::SendCancelReceiver(Vec::new()));
2981                    }
2982                }
2983
2984                assemble_actions = converted_actions;
2985            } else if !split_is_request
2986                && !split_is_response
2987                && matches!(
2988                    link.resource_receive_mode,
2989                    ResourceReceiveMode::TemporaryFile { .. }
2990                )
2991            {
2992                let directory = match &link.resource_receive_mode {
2993                    ResourceReceiveMode::TemporaryFile { directory, .. } => directory.clone(),
2994                    ResourceReceiveMode::Memory { .. } => unreachable!(),
2995                };
2996                let mut converted_actions = Vec::new();
2997                for action in assemble_actions {
2998                    match action {
2999                        ResourceAction::DataReceived { data, metadata } => {
3000                            match crate::resource::create_receive_file(
3001                                &directory,
3002                                &resource_original_hash,
3003                            )
3004                            .and_then(|(mut file, path)| {
3005                                file.write_all(&data)?;
3006                                file.sync_all()?;
3007                                Ok(ReceivedResourceFile::new(
3008                                    path,
3009                                    resource_original_hash,
3010                                    data.len() as u64,
3011                                    metadata,
3012                                ))
3013                            }) {
3014                                Ok(resource) => completed_file = Some(resource),
3015                                Err(error) => storage_failure = Some(error.to_string()),
3016                            }
3017                        }
3018                        other => converted_actions.push(other),
3019                    }
3020                }
3021                if storage_failure.is_some() {
3022                    converted_actions.retain(|action| {
3023                        !matches!(
3024                            action,
3025                            ResourceAction::SendProof(_) | ResourceAction::Completed
3026                        )
3027                    });
3028                    converted_actions.push(ResourceAction::SendCancelReceiver(Vec::new()));
3029                }
3030                assemble_actions = converted_actions;
3031            }
3032            all_actions.extend(assemble_actions);
3033        }
3034
3035        let _ = link;
3036        let mut out = self.process_resource_actions(link_id, all_actions, rng);
3037        if let Some(resource) = completed_file {
3038            out.push(LinkManagerAction::ResourceFileReceived {
3039                link_id: *link_id,
3040                resource,
3041            });
3042        }
3043        if let Some(error) = storage_failure {
3044            out.push(LinkManagerAction::ResourceFailed {
3045                link_id: *link_id,
3046                error: format!("resource storage failed: {error}"),
3047            });
3048        }
3049
3050        if assembled_is_request {
3051            let mut converted = Vec::new();
3052            for action in out {
3053                match action {
3054                    LinkManagerAction::ResourceReceived { data, .. } => {
3055                        if let Some(request_id) = request_request_id {
3056                            converted.extend(self.handle_request(link_id, &data, request_id, rng));
3057                        }
3058                    }
3059                    LinkManagerAction::ResourceAcceptQuery { .. } => {
3060                        // Request resources bypass application resource acceptance.
3061                    }
3062                    other => converted.push(other),
3063                }
3064            }
3065            out = converted;
3066        } else if assembled_is_response {
3067            let mut converted = Vec::new();
3068            for action in out {
3069                match action {
3070                    LinkManagerAction::ResourceReceived { data, metadata, .. } => {
3071                        converted.extend(self.handle_response(
3072                            link_id,
3073                            &data,
3074                            metadata,
3075                            response_request_id,
3076                        ));
3077                    }
3078                    LinkManagerAction::ResourceAcceptQuery { .. } => {
3079                        // Response resources bypass application acceptance
3080                    }
3081                    other => converted.push(other),
3082                }
3083            }
3084            out = converted;
3085        }
3086
3087        out
3088    }
3089
3090    /// Handle resource proof (CONTEXT_RESOURCE_PRF) — feed to sender.
3091    fn handle_resource_prf(
3092        &mut self,
3093        link_id: &LinkId,
3094        plaintext: &[u8],
3095        rng: &mut dyn Rng,
3096    ) -> Vec<LinkManagerAction> {
3097        let link = match self.links.get_mut(link_id) {
3098            Some(l) => l,
3099            None => return Vec::new(),
3100        };
3101
3102        let now = time::now();
3103        let mut result_actions = Vec::new();
3104        let mut completed_sender = None;
3105        let mut completed_request_id = None;
3106        let mut failed_split = None;
3107        let mut failed_request_id = None;
3108        let proof_hash = plaintext.get(..32);
3109        for sender in &mut link.outgoing_resources {
3110            if proof_hash.is_some_and(|hash| hash != sender.resource_hash.as_slice()) {
3111                continue;
3112            }
3113            let resource_actions = sender.handle_proof(plaintext, now);
3114            if !resource_actions.is_empty() {
3115                if resource_actions
3116                    .iter()
3117                    .any(|action| matches!(action, ResourceAction::Completed))
3118                {
3119                    completed_sender = Some((
3120                        sender.original_hash,
3121                        sender.segment_index,
3122                        sender.total_segments,
3123                        sender.total_parts(),
3124                    ));
3125                    if sender.flags.is_request && sender.segment_index == sender.total_segments {
3126                        completed_request_id = Self::response_request_id(&sender.request_id);
3127                    }
3128                }
3129                if resource_actions
3130                    .iter()
3131                    .any(|action| matches!(action, ResourceAction::Failed(_)))
3132                {
3133                    failed_split = Some(sender.original_hash);
3134                }
3135                if resource_actions
3136                    .iter()
3137                    .any(|action| matches!(action, ResourceAction::Failed(_)))
3138                    && sender.flags.is_request
3139                {
3140                    failed_request_id = Self::response_request_id(&sender.request_id);
3141                }
3142                result_actions.extend(resource_actions);
3143                break;
3144            }
3145        }
3146
3147        // Convert to LinkManagerActions
3148        let mut actions = Vec::new();
3149        let mut advertise_next = None;
3150        let mut stream_failure = None;
3151        for ra in result_actions {
3152            match ra {
3153                ResourceAction::Completed => {
3154                    if let Some((original_hash, segment_index, total_segments, total_parts)) =
3155                        completed_sender
3156                    {
3157                        if total_segments > 1 && segment_index < total_segments {
3158                            if let Some(split) = link.outgoing_splits.get_mut(&original_hash) {
3159                                split.completed_segments = segment_index;
3160                                split.current_segment_index = segment_index;
3161                                split.current_sent_parts = total_parts;
3162                                split.current_total_parts = total_parts;
3163                            }
3164                            if let Some(next) = link.outgoing_resources.iter_mut().find(|s| {
3165                                s.flags.split
3166                                    && s.original_hash == original_hash
3167                                    && s.segment_index == segment_index + 1
3168                            }) {
3169                                if let Some(split) = link.outgoing_splits.get_mut(&original_hash) {
3170                                    split.current_segment_index = next.segment_index;
3171                                    split.current_sent_parts = 0;
3172                                    split.current_total_parts = next.total_parts();
3173                                }
3174                                advertise_next = Some(next.advertise(now));
3175                            } else if let Some(mut stream) =
3176                                link.outgoing_streams.remove(&original_hash)
3177                            {
3178                                match Self::build_stream_segment(
3179                                    link,
3180                                    &mut stream,
3181                                    None,
3182                                    segment_index + 1,
3183                                    total_segments,
3184                                    Some(original_hash),
3185                                    rng,
3186                                    now,
3187                                ) {
3188                                    Ok(mut next) => {
3189                                        if let Some(split) =
3190                                            link.outgoing_splits.get_mut(&original_hash)
3191                                        {
3192                                            split.current_segment_index = next.segment_index;
3193                                            split.current_sent_parts = 0;
3194                                            split.current_total_parts = next.total_parts();
3195                                        }
3196                                        advertise_next = Some(next.advertise(now));
3197                                        link.outgoing_resources.push(next);
3198                                        link.outgoing_streams.insert(original_hash, stream);
3199                                    }
3200                                    Err(error) => {
3201                                        stream_failure = Some((stream.transfer_id, error));
3202                                        link.outgoing_splits.remove(&original_hash);
3203                                    }
3204                                }
3205                            }
3206                        } else {
3207                            link.outgoing_splits.remove(&original_hash);
3208                            if let Some(stream) = link.outgoing_streams.remove(&original_hash) {
3209                                actions.push(LinkManagerAction::ResourceStreamCompleted {
3210                                    link_id: *link_id,
3211                                    transfer_id: stream.transfer_id,
3212                                });
3213                            } else {
3214                                actions.push(LinkManagerAction::ResourceCompleted {
3215                                    link_id: *link_id,
3216                                });
3217                            }
3218                        }
3219                    } else {
3220                        actions.push(LinkManagerAction::ResourceCompleted { link_id: *link_id });
3221                    }
3222                }
3223                ResourceAction::Failed(e) => {
3224                    if let Some(original_hash) = failed_split {
3225                        link.outgoing_splits.remove(&original_hash);
3226                        if let Some(stream) = link.outgoing_streams.remove(&original_hash) {
3227                            actions.push(LinkManagerAction::ResourceStreamFailed {
3228                                link_id: *link_id,
3229                                transfer_id: stream.transfer_id,
3230                                error: ResourceTransferError::Protocol(e.to_string()),
3231                            });
3232                            continue;
3233                        }
3234                    }
3235                    actions.push(LinkManagerAction::ResourceFailed {
3236                        link_id: *link_id,
3237                        error: format!("{}", e),
3238                    });
3239                }
3240                _ => {}
3241            }
3242        }
3243
3244        if let Some((transfer_id, error)) = stream_failure {
3245            actions.push(LinkManagerAction::ResourceStreamFailed {
3246                link_id: *link_id,
3247                transfer_id,
3248                error,
3249            });
3250        }
3251
3252        if let Some(request_id) = completed_request_id {
3253            let deadline = Self::request_response_deadline(link, now);
3254            if let Some(entry) = link.pending_requests.get_mut(&request_id) {
3255                entry.deadline = Some(deadline);
3256            }
3257        }
3258        if let Some(request_id) = failed_request_id {
3259            link.pending_requests.remove(&request_id);
3260        }
3261
3262        // Clean up completed/failed senders
3263        link.outgoing_resources
3264            .retain(|s| s.status < rns_core::resource::ResourceStatus::Complete);
3265
3266        let _ = link;
3267        if let Some(next_actions) = advertise_next {
3268            actions.extend(self.process_resource_actions(link_id, next_actions, rng));
3269        }
3270
3271        actions
3272    }
3273
3274    /// Handle cancel from initiator (CONTEXT_RESOURCE_ICL).
3275    fn handle_resource_icl(&mut self, link_id: &LinkId) -> Vec<LinkManagerAction> {
3276        let link = match self.links.get_mut(link_id) {
3277            Some(l) => l,
3278            None => return Vec::new(),
3279        };
3280
3281        let mut actions = Vec::new();
3282        for receiver in &mut link.incoming_resources {
3283            let ra = receiver.handle_cancel();
3284            for a in ra {
3285                if let ResourceAction::Failed(ref e) = a {
3286                    actions.push(LinkManagerAction::ResourceFailed {
3287                        link_id: *link_id,
3288                        error: format!("{}", e),
3289                    });
3290                }
3291            }
3292        }
3293        link.incoming_resources
3294            .retain(|r| r.status < rns_core::resource::ResourceStatus::Complete);
3295        link.incoming_splits.clear();
3296        actions
3297    }
3298
3299    /// Handle cancel from receiver (CONTEXT_RESOURCE_RCL).
3300    fn handle_resource_rcl(&mut self, link_id: &LinkId) -> Vec<LinkManagerAction> {
3301        let link = match self.links.get_mut(link_id) {
3302            Some(l) => l,
3303            None => return Vec::new(),
3304        };
3305
3306        let request_ids: Vec<[u8; 16]> = link
3307            .outgoing_resources
3308            .iter()
3309            .filter(|sender| sender.flags.is_request)
3310            .filter_map(|sender| Self::response_request_id(&sender.request_id))
3311            .collect();
3312        let mut actions = Vec::new();
3313        for sender in &mut link.outgoing_resources {
3314            let ra = sender.handle_reject();
3315            for a in ra {
3316                if let ResourceAction::Failed(ref e) = a {
3317                    actions.push(LinkManagerAction::ResourceFailed {
3318                        link_id: *link_id,
3319                        error: format!("{}", e),
3320                    });
3321                }
3322            }
3323        }
3324        link.outgoing_resources
3325            .retain(|s| s.status < rns_core::resource::ResourceStatus::Complete);
3326        link.outgoing_splits.clear();
3327        actions.extend(link.outgoing_streams.drain().map(|(_, stream)| {
3328            LinkManagerAction::ResourceStreamFailed {
3329                link_id: *link_id,
3330                transfer_id: stream.transfer_id,
3331                error: ResourceTransferError::Cancelled,
3332            }
3333        }));
3334        for request_id in request_ids {
3335            link.pending_requests.remove(&request_id);
3336        }
3337        actions
3338    }
3339
3340    fn resource_link_is_active(&self, link_id: &LinkId) -> bool {
3341        self.links
3342            .get(link_id)
3343            .is_some_and(|link| link.engine.state() == LinkState::Active)
3344    }
3345
3346    fn abort_resources_on_inactive_link(&mut self, link_id: &LinkId) -> Vec<LinkManagerAction> {
3347        let Some(link) = self.links.get_mut(link_id) else {
3348            return Vec::new();
3349        };
3350        for sender in &mut link.outgoing_resources {
3351            let _ = sender.cancel();
3352        }
3353        for receiver in &mut link.incoming_resources {
3354            let _ = receiver.cancel();
3355        }
3356        link.outgoing_resources.clear();
3357        link.incoming_resources.clear();
3358        link.outgoing_splits.clear();
3359        let actions = link
3360            .outgoing_streams
3361            .drain()
3362            .map(|(_, stream)| LinkManagerAction::ResourceStreamFailed {
3363                link_id: *link_id,
3364                transfer_id: stream.transfer_id,
3365                error: ResourceTransferError::Cancelled,
3366            })
3367            .collect();
3368        link.incoming_splits.clear();
3369        link.pending_requests.clear();
3370        actions
3371    }
3372
3373    /// Convert ResourceActions to LinkManagerActions.
3374    fn process_resource_actions(
3375        &mut self,
3376        link_id: &LinkId,
3377        actions: Vec<ResourceAction>,
3378        rng: &mut dyn Rng,
3379    ) -> Vec<LinkManagerAction> {
3380        let mut result = Vec::new();
3381        for action in actions {
3382            let requires_active_link = matches!(
3383                &action,
3384                ResourceAction::SendAdvertisement(_)
3385                    | ResourceAction::SendPart(_)
3386                    | ResourceAction::SendRequest(_)
3387                    | ResourceAction::SendHmu(_)
3388                    | ResourceAction::SendProof(_)
3389            );
3390            if requires_active_link && !self.resource_link_is_active(link_id) {
3391                result.extend(self.abort_resources_on_inactive_link(link_id));
3392                continue;
3393            }
3394
3395            match action {
3396                ResourceAction::SendAdvertisement(data) => {
3397                    // Link-encrypt and send as CONTEXT_RESOURCE_ADV
3398                    let encrypted = self
3399                        .links
3400                        .get(link_id)
3401                        .and_then(|link| link.engine.encrypt(&data, rng).ok());
3402                    if let Some(encrypted) = encrypted {
3403                        result.extend(self.build_link_packet(
3404                            link_id,
3405                            constants::CONTEXT_RESOURCE_ADV,
3406                            &encrypted,
3407                        ));
3408                    }
3409                }
3410                ResourceAction::SendPart(data) => {
3411                    // Parts are NOT link-encrypted — send raw as CONTEXT_RESOURCE
3412                    result.extend(self.build_link_packet(
3413                        link_id,
3414                        constants::CONTEXT_RESOURCE,
3415                        &data,
3416                    ));
3417                }
3418                ResourceAction::SendRequest(data) => {
3419                    let encrypted = self
3420                        .links
3421                        .get(link_id)
3422                        .and_then(|link| link.engine.encrypt(&data, rng).ok());
3423                    if let Some(encrypted) = encrypted {
3424                        result.extend(self.build_link_packet(
3425                            link_id,
3426                            constants::CONTEXT_RESOURCE_REQ,
3427                            &encrypted,
3428                        ));
3429                    }
3430                }
3431                ResourceAction::SendHmu(data) => {
3432                    let encrypted = self
3433                        .links
3434                        .get(link_id)
3435                        .and_then(|link| link.engine.encrypt(&data, rng).ok());
3436                    if let Some(encrypted) = encrypted {
3437                        result.extend(self.build_link_packet(
3438                            link_id,
3439                            constants::CONTEXT_RESOURCE_HMU,
3440                            &encrypted,
3441                        ));
3442                    }
3443                }
3444                ResourceAction::SendProof(data) => {
3445                    let flags = PacketFlags {
3446                        header_type: constants::HEADER_1,
3447                        context_flag: constants::FLAG_UNSET,
3448                        transport_type: constants::TRANSPORT_BROADCAST,
3449                        destination_type: constants::DESTINATION_LINK,
3450                        packet_type: constants::PACKET_TYPE_PROOF,
3451                    };
3452                    if let Ok((raw, _packet_hash)) = RawPacket::pack_raw_with_hash(
3453                        flags,
3454                        0,
3455                        link_id,
3456                        None,
3457                        constants::CONTEXT_RESOURCE_PRF,
3458                        &data,
3459                    ) {
3460                        result.push(LinkManagerAction::SendPacket {
3461                            raw,
3462                            dest_type: constants::DESTINATION_LINK,
3463                            attached_interface: None,
3464                        });
3465                    }
3466                }
3467                ResourceAction::SendCancelInitiator(data) => {
3468                    let encrypted = self
3469                        .links
3470                        .get(link_id)
3471                        .filter(|link| link.engine.state() == LinkState::Active)
3472                        .and_then(|link| link.engine.encrypt(&data, rng).ok());
3473                    if let Some(encrypted) = encrypted {
3474                        result.extend(self.build_link_packet(
3475                            link_id,
3476                            constants::CONTEXT_RESOURCE_ICL,
3477                            &encrypted,
3478                        ));
3479                    }
3480                }
3481                ResourceAction::SendCancelReceiver(data) => {
3482                    let encrypted = self
3483                        .links
3484                        .get(link_id)
3485                        .filter(|link| link.engine.state() == LinkState::Active)
3486                        .and_then(|link| link.engine.encrypt(&data, rng).ok());
3487                    if let Some(encrypted) = encrypted {
3488                        result.extend(self.build_link_packet(
3489                            link_id,
3490                            constants::CONTEXT_RESOURCE_RCL,
3491                            &encrypted,
3492                        ));
3493                    }
3494                }
3495                ResourceAction::DataReceived { data, metadata } => {
3496                    result.push(LinkManagerAction::ResourceReceived {
3497                        link_id: *link_id,
3498                        data,
3499                        metadata,
3500                    });
3501                }
3502                ResourceAction::Completed => {
3503                    result.push(LinkManagerAction::ResourceCompleted { link_id: *link_id });
3504                }
3505                ResourceAction::Failed(e) => {
3506                    result.push(LinkManagerAction::ResourceFailed {
3507                        link_id: *link_id,
3508                        error: format!("{}", e),
3509                    });
3510                }
3511                ResourceAction::TeardownLink => {
3512                    let teardown_actions = match self.links.get_mut(link_id) {
3513                        Some(link) => link.engine.handle_teardown(),
3514                        None => Vec::new(),
3515                    };
3516                    result.extend(self.process_link_actions(link_id, &teardown_actions));
3517                }
3518                ResourceAction::ProgressUpdate { received, total } => {
3519                    result.push(LinkManagerAction::ResourceProgress {
3520                        link_id: *link_id,
3521                        received,
3522                        total,
3523                    });
3524                }
3525            }
3526        }
3527        result
3528    }
3529
3530    /// Build a link DATA packet with a given context and data.
3531    fn build_link_packet(
3532        &self,
3533        link_id: &LinkId,
3534        context: u8,
3535        data: &[u8],
3536    ) -> Vec<LinkManagerAction> {
3537        let flags = PacketFlags {
3538            header_type: constants::HEADER_1,
3539            context_flag: constants::FLAG_UNSET,
3540            transport_type: constants::TRANSPORT_BROADCAST,
3541            destination_type: constants::DESTINATION_LINK,
3542            packet_type: constants::PACKET_TYPE_DATA,
3543        };
3544        let mut actions = Vec::new();
3545        let max_mtu = self
3546            .links
3547            .get(link_id)
3548            .map(|l| l.engine.mtu() as usize)
3549            .unwrap_or(constants::MTU);
3550        if let Ok((raw, _packet_hash)) = RawPacket::pack_raw_with_hash_with_max_mtu(
3551            flags, 0, link_id, None, context, data, max_mtu,
3552        ) {
3553            actions.push(LinkManagerAction::SendPacket {
3554                raw,
3555                dest_type: constants::DESTINATION_LINK,
3556                attached_interface: None,
3557            });
3558        }
3559        actions
3560    }
3561
3562    /// Start sending a resource on a link.
3563    pub fn send_resource(
3564        &mut self,
3565        link_id: &LinkId,
3566        data: &[u8],
3567        metadata: Option<&[u8]>,
3568        rng: &mut dyn Rng,
3569    ) -> Vec<LinkManagerAction> {
3570        self.send_resource_with_auto_compress(link_id, data, metadata, true, rng)
3571    }
3572
3573    /// Start sending a resource on a link, controlling automatic compression.
3574    pub fn send_resource_with_auto_compress(
3575        &mut self,
3576        link_id: &LinkId,
3577        data: &[u8],
3578        metadata: Option<&[u8]>,
3579        auto_compress: bool,
3580        rng: &mut dyn Rng,
3581    ) -> Vec<LinkManagerAction> {
3582        let link = match self.links.get_mut(link_id) {
3583            Some(l) => l,
3584            None => return Vec::new(),
3585        };
3586
3587        if link.engine.state() != LinkState::Active {
3588            return Vec::new();
3589        }
3590
3591        let now = time::now();
3592
3593        let senders = match Self::build_resource_senders(
3594            link,
3595            data,
3596            metadata,
3597            auto_compress,
3598            false, // is_response
3599            None,  // request_id
3600            rng,
3601            now,
3602        ) {
3603            Ok(s) => s,
3604            Err(e) => {
3605                log::debug!("Failed to create ResourceSender: {}", e);
3606                return Vec::new();
3607            }
3608        };
3609
3610        let adv_actions = Self::start_resource_senders(link, senders, now);
3611
3612        let _ = link;
3613        self.process_resource_actions(link_id, adv_actions, rng)
3614    }
3615
3616    /// Start a bounded-memory Resource transfer from a sequential reader.
3617    #[allow(clippy::too_many_arguments)]
3618    pub fn send_resource_stream(
3619        &mut self,
3620        link_id: &LinkId,
3621        transfer_id: ResourceTransferId,
3622        reader: Box<dyn Read + Send>,
3623        declared_length: u64,
3624        metadata: Option<Vec<u8>>,
3625        auto_compress: bool,
3626        rng: &mut dyn Rng,
3627    ) -> Vec<LinkManagerAction> {
3628        let Some(link) = self.links.get_mut(link_id) else {
3629            return vec![LinkManagerAction::ResourceStreamFailed {
3630                link_id: *link_id,
3631                transfer_id,
3632                error: ResourceTransferError::Protocol("link does not exist".into()),
3633            }];
3634        };
3635        if link.engine.state() != LinkState::Active {
3636            return vec![LinkManagerAction::ResourceStreamFailed {
3637                link_id: *link_id,
3638                transfer_id,
3639                error: ResourceTransferError::Protocol("link is not active".into()),
3640            }];
3641        }
3642
3643        let metadata_overhead = metadata.as_ref().map(|value| 3 + value.len()).unwrap_or(0);
3644        let Some(logical_size) = declared_length.checked_add(metadata_overhead as u64) else {
3645            return vec![LinkManagerAction::ResourceStreamFailed {
3646                link_id: *link_id,
3647                transfer_id,
3648                error: ResourceTransferError::Protocol("resource length overflow".into()),
3649            }];
3650        };
3651        if usize::try_from(logical_size).is_err()
3652            || metadata_overhead > constants::RESOURCE_MAX_EFFICIENT_SIZE
3653        {
3654            return vec![LinkManagerAction::ResourceStreamFailed {
3655                link_id: *link_id,
3656                transfer_id,
3657                error: ResourceTransferError::Protocol(
3658                    "resource length cannot be represented by this platform".into(),
3659                ),
3660            }];
3661        }
3662
3663        let first_capacity = constants::RESOURCE_MAX_EFFICIENT_SIZE - metadata_overhead;
3664        let first_length = declared_length.min(first_capacity as u64);
3665        let remaining_after_first = declared_length - first_length;
3666        let total_segments =
3667            1 + remaining_after_first.div_ceil(constants::RESOURCE_MAX_EFFICIENT_SIZE as u64);
3668        let now = time::now();
3669        let mut stream = OutgoingStreamTransfer {
3670            transfer_id,
3671            reader,
3672            remaining: declared_length,
3673            declared_length,
3674            metadata_overhead: metadata_overhead as u64,
3675            auto_compress,
3676        };
3677        let mut first = match Self::build_stream_segment(
3678            link,
3679            &mut stream,
3680            metadata.as_deref(),
3681            1,
3682            total_segments,
3683            None,
3684            rng,
3685            now,
3686        ) {
3687            Ok(sender) => sender,
3688            Err(error) => {
3689                return vec![LinkManagerAction::ResourceStreamFailed {
3690                    link_id: *link_id,
3691                    transfer_id,
3692                    error,
3693                }]
3694            }
3695        };
3696        let original_hash = first.original_hash;
3697        let current_total_parts = first.total_parts();
3698        let adv_actions = first.advertise(now);
3699        if total_segments > 1 {
3700            link.outgoing_splits.insert(
3701                original_hash,
3702                OutgoingSplitTransfer {
3703                    total_segments,
3704                    completed_segments: 0,
3705                    current_segment_index: 1,
3706                    current_sent_parts: 0,
3707                    current_total_parts,
3708                },
3709            );
3710        }
3711        link.outgoing_streams.insert(original_hash, stream);
3712        link.outgoing_resources.push(first);
3713        let _ = link;
3714        self.process_resource_actions(link_id, adv_actions, rng)
3715    }
3716
3717    /// Set the resource acceptance strategy for a link.
3718    pub fn set_resource_strategy(&mut self, link_id: &LinkId, strategy: ResourceStrategy) {
3719        if let Some(link) = self.links.get_mut(link_id) {
3720            link.resource_strategy = strategy;
3721        }
3722    }
3723
3724    pub fn set_resource_receive_mode(&mut self, link_id: &LinkId, mode: ResourceReceiveMode) {
3725        if let Some(link) = self.links.get_mut(link_id) {
3726            link.resource_receive_mode = mode;
3727        }
3728    }
3729
3730    /// Flush the channel TX ring for a link, clearing outstanding messages.
3731    /// Called after holepunch completion where signaling messages are fire-and-forget.
3732    pub fn flush_channel_tx(&mut self, link_id: &LinkId) {
3733        if let Some(link) = self.links.get_mut(link_id) {
3734            if let Some(ref mut channel) = link.channel {
3735                channel.flush_tx();
3736            }
3737        }
3738    }
3739
3740    /// Send a channel message on a link.
3741    pub fn send_channel_message(
3742        &mut self,
3743        link_id: &LinkId,
3744        msgtype: u16,
3745        payload: &[u8],
3746        rng: &mut dyn Rng,
3747    ) -> Result<Vec<LinkManagerAction>, String> {
3748        let link = match self.links.get_mut(link_id) {
3749            Some(l) => l,
3750            None => return Err("unknown link".to_string()),
3751        };
3752
3753        let channel = match link.channel {
3754            Some(ref mut ch) => ch,
3755            None => return Err("link has no active channel".to_string()),
3756        };
3757
3758        let link_mdu = link.engine.mdu();
3759        let now = time::now();
3760        let chan_actions = match channel.send(msgtype, payload, now, link_mdu) {
3761            Ok(a) => {
3762                link.channel_send_ok += 1;
3763                a
3764            }
3765            Err(e) => {
3766                log::debug!("Channel send failed: {:?}", e);
3767                match e {
3768                    rns_core::channel::ChannelError::NotReady => link.channel_send_not_ready += 1,
3769                    rns_core::channel::ChannelError::MessageTooBig => {
3770                        link.channel_send_too_big += 1;
3771                    }
3772                    rns_core::channel::ChannelError::InvalidEnvelope => {
3773                        link.channel_send_other_error += 1;
3774                    }
3775                }
3776                return Err(e.to_string());
3777            }
3778        };
3779
3780        let _ = link;
3781        Ok(self.process_channel_actions(link_id, chan_actions, rng))
3782    }
3783
3784    /// Periodic tick: check keepalive, stale, timeouts for all links.
3785    pub fn tick(&mut self, rng: &mut dyn Rng) -> Vec<LinkManagerAction> {
3786        let now = time::now();
3787        let mut all_actions = Vec::new();
3788
3789        // Collect link_ids to avoid borrow issues
3790        let link_ids: Vec<LinkId> = self.links.keys().copied().collect();
3791
3792        for link_id in &link_ids {
3793            let link = match self.links.get_mut(link_id) {
3794                Some(l) => l,
3795                None => continue,
3796            };
3797
3798            // Tick the engine
3799            let tick_actions = link.engine.tick(now);
3800            all_actions.extend(self.process_link_actions(link_id, &tick_actions));
3801
3802            // Check if keepalive is needed
3803            let link = match self.links.get_mut(link_id) {
3804                Some(l) => l,
3805                None => continue,
3806            };
3807            if link.engine.needs_keepalive(now) {
3808                // Only initiators reach this branch. Send the upstream 0xff
3809                // probe; responders conditionally return 0xfe on receipt.
3810                let flags = PacketFlags {
3811                    header_type: constants::HEADER_1,
3812                    context_flag: constants::FLAG_UNSET,
3813                    transport_type: constants::TRANSPORT_BROADCAST,
3814                    destination_type: constants::DESTINATION_LINK,
3815                    packet_type: constants::PACKET_TYPE_DATA,
3816                };
3817                if let Ok((raw, _packet_hash)) = RawPacket::pack_raw_with_hash(
3818                    flags,
3819                    0,
3820                    link_id,
3821                    None,
3822                    constants::CONTEXT_KEEPALIVE,
3823                    &[0xff],
3824                ) {
3825                    all_actions.push(LinkManagerAction::SendPacket {
3826                        raw,
3827                        dest_type: constants::DESTINATION_LINK,
3828                        attached_interface: None,
3829                    });
3830                    link.engine.record_outbound(now, true);
3831                }
3832            }
3833
3834            if let Some(channel) = link.channel.as_mut() {
3835                let chan_actions = channel.tick(now);
3836                let _ = channel;
3837                let _ = link;
3838                all_actions.extend(self.process_channel_actions(link_id, chan_actions, rng));
3839            }
3840        }
3841
3842        // Tick resource senders and receivers
3843        for link_id in &link_ids {
3844            let link = match self.links.get_mut(link_id) {
3845                Some(l) => l,
3846                None => continue,
3847            };
3848
3849            // Tick outgoing resources (senders)
3850            let mut sender_actions = Vec::new();
3851            for sender in &mut link.outgoing_resources {
3852                sender_actions.extend(sender.tick(now));
3853            }
3854
3855            // Tick incoming resources (receivers)
3856            let mut receiver_actions = Vec::new();
3857            for receiver in &mut link.incoming_resources {
3858                let decrypt_fn = |ciphertext: &[u8]| -> Result<Vec<u8>, ()> {
3859                    link.engine.decrypt(ciphertext).map_err(|_| ())
3860                };
3861                receiver_actions.extend(receiver.tick(now, &decrypt_fn, &Bzip2Compressor));
3862            }
3863
3864            let failed_request_ids: Vec<[u8; 16]> = link
3865                .outgoing_resources
3866                .iter()
3867                .filter(|sender| {
3868                    sender.flags.is_request
3869                        && sender.status >= rns_core::resource::ResourceStatus::Failed
3870                })
3871                .filter_map(|sender| Self::response_request_id(&sender.request_id))
3872                .collect();
3873            for request_id in failed_request_ids {
3874                link.pending_requests.remove(&request_id);
3875            }
3876            link.pending_requests
3877                .retain(|_, request| request.deadline.is_none_or(|expires_at| now <= expires_at));
3878
3879            // Clean up completed/failed resources
3880            link.outgoing_resources
3881                .retain(|s| s.status < rns_core::resource::ResourceStatus::Complete);
3882            link.incoming_resources
3883                .retain(|r| r.status < rns_core::resource::ResourceStatus::Assembling);
3884            let active_split_hashes: Vec<[u8; 32]> = link
3885                .outgoing_resources
3886                .iter()
3887                .filter(|s| s.flags.split)
3888                .map(|s| s.original_hash)
3889                .collect();
3890            link.outgoing_splits.retain(|original_hash, split| {
3891                split.completed_segments < split.total_segments
3892                    && active_split_hashes.contains(original_hash)
3893            });
3894
3895            let _ = link;
3896            all_actions.extend(self.process_resource_actions(link_id, sender_actions, rng));
3897            all_actions.extend(self.process_resource_actions(link_id, receiver_actions, rng));
3898        }
3899
3900        // Clean up closed links
3901        let closed: Vec<LinkId> = self
3902            .links
3903            .iter()
3904            .filter(|(_, l)| l.engine.state() == LinkState::Closed)
3905            .map(|(id, _)| *id)
3906            .collect();
3907        for id in closed {
3908            self.links.remove(&id);
3909            all_actions.push(LinkManagerAction::DeregisterLinkDest { link_id: id });
3910        }
3911
3912        all_actions
3913    }
3914
3915    /// Check if a destination hash is a known link_id managed by this manager.
3916    pub fn is_link_destination(&self, dest_hash: &[u8; 16]) -> bool {
3917        self.links.contains_key(dest_hash) || self.link_destinations.contains_key(dest_hash)
3918    }
3919
3920    /// Get the state of a link.
3921    pub fn link_state(&self, link_id: &LinkId) -> Option<LinkState> {
3922        self.links.get(link_id).map(|l| l.engine.state())
3923    }
3924
3925    /// Get the RTT of a link.
3926    pub fn link_rtt(&self, link_id: &LinkId) -> Option<f64> {
3927        self.links.get(link_id).and_then(|l| l.engine.rtt())
3928    }
3929
3930    /// Update the RTT of a link (e.g., after path redirect to a direct connection).
3931    pub fn set_link_rtt(&mut self, link_id: &LinkId, rtt: f64) {
3932        if let Some(link) = self.links.get_mut(link_id) {
3933            link.engine.set_rtt(rtt);
3934        }
3935    }
3936
3937    /// Reset the inbound timer for a link (e.g., after path redirect).
3938    pub fn record_link_inbound(&mut self, link_id: &LinkId) {
3939        if let Some(link) = self.links.get_mut(link_id) {
3940            link.engine.record_inbound(time::now());
3941        }
3942    }
3943
3944    /// Update the MTU of a link (e.g., after path redirect to a different interface).
3945    pub fn set_link_mtu(&mut self, link_id: &LinkId, mtu: u32) {
3946        if let Some(link) = self.links.get_mut(link_id) {
3947            link.engine.set_mtu(mtu);
3948        }
3949    }
3950
3951    /// Get the number of active links.
3952    pub fn link_count(&self) -> usize {
3953        self.links.len()
3954    }
3955
3956    /// Get the number of active resource transfers across all links.
3957    pub fn resource_transfer_count(&self) -> usize {
3958        self.links
3959            .values()
3960            .map(|managed| {
3961                managed
3962                    .incoming_resources
3963                    .iter()
3964                    .filter(|resource| !resource.flags.split)
3965                    .count()
3966                    + managed.incoming_splits.len()
3967                    + managed
3968                        .outgoing_resources
3969                        .iter()
3970                        .filter(|resource| !resource.flags.split)
3971                        .count()
3972                    + managed.outgoing_splits.len()
3973            })
3974            .sum()
3975    }
3976
3977    /// Cancel all active resource transfers and return the generated actions.
3978    pub fn cancel_all_resources(&mut self, rng: &mut dyn Rng) -> Vec<LinkManagerAction> {
3979        let link_ids: Vec<LinkId> = self.links.keys().copied().collect();
3980        let mut all_actions = Vec::new();
3981
3982        for link_id in &link_ids {
3983            let link = match self.links.get_mut(link_id) {
3984                Some(l) => l,
3985                None => continue,
3986            };
3987
3988            let mut sender_actions = Vec::new();
3989            for sender in &mut link.outgoing_resources {
3990                sender_actions.extend(sender.cancel());
3991            }
3992
3993            let mut receiver_actions = Vec::new();
3994            for receiver in &mut link.incoming_resources {
3995                receiver_actions.extend(receiver.cancel());
3996            }
3997
3998            link.outgoing_resources
3999                .retain(|s| s.status < rns_core::resource::ResourceStatus::Complete);
4000            link.incoming_resources
4001                .retain(|r| r.status < rns_core::resource::ResourceStatus::Assembling);
4002            link.outgoing_splits.clear();
4003            all_actions.extend(link.outgoing_streams.drain().map(|(_, stream)| {
4004                LinkManagerAction::ResourceStreamFailed {
4005                    link_id: *link_id,
4006                    transfer_id: stream.transfer_id,
4007                    error: ResourceTransferError::Cancelled,
4008                }
4009            }));
4010            link.incoming_splits.clear();
4011
4012            let _ = link;
4013            all_actions.extend(self.process_resource_actions(link_id, sender_actions, rng));
4014            all_actions.extend(self.process_resource_actions(link_id, receiver_actions, rng));
4015        }
4016
4017        all_actions
4018    }
4019
4020    /// Get information about all active links.
4021    pub fn link_entries(&self) -> Vec<crate::event::LinkInfoEntry> {
4022        self.links
4023            .iter()
4024            .map(|(link_id, managed)| {
4025                let state = match managed.engine.state() {
4026                    LinkState::Pending => "pending",
4027                    LinkState::Handshake => "handshake",
4028                    LinkState::Active => "active",
4029                    LinkState::Stale => "stale",
4030                    LinkState::Closed => "closed",
4031                };
4032                crate::event::LinkInfoEntry {
4033                    link_id: *link_id,
4034                    state: state.to_string(),
4035                    is_initiator: managed.engine.is_initiator(),
4036                    dest_hash: managed.dest_hash,
4037                    remote_identity: managed.remote_identity.as_ref().map(|(h, _)| *h),
4038                    rtt: managed.engine.rtt(),
4039                    expected_hops: managed.engine.expected_hops(),
4040                    tx_packets: managed.engine.tx_packets(),
4041                    rx_packets: managed.engine.rx_packets(),
4042                    tx_bytes: managed.engine.tx_bytes(),
4043                    rx_bytes: managed.engine.rx_bytes(),
4044                    channel_window: managed.channel.as_ref().map(|c| c.window()),
4045                    channel_outstanding: managed.channel.as_ref().map(|c| c.outstanding()),
4046                    pending_channel_packets: managed.pending_channel_packets.len(),
4047                    channel_send_ok: managed.channel_send_ok,
4048                    channel_send_not_ready: managed.channel_send_not_ready,
4049                    channel_send_too_big: managed.channel_send_too_big,
4050                    channel_send_other_error: managed.channel_send_other_error,
4051                    channel_messages_received: managed.channel_messages_received,
4052                    channel_proofs_sent: managed.channel_proofs_sent,
4053                    channel_proofs_received: managed.channel_proofs_received,
4054                }
4055            })
4056            .collect()
4057    }
4058
4059    /// Account a packed outbound action at the single driver dispatch point.
4060    /// LINKREQUEST is accounted when its engine is created since its wire
4061    /// destination is not the resulting link ID.
4062    pub fn record_outbound_packet(&mut self, packet: &RawPacket) {
4063        if packet.flags.destination_type == constants::DESTINATION_LINK {
4064            if let Some(link) = self.links.get_mut(&packet.destination_hash) {
4065                link.engine.record_outbound_traffic(packet.data.len());
4066            }
4067        }
4068    }
4069
4070    /// Get information about all active resource transfers.
4071    pub fn resource_entries(&self) -> Vec<crate::event::ResourceInfoEntry> {
4072        let mut entries = Vec::new();
4073        for (link_id, managed) in &self.links {
4074            let resource_sdu = Self::resource_sdu_for_link(managed);
4075            for split in managed.incoming_splits.values() {
4076                let (received, total) = Self::incoming_split_progress(split, resource_sdu);
4077                entries.push(crate::event::ResourceInfoEntry {
4078                    link_id: *link_id,
4079                    direction: "incoming".to_string(),
4080                    total_parts: total,
4081                    transferred_parts: received,
4082                    complete: received >= total && total > 0,
4083                });
4084            }
4085            for recv in &managed.incoming_resources {
4086                if recv.flags.split {
4087                    continue;
4088                }
4089                let (received, total) = recv.progress();
4090                entries.push(crate::event::ResourceInfoEntry {
4091                    link_id: *link_id,
4092                    direction: "incoming".to_string(),
4093                    total_parts: total,
4094                    transferred_parts: received,
4095                    complete: received >= total && total > 0,
4096                });
4097            }
4098            for split in managed.outgoing_splits.values() {
4099                let (sent, total) = Self::outgoing_split_progress(split, resource_sdu);
4100                entries.push(crate::event::ResourceInfoEntry {
4101                    link_id: *link_id,
4102                    direction: "outgoing".to_string(),
4103                    total_parts: total,
4104                    transferred_parts: sent,
4105                    complete: sent >= total && total > 0,
4106                });
4107            }
4108            for send in &managed.outgoing_resources {
4109                if send.flags.split {
4110                    continue;
4111                }
4112                let total = send.total_parts();
4113                let sent = send.sent_parts;
4114                entries.push(crate::event::ResourceInfoEntry {
4115                    link_id: *link_id,
4116                    direction: "outgoing".to_string(),
4117                    total_parts: total,
4118                    transferred_parts: sent,
4119                    complete: sent >= total && total > 0,
4120                });
4121            }
4122        }
4123        entries
4124    }
4125
4126    /// Convert LinkActions to LinkManagerActions.
4127    fn process_link_actions(
4128        &self,
4129        link_id: &LinkId,
4130        actions: &[LinkAction],
4131    ) -> Vec<LinkManagerAction> {
4132        let mut result = Vec::new();
4133        for action in actions {
4134            match action {
4135                LinkAction::StateChanged {
4136                    new_state, reason, ..
4137                } => match new_state {
4138                    LinkState::Closed => {
4139                        result.push(LinkManagerAction::LinkClosed {
4140                            link_id: *link_id,
4141                            reason: *reason,
4142                        });
4143                    }
4144                    _ => {}
4145                },
4146                LinkAction::LinkEstablished {
4147                    rtt, is_initiator, ..
4148                } => {
4149                    let dest_hash = self
4150                        .links
4151                        .get(link_id)
4152                        .map(|l| l.dest_hash)
4153                        .unwrap_or([0u8; 16]);
4154                    let hops = self
4155                        .links
4156                        .get(link_id)
4157                        .map(|link| link.engine.expected_hops())
4158                        .unwrap_or(rns_core::constants::PATHFINDER_M);
4159                    let rebalanced_at = self
4160                        .links
4161                        .get(link_id)
4162                        .and_then(|link| link.engine.rebalanced_at());
4163                    result.push(LinkManagerAction::LinkEstablished {
4164                        link_id: *link_id,
4165                        dest_hash,
4166                        hops,
4167                        rebalanced_at,
4168                        rtt: *rtt,
4169                        is_initiator: *is_initiator,
4170                    });
4171                }
4172                LinkAction::RemoteIdentified {
4173                    identity_hash,
4174                    public_key,
4175                    ..
4176                } => {
4177                    result.push(LinkManagerAction::RemoteIdentified {
4178                        link_id: *link_id,
4179                        identity_hash: *identity_hash,
4180                        public_key: *public_key,
4181                    });
4182                }
4183                LinkAction::DataReceived { .. } => {
4184                    // Data delivery is handled at a higher level
4185                }
4186            }
4187        }
4188        result
4189    }
4190
4191    /// Convert ChannelActions to LinkManagerActions.
4192    fn process_channel_actions(
4193        &mut self,
4194        link_id: &LinkId,
4195        actions: Vec<rns_core::channel::ChannelAction>,
4196        rng: &mut dyn Rng,
4197    ) -> Vec<LinkManagerAction> {
4198        let mut result = Vec::new();
4199        for action in actions {
4200            match action {
4201                rns_core::channel::ChannelAction::SendOnLink { raw, sequence } => {
4202                    // Encrypt and send as CHANNEL context. If the packet cannot be
4203                    // emitted, remove the reserved channel sequence so receivers do
4204                    // not stall on a gap that never reached the wire.
4205                    let encrypted = match self.links.get(link_id) {
4206                        Some(link) => match link.engine.encrypt(&raw, rng) {
4207                            Ok(encrypted) => encrypted,
4208                            Err(_) => {
4209                                if let Some(link_mut) = self.links.get_mut(link_id) {
4210                                    if let Some(channel) = link_mut.channel.as_mut() {
4211                                        channel.cancel_send(sequence);
4212                                    }
4213                                }
4214                                continue;
4215                            }
4216                        },
4217                        None => continue,
4218                    };
4219                    let flags = PacketFlags {
4220                        header_type: constants::HEADER_1,
4221                        context_flag: constants::FLAG_UNSET,
4222                        transport_type: constants::TRANSPORT_BROADCAST,
4223                        destination_type: constants::DESTINATION_LINK,
4224                        packet_type: constants::PACKET_TYPE_DATA,
4225                    };
4226                    match RawPacket::pack_raw_with_hash(
4227                        flags,
4228                        0,
4229                        link_id,
4230                        None,
4231                        constants::CONTEXT_CHANNEL,
4232                        &encrypted,
4233                    ) {
4234                        Ok((raw_bytes, packet_hash)) => {
4235                            if let Some(link_mut) = self.links.get_mut(link_id) {
4236                                link_mut
4237                                    .pending_channel_packets
4238                                    .insert(packet_hash, sequence);
4239                            }
4240                            result.push(LinkManagerAction::SendPacket {
4241                                raw: raw_bytes,
4242                                dest_type: constants::DESTINATION_LINK,
4243                                attached_interface: None,
4244                            });
4245                        }
4246                        Err(_) => {
4247                            if let Some(link_mut) = self.links.get_mut(link_id) {
4248                                if let Some(channel) = link_mut.channel.as_mut() {
4249                                    channel.cancel_send(sequence);
4250                                }
4251                            }
4252                        }
4253                    }
4254                }
4255                rns_core::channel::ChannelAction::MessageReceived {
4256                    msgtype, payload, ..
4257                } => {
4258                    result.push(LinkManagerAction::ChannelMessageReceived {
4259                        link_id: *link_id,
4260                        msgtype,
4261                        payload,
4262                    });
4263                }
4264                rns_core::channel::ChannelAction::TeardownLink => {
4265                    result.push(LinkManagerAction::LinkClosed {
4266                        link_id: *link_id,
4267                        reason: Some(TeardownReason::Timeout),
4268                    });
4269                }
4270            }
4271        }
4272        result
4273    }
4274}
4275
4276impl Default for LinkManager {
4277    fn default() -> Self {
4278        Self::new()
4279    }
4280}
4281
4282/// Compute a path hash from a path string.
4283/// Uses truncated SHA-256 (first 16 bytes).
4284fn compute_path_hash(path: &str) -> [u8; 16] {
4285    let full = rns_core::hash::full_hash(path.as_bytes());
4286    let mut result = [0u8; 16];
4287    result.copy_from_slice(&full[..16]);
4288    result
4289}
4290
4291#[cfg(test)]
4292mod tests {
4293    use super::*;
4294    use rns_crypto::identity::Identity;
4295    use rns_crypto::{FixedRng, OsRng};
4296
4297    fn make_rng(seed: u8) -> FixedRng {
4298        FixedRng::new(&[seed; 128])
4299    }
4300
4301    fn make_dest_keys(rng: &mut dyn Rng) -> (Ed25519PrivateKey, [u8; 32]) {
4302        let sig_prv = Ed25519PrivateKey::generate(rng);
4303        let sig_pub_bytes = sig_prv.public_key().public_bytes();
4304        (sig_prv, sig_pub_bytes)
4305    }
4306
4307    #[test]
4308    fn test_register_link_destination() {
4309        let mut mgr = LinkManager::new();
4310        let mut rng = make_rng(0x01);
4311        let (sig_prv, sig_pub_bytes) = make_dest_keys(&mut rng);
4312        let dest_hash = [0xDD; 16];
4313
4314        mgr.register_link_destination(
4315            dest_hash,
4316            sig_prv,
4317            sig_pub_bytes,
4318            ResourceStrategy::AcceptNone,
4319        );
4320        assert!(mgr.is_link_destination(&dest_hash));
4321
4322        mgr.deregister_link_destination(&dest_hash);
4323        assert!(!mgr.is_link_destination(&dest_hash));
4324    }
4325
4326    #[test]
4327    fn test_create_link() {
4328        let mut mgr = LinkManager::new();
4329        let mut rng = OsRng;
4330        let dest_hash = [0xDD; 16];
4331
4332        let sig_pub_bytes = [0xAA; 32]; // dummy sig pub for test
4333        let (link_id, actions) = mgr.create_link(
4334            &dest_hash,
4335            &sig_pub_bytes,
4336            1,
4337            constants::MTU as u32,
4338            &mut rng,
4339        );
4340        assert_ne!(link_id, [0u8; 16]);
4341        // Should have RegisterLinkDest + SendPacket
4342        assert_eq!(actions.len(), 2);
4343        assert!(matches!(
4344            actions[0],
4345            LinkManagerAction::RegisterLinkDest { .. }
4346        ));
4347        assert!(matches!(actions[1], LinkManagerAction::SendPacket { .. }));
4348
4349        // Link should be in Pending state
4350        assert_eq!(mgr.link_state(&link_id), Some(LinkState::Pending));
4351    }
4352
4353    #[test]
4354    fn test_full_handshake_via_manager() {
4355        let mut rng = OsRng;
4356        let dest_hash = [0xDD; 16];
4357
4358        // Setup responder
4359        let mut responder_mgr = LinkManager::new();
4360        let (sig_prv, sig_pub_bytes) = make_dest_keys(&mut rng);
4361        responder_mgr.register_link_destination(
4362            dest_hash,
4363            sig_prv,
4364            sig_pub_bytes,
4365            ResourceStrategy::AcceptNone,
4366        );
4367
4368        // Setup initiator
4369        let mut initiator_mgr = LinkManager::new();
4370
4371        // Step 1: Initiator creates link (needs dest signing pub key for LRPROOF verification)
4372        let (link_id, init_actions) = initiator_mgr.create_link(
4373            &dest_hash,
4374            &sig_pub_bytes,
4375            1,
4376            constants::MTU as u32,
4377            &mut rng,
4378        );
4379        assert_eq!(init_actions.len(), 2);
4380
4381        // Extract the LINKREQUEST packet raw bytes
4382        let linkrequest_raw = match &init_actions[1] {
4383            LinkManagerAction::SendPacket { raw, .. } => raw.clone(),
4384            _ => panic!("Expected SendPacket"),
4385        };
4386
4387        // Parse to get packet_hash and dest_hash
4388        let lr_packet = RawPacket::unpack(&linkrequest_raw).unwrap();
4389
4390        // Step 2: Responder handles LINKREQUEST
4391        let resp_actions = responder_mgr.handle_local_delivery(
4392            lr_packet.destination_hash,
4393            &linkrequest_raw,
4394            lr_packet.packet_hash,
4395            rns_core::transport::types::InterfaceId(0),
4396            &mut rng,
4397        );
4398        // Should have RegisterLinkDest + SendPacket(LRPROOF)
4399        assert!(resp_actions.len() >= 2);
4400        assert!(matches!(
4401            resp_actions[0],
4402            LinkManagerAction::RegisterLinkDest { .. }
4403        ));
4404
4405        // Extract LRPROOF packet
4406        let lrproof_raw = match &resp_actions[1] {
4407            LinkManagerAction::SendPacket { raw, .. } => raw.clone(),
4408            _ => panic!("Expected SendPacket for LRPROOF"),
4409        };
4410
4411        // Step 3: Initiator handles LRPROOF
4412        let lrproof_packet = RawPacket::unpack(&lrproof_raw).unwrap();
4413        let init_actions2 = initiator_mgr.handle_local_delivery(
4414            lrproof_packet.destination_hash,
4415            &lrproof_raw,
4416            lrproof_packet.packet_hash,
4417            rns_core::transport::types::InterfaceId(0),
4418            &mut rng,
4419        );
4420
4421        // Should have LinkEstablished + SendPacket(LRRTT)
4422        let has_established = init_actions2
4423            .iter()
4424            .any(|a| matches!(a, LinkManagerAction::LinkEstablished { .. }));
4425        assert!(has_established, "Initiator should emit LinkEstablished");
4426
4427        // Extract LRRTT
4428        let lrrtt_raw = init_actions2
4429            .iter()
4430            .find_map(|a| match a {
4431                LinkManagerAction::SendPacket { raw, .. } => Some(raw.clone()),
4432                _ => None,
4433            })
4434            .expect("Should have LRRTT SendPacket");
4435
4436        // Step 4: Responder handles LRRTT
4437        let lrrtt_packet = RawPacket::unpack(&lrrtt_raw).unwrap();
4438        let resp_link_id = lrrtt_packet.destination_hash;
4439        let resp_actions2 = responder_mgr.handle_local_delivery(
4440            resp_link_id,
4441            &lrrtt_raw,
4442            lrrtt_packet.packet_hash,
4443            rns_core::transport::types::InterfaceId(0),
4444            &mut rng,
4445        );
4446
4447        let has_established = resp_actions2
4448            .iter()
4449            .any(|a| matches!(a, LinkManagerAction::LinkEstablished { .. }));
4450        assert!(has_established, "Responder should emit LinkEstablished");
4451
4452        // Both sides should be Active
4453        assert_eq!(initiator_mgr.link_state(&link_id), Some(LinkState::Active));
4454        assert_eq!(responder_mgr.link_state(&link_id), Some(LinkState::Active));
4455
4456        // Both should have RTT
4457        assert!(initiator_mgr.link_rtt(&link_id).is_some());
4458        assert!(responder_mgr.link_rtt(&link_id).is_some());
4459    }
4460
4461    #[test]
4462    fn test_encrypted_data_exchange() {
4463        let mut rng = OsRng;
4464        let dest_hash = [0xDD; 16];
4465        let mut resp_mgr = LinkManager::new();
4466        let (sig_prv, sig_pub_bytes) = make_dest_keys(&mut rng);
4467        resp_mgr.register_link_destination(
4468            dest_hash,
4469            sig_prv,
4470            sig_pub_bytes,
4471            ResourceStrategy::AcceptNone,
4472        );
4473        let mut init_mgr = LinkManager::new();
4474
4475        // Handshake
4476        let (link_id, init_actions) = init_mgr.create_link(
4477            &dest_hash,
4478            &sig_pub_bytes,
4479            1,
4480            constants::MTU as u32,
4481            &mut rng,
4482        );
4483        let lr_raw = extract_send_packet(&init_actions);
4484        let lr_pkt = RawPacket::unpack(&lr_raw).unwrap();
4485        let resp_actions = resp_mgr.handle_local_delivery(
4486            lr_pkt.destination_hash,
4487            &lr_raw,
4488            lr_pkt.packet_hash,
4489            rns_core::transport::types::InterfaceId(0),
4490            &mut rng,
4491        );
4492        let lrproof_raw = extract_send_packet_at(&resp_actions, 1);
4493        let lrproof_pkt = RawPacket::unpack(&lrproof_raw).unwrap();
4494        let init_actions2 = init_mgr.handle_local_delivery(
4495            lrproof_pkt.destination_hash,
4496            &lrproof_raw,
4497            lrproof_pkt.packet_hash,
4498            rns_core::transport::types::InterfaceId(0),
4499            &mut rng,
4500        );
4501        let lrrtt_raw = extract_any_send_packet(&init_actions2);
4502        let lrrtt_pkt = RawPacket::unpack(&lrrtt_raw).unwrap();
4503        resp_mgr.handle_local_delivery(
4504            lrrtt_pkt.destination_hash,
4505            &lrrtt_raw,
4506            lrrtt_pkt.packet_hash,
4507            rns_core::transport::types::InterfaceId(0),
4508            &mut rng,
4509        );
4510
4511        // Send data from initiator to responder
4512        let actions =
4513            init_mgr.send_on_link(&link_id, b"hello link!", constants::CONTEXT_NONE, &mut rng);
4514        assert_eq!(actions.len(), 1);
4515        assert!(matches!(actions[0], LinkManagerAction::SendPacket { .. }));
4516    }
4517
4518    #[test]
4519    fn test_request_response() {
4520        let mut rng = OsRng;
4521        let dest_hash = [0xDD; 16];
4522        let mut resp_mgr = LinkManager::new();
4523        let (sig_prv, sig_pub_bytes) = make_dest_keys(&mut rng);
4524        resp_mgr.register_link_destination(
4525            dest_hash,
4526            sig_prv,
4527            sig_pub_bytes,
4528            ResourceStrategy::AcceptNone,
4529        );
4530
4531        // Register a request handler
4532        resp_mgr.register_request_handler("/status", None, |_link_id, _path, _data, _remote| {
4533            Some(b"OK".to_vec())
4534        });
4535
4536        let mut init_mgr = LinkManager::new();
4537
4538        // Complete handshake
4539        let (link_id, init_actions) = init_mgr.create_link(
4540            &dest_hash,
4541            &sig_pub_bytes,
4542            1,
4543            constants::MTU as u32,
4544            &mut rng,
4545        );
4546        let lr_raw = extract_send_packet(&init_actions);
4547        let lr_pkt = RawPacket::unpack(&lr_raw).unwrap();
4548        let resp_actions = resp_mgr.handle_local_delivery(
4549            lr_pkt.destination_hash,
4550            &lr_raw,
4551            lr_pkt.packet_hash,
4552            rns_core::transport::types::InterfaceId(0),
4553            &mut rng,
4554        );
4555        let lrproof_raw = extract_send_packet_at(&resp_actions, 1);
4556        let lrproof_pkt = RawPacket::unpack(&lrproof_raw).unwrap();
4557        let init_actions2 = init_mgr.handle_local_delivery(
4558            lrproof_pkt.destination_hash,
4559            &lrproof_raw,
4560            lrproof_pkt.packet_hash,
4561            rns_core::transport::types::InterfaceId(0),
4562            &mut rng,
4563        );
4564        let lrrtt_raw = extract_any_send_packet(&init_actions2);
4565        let lrrtt_pkt = RawPacket::unpack(&lrrtt_raw).unwrap();
4566        resp_mgr.handle_local_delivery(
4567            lrrtt_pkt.destination_hash,
4568            &lrrtt_raw,
4569            lrrtt_pkt.packet_hash,
4570            rns_core::transport::types::InterfaceId(0),
4571            &mut rng,
4572        );
4573
4574        // Send request from initiator
4575        let req_actions = init_mgr.send_request(&link_id, "/status", b"query", &mut rng);
4576        assert_eq!(req_actions.len(), 1);
4577
4578        // Deliver request to responder
4579        let req_raw = extract_send_packet_from(&req_actions);
4580        let req_pkt = RawPacket::unpack(&req_raw).unwrap();
4581        let resp_actions = resp_mgr.handle_local_delivery(
4582            req_pkt.destination_hash,
4583            &req_raw,
4584            req_pkt.packet_hash,
4585            rns_core::transport::types::InterfaceId(0),
4586            &mut rng,
4587        );
4588
4589        // Should have a response SendPacket
4590        let has_response = resp_actions
4591            .iter()
4592            .any(|a| matches!(a, LinkManagerAction::SendPacket { .. }));
4593        assert!(has_response, "Handler should produce a response packet");
4594    }
4595
4596    #[test]
4597    fn packet_request_at_exact_size_limit_is_dispatched() {
4598        use std::sync::{Arc, Mutex};
4599
4600        let (mut init_mgr, mut resp_mgr, link_id) = setup_active_link();
4601        let mut rng = OsRng;
4602        let observed = Arc::new(Mutex::new(false));
4603        resp_mgr.register_request_handler("/bounded", None, {
4604            let observed = Arc::clone(&observed);
4605            move |_link_id, _path, _data, _remote| {
4606                *observed.lock().unwrap() = true;
4607                None
4608            }
4609        });
4610
4611        let request_actions = init_mgr.send_request(&link_id, "/bounded", b"\xc0", &mut rng);
4612        let raw = extract_any_send_packet(&request_actions);
4613        let packet = RawPacket::unpack(&raw).unwrap();
4614        let packed_size = resp_mgr.links[&link_id]
4615            .engine
4616            .decrypt(&packet.data)
4617            .unwrap()
4618            .len();
4619        resp_mgr.links.get_mut(&link_id).unwrap().max_request_size = Some(packed_size);
4620
4621        resp_mgr.handle_local_delivery(
4622            packet.destination_hash,
4623            &raw,
4624            packet.packet_hash,
4625            rns_core::transport::types::InterfaceId(0),
4626            &mut rng,
4627        );
4628
4629        assert!(*observed.lock().unwrap());
4630    }
4631
4632    #[test]
4633    fn packet_request_one_byte_over_size_limit_is_ignored_before_dispatch() {
4634        use std::sync::{Arc, Mutex};
4635
4636        let (mut init_mgr, mut resp_mgr, link_id) = setup_active_link();
4637        let mut rng = OsRng;
4638        let observed = Arc::new(Mutex::new(false));
4639        resp_mgr.register_request_handler("/bounded", None, {
4640            let observed = Arc::clone(&observed);
4641            move |_link_id, _path, _data, _remote| {
4642                *observed.lock().unwrap() = true;
4643                None
4644            }
4645        });
4646
4647        let request_actions = init_mgr.send_request(&link_id, "/bounded", b"\xc0", &mut rng);
4648        let raw = extract_any_send_packet(&request_actions);
4649        let packet = RawPacket::unpack(&raw).unwrap();
4650        let packed_size = resp_mgr.links[&link_id]
4651            .engine
4652            .decrypt(&packet.data)
4653            .unwrap()
4654            .len();
4655        resp_mgr.links.get_mut(&link_id).unwrap().max_request_size = Some(packed_size - 1);
4656
4657        let actions = resp_mgr.handle_local_delivery(
4658            packet.destination_hash,
4659            &raw,
4660            packet.packet_hash,
4661            rns_core::transport::types::InterfaceId(0),
4662            &mut rng,
4663        );
4664
4665        assert!(actions.is_empty());
4666        assert!(!*observed.lock().unwrap());
4667    }
4668
4669    #[test]
4670    fn test_send_request_wraps_invalid_msgpack_data_as_bin() {
4671        use std::sync::{Arc, Mutex};
4672
4673        let (mut init_mgr, mut resp_mgr, link_id) = setup_active_link();
4674        let mut rng = OsRng;
4675
4676        let invalid = vec![0xC1];
4677        let expected = rns_core::msgpack::pack(&rns_core::msgpack::Value::Bin(invalid.clone()));
4678        let captured = Arc::new(Mutex::new(None::<Vec<u8>>));
4679        let captured_for_handler = Arc::clone(&captured);
4680
4681        resp_mgr.register_request_handler("/bin", None, move |_link_id, _path, data, _remote| {
4682            *captured_for_handler.lock().unwrap() = Some(data.to_vec());
4683            Some(rns_core::msgpack::pack(&rns_core::msgpack::Value::Bool(
4684                true,
4685            )))
4686        });
4687
4688        let req_actions = init_mgr.send_request(&link_id, "/bin", &invalid, &mut rng);
4689        let req_raw = extract_send_packet_from(&req_actions);
4690        let req_pkt = RawPacket::unpack(&req_raw).unwrap();
4691        let resp_actions = resp_mgr.handle_local_delivery(
4692            req_pkt.destination_hash,
4693            &req_raw,
4694            req_pkt.packet_hash,
4695            rns_core::transport::types::InterfaceId(0),
4696            &mut rng,
4697        );
4698
4699        assert!(
4700            resp_actions
4701                .iter()
4702                .any(|a| matches!(a, LinkManagerAction::SendPacket { .. })),
4703            "handler should still produce a response"
4704        );
4705        assert_eq!(*captured.lock().unwrap(), Some(expected));
4706    }
4707
4708    #[test]
4709    fn test_invalid_response_bytes_are_returned_as_msgpack_bin() {
4710        let (mut init_mgr, mut resp_mgr, link_id) = setup_active_link();
4711        let mut rng = OsRng;
4712        let invalid_response = vec![0xC1];
4713        let expected =
4714            rns_core::msgpack::pack(&rns_core::msgpack::Value::Bin(invalid_response.clone()));
4715
4716        resp_mgr.register_request_handler("/invalid-response", None, {
4717            let invalid_response = invalid_response.clone();
4718            move |_link_id, _path, _data, _remote| Some(invalid_response.clone())
4719        });
4720
4721        let req_actions = init_mgr.send_request(&link_id, "/invalid-response", b"\xc0", &mut rng);
4722        let req_raw = extract_send_packet_from(&req_actions);
4723        let req_pkt = RawPacket::unpack(&req_raw).unwrap();
4724        let resp_actions = resp_mgr.handle_local_delivery(
4725            req_pkt.destination_hash,
4726            &req_raw,
4727            req_pkt.packet_hash,
4728            rns_core::transport::types::InterfaceId(0),
4729            &mut rng,
4730        );
4731
4732        let resp_raw = extract_any_send_packet(&resp_actions);
4733        let resp_pkt = RawPacket::unpack(&resp_raw).unwrap();
4734        let init_actions = init_mgr.handle_local_delivery(
4735            resp_pkt.destination_hash,
4736            &resp_raw,
4737            resp_pkt.packet_hash,
4738            rns_core::transport::types::InterfaceId(0),
4739            &mut rng,
4740        );
4741
4742        let response_data = init_actions
4743            .iter()
4744            .find_map(|action| match action {
4745                LinkManagerAction::ResponseReceived { data, .. } => Some(data.clone()),
4746                _ => None,
4747            })
4748            .expect("initiator should receive a response");
4749        assert_eq!(response_data, expected);
4750    }
4751
4752    #[test]
4753    fn packet_response_at_exact_size_limit_is_accepted() {
4754        let (mut init_mgr, mut resp_mgr, link_id) = setup_active_link();
4755        let mut rng = OsRng;
4756        let payload = vec![0xA5; 64];
4757        let response_value =
4758            rns_core::msgpack::pack(&rns_core::msgpack::Value::Bin(payload.clone()));
4759        resp_mgr.register_request_handler("/bounded-response", None, {
4760            let response_value = response_value.clone();
4761            move |_, _, _, _| Some(response_value.clone())
4762        });
4763
4764        let request_actions = init_mgr.send_request_with_max_response_size(
4765            &link_id,
4766            "/bounded-response",
4767            b"\xc0",
4768            Some(payload.len()),
4769            &mut rng,
4770        );
4771        let request_raw = extract_any_send_packet(&request_actions);
4772        let request = RawPacket::unpack(&request_raw).unwrap();
4773        let response_actions = resp_mgr.handle_local_delivery(
4774            request.destination_hash,
4775            &request_raw,
4776            request.packet_hash,
4777            rns_core::transport::types::InterfaceId(0),
4778            &mut rng,
4779        );
4780        let response_raw = extract_any_send_packet(&response_actions);
4781        let response = RawPacket::unpack(&response_raw).unwrap();
4782        let actions = init_mgr.handle_local_delivery(
4783            response.destination_hash,
4784            &response_raw,
4785            response.packet_hash,
4786            rns_core::transport::types::InterfaceId(0),
4787            &mut rng,
4788        );
4789
4790        assert!(actions.iter().any(|action| matches!(
4791            action,
4792            LinkManagerAction::ResponseReceived { data, .. } if data == &response_value
4793        )));
4794        assert!(!actions
4795            .iter()
4796            .any(|action| matches!(action, LinkManagerAction::RequestFailed { .. })));
4797    }
4798
4799    #[test]
4800    fn packet_response_one_byte_over_size_limit_fails_request() {
4801        let (mut init_mgr, mut resp_mgr, link_id) = setup_active_link();
4802        let mut rng = OsRng;
4803        let payload = vec![0xA5; 64];
4804        let response_value =
4805            rns_core::msgpack::pack(&rns_core::msgpack::Value::Bin(payload.clone()));
4806        resp_mgr.register_request_handler("/bounded-response", None, {
4807            let response_value = response_value.clone();
4808            move |_, _, _, _| Some(response_value.clone())
4809        });
4810
4811        let request_actions = init_mgr.send_request_with_max_response_size(
4812            &link_id,
4813            "/bounded-response",
4814            b"\xc0",
4815            Some(payload.len() - 1),
4816            &mut rng,
4817        );
4818        let request_raw = extract_any_send_packet(&request_actions);
4819        let request = RawPacket::unpack(&request_raw).unwrap();
4820        let request_id = request.get_truncated_hash();
4821        let response_actions = resp_mgr.handle_local_delivery(
4822            request.destination_hash,
4823            &request_raw,
4824            request.packet_hash,
4825            rns_core::transport::types::InterfaceId(0),
4826            &mut rng,
4827        );
4828        let response_raw = extract_any_send_packet(&response_actions);
4829        let response = RawPacket::unpack(&response_raw).unwrap();
4830        let actions = init_mgr.handle_local_delivery(
4831            response.destination_hash,
4832            &response_raw,
4833            response.packet_hash,
4834            rns_core::transport::types::InterfaceId(0),
4835            &mut rng,
4836        );
4837
4838        assert!(actions.iter().any(|action| matches!(
4839            action,
4840            LinkManagerAction::RequestFailed {
4841                request_id: failed_id,
4842                reason: RequestFailure::ResponseTooLarge { size: 64, maximum: 63 },
4843                ..
4844            } if failed_id == &request_id
4845        )));
4846        assert!(!actions
4847            .iter()
4848            .any(|action| matches!(action, LinkManagerAction::ResponseReceived { .. })));
4849        assert!(!init_mgr.links[&link_id]
4850            .pending_requests
4851            .contains_key(&request_id));
4852        assert!(init_mgr
4853            .handle_local_delivery(
4854                response.destination_hash,
4855                &response_raw,
4856                response.packet_hash,
4857                rns_core::transport::types::InterfaceId(0),
4858                &mut rng,
4859            )
4860            .is_empty());
4861    }
4862
4863    #[test]
4864    fn test_request_acl_deny_unidentified() {
4865        let mut rng = OsRng;
4866        let dest_hash = [0xDD; 16];
4867        let mut resp_mgr = LinkManager::new();
4868        let (sig_prv, sig_pub_bytes) = make_dest_keys(&mut rng);
4869        resp_mgr.register_link_destination(
4870            dest_hash,
4871            sig_prv,
4872            sig_pub_bytes,
4873            ResourceStrategy::AcceptNone,
4874        );
4875
4876        // Register handler with ACL (only allow specific identity)
4877        resp_mgr.register_request_handler(
4878            "/restricted",
4879            Some(vec![[0xAA; 16]]),
4880            |_link_id, _path, _data, _remote| Some(b"secret".to_vec()),
4881        );
4882
4883        let mut init_mgr = LinkManager::new();
4884
4885        // Complete handshake (without identification)
4886        let (link_id, init_actions) = init_mgr.create_link(
4887            &dest_hash,
4888            &sig_pub_bytes,
4889            1,
4890            constants::MTU as u32,
4891            &mut rng,
4892        );
4893        let lr_raw = extract_send_packet(&init_actions);
4894        let lr_pkt = RawPacket::unpack(&lr_raw).unwrap();
4895        let resp_actions = resp_mgr.handle_local_delivery(
4896            lr_pkt.destination_hash,
4897            &lr_raw,
4898            lr_pkt.packet_hash,
4899            rns_core::transport::types::InterfaceId(0),
4900            &mut rng,
4901        );
4902        let lrproof_raw = extract_send_packet_at(&resp_actions, 1);
4903        let lrproof_pkt = RawPacket::unpack(&lrproof_raw).unwrap();
4904        let init_actions2 = init_mgr.handle_local_delivery(
4905            lrproof_pkt.destination_hash,
4906            &lrproof_raw,
4907            lrproof_pkt.packet_hash,
4908            rns_core::transport::types::InterfaceId(0),
4909            &mut rng,
4910        );
4911        let lrrtt_raw = extract_any_send_packet(&init_actions2);
4912        let lrrtt_pkt = RawPacket::unpack(&lrrtt_raw).unwrap();
4913        resp_mgr.handle_local_delivery(
4914            lrrtt_pkt.destination_hash,
4915            &lrrtt_raw,
4916            lrrtt_pkt.packet_hash,
4917            rns_core::transport::types::InterfaceId(0),
4918            &mut rng,
4919        );
4920
4921        // Send request without identifying first
4922        let req_actions = init_mgr.send_request(&link_id, "/restricted", b"query", &mut rng);
4923        let req_raw = extract_send_packet_from(&req_actions);
4924        let req_pkt = RawPacket::unpack(&req_raw).unwrap();
4925        let resp_actions = resp_mgr.handle_local_delivery(
4926            req_pkt.destination_hash,
4927            &req_raw,
4928            req_pkt.packet_hash,
4929            rns_core::transport::types::InterfaceId(0),
4930            &mut rng,
4931        );
4932
4933        // Should be denied — no response packet
4934        let has_response = resp_actions
4935            .iter()
4936            .any(|a| matches!(a, LinkManagerAction::SendPacket { .. }));
4937        assert!(!has_response, "Unidentified peer should be denied");
4938    }
4939
4940    #[test]
4941    fn test_teardown_link() {
4942        let mut rng = OsRng;
4943        let dest_hash = [0xDD; 16];
4944        let mut mgr = LinkManager::new();
4945
4946        let dummy_sig = [0xAA; 32];
4947        let (link_id, _) =
4948            mgr.create_link(&dest_hash, &dummy_sig, 1, constants::MTU as u32, &mut rng);
4949        assert_eq!(mgr.link_count(), 1);
4950
4951        let actions = mgr.teardown_link(&link_id);
4952        let has_close = actions
4953            .iter()
4954            .any(|a| matches!(a, LinkManagerAction::LinkClosed { .. }));
4955        assert!(has_close);
4956        assert!(!actions
4957            .iter()
4958            .any(|a| matches!(a, LinkManagerAction::SendPacket { .. })));
4959        assert!(mgr.teardown_link(&link_id).is_empty());
4960
4961        // After tick, closed links should be cleaned up
4962        let tick_actions = mgr.tick(&mut rng);
4963        let has_deregister = tick_actions
4964            .iter()
4965            .any(|a| matches!(a, LinkManagerAction::DeregisterLinkDest { .. }));
4966        assert!(has_deregister);
4967        assert_eq!(mgr.link_count(), 0);
4968    }
4969
4970    #[test]
4971    fn active_teardown_sends_linkclose_and_remote_closed_teardown_is_noop() {
4972        let (mut initiator, mut responder, link_id) = setup_active_link();
4973        let actions = initiator.teardown_link(&link_id);
4974        let close_packet = actions
4975            .iter()
4976            .find_map(|action| match action {
4977                LinkManagerAction::SendPacket { raw, .. } => RawPacket::unpack(raw)
4978                    .ok()
4979                    .filter(|packet| packet.context == constants::CONTEXT_LINKCLOSE),
4980                _ => None,
4981            })
4982            .expect("active teardown must emit LINKCLOSE");
4983        assert!(close_packet.raw.len() > constants::HEADER_MINSIZE);
4984        let plaintext = responder
4985            .links
4986            .get(&link_id)
4987            .unwrap()
4988            .engine
4989            .decrypt(&close_packet.data)
4990            .expect("LINKCLOSE payload must authenticate");
4991        assert_eq!(plaintext, link_id);
4992        assert!(initiator.teardown_link(&link_id).is_empty());
4993
4994        responder
4995            .links
4996            .get_mut(&link_id)
4997            .unwrap()
4998            .engine
4999            .handle_teardown();
5000        assert!(responder.teardown_link(&link_id).is_empty());
5001    }
5002
5003    #[test]
5004    fn forged_or_empty_linkclose_does_not_close_an_active_link() {
5005        let (_initiator, mut responder, link_id) = setup_active_link();
5006        let flags = PacketFlags {
5007            header_type: constants::HEADER_1,
5008            context_flag: constants::FLAG_UNSET,
5009            transport_type: constants::TRANSPORT_BROADCAST,
5010            destination_type: constants::DESTINATION_LINK,
5011            packet_type: constants::PACKET_TYPE_DATA,
5012        };
5013        let mut rng = OsRng;
5014
5015        for forged_payload in [Vec::new(), vec![0x55; 48]] {
5016            let packet = RawPacket::pack(
5017                flags,
5018                0,
5019                &link_id,
5020                None,
5021                constants::CONTEXT_LINKCLOSE,
5022                &forged_payload,
5023            )
5024            .unwrap();
5025            let actions = responder.handle_local_delivery(
5026                link_id,
5027                &packet.raw,
5028                [0u8; 32],
5029                rns_core::transport::types::InterfaceId(1),
5030                &mut rng,
5031            );
5032
5033            assert_eq!(responder.link_state(&link_id), Some(LinkState::Active));
5034            assert!(!actions
5035                .iter()
5036                .any(|action| matches!(action, LinkManagerAction::LinkClosed { .. })));
5037        }
5038    }
5039
5040    #[test]
5041    fn test_identify_on_link() {
5042        let mut rng = OsRng;
5043        let dest_hash = [0xDD; 16];
5044        let mut resp_mgr = LinkManager::new();
5045        let (sig_prv, sig_pub_bytes) = make_dest_keys(&mut rng);
5046        resp_mgr.register_link_destination(
5047            dest_hash,
5048            sig_prv,
5049            sig_pub_bytes,
5050            ResourceStrategy::AcceptNone,
5051        );
5052        let mut init_mgr = LinkManager::new();
5053
5054        // Complete handshake
5055        let (link_id, init_actions) = init_mgr.create_link(
5056            &dest_hash,
5057            &sig_pub_bytes,
5058            1,
5059            constants::MTU as u32,
5060            &mut rng,
5061        );
5062        let lr_raw = extract_send_packet(&init_actions);
5063        let lr_pkt = RawPacket::unpack(&lr_raw).unwrap();
5064        let resp_actions = resp_mgr.handle_local_delivery(
5065            lr_pkt.destination_hash,
5066            &lr_raw,
5067            lr_pkt.packet_hash,
5068            rns_core::transport::types::InterfaceId(0),
5069            &mut rng,
5070        );
5071        let lrproof_raw = extract_send_packet_at(&resp_actions, 1);
5072        let lrproof_pkt = RawPacket::unpack(&lrproof_raw).unwrap();
5073        let init_actions2 = init_mgr.handle_local_delivery(
5074            lrproof_pkt.destination_hash,
5075            &lrproof_raw,
5076            lrproof_pkt.packet_hash,
5077            rns_core::transport::types::InterfaceId(0),
5078            &mut rng,
5079        );
5080        let lrrtt_raw = extract_any_send_packet(&init_actions2);
5081        let lrrtt_pkt = RawPacket::unpack(&lrrtt_raw).unwrap();
5082        resp_mgr.handle_local_delivery(
5083            lrrtt_pkt.destination_hash,
5084            &lrrtt_raw,
5085            lrrtt_pkt.packet_hash,
5086            rns_core::transport::types::InterfaceId(0),
5087            &mut rng,
5088        );
5089
5090        // Identify initiator to responder
5091        let identity = Identity::new(&mut rng);
5092        let id_actions = init_mgr.identify(&link_id, &identity, &mut rng);
5093        assert_eq!(id_actions.len(), 1);
5094
5095        // Deliver identify to responder
5096        let id_raw = extract_send_packet_from(&id_actions);
5097        let id_pkt = RawPacket::unpack(&id_raw).unwrap();
5098        let resp_actions = resp_mgr.handle_local_delivery(
5099            id_pkt.destination_hash,
5100            &id_raw,
5101            id_pkt.packet_hash,
5102            rns_core::transport::types::InterfaceId(0),
5103            &mut rng,
5104        );
5105
5106        let has_identified = resp_actions
5107            .iter()
5108            .any(|a| matches!(a, LinkManagerAction::RemoteIdentified { .. }));
5109        assert!(has_identified, "Responder should emit RemoteIdentified");
5110    }
5111
5112    #[test]
5113    fn test_path_hash_computation() {
5114        let h1 = compute_path_hash("/status");
5115        let h2 = compute_path_hash("/path");
5116        assert_ne!(h1, h2);
5117
5118        // Deterministic
5119        assert_eq!(h1, compute_path_hash("/status"));
5120    }
5121
5122    #[test]
5123    fn test_link_count() {
5124        let mut mgr = LinkManager::new();
5125        let mut rng = OsRng;
5126
5127        assert_eq!(mgr.link_count(), 0);
5128
5129        let dummy_sig = [0xAA; 32];
5130        mgr.create_link(&[0x11; 16], &dummy_sig, 1, constants::MTU as u32, &mut rng);
5131        assert_eq!(mgr.link_count(), 1);
5132
5133        mgr.create_link(&[0x22; 16], &dummy_sig, 1, constants::MTU as u32, &mut rng);
5134        assert_eq!(mgr.link_count(), 2);
5135    }
5136
5137    // --- Test helpers ---
5138
5139    fn extract_send_packet(actions: &[LinkManagerAction]) -> Vec<u8> {
5140        extract_send_packet_at(actions, actions.len() - 1)
5141    }
5142
5143    fn extract_send_packet_at(actions: &[LinkManagerAction], idx: usize) -> Vec<u8> {
5144        match &actions[idx] {
5145            LinkManagerAction::SendPacket { raw, .. } => raw.clone(),
5146            other => panic!("Expected SendPacket at index {}, got {:?}", idx, other),
5147        }
5148    }
5149
5150    fn extract_any_send_packet(actions: &[LinkManagerAction]) -> Vec<u8> {
5151        actions
5152            .iter()
5153            .find_map(|a| match a {
5154                LinkManagerAction::SendPacket { raw, .. } => Some(raw.clone()),
5155                _ => None,
5156            })
5157            .expect("Expected at least one SendPacket action")
5158    }
5159
5160    fn extract_send_packet_from(actions: &[LinkManagerAction]) -> Vec<u8> {
5161        extract_any_send_packet(actions)
5162    }
5163
5164    /// Set up two linked managers with an active link.
5165    /// Returns (initiator_mgr, responder_mgr, link_id).
5166    fn setup_active_link() -> (LinkManager, LinkManager, LinkId) {
5167        setup_active_link_with_max_request_size(None)
5168    }
5169
5170    fn setup_active_link_with_max_request_size(
5171        max_request_size: Option<usize>,
5172    ) -> (LinkManager, LinkManager, LinkId) {
5173        let mut rng = OsRng;
5174        let dest_hash = [0xDD; 16];
5175        let mut resp_mgr = LinkManager::new();
5176        let (sig_prv, sig_pub_bytes) = make_dest_keys(&mut rng);
5177        resp_mgr.register_link_destination(
5178            dest_hash,
5179            sig_prv,
5180            sig_pub_bytes,
5181            ResourceStrategy::AcceptNone,
5182        );
5183        assert!(resp_mgr.set_link_destination_max_request_size(&dest_hash, max_request_size));
5184        let mut init_mgr = LinkManager::new();
5185
5186        let (link_id, init_actions) = init_mgr.create_link(
5187            &dest_hash,
5188            &sig_pub_bytes,
5189            1,
5190            constants::MTU as u32,
5191            &mut rng,
5192        );
5193        let lr_raw = extract_send_packet(&init_actions);
5194        let lr_pkt = RawPacket::unpack(&lr_raw).unwrap();
5195        let resp_actions = resp_mgr.handle_local_delivery(
5196            lr_pkt.destination_hash,
5197            &lr_raw,
5198            lr_pkt.packet_hash,
5199            rns_core::transport::types::InterfaceId(0),
5200            &mut rng,
5201        );
5202        let lrproof_raw = extract_send_packet_at(&resp_actions, 1);
5203        let lrproof_pkt = RawPacket::unpack(&lrproof_raw).unwrap();
5204        let init_actions2 = init_mgr.handle_local_delivery(
5205            lrproof_pkt.destination_hash,
5206            &lrproof_raw,
5207            lrproof_pkt.packet_hash,
5208            rns_core::transport::types::InterfaceId(0),
5209            &mut rng,
5210        );
5211        let lrrtt_raw = extract_any_send_packet(&init_actions2);
5212        let lrrtt_pkt = RawPacket::unpack(&lrrtt_raw).unwrap();
5213        resp_mgr.handle_local_delivery(
5214            lrrtt_pkt.destination_hash,
5215            &lrrtt_raw,
5216            lrrtt_pkt.packet_hash,
5217            rns_core::transport::types::InterfaceId(0),
5218            &mut rng,
5219        );
5220
5221        assert_eq!(init_mgr.link_state(&link_id), Some(LinkState::Active));
5222        assert_eq!(resp_mgr.link_state(&link_id), Some(LinkState::Active));
5223
5224        (init_mgr, resp_mgr, link_id)
5225    }
5226
5227    #[test]
5228    fn request_size_limit_is_inherited_when_link_is_accepted() {
5229        let (_init_mgr, resp_mgr, link_id) = setup_active_link_with_max_request_size(Some(321));
5230        assert_eq!(resp_mgr.links[&link_id].max_request_size, Some(321));
5231    }
5232
5233    #[test]
5234    fn request_size_limit_updates_existing_responder_links() {
5235        let (_init_mgr, mut resp_mgr, link_id) = setup_active_link();
5236        let dest_hash = resp_mgr.links[&link_id].dest_hash;
5237        assert!(resp_mgr.set_link_destination_max_request_size(&dest_hash, Some(654)));
5238        assert_eq!(resp_mgr.links[&link_id].max_request_size, Some(654));
5239    }
5240
5241    #[test]
5242    fn setting_request_size_limit_for_unknown_destination_fails() {
5243        let mut mgr = LinkManager::new();
5244        assert!(!mgr.set_link_destination_max_request_size(&[0xEE; 16], Some(1)));
5245    }
5246
5247    fn packed_keepalive(link_id: &LinkId, payload: &[u8]) -> (Vec<u8>, RawPacket) {
5248        let flags = PacketFlags {
5249            header_type: constants::HEADER_1,
5250            context_flag: constants::FLAG_UNSET,
5251            transport_type: constants::TRANSPORT_BROADCAST,
5252            destination_type: constants::DESTINATION_LINK,
5253            packet_type: constants::PACKET_TYPE_DATA,
5254        };
5255        let (raw, _) = RawPacket::pack_raw_with_hash(
5256            flags,
5257            0,
5258            link_id,
5259            None,
5260            constants::CONTEXT_KEEPALIVE,
5261            payload,
5262        )
5263        .unwrap();
5264        let packet = RawPacket::unpack(&raw).unwrap();
5265        (raw, packet)
5266    }
5267
5268    fn keepalive_packets(actions: &[LinkManagerAction]) -> Vec<RawPacket> {
5269        actions
5270            .iter()
5271            .filter_map(|action| match action {
5272                LinkManagerAction::SendPacket { raw, .. } => RawPacket::unpack(raw).ok(),
5273                _ => None,
5274            })
5275            .filter(|packet| packet.context == constants::CONTEXT_KEEPALIVE)
5276            .collect()
5277    }
5278
5279    #[test]
5280    fn initiator_tick_emits_ff_keepalive_probe_when_outbound_is_quiet() {
5281        let (mut initiator, _, link_id) = setup_active_link();
5282        let now = time::now();
5283        let keepalive = initiator.links[&link_id].engine.keepalive_interval();
5284        let engine = &mut initiator.links.get_mut(&link_id).unwrap().engine;
5285        engine.record_inbound(now);
5286        engine.record_outbound(now - keepalive - 1.0, true);
5287
5288        let packets = keepalive_packets(&initiator.tick(&mut OsRng));
5289        assert_eq!(packets.len(), 1);
5290        assert_eq!(packets[0].data, [0xff]);
5291    }
5292
5293    #[test]
5294    fn responder_tick_never_originates_keepalive_probe() {
5295        let (_, mut responder, link_id) = setup_active_link();
5296        let now = time::now();
5297        let keepalive = responder.links[&link_id].engine.keepalive_interval();
5298        let engine = &mut responder.links.get_mut(&link_id).unwrap().engine;
5299        engine.record_inbound(now - keepalive - 1.0);
5300        engine.record_outbound(now - keepalive - 1.0, false);
5301
5302        assert!(keepalive_packets(&responder.tick(&mut OsRng)).is_empty());
5303    }
5304
5305    #[test]
5306    fn responder_acknowledges_ff_probe_after_outbound_silence() {
5307        let (_, mut responder, link_id) = setup_active_link();
5308        let now = time::now();
5309        let keepalive = responder.links[&link_id].engine.keepalive_interval();
5310        responder
5311            .links
5312            .get_mut(&link_id)
5313            .unwrap()
5314            .engine
5315            .record_outbound(now - keepalive - 1.0, false);
5316        let (raw, packet) = packed_keepalive(&link_id, &[0xff]);
5317
5318        let actions = responder.handle_local_delivery(
5319            packet.destination_hash,
5320            &raw,
5321            packet.packet_hash,
5322            rns_core::transport::types::InterfaceId(0),
5323            &mut OsRng,
5324        );
5325        let packets = keepalive_packets(&actions);
5326        assert_eq!(packets.len(), 1);
5327        assert_eq!(packets[0].data, [0xfe]);
5328    }
5329
5330    #[test]
5331    fn recent_responder_traffic_suppresses_keepalive_ack() {
5332        let (_, mut responder, link_id) = setup_active_link();
5333        responder
5334            .links
5335            .get_mut(&link_id)
5336            .unwrap()
5337            .engine
5338            .record_outbound(time::now(), false);
5339        let (raw, packet) = packed_keepalive(&link_id, &[0xff]);
5340
5341        let actions = responder.handle_local_delivery(
5342            packet.destination_hash,
5343            &raw,
5344            packet.packet_hash,
5345            rns_core::transport::types::InterfaceId(0),
5346            &mut OsRng,
5347        );
5348        assert!(keepalive_packets(&actions).is_empty());
5349    }
5350
5351    #[test]
5352    fn keepalive_ack_does_not_create_ping_pong_reply() {
5353        let (mut initiator, _, link_id) = setup_active_link();
5354        let (raw, packet) = packed_keepalive(&link_id, &[0xfe]);
5355
5356        let actions = initiator.handle_local_delivery(
5357            packet.destination_hash,
5358            &raw,
5359            packet.packet_hash,
5360            rns_core::transport::types::InterfaceId(0),
5361            &mut OsRng,
5362        );
5363        assert!(keepalive_packets(&actions).is_empty());
5364    }
5365
5366    // ====================================================================
5367    // Phase 8a: Resource wiring tests
5368    // ====================================================================
5369
5370    #[test]
5371    fn test_resource_strategy_default() {
5372        let mut mgr = LinkManager::new();
5373        let mut rng = OsRng;
5374        let dummy_sig = [0xAA; 32];
5375        let (link_id, _) =
5376            mgr.create_link(&[0x11; 16], &dummy_sig, 1, constants::MTU as u32, &mut rng);
5377
5378        // Default strategy is AcceptNone
5379        let link = mgr.links.get(&link_id).unwrap();
5380        assert_eq!(link.resource_strategy, ResourceStrategy::AcceptNone);
5381    }
5382
5383    #[test]
5384    fn test_set_resource_strategy() {
5385        let mut mgr = LinkManager::new();
5386        let mut rng = OsRng;
5387        let dummy_sig = [0xAA; 32];
5388        let (link_id, _) =
5389            mgr.create_link(&[0x11; 16], &dummy_sig, 1, constants::MTU as u32, &mut rng);
5390
5391        mgr.set_resource_strategy(&link_id, ResourceStrategy::AcceptAll);
5392        assert_eq!(
5393            mgr.links.get(&link_id).unwrap().resource_strategy,
5394            ResourceStrategy::AcceptAll
5395        );
5396
5397        mgr.set_resource_strategy(&link_id, ResourceStrategy::AcceptApp);
5398        assert_eq!(
5399            mgr.links.get(&link_id).unwrap().resource_strategy,
5400            ResourceStrategy::AcceptApp
5401        );
5402    }
5403
5404    #[test]
5405    fn test_send_resource_on_active_link() {
5406        let (mut init_mgr, _resp_mgr, link_id) = setup_active_link();
5407        let mut rng = OsRng;
5408
5409        // Send resource data
5410        let data = vec![0xAB; 100]; // small enough for a single part
5411        let actions = init_mgr.send_resource(&link_id, &data, None, &mut rng);
5412
5413        // Should produce at least a SendPacket (advertisement)
5414        let has_send = actions
5415            .iter()
5416            .any(|a| matches!(a, LinkManagerAction::SendPacket { .. }));
5417        assert!(
5418            has_send,
5419            "send_resource should emit advertisement SendPacket"
5420        );
5421    }
5422
5423    #[test]
5424    fn test_large_send_request_uses_request_resource() {
5425        let (mut init_mgr, _resp_mgr, link_id) = setup_active_link();
5426        let mut rng = OsRng;
5427
5428        let data = vec![0xAB; 2048];
5429        let actions = init_mgr.send_request(&link_id, "/large", &data, &mut rng);
5430
5431        let adv = first_resource_advertisement(&init_mgr, &link_id, &actions);
5432        assert!(adv.is_request());
5433        assert!(!adv.is_response());
5434        assert_eq!(adv.request_id.as_ref().map(Vec::len), Some(16));
5435        let request_id = LinkManager::response_request_id(&adv.request_id).unwrap();
5436        assert_eq!(
5437            init_mgr.links[&link_id]
5438                .pending_requests
5439                .get(&request_id)
5440                .and_then(|request| request.deadline),
5441            None,
5442            "resource request timeout must wait for delivery proof"
5443        );
5444        assert!(init_mgr.links[&link_id]
5445            .pending_requests
5446            .contains_key(&request_id));
5447
5448        let has_request_packet = actions.iter().any(|action| match action {
5449            LinkManagerAction::SendPacket { raw, .. } => RawPacket::unpack(raw)
5450                .map(|pkt| pkt.context == constants::CONTEXT_REQUEST)
5451                .unwrap_or(false),
5452            _ => false,
5453        });
5454        assert!(!has_request_packet);
5455    }
5456
5457    #[test]
5458    fn unanswered_packet_request_expires_from_pending_set() {
5459        let (mut init_mgr, _resp_mgr, link_id) = setup_active_link();
5460        let mut rng = OsRng;
5461        let actions = init_mgr.send_request(&link_id, "/no-response", b"\xc0", &mut rng);
5462        let raw = extract_any_send_packet(&actions);
5463        let request_id = RawPacket::unpack(&raw).unwrap().get_truncated_hash();
5464        assert!(init_mgr.links[&link_id]
5465            .pending_requests
5466            .get(&request_id)
5467            .is_some_and(|request| request.deadline.is_some()));
5468
5469        init_mgr
5470            .links
5471            .get_mut(&link_id)
5472            .unwrap()
5473            .pending_requests
5474            .get_mut(&request_id)
5475            .unwrap()
5476            .deadline = Some(time::now() - 1.0);
5477        init_mgr.tick(&mut rng);
5478
5479        assert!(!init_mgr.links[&link_id]
5480            .pending_requests
5481            .contains_key(&request_id));
5482    }
5483
5484    #[test]
5485    fn invalid_resource_advertisement_tears_down_link_once() {
5486        let (_init_mgr, mut resp_mgr, link_id) = setup_active_link();
5487        let mut rng = OsRng;
5488
5489        let actions = resp_mgr.handle_resource_adv(&link_id, &[0xc1], &mut rng);
5490
5491        assert_eq!(resp_mgr.link_state(&link_id), Some(LinkState::Closed));
5492        assert_eq!(
5493            actions
5494                .iter()
5495                .filter(|action| matches!(action, LinkManagerAction::LinkClosed { .. }))
5496                .count(),
5497            1
5498        );
5499        assert!(resp_mgr
5500            .handle_resource_adv(&link_id, &[0xc1], &mut rng)
5501            .is_empty());
5502    }
5503
5504    #[test]
5505    fn oversized_resource_advertisement_tears_down_link() {
5506        let (mut init_mgr, mut resp_mgr, link_id) = setup_active_link();
5507        let mut rng = OsRng;
5508        let adv_actions = init_mgr.send_resource_with_auto_compress(
5509            &link_id,
5510            &deterministic_bytes(1024),
5511            None,
5512            false,
5513            &mut rng,
5514        );
5515        let mut adv = first_resource_advertisement(&init_mgr, &link_id, &adv_actions);
5516        adv.transfer_size = (constants::RESOURCE_MAX_EFFICIENT_SIZE * 3 + 1) as u64;
5517
5518        let actions = resp_mgr.handle_resource_adv(&link_id, &adv.pack(0), &mut rng);
5519
5520        assert_eq!(resp_mgr.link_state(&link_id), Some(LinkState::Closed));
5521        assert!(actions
5522            .iter()
5523            .any(|action| matches!(action, LinkManagerAction::LinkClosed { .. })));
5524    }
5525
5526    #[test]
5527    fn request_resource_without_any_handler_is_ignored() {
5528        let (mut init_mgr, mut resp_mgr, link_id) = setup_active_link();
5529        let mut rng = OsRng;
5530        let data = deterministic_bytes(4096);
5531
5532        let adv_actions = init_mgr.send_request(&link_id, "/missing", &data, &mut rng);
5533        let adv_raw = extract_any_send_packet(&adv_actions);
5534        let adv_pkt = RawPacket::unpack(&adv_raw).unwrap();
5535        assert_eq!(adv_pkt.context, constants::CONTEXT_RESOURCE_ADV);
5536
5537        let actions = resp_mgr.handle_local_delivery(
5538            adv_pkt.destination_hash,
5539            &adv_raw,
5540            adv_pkt.packet_hash,
5541            rns_core::transport::types::InterfaceId(0),
5542            &mut rng,
5543        );
5544
5545        assert!(
5546            actions.is_empty(),
5547            "unhandled request resource must be ignored"
5548        );
5549        assert!(resp_mgr.links[&link_id].incoming_resources.is_empty());
5550    }
5551
5552    #[test]
5553    fn request_resource_at_exact_declared_size_limit_is_accepted() {
5554        let (mut init_mgr, mut resp_mgr, link_id) = setup_active_link();
5555        let mut rng = OsRng;
5556        resp_mgr.register_request_handler("/bounded", None, |_, _, _, _| None);
5557        let request = deterministic_bytes(4096);
5558        let adv_actions = init_mgr.send_request(&link_id, "/bounded", &request, &mut rng);
5559        let adv = first_resource_advertisement(&init_mgr, &link_id, &adv_actions);
5560        resp_mgr.links.get_mut(&link_id).unwrap().max_request_size = Some(adv.data_size as usize);
5561        let raw = extract_any_send_packet(&adv_actions);
5562        let packet = RawPacket::unpack(&raw).unwrap();
5563
5564        let actions = resp_mgr.handle_local_delivery(
5565            packet.destination_hash,
5566            &raw,
5567            packet.packet_hash,
5568            rns_core::transport::types::InterfaceId(0),
5569            &mut rng,
5570        );
5571
5572        assert!(
5573            !actions.is_empty(),
5574            "accepted resource should request parts"
5575        );
5576        assert_eq!(resp_mgr.links[&link_id].incoming_resources.len(), 1);
5577    }
5578
5579    #[test]
5580    fn request_resource_one_byte_over_declared_size_limit_is_rejected() {
5581        let (mut init_mgr, mut resp_mgr, link_id) = setup_active_link();
5582        let mut rng = OsRng;
5583        resp_mgr.register_request_handler("/bounded", None, |_, _, _, _| None);
5584        let adv_actions =
5585            init_mgr.send_request(&link_id, "/bounded", &deterministic_bytes(4096), &mut rng);
5586        let adv = first_resource_advertisement(&init_mgr, &link_id, &adv_actions);
5587        resp_mgr.links.get_mut(&link_id).unwrap().max_request_size =
5588            Some(adv.data_size as usize - 1);
5589        let raw = extract_any_send_packet(&adv_actions);
5590        let packet = RawPacket::unpack(&raw).unwrap();
5591
5592        let actions = resp_mgr.handle_local_delivery(
5593            packet.destination_hash,
5594            &raw,
5595            packet.packet_hash,
5596            rns_core::transport::types::InterfaceId(0),
5597            &mut rng,
5598        );
5599
5600        assert!(!actions.is_empty(), "rejected resource should send an RCL");
5601        assert!(resp_mgr.links[&link_id].incoming_resources.is_empty());
5602    }
5603
5604    #[test]
5605    fn request_size_limit_does_not_reject_ordinary_resources() {
5606        let (mut init_mgr, mut resp_mgr, link_id) = setup_active_link();
5607        let mut rng = OsRng;
5608        let link = resp_mgr.links.get_mut(&link_id).unwrap();
5609        link.max_request_size = Some(0);
5610        link.resource_strategy = ResourceStrategy::AcceptAll;
5611        let adv_actions = init_mgr.send_resource_with_auto_compress(
5612            &link_id,
5613            &deterministic_bytes(4096),
5614            None,
5615            false,
5616            &mut rng,
5617        );
5618        let raw = extract_any_send_packet(&adv_actions);
5619        let packet = RawPacket::unpack(&raw).unwrap();
5620
5621        let actions = resp_mgr.handle_local_delivery(
5622            packet.destination_hash,
5623            &raw,
5624            packet.packet_hash,
5625            rns_core::transport::types::InterfaceId(0),
5626            &mut rng,
5627        );
5628
5629        assert!(!actions.is_empty(), "ordinary resource should be accepted");
5630        assert_eq!(resp_mgr.links[&link_id].incoming_resources.len(), 1);
5631    }
5632
5633    #[test]
5634    fn unsolicited_response_resource_is_ignored() {
5635        let (mut init_mgr, mut resp_mgr, link_id) = setup_active_link();
5636        let mut rng = OsRng;
5637        let unsolicited_id = [0xEF; 16];
5638
5639        let adv_actions = resp_mgr.send_response_resource(
5640            &link_id,
5641            &unsolicited_id,
5642            &deterministic_bytes(4096),
5643            None,
5644            false,
5645            &mut rng,
5646        );
5647        let adv_raw = extract_any_send_packet(&adv_actions);
5648        let adv_pkt = RawPacket::unpack(&adv_raw).unwrap();
5649        let actions = init_mgr.handle_local_delivery(
5650            adv_pkt.destination_hash,
5651            &adv_raw,
5652            adv_pkt.packet_hash,
5653            rns_core::transport::types::InterfaceId(0),
5654            &mut rng,
5655        );
5656
5657        assert!(
5658            actions.is_empty(),
5659            "unsolicited response resource must be ignored"
5660        );
5661        assert!(init_mgr.links[&link_id].incoming_resources.is_empty());
5662    }
5663
5664    #[test]
5665    fn resource_packets_are_suppressed_after_link_closes() {
5666        let (mut init_mgr, _resp_mgr, link_id) = setup_active_link();
5667        let mut rng = OsRng;
5668        init_mgr.send_resource_with_auto_compress(
5669            &link_id,
5670            &deterministic_bytes(4096),
5671            None,
5672            false,
5673            &mut rng,
5674        );
5675        init_mgr
5676            .links
5677            .get_mut(&link_id)
5678            .unwrap()
5679            .engine
5680            .handle_teardown();
5681
5682        for action in [
5683            ResourceAction::SendAdvertisement(vec![1]),
5684            ResourceAction::SendPart(vec![2]),
5685            ResourceAction::SendRequest(vec![3]),
5686            ResourceAction::SendHmu(vec![4]),
5687            ResourceAction::SendProof(vec![5; 64]),
5688        ] {
5689            let emitted = init_mgr.process_resource_actions(&link_id, vec![action], &mut rng);
5690            assert!(
5691                !emitted
5692                    .iter()
5693                    .any(|item| matches!(item, LinkManagerAction::SendPacket { .. })),
5694                "closed link emitted a resource packet"
5695            );
5696        }
5697        assert!(init_mgr.links[&link_id]
5698            .outgoing_resources
5699            .iter()
5700            .all(|sender| sender.status == rns_core::resource::ResourceStatus::Failed));
5701    }
5702
5703    #[test]
5704    fn large_request_resource_reaches_registered_handler_and_returns_response() {
5705        use std::sync::{Arc, Mutex};
5706
5707        let (mut init_mgr, mut resp_mgr, link_id) = setup_active_link();
5708        let mut rng = OsRng;
5709        let request_value =
5710            rns_core::msgpack::pack(&rns_core::msgpack::Value::Bin(deterministic_bytes(4096)));
5711        let observed = Arc::new(Mutex::new(None));
5712        resp_mgr.register_request_handler("/large-request", None, {
5713            let observed = Arc::clone(&observed);
5714            move |_link_id, _path, data, _remote| {
5715                *observed.lock().unwrap() = Some(data.to_vec());
5716                Some(rns_core::msgpack::pack(&rns_core::msgpack::Value::Bool(
5717                    true,
5718                )))
5719            }
5720        });
5721
5722        let initial = init_mgr.send_request(&link_id, "/large-request", &request_value, &mut rng);
5723        let mut pending: Vec<(char, LinkManagerAction)> =
5724            initial.into_iter().map(|action| ('i', action)).collect();
5725        let mut response = None;
5726
5727        for _ in 0..300 {
5728            if pending.is_empty() || response.is_some() {
5729                break;
5730            }
5731            let mut next = Vec::new();
5732            for (source, action) in pending.drain(..) {
5733                let LinkManagerAction::SendPacket { raw, .. } = action else {
5734                    continue;
5735                };
5736                let packet = RawPacket::unpack(&raw).unwrap();
5737                let actions = if source == 'i' {
5738                    resp_mgr.handle_local_delivery(
5739                        packet.destination_hash,
5740                        &raw,
5741                        packet.packet_hash,
5742                        rns_core::transport::types::InterfaceId(0),
5743                        &mut rng,
5744                    )
5745                } else {
5746                    init_mgr.handle_local_delivery(
5747                        packet.destination_hash,
5748                        &raw,
5749                        packet.packet_hash,
5750                        rns_core::transport::types::InterfaceId(0),
5751                        &mut rng,
5752                    )
5753                };
5754                for action in &actions {
5755                    if let LinkManagerAction::ResponseReceived { data, .. } = action {
5756                        response = Some(data.clone());
5757                    }
5758                    assert!(!matches!(
5759                        action,
5760                        LinkManagerAction::ResourceAcceptQuery { .. }
5761                    ));
5762                }
5763                let next_source = if source == 'i' { 'r' } else { 'i' };
5764                next.extend(actions.into_iter().map(|action| (next_source, action)));
5765            }
5766            pending = next;
5767        }
5768
5769        assert_eq!(*observed.lock().unwrap(), Some(request_value));
5770        assert_eq!(
5771            response,
5772            Some(rns_core::msgpack::pack(&rns_core::msgpack::Value::Bool(
5773                true
5774            )))
5775        );
5776        assert!(init_mgr.links[&link_id].pending_requests.is_empty());
5777    }
5778
5779    fn first_resource_advertisement(
5780        mgr: &LinkManager,
5781        link_id: &LinkId,
5782        actions: &[LinkManagerAction],
5783    ) -> rns_core::resource::ResourceAdvertisement {
5784        let adv_raw = actions
5785            .iter()
5786            .find_map(|action| match action {
5787                LinkManagerAction::SendPacket { raw, .. } => {
5788                    let pkt = RawPacket::unpack(raw).ok()?;
5789                    (pkt.context == constants::CONTEXT_RESOURCE_ADV).then_some(raw)
5790                }
5791                _ => None,
5792            })
5793            .expect("sender should emit a resource advertisement");
5794        let adv_pkt = RawPacket::unpack(adv_raw).unwrap();
5795        let plaintext = mgr
5796            .links
5797            .get(link_id)
5798            .unwrap()
5799            .engine
5800            .decrypt(&adv_pkt.data)
5801            .unwrap();
5802        rns_core::resource::ResourceAdvertisement::unpack(&plaintext).unwrap()
5803    }
5804
5805    fn deterministic_bytes(len: usize) -> Vec<u8> {
5806        let mut state = 0x1234_5678u32;
5807        (0..len)
5808            .map(|_| {
5809                state = state.wrapping_mul(1664525).wrapping_add(1013904223);
5810                (state >> 16) as u8
5811            })
5812            .collect()
5813    }
5814
5815    struct CountingReader {
5816        inner: std::io::Cursor<Vec<u8>>,
5817        bytes_read: std::sync::Arc<std::sync::atomic::AtomicUsize>,
5818    }
5819
5820    impl Read for CountingReader {
5821        fn read(&mut self, buffer: &mut [u8]) -> std::io::Result<usize> {
5822            let count = self.inner.read(buffer)?;
5823            self.bytes_read
5824                .fetch_add(count, std::sync::atomic::Ordering::Relaxed);
5825            Ok(count)
5826        }
5827    }
5828
5829    #[test]
5830    fn streaming_resource_reads_one_segment_at_a_time_and_receives_to_disk() {
5831        let (mut init_mgr, mut resp_mgr, link_id) = setup_active_link();
5832        let mut rng = OsRng;
5833        let directory = tempfile::tempdir().unwrap();
5834        resp_mgr.set_resource_strategy(&link_id, ResourceStrategy::AcceptAll);
5835        resp_mgr.set_resource_receive_mode(
5836            &link_id,
5837            ResourceReceiveMode::TemporaryFile {
5838                directory: directory.path().to_path_buf(),
5839                max_bytes: None,
5840            },
5841        );
5842
5843        let data = deterministic_bytes(constants::RESOURCE_MAX_EFFICIENT_SIZE * 2 + 12345);
5844        let bytes_read = std::sync::Arc::new(std::sync::atomic::AtomicUsize::new(0));
5845        let reader = CountingReader {
5846            inner: std::io::Cursor::new(data.clone()),
5847            bytes_read: bytes_read.clone(),
5848        };
5849        let initial = init_mgr.send_resource_stream(
5850            &link_id,
5851            ResourceTransferId(42),
5852            Box::new(reader),
5853            data.len() as u64,
5854            None,
5855            false,
5856            &mut rng,
5857        );
5858
5859        assert_eq!(
5860            bytes_read.load(std::sync::atomic::Ordering::Relaxed),
5861            constants::RESOURCE_MAX_EFFICIENT_SIZE,
5862            "starting a stream must not consume later segments"
5863        );
5864        assert_eq!(init_mgr.links[&link_id].outgoing_resources.len(), 1);
5865
5866        let (received, completed, _progress, failures, _rounds) = drive_link_manager_packets(
5867            &mut init_mgr,
5868            &mut resp_mgr,
5869            initial,
5870            'i',
5871            &mut rng,
5872            30_000,
5873        );
5874        assert_eq!(received, Some(data));
5875        assert!(completed);
5876        assert!(
5877            failures.is_empty(),
5878            "streaming transfer failed: {failures:?}"
5879        );
5880        assert_eq!(
5881            bytes_read.load(std::sync::atomic::Ordering::Relaxed),
5882            constants::RESOURCE_MAX_EFFICIENT_SIZE * 2 + 12345
5883        );
5884        assert_eq!(std::fs::read_dir(directory.path()).unwrap().count(), 0);
5885    }
5886
5887    fn drive_link_manager_packets(
5888        init_mgr: &mut LinkManager,
5889        resp_mgr: &mut LinkManager,
5890        initial_actions: Vec<LinkManagerAction>,
5891        initial_source: char,
5892        rng: &mut dyn Rng,
5893        max_rounds: usize,
5894    ) -> (
5895        Option<Vec<u8>>,
5896        bool,
5897        Vec<(char, usize, usize)>,
5898        Vec<(char, String)>,
5899        usize,
5900    ) {
5901        let mut pending: Vec<(char, LinkManagerAction)> = initial_actions
5902            .into_iter()
5903            .map(|a| (initial_source, a))
5904            .collect();
5905        let mut rounds = 0;
5906        let mut received_data = None;
5907        let mut sender_completed = false;
5908        let mut progress = Vec::new();
5909        let mut failures = Vec::new();
5910
5911        while !pending.is_empty() && rounds < max_rounds {
5912            rounds += 1;
5913            let mut next = Vec::new();
5914            for (source, action) in pending.drain(..) {
5915                let LinkManagerAction::SendPacket { raw, .. } = action else {
5916                    continue;
5917                };
5918                let pkt = RawPacket::unpack(&raw).unwrap();
5919                let target_actions = if source == 'i' {
5920                    resp_mgr.handle_local_delivery(
5921                        pkt.destination_hash,
5922                        &raw,
5923                        pkt.packet_hash,
5924                        rns_core::transport::types::InterfaceId(0),
5925                        rng,
5926                    )
5927                } else {
5928                    init_mgr.handle_local_delivery(
5929                        pkt.destination_hash,
5930                        &raw,
5931                        pkt.packet_hash,
5932                        rns_core::transport::types::InterfaceId(0),
5933                        rng,
5934                    )
5935                };
5936                let target_source = if source == 'i' { 'r' } else { 'i' };
5937                for action in &target_actions {
5938                    match action {
5939                        LinkManagerAction::ResourceReceived { data, .. } => {
5940                            received_data = Some(data.clone());
5941                        }
5942                        LinkManagerAction::ResourceFileReceived { resource, .. } => {
5943                            received_data = Some(std::fs::read(resource.path()).unwrap());
5944                        }
5945                        LinkManagerAction::ResourceCompleted { .. } => {
5946                            sender_completed = true;
5947                        }
5948                        LinkManagerAction::ResourceStreamCompleted { .. } => {
5949                            sender_completed = true;
5950                        }
5951                        LinkManagerAction::ResourceProgress {
5952                            received, total, ..
5953                        } => {
5954                            progress.push((target_source, *received, *total));
5955                        }
5956                        LinkManagerAction::ResourceFailed { error, .. } => {
5957                            failures.push((target_source, error.clone()));
5958                        }
5959                        LinkManagerAction::ResourceStreamFailed { error, .. } => {
5960                            failures.push((target_source, format!("{error:?}")));
5961                        }
5962                        _ => {}
5963                    }
5964                }
5965                next.extend(target_actions.into_iter().map(|a| (target_source, a)));
5966            }
5967            pending = next;
5968        }
5969
5970        (received_data, sender_completed, progress, failures, rounds)
5971    }
5972
5973    #[test]
5974    fn test_send_resource_auto_compress_option_controls_adv_flag() {
5975        let data = vec![0x41; 2048];
5976
5977        let (mut compressed_mgr, _resp_mgr, link_id) = setup_active_link();
5978        let mut rng = OsRng;
5979        let actions =
5980            compressed_mgr.send_resource_with_auto_compress(&link_id, &data, None, true, &mut rng);
5981        let adv = first_resource_advertisement(&compressed_mgr, &link_id, &actions);
5982        assert!(
5983            adv.flags.compressed,
5984            "compressible resource should compress"
5985        );
5986
5987        let (mut plain_mgr, _resp_mgr, link_id) = setup_active_link();
5988        let mut rng = OsRng;
5989        let actions =
5990            plain_mgr.send_resource_with_auto_compress(&link_id, &data, None, false, &mut rng);
5991        let adv = first_resource_advertisement(&plain_mgr, &link_id, &actions);
5992        assert!(
5993            !adv.flags.compressed,
5994            "auto_compress=false should keep resource uncompressed"
5995        );
5996    }
5997
5998    #[test]
5999    fn test_send_resource_on_inactive_link() {
6000        let mut mgr = LinkManager::new();
6001        let mut rng = OsRng;
6002        let dummy_sig = [0xAA; 32];
6003        let (link_id, _) =
6004            mgr.create_link(&[0x11; 16], &dummy_sig, 1, constants::MTU as u32, &mut rng);
6005
6006        // Link is Pending, not Active
6007        let actions = mgr.send_resource(&link_id, b"data", None, &mut rng);
6008        assert!(actions.is_empty(), "Cannot send resource on inactive link");
6009    }
6010
6011    #[test]
6012    fn test_send_resource_without_session_key_uses_encrypt_fallback_path() {
6013        let (mut init_mgr, _resp_mgr, link_id) = setup_active_link();
6014        let mut rng = OsRng;
6015        init_mgr
6016            .links
6017            .get_mut(&link_id)
6018            .unwrap()
6019            .engine
6020            .clear_session_for_testing();
6021
6022        let actions = init_mgr.send_resource(&link_id, b"data", None, &mut rng);
6023
6024        assert!(
6025            actions.is_empty(),
6026            "without a session key, no advertisement should be emitted"
6027        );
6028        assert_eq!(
6029            init_mgr
6030                .links
6031                .get(&link_id)
6032                .map(|managed| managed.outgoing_resources.len()),
6033            Some(1)
6034        );
6035    }
6036
6037    #[test]
6038    fn test_resource_adv_rejected_by_accept_none() {
6039        let (mut init_mgr, mut resp_mgr, link_id) = setup_active_link();
6040        let mut rng = OsRng;
6041
6042        // Responder uses default AcceptNone strategy
6043        // Send resource from initiator
6044        let data = vec![0xCD; 100];
6045        let adv_actions = init_mgr.send_resource(&link_id, &data, None, &mut rng);
6046
6047        // Deliver advertisement to responder
6048        for action in &adv_actions {
6049            if let LinkManagerAction::SendPacket { raw, .. } = action {
6050                let pkt = RawPacket::unpack(raw).unwrap();
6051                let resp_actions = resp_mgr.handle_local_delivery(
6052                    pkt.destination_hash,
6053                    raw,
6054                    pkt.packet_hash,
6055                    rns_core::transport::types::InterfaceId(0),
6056                    &mut rng,
6057                );
6058                // AcceptNone: should not produce ResourceReceived, may produce SendPacket (RCL)
6059                let has_resource_received = resp_actions
6060                    .iter()
6061                    .any(|a| matches!(a, LinkManagerAction::ResourceReceived { .. }));
6062                assert!(
6063                    !has_resource_received,
6064                    "AcceptNone should not accept resource"
6065                );
6066            }
6067        }
6068    }
6069
6070    #[test]
6071    fn test_resource_adv_accepted_by_accept_all() {
6072        let (mut init_mgr, mut resp_mgr, link_id) = setup_active_link();
6073        let mut rng = OsRng;
6074
6075        // Set responder to AcceptAll
6076        resp_mgr.set_resource_strategy(&link_id, ResourceStrategy::AcceptAll);
6077
6078        // Send resource from initiator
6079        let data = vec![0xCD; 100];
6080        let adv_actions = init_mgr.send_resource(&link_id, &data, None, &mut rng);
6081
6082        // Deliver advertisement to responder
6083        for action in &adv_actions {
6084            if let LinkManagerAction::SendPacket { raw, .. } = action {
6085                let pkt = RawPacket::unpack(raw).unwrap();
6086                let resp_actions = resp_mgr.handle_local_delivery(
6087                    pkt.destination_hash,
6088                    raw,
6089                    pkt.packet_hash,
6090                    rns_core::transport::types::InterfaceId(0),
6091                    &mut rng,
6092                );
6093                // AcceptAll: should accept and produce a SendPacket (request for parts)
6094                let has_send = resp_actions
6095                    .iter()
6096                    .any(|a| matches!(a, LinkManagerAction::SendPacket { .. }));
6097                assert!(has_send, "AcceptAll should accept and request parts");
6098            }
6099        }
6100    }
6101
6102    #[test]
6103    fn test_resource_accept_app_query() {
6104        let (mut init_mgr, mut resp_mgr, link_id) = setup_active_link();
6105        let mut rng = OsRng;
6106
6107        // Set responder to AcceptApp
6108        resp_mgr.set_resource_strategy(&link_id, ResourceStrategy::AcceptApp);
6109
6110        // Send resource from initiator
6111        let data = vec![0xCD; 100];
6112        let adv_actions = init_mgr.send_resource(&link_id, &data, None, &mut rng);
6113
6114        // Deliver advertisement to responder
6115        let mut got_query = false;
6116        for action in &adv_actions {
6117            if let LinkManagerAction::SendPacket { raw, .. } = action {
6118                let pkt = RawPacket::unpack(raw).unwrap();
6119                let resp_actions = resp_mgr.handle_local_delivery(
6120                    pkt.destination_hash,
6121                    raw,
6122                    pkt.packet_hash,
6123                    rns_core::transport::types::InterfaceId(0),
6124                    &mut rng,
6125                );
6126                for a in &resp_actions {
6127                    if matches!(a, LinkManagerAction::ResourceAcceptQuery { .. }) {
6128                        got_query = true;
6129                    }
6130                }
6131            }
6132        }
6133        assert!(got_query, "AcceptApp should emit ResourceAcceptQuery");
6134    }
6135
6136    #[test]
6137    fn test_resource_accept_app_accept() {
6138        let (mut init_mgr, mut resp_mgr, link_id) = setup_active_link();
6139        let mut rng = OsRng;
6140
6141        resp_mgr.set_resource_strategy(&link_id, ResourceStrategy::AcceptApp);
6142
6143        let data = vec![0xCD; 100];
6144        let adv_actions = init_mgr.send_resource(&link_id, &data, None, &mut rng);
6145
6146        for action in &adv_actions {
6147            if let LinkManagerAction::SendPacket { raw, .. } = action {
6148                let pkt = RawPacket::unpack(raw).unwrap();
6149                let resp_actions = resp_mgr.handle_local_delivery(
6150                    pkt.destination_hash,
6151                    raw,
6152                    pkt.packet_hash,
6153                    rns_core::transport::types::InterfaceId(0),
6154                    &mut rng,
6155                );
6156                for a in &resp_actions {
6157                    if let LinkManagerAction::ResourceAcceptQuery {
6158                        link_id: lid,
6159                        resource_hash,
6160                        ..
6161                    } = a
6162                    {
6163                        // Accept the resource
6164                        let accept_actions =
6165                            resp_mgr.accept_resource(lid, resource_hash, true, &mut rng);
6166                        // Should produce a SendPacket (request for parts)
6167                        let has_send = accept_actions
6168                            .iter()
6169                            .any(|a| matches!(a, LinkManagerAction::SendPacket { .. }));
6170                        assert!(
6171                            has_send,
6172                            "Accepting resource should produce request for parts"
6173                        );
6174                    }
6175                }
6176            }
6177        }
6178    }
6179
6180    #[test]
6181    fn test_resource_accept_app_reject() {
6182        let (mut init_mgr, mut resp_mgr, link_id) = setup_active_link();
6183        let mut rng = OsRng;
6184
6185        resp_mgr.set_resource_strategy(&link_id, ResourceStrategy::AcceptApp);
6186
6187        let data = vec![0xCD; 100];
6188        let adv_actions = init_mgr.send_resource(&link_id, &data, None, &mut rng);
6189
6190        for action in &adv_actions {
6191            if let LinkManagerAction::SendPacket { raw, .. } = action {
6192                let pkt = RawPacket::unpack(raw).unwrap();
6193                let resp_actions = resp_mgr.handle_local_delivery(
6194                    pkt.destination_hash,
6195                    raw,
6196                    pkt.packet_hash,
6197                    rns_core::transport::types::InterfaceId(0),
6198                    &mut rng,
6199                );
6200                for a in &resp_actions {
6201                    if let LinkManagerAction::ResourceAcceptQuery {
6202                        link_id: lid,
6203                        resource_hash,
6204                        ..
6205                    } = a
6206                    {
6207                        // Reject the resource
6208                        let reject_actions =
6209                            resp_mgr.accept_resource(lid, resource_hash, false, &mut rng);
6210                        // Rejecting should send a cancel and not request parts
6211                        // No ResourceReceived should appear
6212                        let has_resource_received = reject_actions
6213                            .iter()
6214                            .any(|a| matches!(a, LinkManagerAction::ResourceReceived { .. }));
6215                        assert!(!has_resource_received);
6216                    }
6217                }
6218            }
6219        }
6220    }
6221
6222    #[test]
6223    fn test_resource_full_transfer() {
6224        let (mut init_mgr, mut resp_mgr, link_id) = setup_active_link();
6225        let mut rng = OsRng;
6226
6227        // Set responder to AcceptAll
6228        resp_mgr.set_resource_strategy(&link_id, ResourceStrategy::AcceptAll);
6229
6230        // Small data (fits in single SDU)
6231        let original_data = b"Hello, Resource Transfer!".to_vec();
6232        let adv_actions = init_mgr.send_resource(&link_id, &original_data, None, &mut rng);
6233
6234        // Drive the full transfer protocol between the two managers.
6235        // Tag each SendPacket with its source ('i' = initiator, 'r' = responder).
6236        let mut pending: Vec<(char, LinkManagerAction)> =
6237            adv_actions.into_iter().map(|a| ('i', a)).collect();
6238        let mut rounds = 0;
6239        let max_rounds = 50;
6240        let mut resource_received = false;
6241        let mut sender_completed = false;
6242
6243        while !pending.is_empty() && rounds < max_rounds {
6244            rounds += 1;
6245            let mut next: Vec<(char, LinkManagerAction)> = Vec::new();
6246
6247            for (source, action) in pending.drain(..) {
6248                if let LinkManagerAction::SendPacket { raw, .. } = action {
6249                    let pkt = RawPacket::unpack(&raw).unwrap();
6250
6251                    // Deliver only to the OTHER side
6252                    let target_actions = if source == 'i' {
6253                        resp_mgr.handle_local_delivery(
6254                            pkt.destination_hash,
6255                            &raw,
6256                            pkt.packet_hash,
6257                            rns_core::transport::types::InterfaceId(0),
6258                            &mut rng,
6259                        )
6260                    } else {
6261                        init_mgr.handle_local_delivery(
6262                            pkt.destination_hash,
6263                            &raw,
6264                            pkt.packet_hash,
6265                            rns_core::transport::types::InterfaceId(0),
6266                            &mut rng,
6267                        )
6268                    };
6269
6270                    let target_source = if source == 'i' { 'r' } else { 'i' };
6271                    for a in &target_actions {
6272                        match a {
6273                            LinkManagerAction::ResourceReceived { data, .. } => {
6274                                assert_eq!(*data, original_data);
6275                                resource_received = true;
6276                            }
6277                            LinkManagerAction::ResourceCompleted { .. } => {
6278                                sender_completed = true;
6279                            }
6280                            _ => {}
6281                        }
6282                    }
6283                    next.extend(target_actions.into_iter().map(|a| (target_source, a)));
6284                }
6285            }
6286            pending = next;
6287        }
6288
6289        assert!(
6290            resource_received,
6291            "Responder should receive resource data (rounds={})",
6292            rounds
6293        );
6294        assert!(
6295            sender_completed,
6296            "Sender should get completion proof (rounds={})",
6297            rounds
6298        );
6299    }
6300
6301    #[test]
6302    fn test_split_resource_advertisement_and_progress_entries() {
6303        let (mut init_mgr, _resp_mgr, link_id) = setup_active_link();
6304        let mut rng = OsRng;
6305        let data = deterministic_bytes(constants::RESOURCE_MAX_EFFICIENT_SIZE + 1024);
6306
6307        let actions =
6308            init_mgr.send_resource_with_auto_compress(&link_id, &data, None, false, &mut rng);
6309        let adv = first_resource_advertisement(&init_mgr, &link_id, &actions);
6310
6311        assert!(adv.flags.split);
6312        assert_eq!(adv.segment_index, 1);
6313        assert_eq!(adv.total_segments, 2);
6314        assert_eq!(adv.data_size, data.len() as u64);
6315
6316        let managed = init_mgr.links.get(&link_id).unwrap();
6317        assert_eq!(managed.outgoing_splits.len(), 1);
6318        assert_eq!(
6319            managed
6320                .outgoing_resources
6321                .iter()
6322                .filter(|sender| sender.flags.split)
6323                .count(),
6324            2
6325        );
6326
6327        let entries = init_mgr.resource_entries();
6328        assert_eq!(entries.len(), 1);
6329        assert_eq!(entries[0].direction, "outgoing");
6330        assert!(entries[0].total_parts > managed.outgoing_resources[0].total_parts());
6331        assert_eq!(entries[0].transferred_parts, 0);
6332    }
6333
6334    #[test]
6335    fn test_split_resource_full_transfer_and_monotonic_progress() {
6336        let (mut init_mgr, mut resp_mgr, link_id) = setup_active_link();
6337        let mut rng = OsRng;
6338        resp_mgr.set_resource_strategy(&link_id, ResourceStrategy::AcceptAll);
6339
6340        let original_data = deterministic_bytes(constants::RESOURCE_MAX_EFFICIENT_SIZE + 2048);
6341        let initial_actions = init_mgr.send_resource_with_auto_compress(
6342            &link_id,
6343            &original_data,
6344            None,
6345            false,
6346            &mut rng,
6347        );
6348
6349        let (received_data, sender_completed, progress, failures, rounds) =
6350            drive_link_manager_packets(
6351                &mut init_mgr,
6352                &mut resp_mgr,
6353                initial_actions,
6354                'i',
6355                &mut rng,
6356                10_000,
6357            );
6358
6359        assert!(
6360            received_data.as_ref().is_some_and(|data| data == &original_data),
6361            "split transfer did not deliver payload in {rounds} rounds; sender_completed={sender_completed}; failures={failures:?}; last_progress={:?}; init_entries={:?}; resp_entries={:?}",
6362            progress.last(),
6363            init_mgr.resource_entries(),
6364            resp_mgr.resource_entries()
6365        );
6366        assert!(
6367            sender_completed,
6368            "sender did not complete in {rounds} rounds"
6369        );
6370        assert!(
6371            progress
6372                .iter()
6373                .any(|(_, received, total)| received == total),
6374            "expected final progress update"
6375        );
6376
6377        let mut init_last = 0;
6378        let mut resp_last = 0;
6379        for (side, received, total) in progress {
6380            assert!(received <= total);
6381            match side {
6382                'i' => {
6383                    assert!(
6384                        received >= init_last,
6385                        "initiator progress regressed from {init_last} to {received}"
6386                    );
6387                    init_last = received;
6388                }
6389                'r' => {
6390                    assert!(
6391                        received >= resp_last,
6392                        "responder progress regressed from {resp_last} to {received}"
6393                    );
6394                    resp_last = received;
6395                }
6396                _ => unreachable!(),
6397            }
6398        }
6399    }
6400
6401    #[test]
6402    fn test_split_resource_accept_app_queries_only_first_segment() {
6403        let (mut init_mgr, mut resp_mgr, link_id) = setup_active_link();
6404        let mut rng = OsRng;
6405        resp_mgr.set_resource_strategy(&link_id, ResourceStrategy::AcceptApp);
6406
6407        let original_data = deterministic_bytes(constants::RESOURCE_MAX_EFFICIENT_SIZE + 1024);
6408        let adv_actions = init_mgr.send_resource_with_auto_compress(
6409            &link_id,
6410            &original_data,
6411            None,
6412            false,
6413            &mut rng,
6414        );
6415        let adv_raw = extract_any_send_packet(&adv_actions);
6416        let adv_pkt = RawPacket::unpack(&adv_raw).unwrap();
6417        let query_actions = resp_mgr.handle_local_delivery(
6418            adv_pkt.destination_hash,
6419            &adv_raw,
6420            adv_pkt.packet_hash,
6421            rns_core::transport::types::InterfaceId(0),
6422            &mut rng,
6423        );
6424
6425        let queries: Vec<_> = query_actions
6426            .iter()
6427            .filter_map(|action| match action {
6428                LinkManagerAction::ResourceAcceptQuery { resource_hash, .. } => {
6429                    Some(resource_hash.clone())
6430                }
6431                _ => None,
6432            })
6433            .collect();
6434        assert_eq!(queries.len(), 1);
6435
6436        let accept_actions = resp_mgr.accept_resource(&link_id, &queries[0], true, &mut rng);
6437        let (received_data, sender_completed, _progress, failures, rounds) =
6438            drive_link_manager_packets(
6439                &mut init_mgr,
6440                &mut resp_mgr,
6441                accept_actions,
6442                'r',
6443                &mut rng,
6444                10_000,
6445            );
6446
6447        assert!(
6448            failures.is_empty(),
6449            "split AcceptApp transfer failed: {failures:?}"
6450        );
6451        assert!(
6452            received_data
6453                .as_ref()
6454                .is_some_and(|data| data == &original_data),
6455            "split AcceptApp transfer did not deliver in {rounds} rounds"
6456        );
6457        assert!(sender_completed);
6458    }
6459
6460    #[test]
6461    fn test_resource_cancel_icl() {
6462        let (mut init_mgr, mut resp_mgr, link_id) = setup_active_link();
6463        let mut rng = OsRng;
6464
6465        resp_mgr.set_resource_strategy(&link_id, ResourceStrategy::AcceptAll);
6466
6467        // Use large data so transfer is multi-part
6468        let data = vec![0xAB; 2000];
6469        let adv_actions = init_mgr.send_resource(&link_id, &data, None, &mut rng);
6470
6471        // Deliver advertisement — responder accepts and sends request
6472        for action in &adv_actions {
6473            if let LinkManagerAction::SendPacket { raw, .. } = action {
6474                let pkt = RawPacket::unpack(raw).unwrap();
6475                resp_mgr.handle_local_delivery(
6476                    pkt.destination_hash,
6477                    raw,
6478                    pkt.packet_hash,
6479                    rns_core::transport::types::InterfaceId(0),
6480                    &mut rng,
6481                );
6482            }
6483        }
6484
6485        // Verify there are incoming resources on the responder
6486        assert!(!resp_mgr
6487            .links
6488            .get(&link_id)
6489            .unwrap()
6490            .incoming_resources
6491            .is_empty());
6492
6493        // Simulate ICL (cancel from initiator side) by calling handle_resource_icl
6494        let icl_actions = resp_mgr.handle_resource_icl(&link_id);
6495
6496        // Should have resource failed
6497        let has_failed = icl_actions
6498            .iter()
6499            .any(|a| matches!(a, LinkManagerAction::ResourceFailed { .. }));
6500        assert!(has_failed, "ICL should produce ResourceFailed");
6501    }
6502
6503    #[test]
6504    fn test_resource_cancel_rcl() {
6505        let (mut init_mgr, _resp_mgr, link_id) = setup_active_link();
6506        let mut rng = OsRng;
6507
6508        // Create a resource sender
6509        let data = vec![0xAB; 2000];
6510        init_mgr.send_resource(&link_id, &data, None, &mut rng);
6511
6512        // Verify there are outgoing resources
6513        assert!(!init_mgr
6514            .links
6515            .get(&link_id)
6516            .unwrap()
6517            .outgoing_resources
6518            .is_empty());
6519
6520        // Simulate RCL (cancel from receiver side)
6521        let rcl_actions = init_mgr.handle_resource_rcl(&link_id);
6522
6523        let has_failed = rcl_actions
6524            .iter()
6525            .any(|a| matches!(a, LinkManagerAction::ResourceFailed { .. }));
6526        assert!(has_failed, "RCL should produce ResourceFailed");
6527    }
6528
6529    #[test]
6530    fn test_cancel_all_resources_clears_active_transfers() {
6531        let (mut init_mgr, _resp_mgr, link_id) = setup_active_link();
6532        let mut rng = OsRng;
6533
6534        let actions = init_mgr.send_resource(&link_id, b"resource body", None, &mut rng);
6535        assert!(!actions.is_empty());
6536        assert_eq!(init_mgr.resource_transfer_count(), 1);
6537
6538        let cancel_actions = init_mgr.cancel_all_resources(&mut rng);
6539
6540        assert_eq!(init_mgr.resource_transfer_count(), 0);
6541        assert!(cancel_actions.iter().any(|action| match action {
6542            LinkManagerAction::SendPacket { raw, .. } => RawPacket::unpack(raw)
6543                .map(|packet| packet.context == constants::CONTEXT_RESOURCE_ICL)
6544                .unwrap_or(false),
6545            _ => false,
6546        }));
6547    }
6548
6549    #[test]
6550    fn cancelling_stream_reports_one_terminal_failure() {
6551        let (mut init_mgr, _resp_mgr, link_id) = setup_active_link();
6552        let mut rng = OsRng;
6553        let transfer_id = ResourceTransferId(42);
6554        let length = constants::RESOURCE_MAX_EFFICIENT_SIZE as u64 + 1;
6555        init_mgr.send_resource_stream(
6556            &link_id,
6557            transfer_id,
6558            Box::new(std::io::Cursor::new(vec![0x5a; length as usize])),
6559            length,
6560            None,
6561            false,
6562            &mut rng,
6563        );
6564
6565        let actions = init_mgr.cancel_all_resources(&mut rng);
6566        assert_eq!(
6567            actions
6568                .iter()
6569                .filter(|action| matches!(
6570                    action,
6571                    LinkManagerAction::ResourceStreamFailed {
6572                        transfer_id: failed,
6573                        error: ResourceTransferError::Cancelled,
6574                        ..
6575                    } if *failed == transfer_id
6576                ))
6577                .count(),
6578            1
6579        );
6580        assert_eq!(init_mgr.resource_transfer_count(), 0);
6581    }
6582
6583    #[test]
6584    fn receiver_cancel_sends_rcl_while_active_and_closed_cleanup_is_local_only() {
6585        let (mut init_mgr, mut resp_mgr, link_id) = setup_active_link();
6586        let mut rng = OsRng;
6587        resp_mgr.set_resource_strategy(&link_id, ResourceStrategy::AcceptApp);
6588
6589        let advertisement = init_mgr.send_resource(&link_id, b"incoming body", None, &mut rng);
6590        let raw = extract_any_send_packet(&advertisement);
6591        let packet = RawPacket::unpack(&raw).unwrap();
6592        resp_mgr.handle_local_delivery(
6593            packet.destination_hash,
6594            &raw,
6595            packet.packet_hash,
6596            rns_core::transport::types::InterfaceId(0),
6597            &mut rng,
6598        );
6599        assert_eq!(resp_mgr.resource_transfer_count(), 1);
6600        let active_cancel = resp_mgr.cancel_all_resources(&mut rng);
6601        assert_eq!(resp_mgr.resource_transfer_count(), 0);
6602        assert!(active_cancel.iter().any(|action| match action {
6603            LinkManagerAction::SendPacket { raw, .. } => RawPacket::unpack(raw)
6604                .map(|packet| packet.context == constants::CONTEXT_RESOURCE_RCL)
6605                .unwrap_or(false),
6606            _ => false,
6607        }));
6608
6609        let (mut init_mgr, mut resp_mgr, link_id) = setup_active_link();
6610        resp_mgr.set_resource_strategy(&link_id, ResourceStrategy::AcceptApp);
6611        let advertisement = init_mgr.send_resource(&link_id, b"second body", None, &mut rng);
6612        let raw = extract_any_send_packet(&advertisement);
6613        let packet = RawPacket::unpack(&raw).unwrap();
6614        resp_mgr.handle_local_delivery(
6615            packet.destination_hash,
6616            &raw,
6617            packet.packet_hash,
6618            rns_core::transport::types::InterfaceId(0),
6619            &mut rng,
6620        );
6621        resp_mgr
6622            .links
6623            .get_mut(&link_id)
6624            .unwrap()
6625            .engine
6626            .handle_teardown();
6627        let closed_cancel = resp_mgr.cancel_all_resources(&mut rng);
6628        assert_eq!(resp_mgr.resource_transfer_count(), 0);
6629        assert!(!closed_cancel
6630            .iter()
6631            .any(|action| matches!(action, LinkManagerAction::SendPacket { .. })));
6632    }
6633
6634    #[test]
6635    fn stale_sender_cancel_cleans_up_without_icl() {
6636        let (mut init_mgr, _resp_mgr, link_id) = setup_active_link();
6637        let mut rng = OsRng;
6638        init_mgr.send_resource(&link_id, b"stale transfer", None, &mut rng);
6639        init_mgr
6640            .links
6641            .get_mut(&link_id)
6642            .unwrap()
6643            .engine
6644            .tick(time::now() + 100_000.0);
6645        assert_eq!(init_mgr.link_state(&link_id), Some(LinkState::Stale));
6646
6647        let actions = init_mgr.cancel_all_resources(&mut rng);
6648        assert_eq!(init_mgr.resource_transfer_count(), 0);
6649        assert!(!actions
6650            .iter()
6651            .any(|action| matches!(action, LinkManagerAction::SendPacket { .. })));
6652    }
6653
6654    #[test]
6655    fn test_resource_tick_cleans_up() {
6656        let (mut init_mgr, _resp_mgr, link_id) = setup_active_link();
6657        let mut rng = OsRng;
6658
6659        let data = vec![0xAB; 100];
6660        init_mgr.send_resource(&link_id, &data, None, &mut rng);
6661
6662        assert!(!init_mgr
6663            .links
6664            .get(&link_id)
6665            .unwrap()
6666            .outgoing_resources
6667            .is_empty());
6668
6669        // Cancel the sender to make it Complete
6670        init_mgr.handle_resource_rcl(&link_id);
6671
6672        // Tick should clean up completed resources
6673        init_mgr.tick(&mut rng);
6674
6675        assert!(
6676            init_mgr
6677                .links
6678                .get(&link_id)
6679                .unwrap()
6680                .outgoing_resources
6681                .is_empty(),
6682            "Tick should clean up completed/failed outgoing resources"
6683        );
6684    }
6685
6686    #[test]
6687    fn test_build_link_packet() {
6688        let (init_mgr, _resp_mgr, link_id) = setup_active_link();
6689
6690        let actions =
6691            init_mgr.build_link_packet(&link_id, constants::CONTEXT_RESOURCE, b"test data");
6692        assert_eq!(actions.len(), 1);
6693        if let LinkManagerAction::SendPacket { raw, dest_type, .. } = &actions[0] {
6694            let pkt = RawPacket::unpack(raw).unwrap();
6695            assert_eq!(pkt.context, constants::CONTEXT_RESOURCE);
6696            assert_eq!(*dest_type, constants::DESTINATION_LINK);
6697        } else {
6698            panic!("Expected SendPacket");
6699        }
6700    }
6701
6702    #[test]
6703    fn link_traffic_accounting_uses_ciphertext_data_once() {
6704        let (mut initiator, mut responder, link_id) = setup_active_link();
6705        let before_tx = initiator.links[&link_id].engine.tx_packets();
6706        let before_tx_bytes = initiator.links[&link_id].engine.tx_bytes();
6707        let before_rx = responder.links[&link_id].engine.rx_packets();
6708        let before_rx_bytes = responder.links[&link_id].engine.rx_bytes();
6709        let actions = initiator.send_on_link(
6710            &link_id,
6711            b"short plaintext",
6712            constants::CONTEXT_NONE,
6713            &mut OsRng,
6714        );
6715        let raw = actions
6716            .iter()
6717            .find_map(|action| match action {
6718                LinkManagerAction::SendPacket { raw, .. } => Some(raw.clone()),
6719                _ => None,
6720            })
6721            .unwrap();
6722        let packet = RawPacket::unpack(&raw).unwrap();
6723        assert!(packet.data.len() > b"short plaintext".len());
6724
6725        initiator.record_outbound_packet(&packet);
6726        assert_eq!(initiator.links[&link_id].engine.tx_packets(), before_tx + 1);
6727        assert_eq!(
6728            initiator.links[&link_id].engine.tx_bytes(),
6729            before_tx_bytes + packet.data.len() as u64
6730        );
6731
6732        responder.handle_local_delivery(
6733            link_id,
6734            &raw,
6735            packet.packet_hash,
6736            rns_core::transport::types::InterfaceId(1),
6737            &mut OsRng,
6738        );
6739        assert_eq!(responder.links[&link_id].engine.rx_packets(), before_rx + 1);
6740        assert_eq!(
6741            responder.links[&link_id].engine.rx_bytes(),
6742            before_rx_bytes + packet.data.len() as u64
6743        );
6744    }
6745
6746    #[test]
6747    fn test_build_link_packet_returns_empty_when_mtu_too_small() {
6748        let (mut init_mgr, _resp_mgr, link_id) = setup_active_link();
6749        init_mgr.set_link_mtu(&link_id, 84);
6750
6751        let actions =
6752            init_mgr.build_link_packet(&link_id, constants::CONTEXT_RESOURCE, &[0xAA; 200]);
6753        assert!(actions.is_empty(), "oversized packet should not be built");
6754    }
6755
6756    #[test]
6757    fn test_process_resource_actions_encrypted_variants_drop_on_encrypt_failure() {
6758        let (mut init_mgr, _resp_mgr, link_id) = setup_active_link();
6759        let mut rng = OsRng;
6760        init_mgr
6761            .links
6762            .get_mut(&link_id)
6763            .unwrap()
6764            .engine
6765            .clear_session_for_testing();
6766
6767        let cases = vec![
6768            ResourceAction::SendAdvertisement(vec![1, 2, 3]),
6769            ResourceAction::SendRequest(vec![4, 5, 6]),
6770            ResourceAction::SendHmu(vec![7, 8, 9]),
6771            ResourceAction::SendCancelInitiator(vec![13, 14, 15]),
6772            ResourceAction::SendCancelReceiver(vec![16, 17, 18]),
6773        ];
6774
6775        for action in cases {
6776            let out = init_mgr.process_resource_actions(&link_id, vec![action], &mut rng);
6777            assert!(
6778                out.is_empty(),
6779                "encrypt failure should suppress packet emission"
6780            );
6781        }
6782    }
6783
6784    #[test]
6785    fn test_resource_proof_is_raw_proof_packet() {
6786        let (mut init_mgr, _resp_mgr, link_id) = setup_active_link();
6787        let mut rng = OsRng;
6788        let proof = vec![0x22; 64];
6789
6790        let out = init_mgr.process_resource_actions(
6791            &link_id,
6792            vec![ResourceAction::SendProof(proof.clone())],
6793            &mut rng,
6794        );
6795        let raw = extract_any_send_packet(&out);
6796        let pkt = RawPacket::unpack(&raw).unwrap();
6797
6798        assert_eq!(pkt.flags.packet_type, constants::PACKET_TYPE_PROOF);
6799        assert_eq!(pkt.context, constants::CONTEXT_RESOURCE_PRF);
6800        assert_eq!(pkt.data, proof);
6801    }
6802
6803    // ====================================================================
6804    // Phase 8b: Channel message & data callback tests
6805    // ====================================================================
6806
6807    #[test]
6808    fn test_channel_message_delivery() {
6809        let (mut init_mgr, mut resp_mgr, link_id) = setup_active_link();
6810        let mut rng = OsRng;
6811
6812        // Send channel message from initiator
6813        let chan_actions = init_mgr
6814            .send_channel_message(&link_id, 42, b"channel data", &mut rng)
6815            .expect("active link channel send should succeed");
6816        assert!(!chan_actions.is_empty());
6817
6818        // Deliver to responder
6819        let mut got_channel_msg = false;
6820        for action in &chan_actions {
6821            if let LinkManagerAction::SendPacket { raw, .. } = action {
6822                let pkt = RawPacket::unpack(raw).unwrap();
6823                let resp_actions = resp_mgr.handle_local_delivery(
6824                    pkt.destination_hash,
6825                    raw,
6826                    pkt.packet_hash,
6827                    rns_core::transport::types::InterfaceId(0),
6828                    &mut rng,
6829                );
6830                for a in &resp_actions {
6831                    if let LinkManagerAction::ChannelMessageReceived {
6832                        msgtype, payload, ..
6833                    } = a
6834                    {
6835                        assert_eq!(*msgtype, 42);
6836                        assert_eq!(*payload, b"channel data");
6837                        got_channel_msg = true;
6838                    }
6839                }
6840            }
6841        }
6842        assert!(got_channel_msg, "Responder should receive channel message");
6843    }
6844
6845    #[test]
6846    fn test_channel_send_drops_packet_when_encrypt_fails() {
6847        let (mut init_mgr, _resp_mgr, link_id) = setup_active_link();
6848        let mut rng = OsRng;
6849        init_mgr
6850            .links
6851            .get_mut(&link_id)
6852            .unwrap()
6853            .engine
6854            .clear_session_for_testing();
6855
6856        let actions = init_mgr
6857            .send_channel_message(&link_id, 42, b"channel data", &mut rng)
6858            .expect("channel should still accept the message locally");
6859
6860        assert!(
6861            actions.is_empty(),
6862            "encrypt failure should suppress channel packet"
6863        );
6864        assert!(
6865            init_mgr
6866                .links
6867                .get(&link_id)
6868                .unwrap()
6869                .pending_channel_packets
6870                .is_empty(),
6871            "failed packet encryption must not track a pending channel proof"
6872        );
6873    }
6874
6875    #[test]
6876    fn test_channel_proof_reopens_send_window() {
6877        let (mut init_mgr, _resp_mgr, link_id) = setup_active_link();
6878        let mut rng = OsRng;
6879
6880        init_mgr
6881            .send_channel_message(&link_id, 42, b"first", &mut rng)
6882            .expect("first send should succeed");
6883        init_mgr
6884            .send_channel_message(&link_id, 42, b"second", &mut rng)
6885            .expect("second send should succeed");
6886
6887        let err = init_mgr
6888            .send_channel_message(&link_id, 42, b"third", &mut rng)
6889            .expect_err("third send should hit the initial channel window");
6890        assert_eq!(err, "Channel is not ready to send");
6891
6892        let queued_packets = init_mgr
6893            .links
6894            .get(&link_id)
6895            .unwrap()
6896            .pending_channel_packets
6897            .clone();
6898        assert_eq!(queued_packets.len(), 2);
6899        for tracked_hash in queued_packets.keys().take(1) {
6900            let mut proof_data = Vec::with_capacity(96);
6901            proof_data.extend_from_slice(tracked_hash);
6902            proof_data.extend_from_slice(&[0x11; 64]);
6903            let flags = PacketFlags {
6904                header_type: constants::HEADER_1,
6905                context_flag: constants::FLAG_UNSET,
6906                transport_type: constants::TRANSPORT_BROADCAST,
6907                destination_type: constants::DESTINATION_LINK,
6908                packet_type: constants::PACKET_TYPE_PROOF,
6909            };
6910            let proof = RawPacket::pack(
6911                flags,
6912                0,
6913                &link_id,
6914                None,
6915                constants::CONTEXT_NONE,
6916                &proof_data,
6917            )
6918            .expect("proof packet should pack");
6919            let ack_actions = init_mgr.handle_local_delivery(
6920                link_id,
6921                &proof.raw,
6922                proof.packet_hash,
6923                rns_core::transport::types::InterfaceId(0),
6924                &mut rng,
6925            );
6926            assert!(
6927                ack_actions.is_empty(),
6928                "proof delivery should only update channel state"
6929            );
6930        }
6931
6932        init_mgr
6933            .send_channel_message(&link_id, 42, b"third", &mut rng)
6934            .expect("proof should free one channel slot");
6935    }
6936
6937    #[test]
6938    fn test_generic_link_data_delivery() {
6939        let (init_mgr, mut resp_mgr, link_id) = setup_active_link();
6940        let mut rng = OsRng;
6941
6942        // Send generic data with a custom context
6943        let actions = init_mgr.send_on_link(&link_id, b"raw stuff", 0x42, &mut rng);
6944        assert_eq!(actions.len(), 1);
6945
6946        // Deliver to responder
6947        let raw = extract_any_send_packet(&actions);
6948        let pkt = RawPacket::unpack(&raw).unwrap();
6949        let resp_actions = resp_mgr.handle_local_delivery(
6950            pkt.destination_hash,
6951            &raw,
6952            pkt.packet_hash,
6953            rns_core::transport::types::InterfaceId(0),
6954            &mut rng,
6955        );
6956
6957        let has_data = resp_actions
6958            .iter()
6959            .any(|a| matches!(a, LinkManagerAction::LinkDataReceived { context: 0x42, .. }));
6960        assert!(
6961            has_data,
6962            "Responder should receive LinkDataReceived for unknown context"
6963        );
6964    }
6965
6966    #[test]
6967    fn test_invalid_encrypted_contexts_are_ignored() {
6968        let (_init_mgr, mut resp_mgr, link_id) = setup_active_link();
6969        let mut rng = OsRng;
6970        let contexts = [
6971            constants::CONTEXT_CHANNEL,
6972            constants::CONTEXT_REQUEST,
6973            constants::CONTEXT_RESPONSE,
6974            constants::CONTEXT_RESOURCE_ADV,
6975            constants::CONTEXT_RESOURCE_REQ,
6976            constants::CONTEXT_RESOURCE_HMU,
6977            constants::CONTEXT_RESOURCE_PRF,
6978            0x42,
6979        ];
6980
6981        for context in contexts {
6982            let flags = PacketFlags {
6983                header_type: constants::HEADER_1,
6984                context_flag: constants::FLAG_UNSET,
6985                transport_type: constants::TRANSPORT_BROADCAST,
6986                destination_type: constants::DESTINATION_LINK,
6987                packet_type: constants::PACKET_TYPE_DATA,
6988            };
6989            let pkt = RawPacket::pack(flags, 0, &link_id, None, context, b"invalid-ciphertext")
6990                .expect("test packet should pack");
6991            let actions = resp_mgr.handle_local_delivery(
6992                pkt.destination_hash,
6993                &pkt.raw,
6994                pkt.packet_hash,
6995                rns_core::transport::types::InterfaceId(0),
6996                &mut rng,
6997            );
6998            assert!(
6999                actions.is_empty(),
7000                "invalid ciphertext for context {context:#x} should be ignored"
7001            );
7002        }
7003    }
7004
7005    #[test]
7006    fn test_resource_part_without_matching_receiver_is_ignored() {
7007        let (_init_mgr, mut resp_mgr, link_id) = setup_active_link();
7008        let mut rng = OsRng;
7009        let flags = PacketFlags {
7010            header_type: constants::HEADER_1,
7011            context_flag: constants::FLAG_UNSET,
7012            transport_type: constants::TRANSPORT_BROADCAST,
7013            destination_type: constants::DESTINATION_LINK,
7014            packet_type: constants::PACKET_TYPE_DATA,
7015        };
7016        let pkt = RawPacket::pack(
7017            flags,
7018            0,
7019            &link_id,
7020            None,
7021            constants::CONTEXT_RESOURCE,
7022            b"orphan-part",
7023        )
7024        .expect("test packet should pack");
7025
7026        let actions = resp_mgr.handle_local_delivery(
7027            pkt.destination_hash,
7028            &pkt.raw,
7029            pkt.packet_hash,
7030            rns_core::transport::types::InterfaceId(0),
7031            &mut rng,
7032        );
7033
7034        assert!(actions.is_empty(), "orphan resource part should be ignored");
7035    }
7036
7037    #[test]
7038    fn test_response_delivery() {
7039        let (mut init_mgr, mut resp_mgr, link_id) = setup_active_link();
7040        let mut rng = OsRng;
7041
7042        // Register handler on responder
7043        resp_mgr.register_request_handler("/echo", None, |_link_id, _path, data, _remote| {
7044            Some(data.to_vec())
7045        });
7046
7047        // Send request from initiator
7048        let req_actions = init_mgr.send_request(&link_id, "/echo", b"\xc0", &mut rng); // msgpack nil
7049        assert!(!req_actions.is_empty());
7050
7051        // Deliver request to responder — should produce response
7052        let req_raw = extract_any_send_packet(&req_actions);
7053        let req_pkt = RawPacket::unpack(&req_raw).unwrap();
7054        let resp_actions = resp_mgr.handle_local_delivery(
7055            req_pkt.destination_hash,
7056            &req_raw,
7057            req_pkt.packet_hash,
7058            rns_core::transport::types::InterfaceId(0),
7059            &mut rng,
7060        );
7061        let has_resp_send = resp_actions
7062            .iter()
7063            .any(|a| matches!(a, LinkManagerAction::SendPacket { .. }));
7064        assert!(has_resp_send, "Handler should produce response");
7065
7066        // Deliver response back to initiator
7067        let resp_raw = extract_any_send_packet(&resp_actions);
7068        let resp_pkt = RawPacket::unpack(&resp_raw).unwrap();
7069        let init_actions = init_mgr.handle_local_delivery(
7070            resp_pkt.destination_hash,
7071            &resp_raw,
7072            resp_pkt.packet_hash,
7073            rns_core::transport::types::InterfaceId(0),
7074            &mut rng,
7075        );
7076
7077        let has_response_received = init_actions
7078            .iter()
7079            .any(|a| matches!(a, LinkManagerAction::ResponseReceived { .. }));
7080        assert!(
7081            has_response_received,
7082            "Initiator should receive ResponseReceived"
7083        );
7084    }
7085
7086    #[test]
7087    fn deferred_request_response_round_trip() {
7088        use std::sync::{Arc, Mutex};
7089
7090        let (mut init_mgr, mut resp_mgr, link_id) = setup_active_link();
7091        let mut rng = OsRng;
7092        let deferred = Arc::new(Mutex::new(None));
7093        resp_mgr.register_deferred_request_handler("/deferred", None, {
7094            let deferred = Arc::clone(&deferred);
7095            move |received_link, _, request_id, data, _| {
7096                *deferred.lock().unwrap() = Some((received_link, request_id, data.to_vec()));
7097            }
7098        });
7099
7100        let request_actions = init_mgr.send_request(&link_id, "/deferred", b"\xa5hello", &mut rng);
7101        let request_raw = extract_any_send_packet(&request_actions);
7102        let request_packet = RawPacket::unpack(&request_raw).unwrap();
7103        let immediate = resp_mgr.handle_local_delivery(
7104            request_packet.destination_hash,
7105            &request_raw,
7106            request_packet.packet_hash,
7107            rns_core::transport::types::InterfaceId(0),
7108            &mut rng,
7109        );
7110        assert!(
7111            immediate
7112                .iter()
7113                .all(|action| !matches!(action, LinkManagerAction::SendPacket { .. })),
7114            "deferred handlers must not send an immediate response"
7115        );
7116
7117        let (received_link, request_id, data) = deferred.lock().unwrap().take().unwrap();
7118        assert_eq!(received_link, link_id);
7119        assert_eq!(data, b"\xa5hello");
7120        let response_actions =
7121            resp_mgr.send_deferred_response(&link_id, &request_id, b"\xa2OK", &mut rng);
7122        let response_raw = extract_any_send_packet(&response_actions);
7123        let response_packet = RawPacket::unpack(&response_raw).unwrap();
7124        let delivered = init_mgr.handle_local_delivery(
7125            response_packet.destination_hash,
7126            &response_raw,
7127            response_packet.packet_hash,
7128            rns_core::transport::types::InterfaceId(0),
7129            &mut rng,
7130        );
7131        assert!(delivered.iter().any(|action| matches!(
7132            action,
7133            LinkManagerAction::ResponseReceived { data, .. } if data == b"\xa2OK"
7134        )));
7135    }
7136
7137    #[test]
7138    fn test_large_response_uses_resource_fallback() {
7139        let (mut init_mgr, mut resp_mgr, link_id) = setup_active_link();
7140        let mut rng = OsRng;
7141
7142        // Register handler on responder with payload that cannot fit a direct
7143        // CONTEXT_RESPONSE packet.
7144        let large_payload: Vec<u8> = (0..5000u32).map(|i| (i & 0xFF) as u8).collect();
7145        resp_mgr.register_request_handler("/large", None, {
7146            let large_payload = large_payload.clone();
7147            move |_link_id, _path, _data, _remote| Some(large_payload.clone())
7148        });
7149
7150        // Send request from initiator.
7151        let req_actions = init_mgr.send_request(&link_id, "/large", b"\xc0", &mut rng);
7152        assert!(!req_actions.is_empty());
7153
7154        // Deliver request to responder and inspect responder outbound packets.
7155        let req_raw = extract_any_send_packet(&req_actions);
7156        let req_pkt = RawPacket::unpack(&req_raw).unwrap();
7157        let resp_actions = resp_mgr.handle_local_delivery(
7158            req_pkt.destination_hash,
7159            &req_raw,
7160            req_pkt.packet_hash,
7161            rns_core::transport::types::InterfaceId(0),
7162            &mut rng,
7163        );
7164
7165        let mut has_resource_adv = false;
7166        let mut has_direct_response = false;
7167        for action in &resp_actions {
7168            if let LinkManagerAction::SendPacket { raw, .. } = action {
7169                let pkt = RawPacket::unpack(raw).unwrap();
7170                if pkt.context == constants::CONTEXT_RESOURCE_ADV {
7171                    has_resource_adv = true;
7172                }
7173                if pkt.context == constants::CONTEXT_RESPONSE {
7174                    has_direct_response = true;
7175                }
7176            }
7177        }
7178
7179        assert!(
7180            has_resource_adv,
7181            "Large response should advertise a response resource"
7182        );
7183        assert!(
7184            !has_direct_response,
7185            "Large response should not use direct CONTEXT_RESPONSE packet"
7186        );
7187    }
7188
7189    #[test]
7190    fn test_send_management_response_without_session_key_uses_resource_fallback_path() {
7191        let (mut init_mgr, _resp_mgr, link_id) = setup_active_link();
7192        let mut rng = OsRng;
7193        init_mgr
7194            .links
7195            .get_mut(&link_id)
7196            .unwrap()
7197            .engine
7198            .clear_session_for_testing();
7199
7200        let large_response: Vec<u8> = (0..5000u32).map(|i| (i & 0xFF) as u8).collect();
7201        let actions =
7202            init_mgr.send_management_response(&link_id, &[0x11; 16], &large_response, &mut rng);
7203
7204        assert!(
7205            actions.is_empty(),
7206            "without a session key, no response packets should be emitted"
7207        );
7208        assert_eq!(
7209            init_mgr
7210                .links
7211                .get(&link_id)
7212                .map(|managed| managed.outgoing_resources.len()),
7213            Some(1)
7214        );
7215    }
7216
7217    #[test]
7218    fn test_send_channel_message_on_no_channel() {
7219        let mut mgr = LinkManager::new();
7220        let mut rng = OsRng;
7221        let dummy_sig = [0xAA; 32];
7222        let (link_id, _) =
7223            mgr.create_link(&[0x11; 16], &dummy_sig, 1, constants::MTU as u32, &mut rng);
7224
7225        // Link is Pending (no channel), should return empty
7226        let err = mgr
7227            .send_channel_message(&link_id, 1, b"test", &mut rng)
7228            .expect_err("pending link should reject channel send");
7229        assert_eq!(err, "link has no active channel");
7230    }
7231
7232    #[test]
7233    fn test_send_on_link_requires_active() {
7234        let mut mgr = LinkManager::new();
7235        let mut rng = OsRng;
7236        let dummy_sig = [0xAA; 32];
7237        let (link_id, _) =
7238            mgr.create_link(&[0x11; 16], &dummy_sig, 1, constants::MTU as u32, &mut rng);
7239
7240        let actions = mgr.send_on_link(&link_id, b"test", constants::CONTEXT_NONE, &mut rng);
7241        assert!(actions.is_empty(), "Cannot send on pending link");
7242    }
7243
7244    #[test]
7245    fn test_send_on_link_unknown_link() {
7246        let mgr = LinkManager::new();
7247        let mut rng = OsRng;
7248
7249        let actions = mgr.send_on_link(&[0xFF; 16], b"test", constants::CONTEXT_NONE, &mut rng);
7250        assert!(actions.is_empty());
7251    }
7252
7253    #[test]
7254    fn test_resource_full_transfer_large() {
7255        let (mut init_mgr, mut resp_mgr, link_id) = setup_active_link();
7256        let mut rng = OsRng;
7257
7258        resp_mgr.set_resource_strategy(&link_id, ResourceStrategy::AcceptAll);
7259
7260        // Multi-part data (larger than a single SDU of 464 bytes)
7261        let original_data: Vec<u8> = (0..2000u32)
7262            .map(|i| {
7263                let pos = i as usize;
7264                (pos ^ (pos >> 8) ^ (pos >> 16)) as u8
7265            })
7266            .collect();
7267
7268        let adv_actions = init_mgr.send_resource(&link_id, &original_data, None, &mut rng);
7269
7270        let mut pending: Vec<(char, LinkManagerAction)> =
7271            adv_actions.into_iter().map(|a| ('i', a)).collect();
7272        let mut rounds = 0;
7273        let max_rounds = 200;
7274        let mut resource_received = false;
7275        let mut sender_completed = false;
7276
7277        while !pending.is_empty() && rounds < max_rounds {
7278            rounds += 1;
7279            let mut next: Vec<(char, LinkManagerAction)> = Vec::new();
7280
7281            for (source, action) in pending.drain(..) {
7282                if let LinkManagerAction::SendPacket { raw, .. } = action {
7283                    let pkt = match RawPacket::unpack(&raw) {
7284                        Ok(p) => p,
7285                        Err(_) => continue,
7286                    };
7287
7288                    let target_actions = if source == 'i' {
7289                        resp_mgr.handle_local_delivery(
7290                            pkt.destination_hash,
7291                            &raw,
7292                            pkt.packet_hash,
7293                            rns_core::transport::types::InterfaceId(0),
7294                            &mut rng,
7295                        )
7296                    } else {
7297                        init_mgr.handle_local_delivery(
7298                            pkt.destination_hash,
7299                            &raw,
7300                            pkt.packet_hash,
7301                            rns_core::transport::types::InterfaceId(0),
7302                            &mut rng,
7303                        )
7304                    };
7305
7306                    let target_source = if source == 'i' { 'r' } else { 'i' };
7307                    for a in &target_actions {
7308                        match a {
7309                            LinkManagerAction::ResourceReceived { data, .. } => {
7310                                assert_eq!(*data, original_data);
7311                                resource_received = true;
7312                            }
7313                            LinkManagerAction::ResourceCompleted { .. } => {
7314                                sender_completed = true;
7315                            }
7316                            _ => {}
7317                        }
7318                    }
7319                    next.extend(target_actions.into_iter().map(|a| (target_source, a)));
7320                }
7321            }
7322            pending = next;
7323        }
7324
7325        assert!(
7326            resource_received,
7327            "Should receive large resource (rounds={})",
7328            rounds
7329        );
7330        assert!(
7331            sender_completed,
7332            "Sender should complete (rounds={})",
7333            rounds
7334        );
7335    }
7336
7337    #[test]
7338    fn test_resource_receiver_stores_original_advertisement_plaintext() {
7339        let (mut init_mgr, mut resp_mgr, link_id) = setup_active_link();
7340        let mut rng = OsRng;
7341
7342        resp_mgr.set_resource_strategy(&link_id, ResourceStrategy::AcceptAll);
7343
7344        let data = vec![0x41; 256];
7345        let adv_actions = init_mgr.send_resource(&link_id, &data, None, &mut rng);
7346
7347        let adv_raw = adv_actions
7348            .iter()
7349            .find_map(|action| match action {
7350                LinkManagerAction::SendPacket { raw, .. } => {
7351                    let pkt = RawPacket::unpack(raw).ok()?;
7352                    (pkt.context == constants::CONTEXT_RESOURCE_ADV).then_some(raw.clone())
7353                }
7354                _ => None,
7355            })
7356            .expect("sender should emit a resource advertisement");
7357
7358        let adv_pkt = RawPacket::unpack(&adv_raw).unwrap();
7359        let adv_plaintext = resp_mgr
7360            .links
7361            .get(&link_id)
7362            .unwrap()
7363            .engine
7364            .decrypt(&adv_pkt.data)
7365            .unwrap();
7366
7367        let _resp_actions = resp_mgr.handle_local_delivery(
7368            adv_pkt.destination_hash,
7369            &adv_raw,
7370            adv_pkt.packet_hash,
7371            rns_core::transport::types::InterfaceId(0),
7372            &mut rng,
7373        );
7374
7375        let receiver = resp_mgr
7376            .links
7377            .get(&link_id)
7378            .and_then(|managed| managed.incoming_resources.first())
7379            .expect("advertisement should create an incoming receiver");
7380        assert_eq!(receiver.advertisement_packet, adv_plaintext);
7381        assert_eq!(
7382            receiver.max_decompressed_size,
7383            constants::RESOURCE_AUTO_COMPRESS_MAX_SIZE
7384        );
7385    }
7386
7387    #[test]
7388    fn test_corrupt_compressed_resource_rejects_and_tears_down_link() {
7389        let (mut init_mgr, mut resp_mgr, link_id) = setup_active_link();
7390        let mut rng = OsRng;
7391
7392        resp_mgr.set_resource_strategy(&link_id, ResourceStrategy::AcceptAll);
7393
7394        let data = vec![b'A'; 4096];
7395        let adv_actions = init_mgr.send_resource(&link_id, &data, None, &mut rng);
7396
7397        let mut request_actions = Vec::new();
7398        for action in &adv_actions {
7399            let LinkManagerAction::SendPacket { raw, .. } = action else {
7400                continue;
7401            };
7402            let pkt = RawPacket::unpack(raw).unwrap();
7403            let actions = resp_mgr.handle_local_delivery(
7404                pkt.destination_hash,
7405                raw,
7406                pkt.packet_hash,
7407                rns_core::transport::types::InterfaceId(0),
7408                &mut rng,
7409            );
7410            request_actions.extend(actions);
7411        }
7412
7413        {
7414            let receiver = resp_mgr
7415                .links
7416                .get_mut(&link_id)
7417                .and_then(|managed| managed.incoming_resources.first_mut())
7418                .expect("receiver should exist after advertisement");
7419            assert!(receiver.flags.compressed, "test data should be compressed");
7420            receiver.max_decompressed_size = 64;
7421        }
7422
7423        let mut responder_actions = Vec::new();
7424        for action in request_actions {
7425            let LinkManagerAction::SendPacket { raw, .. } = action else {
7426                continue;
7427            };
7428            let pkt = RawPacket::unpack(&raw).unwrap();
7429            let actions = init_mgr.handle_local_delivery(
7430                pkt.destination_hash,
7431                &raw,
7432                pkt.packet_hash,
7433                rns_core::transport::types::InterfaceId(0),
7434                &mut rng,
7435            );
7436
7437            for action in actions {
7438                let LinkManagerAction::SendPacket { raw, .. } = &action else {
7439                    continue;
7440                };
7441                let pkt = RawPacket::unpack(raw).unwrap();
7442                let delivered = resp_mgr.handle_local_delivery(
7443                    pkt.destination_hash,
7444                    raw,
7445                    pkt.packet_hash,
7446                    rns_core::transport::types::InterfaceId(0),
7447                    &mut rng,
7448                );
7449                responder_actions.extend(delivered);
7450            }
7451        }
7452
7453        assert!(
7454            responder_actions.iter().any(|action| matches!(
7455                action,
7456                LinkManagerAction::ResourceFailed { error, .. }
7457                    if error == "Resource too large"
7458            )),
7459            "corrupt oversized resource should fail with TooLarge"
7460        );
7461        assert!(
7462            responder_actions.iter().any(|action| matches!(
7463                action,
7464                LinkManagerAction::LinkClosed { link_id: closed_id, .. } if *closed_id == link_id
7465            )),
7466            "corrupt oversized resource should tear down the link"
7467        );
7468        assert!(
7469            responder_actions.iter().any(|action| match action {
7470                LinkManagerAction::SendPacket { raw, .. } => RawPacket::unpack(raw)
7471                    .map(|pkt| pkt.context == constants::CONTEXT_RESOURCE_RCL)
7472                    .unwrap_or(false),
7473                _ => false,
7474            }),
7475            "corrupt oversized resource should send a receiver cancel/reject packet"
7476        );
7477        assert_eq!(
7478            resp_mgr
7479                .links
7480                .get(&link_id)
7481                .map(|managed| managed.engine.state()),
7482            Some(LinkState::Closed)
7483        );
7484    }
7485
7486    #[test]
7487    fn test_resource_hmu_timeout_extension_in_link_manager_flow() {
7488        let (mut init_mgr, mut resp_mgr, link_id) = setup_active_link();
7489        let mut rng = OsRng;
7490
7491        resp_mgr.set_resource_strategy(&link_id, ResourceStrategy::AcceptAll);
7492
7493        // Large and incompressible enough to require multiple hashmap segments
7494        // even with the live Bzip2Compressor in the LinkManager path.
7495        let mut state = 0x1234_5678u32;
7496        let data: Vec<u8> = (0..50000)
7497            .map(|_| {
7498                state = state.wrapping_mul(1664525).wrapping_add(1013904223);
7499                (state >> 16) as u8
7500            })
7501            .collect();
7502        let adv_actions = init_mgr.send_resource(&link_id, &data, None, &mut rng);
7503        let mut pending: Vec<(char, LinkManagerAction)> =
7504            adv_actions.into_iter().map(|a| ('i', a)).collect();
7505
7506        let mut rounds = 0;
7507
7508        // Drive the real link-manager exchange until the receiver is genuinely
7509        // waiting for an HMU after exhausting the advertised hashmap segment.
7510        while rounds < 300 {
7511            rounds += 1;
7512            let mut next: Vec<(char, LinkManagerAction)> = Vec::new();
7513
7514            for (source, action) in pending.drain(..) {
7515                let LinkManagerAction::SendPacket { raw, .. } = action else {
7516                    continue;
7517                };
7518
7519                let pkt = match RawPacket::unpack(&raw) {
7520                    Ok(p) => p,
7521                    Err(_) => continue,
7522                };
7523
7524                let target_actions = if source == 'i' {
7525                    resp_mgr.handle_local_delivery(
7526                        pkt.destination_hash,
7527                        &raw,
7528                        pkt.packet_hash,
7529                        rns_core::transport::types::InterfaceId(0),
7530                        &mut rng,
7531                    )
7532                } else {
7533                    init_mgr.handle_local_delivery(
7534                        pkt.destination_hash,
7535                        &raw,
7536                        pkt.packet_hash,
7537                        rns_core::transport::types::InterfaceId(0),
7538                        &mut rng,
7539                    )
7540                };
7541
7542                let target_source = if source == 'i' { 'r' } else { 'i' };
7543                next.extend(target_actions.into_iter().map(|a| (target_source, a)));
7544            }
7545
7546            if resp_mgr
7547                .links
7548                .get(&link_id)
7549                .and_then(|managed| managed.incoming_resources.first())
7550                .is_some_and(|receiver| receiver.waiting_for_hmu)
7551            {
7552                break;
7553            }
7554
7555            pending = next;
7556        }
7557
7558        assert!(
7559            resp_mgr
7560                .links
7561                .get(&link_id)
7562                .and_then(|managed| managed.incoming_resources.first())
7563                .is_some_and(|receiver| receiver.waiting_for_hmu),
7564            "expected receiver to reach a live HMU wait state"
7565        );
7566
7567        // Prime the live receiver once so it computes the same EIFR it will use
7568        // for timeout decisions in this HMU-wait state.
7569        let prime_actions = {
7570            let managed = resp_mgr.links.get_mut(&link_id).unwrap();
7571            let receiver = managed.incoming_resources.first_mut().unwrap();
7572            let decrypt_fn = |ciphertext: &[u8]| -> Result<Vec<u8>, ()> {
7573                managed.engine.decrypt(ciphertext).map_err(|_| ())
7574            };
7575            receiver.tick(
7576                receiver.last_activity + 0.0001,
7577                &decrypt_fn,
7578                &Bzip2Compressor,
7579            )
7580        };
7581        assert!(
7582            !prime_actions
7583                .iter()
7584                .any(|a| matches!(a, ResourceAction::SendRequest(_))),
7585            "fresh HMU wait state should not immediately emit a retry request"
7586        );
7587
7588        let (late_delta, retries_before) = {
7589            let managed = resp_mgr
7590                .links
7591                .get_mut(&link_id)
7592                .expect("receiver link should still exist");
7593            let receiver = managed
7594                .incoming_resources
7595                .first_mut()
7596                .expect("receiver should have an active incoming resource");
7597
7598            assert!(
7599                receiver.waiting_for_hmu,
7600                "receiver should be waiting for HMU"
7601            );
7602
7603            let eifr = receiver.eifr.unwrap_or_else(|| {
7604                (constants::RESOURCE_SDU as f64 * 8.0) / receiver.rtt.unwrap_or(0.5)
7605            });
7606            let expected_tof = if receiver.outstanding_parts > 0 {
7607                (receiver.outstanding_parts as f64 * constants::RESOURCE_SDU as f64 * 8.0) / eifr
7608            } else {
7609                (3.0 * constants::RESOURCE_SDU as f64) / eifr
7610            };
7611            let expected_hmu_wait =
7612                (constants::RESOURCE_SDU as f64 * 8.0 * constants::RESOURCE_HMU_WAIT_FACTOR) / eifr;
7613            let old_delta = constants::RESOURCE_PART_TIMEOUT_FACTOR_AFTER_RTT * expected_tof
7614                + constants::RESOURCE_RETRY_GRACE_TIME;
7615            (
7616                old_delta + expected_hmu_wait + expected_hmu_wait.max(1.0),
7617                receiver.retries_left,
7618            )
7619        };
7620        {
7621            let managed = resp_mgr.links.get(&link_id).unwrap();
7622            let receiver = managed.incoming_resources.first().unwrap();
7623            assert_eq!(receiver.retries_left, retries_before);
7624            assert!(
7625                receiver.eifr.is_some(),
7626                "receiver tick should have populated EIFR"
7627            );
7628        }
7629
7630        let late_resource_actions = {
7631            let managed = resp_mgr.links.get_mut(&link_id).unwrap();
7632            let receiver = managed.incoming_resources.first_mut().unwrap();
7633            let decrypt_fn = |ciphertext: &[u8]| -> Result<Vec<u8>, ()> {
7634                managed.engine.decrypt(ciphertext).map_err(|_| ())
7635            };
7636            receiver.tick(
7637                receiver.last_activity + late_delta,
7638                &decrypt_fn,
7639                &Bzip2Compressor,
7640            )
7641        };
7642        let late_actions =
7643            resp_mgr.process_resource_actions(&link_id, late_resource_actions, &mut rng);
7644        let retry_raw = late_actions
7645            .iter()
7646            .find_map(|a| match a {
7647                LinkManagerAction::SendPacket { raw, .. } => {
7648                    let pkt = RawPacket::unpack(raw).ok()?;
7649                    (pkt.context == constants::CONTEXT_RESOURCE_REQ).then_some(raw.clone())
7650                }
7651                _ => None,
7652            })
7653            .expect("receiver should emit a resource retry request after extended timeout");
7654
7655        {
7656            let managed = resp_mgr.links.get(&link_id).unwrap();
7657            let receiver = managed.incoming_resources.first().unwrap();
7658            assert_eq!(receiver.retries_left, retries_before - 1);
7659        }
7660
7661        let retry_pkt = RawPacket::unpack(&retry_raw).unwrap();
7662        let retry_plaintext = resp_mgr
7663            .links
7664            .get(&link_id)
7665            .unwrap()
7666            .engine
7667            .decrypt(&retry_pkt.data)
7668            .expect("retry request should decrypt");
7669        assert_eq!(retry_plaintext[0], constants::RESOURCE_HASHMAP_IS_EXHAUSTED);
7670
7671        // Deliver the retry request to the sender and verify it turns into a
7672        // real HMU packet in the live LinkManager flow.
7673        let retry_to_sender = init_mgr.handle_local_delivery(
7674            retry_pkt.destination_hash,
7675            &retry_raw,
7676            retry_pkt.packet_hash,
7677            rns_core::transport::types::InterfaceId(0),
7678            &mut rng,
7679        );
7680        assert!(
7681            retry_to_sender.iter().any(|a| match a {
7682                LinkManagerAction::SendPacket { raw, .. } => RawPacket::unpack(raw)
7683                    .map(|pkt| pkt.context == constants::CONTEXT_RESOURCE_HMU)
7684                    .unwrap_or(false),
7685                _ => false,
7686            }),
7687            "sender should answer the exhausted retry request with a live HMU packet"
7688        );
7689    }
7690
7691    #[test]
7692    fn test_process_resource_actions_mapping() {
7693        let (mut init_mgr, _resp_mgr, link_id) = setup_active_link();
7694        let mut rng = OsRng;
7695
7696        // Test that various ResourceActions map to correct LinkManagerActions
7697        let actions = vec![
7698            ResourceAction::DataReceived {
7699                data: vec![1, 2, 3],
7700                metadata: Some(vec![4, 5]),
7701            },
7702            ResourceAction::Completed,
7703            ResourceAction::Failed(rns_core::resource::ResourceError::Timeout),
7704            ResourceAction::ProgressUpdate {
7705                received: 10,
7706                total: 20,
7707            },
7708            ResourceAction::TeardownLink,
7709        ];
7710
7711        let result = init_mgr.process_resource_actions(&link_id, actions, &mut rng);
7712
7713        assert!(matches!(
7714            result[0],
7715            LinkManagerAction::ResourceReceived { .. }
7716        ));
7717        assert!(matches!(
7718            result[1],
7719            LinkManagerAction::ResourceCompleted { .. }
7720        ));
7721        assert!(matches!(
7722            result[2],
7723            LinkManagerAction::ResourceFailed { .. }
7724        ));
7725        assert!(matches!(
7726            result[3],
7727            LinkManagerAction::ResourceProgress {
7728                received: 10,
7729                total: 20,
7730                ..
7731            }
7732        ));
7733        assert!(result
7734            .iter()
7735            .any(|action| matches!(action, LinkManagerAction::LinkClosed { .. })));
7736    }
7737
7738    #[test]
7739    fn test_link_state_empty() {
7740        let mgr = LinkManager::new();
7741        let fake_id = [0xAA; 16];
7742        assert!(mgr.link_state(&fake_id).is_none());
7743    }
7744
7745    #[test]
7746    fn test_large_response_resource_completes_as_response() {
7747        let (mut init_mgr, mut resp_mgr, link_id) = setup_active_link();
7748        let mut rng = OsRng;
7749
7750        let large_payload: Vec<u8> = (0..5000u32).map(|i| (i & 0xFF) as u8).collect();
7751        let response_value = rns_core::msgpack::pack(&rns_core::msgpack::Value::Bin(large_payload));
7752        resp_mgr.register_request_handler("/large", None, {
7753            let response_value = response_value.clone();
7754            move |_link_id, _path, _data, _remote| Some(response_value.clone())
7755        });
7756
7757        let req_actions = init_mgr.send_request(&link_id, "/large", b"\xc0", &mut rng);
7758        let req_raw = extract_any_send_packet(&req_actions);
7759        let req_pkt = RawPacket::unpack(&req_raw).unwrap();
7760        let request_id = req_pkt.get_truncated_hash();
7761        let resp_actions = resp_mgr.handle_local_delivery(
7762            req_pkt.destination_hash,
7763            &req_raw,
7764            req_pkt.packet_hash,
7765            rns_core::transport::types::InterfaceId(0),
7766            &mut rng,
7767        );
7768
7769        let mut pending: Vec<(char, LinkManagerAction)> =
7770            resp_actions.into_iter().map(|a| ('r', a)).collect();
7771        let mut rounds = 0;
7772        let mut received_response = None;
7773
7774        while !pending.is_empty() && rounds < 200 {
7775            rounds += 1;
7776            let mut next = Vec::new();
7777
7778            for (source, action) in pending.drain(..) {
7779                let LinkManagerAction::SendPacket { raw, .. } = action else {
7780                    continue;
7781                };
7782                let pkt = RawPacket::unpack(&raw).unwrap();
7783                let target_actions = if source == 'r' {
7784                    init_mgr.handle_local_delivery(
7785                        pkt.destination_hash,
7786                        &raw,
7787                        pkt.packet_hash,
7788                        rns_core::transport::types::InterfaceId(0),
7789                        &mut rng,
7790                    )
7791                } else {
7792                    resp_mgr.handle_local_delivery(
7793                        pkt.destination_hash,
7794                        &raw,
7795                        pkt.packet_hash,
7796                        rns_core::transport::types::InterfaceId(0),
7797                        &mut rng,
7798                    )
7799                };
7800
7801                let target_source = if source == 'r' { 'i' } else { 'r' };
7802                for target_action in &target_actions {
7803                    match target_action {
7804                        LinkManagerAction::ResponseReceived {
7805                            request_id: rid,
7806                            data,
7807                            ..
7808                        } => {
7809                            received_response = Some((*rid, data.clone()));
7810                        }
7811                        LinkManagerAction::ResourceReceived { .. } => {
7812                            panic!("response resources must complete as ResponseReceived")
7813                        }
7814                        LinkManagerAction::ResourceAcceptQuery { .. } => {
7815                            panic!("response resources must bypass application acceptance")
7816                        }
7817                        _ => {}
7818                    }
7819                }
7820                next.extend(target_actions.into_iter().map(|a| (target_source, a)));
7821            }
7822
7823            pending = next;
7824        }
7825
7826        let (received_request_id, received_data) = received_response.unwrap_or_else(|| {
7827            panic!(
7828                "large response resource did not complete as ResponseReceived after {} rounds",
7829                rounds
7830            )
7831        });
7832        assert_eq!(received_request_id, request_id);
7833        assert_eq!(received_data, response_value);
7834    }
7835
7836    #[test]
7837    fn response_resource_at_exact_declared_size_limit_is_accepted() {
7838        let (mut init_mgr, mut resp_mgr, link_id) = setup_active_link();
7839        let mut rng = OsRng;
7840        let payload = deterministic_bytes(4096);
7841        let response_value =
7842            rns_core::msgpack::pack(&rns_core::msgpack::Value::Bin(payload.clone()));
7843        let expected_size = rns_core::msgpack::pack(&rns_core::msgpack::Value::Array(vec![
7844            rns_core::msgpack::Value::Bin(vec![0; 16]),
7845            rns_core::msgpack::Value::Bin(payload),
7846        ]))
7847        .len();
7848        resp_mgr.register_request_handler_response("/bounded-resource", None, {
7849            let response_value = response_value.clone();
7850            move |_, _, _, _| {
7851                Some(RequestResponse::Resource {
7852                    data: response_value.clone(),
7853                    metadata: None,
7854                    auto_compress: false,
7855                })
7856            }
7857        });
7858
7859        let request_actions = init_mgr.send_request_with_max_response_size(
7860            &link_id,
7861            "/bounded-resource",
7862            b"\xc0",
7863            Some(expected_size),
7864            &mut rng,
7865        );
7866        let request_raw = extract_any_send_packet(&request_actions);
7867        let request = RawPacket::unpack(&request_raw).unwrap();
7868        let response_actions = resp_mgr.handle_local_delivery(
7869            request.destination_hash,
7870            &request_raw,
7871            request.packet_hash,
7872            rns_core::transport::types::InterfaceId(0),
7873            &mut rng,
7874        );
7875        let adv = first_resource_advertisement(&resp_mgr, &link_id, &response_actions);
7876        assert_eq!(adv.data_size as usize, expected_size);
7877        let adv_raw = extract_any_send_packet(&response_actions);
7878        let adv_packet = RawPacket::unpack(&adv_raw).unwrap();
7879        let actions = init_mgr.handle_local_delivery(
7880            adv_packet.destination_hash,
7881            &adv_raw,
7882            adv_packet.packet_hash,
7883            rns_core::transport::types::InterfaceId(0),
7884            &mut rng,
7885        );
7886
7887        assert!(!actions
7888            .iter()
7889            .any(|action| matches!(action, LinkManagerAction::RequestFailed { .. })));
7890        assert_eq!(init_mgr.links[&link_id].incoming_resources.len(), 1);
7891    }
7892
7893    #[test]
7894    fn response_resource_one_byte_over_declared_size_limit_is_rejected() {
7895        let (mut init_mgr, mut resp_mgr, link_id) = setup_active_link();
7896        let mut rng = OsRng;
7897        let payload = deterministic_bytes(4096);
7898        let response_value =
7899            rns_core::msgpack::pack(&rns_core::msgpack::Value::Bin(payload.clone()));
7900        let expected_size = rns_core::msgpack::pack(&rns_core::msgpack::Value::Array(vec![
7901            rns_core::msgpack::Value::Bin(vec![0; 16]),
7902            rns_core::msgpack::Value::Bin(payload),
7903        ]))
7904        .len();
7905        resp_mgr.register_request_handler_response("/bounded-resource", None, {
7906            let response_value = response_value.clone();
7907            move |_, _, _, _| {
7908                Some(RequestResponse::Resource {
7909                    data: response_value.clone(),
7910                    metadata: None,
7911                    auto_compress: false,
7912                })
7913            }
7914        });
7915
7916        let request_actions = init_mgr.send_request_with_max_response_size(
7917            &link_id,
7918            "/bounded-resource",
7919            b"\xc0",
7920            Some(expected_size - 1),
7921            &mut rng,
7922        );
7923        let request_raw = extract_any_send_packet(&request_actions);
7924        let request = RawPacket::unpack(&request_raw).unwrap();
7925        let request_id = request.get_truncated_hash();
7926        let response_actions = resp_mgr.handle_local_delivery(
7927            request.destination_hash,
7928            &request_raw,
7929            request.packet_hash,
7930            rns_core::transport::types::InterfaceId(0),
7931            &mut rng,
7932        );
7933        let adv_raw = extract_any_send_packet(&response_actions);
7934        let adv_packet = RawPacket::unpack(&adv_raw).unwrap();
7935        let actions = init_mgr.handle_local_delivery(
7936            adv_packet.destination_hash,
7937            &adv_raw,
7938            adv_packet.packet_hash,
7939            rns_core::transport::types::InterfaceId(0),
7940            &mut rng,
7941        );
7942
7943        assert!(actions.iter().any(|action| matches!(
7944            action,
7945            LinkManagerAction::RequestFailed {
7946                request_id: failed_id,
7947                reason: RequestFailure::ResponseTooLarge { size, maximum },
7948                ..
7949            } if failed_id == &request_id
7950                && *size == expected_size as u64
7951                && *maximum == expected_size - 1
7952        )));
7953        assert!(init_mgr.links[&link_id].incoming_resources.is_empty());
7954        assert!(!init_mgr.links[&link_id]
7955            .pending_requests
7956            .contains_key(&request_id));
7957    }
7958
7959    #[test]
7960    fn test_response_resource_preserves_metadata() {
7961        let (mut init_mgr, mut resp_mgr, link_id) = setup_active_link();
7962        let mut rng = OsRng;
7963
7964        let payload = b"bundle-data".to_vec();
7965        let metadata = b"git-status-ok".to_vec();
7966        let response_value = rns_core::msgpack::pack(&rns_core::msgpack::Value::Bin(payload));
7967        resp_mgr.register_request_handler_response("/fetch", None, {
7968            let response_value = response_value.clone();
7969            let metadata = metadata.clone();
7970            move |_link_id, _path, _data, _remote| {
7971                Some(RequestResponse::Resource {
7972                    data: response_value.clone(),
7973                    metadata: Some(metadata.clone()),
7974                    auto_compress: false,
7975                })
7976            }
7977        });
7978
7979        let req_actions = init_mgr.send_request(&link_id, "/fetch", b"\xc0", &mut rng);
7980        let req_raw = extract_any_send_packet(&req_actions);
7981        let req_pkt = RawPacket::unpack(&req_raw).unwrap();
7982        let request_id = req_pkt.get_truncated_hash();
7983        let resp_actions = resp_mgr.handle_local_delivery(
7984            req_pkt.destination_hash,
7985            &req_raw,
7986            req_pkt.packet_hash,
7987            rns_core::transport::types::InterfaceId(0),
7988            &mut rng,
7989        );
7990
7991        let mut pending: Vec<(char, LinkManagerAction)> =
7992            resp_actions.into_iter().map(|a| ('r', a)).collect();
7993        let mut received_response = None;
7994
7995        for _ in 0..200 {
7996            if pending.is_empty() || received_response.is_some() {
7997                break;
7998            }
7999
8000            let mut next = Vec::new();
8001            for (source, action) in pending.drain(..) {
8002                let LinkManagerAction::SendPacket { raw, .. } = action else {
8003                    continue;
8004                };
8005                let pkt = RawPacket::unpack(&raw).unwrap();
8006                let target_actions = if source == 'r' {
8007                    init_mgr.handle_local_delivery(
8008                        pkt.destination_hash,
8009                        &raw,
8010                        pkt.packet_hash,
8011                        rns_core::transport::types::InterfaceId(0),
8012                        &mut rng,
8013                    )
8014                } else {
8015                    resp_mgr.handle_local_delivery(
8016                        pkt.destination_hash,
8017                        &raw,
8018                        pkt.packet_hash,
8019                        rns_core::transport::types::InterfaceId(0),
8020                        &mut rng,
8021                    )
8022                };
8023
8024                let target_source = if source == 'r' { 'i' } else { 'r' };
8025                for target_action in &target_actions {
8026                    match target_action {
8027                        LinkManagerAction::ResponseReceived {
8028                            request_id: rid,
8029                            data,
8030                            metadata: response_metadata,
8031                            ..
8032                        } => {
8033                            received_response =
8034                                Some((*rid, data.clone(), response_metadata.clone()));
8035                        }
8036                        LinkManagerAction::ResourceReceived { .. } => {
8037                            panic!("response resources must complete as ResponseReceived")
8038                        }
8039                        _ => {}
8040                    }
8041                }
8042                next.extend(target_actions.into_iter().map(|a| (target_source, a)));
8043            }
8044            pending = next;
8045        }
8046
8047        let (received_request_id, received_data, received_metadata) = received_response
8048            .expect("resource response with metadata should complete as ResponseReceived");
8049        assert_eq!(received_request_id, request_id);
8050        assert_eq!(received_data, response_value);
8051        assert_eq!(received_metadata, Some(metadata));
8052    }
8053
8054    #[test]
8055    fn test_negotiated_mtu_response_uses_resource_before_global_mtu() {
8056        let (mut init_mgr, mut resp_mgr, link_id) = setup_active_link();
8057        let mut rng = OsRng;
8058
8059        init_mgr.set_link_mtu(&link_id, 300);
8060        resp_mgr.set_link_mtu(&link_id, 300);
8061
8062        let payload = vec![0xAB; 350];
8063        let response_value = rns_core::msgpack::pack(&rns_core::msgpack::Value::Bin(payload));
8064        resp_mgr.register_request_handler("/mtu", None, {
8065            let response_value = response_value.clone();
8066            move |_link_id, _path, _data, _remote| Some(response_value.clone())
8067        });
8068
8069        let req_actions = init_mgr.send_request(&link_id, "/mtu", b"\xc0", &mut rng);
8070        let req_raw = extract_any_send_packet(&req_actions);
8071        let req_pkt = RawPacket::unpack(&req_raw).unwrap();
8072        let resp_actions = resp_mgr.handle_local_delivery(
8073            req_pkt.destination_hash,
8074            &req_raw,
8075            req_pkt.packet_hash,
8076            rns_core::transport::types::InterfaceId(0),
8077            &mut rng,
8078        );
8079
8080        let mut has_resource_adv = false;
8081        let mut direct_response_len = None;
8082        for action in &resp_actions {
8083            if let LinkManagerAction::SendPacket { raw, .. } = action {
8084                let pkt = RawPacket::unpack(raw).unwrap();
8085                has_resource_adv |= pkt.context == constants::CONTEXT_RESOURCE_ADV;
8086                if pkt.context == constants::CONTEXT_RESPONSE {
8087                    direct_response_len = Some(raw.len());
8088                }
8089            }
8090        }
8091
8092        assert!(
8093            has_resource_adv,
8094            "responses larger than the negotiated link MTU should use resource fallback"
8095        );
8096        assert!(
8097            direct_response_len.is_none(),
8098            "sent direct response of {} bytes on a 300 byte negotiated MTU",
8099            direct_response_len.unwrap_or_default()
8100        );
8101    }
8102
8103    #[test]
8104    fn test_large_management_response_uses_resource_fallback() {
8105        let (_init_mgr, mut resp_mgr, link_id) = setup_active_link();
8106        let mut rng = OsRng;
8107
8108        let payload = vec![0xBC; 5000];
8109        let response_value = rns_core::msgpack::pack(&rns_core::msgpack::Value::Bin(payload));
8110        let actions =
8111            resp_mgr.send_management_response(&link_id, &[0x55; 16], &response_value, &mut rng);
8112
8113        let mut has_resource_adv = false;
8114        let mut has_direct_response = false;
8115        for action in &actions {
8116            if let LinkManagerAction::SendPacket { raw, .. } = action {
8117                let pkt = RawPacket::unpack(raw).unwrap();
8118                has_resource_adv |= pkt.context == constants::CONTEXT_RESOURCE_ADV;
8119                has_direct_response |= pkt.context == constants::CONTEXT_RESPONSE;
8120            }
8121        }
8122
8123        assert!(
8124            has_resource_adv,
8125            "large management responses should advertise a response resource"
8126        );
8127        assert!(
8128            !has_direct_response,
8129            "large management responses should not use a direct CONTEXT_RESPONSE packet"
8130        );
8131    }
8132}