Skip to main content

rns_net/
link_manager.rs

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