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