Skip to main content

rns_net/
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 rns_core::buffer::types::NoopCompressor;
12use rns_core::channel::Channel;
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 crate::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    /// Destination hash this link belongs to.
45    dest_hash: [u8; 16],
46    /// Remote identity (hash, public_key) once identified.
47    remote_identity: Option<([u8; 16], [u8; 64])>,
48    /// Destination's Ed25519 signing public key (for initiator to verify LRPROOF).
49    dest_sig_pub_bytes: Option<[u8; 32]>,
50    /// Active incoming resource transfers.
51    incoming_resources: Vec<ResourceReceiver>,
52    /// Active outgoing resource transfers.
53    outgoing_resources: Vec<ResourceSender>,
54    /// Resource acceptance strategy.
55    resource_strategy: ResourceStrategy,
56}
57
58/// A registered link destination that can accept incoming LINKREQUEST.
59struct LinkDestination {
60    sig_prv: Ed25519PrivateKey,
61    sig_pub_bytes: [u8; 32],
62}
63
64/// A registered request handler for a path.
65struct RequestHandlerEntry {
66    /// The path this handler serves (e.g. "/status").
67    path: String,
68    /// The truncated hash of the path (first 16 bytes of SHA-256).
69    path_hash: [u8; 16],
70    /// Access control: None means allow all, Some(list) means allow only listed identities.
71    allowed_list: Option<Vec<[u8; 16]>>,
72    /// Handler function: (link_id, path, request_id, data, remote_identity) -> Option<response_data>.
73    handler: Box<dyn Fn(LinkId, &str, &[u8], Option<&([u8; 16], [u8; 64])>) -> Option<Vec<u8>> + Send>,
74}
75
76/// Actions produced by LinkManager for the driver to dispatch.
77#[derive(Debug)]
78pub enum LinkManagerAction {
79    /// Send a packet via the transport engine outbound path.
80    SendPacket {
81        raw: Vec<u8>,
82        dest_type: u8,
83        attached_interface: Option<rns_core::transport::types::InterfaceId>,
84    },
85    /// Link established — notify callbacks.
86    LinkEstablished {
87        link_id: LinkId,
88        dest_hash: [u8; 16],
89        rtt: f64,
90        is_initiator: bool,
91    },
92    /// Link closed — notify callbacks.
93    LinkClosed {
94        link_id: LinkId,
95        reason: Option<TeardownReason>,
96    },
97    /// Remote peer identified — notify callbacks.
98    RemoteIdentified {
99        link_id: LinkId,
100        identity_hash: [u8; 16],
101        public_key: [u8; 64],
102    },
103    /// Register a link_id as local destination in transport (for receiving link data).
104    RegisterLinkDest {
105        link_id: LinkId,
106    },
107    /// Deregister a link_id from transport local destinations.
108    DeregisterLinkDest {
109        link_id: LinkId,
110    },
111    /// A management request that needs to be handled by the driver.
112    /// The driver has access to engine state needed to build the response.
113    ManagementRequest {
114        link_id: LinkId,
115        path_hash: [u8; 16],
116        /// The request data (msgpack-encoded Value from the request array).
117        data: Vec<u8>,
118        /// The request_id (truncated hash of the packed request).
119        request_id: [u8; 16],
120        remote_identity: Option<([u8; 16], [u8; 64])>,
121    },
122    /// Resource data fully received and assembled.
123    ResourceReceived {
124        link_id: LinkId,
125        data: Vec<u8>,
126        metadata: Option<Vec<u8>>,
127    },
128    /// Resource transfer completed (proof validated on sender side).
129    ResourceCompleted {
130        link_id: LinkId,
131    },
132    /// Resource transfer failed.
133    ResourceFailed {
134        link_id: LinkId,
135        error: String,
136    },
137    /// Resource transfer progress update.
138    ResourceProgress {
139        link_id: LinkId,
140        received: usize,
141        total: usize,
142    },
143    /// Query application whether to accept an incoming resource (for AcceptApp strategy).
144    ResourceAcceptQuery {
145        link_id: LinkId,
146        resource_hash: Vec<u8>,
147        transfer_size: u64,
148        has_metadata: bool,
149    },
150    /// Channel message received on a link.
151    ChannelMessageReceived {
152        link_id: LinkId,
153        msgtype: u16,
154        payload: Vec<u8>,
155    },
156    /// Generic link data received (CONTEXT_NONE).
157    LinkDataReceived {
158        link_id: LinkId,
159        context: u8,
160        data: Vec<u8>,
161    },
162    /// Response received on a link.
163    ResponseReceived {
164        link_id: LinkId,
165        request_id: [u8; 16],
166        data: Vec<u8>,
167    },
168    /// A link request was received (for hook notification).
169    LinkRequestReceived {
170        link_id: LinkId,
171        receiving_interface: rns_core::transport::types::InterfaceId,
172    },
173}
174
175/// Manages multiple links, link destinations, and request/response.
176pub struct LinkManager {
177    links: HashMap<LinkId, ManagedLink>,
178    link_destinations: HashMap<[u8; 16], LinkDestination>,
179    request_handlers: Vec<RequestHandlerEntry>,
180    /// Path hashes that should be handled externally (by the driver) rather than
181    /// by registered handler closures. Used for management destinations.
182    management_paths: Vec<[u8; 16]>,
183}
184
185impl LinkManager {
186    /// Create a new empty link manager.
187    pub fn new() -> Self {
188        LinkManager {
189            links: HashMap::new(),
190            link_destinations: HashMap::new(),
191            request_handlers: Vec::new(),
192            management_paths: Vec::new(),
193        }
194    }
195
196    /// Register a path hash as a management path.
197    /// Management requests are returned as ManagementRequest actions
198    /// for the driver to handle (since they need access to engine state).
199    pub fn register_management_path(&mut self, path_hash: [u8; 16]) {
200        if !self.management_paths.contains(&path_hash) {
201            self.management_paths.push(path_hash);
202        }
203    }
204
205    /// Get the derived session key for a link (needed for hole-punch token derivation).
206    pub fn get_derived_key(&self, link_id: &LinkId) -> Option<Vec<u8>> {
207        self.links.get(link_id)
208            .and_then(|link| link.engine.derived_key().map(|dk| dk.to_vec()))
209    }
210
211    /// Register a destination that can accept incoming links.
212    pub fn register_link_destination(
213        &mut self,
214        dest_hash: [u8; 16],
215        sig_prv: Ed25519PrivateKey,
216        sig_pub_bytes: [u8; 32],
217    ) {
218        self.link_destinations.insert(dest_hash, LinkDestination {
219            sig_prv,
220            sig_pub_bytes,
221        });
222    }
223
224    /// Deregister a link destination.
225    pub fn deregister_link_destination(&mut self, dest_hash: &[u8; 16]) {
226        self.link_destinations.remove(dest_hash);
227    }
228
229    /// Register a request handler for a given path.
230    ///
231    /// `path`: the request path string (e.g. "/status")
232    /// `allowed_list`: None = allow all, Some(list) = restrict to these identity hashes
233    /// `handler`: called with (link_id, path, request_data, remote_identity) -> Option<response>
234    pub fn register_request_handler<F>(
235        &mut self,
236        path: &str,
237        allowed_list: Option<Vec<[u8; 16]>>,
238        handler: F,
239    ) where
240        F: Fn(LinkId, &str, &[u8], Option<&([u8; 16], [u8; 64])>) -> Option<Vec<u8>> + Send + 'static,
241    {
242        let path_hash = compute_path_hash(path);
243        self.request_handlers.push(RequestHandlerEntry {
244            path: path.to_string(),
245            path_hash,
246            allowed_list,
247            handler: Box::new(handler),
248        });
249    }
250
251    /// Create an outbound link to a destination.
252    ///
253    /// `dest_sig_pub_bytes` is the destination's Ed25519 signing public key
254    /// (needed to verify LRPROOF). In Python this comes from the Destination's Identity.
255    ///
256    /// Returns `(link_id, actions)`. The first action will be a SendPacket with
257    /// the LINKREQUEST.
258    pub fn create_link(
259        &mut self,
260        dest_hash: &[u8; 16],
261        dest_sig_pub_bytes: &[u8; 32],
262        hops: u8,
263        mtu: u32,
264        rng: &mut dyn Rng,
265    ) -> (LinkId, Vec<LinkManagerAction>) {
266        let mode = LinkMode::Aes256Cbc;
267        let (mut engine, request_data) =
268            LinkEngine::new_initiator(dest_hash, hops, mode, Some(mtu), time::now(), rng);
269
270        // Build the LINKREQUEST packet to compute link_id
271        let flags = PacketFlags {
272            header_type: constants::HEADER_1,
273            context_flag: constants::FLAG_UNSET,
274            transport_type: constants::TRANSPORT_BROADCAST,
275            destination_type: constants::DESTINATION_SINGLE,
276            packet_type: constants::PACKET_TYPE_LINKREQUEST,
277        };
278
279        let packet = match RawPacket::pack(
280            flags, 0, dest_hash, None, constants::CONTEXT_NONE, &request_data,
281        ) {
282            Ok(p) => p,
283            Err(_) => {
284                // Should not happen with valid data
285                return ([0u8; 16], Vec::new());
286            }
287        };
288
289        engine.set_link_id_from_hashable(&packet.get_hashable_part(), request_data.len());
290        let link_id = *engine.link_id();
291
292        let managed = ManagedLink {
293            engine,
294            channel: None,
295            dest_hash: *dest_hash,
296            remote_identity: None,
297            dest_sig_pub_bytes: Some(*dest_sig_pub_bytes),
298            incoming_resources: Vec::new(),
299            outgoing_resources: Vec::new(),
300            resource_strategy: ResourceStrategy::default(),
301        };
302        self.links.insert(link_id, managed);
303
304        let mut actions = Vec::new();
305        // Register the link_id as a local destination so we can receive LRPROOF
306        actions.push(LinkManagerAction::RegisterLinkDest { link_id });
307        // Send the LINKREQUEST packet
308        actions.push(LinkManagerAction::SendPacket {
309            raw: packet.raw,
310            dest_type: constants::DESTINATION_LINK,
311            attached_interface: None,
312        });
313
314        (link_id, actions)
315    }
316
317    /// Handle a packet delivered locally (via DeliverLocal).
318    ///
319    /// Returns actions for the driver to dispatch. The `dest_hash` is the
320    /// packet's destination_hash field. `raw` is the full packet bytes.
321    /// `packet_hash` is the SHA-256 hash.
322    pub fn handle_local_delivery(
323        &mut self,
324        dest_hash: [u8; 16],
325        raw: &[u8],
326        packet_hash: [u8; 32],
327        receiving_interface: rns_core::transport::types::InterfaceId,
328        rng: &mut dyn Rng,
329    ) -> Vec<LinkManagerAction> {
330        let packet = match RawPacket::unpack(raw) {
331            Ok(p) => p,
332            Err(_) => return Vec::new(),
333        };
334
335        match packet.flags.packet_type {
336            constants::PACKET_TYPE_LINKREQUEST => {
337                self.handle_linkrequest(&dest_hash, &packet, receiving_interface, rng)
338            }
339            constants::PACKET_TYPE_PROOF if packet.context == constants::CONTEXT_LRPROOF => {
340                // LRPROOF: dest_hash is the link_id
341                self.handle_lrproof(&dest_hash, &packet, rng)
342            }
343            constants::PACKET_TYPE_DATA => {
344                self.handle_link_data(&dest_hash, &packet, packet_hash, rng)
345            }
346            _ => Vec::new(),
347        }
348    }
349
350    /// Handle an incoming LINKREQUEST packet.
351    fn handle_linkrequest(
352        &mut self,
353        dest_hash: &[u8; 16],
354        packet: &RawPacket,
355        receiving_interface: rns_core::transport::types::InterfaceId,
356        rng: &mut dyn Rng,
357    ) -> Vec<LinkManagerAction> {
358        // Look up the link destination
359        let ld = match self.link_destinations.get(dest_hash) {
360            Some(ld) => ld,
361            None => return Vec::new(),
362        };
363
364        let hashable = packet.get_hashable_part();
365        let now = time::now();
366
367        // Create responder engine
368        let (engine, lrproof_data) = match LinkEngine::new_responder(
369            &ld.sig_prv,
370            &ld.sig_pub_bytes,
371            &packet.data,
372            &hashable,
373            dest_hash,
374            packet.hops,
375            now,
376            rng,
377        ) {
378            Ok(r) => r,
379            Err(e) => {
380                log::debug!("LINKREQUEST rejected: {}", e);
381                return Vec::new();
382            }
383        };
384
385        let link_id = *engine.link_id();
386
387        let managed = ManagedLink {
388            engine,
389            channel: None,
390            dest_hash: *dest_hash,
391            remote_identity: None,
392            dest_sig_pub_bytes: None,
393            incoming_resources: Vec::new(),
394            outgoing_resources: Vec::new(),
395            resource_strategy: ResourceStrategy::default(),
396        };
397        self.links.insert(link_id, managed);
398
399        // Build LRPROOF packet: type=PROOF, context=LRPROOF, dest=link_id
400        let flags = PacketFlags {
401            header_type: constants::HEADER_1,
402            context_flag: constants::FLAG_UNSET,
403            transport_type: constants::TRANSPORT_BROADCAST,
404            destination_type: constants::DESTINATION_LINK,
405            packet_type: constants::PACKET_TYPE_PROOF,
406        };
407
408        let mut actions = Vec::new();
409
410        // Register link_id as local destination so we receive link data
411        actions.push(LinkManagerAction::RegisterLinkDest { link_id });
412
413        if let Ok(pkt) = RawPacket::pack(
414            flags, 0, &link_id, None, constants::CONTEXT_LRPROOF, &lrproof_data,
415        ) {
416            actions.push(LinkManagerAction::SendPacket {
417                raw: pkt.raw,
418                dest_type: constants::DESTINATION_LINK,
419                attached_interface: None,
420            });
421        }
422
423        // Notify hook system about the incoming link request
424        actions.push(LinkManagerAction::LinkRequestReceived { link_id, receiving_interface });
425
426        actions
427    }
428
429    /// Handle an incoming LRPROOF packet (initiator side).
430    fn handle_lrproof(
431        &mut self,
432        link_id_bytes: &[u8; 16],
433        packet: &RawPacket,
434        rng: &mut dyn Rng,
435    ) -> Vec<LinkManagerAction> {
436        let link = match self.links.get_mut(link_id_bytes) {
437            Some(l) => l,
438            None => return Vec::new(),
439        };
440
441        if link.engine.state() != LinkState::Pending || !link.engine.is_initiator() {
442            return Vec::new();
443        }
444
445        // The destination's signing pub key was stored when create_link was called
446        let dest_sig_pub_bytes = match link.dest_sig_pub_bytes {
447            Some(b) => b,
448            None => {
449                log::debug!("LRPROOF: no destination signing key available");
450                return Vec::new();
451            }
452        };
453
454        let now = time::now();
455        let (lrrtt_encrypted, link_actions) = match link.engine.handle_lrproof(
456            &packet.data,
457            &dest_sig_pub_bytes,
458            now,
459            rng,
460        ) {
461            Ok(r) => r,
462            Err(e) => {
463                log::debug!("LRPROOF validation failed: {}", e);
464                return Vec::new();
465            }
466        };
467
468        let link_id = *link.engine.link_id();
469        let mut actions = Vec::new();
470
471        // Process link actions (StateChanged, LinkEstablished)
472        actions.extend(self.process_link_actions(&link_id, &link_actions));
473
474        // Send LRRTT: type=DATA, context=LRRTT, dest=link_id
475        let flags = PacketFlags {
476            header_type: constants::HEADER_1,
477            context_flag: constants::FLAG_UNSET,
478            transport_type: constants::TRANSPORT_BROADCAST,
479            destination_type: constants::DESTINATION_LINK,
480            packet_type: constants::PACKET_TYPE_DATA,
481        };
482
483        if let Ok(pkt) = RawPacket::pack(
484            flags, 0, &link_id, None, constants::CONTEXT_LRRTT, &lrrtt_encrypted,
485        ) {
486            actions.push(LinkManagerAction::SendPacket {
487                raw: pkt.raw,
488                dest_type: constants::DESTINATION_LINK,
489                attached_interface: None,
490            });
491        }
492
493        // Initialize channel now that link is active
494        if let Some(link) = self.links.get_mut(&link_id) {
495            if link.engine.state() == LinkState::Active {
496                let rtt = link.engine.rtt().unwrap_or(1.0);
497                link.channel = Some(Channel::new(rtt));
498            }
499        }
500
501        actions
502    }
503
504    /// Handle DATA packets on an established link.
505    ///
506    /// Structured to avoid borrow checker issues: we perform engine operations
507    /// on the link, collect intermediate results, drop the mutable borrow, then
508    /// call self methods that need immutable access.
509    fn handle_link_data(
510        &mut self,
511        link_id_bytes: &[u8; 16],
512        packet: &RawPacket,
513        _packet_hash: [u8; 32],
514        rng: &mut dyn Rng,
515    ) -> Vec<LinkManagerAction> {
516        // First pass: perform engine operations, collect results
517        enum LinkDataResult {
518            Lrrtt { link_id: LinkId, link_actions: Vec<LinkAction> },
519            Identify { link_id: LinkId, link_actions: Vec<LinkAction> },
520            Keepalive { link_id: LinkId, inbound_actions: Vec<LinkAction> },
521            LinkClose { link_id: LinkId, teardown_actions: Vec<LinkAction> },
522            Channel { link_id: LinkId, inbound_actions: Vec<LinkAction>, plaintext: Vec<u8> },
523            Request { link_id: LinkId, inbound_actions: Vec<LinkAction>, plaintext: Vec<u8> },
524            Response { link_id: LinkId, inbound_actions: Vec<LinkAction>, plaintext: Vec<u8> },
525            Generic { link_id: LinkId, inbound_actions: Vec<LinkAction>, plaintext: Vec<u8>, context: u8 },
526            /// Resource advertisement (link-decrypted).
527            ResourceAdv { link_id: LinkId, inbound_actions: Vec<LinkAction>, plaintext: Vec<u8> },
528            /// Resource part request (link-decrypted).
529            ResourceReq { link_id: LinkId, inbound_actions: Vec<LinkAction>, plaintext: Vec<u8> },
530            /// Resource hashmap update (link-decrypted).
531            ResourceHmu { link_id: LinkId, inbound_actions: Vec<LinkAction>, plaintext: Vec<u8> },
532            /// Resource part data (NOT link-decrypted; parts are pre-encrypted by ResourceSender).
533            ResourcePart { link_id: LinkId, inbound_actions: Vec<LinkAction>, raw_data: Vec<u8> },
534            /// Resource proof (feed to sender).
535            ResourcePrf { link_id: LinkId, inbound_actions: Vec<LinkAction>, plaintext: Vec<u8> },
536            /// Resource cancel from initiator (link-decrypted).
537            ResourceIcl { link_id: LinkId, inbound_actions: Vec<LinkAction> },
538            /// Resource cancel from receiver (link-decrypted).
539            ResourceRcl { link_id: LinkId, inbound_actions: Vec<LinkAction> },
540            Error,
541        }
542
543        let result = {
544            let link = match self.links.get_mut(link_id_bytes) {
545                Some(l) => l,
546                None => return Vec::new(),
547            };
548
549            match packet.context {
550                constants::CONTEXT_LRRTT => {
551                    match link.engine.handle_lrrtt(&packet.data, time::now()) {
552                        Ok(link_actions) => {
553                            let link_id = *link.engine.link_id();
554                            LinkDataResult::Lrrtt { link_id, link_actions }
555                        }
556                        Err(e) => {
557                            log::debug!("LRRTT handling failed: {}", e);
558                            LinkDataResult::Error
559                        }
560                    }
561                }
562                constants::CONTEXT_LINKIDENTIFY => {
563                    match link.engine.handle_identify(&packet.data) {
564                        Ok(link_actions) => {
565                            let link_id = *link.engine.link_id();
566                            link.remote_identity = link.engine.remote_identity().cloned();
567                            LinkDataResult::Identify { link_id, link_actions }
568                        }
569                        Err(e) => {
570                            log::debug!("LINKIDENTIFY failed: {}", e);
571                            LinkDataResult::Error
572                        }
573                    }
574                }
575                constants::CONTEXT_KEEPALIVE => {
576                    let inbound_actions = link.engine.record_inbound(time::now());
577                    let link_id = *link.engine.link_id();
578                    LinkDataResult::Keepalive { link_id, inbound_actions }
579                }
580                constants::CONTEXT_LINKCLOSE => {
581                    let teardown_actions = link.engine.handle_teardown();
582                    let link_id = *link.engine.link_id();
583                    LinkDataResult::LinkClose { link_id, teardown_actions }
584                }
585                constants::CONTEXT_CHANNEL => {
586                    match link.engine.decrypt(&packet.data) {
587                        Ok(plaintext) => {
588                            let inbound_actions = link.engine.record_inbound(time::now());
589                            let link_id = *link.engine.link_id();
590                            LinkDataResult::Channel { link_id, inbound_actions, plaintext }
591                        }
592                        Err(_) => LinkDataResult::Error,
593                    }
594                }
595                constants::CONTEXT_REQUEST => {
596                    match link.engine.decrypt(&packet.data) {
597                        Ok(plaintext) => {
598                            let inbound_actions = link.engine.record_inbound(time::now());
599                            let link_id = *link.engine.link_id();
600                            LinkDataResult::Request { link_id, inbound_actions, plaintext }
601                        }
602                        Err(_) => LinkDataResult::Error,
603                    }
604                }
605                constants::CONTEXT_RESPONSE => {
606                    match link.engine.decrypt(&packet.data) {
607                        Ok(plaintext) => {
608                            let inbound_actions = link.engine.record_inbound(time::now());
609                            let link_id = *link.engine.link_id();
610                            LinkDataResult::Response { link_id, inbound_actions, plaintext }
611                        }
612                        Err(_) => LinkDataResult::Error,
613                    }
614                }
615                // --- Resource contexts ---
616                constants::CONTEXT_RESOURCE_ADV => {
617                    match link.engine.decrypt(&packet.data) {
618                        Ok(plaintext) => {
619                            let inbound_actions = link.engine.record_inbound(time::now());
620                            let link_id = *link.engine.link_id();
621                            LinkDataResult::ResourceAdv { link_id, inbound_actions, plaintext }
622                        }
623                        Err(_) => LinkDataResult::Error,
624                    }
625                }
626                constants::CONTEXT_RESOURCE_REQ => {
627                    match link.engine.decrypt(&packet.data) {
628                        Ok(plaintext) => {
629                            let inbound_actions = link.engine.record_inbound(time::now());
630                            let link_id = *link.engine.link_id();
631                            LinkDataResult::ResourceReq { link_id, inbound_actions, plaintext }
632                        }
633                        Err(_) => LinkDataResult::Error,
634                    }
635                }
636                constants::CONTEXT_RESOURCE_HMU => {
637                    match link.engine.decrypt(&packet.data) {
638                        Ok(plaintext) => {
639                            let inbound_actions = link.engine.record_inbound(time::now());
640                            let link_id = *link.engine.link_id();
641                            LinkDataResult::ResourceHmu { link_id, inbound_actions, plaintext }
642                        }
643                        Err(_) => LinkDataResult::Error,
644                    }
645                }
646                constants::CONTEXT_RESOURCE => {
647                    // Resource parts are NOT link-decrypted — they're pre-encrypted by ResourceSender
648                    let inbound_actions = link.engine.record_inbound(time::now());
649                    let link_id = *link.engine.link_id();
650                    LinkDataResult::ResourcePart { link_id, inbound_actions, raw_data: packet.data.clone() }
651                }
652                constants::CONTEXT_RESOURCE_PRF => {
653                    match link.engine.decrypt(&packet.data) {
654                        Ok(plaintext) => {
655                            let inbound_actions = link.engine.record_inbound(time::now());
656                            let link_id = *link.engine.link_id();
657                            LinkDataResult::ResourcePrf { link_id, inbound_actions, plaintext }
658                        }
659                        Err(_) => LinkDataResult::Error,
660                    }
661                }
662                constants::CONTEXT_RESOURCE_ICL => {
663                    let _ = link.engine.decrypt(&packet.data); // decrypt to validate
664                    let inbound_actions = link.engine.record_inbound(time::now());
665                    let link_id = *link.engine.link_id();
666                    LinkDataResult::ResourceIcl { link_id, inbound_actions }
667                }
668                constants::CONTEXT_RESOURCE_RCL => {
669                    let _ = link.engine.decrypt(&packet.data); // decrypt to validate
670                    let inbound_actions = link.engine.record_inbound(time::now());
671                    let link_id = *link.engine.link_id();
672                    LinkDataResult::ResourceRcl { link_id, inbound_actions }
673                }
674                _ => {
675                    match link.engine.decrypt(&packet.data) {
676                        Ok(plaintext) => {
677                            let inbound_actions = link.engine.record_inbound(time::now());
678                            let link_id = *link.engine.link_id();
679                            LinkDataResult::Generic { link_id, inbound_actions, plaintext, context: packet.context }
680                        }
681                        Err(_) => LinkDataResult::Error,
682                    }
683                }
684            }
685        }; // mutable borrow of self.links dropped here
686
687        // Second pass: process results using self methods
688        let mut actions = Vec::new();
689        match result {
690            LinkDataResult::Lrrtt { link_id, link_actions } => {
691                actions.extend(self.process_link_actions(&link_id, &link_actions));
692                // Initialize channel
693                if let Some(link) = self.links.get_mut(&link_id) {
694                    if link.engine.state() == LinkState::Active {
695                        let rtt = link.engine.rtt().unwrap_or(1.0);
696                        link.channel = Some(Channel::new(rtt));
697                    }
698                }
699            }
700            LinkDataResult::Identify { link_id, link_actions } => {
701                actions.extend(self.process_link_actions(&link_id, &link_actions));
702            }
703            LinkDataResult::Keepalive { link_id, inbound_actions } => {
704                actions.extend(self.process_link_actions(&link_id, &inbound_actions));
705                // Reply with a keepalive so the other side's last_inbound is updated.
706                // This handles asymmetric keepalive intervals (different RTTs per side).
707                if let Some(link) = self.links.get_mut(&link_id) {
708                    let now = time::now();
709                    let flags = PacketFlags {
710                        header_type: constants::HEADER_1,
711                        context_flag: constants::FLAG_UNSET,
712                        transport_type: constants::TRANSPORT_BROADCAST,
713                        destination_type: constants::DESTINATION_LINK,
714                        packet_type: constants::PACKET_TYPE_DATA,
715                    };
716                    if let Ok(pkt) = RawPacket::pack(
717                        flags, 0, &link_id, None, constants::CONTEXT_KEEPALIVE, &[],
718                    ) {
719                        actions.push(LinkManagerAction::SendPacket {
720                            raw: pkt.raw,
721                            dest_type: constants::DESTINATION_LINK,
722                            attached_interface: None,
723                        });
724                        link.engine.record_outbound(now, true);
725                    }
726                }
727            }
728            LinkDataResult::LinkClose { link_id, teardown_actions } => {
729                actions.extend(self.process_link_actions(&link_id, &teardown_actions));
730            }
731            LinkDataResult::Channel { link_id, inbound_actions, plaintext } => {
732                actions.extend(self.process_link_actions(&link_id, &inbound_actions));
733                // Feed plaintext to channel
734                if let Some(link) = self.links.get_mut(&link_id) {
735                    if let Some(ref mut channel) = link.channel {
736                        let chan_actions = channel.receive(&plaintext, time::now());
737                        // process_channel_actions needs immutable self, so collect first
738                        let _ = link;
739                        actions.extend(self.process_channel_actions(&link_id, chan_actions, rng));
740                    }
741                }
742            }
743            LinkDataResult::Request { link_id, inbound_actions, plaintext } => {
744                actions.extend(self.process_link_actions(&link_id, &inbound_actions));
745                actions.extend(self.handle_request(&link_id, &plaintext, rng));
746            }
747            LinkDataResult::Response { link_id, inbound_actions, plaintext } => {
748                actions.extend(self.process_link_actions(&link_id, &inbound_actions));
749                // Unpack msgpack response: [Bin(request_id), response_value]
750                actions.extend(self.handle_response(&link_id, &plaintext));
751            }
752            LinkDataResult::Generic { link_id, inbound_actions, plaintext, context } => {
753                actions.extend(self.process_link_actions(&link_id, &inbound_actions));
754                actions.push(LinkManagerAction::LinkDataReceived {
755                    link_id,
756                    context,
757                    data: plaintext,
758                });
759            }
760            LinkDataResult::ResourceAdv { link_id, inbound_actions, plaintext } => {
761                actions.extend(self.process_link_actions(&link_id, &inbound_actions));
762                actions.extend(self.handle_resource_adv(&link_id, &plaintext, rng));
763            }
764            LinkDataResult::ResourceReq { link_id, inbound_actions, plaintext } => {
765                actions.extend(self.process_link_actions(&link_id, &inbound_actions));
766                actions.extend(self.handle_resource_req(&link_id, &plaintext, rng));
767            }
768            LinkDataResult::ResourceHmu { link_id, inbound_actions, plaintext } => {
769                actions.extend(self.process_link_actions(&link_id, &inbound_actions));
770                actions.extend(self.handle_resource_hmu(&link_id, &plaintext, rng));
771            }
772            LinkDataResult::ResourcePart { link_id, inbound_actions, raw_data } => {
773                actions.extend(self.process_link_actions(&link_id, &inbound_actions));
774                actions.extend(self.handle_resource_part(&link_id, &raw_data, rng));
775            }
776            LinkDataResult::ResourcePrf { link_id, inbound_actions, plaintext } => {
777                actions.extend(self.process_link_actions(&link_id, &inbound_actions));
778                actions.extend(self.handle_resource_prf(&link_id, &plaintext));
779            }
780            LinkDataResult::ResourceIcl { link_id, inbound_actions } => {
781                actions.extend(self.process_link_actions(&link_id, &inbound_actions));
782                actions.extend(self.handle_resource_icl(&link_id));
783            }
784            LinkDataResult::ResourceRcl { link_id, inbound_actions } => {
785                actions.extend(self.process_link_actions(&link_id, &inbound_actions));
786                actions.extend(self.handle_resource_rcl(&link_id));
787            }
788            LinkDataResult::Error => {}
789        }
790
791        actions
792    }
793
794    /// Handle a request on a link.
795    fn handle_request(
796        &mut self,
797        link_id: &LinkId,
798        plaintext: &[u8],
799        rng: &mut dyn Rng,
800    ) -> Vec<LinkManagerAction> {
801        use rns_core::msgpack::{self, Value};
802
803        // Python-compatible format: msgpack([timestamp, Bin(path_hash), data_value])
804        let arr = match msgpack::unpack_exact(plaintext) {
805            Ok(Value::Array(arr)) if arr.len() >= 3 => arr,
806            _ => return Vec::new(),
807        };
808
809        let path_hash_bytes = match &arr[1] {
810            Value::Bin(b) if b.len() == 16 => b,
811            _ => return Vec::new(),
812        };
813        let mut path_hash = [0u8; 16];
814        path_hash.copy_from_slice(path_hash_bytes);
815
816        // Compute request_id = truncated_hash(packed_request_bytes)
817        let request_id = rns_core::hash::truncated_hash(plaintext);
818
819        // Re-encode the data element for the handler
820        let request_data = msgpack::pack(&arr[2]);
821
822        // Check if this is a management path (handled by the driver)
823        if self.management_paths.contains(&path_hash) {
824            let remote_identity = self.links.get(link_id)
825                .and_then(|l| l.remote_identity)
826                .map(|(h, k)| (h, k));
827            return vec![LinkManagerAction::ManagementRequest {
828                link_id: *link_id,
829                path_hash,
830                data: request_data,
831                request_id,
832                remote_identity,
833            }];
834        }
835
836        // Look up handler by path_hash
837        let handler_idx = self.request_handlers.iter().position(|h| h.path_hash == path_hash);
838        let handler_idx = match handler_idx {
839            Some(i) => i,
840            None => return Vec::new(),
841        };
842
843        // Check ACL
844        let remote_identity = self.links.get(link_id).and_then(|l| l.remote_identity.as_ref());
845        let handler = &self.request_handlers[handler_idx];
846        if let Some(ref allowed) = handler.allowed_list {
847            match remote_identity {
848                Some((identity_hash, _)) => {
849                    if !allowed.contains(identity_hash) {
850                        log::debug!("Request denied: identity not in allowed list");
851                        return Vec::new();
852                    }
853                }
854                None => {
855                    log::debug!("Request denied: peer not identified");
856                    return Vec::new();
857                }
858            }
859        }
860
861        // Call handler
862        let path = handler.path.clone();
863        let response = (handler.handler)(*link_id, &path, &request_data, remote_identity);
864
865        let mut actions = Vec::new();
866        if let Some(response_data) = response {
867            actions.extend(self.build_response_packet(link_id, &request_id, &response_data, rng));
868        }
869
870        actions
871    }
872
873    /// Build a response packet for a request.
874    /// `response_data` is the msgpack-encoded response value.
875    fn build_response_packet(
876        &self,
877        link_id: &LinkId,
878        request_id: &[u8; 16],
879        response_data: &[u8],
880        rng: &mut dyn Rng,
881    ) -> Vec<LinkManagerAction> {
882        use rns_core::msgpack::{self, Value};
883
884        // Python-compatible response: msgpack([Bin(request_id), response_value])
885        let response_value = msgpack::unpack_exact(response_data)
886            .unwrap_or_else(|_| Value::Bin(response_data.to_vec()));
887
888        let response_array = Value::Array(vec![
889            Value::Bin(request_id.to_vec()),
890            response_value,
891        ]);
892        let response_plaintext = msgpack::pack(&response_array);
893
894        let mut actions = Vec::new();
895        if let Some(link) = self.links.get(link_id) {
896            if let Ok(encrypted) = link.engine.encrypt(&response_plaintext, rng) {
897                let flags = PacketFlags {
898                    header_type: constants::HEADER_1,
899                    context_flag: constants::FLAG_UNSET,
900                    transport_type: constants::TRANSPORT_BROADCAST,
901                    destination_type: constants::DESTINATION_LINK,
902                    packet_type: constants::PACKET_TYPE_DATA,
903                };
904                if let Ok(pkt) = RawPacket::pack(
905                    flags, 0, link_id, None, constants::CONTEXT_RESPONSE, &encrypted,
906                ) {
907                    actions.push(LinkManagerAction::SendPacket {
908                        raw: pkt.raw,
909                        dest_type: constants::DESTINATION_LINK,
910                        attached_interface: None,
911                    });
912                }
913            }
914        }
915        actions
916    }
917
918    /// Send a management response on a link.
919    /// Called by the driver after building the response for a ManagementRequest.
920    pub fn send_management_response(
921        &self,
922        link_id: &LinkId,
923        request_id: &[u8; 16],
924        response_data: &[u8],
925        rng: &mut dyn Rng,
926    ) -> Vec<LinkManagerAction> {
927        self.build_response_packet(link_id, request_id, response_data, rng)
928    }
929
930    /// Send a request on a link.
931    ///
932    /// `data` is the msgpack-encoded request data value (e.g. msgpack([True]) for /status).
933    ///
934    /// Uses Python-compatible format: plaintext = msgpack([timestamp, path_hash_bytes, data_value]).
935    /// Returns actions (the encrypted request packet). The response will arrive
936    /// later via handle_local_delivery with CONTEXT_RESPONSE.
937    pub fn send_request(
938        &self,
939        link_id: &LinkId,
940        path: &str,
941        data: &[u8],
942        rng: &mut dyn Rng,
943    ) -> Vec<LinkManagerAction> {
944        use rns_core::msgpack::{self, Value};
945
946        let link = match self.links.get(link_id) {
947            Some(l) => l,
948            None => return Vec::new(),
949        };
950
951        if link.engine.state() != LinkState::Active {
952            return Vec::new();
953        }
954
955        let path_hash = compute_path_hash(path);
956
957        // Decode data bytes to msgpack Value (or use Bin if can't decode)
958        let data_value = msgpack::unpack_exact(data)
959            .unwrap_or_else(|_| Value::Bin(data.to_vec()));
960
961        // Python-compatible format: msgpack([timestamp, Bin(path_hash), data_value])
962        let request_array = Value::Array(vec![
963            Value::Float(time::now()),
964            Value::Bin(path_hash.to_vec()),
965            data_value,
966        ]);
967        let plaintext = msgpack::pack(&request_array);
968
969        let encrypted = match link.engine.encrypt(&plaintext, rng) {
970            Ok(e) => e,
971            Err(_) => return Vec::new(),
972        };
973
974        let flags = PacketFlags {
975            header_type: constants::HEADER_1,
976            context_flag: constants::FLAG_UNSET,
977            transport_type: constants::TRANSPORT_BROADCAST,
978            destination_type: constants::DESTINATION_LINK,
979            packet_type: constants::PACKET_TYPE_DATA,
980        };
981
982        let mut actions = Vec::new();
983        if let Ok(pkt) = RawPacket::pack(
984            flags, 0, link_id, None, constants::CONTEXT_REQUEST, &encrypted,
985        ) {
986            actions.push(LinkManagerAction::SendPacket {
987                raw: pkt.raw,
988                dest_type: constants::DESTINATION_LINK,
989                attached_interface: None,
990            });
991        }
992        actions
993    }
994
995    /// Send encrypted data on a link with a given context.
996    pub fn send_on_link(
997        &self,
998        link_id: &LinkId,
999        plaintext: &[u8],
1000        context: u8,
1001        rng: &mut dyn Rng,
1002    ) -> Vec<LinkManagerAction> {
1003        let link = match self.links.get(link_id) {
1004            Some(l) => l,
1005            None => return Vec::new(),
1006        };
1007
1008        if link.engine.state() != LinkState::Active {
1009            return Vec::new();
1010        }
1011
1012        let encrypted = match link.engine.encrypt(plaintext, rng) {
1013            Ok(e) => e,
1014            Err(_) => return Vec::new(),
1015        };
1016
1017        let flags = PacketFlags {
1018            header_type: constants::HEADER_1,
1019            context_flag: constants::FLAG_UNSET,
1020            transport_type: constants::TRANSPORT_BROADCAST,
1021            destination_type: constants::DESTINATION_LINK,
1022            packet_type: constants::PACKET_TYPE_DATA,
1023        };
1024
1025        let mut actions = Vec::new();
1026        if let Ok(pkt) = RawPacket::pack(
1027            flags, 0, link_id, None, context, &encrypted,
1028        ) {
1029            actions.push(LinkManagerAction::SendPacket {
1030                raw: pkt.raw,
1031                dest_type: constants::DESTINATION_LINK,
1032                attached_interface: None,
1033            });
1034        }
1035        actions
1036    }
1037
1038    /// Send an identify message on a link (initiator reveals identity to responder).
1039    pub fn identify(
1040        &self,
1041        link_id: &LinkId,
1042        identity: &rns_crypto::identity::Identity,
1043        rng: &mut dyn Rng,
1044    ) -> Vec<LinkManagerAction> {
1045        let link = match self.links.get(link_id) {
1046            Some(l) => l,
1047            None => return Vec::new(),
1048        };
1049
1050        let encrypted = match link.engine.build_identify(identity, rng) {
1051            Ok(e) => e,
1052            Err(_) => return Vec::new(),
1053        };
1054
1055        let flags = PacketFlags {
1056            header_type: constants::HEADER_1,
1057            context_flag: constants::FLAG_UNSET,
1058            transport_type: constants::TRANSPORT_BROADCAST,
1059            destination_type: constants::DESTINATION_LINK,
1060            packet_type: constants::PACKET_TYPE_DATA,
1061        };
1062
1063        let mut actions = Vec::new();
1064        if let Ok(pkt) = RawPacket::pack(
1065            flags, 0, link_id, None, constants::CONTEXT_LINKIDENTIFY, &encrypted,
1066        ) {
1067            actions.push(LinkManagerAction::SendPacket {
1068                raw: pkt.raw,
1069                dest_type: constants::DESTINATION_LINK,
1070                attached_interface: None,
1071            });
1072        }
1073        actions
1074    }
1075
1076    /// Tear down a link.
1077    pub fn teardown_link(&mut self, link_id: &LinkId) -> Vec<LinkManagerAction> {
1078        let link = match self.links.get_mut(link_id) {
1079            Some(l) => l,
1080            None => return Vec::new(),
1081        };
1082
1083        let teardown_actions = link.engine.teardown();
1084        if let Some(ref mut channel) = link.channel {
1085            channel.shutdown();
1086        }
1087
1088        let mut actions = self.process_link_actions(link_id, &teardown_actions);
1089
1090        // Send LINKCLOSE packet
1091        let flags = PacketFlags {
1092            header_type: constants::HEADER_1,
1093            context_flag: constants::FLAG_UNSET,
1094            transport_type: constants::TRANSPORT_BROADCAST,
1095            destination_type: constants::DESTINATION_LINK,
1096            packet_type: constants::PACKET_TYPE_DATA,
1097        };
1098        if let Ok(pkt) = RawPacket::pack(
1099            flags, 0, link_id, None, constants::CONTEXT_LINKCLOSE, &[],
1100        ) {
1101            actions.push(LinkManagerAction::SendPacket {
1102                raw: pkt.raw,
1103                dest_type: constants::DESTINATION_LINK,
1104                attached_interface: None,
1105            });
1106        }
1107
1108        actions
1109    }
1110
1111    /// Handle a response on a link.
1112    fn handle_response(
1113        &self,
1114        link_id: &LinkId,
1115        plaintext: &[u8],
1116    ) -> Vec<LinkManagerAction> {
1117        use rns_core::msgpack;
1118
1119        // Python-compatible response: msgpack([Bin(request_id), response_value])
1120        let arr = match msgpack::unpack_exact(plaintext) {
1121            Ok(msgpack::Value::Array(arr)) if arr.len() >= 2 => arr,
1122            _ => return Vec::new(),
1123        };
1124
1125        let request_id_bytes = match &arr[0] {
1126            msgpack::Value::Bin(b) if b.len() == 16 => b,
1127            _ => return Vec::new(),
1128        };
1129        let mut request_id = [0u8; 16];
1130        request_id.copy_from_slice(request_id_bytes);
1131
1132        let response_data = msgpack::pack(&arr[1]);
1133
1134        vec![LinkManagerAction::ResponseReceived {
1135            link_id: *link_id,
1136            request_id,
1137            data: response_data,
1138        }]
1139    }
1140
1141    /// Handle resource advertisement (CONTEXT_RESOURCE_ADV).
1142    fn handle_resource_adv(
1143        &mut self,
1144        link_id: &LinkId,
1145        adv_plaintext: &[u8],
1146        rng: &mut dyn Rng,
1147    ) -> Vec<LinkManagerAction> {
1148        let link = match self.links.get_mut(link_id) {
1149            Some(l) => l,
1150            None => return Vec::new(),
1151        };
1152
1153        let link_rtt = link.engine.rtt().unwrap_or(1.0);
1154        let now = time::now();
1155
1156        let receiver = match ResourceReceiver::from_advertisement(
1157            adv_plaintext,
1158            constants::RESOURCE_SDU,
1159            link_rtt,
1160            now,
1161            None,
1162            None,
1163        ) {
1164            Ok(r) => r,
1165            Err(e) => {
1166                log::debug!("Resource ADV rejected: {}", e);
1167                return Vec::new();
1168            }
1169        };
1170
1171        let strategy = link.resource_strategy;
1172        let resource_hash = receiver.resource_hash.clone();
1173        let transfer_size = receiver.transfer_size;
1174        let has_metadata = receiver.has_metadata;
1175
1176        match strategy {
1177            ResourceStrategy::AcceptNone => {
1178                // Reject: send RCL
1179                let reject_actions = {
1180                    let mut r = receiver;
1181                    r.reject()
1182                };
1183                self.process_resource_actions(link_id, reject_actions, rng)
1184            }
1185            ResourceStrategy::AcceptAll => {
1186                link.incoming_resources.push(receiver);
1187                let idx = link.incoming_resources.len() - 1;
1188                let resource_actions = link.incoming_resources[idx].accept(now);
1189                let _ = link;
1190                self.process_resource_actions(link_id, resource_actions, rng)
1191            }
1192            ResourceStrategy::AcceptApp => {
1193                link.incoming_resources.push(receiver);
1194                // Query application callback
1195                vec![LinkManagerAction::ResourceAcceptQuery {
1196                    link_id: *link_id,
1197                    resource_hash,
1198                    transfer_size,
1199                    has_metadata,
1200                }]
1201            }
1202        }
1203    }
1204
1205    /// Accept or reject a pending resource (for AcceptApp strategy).
1206    pub fn accept_resource(
1207        &mut self,
1208        link_id: &LinkId,
1209        resource_hash: &[u8],
1210        accept: bool,
1211        rng: &mut dyn Rng,
1212    ) -> Vec<LinkManagerAction> {
1213        let link = match self.links.get_mut(link_id) {
1214            Some(l) => l,
1215            None => return Vec::new(),
1216        };
1217
1218        let now = time::now();
1219        let idx = link.incoming_resources.iter().position(|r| r.resource_hash == resource_hash);
1220        let idx = match idx {
1221            Some(i) => i,
1222            None => return Vec::new(),
1223        };
1224
1225        let resource_actions = if accept {
1226            link.incoming_resources[idx].accept(now)
1227        } else {
1228            link.incoming_resources[idx].reject()
1229        };
1230
1231        let _ = link;
1232        self.process_resource_actions(link_id, resource_actions, rng)
1233    }
1234
1235    /// Handle resource request (CONTEXT_RESOURCE_REQ) — feed to sender.
1236    fn handle_resource_req(
1237        &mut self,
1238        link_id: &LinkId,
1239        plaintext: &[u8],
1240        rng: &mut dyn Rng,
1241    ) -> Vec<LinkManagerAction> {
1242        let link = match self.links.get_mut(link_id) {
1243            Some(l) => l,
1244            None => return Vec::new(),
1245        };
1246
1247        let now = time::now();
1248        let mut all_actions = Vec::new();
1249        for sender in &mut link.outgoing_resources {
1250            let resource_actions = sender.handle_request(plaintext, now);
1251            if !resource_actions.is_empty() {
1252                all_actions.extend(resource_actions);
1253                break;
1254            }
1255        }
1256
1257        let _ = link;
1258        self.process_resource_actions(link_id, all_actions, rng)
1259    }
1260
1261    /// Handle resource HMU (CONTEXT_RESOURCE_HMU) — feed to receiver.
1262    fn handle_resource_hmu(
1263        &mut self,
1264        link_id: &LinkId,
1265        plaintext: &[u8],
1266        rng: &mut dyn Rng,
1267    ) -> Vec<LinkManagerAction> {
1268        let link = match self.links.get_mut(link_id) {
1269            Some(l) => l,
1270            None => return Vec::new(),
1271        };
1272
1273        let now = time::now();
1274        let mut all_actions = Vec::new();
1275        for receiver in &mut link.incoming_resources {
1276            let resource_actions = receiver.handle_hashmap_update(plaintext, now);
1277            if !resource_actions.is_empty() {
1278                all_actions.extend(resource_actions);
1279                break;
1280            }
1281        }
1282
1283        let _ = link;
1284        self.process_resource_actions(link_id, all_actions, rng)
1285    }
1286
1287    /// Handle resource part (CONTEXT_RESOURCE) — feed raw to receiver.
1288    fn handle_resource_part(
1289        &mut self,
1290        link_id: &LinkId,
1291        raw_data: &[u8],
1292        rng: &mut dyn Rng,
1293    ) -> Vec<LinkManagerAction> {
1294        let link = match self.links.get_mut(link_id) {
1295            Some(l) => l,
1296            None => return Vec::new(),
1297        };
1298
1299        let now = time::now();
1300        let mut all_actions = Vec::new();
1301        let mut assemble_idx = None;
1302
1303        for (idx, receiver) in link.incoming_resources.iter_mut().enumerate() {
1304            let resource_actions = receiver.receive_part(raw_data, now);
1305            if !resource_actions.is_empty() {
1306                // Check if all parts received (triggers assembly)
1307                if receiver.received_count == receiver.total_parts {
1308                    assemble_idx = Some(idx);
1309                }
1310                all_actions.extend(resource_actions);
1311                break;
1312            }
1313        }
1314
1315        // Assemble if all parts received
1316        if let Some(idx) = assemble_idx {
1317            let decrypt_fn = |ciphertext: &[u8]| -> Result<Vec<u8>, ()> {
1318                link.engine.decrypt(ciphertext).map_err(|_| ())
1319            };
1320            let assemble_actions = link.incoming_resources[idx].assemble(&decrypt_fn, &NoopCompressor);
1321            all_actions.extend(assemble_actions);
1322        }
1323
1324        let _ = link;
1325        self.process_resource_actions(link_id, all_actions, rng)
1326    }
1327
1328    /// Handle resource proof (CONTEXT_RESOURCE_PRF) — feed to sender.
1329    fn handle_resource_prf(
1330        &mut self,
1331        link_id: &LinkId,
1332        plaintext: &[u8],
1333    ) -> Vec<LinkManagerAction> {
1334        let link = match self.links.get_mut(link_id) {
1335            Some(l) => l,
1336            None => return Vec::new(),
1337        };
1338
1339        let now = time::now();
1340        let mut result_actions = Vec::new();
1341        for sender in &mut link.outgoing_resources {
1342            let resource_actions = sender.handle_proof(plaintext, now);
1343            if !resource_actions.is_empty() {
1344                result_actions.extend(resource_actions);
1345                break;
1346            }
1347        }
1348
1349        // Convert to LinkManagerActions
1350        let mut actions = Vec::new();
1351        for ra in result_actions {
1352            match ra {
1353                ResourceAction::Completed => {
1354                    actions.push(LinkManagerAction::ResourceCompleted { link_id: *link_id });
1355                }
1356                ResourceAction::Failed(e) => {
1357                    actions.push(LinkManagerAction::ResourceFailed {
1358                        link_id: *link_id,
1359                        error: format!("{}", e),
1360                    });
1361                }
1362                _ => {}
1363            }
1364        }
1365
1366        // Clean up completed/failed senders
1367        link.outgoing_resources.retain(|s| {
1368            s.status < rns_core::resource::ResourceStatus::Complete
1369        });
1370
1371        actions
1372    }
1373
1374    /// Handle cancel from initiator (CONTEXT_RESOURCE_ICL).
1375    fn handle_resource_icl(
1376        &mut self,
1377        link_id: &LinkId,
1378    ) -> Vec<LinkManagerAction> {
1379        let link = match self.links.get_mut(link_id) {
1380            Some(l) => l,
1381            None => return Vec::new(),
1382        };
1383
1384        let mut actions = Vec::new();
1385        for receiver in &mut link.incoming_resources {
1386            let ra = receiver.handle_cancel();
1387            for a in ra {
1388                if let ResourceAction::Failed(ref e) = a {
1389                    actions.push(LinkManagerAction::ResourceFailed {
1390                        link_id: *link_id,
1391                        error: format!("{}", e),
1392                    });
1393                }
1394            }
1395        }
1396        link.incoming_resources.retain(|r| {
1397            r.status < rns_core::resource::ResourceStatus::Complete
1398        });
1399        actions
1400    }
1401
1402    /// Handle cancel from receiver (CONTEXT_RESOURCE_RCL).
1403    fn handle_resource_rcl(
1404        &mut self,
1405        link_id: &LinkId,
1406    ) -> Vec<LinkManagerAction> {
1407        let link = match self.links.get_mut(link_id) {
1408            Some(l) => l,
1409            None => return Vec::new(),
1410        };
1411
1412        let mut actions = Vec::new();
1413        for sender in &mut link.outgoing_resources {
1414            let ra = sender.handle_reject();
1415            for a in ra {
1416                if let ResourceAction::Failed(ref e) = a {
1417                    actions.push(LinkManagerAction::ResourceFailed {
1418                        link_id: *link_id,
1419                        error: format!("{}", e),
1420                    });
1421                }
1422            }
1423        }
1424        link.outgoing_resources.retain(|s| {
1425            s.status < rns_core::resource::ResourceStatus::Complete
1426        });
1427        actions
1428    }
1429
1430    /// Convert ResourceActions to LinkManagerActions.
1431    fn process_resource_actions(
1432        &self,
1433        link_id: &LinkId,
1434        actions: Vec<ResourceAction>,
1435        rng: &mut dyn Rng,
1436    ) -> Vec<LinkManagerAction> {
1437        let link = match self.links.get(link_id) {
1438            Some(l) => l,
1439            None => return Vec::new(),
1440        };
1441
1442        let mut result = Vec::new();
1443        for action in actions {
1444            match action {
1445                ResourceAction::SendAdvertisement(data) => {
1446                    // Link-encrypt and send as CONTEXT_RESOURCE_ADV
1447                    if let Ok(encrypted) = link.engine.encrypt(&data, rng) {
1448                        result.extend(self.build_link_packet(
1449                            link_id, constants::CONTEXT_RESOURCE_ADV, &encrypted,
1450                        ));
1451                    }
1452                }
1453                ResourceAction::SendPart(data) => {
1454                    // Parts are NOT link-encrypted — send raw as CONTEXT_RESOURCE
1455                    result.extend(self.build_link_packet(
1456                        link_id, constants::CONTEXT_RESOURCE, &data,
1457                    ));
1458                }
1459                ResourceAction::SendRequest(data) => {
1460                    if let Ok(encrypted) = link.engine.encrypt(&data, rng) {
1461                        result.extend(self.build_link_packet(
1462                            link_id, constants::CONTEXT_RESOURCE_REQ, &encrypted,
1463                        ));
1464                    }
1465                }
1466                ResourceAction::SendHmu(data) => {
1467                    if let Ok(encrypted) = link.engine.encrypt(&data, rng) {
1468                        result.extend(self.build_link_packet(
1469                            link_id, constants::CONTEXT_RESOURCE_HMU, &encrypted,
1470                        ));
1471                    }
1472                }
1473                ResourceAction::SendProof(data) => {
1474                    if let Ok(encrypted) = link.engine.encrypt(&data, rng) {
1475                        result.extend(self.build_link_packet(
1476                            link_id, constants::CONTEXT_RESOURCE_PRF, &encrypted,
1477                        ));
1478                    }
1479                }
1480                ResourceAction::SendCancelInitiator(data) => {
1481                    if let Ok(encrypted) = link.engine.encrypt(&data, rng) {
1482                        result.extend(self.build_link_packet(
1483                            link_id, constants::CONTEXT_RESOURCE_ICL, &encrypted,
1484                        ));
1485                    }
1486                }
1487                ResourceAction::SendCancelReceiver(data) => {
1488                    if let Ok(encrypted) = link.engine.encrypt(&data, rng) {
1489                        result.extend(self.build_link_packet(
1490                            link_id, constants::CONTEXT_RESOURCE_RCL, &encrypted,
1491                        ));
1492                    }
1493                }
1494                ResourceAction::DataReceived { data, metadata } => {
1495                    result.push(LinkManagerAction::ResourceReceived {
1496                        link_id: *link_id,
1497                        data,
1498                        metadata,
1499                    });
1500                }
1501                ResourceAction::Completed => {
1502                    result.push(LinkManagerAction::ResourceCompleted { link_id: *link_id });
1503                }
1504                ResourceAction::Failed(e) => {
1505                    result.push(LinkManagerAction::ResourceFailed {
1506                        link_id: *link_id,
1507                        error: format!("{}", e),
1508                    });
1509                }
1510                ResourceAction::ProgressUpdate { received, total } => {
1511                    result.push(LinkManagerAction::ResourceProgress {
1512                        link_id: *link_id,
1513                        received,
1514                        total,
1515                    });
1516                }
1517            }
1518        }
1519        result
1520    }
1521
1522    /// Build a link DATA packet with a given context and data.
1523    fn build_link_packet(
1524        &self,
1525        link_id: &LinkId,
1526        context: u8,
1527        data: &[u8],
1528    ) -> Vec<LinkManagerAction> {
1529        let flags = PacketFlags {
1530            header_type: constants::HEADER_1,
1531            context_flag: constants::FLAG_UNSET,
1532            transport_type: constants::TRANSPORT_BROADCAST,
1533            destination_type: constants::DESTINATION_LINK,
1534            packet_type: constants::PACKET_TYPE_DATA,
1535        };
1536        let mut actions = Vec::new();
1537        if let Ok(pkt) = RawPacket::pack(flags, 0, link_id, None, context, data) {
1538            actions.push(LinkManagerAction::SendPacket {
1539                raw: pkt.raw,
1540                dest_type: constants::DESTINATION_LINK,
1541                attached_interface: None,
1542            });
1543        }
1544        actions
1545    }
1546
1547    /// Start sending a resource on a link.
1548    pub fn send_resource(
1549        &mut self,
1550        link_id: &LinkId,
1551        data: &[u8],
1552        metadata: Option<&[u8]>,
1553        rng: &mut dyn Rng,
1554    ) -> Vec<LinkManagerAction> {
1555        let link = match self.links.get_mut(link_id) {
1556            Some(l) => l,
1557            None => return Vec::new(),
1558        };
1559
1560        if link.engine.state() != LinkState::Active {
1561            return Vec::new();
1562        }
1563
1564        let link_rtt = link.engine.rtt().unwrap_or(1.0);
1565        let now = time::now();
1566
1567        // Use RefCell for interior mutability since ResourceSender::new expects &dyn Fn (not FnMut)
1568        // but link.engine.encrypt needs &mut dyn Rng
1569        let enc_rng = std::cell::RefCell::new(rns_crypto::OsRng);
1570        let encrypt_fn = |plaintext: &[u8]| -> Vec<u8> {
1571            link.engine.encrypt(plaintext, &mut *enc_rng.borrow_mut()).unwrap_or_else(|_| plaintext.to_vec())
1572        };
1573
1574        let sender = match ResourceSender::new(
1575            data,
1576            metadata,
1577            constants::RESOURCE_SDU,
1578            &encrypt_fn,
1579            &NoopCompressor,
1580            rng,
1581            now,
1582            false, // auto_compress (we use NoopCompressor)
1583            false, // is_response
1584            None,  // request_id
1585            1,     // segment_index
1586            1,     // total_segments
1587            None,  // original_hash
1588            link_rtt,
1589            6.0,   // traffic_timeout_factor
1590        ) {
1591            Ok(s) => s,
1592            Err(e) => {
1593                log::debug!("Failed to create ResourceSender: {}", e);
1594                return Vec::new();
1595            }
1596        };
1597
1598        let mut sender = sender;
1599        let adv_actions = sender.advertise(now);
1600        link.outgoing_resources.push(sender);
1601
1602        let _ = link;
1603        self.process_resource_actions(link_id, adv_actions, rng)
1604    }
1605
1606    /// Set the resource acceptance strategy for a link.
1607    pub fn set_resource_strategy(&mut self, link_id: &LinkId, strategy: ResourceStrategy) {
1608        if let Some(link) = self.links.get_mut(link_id) {
1609            link.resource_strategy = strategy;
1610        }
1611    }
1612
1613    /// Flush the channel TX ring for a link, clearing outstanding messages.
1614    /// Called after holepunch completion where signaling messages are fire-and-forget.
1615    pub fn flush_channel_tx(&mut self, link_id: &LinkId) {
1616        if let Some(link) = self.links.get_mut(link_id) {
1617            if let Some(ref mut channel) = link.channel {
1618                channel.flush_tx();
1619            }
1620        }
1621    }
1622
1623    /// Send a channel message on a link.
1624    pub fn send_channel_message(
1625        &mut self,
1626        link_id: &LinkId,
1627        msgtype: u16,
1628        payload: &[u8],
1629        rng: &mut dyn Rng,
1630    ) -> Vec<LinkManagerAction> {
1631        let link = match self.links.get_mut(link_id) {
1632            Some(l) => l,
1633            None => return Vec::new(),
1634        };
1635
1636        let channel = match link.channel {
1637            Some(ref mut ch) => ch,
1638            None => return Vec::new(),
1639        };
1640
1641        let link_mdu = constants::MDU; // Use MDU as approximate link MDU
1642        let now = time::now();
1643        let chan_actions = match channel.send(msgtype, payload, now, link_mdu) {
1644            Ok(a) => a,
1645            Err(e) => {
1646                log::debug!("Channel send failed: {:?}", e);
1647                return Vec::new();
1648            }
1649        };
1650
1651        let _ = link;
1652        self.process_channel_actions(link_id, chan_actions, rng)
1653    }
1654
1655    /// Periodic tick: check keepalive, stale, timeouts for all links.
1656    pub fn tick(&mut self, rng: &mut dyn Rng) -> Vec<LinkManagerAction> {
1657        let now = time::now();
1658        let mut all_actions = Vec::new();
1659
1660        // Collect link_ids to avoid borrow issues
1661        let link_ids: Vec<LinkId> = self.links.keys().copied().collect();
1662
1663        for link_id in &link_ids {
1664            let link = match self.links.get_mut(link_id) {
1665                Some(l) => l,
1666                None => continue,
1667            };
1668
1669            // Tick the engine
1670            let tick_actions = link.engine.tick(now);
1671            all_actions.extend(self.process_link_actions(link_id, &tick_actions));
1672
1673            // Check if keepalive is needed
1674            let link = match self.links.get_mut(link_id) {
1675                Some(l) => l,
1676                None => continue,
1677            };
1678            if link.engine.needs_keepalive(now) {
1679                // Send keepalive packet (empty data with CONTEXT_KEEPALIVE)
1680                let flags = PacketFlags {
1681                    header_type: constants::HEADER_1,
1682                    context_flag: constants::FLAG_UNSET,
1683                    transport_type: constants::TRANSPORT_BROADCAST,
1684                    destination_type: constants::DESTINATION_LINK,
1685                    packet_type: constants::PACKET_TYPE_DATA,
1686                };
1687                if let Ok(pkt) = RawPacket::pack(
1688                    flags, 0, link_id, None, constants::CONTEXT_KEEPALIVE, &[],
1689                ) {
1690                    all_actions.push(LinkManagerAction::SendPacket {
1691                        raw: pkt.raw,
1692                        dest_type: constants::DESTINATION_LINK,
1693                        attached_interface: None,
1694                    });
1695                    link.engine.record_outbound(now, true);
1696                }
1697            }
1698        }
1699
1700        // Tick resource senders and receivers
1701        for link_id in &link_ids {
1702            let link = match self.links.get_mut(link_id) {
1703                Some(l) => l,
1704                None => continue,
1705            };
1706
1707            // Tick outgoing resources (senders)
1708            let mut sender_actions = Vec::new();
1709            for sender in &mut link.outgoing_resources {
1710                sender_actions.extend(sender.tick(now));
1711            }
1712
1713            // Tick incoming resources (receivers)
1714            let mut receiver_actions = Vec::new();
1715            for receiver in &mut link.incoming_resources {
1716                let decrypt_fn = |ciphertext: &[u8]| -> Result<Vec<u8>, ()> {
1717                    link.engine.decrypt(ciphertext).map_err(|_| ())
1718                };
1719                receiver_actions.extend(receiver.tick(now, &decrypt_fn, &NoopCompressor));
1720            }
1721
1722            // Clean up completed/failed resources
1723            link.outgoing_resources.retain(|s| {
1724                s.status < rns_core::resource::ResourceStatus::Complete
1725            });
1726            link.incoming_resources.retain(|r| {
1727                r.status < rns_core::resource::ResourceStatus::Assembling
1728            });
1729
1730            let _ = link;
1731            all_actions.extend(self.process_resource_actions(link_id, sender_actions, rng));
1732            all_actions.extend(self.process_resource_actions(link_id, receiver_actions, rng));
1733        }
1734
1735        // Clean up closed links
1736        let closed: Vec<LinkId> = self.links.iter()
1737            .filter(|(_, l)| l.engine.state() == LinkState::Closed)
1738            .map(|(id, _)| *id)
1739            .collect();
1740        for id in closed {
1741            self.links.remove(&id);
1742            all_actions.push(LinkManagerAction::DeregisterLinkDest { link_id: id });
1743        }
1744
1745        all_actions
1746    }
1747
1748    /// Check if a destination hash is a known link_id managed by this manager.
1749    pub fn is_link_destination(&self, dest_hash: &[u8; 16]) -> bool {
1750        self.links.contains_key(dest_hash) || self.link_destinations.contains_key(dest_hash)
1751    }
1752
1753    /// Get the state of a link.
1754    pub fn link_state(&self, link_id: &LinkId) -> Option<LinkState> {
1755        self.links.get(link_id).map(|l| l.engine.state())
1756    }
1757
1758    /// Get the RTT of a link.
1759    pub fn link_rtt(&self, link_id: &LinkId) -> Option<f64> {
1760        self.links.get(link_id).and_then(|l| l.engine.rtt())
1761    }
1762
1763    /// Update the RTT of a link (e.g., after path redirect to a direct connection).
1764    pub fn set_link_rtt(&mut self, link_id: &LinkId, rtt: f64) {
1765        if let Some(link) = self.links.get_mut(link_id) {
1766            link.engine.set_rtt(rtt);
1767        }
1768    }
1769
1770    /// Reset the inbound timer for a link (e.g., after path redirect).
1771    pub fn record_link_inbound(&mut self, link_id: &LinkId) {
1772        if let Some(link) = self.links.get_mut(link_id) {
1773            link.engine.record_inbound(time::now());
1774        }
1775    }
1776
1777    /// Update the MTU of a link (e.g., after path redirect to a different interface).
1778    pub fn set_link_mtu(&mut self, link_id: &LinkId, mtu: u32) {
1779        if let Some(link) = self.links.get_mut(link_id) {
1780            link.engine.set_mtu(mtu);
1781        }
1782    }
1783
1784    /// Get the number of active links.
1785    pub fn link_count(&self) -> usize {
1786        self.links.len()
1787    }
1788
1789    /// Get information about all active links.
1790    pub fn link_entries(&self) -> Vec<crate::event::LinkInfoEntry> {
1791        self.links
1792            .iter()
1793            .map(|(link_id, managed)| {
1794                let state = match managed.engine.state() {
1795                    LinkState::Pending => "pending",
1796                    LinkState::Handshake => "handshake",
1797                    LinkState::Active => "active",
1798                    LinkState::Stale => "stale",
1799                    LinkState::Closed => "closed",
1800                };
1801                crate::event::LinkInfoEntry {
1802                    link_id: *link_id,
1803                    state: state.to_string(),
1804                    is_initiator: managed.engine.is_initiator(),
1805                    dest_hash: managed.dest_hash,
1806                    remote_identity: managed.remote_identity.as_ref().map(|(h, _)| *h),
1807                    rtt: managed.engine.rtt(),
1808                }
1809            })
1810            .collect()
1811    }
1812
1813    /// Get information about all active resource transfers.
1814    pub fn resource_entries(&self) -> Vec<crate::event::ResourceInfoEntry> {
1815        let mut entries = Vec::new();
1816        for (link_id, managed) in &self.links {
1817            for recv in &managed.incoming_resources {
1818                let (received, total) = recv.progress();
1819                entries.push(crate::event::ResourceInfoEntry {
1820                    link_id: *link_id,
1821                    direction: "incoming".to_string(),
1822                    total_parts: total,
1823                    transferred_parts: received,
1824                    complete: received >= total && total > 0,
1825                });
1826            }
1827            for send in &managed.outgoing_resources {
1828                let total = send.total_parts();
1829                let sent = send.sent_parts;
1830                entries.push(crate::event::ResourceInfoEntry {
1831                    link_id: *link_id,
1832                    direction: "outgoing".to_string(),
1833                    total_parts: total,
1834                    transferred_parts: sent,
1835                    complete: sent >= total && total > 0,
1836                });
1837            }
1838        }
1839        entries
1840    }
1841
1842    /// Convert LinkActions to LinkManagerActions.
1843    fn process_link_actions(&self, link_id: &LinkId, actions: &[LinkAction]) -> Vec<LinkManagerAction> {
1844        let mut result = Vec::new();
1845        for action in actions {
1846            match action {
1847                LinkAction::StateChanged { new_state, reason, .. } => {
1848                    match new_state {
1849                        LinkState::Closed => {
1850                            result.push(LinkManagerAction::LinkClosed {
1851                                link_id: *link_id,
1852                                reason: *reason,
1853                            });
1854                        }
1855                        _ => {}
1856                    }
1857                }
1858                LinkAction::LinkEstablished { rtt, is_initiator, .. } => {
1859                    let dest_hash = self.links.get(link_id)
1860                        .map(|l| l.dest_hash)
1861                        .unwrap_or([0u8; 16]);
1862                    result.push(LinkManagerAction::LinkEstablished {
1863                        link_id: *link_id,
1864                        dest_hash,
1865                        rtt: *rtt,
1866                        is_initiator: *is_initiator,
1867                    });
1868                }
1869                LinkAction::RemoteIdentified { identity_hash, public_key, .. } => {
1870                    result.push(LinkManagerAction::RemoteIdentified {
1871                        link_id: *link_id,
1872                        identity_hash: *identity_hash,
1873                        public_key: *public_key,
1874                    });
1875                }
1876                LinkAction::DataReceived { .. } => {
1877                    // Data delivery is handled at a higher level
1878                }
1879            }
1880        }
1881        result
1882    }
1883
1884    /// Convert ChannelActions to LinkManagerActions.
1885    fn process_channel_actions(
1886        &self,
1887        link_id: &LinkId,
1888        actions: Vec<rns_core::channel::ChannelAction>,
1889        rng: &mut dyn Rng,
1890    ) -> Vec<LinkManagerAction> {
1891        let mut result = Vec::new();
1892        for action in actions {
1893            match action {
1894                rns_core::channel::ChannelAction::SendOnLink { raw } => {
1895                    // Encrypt and send as CHANNEL context
1896                    if let Some(link) = self.links.get(link_id) {
1897                        if let Ok(encrypted) = link.engine.encrypt(&raw, rng) {
1898                            let flags = PacketFlags {
1899                                header_type: constants::HEADER_1,
1900                                context_flag: constants::FLAG_UNSET,
1901                                transport_type: constants::TRANSPORT_BROADCAST,
1902                                destination_type: constants::DESTINATION_LINK,
1903                                packet_type: constants::PACKET_TYPE_DATA,
1904                            };
1905                            if let Ok(pkt) = RawPacket::pack(
1906                                flags, 0, link_id, None, constants::CONTEXT_CHANNEL, &encrypted,
1907                            ) {
1908                                result.push(LinkManagerAction::SendPacket {
1909                                    raw: pkt.raw,
1910                                    dest_type: constants::DESTINATION_LINK,
1911                                    attached_interface: None,
1912                                });
1913                            }
1914                        }
1915                    }
1916                }
1917                rns_core::channel::ChannelAction::MessageReceived { msgtype, payload, .. } => {
1918                    result.push(LinkManagerAction::ChannelMessageReceived {
1919                        link_id: *link_id,
1920                        msgtype,
1921                        payload,
1922                    });
1923                }
1924                rns_core::channel::ChannelAction::TeardownLink => {
1925                    result.push(LinkManagerAction::LinkClosed {
1926                        link_id: *link_id,
1927                        reason: Some(TeardownReason::Timeout),
1928                    });
1929                }
1930            }
1931        }
1932        result
1933    }
1934}
1935
1936/// Compute a path hash from a path string.
1937/// Uses truncated SHA-256 (first 16 bytes).
1938fn compute_path_hash(path: &str) -> [u8; 16] {
1939    let full = rns_core::hash::full_hash(path.as_bytes());
1940    let mut result = [0u8; 16];
1941    result.copy_from_slice(&full[..16]);
1942    result
1943}
1944
1945#[cfg(test)]
1946mod tests {
1947    use super::*;
1948    use rns_crypto::identity::Identity;
1949    use rns_crypto::{FixedRng, OsRng};
1950
1951    fn make_rng(seed: u8) -> FixedRng {
1952        FixedRng::new(&[seed; 128])
1953    }
1954
1955    fn make_dest_keys(rng: &mut dyn Rng) -> (Ed25519PrivateKey, [u8; 32]) {
1956        let sig_prv = Ed25519PrivateKey::generate(rng);
1957        let sig_pub_bytes = sig_prv.public_key().public_bytes();
1958        (sig_prv, sig_pub_bytes)
1959    }
1960
1961    #[test]
1962    fn test_register_link_destination() {
1963        let mut mgr = LinkManager::new();
1964        let mut rng = make_rng(0x01);
1965        let (sig_prv, sig_pub_bytes) = make_dest_keys(&mut rng);
1966        let dest_hash = [0xDD; 16];
1967
1968        mgr.register_link_destination(dest_hash, sig_prv, sig_pub_bytes);
1969        assert!(mgr.is_link_destination(&dest_hash));
1970
1971        mgr.deregister_link_destination(&dest_hash);
1972        assert!(!mgr.is_link_destination(&dest_hash));
1973    }
1974
1975    #[test]
1976    fn test_create_link() {
1977        let mut mgr = LinkManager::new();
1978        let mut rng = OsRng;
1979        let dest_hash = [0xDD; 16];
1980
1981        let sig_pub_bytes = [0xAA; 32]; // dummy sig pub for test
1982        let (link_id, actions) = mgr.create_link(&dest_hash, &sig_pub_bytes, 1, constants::MTU as u32, &mut rng);
1983        assert_ne!(link_id, [0u8; 16]);
1984        // Should have RegisterLinkDest + SendPacket
1985        assert_eq!(actions.len(), 2);
1986        assert!(matches!(actions[0], LinkManagerAction::RegisterLinkDest { .. }));
1987        assert!(matches!(actions[1], LinkManagerAction::SendPacket { .. }));
1988
1989        // Link should be in Pending state
1990        assert_eq!(mgr.link_state(&link_id), Some(LinkState::Pending));
1991    }
1992
1993    #[test]
1994    fn test_full_handshake_via_manager() {
1995        let mut rng = OsRng;
1996        let dest_hash = [0xDD; 16];
1997
1998        // Setup responder
1999        let mut responder_mgr = LinkManager::new();
2000        let (sig_prv, sig_pub_bytes) = make_dest_keys(&mut rng);
2001        responder_mgr.register_link_destination(dest_hash, sig_prv, sig_pub_bytes);
2002
2003        // Setup initiator
2004        let mut initiator_mgr = LinkManager::new();
2005
2006        // Step 1: Initiator creates link (needs dest signing pub key for LRPROOF verification)
2007        let (link_id, init_actions) = initiator_mgr.create_link(&dest_hash, &sig_pub_bytes, 1, constants::MTU as u32, &mut rng);
2008        assert_eq!(init_actions.len(), 2);
2009
2010        // Extract the LINKREQUEST packet raw bytes
2011        let linkrequest_raw = match &init_actions[1] {
2012            LinkManagerAction::SendPacket { raw, .. } => raw.clone(),
2013            _ => panic!("Expected SendPacket"),
2014        };
2015
2016        // Parse to get packet_hash and dest_hash
2017        let lr_packet = RawPacket::unpack(&linkrequest_raw).unwrap();
2018
2019        // Step 2: Responder handles LINKREQUEST
2020        let resp_actions = responder_mgr.handle_local_delivery(
2021            lr_packet.destination_hash,
2022            &linkrequest_raw,
2023            lr_packet.packet_hash,
2024            rns_core::transport::types::InterfaceId(0),
2025            &mut rng,
2026        );
2027        // Should have RegisterLinkDest + SendPacket(LRPROOF)
2028        assert!(resp_actions.len() >= 2);
2029        assert!(matches!(resp_actions[0], LinkManagerAction::RegisterLinkDest { .. }));
2030
2031        // Extract LRPROOF packet
2032        let lrproof_raw = match &resp_actions[1] {
2033            LinkManagerAction::SendPacket { raw, .. } => raw.clone(),
2034            _ => panic!("Expected SendPacket for LRPROOF"),
2035        };
2036
2037        // Step 3: Initiator handles LRPROOF
2038        let lrproof_packet = RawPacket::unpack(&lrproof_raw).unwrap();
2039        let init_actions2 = initiator_mgr.handle_local_delivery(
2040            lrproof_packet.destination_hash,
2041            &lrproof_raw,
2042            lrproof_packet.packet_hash,
2043            rns_core::transport::types::InterfaceId(0),
2044            &mut rng,
2045        );
2046
2047        // Should have LinkEstablished + SendPacket(LRRTT)
2048        let has_established = init_actions2.iter().any(|a| matches!(a, LinkManagerAction::LinkEstablished { .. }));
2049        assert!(has_established, "Initiator should emit LinkEstablished");
2050
2051        // Extract LRRTT
2052        let lrrtt_raw = init_actions2.iter().find_map(|a| match a {
2053            LinkManagerAction::SendPacket { raw, .. } => Some(raw.clone()),
2054            _ => None,
2055        }).expect("Should have LRRTT SendPacket");
2056
2057        // Step 4: Responder handles LRRTT
2058        let lrrtt_packet = RawPacket::unpack(&lrrtt_raw).unwrap();
2059        let resp_link_id = lrrtt_packet.destination_hash;
2060        let resp_actions2 = responder_mgr.handle_local_delivery(
2061            resp_link_id,
2062            &lrrtt_raw,
2063            lrrtt_packet.packet_hash,
2064            rns_core::transport::types::InterfaceId(0),
2065            &mut rng,
2066        );
2067
2068        let has_established = resp_actions2.iter().any(|a| matches!(a, LinkManagerAction::LinkEstablished { .. }));
2069        assert!(has_established, "Responder should emit LinkEstablished");
2070
2071        // Both sides should be Active
2072        assert_eq!(initiator_mgr.link_state(&link_id), Some(LinkState::Active));
2073        assert_eq!(responder_mgr.link_state(&link_id), Some(LinkState::Active));
2074
2075        // Both should have RTT
2076        assert!(initiator_mgr.link_rtt(&link_id).is_some());
2077        assert!(responder_mgr.link_rtt(&link_id).is_some());
2078    }
2079
2080    #[test]
2081    fn test_encrypted_data_exchange() {
2082        let mut rng = OsRng;
2083        let dest_hash = [0xDD; 16];
2084        let mut resp_mgr = LinkManager::new();
2085        let (sig_prv, sig_pub_bytes) = make_dest_keys(&mut rng);
2086        resp_mgr.register_link_destination(dest_hash, sig_prv, sig_pub_bytes);
2087        let mut init_mgr = LinkManager::new();
2088
2089        // Handshake
2090        let (link_id, init_actions) = init_mgr.create_link(&dest_hash, &sig_pub_bytes, 1, constants::MTU as u32, &mut rng);
2091        let lr_raw = extract_send_packet(&init_actions);
2092        let lr_pkt = RawPacket::unpack(&lr_raw).unwrap();
2093        let resp_actions = resp_mgr.handle_local_delivery(lr_pkt.destination_hash, &lr_raw, lr_pkt.packet_hash, rns_core::transport::types::InterfaceId(0), &mut rng);
2094        let lrproof_raw = extract_send_packet_at(&resp_actions, 1);
2095        let lrproof_pkt = RawPacket::unpack(&lrproof_raw).unwrap();
2096        let init_actions2 = init_mgr.handle_local_delivery(lrproof_pkt.destination_hash, &lrproof_raw, lrproof_pkt.packet_hash, rns_core::transport::types::InterfaceId(0), &mut rng);
2097        let lrrtt_raw = extract_any_send_packet(&init_actions2);
2098        let lrrtt_pkt = RawPacket::unpack(&lrrtt_raw).unwrap();
2099        resp_mgr.handle_local_delivery(lrrtt_pkt.destination_hash, &lrrtt_raw, lrrtt_pkt.packet_hash, rns_core::transport::types::InterfaceId(0), &mut rng);
2100
2101        // Send data from initiator to responder
2102        let actions = init_mgr.send_on_link(&link_id, b"hello link!", constants::CONTEXT_NONE, &mut rng);
2103        assert_eq!(actions.len(), 1);
2104        assert!(matches!(actions[0], LinkManagerAction::SendPacket { .. }));
2105    }
2106
2107    #[test]
2108    fn test_request_response() {
2109        let mut rng = OsRng;
2110        let dest_hash = [0xDD; 16];
2111        let mut resp_mgr = LinkManager::new();
2112        let (sig_prv, sig_pub_bytes) = make_dest_keys(&mut rng);
2113        resp_mgr.register_link_destination(dest_hash, sig_prv, sig_pub_bytes);
2114
2115        // Register a request handler
2116        resp_mgr.register_request_handler("/status", None, |_link_id, _path, _data, _remote| {
2117            Some(b"OK".to_vec())
2118        });
2119
2120        let mut init_mgr = LinkManager::new();
2121
2122        // Complete handshake
2123        let (link_id, init_actions) = init_mgr.create_link(&dest_hash, &sig_pub_bytes, 1, constants::MTU as u32, &mut rng);
2124        let lr_raw = extract_send_packet(&init_actions);
2125        let lr_pkt = RawPacket::unpack(&lr_raw).unwrap();
2126        let resp_actions = resp_mgr.handle_local_delivery(lr_pkt.destination_hash, &lr_raw, lr_pkt.packet_hash, rns_core::transport::types::InterfaceId(0), &mut rng);
2127        let lrproof_raw = extract_send_packet_at(&resp_actions, 1);
2128        let lrproof_pkt = RawPacket::unpack(&lrproof_raw).unwrap();
2129        let init_actions2 = init_mgr.handle_local_delivery(lrproof_pkt.destination_hash, &lrproof_raw, lrproof_pkt.packet_hash, rns_core::transport::types::InterfaceId(0), &mut rng);
2130        let lrrtt_raw = extract_any_send_packet(&init_actions2);
2131        let lrrtt_pkt = RawPacket::unpack(&lrrtt_raw).unwrap();
2132        resp_mgr.handle_local_delivery(lrrtt_pkt.destination_hash, &lrrtt_raw, lrrtt_pkt.packet_hash, rns_core::transport::types::InterfaceId(0), &mut rng);
2133
2134        // Send request from initiator
2135        let req_actions = init_mgr.send_request(&link_id, "/status", b"query", &mut rng);
2136        assert_eq!(req_actions.len(), 1);
2137
2138        // Deliver request to responder
2139        let req_raw = extract_send_packet_from(&req_actions);
2140        let req_pkt = RawPacket::unpack(&req_raw).unwrap();
2141        let resp_actions = resp_mgr.handle_local_delivery(
2142            req_pkt.destination_hash, &req_raw, req_pkt.packet_hash, rns_core::transport::types::InterfaceId(0), &mut rng,
2143        );
2144
2145        // Should have a response SendPacket
2146        let has_response = resp_actions.iter().any(|a| matches!(a, LinkManagerAction::SendPacket { .. }));
2147        assert!(has_response, "Handler should produce a response packet");
2148    }
2149
2150    #[test]
2151    fn test_request_acl_deny_unidentified() {
2152        let mut rng = OsRng;
2153        let dest_hash = [0xDD; 16];
2154        let mut resp_mgr = LinkManager::new();
2155        let (sig_prv, sig_pub_bytes) = make_dest_keys(&mut rng);
2156        resp_mgr.register_link_destination(dest_hash, sig_prv, sig_pub_bytes);
2157
2158        // Register handler with ACL (only allow specific identity)
2159        resp_mgr.register_request_handler(
2160            "/restricted",
2161            Some(vec![[0xAA; 16]]),
2162            |_link_id, _path, _data, _remote| Some(b"secret".to_vec()),
2163        );
2164
2165        let mut init_mgr = LinkManager::new();
2166
2167        // Complete handshake (without identification)
2168        let (link_id, init_actions) = init_mgr.create_link(&dest_hash, &sig_pub_bytes, 1, constants::MTU as u32, &mut rng);
2169        let lr_raw = extract_send_packet(&init_actions);
2170        let lr_pkt = RawPacket::unpack(&lr_raw).unwrap();
2171        let resp_actions = resp_mgr.handle_local_delivery(lr_pkt.destination_hash, &lr_raw, lr_pkt.packet_hash, rns_core::transport::types::InterfaceId(0), &mut rng);
2172        let lrproof_raw = extract_send_packet_at(&resp_actions, 1);
2173        let lrproof_pkt = RawPacket::unpack(&lrproof_raw).unwrap();
2174        let init_actions2 = init_mgr.handle_local_delivery(lrproof_pkt.destination_hash, &lrproof_raw, lrproof_pkt.packet_hash, rns_core::transport::types::InterfaceId(0), &mut rng);
2175        let lrrtt_raw = extract_any_send_packet(&init_actions2);
2176        let lrrtt_pkt = RawPacket::unpack(&lrrtt_raw).unwrap();
2177        resp_mgr.handle_local_delivery(lrrtt_pkt.destination_hash, &lrrtt_raw, lrrtt_pkt.packet_hash, rns_core::transport::types::InterfaceId(0), &mut rng);
2178
2179        // Send request without identifying first
2180        let req_actions = init_mgr.send_request(&link_id, "/restricted", b"query", &mut rng);
2181        let req_raw = extract_send_packet_from(&req_actions);
2182        let req_pkt = RawPacket::unpack(&req_raw).unwrap();
2183        let resp_actions = resp_mgr.handle_local_delivery(
2184            req_pkt.destination_hash, &req_raw, req_pkt.packet_hash, rns_core::transport::types::InterfaceId(0), &mut rng,
2185        );
2186
2187        // Should be denied — no response packet
2188        let has_response = resp_actions.iter().any(|a| matches!(a, LinkManagerAction::SendPacket { .. }));
2189        assert!(!has_response, "Unidentified peer should be denied");
2190    }
2191
2192    #[test]
2193    fn test_teardown_link() {
2194        let mut rng = OsRng;
2195        let dest_hash = [0xDD; 16];
2196        let mut mgr = LinkManager::new();
2197
2198        let dummy_sig = [0xAA; 32];
2199        let (link_id, _) = mgr.create_link(&dest_hash, &dummy_sig, 1, constants::MTU as u32, &mut rng);
2200        assert_eq!(mgr.link_count(), 1);
2201
2202        let actions = mgr.teardown_link(&link_id);
2203        let has_close = actions.iter().any(|a| matches!(a, LinkManagerAction::LinkClosed { .. }));
2204        assert!(has_close);
2205
2206        // After tick, closed links should be cleaned up
2207        let tick_actions = mgr.tick(&mut rng);
2208        let has_deregister = tick_actions.iter().any(|a| matches!(a, LinkManagerAction::DeregisterLinkDest { .. }));
2209        assert!(has_deregister);
2210        assert_eq!(mgr.link_count(), 0);
2211    }
2212
2213    #[test]
2214    fn test_identify_on_link() {
2215        let mut rng = OsRng;
2216        let dest_hash = [0xDD; 16];
2217        let mut resp_mgr = LinkManager::new();
2218        let (sig_prv, sig_pub_bytes) = make_dest_keys(&mut rng);
2219        resp_mgr.register_link_destination(dest_hash, sig_prv, sig_pub_bytes);
2220        let mut init_mgr = LinkManager::new();
2221
2222        // Complete handshake
2223        let (link_id, init_actions) = init_mgr.create_link(&dest_hash, &sig_pub_bytes, 1, constants::MTU as u32, &mut rng);
2224        let lr_raw = extract_send_packet(&init_actions);
2225        let lr_pkt = RawPacket::unpack(&lr_raw).unwrap();
2226        let resp_actions = resp_mgr.handle_local_delivery(lr_pkt.destination_hash, &lr_raw, lr_pkt.packet_hash, rns_core::transport::types::InterfaceId(0), &mut rng);
2227        let lrproof_raw = extract_send_packet_at(&resp_actions, 1);
2228        let lrproof_pkt = RawPacket::unpack(&lrproof_raw).unwrap();
2229        let init_actions2 = init_mgr.handle_local_delivery(lrproof_pkt.destination_hash, &lrproof_raw, lrproof_pkt.packet_hash, rns_core::transport::types::InterfaceId(0), &mut rng);
2230        let lrrtt_raw = extract_any_send_packet(&init_actions2);
2231        let lrrtt_pkt = RawPacket::unpack(&lrrtt_raw).unwrap();
2232        resp_mgr.handle_local_delivery(lrrtt_pkt.destination_hash, &lrrtt_raw, lrrtt_pkt.packet_hash, rns_core::transport::types::InterfaceId(0), &mut rng);
2233
2234        // Identify initiator to responder
2235        let identity = Identity::new(&mut rng);
2236        let id_actions = init_mgr.identify(&link_id, &identity, &mut rng);
2237        assert_eq!(id_actions.len(), 1);
2238
2239        // Deliver identify to responder
2240        let id_raw = extract_send_packet_from(&id_actions);
2241        let id_pkt = RawPacket::unpack(&id_raw).unwrap();
2242        let resp_actions = resp_mgr.handle_local_delivery(
2243            id_pkt.destination_hash, &id_raw, id_pkt.packet_hash, rns_core::transport::types::InterfaceId(0), &mut rng,
2244        );
2245
2246        let has_identified = resp_actions.iter().any(|a| matches!(a, LinkManagerAction::RemoteIdentified { .. }));
2247        assert!(has_identified, "Responder should emit RemoteIdentified");
2248    }
2249
2250    #[test]
2251    fn test_path_hash_computation() {
2252        let h1 = compute_path_hash("/status");
2253        let h2 = compute_path_hash("/path");
2254        assert_ne!(h1, h2);
2255
2256        // Deterministic
2257        assert_eq!(h1, compute_path_hash("/status"));
2258    }
2259
2260    #[test]
2261    fn test_link_count() {
2262        let mut mgr = LinkManager::new();
2263        let mut rng = OsRng;
2264
2265        assert_eq!(mgr.link_count(), 0);
2266
2267        let dummy_sig = [0xAA; 32];
2268        mgr.create_link(&[0x11; 16], &dummy_sig, 1, constants::MTU as u32, &mut rng);
2269        assert_eq!(mgr.link_count(), 1);
2270
2271        mgr.create_link(&[0x22; 16], &dummy_sig, 1, constants::MTU as u32, &mut rng);
2272        assert_eq!(mgr.link_count(), 2);
2273    }
2274
2275    // --- Test helpers ---
2276
2277    fn extract_send_packet(actions: &[LinkManagerAction]) -> Vec<u8> {
2278        extract_send_packet_at(actions, actions.len() - 1)
2279    }
2280
2281    fn extract_send_packet_at(actions: &[LinkManagerAction], idx: usize) -> Vec<u8> {
2282        match &actions[idx] {
2283            LinkManagerAction::SendPacket { raw, .. } => raw.clone(),
2284            other => panic!("Expected SendPacket at index {}, got {:?}", idx, other),
2285        }
2286    }
2287
2288    fn extract_any_send_packet(actions: &[LinkManagerAction]) -> Vec<u8> {
2289        actions.iter().find_map(|a| match a {
2290            LinkManagerAction::SendPacket { raw, .. } => Some(raw.clone()),
2291            _ => None,
2292        }).expect("Expected at least one SendPacket action")
2293    }
2294
2295    fn extract_send_packet_from(actions: &[LinkManagerAction]) -> Vec<u8> {
2296        extract_any_send_packet(actions)
2297    }
2298
2299    /// Set up two linked managers with an active link.
2300    /// Returns (initiator_mgr, responder_mgr, link_id).
2301    fn setup_active_link() -> (LinkManager, LinkManager, LinkId) {
2302        let mut rng = OsRng;
2303        let dest_hash = [0xDD; 16];
2304        let mut resp_mgr = LinkManager::new();
2305        let (sig_prv, sig_pub_bytes) = make_dest_keys(&mut rng);
2306        resp_mgr.register_link_destination(dest_hash, sig_prv, sig_pub_bytes);
2307        let mut init_mgr = LinkManager::new();
2308
2309        let (link_id, init_actions) = init_mgr.create_link(&dest_hash, &sig_pub_bytes, 1, constants::MTU as u32, &mut rng);
2310        let lr_raw = extract_send_packet(&init_actions);
2311        let lr_pkt = RawPacket::unpack(&lr_raw).unwrap();
2312        let resp_actions = resp_mgr.handle_local_delivery(
2313            lr_pkt.destination_hash, &lr_raw, lr_pkt.packet_hash, rns_core::transport::types::InterfaceId(0), &mut rng,
2314        );
2315        let lrproof_raw = extract_send_packet_at(&resp_actions, 1);
2316        let lrproof_pkt = RawPacket::unpack(&lrproof_raw).unwrap();
2317        let init_actions2 = init_mgr.handle_local_delivery(
2318            lrproof_pkt.destination_hash, &lrproof_raw, lrproof_pkt.packet_hash, rns_core::transport::types::InterfaceId(0), &mut rng,
2319        );
2320        let lrrtt_raw = extract_any_send_packet(&init_actions2);
2321        let lrrtt_pkt = RawPacket::unpack(&lrrtt_raw).unwrap();
2322        resp_mgr.handle_local_delivery(
2323            lrrtt_pkt.destination_hash, &lrrtt_raw, lrrtt_pkt.packet_hash, rns_core::transport::types::InterfaceId(0), &mut rng,
2324        );
2325
2326        assert_eq!(init_mgr.link_state(&link_id), Some(LinkState::Active));
2327        assert_eq!(resp_mgr.link_state(&link_id), Some(LinkState::Active));
2328
2329        (init_mgr, resp_mgr, link_id)
2330    }
2331
2332    // ====================================================================
2333    // Phase 8a: Resource wiring tests
2334    // ====================================================================
2335
2336    #[test]
2337    fn test_resource_strategy_default() {
2338        let mut mgr = LinkManager::new();
2339        let mut rng = OsRng;
2340        let dummy_sig = [0xAA; 32];
2341        let (link_id, _) = mgr.create_link(&[0x11; 16], &dummy_sig, 1, constants::MTU as u32, &mut rng);
2342
2343        // Default strategy is AcceptNone
2344        let link = mgr.links.get(&link_id).unwrap();
2345        assert_eq!(link.resource_strategy, ResourceStrategy::AcceptNone);
2346    }
2347
2348    #[test]
2349    fn test_set_resource_strategy() {
2350        let mut mgr = LinkManager::new();
2351        let mut rng = OsRng;
2352        let dummy_sig = [0xAA; 32];
2353        let (link_id, _) = mgr.create_link(&[0x11; 16], &dummy_sig, 1, constants::MTU as u32, &mut rng);
2354
2355        mgr.set_resource_strategy(&link_id, ResourceStrategy::AcceptAll);
2356        assert_eq!(mgr.links.get(&link_id).unwrap().resource_strategy, ResourceStrategy::AcceptAll);
2357
2358        mgr.set_resource_strategy(&link_id, ResourceStrategy::AcceptApp);
2359        assert_eq!(mgr.links.get(&link_id).unwrap().resource_strategy, ResourceStrategy::AcceptApp);
2360    }
2361
2362    #[test]
2363    fn test_send_resource_on_active_link() {
2364        let (mut init_mgr, _resp_mgr, link_id) = setup_active_link();
2365        let mut rng = OsRng;
2366
2367        // Send resource data
2368        let data = vec![0xAB; 100]; // small enough for a single part
2369        let actions = init_mgr.send_resource(&link_id, &data, None, &mut rng);
2370
2371        // Should produce at least a SendPacket (advertisement)
2372        let has_send = actions.iter().any(|a| matches!(a, LinkManagerAction::SendPacket { .. }));
2373        assert!(has_send, "send_resource should emit advertisement SendPacket");
2374    }
2375
2376    #[test]
2377    fn test_send_resource_on_inactive_link() {
2378        let mut mgr = LinkManager::new();
2379        let mut rng = OsRng;
2380        let dummy_sig = [0xAA; 32];
2381        let (link_id, _) = mgr.create_link(&[0x11; 16], &dummy_sig, 1, constants::MTU as u32, &mut rng);
2382
2383        // Link is Pending, not Active
2384        let actions = mgr.send_resource(&link_id, b"data", None, &mut rng);
2385        assert!(actions.is_empty(), "Cannot send resource on inactive link");
2386    }
2387
2388    #[test]
2389    fn test_resource_adv_rejected_by_accept_none() {
2390        let (mut init_mgr, mut resp_mgr, link_id) = setup_active_link();
2391        let mut rng = OsRng;
2392
2393        // Responder uses default AcceptNone strategy
2394        // Send resource from initiator
2395        let data = vec![0xCD; 100];
2396        let adv_actions = init_mgr.send_resource(&link_id, &data, None, &mut rng);
2397
2398        // Deliver advertisement to responder
2399        for action in &adv_actions {
2400            if let LinkManagerAction::SendPacket { raw, .. } = action {
2401                let pkt = RawPacket::unpack(raw).unwrap();
2402                let resp_actions = resp_mgr.handle_local_delivery(
2403                    pkt.destination_hash, raw, pkt.packet_hash, rns_core::transport::types::InterfaceId(0), &mut rng,
2404                );
2405                // AcceptNone: should not produce ResourceReceived, may produce SendPacket (RCL)
2406                let has_resource_received = resp_actions.iter().any(|a|
2407                    matches!(a, LinkManagerAction::ResourceReceived { .. })
2408                );
2409                assert!(!has_resource_received, "AcceptNone should not accept resource");
2410            }
2411        }
2412    }
2413
2414    #[test]
2415    fn test_resource_adv_accepted_by_accept_all() {
2416        let (mut init_mgr, mut resp_mgr, link_id) = setup_active_link();
2417        let mut rng = OsRng;
2418
2419        // Set responder to AcceptAll
2420        resp_mgr.set_resource_strategy(&link_id, ResourceStrategy::AcceptAll);
2421
2422        // Send resource from initiator
2423        let data = vec![0xCD; 100];
2424        let adv_actions = init_mgr.send_resource(&link_id, &data, None, &mut rng);
2425
2426        // Deliver advertisement to responder
2427        for action in &adv_actions {
2428            if let LinkManagerAction::SendPacket { raw, .. } = action {
2429                let pkt = RawPacket::unpack(raw).unwrap();
2430                let resp_actions = resp_mgr.handle_local_delivery(
2431                    pkt.destination_hash, raw, pkt.packet_hash, rns_core::transport::types::InterfaceId(0), &mut rng,
2432                );
2433                // AcceptAll: should accept and produce a SendPacket (request for parts)
2434                let has_send = resp_actions.iter().any(|a|
2435                    matches!(a, LinkManagerAction::SendPacket { .. })
2436                );
2437                assert!(has_send, "AcceptAll should accept and request parts");
2438            }
2439        }
2440    }
2441
2442    #[test]
2443    fn test_resource_accept_app_query() {
2444        let (mut init_mgr, mut resp_mgr, link_id) = setup_active_link();
2445        let mut rng = OsRng;
2446
2447        // Set responder to AcceptApp
2448        resp_mgr.set_resource_strategy(&link_id, ResourceStrategy::AcceptApp);
2449
2450        // Send resource from initiator
2451        let data = vec![0xCD; 100];
2452        let adv_actions = init_mgr.send_resource(&link_id, &data, None, &mut rng);
2453
2454        // Deliver advertisement to responder
2455        let mut got_query = false;
2456        for action in &adv_actions {
2457            if let LinkManagerAction::SendPacket { raw, .. } = action {
2458                let pkt = RawPacket::unpack(raw).unwrap();
2459                let resp_actions = resp_mgr.handle_local_delivery(
2460                    pkt.destination_hash, raw, pkt.packet_hash, rns_core::transport::types::InterfaceId(0), &mut rng,
2461                );
2462                for a in &resp_actions {
2463                    if matches!(a, LinkManagerAction::ResourceAcceptQuery { .. }) {
2464                        got_query = true;
2465                    }
2466                }
2467            }
2468        }
2469        assert!(got_query, "AcceptApp should emit ResourceAcceptQuery");
2470    }
2471
2472    #[test]
2473    fn test_resource_accept_app_accept() {
2474        let (mut init_mgr, mut resp_mgr, link_id) = setup_active_link();
2475        let mut rng = OsRng;
2476
2477        resp_mgr.set_resource_strategy(&link_id, ResourceStrategy::AcceptApp);
2478
2479        let data = vec![0xCD; 100];
2480        let adv_actions = init_mgr.send_resource(&link_id, &data, None, &mut rng);
2481
2482        for action in &adv_actions {
2483            if let LinkManagerAction::SendPacket { raw, .. } = action {
2484                let pkt = RawPacket::unpack(raw).unwrap();
2485                let resp_actions = resp_mgr.handle_local_delivery(
2486                    pkt.destination_hash, raw, pkt.packet_hash, rns_core::transport::types::InterfaceId(0), &mut rng,
2487                );
2488                for a in &resp_actions {
2489                    if let LinkManagerAction::ResourceAcceptQuery { link_id: lid, resource_hash, .. } = a {
2490                        // Accept the resource
2491                        let accept_actions = resp_mgr.accept_resource(lid, resource_hash, true, &mut rng);
2492                        // Should produce a SendPacket (request for parts)
2493                        let has_send = accept_actions.iter().any(|a|
2494                            matches!(a, LinkManagerAction::SendPacket { .. })
2495                        );
2496                        assert!(has_send, "Accepting resource should produce request for parts");
2497                    }
2498                }
2499            }
2500        }
2501    }
2502
2503    #[test]
2504    fn test_resource_accept_app_reject() {
2505        let (mut init_mgr, mut resp_mgr, link_id) = setup_active_link();
2506        let mut rng = OsRng;
2507
2508        resp_mgr.set_resource_strategy(&link_id, ResourceStrategy::AcceptApp);
2509
2510        let data = vec![0xCD; 100];
2511        let adv_actions = init_mgr.send_resource(&link_id, &data, None, &mut rng);
2512
2513        for action in &adv_actions {
2514            if let LinkManagerAction::SendPacket { raw, .. } = action {
2515                let pkt = RawPacket::unpack(raw).unwrap();
2516                let resp_actions = resp_mgr.handle_local_delivery(
2517                    pkt.destination_hash, raw, pkt.packet_hash, rns_core::transport::types::InterfaceId(0), &mut rng,
2518                );
2519                for a in &resp_actions {
2520                    if let LinkManagerAction::ResourceAcceptQuery { link_id: lid, resource_hash, .. } = a {
2521                        // Reject the resource
2522                        let reject_actions = resp_mgr.accept_resource(lid, resource_hash, false, &mut rng);
2523                        // Rejecting should send a cancel and not request parts
2524                        // No ResourceReceived should appear
2525                        let has_resource_received = reject_actions.iter().any(|a|
2526                            matches!(a, LinkManagerAction::ResourceReceived { .. })
2527                        );
2528                        assert!(!has_resource_received);
2529                    }
2530                }
2531            }
2532        }
2533    }
2534
2535    #[test]
2536    fn test_resource_full_transfer() {
2537        let (mut init_mgr, mut resp_mgr, link_id) = setup_active_link();
2538        let mut rng = OsRng;
2539
2540        // Set responder to AcceptAll
2541        resp_mgr.set_resource_strategy(&link_id, ResourceStrategy::AcceptAll);
2542
2543        // Small data (fits in single SDU)
2544        let original_data = b"Hello, Resource Transfer!".to_vec();
2545        let adv_actions = init_mgr.send_resource(&link_id, &original_data, None, &mut rng);
2546
2547        // Drive the full transfer protocol between the two managers.
2548        // Tag each SendPacket with its source ('i' = initiator, 'r' = responder).
2549        let mut pending: Vec<(char, LinkManagerAction)> = adv_actions.into_iter()
2550            .map(|a| ('i', a))
2551            .collect();
2552        let mut rounds = 0;
2553        let max_rounds = 50;
2554        let mut resource_received = false;
2555        let mut sender_completed = false;
2556
2557        while !pending.is_empty() && rounds < max_rounds {
2558            rounds += 1;
2559            let mut next: Vec<(char, LinkManagerAction)> = Vec::new();
2560
2561            for (source, action) in pending.drain(..) {
2562                if let LinkManagerAction::SendPacket { raw, .. } = action {
2563                    let pkt = RawPacket::unpack(&raw).unwrap();
2564
2565                    // Deliver only to the OTHER side
2566                    let target_actions = if source == 'i' {
2567                        resp_mgr.handle_local_delivery(
2568                            pkt.destination_hash, &raw, pkt.packet_hash, rns_core::transport::types::InterfaceId(0), &mut rng,
2569                        )
2570                    } else {
2571                        init_mgr.handle_local_delivery(
2572                            pkt.destination_hash, &raw, pkt.packet_hash, rns_core::transport::types::InterfaceId(0), &mut rng,
2573                        )
2574                    };
2575
2576                    let target_source = if source == 'i' { 'r' } else { 'i' };
2577                    for a in &target_actions {
2578                        match a {
2579                            LinkManagerAction::ResourceReceived { data, .. } => {
2580                                assert_eq!(*data, original_data);
2581                                resource_received = true;
2582                            }
2583                            LinkManagerAction::ResourceCompleted { .. } => {
2584                                sender_completed = true;
2585                            }
2586                            _ => {}
2587                        }
2588                    }
2589                    next.extend(target_actions.into_iter().map(|a| (target_source, a)));
2590                }
2591            }
2592            pending = next;
2593        }
2594
2595        assert!(resource_received, "Responder should receive resource data (rounds={})", rounds);
2596        assert!(sender_completed, "Sender should get completion proof (rounds={})", rounds);
2597    }
2598
2599    #[test]
2600    fn test_resource_cancel_icl() {
2601        let (mut init_mgr, mut resp_mgr, link_id) = setup_active_link();
2602        let mut rng = OsRng;
2603
2604        resp_mgr.set_resource_strategy(&link_id, ResourceStrategy::AcceptAll);
2605
2606        // Use large data so transfer is multi-part
2607        let data = vec![0xAB; 2000];
2608        let adv_actions = init_mgr.send_resource(&link_id, &data, None, &mut rng);
2609
2610        // Deliver advertisement — responder accepts and sends request
2611        for action in &adv_actions {
2612            if let LinkManagerAction::SendPacket { raw, .. } = action {
2613                let pkt = RawPacket::unpack(raw).unwrap();
2614                resp_mgr.handle_local_delivery(
2615                    pkt.destination_hash, raw, pkt.packet_hash, rns_core::transport::types::InterfaceId(0), &mut rng,
2616                );
2617            }
2618        }
2619
2620        // Verify there are incoming resources on the responder
2621        assert!(!resp_mgr.links.get(&link_id).unwrap().incoming_resources.is_empty());
2622
2623        // Simulate ICL (cancel from initiator side) by calling handle_resource_icl
2624        let icl_actions = resp_mgr.handle_resource_icl(&link_id);
2625
2626        // Should have resource failed
2627        let has_failed = icl_actions.iter().any(|a| matches!(a, LinkManagerAction::ResourceFailed { .. }));
2628        assert!(has_failed, "ICL should produce ResourceFailed");
2629    }
2630
2631    #[test]
2632    fn test_resource_cancel_rcl() {
2633        let (mut init_mgr, _resp_mgr, link_id) = setup_active_link();
2634        let mut rng = OsRng;
2635
2636        // Create a resource sender
2637        let data = vec![0xAB; 2000];
2638        init_mgr.send_resource(&link_id, &data, None, &mut rng);
2639
2640        // Verify there are outgoing resources
2641        assert!(!init_mgr.links.get(&link_id).unwrap().outgoing_resources.is_empty());
2642
2643        // Simulate RCL (cancel from receiver side)
2644        let rcl_actions = init_mgr.handle_resource_rcl(&link_id);
2645
2646        let has_failed = rcl_actions.iter().any(|a| matches!(a, LinkManagerAction::ResourceFailed { .. }));
2647        assert!(has_failed, "RCL should produce ResourceFailed");
2648    }
2649
2650    #[test]
2651    fn test_resource_tick_cleans_up() {
2652        let (mut init_mgr, _resp_mgr, link_id) = setup_active_link();
2653        let mut rng = OsRng;
2654
2655        let data = vec![0xAB; 100];
2656        init_mgr.send_resource(&link_id, &data, None, &mut rng);
2657
2658        assert!(!init_mgr.links.get(&link_id).unwrap().outgoing_resources.is_empty());
2659
2660        // Cancel the sender to make it Complete
2661        init_mgr.handle_resource_rcl(&link_id);
2662
2663        // Tick should clean up completed resources
2664        init_mgr.tick(&mut rng);
2665
2666        assert!(init_mgr.links.get(&link_id).unwrap().outgoing_resources.is_empty(),
2667            "Tick should clean up completed/failed outgoing resources");
2668    }
2669
2670    #[test]
2671    fn test_build_link_packet() {
2672        let (init_mgr, _resp_mgr, link_id) = setup_active_link();
2673
2674        let actions = init_mgr.build_link_packet(&link_id, constants::CONTEXT_RESOURCE, b"test data");
2675        assert_eq!(actions.len(), 1);
2676        if let LinkManagerAction::SendPacket { raw, dest_type, .. } = &actions[0] {
2677            let pkt = RawPacket::unpack(raw).unwrap();
2678            assert_eq!(pkt.context, constants::CONTEXT_RESOURCE);
2679            assert_eq!(*dest_type, constants::DESTINATION_LINK);
2680        } else {
2681            panic!("Expected SendPacket");
2682        }
2683    }
2684
2685    // ====================================================================
2686    // Phase 8b: Channel message & data callback tests
2687    // ====================================================================
2688
2689    #[test]
2690    fn test_channel_message_delivery() {
2691        let (mut init_mgr, mut resp_mgr, link_id) = setup_active_link();
2692        let mut rng = OsRng;
2693
2694        // Send channel message from initiator
2695        let chan_actions = init_mgr.send_channel_message(&link_id, 42, b"channel data", &mut rng);
2696        assert!(!chan_actions.is_empty());
2697
2698        // Deliver to responder
2699        let mut got_channel_msg = false;
2700        for action in &chan_actions {
2701            if let LinkManagerAction::SendPacket { raw, .. } = action {
2702                let pkt = RawPacket::unpack(raw).unwrap();
2703                let resp_actions = resp_mgr.handle_local_delivery(
2704                    pkt.destination_hash, raw, pkt.packet_hash, rns_core::transport::types::InterfaceId(0), &mut rng,
2705                );
2706                for a in &resp_actions {
2707                    if let LinkManagerAction::ChannelMessageReceived { msgtype, payload, .. } = a {
2708                        assert_eq!(*msgtype, 42);
2709                        assert_eq!(*payload, b"channel data");
2710                        got_channel_msg = true;
2711                    }
2712                }
2713            }
2714        }
2715        assert!(got_channel_msg, "Responder should receive channel message");
2716    }
2717
2718    #[test]
2719    fn test_generic_link_data_delivery() {
2720        let (mut init_mgr, mut resp_mgr, link_id) = setup_active_link();
2721        let mut rng = OsRng;
2722
2723        // Send generic data with a custom context
2724        let actions = init_mgr.send_on_link(&link_id, b"raw stuff", 0x42, &mut rng);
2725        assert_eq!(actions.len(), 1);
2726
2727        // Deliver to responder
2728        let raw = extract_any_send_packet(&actions);
2729        let pkt = RawPacket::unpack(&raw).unwrap();
2730        let resp_actions = resp_mgr.handle_local_delivery(
2731            pkt.destination_hash, &raw, pkt.packet_hash, rns_core::transport::types::InterfaceId(0), &mut rng,
2732        );
2733
2734        let has_data = resp_actions.iter().any(|a|
2735            matches!(a, LinkManagerAction::LinkDataReceived { context: 0x42, .. })
2736        );
2737        assert!(has_data, "Responder should receive LinkDataReceived for unknown context");
2738    }
2739
2740    #[test]
2741    fn test_response_delivery() {
2742        let (mut init_mgr, mut resp_mgr, link_id) = setup_active_link();
2743        let mut rng = OsRng;
2744
2745        // Register handler on responder
2746        resp_mgr.register_request_handler("/echo", None, |_link_id, _path, data, _remote| {
2747            Some(data.to_vec())
2748        });
2749
2750        // Send request from initiator
2751        let req_actions = init_mgr.send_request(&link_id, "/echo", b"\xc0", &mut rng);  // msgpack nil
2752        assert!(!req_actions.is_empty());
2753
2754        // Deliver request to responder — should produce response
2755        let req_raw = extract_any_send_packet(&req_actions);
2756        let req_pkt = RawPacket::unpack(&req_raw).unwrap();
2757        let resp_actions = resp_mgr.handle_local_delivery(
2758            req_pkt.destination_hash, &req_raw, req_pkt.packet_hash, rns_core::transport::types::InterfaceId(0), &mut rng,
2759        );
2760        let has_resp_send = resp_actions.iter().any(|a| matches!(a, LinkManagerAction::SendPacket { .. }));
2761        assert!(has_resp_send, "Handler should produce response");
2762
2763        // Deliver response back to initiator
2764        let resp_raw = extract_any_send_packet(&resp_actions);
2765        let resp_pkt = RawPacket::unpack(&resp_raw).unwrap();
2766        let init_actions = init_mgr.handle_local_delivery(
2767            resp_pkt.destination_hash, &resp_raw, resp_pkt.packet_hash, rns_core::transport::types::InterfaceId(0), &mut rng,
2768        );
2769
2770        let has_response_received = init_actions.iter().any(|a|
2771            matches!(a, LinkManagerAction::ResponseReceived { .. })
2772        );
2773        assert!(has_response_received, "Initiator should receive ResponseReceived");
2774    }
2775
2776    #[test]
2777    fn test_send_channel_message_on_no_channel() {
2778        let mut mgr = LinkManager::new();
2779        let mut rng = OsRng;
2780        let dummy_sig = [0xAA; 32];
2781        let (link_id, _) = mgr.create_link(&[0x11; 16], &dummy_sig, 1, constants::MTU as u32, &mut rng);
2782
2783        // Link is Pending (no channel), should return empty
2784        let actions = mgr.send_channel_message(&link_id, 1, b"test", &mut rng);
2785        assert!(actions.is_empty(), "No channel on pending link");
2786    }
2787
2788    #[test]
2789    fn test_send_on_link_requires_active() {
2790        let mut mgr = LinkManager::new();
2791        let mut rng = OsRng;
2792        let dummy_sig = [0xAA; 32];
2793        let (link_id, _) = mgr.create_link(&[0x11; 16], &dummy_sig, 1, constants::MTU as u32, &mut rng);
2794
2795        let actions = mgr.send_on_link(&link_id, b"test", constants::CONTEXT_NONE, &mut rng);
2796        assert!(actions.is_empty(), "Cannot send on pending link");
2797    }
2798
2799    #[test]
2800    fn test_send_on_link_unknown_link() {
2801        let mgr = LinkManager::new();
2802        let mut rng = OsRng;
2803
2804        let actions = mgr.send_on_link(&[0xFF; 16], b"test", constants::CONTEXT_NONE, &mut rng);
2805        assert!(actions.is_empty());
2806    }
2807
2808    #[test]
2809    fn test_resource_full_transfer_large() {
2810        let (mut init_mgr, mut resp_mgr, link_id) = setup_active_link();
2811        let mut rng = OsRng;
2812
2813        resp_mgr.set_resource_strategy(&link_id, ResourceStrategy::AcceptAll);
2814
2815        // Multi-part data (larger than a single SDU of 464 bytes)
2816        let original_data: Vec<u8> = (0..2000u32).map(|i| {
2817            let pos = i as usize;
2818            (pos ^ (pos >> 8) ^ (pos >> 16)) as u8
2819        }).collect();
2820
2821        let adv_actions = init_mgr.send_resource(&link_id, &original_data, None, &mut rng);
2822
2823        let mut pending: Vec<(char, LinkManagerAction)> = adv_actions.into_iter()
2824            .map(|a| ('i', a))
2825            .collect();
2826        let mut rounds = 0;
2827        let max_rounds = 200;
2828        let mut resource_received = false;
2829        let mut sender_completed = false;
2830
2831        while !pending.is_empty() && rounds < max_rounds {
2832            rounds += 1;
2833            let mut next: Vec<(char, LinkManagerAction)> = Vec::new();
2834
2835            for (source, action) in pending.drain(..) {
2836                if let LinkManagerAction::SendPacket { raw, .. } = action {
2837                    let pkt = match RawPacket::unpack(&raw) {
2838                        Ok(p) => p,
2839                        Err(_) => continue,
2840                    };
2841
2842                    let target_actions = if source == 'i' {
2843                        resp_mgr.handle_local_delivery(
2844                            pkt.destination_hash, &raw, pkt.packet_hash, rns_core::transport::types::InterfaceId(0), &mut rng,
2845                        )
2846                    } else {
2847                        init_mgr.handle_local_delivery(
2848                            pkt.destination_hash, &raw, pkt.packet_hash, rns_core::transport::types::InterfaceId(0), &mut rng,
2849                        )
2850                    };
2851
2852                    let target_source = if source == 'i' { 'r' } else { 'i' };
2853                    for a in &target_actions {
2854                        match a {
2855                            LinkManagerAction::ResourceReceived { data, .. } => {
2856                                assert_eq!(*data, original_data);
2857                                resource_received = true;
2858                            }
2859                            LinkManagerAction::ResourceCompleted { .. } => {
2860                                sender_completed = true;
2861                            }
2862                            _ => {}
2863                        }
2864                    }
2865                    next.extend(target_actions.into_iter().map(|a| (target_source, a)));
2866                }
2867            }
2868            pending = next;
2869        }
2870
2871        assert!(resource_received, "Should receive large resource (rounds={})", rounds);
2872        assert!(sender_completed, "Sender should complete (rounds={})", rounds);
2873    }
2874
2875    #[test]
2876    fn test_process_resource_actions_mapping() {
2877        let (init_mgr, _resp_mgr, link_id) = setup_active_link();
2878        let mut rng = OsRng;
2879
2880        // Test that various ResourceActions map to correct LinkManagerActions
2881        let actions = vec![
2882            ResourceAction::DataReceived { data: vec![1, 2, 3], metadata: Some(vec![4, 5]) },
2883            ResourceAction::Completed,
2884            ResourceAction::Failed(rns_core::resource::ResourceError::Timeout),
2885            ResourceAction::ProgressUpdate { received: 10, total: 20 },
2886        ];
2887
2888        let result = init_mgr.process_resource_actions(&link_id, actions, &mut rng);
2889
2890        assert!(matches!(result[0], LinkManagerAction::ResourceReceived { .. }));
2891        assert!(matches!(result[1], LinkManagerAction::ResourceCompleted { .. }));
2892        assert!(matches!(result[2], LinkManagerAction::ResourceFailed { .. }));
2893        assert!(matches!(result[3], LinkManagerAction::ResourceProgress { received: 10, total: 20, .. }));
2894    }
2895
2896    #[test]
2897    fn test_link_state_empty() {
2898        let mgr = LinkManager::new();
2899        let fake_id = [0xAA; 16];
2900        assert!(mgr.link_state(&fake_id).is_none());
2901    }
2902}