Skip to main content

rns_net/common/
link_manager.rs

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