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;
10
11use super::compressor::Bzip2Compressor;
12use rns_core::channel::{Channel, Sequence};
13use rns_core::constants;
14use rns_core::link::types::{LinkId, LinkState, TeardownReason};
15use rns_core::link::{LinkAction, LinkEngine, LinkMode};
16use rns_core::packet::{PacketFlags, RawPacket};
17use rns_core::resource::{ResourceAction, ResourceReceiver, ResourceSender};
18use rns_crypto::ed25519::Ed25519PrivateKey;
19use rns_crypto::Rng;
20
21use super::time;
22
23/// Resource acceptance strategy.
24#[derive(Debug, Clone, Copy, PartialEq, Eq)]
25pub enum ResourceStrategy {
26    /// Reject all incoming resources.
27    AcceptNone,
28    /// Accept all incoming resources automatically.
29    AcceptAll,
30    /// Query the application callback for each resource.
31    AcceptApp,
32}
33
34impl Default for ResourceStrategy {
35    fn default() -> Self {
36        ResourceStrategy::AcceptNone
37    }
38}
39
40/// A managed link wrapping LinkEngine + optional Channel + resources.
41struct ManagedLink {
42    engine: LinkEngine,
43    channel: Option<Channel>,
44    pending_channel_packets: HashMap<[u8; 32], Sequence>,
45    channel_send_ok: u64,
46    channel_send_not_ready: u64,
47    channel_send_too_big: u64,
48    channel_send_other_error: u64,
49    channel_messages_received: u64,
50    channel_proofs_sent: u64,
51    channel_proofs_received: u64,
52    /// Destination hash this link belongs to.
53    dest_hash: [u8; 16],
54    /// Remote identity (hash, public_key) once identified.
55    remote_identity: Option<([u8; 16], [u8; 64])>,
56    /// Destination's Ed25519 signing public key (for initiator to verify LRPROOF).
57    dest_sig_pub_bytes: Option<[u8; 32]>,
58    /// Active incoming resource transfers.
59    incoming_resources: Vec<ResourceReceiver>,
60    /// Active outgoing resource transfers.
61    outgoing_resources: Vec<ResourceSender>,
62    /// Resource acceptance strategy.
63    resource_strategy: ResourceStrategy,
64}
65
66/// A registered link destination that can accept incoming LINKREQUEST.
67struct LinkDestination {
68    sig_prv: Ed25519PrivateKey,
69    sig_pub_bytes: [u8; 32],
70    resource_strategy: ResourceStrategy,
71}
72
73/// A registered request handler for a path.
74struct RequestHandlerEntry {
75    /// The path this handler serves (e.g. "/status").
76    path: String,
77    /// The truncated hash of the path (first 16 bytes of SHA-256).
78    path_hash: [u8; 16],
79    /// Access control: None means allow all, Some(list) means allow only listed identities.
80    allowed_list: Option<Vec<[u8; 16]>>,
81    /// Handler function: (link_id, path, request_id, data, remote_identity) -> Option<response_data>.
82    handler:
83        Box<dyn Fn(LinkId, &str, &[u8], Option<&([u8; 16], [u8; 64])>) -> Option<Vec<u8>> + Send>,
84}
85
86/// Actions produced by LinkManager for the driver to dispatch.
87#[derive(Debug)]
88pub enum LinkManagerAction {
89    /// Send a packet via the transport engine outbound path.
90    SendPacket {
91        raw: Vec<u8>,
92        dest_type: u8,
93        attached_interface: Option<rns_core::transport::types::InterfaceId>,
94    },
95    /// Link established — notify callbacks.
96    LinkEstablished {
97        link_id: LinkId,
98        dest_hash: [u8; 16],
99        rtt: f64,
100        is_initiator: bool,
101    },
102    /// Link closed — notify callbacks.
103    LinkClosed {
104        link_id: LinkId,
105        reason: Option<TeardownReason>,
106    },
107    /// Remote peer identified — notify callbacks.
108    RemoteIdentified {
109        link_id: LinkId,
110        identity_hash: [u8; 16],
111        public_key: [u8; 64],
112    },
113    /// Register a link_id as local destination in transport (for receiving link data).
114    RegisterLinkDest { link_id: LinkId },
115    /// Deregister a link_id from transport local destinations.
116    DeregisterLinkDest { link_id: LinkId },
117    /// A management request that needs to be handled by the driver.
118    /// The driver has access to engine state needed to build the response.
119    ManagementRequest {
120        link_id: LinkId,
121        path_hash: [u8; 16],
122        /// The request data (msgpack-encoded Value from the request array).
123        data: Vec<u8>,
124        /// The request_id (truncated hash of the packed request).
125        request_id: [u8; 16],
126        remote_identity: Option<([u8; 16], [u8; 64])>,
127    },
128    /// Resource data fully received and assembled.
129    ResourceReceived {
130        link_id: LinkId,
131        data: Vec<u8>,
132        metadata: Option<Vec<u8>>,
133    },
134    /// Resource transfer completed (proof validated on sender side).
135    ResourceCompleted { link_id: LinkId },
136    /// Resource transfer failed.
137    ResourceFailed { link_id: LinkId, error: String },
138    /// Resource transfer progress update.
139    ResourceProgress {
140        link_id: LinkId,
141        received: usize,
142        total: usize,
143    },
144    /// Query application whether to accept an incoming resource (for AcceptApp strategy).
145    ResourceAcceptQuery {
146        link_id: LinkId,
147        resource_hash: Vec<u8>,
148        transfer_size: u64,
149        has_metadata: bool,
150    },
151    /// Channel message received on a link.
152    ChannelMessageReceived {
153        link_id: LinkId,
154        msgtype: u16,
155        payload: Vec<u8>,
156    },
157    /// Generic link data received (CONTEXT_NONE).
158    LinkDataReceived {
159        link_id: LinkId,
160        context: u8,
161        data: Vec<u8>,
162    },
163    /// Response received on a link.
164    ResponseReceived {
165        link_id: LinkId,
166        request_id: [u8; 16],
167        data: Vec<u8>,
168    },
169    /// A link request was received (for hook notification).
170    LinkRequestReceived {
171        link_id: LinkId,
172        receiving_interface: rns_core::transport::types::InterfaceId,
173    },
174}
175
176/// Manages multiple links, link destinations, and request/response.
177pub struct LinkManager {
178    links: HashMap<LinkId, ManagedLink>,
179    link_destinations: HashMap<[u8; 16], LinkDestination>,
180    request_handlers: Vec<RequestHandlerEntry>,
181    /// Path hashes that should be handled externally (by the driver) rather than
182    /// by registered handler closures. Used for management destinations.
183    management_paths: Vec<[u8; 16]>,
184}
185
186impl LinkManager {
187    /// Create a new empty link manager.
188    pub fn new() -> Self {
189        LinkManager {
190            links: HashMap::new(),
191            link_destinations: HashMap::new(),
192            request_handlers: Vec::new(),
193            management_paths: Vec::new(),
194        }
195    }
196
197    /// Register a path hash as a management path.
198    /// Management requests are returned as ManagementRequest actions
199    /// for the driver to handle (since they need access to engine state).
200    pub fn register_management_path(&mut self, path_hash: [u8; 16]) {
201        if !self.management_paths.contains(&path_hash) {
202            self.management_paths.push(path_hash);
203        }
204    }
205
206    /// Get the derived session key for a link (needed for hole-punch token derivation).
207    pub fn get_derived_key(&self, link_id: &LinkId) -> Option<Vec<u8>> {
208        self.links
209            .get(link_id)
210            .and_then(|link| link.engine.derived_key().map(|dk| dk.to_vec()))
211    }
212
213    /// Register a destination that can accept incoming links.
214    pub fn register_link_destination(
215        &mut self,
216        dest_hash: [u8; 16],
217        sig_prv: Ed25519PrivateKey,
218        sig_pub_bytes: [u8; 32],
219        resource_strategy: ResourceStrategy,
220    ) {
221        self.link_destinations.insert(
222            dest_hash,
223            LinkDestination {
224                sig_prv,
225                sig_pub_bytes,
226                resource_strategy,
227            },
228        );
229    }
230
231    /// Deregister a link destination.
232    pub fn deregister_link_destination(&mut self, dest_hash: &[u8; 16]) {
233        self.link_destinations.remove(dest_hash);
234    }
235
236    /// Register a request handler for a given path.
237    ///
238    /// `path`: the request path string (e.g. "/status")
239    /// `allowed_list`: None = allow all, Some(list) = restrict to these identity hashes
240    /// `handler`: called with (link_id, path, request_data, remote_identity) -> Option<response>
241    pub fn register_request_handler<F>(
242        &mut self,
243        path: &str,
244        allowed_list: Option<Vec<[u8; 16]>>,
245        handler: F,
246    ) where
247        F: Fn(LinkId, &str, &[u8], Option<&([u8; 16], [u8; 64])>) -> Option<Vec<u8>>
248            + Send
249            + 'static,
250    {
251        let path_hash = compute_path_hash(path);
252        self.request_handlers.push(RequestHandlerEntry {
253            path: path.to_string(),
254            path_hash,
255            allowed_list,
256            handler: Box::new(handler),
257        });
258    }
259
260    /// Create an outbound link to a destination.
261    ///
262    /// `dest_sig_pub_bytes` is the destination's Ed25519 signing public key
263    /// (needed to verify LRPROOF). In Python this comes from the Destination's Identity.
264    ///
265    /// Returns `(link_id, actions)`. The first action will be a SendPacket with
266    /// the LINKREQUEST.
267    pub fn create_link(
268        &mut self,
269        dest_hash: &[u8; 16],
270        dest_sig_pub_bytes: &[u8; 32],
271        hops: u8,
272        mtu: u32,
273        rng: &mut dyn Rng,
274    ) -> (LinkId, Vec<LinkManagerAction>) {
275        let mode = LinkMode::Aes256Cbc;
276        let (mut engine, request_data) =
277            LinkEngine::new_initiator(dest_hash, hops, mode, Some(mtu), time::now(), rng);
278
279        // Build the LINKREQUEST packet to compute link_id
280        let flags = PacketFlags {
281            header_type: constants::HEADER_1,
282            context_flag: constants::FLAG_UNSET,
283            transport_type: constants::TRANSPORT_BROADCAST,
284            destination_type: constants::DESTINATION_SINGLE,
285            packet_type: constants::PACKET_TYPE_LINKREQUEST,
286        };
287
288        let packet = match RawPacket::pack(
289            flags,
290            0,
291            dest_hash,
292            None,
293            constants::CONTEXT_NONE,
294            &request_data,
295        ) {
296            Ok(p) => p,
297            Err(_) => {
298                // Should not happen with valid data
299                return ([0u8; 16], Vec::new());
300            }
301        };
302
303        engine.set_link_id_from_hashable(&packet.get_hashable_part(), request_data.len());
304        let link_id = *engine.link_id();
305
306        let managed = ManagedLink {
307            engine,
308            channel: None,
309            pending_channel_packets: HashMap::new(),
310            channel_send_ok: 0,
311            channel_send_not_ready: 0,
312            channel_send_too_big: 0,
313            channel_send_other_error: 0,
314            channel_messages_received: 0,
315            channel_proofs_sent: 0,
316            channel_proofs_received: 0,
317            dest_hash: *dest_hash,
318            remote_identity: None,
319            dest_sig_pub_bytes: Some(*dest_sig_pub_bytes),
320            incoming_resources: Vec::new(),
321            outgoing_resources: Vec::new(),
322            resource_strategy: ResourceStrategy::default(),
323        };
324        self.links.insert(link_id, managed);
325
326        let mut actions = Vec::new();
327        // Register the link_id as a local destination so we can receive LRPROOF
328        actions.push(LinkManagerAction::RegisterLinkDest { link_id });
329        // Send the LINKREQUEST packet
330        actions.push(LinkManagerAction::SendPacket {
331            raw: packet.raw,
332            dest_type: constants::DESTINATION_LINK,
333            attached_interface: None,
334        });
335
336        (link_id, actions)
337    }
338
339    /// Handle a packet delivered locally (via DeliverLocal).
340    ///
341    /// Returns actions for the driver to dispatch. The `dest_hash` is the
342    /// packet's destination_hash field. `raw` is the full packet bytes.
343    /// `packet_hash` is the SHA-256 hash.
344    pub fn handle_local_delivery(
345        &mut self,
346        dest_hash: [u8; 16],
347        raw: &[u8],
348        packet_hash: [u8; 32],
349        receiving_interface: rns_core::transport::types::InterfaceId,
350        rng: &mut dyn Rng,
351    ) -> Vec<LinkManagerAction> {
352        let packet = match RawPacket::unpack(raw) {
353            Ok(p) => p,
354            Err(_) => return Vec::new(),
355        };
356
357        match packet.flags.packet_type {
358            constants::PACKET_TYPE_LINKREQUEST => {
359                self.handle_linkrequest(&dest_hash, &packet, receiving_interface, rng)
360            }
361            constants::PACKET_TYPE_PROOF if packet.context == constants::CONTEXT_LRPROOF => {
362                // LRPROOF: dest_hash is the link_id
363                self.handle_lrproof(&dest_hash, &packet, rng)
364            }
365            constants::PACKET_TYPE_PROOF => self.handle_link_proof(&dest_hash, &packet, rng),
366            constants::PACKET_TYPE_DATA => {
367                self.handle_link_data(&dest_hash, &packet, packet_hash, rng)
368            }
369            _ => Vec::new(),
370        }
371    }
372
373    /// Handle an incoming LINKREQUEST packet.
374    fn handle_linkrequest(
375        &mut self,
376        dest_hash: &[u8; 16],
377        packet: &RawPacket,
378        receiving_interface: rns_core::transport::types::InterfaceId,
379        rng: &mut dyn Rng,
380    ) -> Vec<LinkManagerAction> {
381        // Look up the link destination
382        let ld = match self.link_destinations.get(dest_hash) {
383            Some(ld) => ld,
384            None => return Vec::new(),
385        };
386
387        let hashable = packet.get_hashable_part();
388        let now = time::now();
389
390        // Create responder engine
391        let (engine, lrproof_data) = match LinkEngine::new_responder(
392            &ld.sig_prv,
393            &ld.sig_pub_bytes,
394            &packet.data,
395            &hashable,
396            dest_hash,
397            packet.hops,
398            now,
399            rng,
400        ) {
401            Ok(r) => r,
402            Err(e) => {
403                log::debug!("LINKREQUEST rejected: {}", e);
404                return Vec::new();
405            }
406        };
407
408        let link_id = *engine.link_id();
409
410        let managed = ManagedLink {
411            engine,
412            channel: None,
413            pending_channel_packets: HashMap::new(),
414            channel_send_ok: 0,
415            channel_send_not_ready: 0,
416            channel_send_too_big: 0,
417            channel_send_other_error: 0,
418            channel_messages_received: 0,
419            channel_proofs_sent: 0,
420            channel_proofs_received: 0,
421            dest_hash: *dest_hash,
422            remote_identity: None,
423            dest_sig_pub_bytes: None,
424            incoming_resources: Vec::new(),
425            outgoing_resources: Vec::new(),
426            resource_strategy: ld.resource_strategy,
427        };
428        self.links.insert(link_id, managed);
429
430        // Build LRPROOF packet: type=PROOF, context=LRPROOF, dest=link_id
431        let flags = PacketFlags {
432            header_type: constants::HEADER_1,
433            context_flag: constants::FLAG_UNSET,
434            transport_type: constants::TRANSPORT_BROADCAST,
435            destination_type: constants::DESTINATION_LINK,
436            packet_type: constants::PACKET_TYPE_PROOF,
437        };
438
439        let mut actions = Vec::new();
440
441        // Register link_id as local destination so we receive link data
442        actions.push(LinkManagerAction::RegisterLinkDest { link_id });
443
444        if let Ok(pkt) = RawPacket::pack(
445            flags,
446            0,
447            &link_id,
448            None,
449            constants::CONTEXT_LRPROOF,
450            &lrproof_data,
451        ) {
452            actions.push(LinkManagerAction::SendPacket {
453                raw: pkt.raw,
454                dest_type: constants::DESTINATION_LINK,
455                attached_interface: None,
456            });
457        }
458
459        // Notify hook system about the incoming link request
460        actions.push(LinkManagerAction::LinkRequestReceived {
461            link_id,
462            receiving_interface,
463        });
464
465        actions
466    }
467
468    fn handle_link_proof(
469        &mut self,
470        link_id: &LinkId,
471        packet: &RawPacket,
472        rng: &mut dyn Rng,
473    ) -> Vec<LinkManagerAction> {
474        if packet.data.len() < 32 {
475            return Vec::new();
476        }
477
478        let mut tracked_hash = [0u8; 32];
479        tracked_hash.copy_from_slice(&packet.data[..32]);
480
481        let Some(link) = self.links.get_mut(link_id) else {
482            return Vec::new();
483        };
484        let Some(sequence) = link.pending_channel_packets.remove(&tracked_hash) else {
485            return Vec::new();
486        };
487        link.channel_proofs_received += 1;
488        let Some(channel) = link.channel.as_mut() else {
489            return Vec::new();
490        };
491
492        let chan_actions = channel.packet_delivered(sequence);
493        let _ = channel;
494        let _ = link;
495        self.process_channel_actions(link_id, chan_actions, rng)
496    }
497
498    fn build_link_packet_proof(
499        &mut self,
500        link_id: &LinkId,
501        packet_hash: &[u8; 32],
502    ) -> Vec<LinkManagerAction> {
503        let dest_hash = match self.links.get(link_id) {
504            Some(link) => link.dest_hash,
505            None => return Vec::new(),
506        };
507        let Some(ld) = self.link_destinations.get(&dest_hash) else {
508            return Vec::new();
509        };
510        if let Some(link) = self.links.get_mut(link_id) {
511            link.channel_proofs_sent += 1;
512        }
513
514        let signature = ld.sig_prv.sign(packet_hash);
515        let mut proof_data = Vec::with_capacity(96);
516        proof_data.extend_from_slice(packet_hash);
517        proof_data.extend_from_slice(&signature);
518
519        let flags = PacketFlags {
520            header_type: constants::HEADER_1,
521            context_flag: constants::FLAG_UNSET,
522            transport_type: constants::TRANSPORT_BROADCAST,
523            destination_type: constants::DESTINATION_LINK,
524            packet_type: constants::PACKET_TYPE_PROOF,
525        };
526        if let Ok(pkt) = RawPacket::pack(
527            flags,
528            0,
529            link_id,
530            None,
531            constants::CONTEXT_NONE,
532            &proof_data,
533        ) {
534            vec![LinkManagerAction::SendPacket {
535                raw: pkt.raw,
536                dest_type: constants::DESTINATION_LINK,
537                attached_interface: None,
538            }]
539        } else {
540            Vec::new()
541        }
542    }
543
544    /// Handle an incoming LRPROOF packet (initiator side).
545    fn handle_lrproof(
546        &mut self,
547        link_id_bytes: &[u8; 16],
548        packet: &RawPacket,
549        rng: &mut dyn Rng,
550    ) -> Vec<LinkManagerAction> {
551        let link = match self.links.get_mut(link_id_bytes) {
552            Some(l) => l,
553            None => return Vec::new(),
554        };
555
556        if link.engine.state() != LinkState::Pending || !link.engine.is_initiator() {
557            return Vec::new();
558        }
559
560        // The destination's signing pub key was stored when create_link was called
561        let dest_sig_pub_bytes = match link.dest_sig_pub_bytes {
562            Some(b) => b,
563            None => {
564                log::debug!("LRPROOF: no destination signing key available");
565                return Vec::new();
566            }
567        };
568
569        let now = time::now();
570        let (lrrtt_encrypted, link_actions) =
571            match link
572                .engine
573                .handle_lrproof(&packet.data, &dest_sig_pub_bytes, now, rng)
574            {
575                Ok(r) => r,
576                Err(e) => {
577                    log::debug!("LRPROOF validation failed: {}", e);
578                    return Vec::new();
579                }
580            };
581
582        let link_id = *link.engine.link_id();
583        let mut actions = Vec::new();
584
585        // Process link actions (StateChanged, LinkEstablished)
586        actions.extend(self.process_link_actions(&link_id, &link_actions));
587
588        // Send LRRTT: type=DATA, context=LRRTT, dest=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_LINK,
594            packet_type: constants::PACKET_TYPE_DATA,
595        };
596
597        if let Ok(pkt) = RawPacket::pack(
598            flags,
599            0,
600            &link_id,
601            None,
602            constants::CONTEXT_LRRTT,
603            &lrrtt_encrypted,
604        ) {
605            actions.push(LinkManagerAction::SendPacket {
606                raw: pkt.raw,
607                dest_type: constants::DESTINATION_LINK,
608                attached_interface: None,
609            });
610        }
611
612        // Initialize channel now that link is active
613        if let Some(link) = self.links.get_mut(&link_id) {
614            if link.engine.state() == LinkState::Active {
615                let rtt = link.engine.rtt().unwrap_or(1.0);
616                link.channel = Some(Channel::new(rtt));
617            }
618        }
619
620        actions
621    }
622
623    /// Handle DATA packets on an established link.
624    ///
625    /// Structured to avoid borrow checker issues: we perform engine operations
626    /// on the link, collect intermediate results, drop the mutable borrow, then
627    /// call self methods that need immutable access.
628    fn handle_link_data(
629        &mut self,
630        link_id_bytes: &[u8; 16],
631        packet: &RawPacket,
632        packet_hash: [u8; 32],
633        rng: &mut dyn Rng,
634    ) -> Vec<LinkManagerAction> {
635        // First pass: perform engine operations, collect results
636        enum LinkDataResult {
637            Lrrtt {
638                link_id: LinkId,
639                link_actions: Vec<LinkAction>,
640            },
641            Identify {
642                link_id: LinkId,
643                link_actions: Vec<LinkAction>,
644            },
645            Keepalive {
646                link_id: LinkId,
647                inbound_actions: Vec<LinkAction>,
648            },
649            LinkClose {
650                link_id: LinkId,
651                teardown_actions: Vec<LinkAction>,
652            },
653            Channel {
654                link_id: LinkId,
655                inbound_actions: Vec<LinkAction>,
656                plaintext: Vec<u8>,
657                packet_hash: [u8; 32],
658            },
659            Request {
660                link_id: LinkId,
661                inbound_actions: Vec<LinkAction>,
662                plaintext: Vec<u8>,
663            },
664            Response {
665                link_id: LinkId,
666                inbound_actions: Vec<LinkAction>,
667                plaintext: Vec<u8>,
668            },
669            Generic {
670                link_id: LinkId,
671                inbound_actions: Vec<LinkAction>,
672                plaintext: Vec<u8>,
673                context: u8,
674                packet_hash: [u8; 32],
675            },
676            /// Resource advertisement (link-decrypted).
677            ResourceAdv {
678                link_id: LinkId,
679                inbound_actions: Vec<LinkAction>,
680                plaintext: Vec<u8>,
681            },
682            /// Resource part request (link-decrypted).
683            ResourceReq {
684                link_id: LinkId,
685                inbound_actions: Vec<LinkAction>,
686                plaintext: Vec<u8>,
687            },
688            /// Resource hashmap update (link-decrypted).
689            ResourceHmu {
690                link_id: LinkId,
691                inbound_actions: Vec<LinkAction>,
692                plaintext: Vec<u8>,
693            },
694            /// Resource part data (NOT link-decrypted; parts are pre-encrypted by ResourceSender).
695            ResourcePart {
696                link_id: LinkId,
697                inbound_actions: Vec<LinkAction>,
698                raw_data: Vec<u8>,
699            },
700            /// Resource proof (feed to sender).
701            ResourcePrf {
702                link_id: LinkId,
703                inbound_actions: Vec<LinkAction>,
704                plaintext: Vec<u8>,
705            },
706            /// Resource cancel from initiator (link-decrypted).
707            ResourceIcl {
708                link_id: LinkId,
709                inbound_actions: Vec<LinkAction>,
710            },
711            /// Resource cancel from receiver (link-decrypted).
712            ResourceRcl {
713                link_id: LinkId,
714                inbound_actions: Vec<LinkAction>,
715            },
716            Error,
717        }
718
719        let result = {
720            let link = match self.links.get_mut(link_id_bytes) {
721                Some(l) => l,
722                None => return Vec::new(),
723            };
724
725            match packet.context {
726                constants::CONTEXT_LRRTT => {
727                    match link.engine.handle_lrrtt(&packet.data, time::now()) {
728                        Ok(link_actions) => {
729                            let link_id = *link.engine.link_id();
730                            LinkDataResult::Lrrtt {
731                                link_id,
732                                link_actions,
733                            }
734                        }
735                        Err(e) => {
736                            log::debug!("LRRTT handling failed: {}", e);
737                            LinkDataResult::Error
738                        }
739                    }
740                }
741                constants::CONTEXT_LINKIDENTIFY => {
742                    match link.engine.handle_identify(&packet.data) {
743                        Ok(link_actions) => {
744                            let link_id = *link.engine.link_id();
745                            link.remote_identity = link.engine.remote_identity().cloned();
746                            LinkDataResult::Identify {
747                                link_id,
748                                link_actions,
749                            }
750                        }
751                        Err(e) => {
752                            log::debug!("LINKIDENTIFY failed: {}", e);
753                            LinkDataResult::Error
754                        }
755                    }
756                }
757                constants::CONTEXT_KEEPALIVE => {
758                    let inbound_actions = link.engine.record_inbound(time::now());
759                    let link_id = *link.engine.link_id();
760                    LinkDataResult::Keepalive {
761                        link_id,
762                        inbound_actions,
763                    }
764                }
765                constants::CONTEXT_LINKCLOSE => {
766                    let teardown_actions = link.engine.handle_teardown();
767                    let link_id = *link.engine.link_id();
768                    LinkDataResult::LinkClose {
769                        link_id,
770                        teardown_actions,
771                    }
772                }
773                constants::CONTEXT_CHANNEL => match link.engine.decrypt(&packet.data) {
774                    Ok(plaintext) => {
775                        let inbound_actions = link.engine.record_inbound(time::now());
776                        let link_id = *link.engine.link_id();
777                        LinkDataResult::Channel {
778                            link_id,
779                            inbound_actions,
780                            plaintext,
781                            packet_hash,
782                        }
783                    }
784                    Err(_) => LinkDataResult::Error,
785                },
786                constants::CONTEXT_REQUEST => match link.engine.decrypt(&packet.data) {
787                    Ok(plaintext) => {
788                        let inbound_actions = link.engine.record_inbound(time::now());
789                        let link_id = *link.engine.link_id();
790                        LinkDataResult::Request {
791                            link_id,
792                            inbound_actions,
793                            plaintext,
794                        }
795                    }
796                    Err(_) => LinkDataResult::Error,
797                },
798                constants::CONTEXT_RESPONSE => match link.engine.decrypt(&packet.data) {
799                    Ok(plaintext) => {
800                        let inbound_actions = link.engine.record_inbound(time::now());
801                        let link_id = *link.engine.link_id();
802                        LinkDataResult::Response {
803                            link_id,
804                            inbound_actions,
805                            plaintext,
806                        }
807                    }
808                    Err(_) => LinkDataResult::Error,
809                },
810                // --- Resource contexts ---
811                constants::CONTEXT_RESOURCE_ADV => match link.engine.decrypt(&packet.data) {
812                    Ok(plaintext) => {
813                        let inbound_actions = link.engine.record_inbound(time::now());
814                        let link_id = *link.engine.link_id();
815                        LinkDataResult::ResourceAdv {
816                            link_id,
817                            inbound_actions,
818                            plaintext,
819                        }
820                    }
821                    Err(_) => LinkDataResult::Error,
822                },
823                constants::CONTEXT_RESOURCE_REQ => match link.engine.decrypt(&packet.data) {
824                    Ok(plaintext) => {
825                        let inbound_actions = link.engine.record_inbound(time::now());
826                        let link_id = *link.engine.link_id();
827                        LinkDataResult::ResourceReq {
828                            link_id,
829                            inbound_actions,
830                            plaintext,
831                        }
832                    }
833                    Err(_) => LinkDataResult::Error,
834                },
835                constants::CONTEXT_RESOURCE_HMU => match link.engine.decrypt(&packet.data) {
836                    Ok(plaintext) => {
837                        let inbound_actions = link.engine.record_inbound(time::now());
838                        let link_id = *link.engine.link_id();
839                        LinkDataResult::ResourceHmu {
840                            link_id,
841                            inbound_actions,
842                            plaintext,
843                        }
844                    }
845                    Err(_) => LinkDataResult::Error,
846                },
847                constants::CONTEXT_RESOURCE => {
848                    // Resource parts are NOT link-decrypted — they're pre-encrypted by ResourceSender
849                    let inbound_actions = link.engine.record_inbound(time::now());
850                    let link_id = *link.engine.link_id();
851                    LinkDataResult::ResourcePart {
852                        link_id,
853                        inbound_actions,
854                        raw_data: packet.data.clone(),
855                    }
856                }
857                constants::CONTEXT_RESOURCE_PRF => match link.engine.decrypt(&packet.data) {
858                    Ok(plaintext) => {
859                        let inbound_actions = link.engine.record_inbound(time::now());
860                        let link_id = *link.engine.link_id();
861                        LinkDataResult::ResourcePrf {
862                            link_id,
863                            inbound_actions,
864                            plaintext,
865                        }
866                    }
867                    Err(_) => LinkDataResult::Error,
868                },
869                constants::CONTEXT_RESOURCE_ICL => {
870                    let _ = link.engine.decrypt(&packet.data); // decrypt to validate
871                    let inbound_actions = link.engine.record_inbound(time::now());
872                    let link_id = *link.engine.link_id();
873                    LinkDataResult::ResourceIcl {
874                        link_id,
875                        inbound_actions,
876                    }
877                }
878                constants::CONTEXT_RESOURCE_RCL => {
879                    let _ = link.engine.decrypt(&packet.data); // decrypt to validate
880                    let inbound_actions = link.engine.record_inbound(time::now());
881                    let link_id = *link.engine.link_id();
882                    LinkDataResult::ResourceRcl {
883                        link_id,
884                        inbound_actions,
885                    }
886                }
887                _ => match link.engine.decrypt(&packet.data) {
888                    Ok(plaintext) => {
889                        let inbound_actions = link.engine.record_inbound(time::now());
890                        let link_id = *link.engine.link_id();
891                        LinkDataResult::Generic {
892                            link_id,
893                            inbound_actions,
894                            plaintext,
895                            context: packet.context,
896                            packet_hash,
897                        }
898                    }
899                    Err(_) => LinkDataResult::Error,
900                },
901            }
902        }; // mutable borrow of self.links dropped here
903
904        // Second pass: process results using self methods
905        let mut actions = Vec::new();
906        match result {
907            LinkDataResult::Lrrtt {
908                link_id,
909                link_actions,
910            } => {
911                actions.extend(self.process_link_actions(&link_id, &link_actions));
912                // Initialize channel
913                if let Some(link) = self.links.get_mut(&link_id) {
914                    if link.engine.state() == LinkState::Active {
915                        let rtt = link.engine.rtt().unwrap_or(1.0);
916                        link.channel = Some(Channel::new(rtt));
917                    }
918                }
919            }
920            LinkDataResult::Identify {
921                link_id,
922                link_actions,
923            } => {
924                actions.extend(self.process_link_actions(&link_id, &link_actions));
925            }
926            LinkDataResult::Keepalive {
927                link_id,
928                inbound_actions,
929            } => {
930                actions.extend(self.process_link_actions(&link_id, &inbound_actions));
931                // record_inbound() already updated last_inbound, so the link
932                // won't go stale.  The regular tick() keepalive mechanism will
933                // send keepalives when needs_keepalive() returns true.
934                // Do NOT reply here — unconditional replies create an infinite
935                // ping-pong loop between the two link endpoints.
936            }
937            LinkDataResult::LinkClose {
938                link_id,
939                teardown_actions,
940            } => {
941                actions.extend(self.process_link_actions(&link_id, &teardown_actions));
942            }
943            LinkDataResult::Channel {
944                link_id,
945                inbound_actions,
946                plaintext,
947                packet_hash,
948            } => {
949                actions.extend(self.process_link_actions(&link_id, &inbound_actions));
950                // Feed plaintext to channel
951                if let Some(link) = self.links.get_mut(&link_id) {
952                    if let Some(ref mut channel) = link.channel {
953                        let chan_actions = channel.receive(&plaintext, time::now());
954                        link.channel_messages_received += chan_actions
955                            .iter()
956                            .filter(|action| {
957                                matches!(
958                                    action,
959                                    rns_core::channel::ChannelAction::MessageReceived { .. }
960                                )
961                            })
962                            .count()
963                            as u64;
964                        // process_channel_actions needs immutable self, so collect first
965                        let _ = link;
966                        actions.extend(self.process_channel_actions(&link_id, chan_actions, rng));
967                    }
968                }
969                actions.extend(self.build_link_packet_proof(&link_id, &packet_hash));
970            }
971            LinkDataResult::Request {
972                link_id,
973                inbound_actions,
974                plaintext,
975            } => {
976                actions.extend(self.process_link_actions(&link_id, &inbound_actions));
977                actions.extend(self.handle_request(&link_id, &plaintext, rng));
978            }
979            LinkDataResult::Response {
980                link_id,
981                inbound_actions,
982                plaintext,
983            } => {
984                actions.extend(self.process_link_actions(&link_id, &inbound_actions));
985                // Unpack msgpack response: [Bin(request_id), response_value]
986                actions.extend(self.handle_response(&link_id, &plaintext));
987            }
988            LinkDataResult::Generic {
989                link_id,
990                inbound_actions,
991                plaintext,
992                context,
993                packet_hash,
994            } => {
995                actions.extend(self.process_link_actions(&link_id, &inbound_actions));
996                actions.push(LinkManagerAction::LinkDataReceived {
997                    link_id,
998                    context,
999                    data: plaintext,
1000                });
1001
1002                actions.extend(self.build_link_packet_proof(&link_id, &packet_hash));
1003            }
1004            LinkDataResult::ResourceAdv {
1005                link_id,
1006                inbound_actions,
1007                plaintext,
1008            } => {
1009                actions.extend(self.process_link_actions(&link_id, &inbound_actions));
1010                actions.extend(self.handle_resource_adv(&link_id, &plaintext, rng));
1011            }
1012            LinkDataResult::ResourceReq {
1013                link_id,
1014                inbound_actions,
1015                plaintext,
1016            } => {
1017                actions.extend(self.process_link_actions(&link_id, &inbound_actions));
1018                actions.extend(self.handle_resource_req(&link_id, &plaintext, rng));
1019            }
1020            LinkDataResult::ResourceHmu {
1021                link_id,
1022                inbound_actions,
1023                plaintext,
1024            } => {
1025                actions.extend(self.process_link_actions(&link_id, &inbound_actions));
1026                actions.extend(self.handle_resource_hmu(&link_id, &plaintext, rng));
1027            }
1028            LinkDataResult::ResourcePart {
1029                link_id,
1030                inbound_actions,
1031                raw_data,
1032            } => {
1033                actions.extend(self.process_link_actions(&link_id, &inbound_actions));
1034                actions.extend(self.handle_resource_part(&link_id, &raw_data, rng));
1035            }
1036            LinkDataResult::ResourcePrf {
1037                link_id,
1038                inbound_actions,
1039                plaintext,
1040            } => {
1041                actions.extend(self.process_link_actions(&link_id, &inbound_actions));
1042                actions.extend(self.handle_resource_prf(&link_id, &plaintext));
1043            }
1044            LinkDataResult::ResourceIcl {
1045                link_id,
1046                inbound_actions,
1047            } => {
1048                actions.extend(self.process_link_actions(&link_id, &inbound_actions));
1049                actions.extend(self.handle_resource_icl(&link_id));
1050            }
1051            LinkDataResult::ResourceRcl {
1052                link_id,
1053                inbound_actions,
1054            } => {
1055                actions.extend(self.process_link_actions(&link_id, &inbound_actions));
1056                actions.extend(self.handle_resource_rcl(&link_id));
1057            }
1058            LinkDataResult::Error => {}
1059        }
1060
1061        actions
1062    }
1063
1064    /// Handle a request on a link.
1065    fn handle_request(
1066        &mut self,
1067        link_id: &LinkId,
1068        plaintext: &[u8],
1069        rng: &mut dyn Rng,
1070    ) -> Vec<LinkManagerAction> {
1071        use rns_core::msgpack::{self, Value};
1072
1073        // Python-compatible format: msgpack([timestamp, Bin(path_hash), data_value])
1074        let arr = match msgpack::unpack_exact(plaintext) {
1075            Ok(Value::Array(arr)) if arr.len() >= 3 => arr,
1076            _ => return Vec::new(),
1077        };
1078
1079        let path_hash_bytes = match &arr[1] {
1080            Value::Bin(b) if b.len() == 16 => b,
1081            _ => return Vec::new(),
1082        };
1083        let mut path_hash = [0u8; 16];
1084        path_hash.copy_from_slice(path_hash_bytes);
1085
1086        // Compute request_id = truncated_hash(packed_request_bytes)
1087        let request_id = rns_core::hash::truncated_hash(plaintext);
1088
1089        // Re-encode the data element for the handler
1090        let request_data = msgpack::pack(&arr[2]);
1091
1092        // Check if this is a management path (handled by the driver)
1093        if self.management_paths.contains(&path_hash) {
1094            let remote_identity = self
1095                .links
1096                .get(link_id)
1097                .and_then(|l| l.remote_identity)
1098                .map(|(h, k)| (h, k));
1099            return vec![LinkManagerAction::ManagementRequest {
1100                link_id: *link_id,
1101                path_hash,
1102                data: request_data,
1103                request_id,
1104                remote_identity,
1105            }];
1106        }
1107
1108        // Look up handler by path_hash
1109        let handler_idx = self
1110            .request_handlers
1111            .iter()
1112            .position(|h| h.path_hash == path_hash);
1113        let handler_idx = match handler_idx {
1114            Some(i) => i,
1115            None => return Vec::new(),
1116        };
1117
1118        // Check ACL
1119        let remote_identity = self
1120            .links
1121            .get(link_id)
1122            .and_then(|l| l.remote_identity.as_ref());
1123        let handler = &self.request_handlers[handler_idx];
1124        if let Some(ref allowed) = handler.allowed_list {
1125            match remote_identity {
1126                Some((identity_hash, _)) => {
1127                    if !allowed.contains(identity_hash) {
1128                        log::debug!("Request denied: identity not in allowed list");
1129                        return Vec::new();
1130                    }
1131                }
1132                None => {
1133                    log::debug!("Request denied: peer not identified");
1134                    return Vec::new();
1135                }
1136            }
1137        }
1138
1139        // Call handler
1140        let path = handler.path.clone();
1141        let response = (handler.handler)(*link_id, &path, &request_data, remote_identity);
1142
1143        let mut actions = Vec::new();
1144        if let Some(response_data) = response {
1145            actions.extend(self.build_response_packet(link_id, &request_id, &response_data, rng));
1146        }
1147
1148        actions
1149    }
1150
1151    /// Build a response packet for a request.
1152    /// `response_data` is the msgpack-encoded response value.
1153    fn build_response_packet(
1154        &self,
1155        link_id: &LinkId,
1156        request_id: &[u8; 16],
1157        response_data: &[u8],
1158        rng: &mut dyn Rng,
1159    ) -> Vec<LinkManagerAction> {
1160        use rns_core::msgpack::{self, Value};
1161
1162        // Python-compatible response: msgpack([Bin(request_id), response_value])
1163        let response_value = msgpack::unpack_exact(response_data)
1164            .unwrap_or_else(|_| Value::Bin(response_data.to_vec()));
1165
1166        let response_array = Value::Array(vec![Value::Bin(request_id.to_vec()), response_value]);
1167        let response_plaintext = msgpack::pack(&response_array);
1168
1169        let mut actions = Vec::new();
1170        if let Some(link) = self.links.get(link_id) {
1171            if let Ok(encrypted) = link.engine.encrypt(&response_plaintext, rng) {
1172                let flags = PacketFlags {
1173                    header_type: constants::HEADER_1,
1174                    context_flag: constants::FLAG_UNSET,
1175                    transport_type: constants::TRANSPORT_BROADCAST,
1176                    destination_type: constants::DESTINATION_LINK,
1177                    packet_type: constants::PACKET_TYPE_DATA,
1178                };
1179                if let Ok(pkt) = RawPacket::pack(
1180                    flags,
1181                    0,
1182                    link_id,
1183                    None,
1184                    constants::CONTEXT_RESPONSE,
1185                    &encrypted,
1186                ) {
1187                    actions.push(LinkManagerAction::SendPacket {
1188                        raw: pkt.raw,
1189                        dest_type: constants::DESTINATION_LINK,
1190                        attached_interface: None,
1191                    });
1192                }
1193            }
1194        }
1195        actions
1196    }
1197
1198    /// Send a management response on a link.
1199    /// Called by the driver after building the response for a ManagementRequest.
1200    pub fn send_management_response(
1201        &self,
1202        link_id: &LinkId,
1203        request_id: &[u8; 16],
1204        response_data: &[u8],
1205        rng: &mut dyn Rng,
1206    ) -> Vec<LinkManagerAction> {
1207        self.build_response_packet(link_id, request_id, response_data, rng)
1208    }
1209
1210    /// Send a request on a link.
1211    ///
1212    /// `data` is the msgpack-encoded request data value (e.g. msgpack([True]) for /status).
1213    ///
1214    /// Uses Python-compatible format: plaintext = msgpack([timestamp, path_hash_bytes, data_value]).
1215    /// Returns actions (the encrypted request packet). The response will arrive
1216    /// later via handle_local_delivery with CONTEXT_RESPONSE.
1217    pub fn send_request(
1218        &self,
1219        link_id: &LinkId,
1220        path: &str,
1221        data: &[u8],
1222        rng: &mut dyn Rng,
1223    ) -> Vec<LinkManagerAction> {
1224        use rns_core::msgpack::{self, Value};
1225
1226        let link = match self.links.get(link_id) {
1227            Some(l) => l,
1228            None => return Vec::new(),
1229        };
1230
1231        if link.engine.state() != LinkState::Active {
1232            return Vec::new();
1233        }
1234
1235        let path_hash = compute_path_hash(path);
1236
1237        // Decode data bytes to msgpack Value (or use Bin if can't decode)
1238        let data_value = msgpack::unpack_exact(data).unwrap_or_else(|_| Value::Bin(data.to_vec()));
1239
1240        // Python-compatible format: msgpack([timestamp, Bin(path_hash), data_value])
1241        let request_array = Value::Array(vec![
1242            Value::Float(time::now()),
1243            Value::Bin(path_hash.to_vec()),
1244            data_value,
1245        ]);
1246        let plaintext = msgpack::pack(&request_array);
1247
1248        let encrypted = match link.engine.encrypt(&plaintext, rng) {
1249            Ok(e) => e,
1250            Err(_) => return Vec::new(),
1251        };
1252
1253        let flags = PacketFlags {
1254            header_type: constants::HEADER_1,
1255            context_flag: constants::FLAG_UNSET,
1256            transport_type: constants::TRANSPORT_BROADCAST,
1257            destination_type: constants::DESTINATION_LINK,
1258            packet_type: constants::PACKET_TYPE_DATA,
1259        };
1260
1261        let mut actions = Vec::new();
1262        if let Ok(pkt) = RawPacket::pack(
1263            flags,
1264            0,
1265            link_id,
1266            None,
1267            constants::CONTEXT_REQUEST,
1268            &encrypted,
1269        ) {
1270            actions.push(LinkManagerAction::SendPacket {
1271                raw: pkt.raw,
1272                dest_type: constants::DESTINATION_LINK,
1273                attached_interface: None,
1274            });
1275        }
1276        actions
1277    }
1278
1279    /// Send encrypted data on a link with a given context.
1280    pub fn send_on_link(
1281        &self,
1282        link_id: &LinkId,
1283        plaintext: &[u8],
1284        context: u8,
1285        rng: &mut dyn Rng,
1286    ) -> Vec<LinkManagerAction> {
1287        let link = match self.links.get(link_id) {
1288            Some(l) => l,
1289            None => return Vec::new(),
1290        };
1291
1292        if link.engine.state() != LinkState::Active {
1293            return Vec::new();
1294        }
1295
1296        let encrypted = match link.engine.encrypt(plaintext, rng) {
1297            Ok(e) => e,
1298            Err(_) => return Vec::new(),
1299        };
1300
1301        let flags = PacketFlags {
1302            header_type: constants::HEADER_1,
1303            context_flag: constants::FLAG_UNSET,
1304            transport_type: constants::TRANSPORT_BROADCAST,
1305            destination_type: constants::DESTINATION_LINK,
1306            packet_type: constants::PACKET_TYPE_DATA,
1307        };
1308
1309        let mut actions = Vec::new();
1310        if let Ok(pkt) = RawPacket::pack(flags, 0, link_id, None, context, &encrypted) {
1311            actions.push(LinkManagerAction::SendPacket {
1312                raw: pkt.raw,
1313                dest_type: constants::DESTINATION_LINK,
1314                attached_interface: None,
1315            });
1316        }
1317        actions
1318    }
1319
1320    /// Send an identify message on a link (initiator reveals identity to responder).
1321    pub fn identify(
1322        &self,
1323        link_id: &LinkId,
1324        identity: &rns_crypto::identity::Identity,
1325        rng: &mut dyn Rng,
1326    ) -> Vec<LinkManagerAction> {
1327        let link = match self.links.get(link_id) {
1328            Some(l) => l,
1329            None => return Vec::new(),
1330        };
1331
1332        let encrypted = match link.engine.build_identify(identity, rng) {
1333            Ok(e) => e,
1334            Err(_) => return Vec::new(),
1335        };
1336
1337        let flags = PacketFlags {
1338            header_type: constants::HEADER_1,
1339            context_flag: constants::FLAG_UNSET,
1340            transport_type: constants::TRANSPORT_BROADCAST,
1341            destination_type: constants::DESTINATION_LINK,
1342            packet_type: constants::PACKET_TYPE_DATA,
1343        };
1344
1345        let mut actions = Vec::new();
1346        if let Ok(pkt) = RawPacket::pack(
1347            flags,
1348            0,
1349            link_id,
1350            None,
1351            constants::CONTEXT_LINKIDENTIFY,
1352            &encrypted,
1353        ) {
1354            actions.push(LinkManagerAction::SendPacket {
1355                raw: pkt.raw,
1356                dest_type: constants::DESTINATION_LINK,
1357                attached_interface: None,
1358            });
1359        }
1360        actions
1361    }
1362
1363    /// Tear down a link.
1364    pub fn teardown_link(&mut self, link_id: &LinkId) -> Vec<LinkManagerAction> {
1365        let link = match self.links.get_mut(link_id) {
1366            Some(l) => l,
1367            None => return Vec::new(),
1368        };
1369
1370        let teardown_actions = link.engine.teardown();
1371        if let Some(ref mut channel) = link.channel {
1372            channel.shutdown();
1373        }
1374
1375        let mut actions = self.process_link_actions(link_id, &teardown_actions);
1376
1377        // Send LINKCLOSE packet
1378        let flags = PacketFlags {
1379            header_type: constants::HEADER_1,
1380            context_flag: constants::FLAG_UNSET,
1381            transport_type: constants::TRANSPORT_BROADCAST,
1382            destination_type: constants::DESTINATION_LINK,
1383            packet_type: constants::PACKET_TYPE_DATA,
1384        };
1385        if let Ok(pkt) = RawPacket::pack(flags, 0, link_id, None, constants::CONTEXT_LINKCLOSE, &[])
1386        {
1387            actions.push(LinkManagerAction::SendPacket {
1388                raw: pkt.raw,
1389                dest_type: constants::DESTINATION_LINK,
1390                attached_interface: None,
1391            });
1392        }
1393
1394        actions
1395    }
1396
1397    /// Tear down all managed links.
1398    pub fn teardown_all_links(&mut self) -> Vec<LinkManagerAction> {
1399        let link_ids: Vec<LinkId> = self.links.keys().copied().collect();
1400        let mut actions = Vec::new();
1401        for link_id in link_ids {
1402            actions.extend(self.teardown_link(&link_id));
1403        }
1404        actions
1405    }
1406
1407    /// Handle a response on a link.
1408    fn handle_response(&self, link_id: &LinkId, plaintext: &[u8]) -> Vec<LinkManagerAction> {
1409        use rns_core::msgpack;
1410
1411        // Python-compatible response: msgpack([Bin(request_id), response_value])
1412        let arr = match msgpack::unpack_exact(plaintext) {
1413            Ok(msgpack::Value::Array(arr)) if arr.len() >= 2 => arr,
1414            _ => return Vec::new(),
1415        };
1416
1417        let request_id_bytes = match &arr[0] {
1418            msgpack::Value::Bin(b) if b.len() == 16 => b,
1419            _ => return Vec::new(),
1420        };
1421        let mut request_id = [0u8; 16];
1422        request_id.copy_from_slice(request_id_bytes);
1423
1424        let response_data = msgpack::pack(&arr[1]);
1425
1426        vec![LinkManagerAction::ResponseReceived {
1427            link_id: *link_id,
1428            request_id,
1429            data: response_data,
1430        }]
1431    }
1432
1433    /// Handle resource advertisement (CONTEXT_RESOURCE_ADV).
1434    fn handle_resource_adv(
1435        &mut self,
1436        link_id: &LinkId,
1437        adv_plaintext: &[u8],
1438        rng: &mut dyn Rng,
1439    ) -> Vec<LinkManagerAction> {
1440        let link = match self.links.get_mut(link_id) {
1441            Some(l) => l,
1442            None => return Vec::new(),
1443        };
1444
1445        let link_rtt = link.engine.rtt().unwrap_or(1.0);
1446        let now = time::now();
1447
1448        let receiver = match ResourceReceiver::from_advertisement(
1449            adv_plaintext,
1450            constants::RESOURCE_SDU,
1451            link_rtt,
1452            now,
1453            None,
1454            None,
1455        ) {
1456            Ok(r) => r,
1457            Err(e) => {
1458                log::debug!("Resource ADV rejected: {}", e);
1459                return Vec::new();
1460            }
1461        };
1462
1463        let strategy = link.resource_strategy;
1464        let resource_hash = receiver.resource_hash.clone();
1465        let transfer_size = receiver.transfer_size;
1466        let has_metadata = receiver.has_metadata;
1467
1468        match strategy {
1469            ResourceStrategy::AcceptNone => {
1470                // Reject: send RCL
1471                let reject_actions = {
1472                    let mut r = receiver;
1473                    r.reject()
1474                };
1475                self.process_resource_actions(link_id, reject_actions, rng)
1476            }
1477            ResourceStrategy::AcceptAll => {
1478                link.incoming_resources.push(receiver);
1479                let idx = link.incoming_resources.len() - 1;
1480                let resource_actions = link.incoming_resources[idx].accept(now);
1481                let _ = link;
1482                self.process_resource_actions(link_id, resource_actions, rng)
1483            }
1484            ResourceStrategy::AcceptApp => {
1485                link.incoming_resources.push(receiver);
1486                // Query application callback
1487                vec![LinkManagerAction::ResourceAcceptQuery {
1488                    link_id: *link_id,
1489                    resource_hash,
1490                    transfer_size,
1491                    has_metadata,
1492                }]
1493            }
1494        }
1495    }
1496
1497    /// Accept or reject a pending resource (for AcceptApp strategy).
1498    pub fn accept_resource(
1499        &mut self,
1500        link_id: &LinkId,
1501        resource_hash: &[u8],
1502        accept: bool,
1503        rng: &mut dyn Rng,
1504    ) -> Vec<LinkManagerAction> {
1505        let link = match self.links.get_mut(link_id) {
1506            Some(l) => l,
1507            None => return Vec::new(),
1508        };
1509
1510        let now = time::now();
1511        let idx = link
1512            .incoming_resources
1513            .iter()
1514            .position(|r| r.resource_hash == resource_hash);
1515        let idx = match idx {
1516            Some(i) => i,
1517            None => return Vec::new(),
1518        };
1519
1520        let resource_actions = if accept {
1521            link.incoming_resources[idx].accept(now)
1522        } else {
1523            link.incoming_resources[idx].reject()
1524        };
1525
1526        let _ = link;
1527        self.process_resource_actions(link_id, resource_actions, rng)
1528    }
1529
1530    /// Handle resource request (CONTEXT_RESOURCE_REQ) — feed to sender.
1531    fn handle_resource_req(
1532        &mut self,
1533        link_id: &LinkId,
1534        plaintext: &[u8],
1535        rng: &mut dyn Rng,
1536    ) -> Vec<LinkManagerAction> {
1537        let link = match self.links.get_mut(link_id) {
1538            Some(l) => l,
1539            None => return Vec::new(),
1540        };
1541
1542        let now = time::now();
1543        let mut all_actions = Vec::new();
1544        for sender in &mut link.outgoing_resources {
1545            let resource_actions = sender.handle_request(plaintext, now);
1546            if !resource_actions.is_empty() {
1547                all_actions.extend(resource_actions);
1548                break;
1549            }
1550        }
1551
1552        let _ = link;
1553        self.process_resource_actions(link_id, all_actions, rng)
1554    }
1555
1556    /// Handle resource HMU (CONTEXT_RESOURCE_HMU) — feed to receiver.
1557    fn handle_resource_hmu(
1558        &mut self,
1559        link_id: &LinkId,
1560        plaintext: &[u8],
1561        rng: &mut dyn Rng,
1562    ) -> Vec<LinkManagerAction> {
1563        let link = match self.links.get_mut(link_id) {
1564            Some(l) => l,
1565            None => return Vec::new(),
1566        };
1567
1568        let now = time::now();
1569        let mut all_actions = Vec::new();
1570        for receiver in &mut link.incoming_resources {
1571            let resource_actions = receiver.handle_hashmap_update(plaintext, now);
1572            if !resource_actions.is_empty() {
1573                all_actions.extend(resource_actions);
1574                break;
1575            }
1576        }
1577
1578        let _ = link;
1579        self.process_resource_actions(link_id, all_actions, rng)
1580    }
1581
1582    /// Handle resource part (CONTEXT_RESOURCE) — feed raw to receiver.
1583    fn handle_resource_part(
1584        &mut self,
1585        link_id: &LinkId,
1586        raw_data: &[u8],
1587        rng: &mut dyn Rng,
1588    ) -> Vec<LinkManagerAction> {
1589        let link = match self.links.get_mut(link_id) {
1590            Some(l) => l,
1591            None => return Vec::new(),
1592        };
1593
1594        let now = time::now();
1595        let mut all_actions = Vec::new();
1596        let mut assemble_idx = None;
1597
1598        for (idx, receiver) in link.incoming_resources.iter_mut().enumerate() {
1599            let resource_actions = receiver.receive_part(raw_data, now);
1600            if !resource_actions.is_empty() {
1601                // Check if all parts received (triggers assembly)
1602                if receiver.received_count == receiver.total_parts {
1603                    assemble_idx = Some(idx);
1604                }
1605                all_actions.extend(resource_actions);
1606                break;
1607            }
1608        }
1609
1610        // Assemble if all parts received
1611        if let Some(idx) = assemble_idx {
1612            let decrypt_fn = |ciphertext: &[u8]| -> Result<Vec<u8>, ()> {
1613                link.engine.decrypt(ciphertext).map_err(|_| ())
1614            };
1615            let assemble_actions =
1616                link.incoming_resources[idx].assemble(&decrypt_fn, &Bzip2Compressor);
1617            all_actions.extend(assemble_actions);
1618        }
1619
1620        let _ = link;
1621        self.process_resource_actions(link_id, all_actions, rng)
1622    }
1623
1624    /// Handle resource proof (CONTEXT_RESOURCE_PRF) — feed to sender.
1625    fn handle_resource_prf(
1626        &mut self,
1627        link_id: &LinkId,
1628        plaintext: &[u8],
1629    ) -> Vec<LinkManagerAction> {
1630        let link = match self.links.get_mut(link_id) {
1631            Some(l) => l,
1632            None => return Vec::new(),
1633        };
1634
1635        let now = time::now();
1636        let mut result_actions = Vec::new();
1637        for sender in &mut link.outgoing_resources {
1638            let resource_actions = sender.handle_proof(plaintext, now);
1639            if !resource_actions.is_empty() {
1640                result_actions.extend(resource_actions);
1641                break;
1642            }
1643        }
1644
1645        // Convert to LinkManagerActions
1646        let mut actions = Vec::new();
1647        for ra in result_actions {
1648            match ra {
1649                ResourceAction::Completed => {
1650                    actions.push(LinkManagerAction::ResourceCompleted { link_id: *link_id });
1651                }
1652                ResourceAction::Failed(e) => {
1653                    actions.push(LinkManagerAction::ResourceFailed {
1654                        link_id: *link_id,
1655                        error: format!("{}", e),
1656                    });
1657                }
1658                _ => {}
1659            }
1660        }
1661
1662        // Clean up completed/failed senders
1663        link.outgoing_resources
1664            .retain(|s| s.status < rns_core::resource::ResourceStatus::Complete);
1665
1666        actions
1667    }
1668
1669    /// Handle cancel from initiator (CONTEXT_RESOURCE_ICL).
1670    fn handle_resource_icl(&mut self, link_id: &LinkId) -> Vec<LinkManagerAction> {
1671        let link = match self.links.get_mut(link_id) {
1672            Some(l) => l,
1673            None => return Vec::new(),
1674        };
1675
1676        let mut actions = Vec::new();
1677        for receiver in &mut link.incoming_resources {
1678            let ra = receiver.handle_cancel();
1679            for a in ra {
1680                if let ResourceAction::Failed(ref e) = a {
1681                    actions.push(LinkManagerAction::ResourceFailed {
1682                        link_id: *link_id,
1683                        error: format!("{}", e),
1684                    });
1685                }
1686            }
1687        }
1688        link.incoming_resources
1689            .retain(|r| r.status < rns_core::resource::ResourceStatus::Complete);
1690        actions
1691    }
1692
1693    /// Handle cancel from receiver (CONTEXT_RESOURCE_RCL).
1694    fn handle_resource_rcl(&mut self, link_id: &LinkId) -> Vec<LinkManagerAction> {
1695        let link = match self.links.get_mut(link_id) {
1696            Some(l) => l,
1697            None => return Vec::new(),
1698        };
1699
1700        let mut actions = Vec::new();
1701        for sender in &mut link.outgoing_resources {
1702            let ra = sender.handle_reject();
1703            for a in ra {
1704                if let ResourceAction::Failed(ref e) = a {
1705                    actions.push(LinkManagerAction::ResourceFailed {
1706                        link_id: *link_id,
1707                        error: format!("{}", e),
1708                    });
1709                }
1710            }
1711        }
1712        link.outgoing_resources
1713            .retain(|s| s.status < rns_core::resource::ResourceStatus::Complete);
1714        actions
1715    }
1716
1717    /// Convert ResourceActions to LinkManagerActions.
1718    fn process_resource_actions(
1719        &self,
1720        link_id: &LinkId,
1721        actions: Vec<ResourceAction>,
1722        rng: &mut dyn Rng,
1723    ) -> Vec<LinkManagerAction> {
1724        let link = match self.links.get(link_id) {
1725            Some(l) => l,
1726            None => return Vec::new(),
1727        };
1728
1729        let mut result = Vec::new();
1730        for action in actions {
1731            match action {
1732                ResourceAction::SendAdvertisement(data) => {
1733                    // Link-encrypt and send as CONTEXT_RESOURCE_ADV
1734                    if let Ok(encrypted) = link.engine.encrypt(&data, rng) {
1735                        result.extend(self.build_link_packet(
1736                            link_id,
1737                            constants::CONTEXT_RESOURCE_ADV,
1738                            &encrypted,
1739                        ));
1740                    }
1741                }
1742                ResourceAction::SendPart(data) => {
1743                    // Parts are NOT link-encrypted — send raw as CONTEXT_RESOURCE
1744                    result.extend(self.build_link_packet(
1745                        link_id,
1746                        constants::CONTEXT_RESOURCE,
1747                        &data,
1748                    ));
1749                }
1750                ResourceAction::SendRequest(data) => {
1751                    if let Ok(encrypted) = link.engine.encrypt(&data, rng) {
1752                        result.extend(self.build_link_packet(
1753                            link_id,
1754                            constants::CONTEXT_RESOURCE_REQ,
1755                            &encrypted,
1756                        ));
1757                    }
1758                }
1759                ResourceAction::SendHmu(data) => {
1760                    if let Ok(encrypted) = link.engine.encrypt(&data, rng) {
1761                        result.extend(self.build_link_packet(
1762                            link_id,
1763                            constants::CONTEXT_RESOURCE_HMU,
1764                            &encrypted,
1765                        ));
1766                    }
1767                }
1768                ResourceAction::SendProof(data) => {
1769                    if let Ok(encrypted) = link.engine.encrypt(&data, rng) {
1770                        result.extend(self.build_link_packet(
1771                            link_id,
1772                            constants::CONTEXT_RESOURCE_PRF,
1773                            &encrypted,
1774                        ));
1775                    }
1776                }
1777                ResourceAction::SendCancelInitiator(data) => {
1778                    if let Ok(encrypted) = link.engine.encrypt(&data, rng) {
1779                        result.extend(self.build_link_packet(
1780                            link_id,
1781                            constants::CONTEXT_RESOURCE_ICL,
1782                            &encrypted,
1783                        ));
1784                    }
1785                }
1786                ResourceAction::SendCancelReceiver(data) => {
1787                    if let Ok(encrypted) = link.engine.encrypt(&data, rng) {
1788                        result.extend(self.build_link_packet(
1789                            link_id,
1790                            constants::CONTEXT_RESOURCE_RCL,
1791                            &encrypted,
1792                        ));
1793                    }
1794                }
1795                ResourceAction::DataReceived { data, metadata } => {
1796                    result.push(LinkManagerAction::ResourceReceived {
1797                        link_id: *link_id,
1798                        data,
1799                        metadata,
1800                    });
1801                }
1802                ResourceAction::Completed => {
1803                    result.push(LinkManagerAction::ResourceCompleted { link_id: *link_id });
1804                }
1805                ResourceAction::Failed(e) => {
1806                    result.push(LinkManagerAction::ResourceFailed {
1807                        link_id: *link_id,
1808                        error: format!("{}", e),
1809                    });
1810                }
1811                ResourceAction::ProgressUpdate { received, total } => {
1812                    result.push(LinkManagerAction::ResourceProgress {
1813                        link_id: *link_id,
1814                        received,
1815                        total,
1816                    });
1817                }
1818            }
1819        }
1820        result
1821    }
1822
1823    /// Build a link DATA packet with a given context and data.
1824    fn build_link_packet(
1825        &self,
1826        link_id: &LinkId,
1827        context: u8,
1828        data: &[u8],
1829    ) -> Vec<LinkManagerAction> {
1830        let flags = PacketFlags {
1831            header_type: constants::HEADER_1,
1832            context_flag: constants::FLAG_UNSET,
1833            transport_type: constants::TRANSPORT_BROADCAST,
1834            destination_type: constants::DESTINATION_LINK,
1835            packet_type: constants::PACKET_TYPE_DATA,
1836        };
1837        let mut actions = Vec::new();
1838        if let Ok(pkt) = RawPacket::pack(flags, 0, link_id, None, context, data) {
1839            actions.push(LinkManagerAction::SendPacket {
1840                raw: pkt.raw,
1841                dest_type: constants::DESTINATION_LINK,
1842                attached_interface: None,
1843            });
1844        }
1845        actions
1846    }
1847
1848    /// Start sending a resource on a link.
1849    pub fn send_resource(
1850        &mut self,
1851        link_id: &LinkId,
1852        data: &[u8],
1853        metadata: Option<&[u8]>,
1854        rng: &mut dyn Rng,
1855    ) -> Vec<LinkManagerAction> {
1856        let link = match self.links.get_mut(link_id) {
1857            Some(l) => l,
1858            None => return Vec::new(),
1859        };
1860
1861        if link.engine.state() != LinkState::Active {
1862            return Vec::new();
1863        }
1864
1865        let link_rtt = link.engine.rtt().unwrap_or(1.0);
1866        let now = time::now();
1867
1868        // Use RefCell for interior mutability since ResourceSender::new expects &dyn Fn (not FnMut)
1869        // but link.engine.encrypt needs &mut dyn Rng
1870        let enc_rng = std::cell::RefCell::new(rns_crypto::OsRng);
1871        let encrypt_fn = |plaintext: &[u8]| -> Vec<u8> {
1872            link.engine
1873                .encrypt(plaintext, &mut *enc_rng.borrow_mut())
1874                .unwrap_or_else(|_| plaintext.to_vec())
1875        };
1876
1877        let sender = match ResourceSender::new(
1878            data,
1879            metadata,
1880            constants::RESOURCE_SDU,
1881            &encrypt_fn,
1882            &Bzip2Compressor,
1883            rng,
1884            now,
1885            true,  // auto_compress
1886            false, // is_response
1887            None,  // request_id
1888            1,     // segment_index
1889            1,     // total_segments
1890            None,  // original_hash
1891            link_rtt,
1892            6.0, // traffic_timeout_factor
1893        ) {
1894            Ok(s) => s,
1895            Err(e) => {
1896                log::debug!("Failed to create ResourceSender: {}", e);
1897                return Vec::new();
1898            }
1899        };
1900
1901        let mut sender = sender;
1902        let adv_actions = sender.advertise(now);
1903        link.outgoing_resources.push(sender);
1904
1905        let _ = link;
1906        self.process_resource_actions(link_id, adv_actions, rng)
1907    }
1908
1909    /// Set the resource acceptance strategy for a link.
1910    pub fn set_resource_strategy(&mut self, link_id: &LinkId, strategy: ResourceStrategy) {
1911        if let Some(link) = self.links.get_mut(link_id) {
1912            link.resource_strategy = strategy;
1913        }
1914    }
1915
1916    /// Flush the channel TX ring for a link, clearing outstanding messages.
1917    /// Called after holepunch completion where signaling messages are fire-and-forget.
1918    pub fn flush_channel_tx(&mut self, link_id: &LinkId) {
1919        if let Some(link) = self.links.get_mut(link_id) {
1920            if let Some(ref mut channel) = link.channel {
1921                channel.flush_tx();
1922            }
1923        }
1924    }
1925
1926    /// Send a channel message on a link.
1927    pub fn send_channel_message(
1928        &mut self,
1929        link_id: &LinkId,
1930        msgtype: u16,
1931        payload: &[u8],
1932        rng: &mut dyn Rng,
1933    ) -> Result<Vec<LinkManagerAction>, String> {
1934        let link = match self.links.get_mut(link_id) {
1935            Some(l) => l,
1936            None => return Err("unknown link".to_string()),
1937        };
1938
1939        let channel = match link.channel {
1940            Some(ref mut ch) => ch,
1941            None => return Err("link has no active channel".to_string()),
1942        };
1943
1944        let link_mdu = link.engine.mdu();
1945        let now = time::now();
1946        let chan_actions = match channel.send(msgtype, payload, now, link_mdu) {
1947            Ok(a) => {
1948                link.channel_send_ok += 1;
1949                a
1950            }
1951            Err(e) => {
1952                log::debug!("Channel send failed: {:?}", e);
1953                match e {
1954                    rns_core::channel::ChannelError::NotReady => link.channel_send_not_ready += 1,
1955                    rns_core::channel::ChannelError::MessageTooBig => {
1956                        link.channel_send_too_big += 1;
1957                    }
1958                    rns_core::channel::ChannelError::InvalidEnvelope => {
1959                        link.channel_send_other_error += 1;
1960                    }
1961                }
1962                return Err(e.to_string());
1963            }
1964        };
1965
1966        let _ = link;
1967        Ok(self.process_channel_actions(link_id, chan_actions, rng))
1968    }
1969
1970    /// Periodic tick: check keepalive, stale, timeouts for all links.
1971    pub fn tick(&mut self, rng: &mut dyn Rng) -> Vec<LinkManagerAction> {
1972        let now = time::now();
1973        let mut all_actions = Vec::new();
1974
1975        // Collect link_ids to avoid borrow issues
1976        let link_ids: Vec<LinkId> = self.links.keys().copied().collect();
1977
1978        for link_id in &link_ids {
1979            let link = match self.links.get_mut(link_id) {
1980                Some(l) => l,
1981                None => continue,
1982            };
1983
1984            // Tick the engine
1985            let tick_actions = link.engine.tick(now);
1986            all_actions.extend(self.process_link_actions(link_id, &tick_actions));
1987
1988            // Check if keepalive is needed
1989            let link = match self.links.get_mut(link_id) {
1990                Some(l) => l,
1991                None => continue,
1992            };
1993            if link.engine.needs_keepalive(now) {
1994                // Send keepalive packet (empty data with CONTEXT_KEEPALIVE)
1995                let flags = PacketFlags {
1996                    header_type: constants::HEADER_1,
1997                    context_flag: constants::FLAG_UNSET,
1998                    transport_type: constants::TRANSPORT_BROADCAST,
1999                    destination_type: constants::DESTINATION_LINK,
2000                    packet_type: constants::PACKET_TYPE_DATA,
2001                };
2002                if let Ok(pkt) =
2003                    RawPacket::pack(flags, 0, link_id, None, constants::CONTEXT_KEEPALIVE, &[])
2004                {
2005                    all_actions.push(LinkManagerAction::SendPacket {
2006                        raw: pkt.raw,
2007                        dest_type: constants::DESTINATION_LINK,
2008                        attached_interface: None,
2009                    });
2010                    link.engine.record_outbound(now, true);
2011                }
2012            }
2013
2014            if let Some(channel) = link.channel.as_mut() {
2015                let chan_actions = channel.tick(now);
2016                let _ = channel;
2017                let _ = link;
2018                all_actions.extend(self.process_channel_actions(link_id, chan_actions, rng));
2019            }
2020        }
2021
2022        // Tick resource senders and receivers
2023        for link_id in &link_ids {
2024            let link = match self.links.get_mut(link_id) {
2025                Some(l) => l,
2026                None => continue,
2027            };
2028
2029            // Tick outgoing resources (senders)
2030            let mut sender_actions = Vec::new();
2031            for sender in &mut link.outgoing_resources {
2032                sender_actions.extend(sender.tick(now));
2033            }
2034
2035            // Tick incoming resources (receivers)
2036            let mut receiver_actions = Vec::new();
2037            for receiver in &mut link.incoming_resources {
2038                let decrypt_fn = |ciphertext: &[u8]| -> Result<Vec<u8>, ()> {
2039                    link.engine.decrypt(ciphertext).map_err(|_| ())
2040                };
2041                receiver_actions.extend(receiver.tick(now, &decrypt_fn, &Bzip2Compressor));
2042            }
2043
2044            // Clean up completed/failed resources
2045            link.outgoing_resources
2046                .retain(|s| s.status < rns_core::resource::ResourceStatus::Complete);
2047            link.incoming_resources
2048                .retain(|r| r.status < rns_core::resource::ResourceStatus::Assembling);
2049
2050            let _ = link;
2051            all_actions.extend(self.process_resource_actions(link_id, sender_actions, rng));
2052            all_actions.extend(self.process_resource_actions(link_id, receiver_actions, rng));
2053        }
2054
2055        // Clean up closed links
2056        let closed: Vec<LinkId> = self
2057            .links
2058            .iter()
2059            .filter(|(_, l)| l.engine.state() == LinkState::Closed)
2060            .map(|(id, _)| *id)
2061            .collect();
2062        for id in closed {
2063            self.links.remove(&id);
2064            all_actions.push(LinkManagerAction::DeregisterLinkDest { link_id: id });
2065        }
2066
2067        all_actions
2068    }
2069
2070    /// Check if a destination hash is a known link_id managed by this manager.
2071    pub fn is_link_destination(&self, dest_hash: &[u8; 16]) -> bool {
2072        self.links.contains_key(dest_hash) || self.link_destinations.contains_key(dest_hash)
2073    }
2074
2075    /// Get the state of a link.
2076    pub fn link_state(&self, link_id: &LinkId) -> Option<LinkState> {
2077        self.links.get(link_id).map(|l| l.engine.state())
2078    }
2079
2080    /// Get the RTT of a link.
2081    pub fn link_rtt(&self, link_id: &LinkId) -> Option<f64> {
2082        self.links.get(link_id).and_then(|l| l.engine.rtt())
2083    }
2084
2085    /// Update the RTT of a link (e.g., after path redirect to a direct connection).
2086    pub fn set_link_rtt(&mut self, link_id: &LinkId, rtt: f64) {
2087        if let Some(link) = self.links.get_mut(link_id) {
2088            link.engine.set_rtt(rtt);
2089        }
2090    }
2091
2092    /// Reset the inbound timer for a link (e.g., after path redirect).
2093    pub fn record_link_inbound(&mut self, link_id: &LinkId) {
2094        if let Some(link) = self.links.get_mut(link_id) {
2095            link.engine.record_inbound(time::now());
2096        }
2097    }
2098
2099    /// Update the MTU of a link (e.g., after path redirect to a different interface).
2100    pub fn set_link_mtu(&mut self, link_id: &LinkId, mtu: u32) {
2101        if let Some(link) = self.links.get_mut(link_id) {
2102            link.engine.set_mtu(mtu);
2103        }
2104    }
2105
2106    /// Get the number of active links.
2107    pub fn link_count(&self) -> usize {
2108        self.links.len()
2109    }
2110
2111    /// Get the number of active resource transfers across all links.
2112    pub fn resource_transfer_count(&self) -> usize {
2113        self.links
2114            .values()
2115            .map(|managed| managed.incoming_resources.len() + managed.outgoing_resources.len())
2116            .sum()
2117    }
2118
2119    /// Cancel all active resource transfers and return the generated actions.
2120    pub fn cancel_all_resources(&mut self, rng: &mut dyn Rng) -> Vec<LinkManagerAction> {
2121        let link_ids: Vec<LinkId> = self.links.keys().copied().collect();
2122        let mut all_actions = Vec::new();
2123
2124        for link_id in &link_ids {
2125            let link = match self.links.get_mut(link_id) {
2126                Some(l) => l,
2127                None => continue,
2128            };
2129
2130            let mut sender_actions = Vec::new();
2131            for sender in &mut link.outgoing_resources {
2132                sender_actions.extend(sender.cancel());
2133            }
2134
2135            let mut receiver_actions = Vec::new();
2136            for receiver in &mut link.incoming_resources {
2137                receiver_actions.extend(receiver.reject());
2138            }
2139
2140            link.outgoing_resources
2141                .retain(|s| s.status < rns_core::resource::ResourceStatus::Complete);
2142            link.incoming_resources
2143                .retain(|r| r.status < rns_core::resource::ResourceStatus::Assembling);
2144
2145            let _ = link;
2146            all_actions.extend(self.process_resource_actions(link_id, sender_actions, rng));
2147            all_actions.extend(self.process_resource_actions(link_id, receiver_actions, rng));
2148        }
2149
2150        all_actions
2151    }
2152
2153    /// Get information about all active links.
2154    pub fn link_entries(&self) -> Vec<crate::event::LinkInfoEntry> {
2155        self.links
2156            .iter()
2157            .map(|(link_id, managed)| {
2158                let state = match managed.engine.state() {
2159                    LinkState::Pending => "pending",
2160                    LinkState::Handshake => "handshake",
2161                    LinkState::Active => "active",
2162                    LinkState::Stale => "stale",
2163                    LinkState::Closed => "closed",
2164                };
2165                crate::event::LinkInfoEntry {
2166                    link_id: *link_id,
2167                    state: state.to_string(),
2168                    is_initiator: managed.engine.is_initiator(),
2169                    dest_hash: managed.dest_hash,
2170                    remote_identity: managed.remote_identity.as_ref().map(|(h, _)| *h),
2171                    rtt: managed.engine.rtt(),
2172                    channel_window: managed.channel.as_ref().map(|c| c.window()),
2173                    channel_outstanding: managed.channel.as_ref().map(|c| c.outstanding()),
2174                    pending_channel_packets: managed.pending_channel_packets.len(),
2175                    channel_send_ok: managed.channel_send_ok,
2176                    channel_send_not_ready: managed.channel_send_not_ready,
2177                    channel_send_too_big: managed.channel_send_too_big,
2178                    channel_send_other_error: managed.channel_send_other_error,
2179                    channel_messages_received: managed.channel_messages_received,
2180                    channel_proofs_sent: managed.channel_proofs_sent,
2181                    channel_proofs_received: managed.channel_proofs_received,
2182                }
2183            })
2184            .collect()
2185    }
2186
2187    /// Get information about all active resource transfers.
2188    pub fn resource_entries(&self) -> Vec<crate::event::ResourceInfoEntry> {
2189        let mut entries = Vec::new();
2190        for (link_id, managed) in &self.links {
2191            for recv in &managed.incoming_resources {
2192                let (received, total) = recv.progress();
2193                entries.push(crate::event::ResourceInfoEntry {
2194                    link_id: *link_id,
2195                    direction: "incoming".to_string(),
2196                    total_parts: total,
2197                    transferred_parts: received,
2198                    complete: received >= total && total > 0,
2199                });
2200            }
2201            for send in &managed.outgoing_resources {
2202                let total = send.total_parts();
2203                let sent = send.sent_parts;
2204                entries.push(crate::event::ResourceInfoEntry {
2205                    link_id: *link_id,
2206                    direction: "outgoing".to_string(),
2207                    total_parts: total,
2208                    transferred_parts: sent,
2209                    complete: sent >= total && total > 0,
2210                });
2211            }
2212        }
2213        entries
2214    }
2215
2216    /// Convert LinkActions to LinkManagerActions.
2217    fn process_link_actions(
2218        &self,
2219        link_id: &LinkId,
2220        actions: &[LinkAction],
2221    ) -> Vec<LinkManagerAction> {
2222        let mut result = Vec::new();
2223        for action in actions {
2224            match action {
2225                LinkAction::StateChanged {
2226                    new_state, reason, ..
2227                } => match new_state {
2228                    LinkState::Closed => {
2229                        result.push(LinkManagerAction::LinkClosed {
2230                            link_id: *link_id,
2231                            reason: *reason,
2232                        });
2233                    }
2234                    _ => {}
2235                },
2236                LinkAction::LinkEstablished {
2237                    rtt, is_initiator, ..
2238                } => {
2239                    let dest_hash = self
2240                        .links
2241                        .get(link_id)
2242                        .map(|l| l.dest_hash)
2243                        .unwrap_or([0u8; 16]);
2244                    result.push(LinkManagerAction::LinkEstablished {
2245                        link_id: *link_id,
2246                        dest_hash,
2247                        rtt: *rtt,
2248                        is_initiator: *is_initiator,
2249                    });
2250                }
2251                LinkAction::RemoteIdentified {
2252                    identity_hash,
2253                    public_key,
2254                    ..
2255                } => {
2256                    result.push(LinkManagerAction::RemoteIdentified {
2257                        link_id: *link_id,
2258                        identity_hash: *identity_hash,
2259                        public_key: *public_key,
2260                    });
2261                }
2262                LinkAction::DataReceived { .. } => {
2263                    // Data delivery is handled at a higher level
2264                }
2265            }
2266        }
2267        result
2268    }
2269
2270    /// Convert ChannelActions to LinkManagerActions.
2271    fn process_channel_actions(
2272        &mut self,
2273        link_id: &LinkId,
2274        actions: Vec<rns_core::channel::ChannelAction>,
2275        rng: &mut dyn Rng,
2276    ) -> Vec<LinkManagerAction> {
2277        let mut result = Vec::new();
2278        for action in actions {
2279            match action {
2280                rns_core::channel::ChannelAction::SendOnLink { raw, sequence } => {
2281                    // Encrypt and send as CHANNEL context
2282                    let encrypted = match self.links.get(link_id) {
2283                        Some(link) => match link.engine.encrypt(&raw, rng) {
2284                            Ok(encrypted) => encrypted,
2285                            Err(_) => continue,
2286                        },
2287                        None => continue,
2288                    };
2289                    let flags = PacketFlags {
2290                        header_type: constants::HEADER_1,
2291                        context_flag: constants::FLAG_UNSET,
2292                        transport_type: constants::TRANSPORT_BROADCAST,
2293                        destination_type: constants::DESTINATION_LINK,
2294                        packet_type: constants::PACKET_TYPE_DATA,
2295                    };
2296                    if let Ok(pkt) = RawPacket::pack(
2297                        flags,
2298                        0,
2299                        link_id,
2300                        None,
2301                        constants::CONTEXT_CHANNEL,
2302                        &encrypted,
2303                    ) {
2304                        if let Some(link_mut) = self.links.get_mut(link_id) {
2305                            link_mut
2306                                .pending_channel_packets
2307                                .insert(pkt.packet_hash, sequence);
2308                        }
2309                        result.push(LinkManagerAction::SendPacket {
2310                            raw: pkt.raw,
2311                            dest_type: constants::DESTINATION_LINK,
2312                            attached_interface: None,
2313                        });
2314                    }
2315                }
2316                rns_core::channel::ChannelAction::MessageReceived {
2317                    msgtype, payload, ..
2318                } => {
2319                    result.push(LinkManagerAction::ChannelMessageReceived {
2320                        link_id: *link_id,
2321                        msgtype,
2322                        payload,
2323                    });
2324                }
2325                rns_core::channel::ChannelAction::TeardownLink => {
2326                    result.push(LinkManagerAction::LinkClosed {
2327                        link_id: *link_id,
2328                        reason: Some(TeardownReason::Timeout),
2329                    });
2330                }
2331            }
2332        }
2333        result
2334    }
2335}
2336
2337/// Compute a path hash from a path string.
2338/// Uses truncated SHA-256 (first 16 bytes).
2339fn compute_path_hash(path: &str) -> [u8; 16] {
2340    let full = rns_core::hash::full_hash(path.as_bytes());
2341    let mut result = [0u8; 16];
2342    result.copy_from_slice(&full[..16]);
2343    result
2344}
2345
2346#[cfg(test)]
2347mod tests {
2348    use super::*;
2349    use rns_crypto::identity::Identity;
2350    use rns_crypto::{FixedRng, OsRng};
2351
2352    fn make_rng(seed: u8) -> FixedRng {
2353        FixedRng::new(&[seed; 128])
2354    }
2355
2356    fn make_dest_keys(rng: &mut dyn Rng) -> (Ed25519PrivateKey, [u8; 32]) {
2357        let sig_prv = Ed25519PrivateKey::generate(rng);
2358        let sig_pub_bytes = sig_prv.public_key().public_bytes();
2359        (sig_prv, sig_pub_bytes)
2360    }
2361
2362    #[test]
2363    fn test_register_link_destination() {
2364        let mut mgr = LinkManager::new();
2365        let mut rng = make_rng(0x01);
2366        let (sig_prv, sig_pub_bytes) = make_dest_keys(&mut rng);
2367        let dest_hash = [0xDD; 16];
2368
2369        mgr.register_link_destination(
2370            dest_hash,
2371            sig_prv,
2372            sig_pub_bytes,
2373            ResourceStrategy::AcceptNone,
2374        );
2375        assert!(mgr.is_link_destination(&dest_hash));
2376
2377        mgr.deregister_link_destination(&dest_hash);
2378        assert!(!mgr.is_link_destination(&dest_hash));
2379    }
2380
2381    #[test]
2382    fn test_create_link() {
2383        let mut mgr = LinkManager::new();
2384        let mut rng = OsRng;
2385        let dest_hash = [0xDD; 16];
2386
2387        let sig_pub_bytes = [0xAA; 32]; // dummy sig pub for test
2388        let (link_id, actions) = mgr.create_link(
2389            &dest_hash,
2390            &sig_pub_bytes,
2391            1,
2392            constants::MTU as u32,
2393            &mut rng,
2394        );
2395        assert_ne!(link_id, [0u8; 16]);
2396        // Should have RegisterLinkDest + SendPacket
2397        assert_eq!(actions.len(), 2);
2398        assert!(matches!(
2399            actions[0],
2400            LinkManagerAction::RegisterLinkDest { .. }
2401        ));
2402        assert!(matches!(actions[1], LinkManagerAction::SendPacket { .. }));
2403
2404        // Link should be in Pending state
2405        assert_eq!(mgr.link_state(&link_id), Some(LinkState::Pending));
2406    }
2407
2408    #[test]
2409    fn test_full_handshake_via_manager() {
2410        let mut rng = OsRng;
2411        let dest_hash = [0xDD; 16];
2412
2413        // Setup responder
2414        let mut responder_mgr = LinkManager::new();
2415        let (sig_prv, sig_pub_bytes) = make_dest_keys(&mut rng);
2416        responder_mgr.register_link_destination(
2417            dest_hash,
2418            sig_prv,
2419            sig_pub_bytes,
2420            ResourceStrategy::AcceptNone,
2421        );
2422
2423        // Setup initiator
2424        let mut initiator_mgr = LinkManager::new();
2425
2426        // Step 1: Initiator creates link (needs dest signing pub key for LRPROOF verification)
2427        let (link_id, init_actions) = initiator_mgr.create_link(
2428            &dest_hash,
2429            &sig_pub_bytes,
2430            1,
2431            constants::MTU as u32,
2432            &mut rng,
2433        );
2434        assert_eq!(init_actions.len(), 2);
2435
2436        // Extract the LINKREQUEST packet raw bytes
2437        let linkrequest_raw = match &init_actions[1] {
2438            LinkManagerAction::SendPacket { raw, .. } => raw.clone(),
2439            _ => panic!("Expected SendPacket"),
2440        };
2441
2442        // Parse to get packet_hash and dest_hash
2443        let lr_packet = RawPacket::unpack(&linkrequest_raw).unwrap();
2444
2445        // Step 2: Responder handles LINKREQUEST
2446        let resp_actions = responder_mgr.handle_local_delivery(
2447            lr_packet.destination_hash,
2448            &linkrequest_raw,
2449            lr_packet.packet_hash,
2450            rns_core::transport::types::InterfaceId(0),
2451            &mut rng,
2452        );
2453        // Should have RegisterLinkDest + SendPacket(LRPROOF)
2454        assert!(resp_actions.len() >= 2);
2455        assert!(matches!(
2456            resp_actions[0],
2457            LinkManagerAction::RegisterLinkDest { .. }
2458        ));
2459
2460        // Extract LRPROOF packet
2461        let lrproof_raw = match &resp_actions[1] {
2462            LinkManagerAction::SendPacket { raw, .. } => raw.clone(),
2463            _ => panic!("Expected SendPacket for LRPROOF"),
2464        };
2465
2466        // Step 3: Initiator handles LRPROOF
2467        let lrproof_packet = RawPacket::unpack(&lrproof_raw).unwrap();
2468        let init_actions2 = initiator_mgr.handle_local_delivery(
2469            lrproof_packet.destination_hash,
2470            &lrproof_raw,
2471            lrproof_packet.packet_hash,
2472            rns_core::transport::types::InterfaceId(0),
2473            &mut rng,
2474        );
2475
2476        // Should have LinkEstablished + SendPacket(LRRTT)
2477        let has_established = init_actions2
2478            .iter()
2479            .any(|a| matches!(a, LinkManagerAction::LinkEstablished { .. }));
2480        assert!(has_established, "Initiator should emit LinkEstablished");
2481
2482        // Extract LRRTT
2483        let lrrtt_raw = init_actions2
2484            .iter()
2485            .find_map(|a| match a {
2486                LinkManagerAction::SendPacket { raw, .. } => Some(raw.clone()),
2487                _ => None,
2488            })
2489            .expect("Should have LRRTT SendPacket");
2490
2491        // Step 4: Responder handles LRRTT
2492        let lrrtt_packet = RawPacket::unpack(&lrrtt_raw).unwrap();
2493        let resp_link_id = lrrtt_packet.destination_hash;
2494        let resp_actions2 = responder_mgr.handle_local_delivery(
2495            resp_link_id,
2496            &lrrtt_raw,
2497            lrrtt_packet.packet_hash,
2498            rns_core::transport::types::InterfaceId(0),
2499            &mut rng,
2500        );
2501
2502        let has_established = resp_actions2
2503            .iter()
2504            .any(|a| matches!(a, LinkManagerAction::LinkEstablished { .. }));
2505        assert!(has_established, "Responder should emit LinkEstablished");
2506
2507        // Both sides should be Active
2508        assert_eq!(initiator_mgr.link_state(&link_id), Some(LinkState::Active));
2509        assert_eq!(responder_mgr.link_state(&link_id), Some(LinkState::Active));
2510
2511        // Both should have RTT
2512        assert!(initiator_mgr.link_rtt(&link_id).is_some());
2513        assert!(responder_mgr.link_rtt(&link_id).is_some());
2514    }
2515
2516    #[test]
2517    fn test_encrypted_data_exchange() {
2518        let mut rng = OsRng;
2519        let dest_hash = [0xDD; 16];
2520        let mut resp_mgr = LinkManager::new();
2521        let (sig_prv, sig_pub_bytes) = make_dest_keys(&mut rng);
2522        resp_mgr.register_link_destination(
2523            dest_hash,
2524            sig_prv,
2525            sig_pub_bytes,
2526            ResourceStrategy::AcceptNone,
2527        );
2528        let mut init_mgr = LinkManager::new();
2529
2530        // Handshake
2531        let (link_id, init_actions) = init_mgr.create_link(
2532            &dest_hash,
2533            &sig_pub_bytes,
2534            1,
2535            constants::MTU as u32,
2536            &mut rng,
2537        );
2538        let lr_raw = extract_send_packet(&init_actions);
2539        let lr_pkt = RawPacket::unpack(&lr_raw).unwrap();
2540        let resp_actions = resp_mgr.handle_local_delivery(
2541            lr_pkt.destination_hash,
2542            &lr_raw,
2543            lr_pkt.packet_hash,
2544            rns_core::transport::types::InterfaceId(0),
2545            &mut rng,
2546        );
2547        let lrproof_raw = extract_send_packet_at(&resp_actions, 1);
2548        let lrproof_pkt = RawPacket::unpack(&lrproof_raw).unwrap();
2549        let init_actions2 = init_mgr.handle_local_delivery(
2550            lrproof_pkt.destination_hash,
2551            &lrproof_raw,
2552            lrproof_pkt.packet_hash,
2553            rns_core::transport::types::InterfaceId(0),
2554            &mut rng,
2555        );
2556        let lrrtt_raw = extract_any_send_packet(&init_actions2);
2557        let lrrtt_pkt = RawPacket::unpack(&lrrtt_raw).unwrap();
2558        resp_mgr.handle_local_delivery(
2559            lrrtt_pkt.destination_hash,
2560            &lrrtt_raw,
2561            lrrtt_pkt.packet_hash,
2562            rns_core::transport::types::InterfaceId(0),
2563            &mut rng,
2564        );
2565
2566        // Send data from initiator to responder
2567        let actions =
2568            init_mgr.send_on_link(&link_id, b"hello link!", constants::CONTEXT_NONE, &mut rng);
2569        assert_eq!(actions.len(), 1);
2570        assert!(matches!(actions[0], LinkManagerAction::SendPacket { .. }));
2571    }
2572
2573    #[test]
2574    fn test_request_response() {
2575        let mut rng = OsRng;
2576        let dest_hash = [0xDD; 16];
2577        let mut resp_mgr = LinkManager::new();
2578        let (sig_prv, sig_pub_bytes) = make_dest_keys(&mut rng);
2579        resp_mgr.register_link_destination(
2580            dest_hash,
2581            sig_prv,
2582            sig_pub_bytes,
2583            ResourceStrategy::AcceptNone,
2584        );
2585
2586        // Register a request handler
2587        resp_mgr.register_request_handler("/status", None, |_link_id, _path, _data, _remote| {
2588            Some(b"OK".to_vec())
2589        });
2590
2591        let mut init_mgr = LinkManager::new();
2592
2593        // Complete handshake
2594        let (link_id, init_actions) = init_mgr.create_link(
2595            &dest_hash,
2596            &sig_pub_bytes,
2597            1,
2598            constants::MTU as u32,
2599            &mut rng,
2600        );
2601        let lr_raw = extract_send_packet(&init_actions);
2602        let lr_pkt = RawPacket::unpack(&lr_raw).unwrap();
2603        let resp_actions = resp_mgr.handle_local_delivery(
2604            lr_pkt.destination_hash,
2605            &lr_raw,
2606            lr_pkt.packet_hash,
2607            rns_core::transport::types::InterfaceId(0),
2608            &mut rng,
2609        );
2610        let lrproof_raw = extract_send_packet_at(&resp_actions, 1);
2611        let lrproof_pkt = RawPacket::unpack(&lrproof_raw).unwrap();
2612        let init_actions2 = init_mgr.handle_local_delivery(
2613            lrproof_pkt.destination_hash,
2614            &lrproof_raw,
2615            lrproof_pkt.packet_hash,
2616            rns_core::transport::types::InterfaceId(0),
2617            &mut rng,
2618        );
2619        let lrrtt_raw = extract_any_send_packet(&init_actions2);
2620        let lrrtt_pkt = RawPacket::unpack(&lrrtt_raw).unwrap();
2621        resp_mgr.handle_local_delivery(
2622            lrrtt_pkt.destination_hash,
2623            &lrrtt_raw,
2624            lrrtt_pkt.packet_hash,
2625            rns_core::transport::types::InterfaceId(0),
2626            &mut rng,
2627        );
2628
2629        // Send request from initiator
2630        let req_actions = init_mgr.send_request(&link_id, "/status", b"query", &mut rng);
2631        assert_eq!(req_actions.len(), 1);
2632
2633        // Deliver request to responder
2634        let req_raw = extract_send_packet_from(&req_actions);
2635        let req_pkt = RawPacket::unpack(&req_raw).unwrap();
2636        let resp_actions = resp_mgr.handle_local_delivery(
2637            req_pkt.destination_hash,
2638            &req_raw,
2639            req_pkt.packet_hash,
2640            rns_core::transport::types::InterfaceId(0),
2641            &mut rng,
2642        );
2643
2644        // Should have a response SendPacket
2645        let has_response = resp_actions
2646            .iter()
2647            .any(|a| matches!(a, LinkManagerAction::SendPacket { .. }));
2648        assert!(has_response, "Handler should produce a response packet");
2649    }
2650
2651    #[test]
2652    fn test_request_acl_deny_unidentified() {
2653        let mut rng = OsRng;
2654        let dest_hash = [0xDD; 16];
2655        let mut resp_mgr = LinkManager::new();
2656        let (sig_prv, sig_pub_bytes) = make_dest_keys(&mut rng);
2657        resp_mgr.register_link_destination(
2658            dest_hash,
2659            sig_prv,
2660            sig_pub_bytes,
2661            ResourceStrategy::AcceptNone,
2662        );
2663
2664        // Register handler with ACL (only allow specific identity)
2665        resp_mgr.register_request_handler(
2666            "/restricted",
2667            Some(vec![[0xAA; 16]]),
2668            |_link_id, _path, _data, _remote| Some(b"secret".to_vec()),
2669        );
2670
2671        let mut init_mgr = LinkManager::new();
2672
2673        // Complete handshake (without identification)
2674        let (link_id, init_actions) = init_mgr.create_link(
2675            &dest_hash,
2676            &sig_pub_bytes,
2677            1,
2678            constants::MTU as u32,
2679            &mut rng,
2680        );
2681        let lr_raw = extract_send_packet(&init_actions);
2682        let lr_pkt = RawPacket::unpack(&lr_raw).unwrap();
2683        let resp_actions = resp_mgr.handle_local_delivery(
2684            lr_pkt.destination_hash,
2685            &lr_raw,
2686            lr_pkt.packet_hash,
2687            rns_core::transport::types::InterfaceId(0),
2688            &mut rng,
2689        );
2690        let lrproof_raw = extract_send_packet_at(&resp_actions, 1);
2691        let lrproof_pkt = RawPacket::unpack(&lrproof_raw).unwrap();
2692        let init_actions2 = init_mgr.handle_local_delivery(
2693            lrproof_pkt.destination_hash,
2694            &lrproof_raw,
2695            lrproof_pkt.packet_hash,
2696            rns_core::transport::types::InterfaceId(0),
2697            &mut rng,
2698        );
2699        let lrrtt_raw = extract_any_send_packet(&init_actions2);
2700        let lrrtt_pkt = RawPacket::unpack(&lrrtt_raw).unwrap();
2701        resp_mgr.handle_local_delivery(
2702            lrrtt_pkt.destination_hash,
2703            &lrrtt_raw,
2704            lrrtt_pkt.packet_hash,
2705            rns_core::transport::types::InterfaceId(0),
2706            &mut rng,
2707        );
2708
2709        // Send request without identifying first
2710        let req_actions = init_mgr.send_request(&link_id, "/restricted", b"query", &mut rng);
2711        let req_raw = extract_send_packet_from(&req_actions);
2712        let req_pkt = RawPacket::unpack(&req_raw).unwrap();
2713        let resp_actions = resp_mgr.handle_local_delivery(
2714            req_pkt.destination_hash,
2715            &req_raw,
2716            req_pkt.packet_hash,
2717            rns_core::transport::types::InterfaceId(0),
2718            &mut rng,
2719        );
2720
2721        // Should be denied — no response packet
2722        let has_response = resp_actions
2723            .iter()
2724            .any(|a| matches!(a, LinkManagerAction::SendPacket { .. }));
2725        assert!(!has_response, "Unidentified peer should be denied");
2726    }
2727
2728    #[test]
2729    fn test_teardown_link() {
2730        let mut rng = OsRng;
2731        let dest_hash = [0xDD; 16];
2732        let mut mgr = LinkManager::new();
2733
2734        let dummy_sig = [0xAA; 32];
2735        let (link_id, _) =
2736            mgr.create_link(&dest_hash, &dummy_sig, 1, constants::MTU as u32, &mut rng);
2737        assert_eq!(mgr.link_count(), 1);
2738
2739        let actions = mgr.teardown_link(&link_id);
2740        let has_close = actions
2741            .iter()
2742            .any(|a| matches!(a, LinkManagerAction::LinkClosed { .. }));
2743        assert!(has_close);
2744
2745        // After tick, closed links should be cleaned up
2746        let tick_actions = mgr.tick(&mut rng);
2747        let has_deregister = tick_actions
2748            .iter()
2749            .any(|a| matches!(a, LinkManagerAction::DeregisterLinkDest { .. }));
2750        assert!(has_deregister);
2751        assert_eq!(mgr.link_count(), 0);
2752    }
2753
2754    #[test]
2755    fn test_identify_on_link() {
2756        let mut rng = OsRng;
2757        let dest_hash = [0xDD; 16];
2758        let mut resp_mgr = LinkManager::new();
2759        let (sig_prv, sig_pub_bytes) = make_dest_keys(&mut rng);
2760        resp_mgr.register_link_destination(
2761            dest_hash,
2762            sig_prv,
2763            sig_pub_bytes,
2764            ResourceStrategy::AcceptNone,
2765        );
2766        let mut init_mgr = LinkManager::new();
2767
2768        // Complete handshake
2769        let (link_id, init_actions) = init_mgr.create_link(
2770            &dest_hash,
2771            &sig_pub_bytes,
2772            1,
2773            constants::MTU as u32,
2774            &mut rng,
2775        );
2776        let lr_raw = extract_send_packet(&init_actions);
2777        let lr_pkt = RawPacket::unpack(&lr_raw).unwrap();
2778        let resp_actions = resp_mgr.handle_local_delivery(
2779            lr_pkt.destination_hash,
2780            &lr_raw,
2781            lr_pkt.packet_hash,
2782            rns_core::transport::types::InterfaceId(0),
2783            &mut rng,
2784        );
2785        let lrproof_raw = extract_send_packet_at(&resp_actions, 1);
2786        let lrproof_pkt = RawPacket::unpack(&lrproof_raw).unwrap();
2787        let init_actions2 = init_mgr.handle_local_delivery(
2788            lrproof_pkt.destination_hash,
2789            &lrproof_raw,
2790            lrproof_pkt.packet_hash,
2791            rns_core::transport::types::InterfaceId(0),
2792            &mut rng,
2793        );
2794        let lrrtt_raw = extract_any_send_packet(&init_actions2);
2795        let lrrtt_pkt = RawPacket::unpack(&lrrtt_raw).unwrap();
2796        resp_mgr.handle_local_delivery(
2797            lrrtt_pkt.destination_hash,
2798            &lrrtt_raw,
2799            lrrtt_pkt.packet_hash,
2800            rns_core::transport::types::InterfaceId(0),
2801            &mut rng,
2802        );
2803
2804        // Identify initiator to responder
2805        let identity = Identity::new(&mut rng);
2806        let id_actions = init_mgr.identify(&link_id, &identity, &mut rng);
2807        assert_eq!(id_actions.len(), 1);
2808
2809        // Deliver identify to responder
2810        let id_raw = extract_send_packet_from(&id_actions);
2811        let id_pkt = RawPacket::unpack(&id_raw).unwrap();
2812        let resp_actions = resp_mgr.handle_local_delivery(
2813            id_pkt.destination_hash,
2814            &id_raw,
2815            id_pkt.packet_hash,
2816            rns_core::transport::types::InterfaceId(0),
2817            &mut rng,
2818        );
2819
2820        let has_identified = resp_actions
2821            .iter()
2822            .any(|a| matches!(a, LinkManagerAction::RemoteIdentified { .. }));
2823        assert!(has_identified, "Responder should emit RemoteIdentified");
2824    }
2825
2826    #[test]
2827    fn test_path_hash_computation() {
2828        let h1 = compute_path_hash("/status");
2829        let h2 = compute_path_hash("/path");
2830        assert_ne!(h1, h2);
2831
2832        // Deterministic
2833        assert_eq!(h1, compute_path_hash("/status"));
2834    }
2835
2836    #[test]
2837    fn test_link_count() {
2838        let mut mgr = LinkManager::new();
2839        let mut rng = OsRng;
2840
2841        assert_eq!(mgr.link_count(), 0);
2842
2843        let dummy_sig = [0xAA; 32];
2844        mgr.create_link(&[0x11; 16], &dummy_sig, 1, constants::MTU as u32, &mut rng);
2845        assert_eq!(mgr.link_count(), 1);
2846
2847        mgr.create_link(&[0x22; 16], &dummy_sig, 1, constants::MTU as u32, &mut rng);
2848        assert_eq!(mgr.link_count(), 2);
2849    }
2850
2851    // --- Test helpers ---
2852
2853    fn extract_send_packet(actions: &[LinkManagerAction]) -> Vec<u8> {
2854        extract_send_packet_at(actions, actions.len() - 1)
2855    }
2856
2857    fn extract_send_packet_at(actions: &[LinkManagerAction], idx: usize) -> Vec<u8> {
2858        match &actions[idx] {
2859            LinkManagerAction::SendPacket { raw, .. } => raw.clone(),
2860            other => panic!("Expected SendPacket at index {}, got {:?}", idx, other),
2861        }
2862    }
2863
2864    fn extract_any_send_packet(actions: &[LinkManagerAction]) -> Vec<u8> {
2865        actions
2866            .iter()
2867            .find_map(|a| match a {
2868                LinkManagerAction::SendPacket { raw, .. } => Some(raw.clone()),
2869                _ => None,
2870            })
2871            .expect("Expected at least one SendPacket action")
2872    }
2873
2874    fn extract_send_packet_from(actions: &[LinkManagerAction]) -> Vec<u8> {
2875        extract_any_send_packet(actions)
2876    }
2877
2878    /// Set up two linked managers with an active link.
2879    /// Returns (initiator_mgr, responder_mgr, link_id).
2880    fn setup_active_link() -> (LinkManager, LinkManager, LinkId) {
2881        let mut rng = OsRng;
2882        let dest_hash = [0xDD; 16];
2883        let mut resp_mgr = LinkManager::new();
2884        let (sig_prv, sig_pub_bytes) = make_dest_keys(&mut rng);
2885        resp_mgr.register_link_destination(
2886            dest_hash,
2887            sig_prv,
2888            sig_pub_bytes,
2889            ResourceStrategy::AcceptNone,
2890        );
2891        let mut init_mgr = LinkManager::new();
2892
2893        let (link_id, init_actions) = init_mgr.create_link(
2894            &dest_hash,
2895            &sig_pub_bytes,
2896            1,
2897            constants::MTU as u32,
2898            &mut rng,
2899        );
2900        let lr_raw = extract_send_packet(&init_actions);
2901        let lr_pkt = RawPacket::unpack(&lr_raw).unwrap();
2902        let resp_actions = resp_mgr.handle_local_delivery(
2903            lr_pkt.destination_hash,
2904            &lr_raw,
2905            lr_pkt.packet_hash,
2906            rns_core::transport::types::InterfaceId(0),
2907            &mut rng,
2908        );
2909        let lrproof_raw = extract_send_packet_at(&resp_actions, 1);
2910        let lrproof_pkt = RawPacket::unpack(&lrproof_raw).unwrap();
2911        let init_actions2 = init_mgr.handle_local_delivery(
2912            lrproof_pkt.destination_hash,
2913            &lrproof_raw,
2914            lrproof_pkt.packet_hash,
2915            rns_core::transport::types::InterfaceId(0),
2916            &mut rng,
2917        );
2918        let lrrtt_raw = extract_any_send_packet(&init_actions2);
2919        let lrrtt_pkt = RawPacket::unpack(&lrrtt_raw).unwrap();
2920        resp_mgr.handle_local_delivery(
2921            lrrtt_pkt.destination_hash,
2922            &lrrtt_raw,
2923            lrrtt_pkt.packet_hash,
2924            rns_core::transport::types::InterfaceId(0),
2925            &mut rng,
2926        );
2927
2928        assert_eq!(init_mgr.link_state(&link_id), Some(LinkState::Active));
2929        assert_eq!(resp_mgr.link_state(&link_id), Some(LinkState::Active));
2930
2931        (init_mgr, resp_mgr, link_id)
2932    }
2933
2934    // ====================================================================
2935    // Phase 8a: Resource wiring tests
2936    // ====================================================================
2937
2938    #[test]
2939    fn test_resource_strategy_default() {
2940        let mut mgr = LinkManager::new();
2941        let mut rng = OsRng;
2942        let dummy_sig = [0xAA; 32];
2943        let (link_id, _) =
2944            mgr.create_link(&[0x11; 16], &dummy_sig, 1, constants::MTU as u32, &mut rng);
2945
2946        // Default strategy is AcceptNone
2947        let link = mgr.links.get(&link_id).unwrap();
2948        assert_eq!(link.resource_strategy, ResourceStrategy::AcceptNone);
2949    }
2950
2951    #[test]
2952    fn test_set_resource_strategy() {
2953        let mut mgr = LinkManager::new();
2954        let mut rng = OsRng;
2955        let dummy_sig = [0xAA; 32];
2956        let (link_id, _) =
2957            mgr.create_link(&[0x11; 16], &dummy_sig, 1, constants::MTU as u32, &mut rng);
2958
2959        mgr.set_resource_strategy(&link_id, ResourceStrategy::AcceptAll);
2960        assert_eq!(
2961            mgr.links.get(&link_id).unwrap().resource_strategy,
2962            ResourceStrategy::AcceptAll
2963        );
2964
2965        mgr.set_resource_strategy(&link_id, ResourceStrategy::AcceptApp);
2966        assert_eq!(
2967            mgr.links.get(&link_id).unwrap().resource_strategy,
2968            ResourceStrategy::AcceptApp
2969        );
2970    }
2971
2972    #[test]
2973    fn test_send_resource_on_active_link() {
2974        let (mut init_mgr, _resp_mgr, link_id) = setup_active_link();
2975        let mut rng = OsRng;
2976
2977        // Send resource data
2978        let data = vec![0xAB; 100]; // small enough for a single part
2979        let actions = init_mgr.send_resource(&link_id, &data, None, &mut rng);
2980
2981        // Should produce at least a SendPacket (advertisement)
2982        let has_send = actions
2983            .iter()
2984            .any(|a| matches!(a, LinkManagerAction::SendPacket { .. }));
2985        assert!(
2986            has_send,
2987            "send_resource should emit advertisement SendPacket"
2988        );
2989    }
2990
2991    #[test]
2992    fn test_send_resource_on_inactive_link() {
2993        let mut mgr = LinkManager::new();
2994        let mut rng = OsRng;
2995        let dummy_sig = [0xAA; 32];
2996        let (link_id, _) =
2997            mgr.create_link(&[0x11; 16], &dummy_sig, 1, constants::MTU as u32, &mut rng);
2998
2999        // Link is Pending, not Active
3000        let actions = mgr.send_resource(&link_id, b"data", None, &mut rng);
3001        assert!(actions.is_empty(), "Cannot send resource on inactive link");
3002    }
3003
3004    #[test]
3005    fn test_resource_adv_rejected_by_accept_none() {
3006        let (mut init_mgr, mut resp_mgr, link_id) = setup_active_link();
3007        let mut rng = OsRng;
3008
3009        // Responder uses default AcceptNone strategy
3010        // Send resource from initiator
3011        let data = vec![0xCD; 100];
3012        let adv_actions = init_mgr.send_resource(&link_id, &data, None, &mut rng);
3013
3014        // Deliver advertisement to responder
3015        for action in &adv_actions {
3016            if let LinkManagerAction::SendPacket { raw, .. } = action {
3017                let pkt = RawPacket::unpack(raw).unwrap();
3018                let resp_actions = resp_mgr.handle_local_delivery(
3019                    pkt.destination_hash,
3020                    raw,
3021                    pkt.packet_hash,
3022                    rns_core::transport::types::InterfaceId(0),
3023                    &mut rng,
3024                );
3025                // AcceptNone: should not produce ResourceReceived, may produce SendPacket (RCL)
3026                let has_resource_received = resp_actions
3027                    .iter()
3028                    .any(|a| matches!(a, LinkManagerAction::ResourceReceived { .. }));
3029                assert!(
3030                    !has_resource_received,
3031                    "AcceptNone should not accept resource"
3032                );
3033            }
3034        }
3035    }
3036
3037    #[test]
3038    fn test_resource_adv_accepted_by_accept_all() {
3039        let (mut init_mgr, mut resp_mgr, link_id) = setup_active_link();
3040        let mut rng = OsRng;
3041
3042        // Set responder to AcceptAll
3043        resp_mgr.set_resource_strategy(&link_id, ResourceStrategy::AcceptAll);
3044
3045        // Send resource from initiator
3046        let data = vec![0xCD; 100];
3047        let adv_actions = init_mgr.send_resource(&link_id, &data, None, &mut rng);
3048
3049        // Deliver advertisement to responder
3050        for action in &adv_actions {
3051            if let LinkManagerAction::SendPacket { raw, .. } = action {
3052                let pkt = RawPacket::unpack(raw).unwrap();
3053                let resp_actions = resp_mgr.handle_local_delivery(
3054                    pkt.destination_hash,
3055                    raw,
3056                    pkt.packet_hash,
3057                    rns_core::transport::types::InterfaceId(0),
3058                    &mut rng,
3059                );
3060                // AcceptAll: should accept and produce a SendPacket (request for parts)
3061                let has_send = resp_actions
3062                    .iter()
3063                    .any(|a| matches!(a, LinkManagerAction::SendPacket { .. }));
3064                assert!(has_send, "AcceptAll should accept and request parts");
3065            }
3066        }
3067    }
3068
3069    #[test]
3070    fn test_resource_accept_app_query() {
3071        let (mut init_mgr, mut resp_mgr, link_id) = setup_active_link();
3072        let mut rng = OsRng;
3073
3074        // Set responder to AcceptApp
3075        resp_mgr.set_resource_strategy(&link_id, ResourceStrategy::AcceptApp);
3076
3077        // Send resource from initiator
3078        let data = vec![0xCD; 100];
3079        let adv_actions = init_mgr.send_resource(&link_id, &data, None, &mut rng);
3080
3081        // Deliver advertisement to responder
3082        let mut got_query = false;
3083        for action in &adv_actions {
3084            if let LinkManagerAction::SendPacket { raw, .. } = action {
3085                let pkt = RawPacket::unpack(raw).unwrap();
3086                let resp_actions = resp_mgr.handle_local_delivery(
3087                    pkt.destination_hash,
3088                    raw,
3089                    pkt.packet_hash,
3090                    rns_core::transport::types::InterfaceId(0),
3091                    &mut rng,
3092                );
3093                for a in &resp_actions {
3094                    if matches!(a, LinkManagerAction::ResourceAcceptQuery { .. }) {
3095                        got_query = true;
3096                    }
3097                }
3098            }
3099        }
3100        assert!(got_query, "AcceptApp should emit ResourceAcceptQuery");
3101    }
3102
3103    #[test]
3104    fn test_resource_accept_app_accept() {
3105        let (mut init_mgr, mut resp_mgr, link_id) = setup_active_link();
3106        let mut rng = OsRng;
3107
3108        resp_mgr.set_resource_strategy(&link_id, ResourceStrategy::AcceptApp);
3109
3110        let data = vec![0xCD; 100];
3111        let adv_actions = init_mgr.send_resource(&link_id, &data, None, &mut rng);
3112
3113        for action in &adv_actions {
3114            if let LinkManagerAction::SendPacket { raw, .. } = action {
3115                let pkt = RawPacket::unpack(raw).unwrap();
3116                let resp_actions = resp_mgr.handle_local_delivery(
3117                    pkt.destination_hash,
3118                    raw,
3119                    pkt.packet_hash,
3120                    rns_core::transport::types::InterfaceId(0),
3121                    &mut rng,
3122                );
3123                for a in &resp_actions {
3124                    if let LinkManagerAction::ResourceAcceptQuery {
3125                        link_id: lid,
3126                        resource_hash,
3127                        ..
3128                    } = a
3129                    {
3130                        // Accept the resource
3131                        let accept_actions =
3132                            resp_mgr.accept_resource(lid, resource_hash, true, &mut rng);
3133                        // Should produce a SendPacket (request for parts)
3134                        let has_send = accept_actions
3135                            .iter()
3136                            .any(|a| matches!(a, LinkManagerAction::SendPacket { .. }));
3137                        assert!(
3138                            has_send,
3139                            "Accepting resource should produce request for parts"
3140                        );
3141                    }
3142                }
3143            }
3144        }
3145    }
3146
3147    #[test]
3148    fn test_resource_accept_app_reject() {
3149        let (mut init_mgr, mut resp_mgr, link_id) = setup_active_link();
3150        let mut rng = OsRng;
3151
3152        resp_mgr.set_resource_strategy(&link_id, ResourceStrategy::AcceptApp);
3153
3154        let data = vec![0xCD; 100];
3155        let adv_actions = init_mgr.send_resource(&link_id, &data, None, &mut rng);
3156
3157        for action in &adv_actions {
3158            if let LinkManagerAction::SendPacket { raw, .. } = action {
3159                let pkt = RawPacket::unpack(raw).unwrap();
3160                let resp_actions = resp_mgr.handle_local_delivery(
3161                    pkt.destination_hash,
3162                    raw,
3163                    pkt.packet_hash,
3164                    rns_core::transport::types::InterfaceId(0),
3165                    &mut rng,
3166                );
3167                for a in &resp_actions {
3168                    if let LinkManagerAction::ResourceAcceptQuery {
3169                        link_id: lid,
3170                        resource_hash,
3171                        ..
3172                    } = a
3173                    {
3174                        // Reject the resource
3175                        let reject_actions =
3176                            resp_mgr.accept_resource(lid, resource_hash, false, &mut rng);
3177                        // Rejecting should send a cancel and not request parts
3178                        // No ResourceReceived should appear
3179                        let has_resource_received = reject_actions
3180                            .iter()
3181                            .any(|a| matches!(a, LinkManagerAction::ResourceReceived { .. }));
3182                        assert!(!has_resource_received);
3183                    }
3184                }
3185            }
3186        }
3187    }
3188
3189    #[test]
3190    fn test_resource_full_transfer() {
3191        let (mut init_mgr, mut resp_mgr, link_id) = setup_active_link();
3192        let mut rng = OsRng;
3193
3194        // Set responder to AcceptAll
3195        resp_mgr.set_resource_strategy(&link_id, ResourceStrategy::AcceptAll);
3196
3197        // Small data (fits in single SDU)
3198        let original_data = b"Hello, Resource Transfer!".to_vec();
3199        let adv_actions = init_mgr.send_resource(&link_id, &original_data, None, &mut rng);
3200
3201        // Drive the full transfer protocol between the two managers.
3202        // Tag each SendPacket with its source ('i' = initiator, 'r' = responder).
3203        let mut pending: Vec<(char, LinkManagerAction)> =
3204            adv_actions.into_iter().map(|a| ('i', a)).collect();
3205        let mut rounds = 0;
3206        let max_rounds = 50;
3207        let mut resource_received = false;
3208        let mut sender_completed = false;
3209
3210        while !pending.is_empty() && rounds < max_rounds {
3211            rounds += 1;
3212            let mut next: Vec<(char, LinkManagerAction)> = Vec::new();
3213
3214            for (source, action) in pending.drain(..) {
3215                if let LinkManagerAction::SendPacket { raw, .. } = action {
3216                    let pkt = RawPacket::unpack(&raw).unwrap();
3217
3218                    // Deliver only to the OTHER side
3219                    let target_actions = if source == 'i' {
3220                        resp_mgr.handle_local_delivery(
3221                            pkt.destination_hash,
3222                            &raw,
3223                            pkt.packet_hash,
3224                            rns_core::transport::types::InterfaceId(0),
3225                            &mut rng,
3226                        )
3227                    } else {
3228                        init_mgr.handle_local_delivery(
3229                            pkt.destination_hash,
3230                            &raw,
3231                            pkt.packet_hash,
3232                            rns_core::transport::types::InterfaceId(0),
3233                            &mut rng,
3234                        )
3235                    };
3236
3237                    let target_source = if source == 'i' { 'r' } else { 'i' };
3238                    for a in &target_actions {
3239                        match a {
3240                            LinkManagerAction::ResourceReceived { data, .. } => {
3241                                assert_eq!(*data, original_data);
3242                                resource_received = true;
3243                            }
3244                            LinkManagerAction::ResourceCompleted { .. } => {
3245                                sender_completed = true;
3246                            }
3247                            _ => {}
3248                        }
3249                    }
3250                    next.extend(target_actions.into_iter().map(|a| (target_source, a)));
3251                }
3252            }
3253            pending = next;
3254        }
3255
3256        assert!(
3257            resource_received,
3258            "Responder should receive resource data (rounds={})",
3259            rounds
3260        );
3261        assert!(
3262            sender_completed,
3263            "Sender should get completion proof (rounds={})",
3264            rounds
3265        );
3266    }
3267
3268    #[test]
3269    fn test_resource_cancel_icl() {
3270        let (mut init_mgr, mut resp_mgr, link_id) = setup_active_link();
3271        let mut rng = OsRng;
3272
3273        resp_mgr.set_resource_strategy(&link_id, ResourceStrategy::AcceptAll);
3274
3275        // Use large data so transfer is multi-part
3276        let data = vec![0xAB; 2000];
3277        let adv_actions = init_mgr.send_resource(&link_id, &data, None, &mut rng);
3278
3279        // Deliver advertisement — responder accepts and sends request
3280        for action in &adv_actions {
3281            if let LinkManagerAction::SendPacket { raw, .. } = action {
3282                let pkt = RawPacket::unpack(raw).unwrap();
3283                resp_mgr.handle_local_delivery(
3284                    pkt.destination_hash,
3285                    raw,
3286                    pkt.packet_hash,
3287                    rns_core::transport::types::InterfaceId(0),
3288                    &mut rng,
3289                );
3290            }
3291        }
3292
3293        // Verify there are incoming resources on the responder
3294        assert!(!resp_mgr
3295            .links
3296            .get(&link_id)
3297            .unwrap()
3298            .incoming_resources
3299            .is_empty());
3300
3301        // Simulate ICL (cancel from initiator side) by calling handle_resource_icl
3302        let icl_actions = resp_mgr.handle_resource_icl(&link_id);
3303
3304        // Should have resource failed
3305        let has_failed = icl_actions
3306            .iter()
3307            .any(|a| matches!(a, LinkManagerAction::ResourceFailed { .. }));
3308        assert!(has_failed, "ICL should produce ResourceFailed");
3309    }
3310
3311    #[test]
3312    fn test_resource_cancel_rcl() {
3313        let (mut init_mgr, _resp_mgr, link_id) = setup_active_link();
3314        let mut rng = OsRng;
3315
3316        // Create a resource sender
3317        let data = vec![0xAB; 2000];
3318        init_mgr.send_resource(&link_id, &data, None, &mut rng);
3319
3320        // Verify there are outgoing resources
3321        assert!(!init_mgr
3322            .links
3323            .get(&link_id)
3324            .unwrap()
3325            .outgoing_resources
3326            .is_empty());
3327
3328        // Simulate RCL (cancel from receiver side)
3329        let rcl_actions = init_mgr.handle_resource_rcl(&link_id);
3330
3331        let has_failed = rcl_actions
3332            .iter()
3333            .any(|a| matches!(a, LinkManagerAction::ResourceFailed { .. }));
3334        assert!(has_failed, "RCL should produce ResourceFailed");
3335    }
3336
3337    #[test]
3338    fn test_cancel_all_resources_clears_active_transfers() {
3339        let (mut init_mgr, _resp_mgr, link_id) = setup_active_link();
3340        let mut rng = OsRng;
3341
3342        let actions = init_mgr.send_resource(&link_id, b"resource body", None, &mut rng);
3343        assert!(!actions.is_empty());
3344        assert_eq!(init_mgr.resource_transfer_count(), 1);
3345
3346        let cancel_actions = init_mgr.cancel_all_resources(&mut rng);
3347
3348        assert_eq!(init_mgr.resource_transfer_count(), 0);
3349        assert!(cancel_actions
3350            .iter()
3351            .any(|action| matches!(action, LinkManagerAction::SendPacket { .. })));
3352    }
3353
3354    #[test]
3355    fn test_resource_tick_cleans_up() {
3356        let (mut init_mgr, _resp_mgr, link_id) = setup_active_link();
3357        let mut rng = OsRng;
3358
3359        let data = vec![0xAB; 100];
3360        init_mgr.send_resource(&link_id, &data, None, &mut rng);
3361
3362        assert!(!init_mgr
3363            .links
3364            .get(&link_id)
3365            .unwrap()
3366            .outgoing_resources
3367            .is_empty());
3368
3369        // Cancel the sender to make it Complete
3370        init_mgr.handle_resource_rcl(&link_id);
3371
3372        // Tick should clean up completed resources
3373        init_mgr.tick(&mut rng);
3374
3375        assert!(
3376            init_mgr
3377                .links
3378                .get(&link_id)
3379                .unwrap()
3380                .outgoing_resources
3381                .is_empty(),
3382            "Tick should clean up completed/failed outgoing resources"
3383        );
3384    }
3385
3386    #[test]
3387    fn test_build_link_packet() {
3388        let (init_mgr, _resp_mgr, link_id) = setup_active_link();
3389
3390        let actions =
3391            init_mgr.build_link_packet(&link_id, constants::CONTEXT_RESOURCE, b"test data");
3392        assert_eq!(actions.len(), 1);
3393        if let LinkManagerAction::SendPacket { raw, dest_type, .. } = &actions[0] {
3394            let pkt = RawPacket::unpack(raw).unwrap();
3395            assert_eq!(pkt.context, constants::CONTEXT_RESOURCE);
3396            assert_eq!(*dest_type, constants::DESTINATION_LINK);
3397        } else {
3398            panic!("Expected SendPacket");
3399        }
3400    }
3401
3402    // ====================================================================
3403    // Phase 8b: Channel message & data callback tests
3404    // ====================================================================
3405
3406    #[test]
3407    fn test_channel_message_delivery() {
3408        let (mut init_mgr, mut resp_mgr, link_id) = setup_active_link();
3409        let mut rng = OsRng;
3410
3411        // Send channel message from initiator
3412        let chan_actions = init_mgr
3413            .send_channel_message(&link_id, 42, b"channel data", &mut rng)
3414            .expect("active link channel send should succeed");
3415        assert!(!chan_actions.is_empty());
3416
3417        // Deliver to responder
3418        let mut got_channel_msg = false;
3419        for action in &chan_actions {
3420            if let LinkManagerAction::SendPacket { raw, .. } = action {
3421                let pkt = RawPacket::unpack(raw).unwrap();
3422                let resp_actions = resp_mgr.handle_local_delivery(
3423                    pkt.destination_hash,
3424                    raw,
3425                    pkt.packet_hash,
3426                    rns_core::transport::types::InterfaceId(0),
3427                    &mut rng,
3428                );
3429                for a in &resp_actions {
3430                    if let LinkManagerAction::ChannelMessageReceived {
3431                        msgtype, payload, ..
3432                    } = a
3433                    {
3434                        assert_eq!(*msgtype, 42);
3435                        assert_eq!(*payload, b"channel data");
3436                        got_channel_msg = true;
3437                    }
3438                }
3439            }
3440        }
3441        assert!(got_channel_msg, "Responder should receive channel message");
3442    }
3443
3444    #[test]
3445    fn test_channel_proof_reopens_send_window() {
3446        let (mut init_mgr, _resp_mgr, link_id) = setup_active_link();
3447        let mut rng = OsRng;
3448
3449        init_mgr
3450            .send_channel_message(&link_id, 42, b"first", &mut rng)
3451            .expect("first send should succeed");
3452        init_mgr
3453            .send_channel_message(&link_id, 42, b"second", &mut rng)
3454            .expect("second send should succeed");
3455
3456        let err = init_mgr
3457            .send_channel_message(&link_id, 42, b"third", &mut rng)
3458            .expect_err("third send should hit the initial channel window");
3459        assert_eq!(err, "Channel is not ready to send");
3460
3461        let queued_packets = init_mgr
3462            .links
3463            .get(&link_id)
3464            .unwrap()
3465            .pending_channel_packets
3466            .clone();
3467        assert_eq!(queued_packets.len(), 2);
3468        for tracked_hash in queued_packets.keys().take(1) {
3469            let mut proof_data = Vec::with_capacity(96);
3470            proof_data.extend_from_slice(tracked_hash);
3471            proof_data.extend_from_slice(&[0x11; 64]);
3472            let flags = PacketFlags {
3473                header_type: constants::HEADER_1,
3474                context_flag: constants::FLAG_UNSET,
3475                transport_type: constants::TRANSPORT_BROADCAST,
3476                destination_type: constants::DESTINATION_LINK,
3477                packet_type: constants::PACKET_TYPE_PROOF,
3478            };
3479            let proof = RawPacket::pack(
3480                flags,
3481                0,
3482                &link_id,
3483                None,
3484                constants::CONTEXT_NONE,
3485                &proof_data,
3486            )
3487            .expect("proof packet should pack");
3488            let ack_actions = init_mgr.handle_local_delivery(
3489                link_id,
3490                &proof.raw,
3491                proof.packet_hash,
3492                rns_core::transport::types::InterfaceId(0),
3493                &mut rng,
3494            );
3495            assert!(
3496                ack_actions.is_empty(),
3497                "proof delivery should only update channel state"
3498            );
3499        }
3500
3501        init_mgr
3502            .send_channel_message(&link_id, 42, b"third", &mut rng)
3503            .expect("proof should free one channel slot");
3504    }
3505
3506    #[test]
3507    fn test_generic_link_data_delivery() {
3508        let (init_mgr, mut resp_mgr, link_id) = setup_active_link();
3509        let mut rng = OsRng;
3510
3511        // Send generic data with a custom context
3512        let actions = init_mgr.send_on_link(&link_id, b"raw stuff", 0x42, &mut rng);
3513        assert_eq!(actions.len(), 1);
3514
3515        // Deliver to responder
3516        let raw = extract_any_send_packet(&actions);
3517        let pkt = RawPacket::unpack(&raw).unwrap();
3518        let resp_actions = resp_mgr.handle_local_delivery(
3519            pkt.destination_hash,
3520            &raw,
3521            pkt.packet_hash,
3522            rns_core::transport::types::InterfaceId(0),
3523            &mut rng,
3524        );
3525
3526        let has_data = resp_actions
3527            .iter()
3528            .any(|a| matches!(a, LinkManagerAction::LinkDataReceived { context: 0x42, .. }));
3529        assert!(
3530            has_data,
3531            "Responder should receive LinkDataReceived for unknown context"
3532        );
3533    }
3534
3535    #[test]
3536    fn test_response_delivery() {
3537        let (mut init_mgr, mut resp_mgr, link_id) = setup_active_link();
3538        let mut rng = OsRng;
3539
3540        // Register handler on responder
3541        resp_mgr.register_request_handler("/echo", None, |_link_id, _path, data, _remote| {
3542            Some(data.to_vec())
3543        });
3544
3545        // Send request from initiator
3546        let req_actions = init_mgr.send_request(&link_id, "/echo", b"\xc0", &mut rng); // msgpack nil
3547        assert!(!req_actions.is_empty());
3548
3549        // Deliver request to responder — should produce response
3550        let req_raw = extract_any_send_packet(&req_actions);
3551        let req_pkt = RawPacket::unpack(&req_raw).unwrap();
3552        let resp_actions = resp_mgr.handle_local_delivery(
3553            req_pkt.destination_hash,
3554            &req_raw,
3555            req_pkt.packet_hash,
3556            rns_core::transport::types::InterfaceId(0),
3557            &mut rng,
3558        );
3559        let has_resp_send = resp_actions
3560            .iter()
3561            .any(|a| matches!(a, LinkManagerAction::SendPacket { .. }));
3562        assert!(has_resp_send, "Handler should produce response");
3563
3564        // Deliver response back to initiator
3565        let resp_raw = extract_any_send_packet(&resp_actions);
3566        let resp_pkt = RawPacket::unpack(&resp_raw).unwrap();
3567        let init_actions = init_mgr.handle_local_delivery(
3568            resp_pkt.destination_hash,
3569            &resp_raw,
3570            resp_pkt.packet_hash,
3571            rns_core::transport::types::InterfaceId(0),
3572            &mut rng,
3573        );
3574
3575        let has_response_received = init_actions
3576            .iter()
3577            .any(|a| matches!(a, LinkManagerAction::ResponseReceived { .. }));
3578        assert!(
3579            has_response_received,
3580            "Initiator should receive ResponseReceived"
3581        );
3582    }
3583
3584    #[test]
3585    fn test_send_channel_message_on_no_channel() {
3586        let mut mgr = LinkManager::new();
3587        let mut rng = OsRng;
3588        let dummy_sig = [0xAA; 32];
3589        let (link_id, _) =
3590            mgr.create_link(&[0x11; 16], &dummy_sig, 1, constants::MTU as u32, &mut rng);
3591
3592        // Link is Pending (no channel), should return empty
3593        let err = mgr
3594            .send_channel_message(&link_id, 1, b"test", &mut rng)
3595            .expect_err("pending link should reject channel send");
3596        assert_eq!(err, "link has no active channel");
3597    }
3598
3599    #[test]
3600    fn test_send_on_link_requires_active() {
3601        let mut mgr = LinkManager::new();
3602        let mut rng = OsRng;
3603        let dummy_sig = [0xAA; 32];
3604        let (link_id, _) =
3605            mgr.create_link(&[0x11; 16], &dummy_sig, 1, constants::MTU as u32, &mut rng);
3606
3607        let actions = mgr.send_on_link(&link_id, b"test", constants::CONTEXT_NONE, &mut rng);
3608        assert!(actions.is_empty(), "Cannot send on pending link");
3609    }
3610
3611    #[test]
3612    fn test_send_on_link_unknown_link() {
3613        let mgr = LinkManager::new();
3614        let mut rng = OsRng;
3615
3616        let actions = mgr.send_on_link(&[0xFF; 16], b"test", constants::CONTEXT_NONE, &mut rng);
3617        assert!(actions.is_empty());
3618    }
3619
3620    #[test]
3621    fn test_resource_full_transfer_large() {
3622        let (mut init_mgr, mut resp_mgr, link_id) = setup_active_link();
3623        let mut rng = OsRng;
3624
3625        resp_mgr.set_resource_strategy(&link_id, ResourceStrategy::AcceptAll);
3626
3627        // Multi-part data (larger than a single SDU of 464 bytes)
3628        let original_data: Vec<u8> = (0..2000u32)
3629            .map(|i| {
3630                let pos = i as usize;
3631                (pos ^ (pos >> 8) ^ (pos >> 16)) as u8
3632            })
3633            .collect();
3634
3635        let adv_actions = init_mgr.send_resource(&link_id, &original_data, None, &mut rng);
3636
3637        let mut pending: Vec<(char, LinkManagerAction)> =
3638            adv_actions.into_iter().map(|a| ('i', a)).collect();
3639        let mut rounds = 0;
3640        let max_rounds = 200;
3641        let mut resource_received = false;
3642        let mut sender_completed = false;
3643
3644        while !pending.is_empty() && rounds < max_rounds {
3645            rounds += 1;
3646            let mut next: Vec<(char, LinkManagerAction)> = Vec::new();
3647
3648            for (source, action) in pending.drain(..) {
3649                if let LinkManagerAction::SendPacket { raw, .. } = action {
3650                    let pkt = match RawPacket::unpack(&raw) {
3651                        Ok(p) => p,
3652                        Err(_) => continue,
3653                    };
3654
3655                    let target_actions = if source == 'i' {
3656                        resp_mgr.handle_local_delivery(
3657                            pkt.destination_hash,
3658                            &raw,
3659                            pkt.packet_hash,
3660                            rns_core::transport::types::InterfaceId(0),
3661                            &mut rng,
3662                        )
3663                    } else {
3664                        init_mgr.handle_local_delivery(
3665                            pkt.destination_hash,
3666                            &raw,
3667                            pkt.packet_hash,
3668                            rns_core::transport::types::InterfaceId(0),
3669                            &mut rng,
3670                        )
3671                    };
3672
3673                    let target_source = if source == 'i' { 'r' } else { 'i' };
3674                    for a in &target_actions {
3675                        match a {
3676                            LinkManagerAction::ResourceReceived { data, .. } => {
3677                                assert_eq!(*data, original_data);
3678                                resource_received = true;
3679                            }
3680                            LinkManagerAction::ResourceCompleted { .. } => {
3681                                sender_completed = true;
3682                            }
3683                            _ => {}
3684                        }
3685                    }
3686                    next.extend(target_actions.into_iter().map(|a| (target_source, a)));
3687                }
3688            }
3689            pending = next;
3690        }
3691
3692        assert!(
3693            resource_received,
3694            "Should receive large resource (rounds={})",
3695            rounds
3696        );
3697        assert!(
3698            sender_completed,
3699            "Sender should complete (rounds={})",
3700            rounds
3701        );
3702    }
3703
3704    #[test]
3705    fn test_process_resource_actions_mapping() {
3706        let (init_mgr, _resp_mgr, link_id) = setup_active_link();
3707        let mut rng = OsRng;
3708
3709        // Test that various ResourceActions map to correct LinkManagerActions
3710        let actions = vec![
3711            ResourceAction::DataReceived {
3712                data: vec![1, 2, 3],
3713                metadata: Some(vec![4, 5]),
3714            },
3715            ResourceAction::Completed,
3716            ResourceAction::Failed(rns_core::resource::ResourceError::Timeout),
3717            ResourceAction::ProgressUpdate {
3718                received: 10,
3719                total: 20,
3720            },
3721        ];
3722
3723        let result = init_mgr.process_resource_actions(&link_id, actions, &mut rng);
3724
3725        assert!(matches!(
3726            result[0],
3727            LinkManagerAction::ResourceReceived { .. }
3728        ));
3729        assert!(matches!(
3730            result[1],
3731            LinkManagerAction::ResourceCompleted { .. }
3732        ));
3733        assert!(matches!(
3734            result[2],
3735            LinkManagerAction::ResourceFailed { .. }
3736        ));
3737        assert!(matches!(
3738            result[3],
3739            LinkManagerAction::ResourceProgress {
3740                received: 10,
3741                total: 20,
3742                ..
3743            }
3744        ));
3745    }
3746
3747    #[test]
3748    fn test_link_state_empty() {
3749        let mgr = LinkManager::new();
3750        let fake_id = [0xAA; 16];
3751        assert!(mgr.link_state(&fake_id).is_none());
3752    }
3753}