Skip to main content

rns_net/
link_manager.rs

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