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