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