Skip to main content

rns_core/link/
mod.rs

1pub mod crypto;
2pub mod handshake;
3pub mod identify;
4pub mod keepalive;
5pub mod types;
6
7use alloc::vec::Vec;
8
9use rns_crypto::ed25519::{Ed25519PrivateKey, Ed25519PublicKey};
10use rns_crypto::token::Token;
11use rns_crypto::x25519::X25519PrivateKey;
12use rns_crypto::Rng;
13
14use crate::constants::{
15    LINK_ECPUBSIZE, LINK_ESTABLISHMENT_TIMEOUT_PER_HOP, LINK_KEEPALIVE_MAX, MTU, PATHFINDER_M,
16};
17
18pub use types::{LinkAction, LinkError, LinkId, LinkMode, LinkState, TeardownReason};
19
20use crypto::{create_session_token, link_decrypt, link_encrypt};
21use handshake::{
22    build_linkrequest_data, compute_link_id, pack_rtt, parse_linkrequest_data,
23    perform_key_exchange, unpack_rtt, validate_lrproof,
24};
25use keepalive::{
26    compute_establishment_timeout, compute_keepalive, compute_stale_time, is_establishment_timeout,
27    should_go_stale, should_send_keepalive,
28};
29
30/// The Link Engine manages a single link's lifecycle.
31///
32/// It follows the action-queue model: methods return `Vec<LinkAction>` instead
33/// of performing I/O directly. The caller dispatches actions.
34pub struct LinkEngine {
35    link_id: LinkId,
36    state: LinkState,
37    is_initiator: bool,
38    mode: LinkMode,
39
40    // Ephemeral keys
41    prv: X25519PrivateKey,
42    sig_prv: Ed25519PrivateKey,
43
44    // Peer keys
45    peer_pub_bytes: Option<[u8; 32]>,
46    peer_sig_pub_bytes: Option<[u8; 32]>,
47
48    // Session crypto
49    derived_key: Option<Vec<u8>>,
50    token: Option<Token>,
51
52    // Timing
53    request_time: f64,
54    activated_at: Option<f64>,
55    last_inbound: f64,
56    last_outbound: f64,
57    last_keepalive: f64,
58    last_proof: f64,
59    rtt: Option<f64>,
60    keepalive_interval: f64,
61    stale_time: f64,
62    establishment_timeout: f64,
63    expected_hops: u8,
64    /// Time of the first authenticated terminus hop rebalance.
65    rebalanced_at: Option<f64>,
66
67    // Counts packed packet data/ciphertext, matching Python Link accounting.
68    tx_packets: u64,
69    rx_packets: u64,
70    tx_bytes: u64,
71    rx_bytes: u64,
72
73    // Identity
74    remote_identity: Option<([u8; 16], [u8; 64])>,
75    destination_hash: [u8; 16],
76
77    // MDU
78    mtu: u32,
79    mdu: usize,
80}
81
82impl LinkEngine {
83    /// Create a new initiator-side link engine.
84    ///
85    /// Returns `(engine, linkrequest_data)` — the caller must pack linkrequest_data
86    /// into a LINKREQUEST packet and send it.
87    pub fn new_initiator(
88        dest_hash: &[u8; 16],
89        hops: u8,
90        mode: LinkMode,
91        mtu: Option<u32>,
92        now: f64,
93        rng: &mut dyn Rng,
94    ) -> (Self, Vec<u8>) {
95        let prv = X25519PrivateKey::generate(rng);
96        let pub_bytes = prv.public_key().public_bytes();
97        let sig_prv = Ed25519PrivateKey::generate(rng);
98        let sig_pub_bytes = sig_prv.public_key().public_bytes();
99
100        let request_data = build_linkrequest_data(&pub_bytes, &sig_pub_bytes, mtu, mode);
101
102        let link_mtu = effective_link_mtu(mtu);
103
104        let engine = LinkEngine {
105            link_id: [0u8; 16], // will be set after packet is built
106            state: LinkState::Pending,
107            is_initiator: true,
108            mode,
109            prv,
110            sig_prv,
111            peer_pub_bytes: None,
112            peer_sig_pub_bytes: None,
113            derived_key: None,
114            token: None,
115            request_time: now,
116            activated_at: None,
117            last_inbound: now,
118            last_outbound: now,
119            last_keepalive: now,
120            last_proof: 0.0,
121            rtt: None,
122            keepalive_interval: LINK_KEEPALIVE_MAX,
123            stale_time: LINK_KEEPALIVE_MAX * 2.0,
124            establishment_timeout: compute_establishment_timeout(
125                LINK_ESTABLISHMENT_TIMEOUT_PER_HOP,
126                hops,
127            ),
128            expected_hops: if hops < PATHFINDER_M {
129                hops
130            } else {
131                PATHFINDER_M
132            },
133            rebalanced_at: None,
134            tx_packets: 0,
135            rx_packets: 0,
136            tx_bytes: 0,
137            rx_bytes: 0,
138            remote_identity: None,
139            destination_hash: *dest_hash,
140            mtu: link_mtu,
141            mdu: compute_mdu(link_mtu as usize),
142        };
143
144        (engine, request_data)
145    }
146
147    /// Set link_id from the hashable part of the packed LINKREQUEST packet.
148    ///
149    /// Must be called after packing the LINKREQUEST packet (since link_id depends
150    /// on the packet's hashable part).
151    pub fn set_link_id_from_hashable(&mut self, hashable_part: &[u8], data_len: usize) {
152        let extra = data_len.saturating_sub(LINK_ECPUBSIZE);
153        self.link_id = compute_link_id(hashable_part, extra);
154    }
155
156    /// Create a new responder-side link engine from an incoming LINKREQUEST.
157    ///
158    /// `owner_sig_prv` / `owner_sig_pub` are the destination's signing keys.
159    /// Returns `(engine, actions)` where actions include the LRPROOF data to send.
160    #[allow(clippy::too_many_arguments)]
161    pub fn new_responder(
162        owner_sig_prv: &Ed25519PrivateKey,
163        owner_sig_pub_bytes: &[u8; 32],
164        linkrequest_data: &[u8],
165        hashable_part: &[u8],
166        dest_hash: &[u8; 16],
167        hops: u8,
168        now: f64,
169        rng: &mut dyn Rng,
170    ) -> Result<(Self, Vec<u8>), LinkError> {
171        let (peer_pub, peer_sig_pub, peer_mtu, mode) = parse_linkrequest_data(linkrequest_data)?;
172
173        let extra = linkrequest_data.len().saturating_sub(LINK_ECPUBSIZE);
174        let link_id = compute_link_id(hashable_part, extra);
175
176        // Generate ephemeral keys for this end
177        let prv = X25519PrivateKey::generate(rng);
178        let pub_bytes = prv.public_key().public_bytes();
179        let sig_pub_bytes = *owner_sig_pub_bytes;
180
181        // Perform ECDH + HKDF
182        let derived_key = perform_key_exchange(&prv, &peer_pub, &link_id, mode)?;
183        let token = create_session_token(&derived_key)?;
184
185        let link_mtu = effective_link_mtu(peer_mtu);
186
187        // Build LRPROOF
188        let lrproof_data = handshake::build_lrproof(
189            &link_id,
190            &pub_bytes,
191            &sig_pub_bytes,
192            owner_sig_prv,
193            peer_mtu,
194            mode,
195        );
196
197        let engine = LinkEngine {
198            link_id,
199            state: LinkState::Handshake,
200            is_initiator: false,
201            mode,
202            prv,
203            sig_prv: Ed25519PrivateKey::from_bytes(&owner_sig_prv.private_bytes()),
204            peer_pub_bytes: Some(peer_pub),
205            peer_sig_pub_bytes: Some(peer_sig_pub),
206            derived_key: Some(derived_key),
207            token: Some(token),
208            request_time: now,
209            activated_at: None,
210            last_inbound: now,
211            last_outbound: now,
212            last_keepalive: now,
213            last_proof: 0.0,
214            rtt: None,
215            keepalive_interval: LINK_KEEPALIVE_MAX,
216            stale_time: LINK_KEEPALIVE_MAX * 2.0,
217            establishment_timeout: compute_establishment_timeout(
218                LINK_ESTABLISHMENT_TIMEOUT_PER_HOP,
219                hops,
220            ),
221            expected_hops: PATHFINDER_M,
222            rebalanced_at: None,
223            tx_packets: 0,
224            rx_packets: 0,
225            tx_bytes: 0,
226            rx_bytes: 0,
227            remote_identity: None,
228            destination_hash: *dest_hash,
229            mtu: link_mtu,
230            mdu: compute_mdu(link_mtu as usize),
231        };
232
233        Ok((engine, lrproof_data))
234    }
235
236    /// Handle an incoming LRPROOF (initiator side).
237    ///
238    /// Validates the proof, performs ECDH, derives session key, returns LRRTT data
239    /// to be encrypted and sent.
240    pub fn handle_lrproof(
241        &mut self,
242        proof_data: &[u8],
243        peer_sig_pub_bytes: &[u8; 32],
244        now: f64,
245        rng: &mut dyn Rng,
246    ) -> Result<(Vec<u8>, Vec<LinkAction>), LinkError> {
247        self.handle_lrproof_with_hops(proof_data, peer_sig_pub_bytes, None, now, rng)
248    }
249
250    /// Validate an LRPROOF while recording its authenticated hop metric.
251    pub fn handle_lrproof_with_hops(
252        &mut self,
253        proof_data: &[u8],
254        peer_sig_pub_bytes: &[u8; 32],
255        packet_hops: Option<u8>,
256        now: f64,
257        rng: &mut dyn Rng,
258    ) -> Result<(Vec<u8>, Vec<LinkAction>), LinkError> {
259        if self.state != LinkState::Pending || !self.is_initiator {
260            return Err(LinkError::InvalidState);
261        }
262
263        let peer_sig_pub = Ed25519PublicKey::from_bytes(peer_sig_pub_bytes);
264
265        let (peer_pub, confirmed_mtu, confirmed_mode) =
266            validate_lrproof(proof_data, &self.link_id, &peer_sig_pub, peer_sig_pub_bytes)?;
267
268        if confirmed_mode != self.mode {
269            return Err(LinkError::UnsupportedMode);
270        }
271
272        if let Some(hops) = packet_hops {
273            if hops != self.expected_hops && self.rebalanced_at.is_none() {
274                self.rebalanced_at = Some(now);
275                self.expected_hops = hops;
276            }
277        }
278
279        self.peer_pub_bytes = Some(peer_pub);
280        self.peer_sig_pub_bytes = Some(*peer_sig_pub_bytes);
281
282        // ECDH + HKDF
283        let derived_key = perform_key_exchange(&self.prv, &peer_pub, &self.link_id, self.mode)?;
284        let token = create_session_token(&derived_key)?;
285
286        self.derived_key = Some(derived_key);
287        self.token = Some(token);
288
289        // Update MTU if confirmed
290        if confirmed_mtu.is_some() {
291            let mtu = effective_link_mtu(confirmed_mtu);
292            self.mtu = mtu;
293            self.mdu = compute_mdu(mtu as usize);
294        }
295
296        // Compute RTT and activate
297        let rtt = now - self.request_time;
298        self.rtt = Some(rtt);
299        self.state = LinkState::Active;
300        self.activated_at = Some(now);
301        self.last_inbound = now;
302        self.update_keepalive();
303
304        // Build encrypted LRRTT packet data
305        let rtt_packed = pack_rtt(rtt);
306        let rtt_encrypted = self.encrypt(&rtt_packed, rng)?;
307
308        let actions = vec![
309            LinkAction::StateChanged {
310                link_id: self.link_id,
311                new_state: LinkState::Active,
312                reason: None,
313            },
314            LinkAction::LinkEstablished {
315                link_id: self.link_id,
316                rtt,
317                is_initiator: true,
318            },
319        ];
320
321        Ok((rtt_encrypted, actions))
322    }
323
324    /// Handle an incoming LRRTT (responder side).
325    ///
326    /// Decrypts the RTT packet, activates the link.
327    pub fn handle_lrrtt(
328        &mut self,
329        encrypted_data: &[u8],
330        now: f64,
331    ) -> Result<Vec<LinkAction>, LinkError> {
332        self.handle_lrrtt_with_hops(encrypted_data, None, now)
333    }
334
335    /// Handle LRRTT while recording the responder-side hop metric. The legacy
336    /// timing-only method remains available for existing callers.
337    pub fn handle_lrrtt_with_hops(
338        &mut self,
339        encrypted_data: &[u8],
340        packet_hops: Option<u8>,
341        now: f64,
342    ) -> Result<Vec<LinkAction>, LinkError> {
343        if self.state != LinkState::Handshake || self.is_initiator {
344            return Err(LinkError::InvalidState);
345        }
346
347        let plaintext = self.decrypt(encrypted_data)?;
348        let initiator_rtt = unpack_rtt(&plaintext).ok_or(LinkError::InvalidData)?;
349        if let Some(hops) = packet_hops {
350            self.expected_hops = hops;
351        }
352
353        let measured_rtt = now - self.request_time;
354        let rtt = if measured_rtt > initiator_rtt {
355            measured_rtt
356        } else {
357            initiator_rtt
358        };
359
360        self.rtt = Some(rtt);
361        self.state = LinkState::Active;
362        self.activated_at = Some(now);
363        self.last_inbound = now;
364        self.update_keepalive();
365
366        let actions = vec![
367            LinkAction::StateChanged {
368                link_id: self.link_id,
369                new_state: LinkState::Active,
370                reason: None,
371            },
372            LinkAction::LinkEstablished {
373                link_id: self.link_id,
374                rtt,
375                is_initiator: false,
376            },
377        ];
378
379        Ok(actions)
380    }
381
382    /// Encrypt plaintext for transmission over this link.
383    pub fn encrypt(&self, plaintext: &[u8], rng: &mut dyn Rng) -> Result<Vec<u8>, LinkError> {
384        let token = self.token.as_ref().ok_or(LinkError::NoSessionKey)?;
385        Ok(link_encrypt(token, plaintext, rng))
386    }
387
388    /// Decrypt ciphertext received on this link.
389    pub fn decrypt(&self, ciphertext: &[u8]) -> Result<Vec<u8>, LinkError> {
390        let token = self.token.as_ref().ok_or(LinkError::NoSessionKey)?;
391        link_decrypt(token, ciphertext)
392    }
393
394    /// Sign an explicit delivery proof for a packet sent over this Link.
395    pub fn sign_packet_hash(&self, packet_hash: &[u8; 32]) -> [u8; 64] {
396        self.sig_prv.sign(packet_hash)
397    }
398
399    /// Validate a delivery proof using the remote endpoint's Link signing key.
400    pub fn validate_packet_proof(&self, packet_hash: &[u8; 32], signature: &[u8; 64]) -> bool {
401        self.peer_sig_pub_bytes
402            .map(|bytes| Ed25519PublicKey::from_bytes(&bytes).verify(signature, packet_hash))
403            .unwrap_or(false)
404    }
405
406    /// Build LINKIDENTIFY data (encrypted).
407    pub fn build_identify(
408        &self,
409        identity: &rns_crypto::identity::Identity,
410        rng: &mut dyn Rng,
411    ) -> Result<Vec<u8>, LinkError> {
412        if self.state != LinkState::Active {
413            return Err(LinkError::InvalidState);
414        }
415        let plaintext = identify::build_identify_data(identity, &self.link_id)?;
416        self.encrypt(&plaintext, rng)
417    }
418
419    /// Handle incoming LINKIDENTIFY (encrypted data).
420    ///
421    /// Only responders (non-initiators) can receive LINKIDENTIFY (Python: Link.py:1017).
422    pub fn handle_identify(&mut self, encrypted_data: &[u8]) -> Result<Vec<LinkAction>, LinkError> {
423        if self.state != LinkState::Active || self.is_initiator {
424            return Err(LinkError::InvalidState);
425        }
426
427        let plaintext = self.decrypt(encrypted_data)?;
428        let (identity_hash, public_key) =
429            identify::validate_identify_data(&plaintext, &self.link_id)?;
430        if self.remote_identity.is_some() {
431            return Ok(Vec::new());
432        }
433        self.remote_identity = Some((identity_hash, public_key));
434
435        Ok(alloc::vec![LinkAction::RemoteIdentified {
436            link_id: self.link_id,
437            identity_hash,
438            public_key,
439        }])
440    }
441
442    /// Record that an inbound packet was received (updates timing).
443    ///
444    /// If the link is STALE, recovers to ACTIVE (Python: Link.py:987-988).
445    pub fn record_inbound(&mut self, now: f64) -> Vec<LinkAction> {
446        self.last_inbound = now;
447        if self.state == LinkState::Stale {
448            self.state = LinkState::Active;
449            return alloc::vec![LinkAction::StateChanged {
450                link_id: self.link_id,
451                new_state: LinkState::Active,
452                reason: None,
453            }];
454        }
455        Vec::new()
456    }
457
458    /// Record that a proof was received (updates timing for stale detection).
459    pub fn record_proof(&mut self, now: f64) {
460        self.last_proof = now;
461    }
462
463    /// Record that an outbound packet was sent (updates timing).
464    pub fn record_outbound(&mut self, now: f64, is_keepalive: bool) {
465        self.last_outbound = now;
466        if is_keepalive {
467            self.last_keepalive = now;
468        }
469    }
470
471    /// Record one accepted inbound packet using its packed data/ciphertext size.
472    pub fn record_inbound_traffic(&mut self, data_len: usize) {
473        if self.state != LinkState::Closed {
474            self.rx_packets = self.rx_packets.saturating_add(1);
475            self.rx_bytes = self.rx_bytes.saturating_add(data_len as u64);
476        }
477    }
478
479    /// Record one successfully packed outbound packet.
480    pub fn record_outbound_traffic(&mut self, data_len: usize) {
481        self.tx_packets = self.tx_packets.saturating_add(1);
482        self.tx_bytes = self.tx_bytes.saturating_add(data_len as u64);
483    }
484
485    /// Periodic tick: check keepalive, stale, timeouts.
486    pub fn tick(&mut self, now: f64) -> Vec<LinkAction> {
487        let mut actions = Vec::new();
488
489        match self.state {
490            LinkState::Pending | LinkState::Handshake => {
491                if is_establishment_timeout(self.request_time, self.establishment_timeout, now) {
492                    self.state = LinkState::Closed;
493                    actions.push(LinkAction::StateChanged {
494                        link_id: self.link_id,
495                        new_state: LinkState::Closed,
496                        reason: Some(TeardownReason::Timeout),
497                    });
498                }
499            }
500            LinkState::Active => {
501                let activated = self.activated_at.unwrap_or(0.0);
502                // Python: max(max(self.last_inbound, self.last_proof), activated_at)
503                let last_inbound = self.last_inbound.max(self.last_proof).max(activated);
504
505                if should_go_stale(last_inbound, self.stale_time, now) {
506                    self.state = LinkState::Stale;
507                    actions.push(LinkAction::StateChanged {
508                        link_id: self.link_id,
509                        new_state: LinkState::Stale,
510                        reason: None,
511                    });
512                }
513            }
514            LinkState::Stale => {
515                // In Python, STALE immediately sends teardown and closes
516                self.state = LinkState::Closed;
517                actions.push(LinkAction::StateChanged {
518                    link_id: self.link_id,
519                    new_state: LinkState::Closed,
520                    reason: Some(TeardownReason::Timeout),
521                });
522            }
523            LinkState::Closed => {}
524        }
525
526        actions
527    }
528
529    /// Check if a keepalive should be sent. Returns true if conditions are met.
530    pub fn needs_keepalive(&self, now: f64) -> bool {
531        if self.state != LinkState::Active || !self.is_initiator {
532            return false;
533        }
534        let activated = self.activated_at.unwrap_or(0.0);
535        let last_inbound = self.last_inbound.max(self.last_proof).max(activated);
536
537        // Initiators probe whenever either direction has been quiet for a full
538        // interval. In particular, continuous responder-to-initiator traffic
539        // must not suppress the probe that keeps the responder's inbound timer
540        // alive.
541        let inbound_quiet = now >= last_inbound + self.keepalive_interval;
542        let outbound_quiet = now >= self.last_outbound + self.keepalive_interval;
543        if !inbound_quiet && !outbound_quiet {
544            return false;
545        }
546
547        should_send_keepalive(self.last_keepalive, self.keepalive_interval, now)
548    }
549
550    /// Return whether a received keepalive probe needs a responder reply.
551    ///
552    /// `0xff` is the initiator probe and `0xfe` the responder acknowledgement,
553    /// matching upstream Reticulum. A responder that sent any traffic within
554    /// the current keepalive interval can omit the acknowledgement because that
555    /// traffic already refreshed the initiator's inbound timer.
556    pub fn should_reply_keepalive(&self, payload: &[u8], now: f64) -> bool {
557        self.state == LinkState::Active
558            && !self.is_initiator
559            && payload == [0xff]
560            && now >= self.last_outbound + self.keepalive_interval
561    }
562
563    /// Tear down the link (initiator-initiated close).
564    pub fn teardown(&mut self) -> Vec<LinkAction> {
565        if self.state == LinkState::Closed {
566            return Vec::new();
567        }
568        self.state = LinkState::Closed;
569        let reason = if self.is_initiator {
570            TeardownReason::InitiatorClosed
571        } else {
572            TeardownReason::DestinationClosed
573        };
574        alloc::vec![LinkAction::StateChanged {
575            link_id: self.link_id,
576            new_state: LinkState::Closed,
577            reason: Some(reason),
578        }]
579    }
580
581    /// Handle incoming teardown (remote close).
582    pub fn handle_teardown(&mut self) -> Vec<LinkAction> {
583        if self.state == LinkState::Closed {
584            return Vec::new();
585        }
586        self.state = LinkState::Closed;
587        let reason = if self.is_initiator {
588            TeardownReason::DestinationClosed
589        } else {
590            TeardownReason::InitiatorClosed
591        };
592        alloc::vec![LinkAction::StateChanged {
593            link_id: self.link_id,
594            new_state: LinkState::Closed,
595            reason: Some(reason),
596        }]
597    }
598
599    // --- Queries ---
600
601    pub fn link_id(&self) -> &LinkId {
602        &self.link_id
603    }
604
605    pub fn state(&self) -> LinkState {
606        self.state
607    }
608
609    pub fn rtt(&self) -> Option<f64> {
610        self.rtt
611    }
612
613    pub fn mdu(&self) -> usize {
614        self.mdu
615    }
616
617    pub fn mtu(&self) -> u32 {
618        self.mtu
619    }
620
621    pub fn is_initiator(&self) -> bool {
622        self.is_initiator
623    }
624
625    pub fn mode(&self) -> LinkMode {
626        self.mode
627    }
628
629    pub fn remote_identity(&self) -> Option<&([u8; 16], [u8; 64])> {
630        self.remote_identity.as_ref()
631    }
632
633    pub fn destination_hash(&self) -> &[u8; 16] {
634        &self.destination_hash
635    }
636
637    pub fn expected_hops(&self) -> u8 {
638        self.expected_hops
639    }
640
641    pub fn rebalanced_at(&self) -> Option<f64> {
642        self.rebalanced_at
643    }
644
645    pub fn tx_packets(&self) -> u64 {
646        self.tx_packets
647    }
648
649    pub fn rx_packets(&self) -> u64 {
650        self.rx_packets
651    }
652
653    pub fn tx_bytes(&self) -> u64 {
654        self.tx_bytes
655    }
656
657    pub fn rx_bytes(&self) -> u64 {
658        self.rx_bytes
659    }
660
661    /// Get the derived session key (needed for hole-punch token derivation).
662    pub fn derived_key(&self) -> Option<&[u8]> {
663        self.derived_key.as_deref()
664    }
665
666    pub fn keepalive_interval(&self) -> f64 {
667        self.keepalive_interval
668    }
669
670    /// Update the measured RTT (e.g., after path redirect to a direct link).
671    /// Also recalculates keepalive and stale timers.
672    pub fn set_rtt(&mut self, rtt: f64) {
673        self.rtt = Some(rtt);
674        self.update_keepalive();
675    }
676
677    /// Update the link MTU (e.g., after path redirect to a different interface).
678    pub fn set_mtu(&mut self, mtu: u32) {
679        self.mtu = mtu;
680        self.mdu = compute_mdu(mtu as usize);
681    }
682
683    #[doc(hidden)]
684    pub fn clear_session_for_testing(&mut self) {
685        self.derived_key = None;
686        self.token = None;
687    }
688
689    // --- Internal ---
690
691    fn update_keepalive(&mut self) {
692        if let Some(rtt) = self.rtt {
693            self.keepalive_interval = compute_keepalive(rtt);
694            self.stale_time = compute_stale_time(self.keepalive_interval);
695        }
696    }
697}
698
699/// Compute link MDU from MTU.
700///
701/// MDU = floor((mtu - IFAC_MIN_SIZE - HEADER_MINSIZE - TOKEN_OVERHEAD) / AES128_BLOCKSIZE) * AES128_BLOCKSIZE - 1
702fn compute_mdu(mtu: usize) -> usize {
703    use crate::constants::{AES128_BLOCKSIZE, HEADER_MINSIZE, IFAC_MIN_SIZE, TOKEN_OVERHEAD};
704    let numerator = mtu.saturating_sub(IFAC_MIN_SIZE + HEADER_MINSIZE + TOKEN_OVERHEAD);
705    (numerator / AES128_BLOCKSIZE) * AES128_BLOCKSIZE - 1
706}
707
708fn effective_link_mtu(signalled_mtu: Option<u32>) -> u32 {
709    signalled_mtu.filter(|mtu| *mtu != 0).unwrap_or(MTU as u32)
710}
711use alloc::vec;
712
713#[cfg(test)]
714mod tests {
715    use super::*;
716    use crate::constants::LINK_MDU;
717    use rns_crypto::FixedRng;
718
719    fn make_rng(seed: u8) -> FixedRng {
720        FixedRng::new(&[seed; 128])
721    }
722
723    fn active_link_pair() -> (LinkEngine, LinkEngine, Vec<u8>) {
724        let mut rng_id = make_rng(0x01);
725        let dest_sig_prv = Ed25519PrivateKey::generate(&mut rng_id);
726        let dest_sig_pub_bytes = dest_sig_prv.public_key().public_bytes();
727        let dest_hash = [0xDD; 16];
728
729        let mut rng_init = make_rng(0x10);
730        let (mut initiator, request_data) = LinkEngine::new_initiator(
731            &dest_hash,
732            1,
733            LinkMode::Aes256Cbc,
734            Some(500),
735            100.0,
736            &mut rng_init,
737        );
738        let mut hashable = vec![0x00, 0x00];
739        hashable.extend_from_slice(&dest_hash);
740        hashable.push(0x00);
741        hashable.extend_from_slice(&request_data);
742        initiator.set_link_id_from_hashable(&hashable, request_data.len());
743
744        let mut rng_resp = make_rng(0x20);
745        let (mut responder, lrproof_data) = LinkEngine::new_responder(
746            &dest_sig_prv,
747            &dest_sig_pub_bytes,
748            &request_data,
749            &hashable,
750            &dest_hash,
751            1,
752            100.5,
753            &mut rng_resp,
754        )
755        .unwrap();
756        let mut rng_lrrtt = make_rng(0x30);
757        let (lrrtt_encrypted, _) = initiator
758            .handle_lrproof(&lrproof_data, &dest_sig_pub_bytes, 100.8, &mut rng_lrrtt)
759            .unwrap();
760        responder.handle_lrrtt(&lrrtt_encrypted, 101.0).unwrap();
761
762        (initiator, responder, lrrtt_encrypted)
763    }
764
765    #[test]
766    fn test_compute_mdu_default() {
767        assert_eq!(compute_mdu(500), LINK_MDU);
768    }
769
770    #[test]
771    fn zero_mtu_signalling_falls_back_to_reticulum_mtu() {
772        let mut rng_id = make_rng(0x01);
773        let dest_sig_prv = Ed25519PrivateKey::generate(&mut rng_id);
774        let dest_sig_pub_bytes = dest_sig_prv.public_key().public_bytes();
775        let dest_hash = [0xDD; 16];
776
777        let mut rng_init = make_rng(0x10);
778        let (mut initiator, request_data) = LinkEngine::new_initiator(
779            &dest_hash,
780            1,
781            LinkMode::Aes256Cbc,
782            Some(0),
783            100.0,
784            &mut rng_init,
785        );
786        let mut hashable = vec![0x00, 0x00];
787        hashable.extend_from_slice(&dest_hash);
788        hashable.push(0x00);
789        hashable.extend_from_slice(&request_data);
790        initiator.set_link_id_from_hashable(&hashable, request_data.len());
791
792        let mut rng_resp = make_rng(0x20);
793        let (responder, lrproof_data) = LinkEngine::new_responder(
794            &dest_sig_prv,
795            &dest_sig_pub_bytes,
796            &request_data,
797            &hashable,
798            &dest_hash,
799            1,
800            100.5,
801            &mut rng_resp,
802        )
803        .unwrap();
804
805        let mut rng_lrrtt = make_rng(0x30);
806        initiator
807            .handle_lrproof(&lrproof_data, &dest_sig_pub_bytes, 100.8, &mut rng_lrrtt)
808            .unwrap();
809
810        assert_eq!(initiator.mtu(), MTU as u32);
811        assert_eq!(responder.mtu(), MTU as u32);
812        assert_eq!(initiator.mdu(), LINK_MDU);
813        assert_eq!(responder.mdu(), LINK_MDU);
814    }
815
816    #[test]
817    fn test_full_handshake() {
818        // Setup: destination identity (for responder)
819        let mut rng_id = make_rng(0x01);
820        let dest_sig_prv = Ed25519PrivateKey::generate(&mut rng_id);
821        let dest_sig_pub_bytes = dest_sig_prv.public_key().public_bytes();
822
823        let dest_hash = [0xDD; 16];
824        let mode = LinkMode::Aes256Cbc;
825
826        // Step 1: Initiator creates link request
827        let mut rng_init = make_rng(0x10);
828        let (mut initiator, request_data) =
829            LinkEngine::new_initiator(&dest_hash, 1, mode, Some(500), 100.0, &mut rng_init);
830        assert_eq!(initiator.state(), LinkState::Pending);
831
832        // Simulate packet packing: build a fake hashable part
833        // In real usage, the caller packs a LINKREQUEST packet and calls set_link_id_from_hashable
834        let mut hashable = Vec::new();
835        hashable.push(0x00); // flags byte (lower nibble)
836        hashable.push(0x00); // hops
837        hashable.extend_from_slice(&dest_hash);
838        hashable.push(0x00); // context
839        hashable.extend_from_slice(&request_data);
840
841        initiator.set_link_id_from_hashable(&hashable, request_data.len());
842        assert_ne!(initiator.link_id(), &[0u8; 16]);
843
844        // Step 2: Responder receives link request
845        let mut rng_resp = make_rng(0x20);
846        let (mut responder, lrproof_data) = LinkEngine::new_responder(
847            &dest_sig_prv,
848            &dest_sig_pub_bytes,
849            &request_data,
850            &hashable,
851            &dest_hash,
852            1,
853            100.5,
854            &mut rng_resp,
855        )
856        .unwrap();
857        assert_eq!(responder.state(), LinkState::Handshake);
858        assert_eq!(responder.link_id(), initiator.link_id());
859
860        // Step 3: Initiator validates LRPROOF
861        let mut rng_lrrtt = make_rng(0x30);
862        assert!(initiator
863            .handle_lrproof_with_hops(
864                &[0xAA; 3],
865                &dest_sig_pub_bytes,
866                Some(8),
867                100.6,
868                &mut rng_lrrtt,
869            )
870            .is_err());
871        assert_eq!(initiator.expected_hops(), 1);
872        let mismatched_mode_proof = handshake::build_lrproof(
873            initiator.link_id(),
874            lrproof_data[64..96].try_into().unwrap(),
875            &dest_sig_pub_bytes,
876            &dest_sig_prv,
877            Some(500),
878            LinkMode::Aes128Cbc,
879        );
880        assert_eq!(
881            initiator
882                .handle_lrproof_with_hops(
883                    &mismatched_mode_proof,
884                    &dest_sig_pub_bytes,
885                    Some(9),
886                    100.65,
887                    &mut rng_lrrtt,
888                )
889                .unwrap_err(),
890            LinkError::UnsupportedMode,
891        );
892        assert_eq!(initiator.expected_hops(), 1);
893        let mut invalid_lrproof = lrproof_data.clone();
894        invalid_lrproof[0] ^= 0x01;
895        assert!(initiator
896            .handle_lrproof_with_hops(
897                &invalid_lrproof,
898                &dest_sig_pub_bytes,
899                Some(7),
900                100.7,
901                &mut rng_lrrtt,
902            )
903            .is_err());
904        assert_eq!(initiator.expected_hops(), 1);
905        let (lrrtt_encrypted, actions) = initiator
906            .handle_lrproof_with_hops(
907                &lrproof_data,
908                &dest_sig_pub_bytes,
909                Some(4),
910                100.8,
911                &mut rng_lrrtt,
912            )
913            .unwrap();
914        assert_eq!(initiator.state(), LinkState::Active);
915        assert!(initiator.rtt().is_some());
916        assert_eq!(initiator.rebalanced_at(), Some(100.8));
917        assert_eq!(actions.len(), 2); // StateChanged + LinkEstablished
918
919        // Step 4: Responder handles LRRTT
920        let actions = responder
921            .handle_lrrtt_with_hops(&lrrtt_encrypted, Some(4), 101.0)
922            .unwrap();
923        assert_eq!(responder.state(), LinkState::Active);
924        assert_eq!(initiator.expected_hops(), 4);
925        assert_eq!(responder.expected_hops(), 4);
926        assert_eq!(responder.rebalanced_at(), None);
927
928        // A later valid proof cannot rewrite the first authenticated
929        // rebalance, even if it reports another hop metric.
930        initiator.state = LinkState::Pending;
931        let mut repeated_rng = make_rng(0x31);
932        initiator
933            .handle_lrproof_with_hops(
934                &lrproof_data,
935                &dest_sig_pub_bytes,
936                Some(6),
937                101.1,
938                &mut repeated_rng,
939            )
940            .unwrap();
941        assert_eq!(initiator.expected_hops(), 4);
942        assert_eq!(initiator.rebalanced_at(), Some(100.8));
943
944        initiator.record_outbound_traffic(48);
945        initiator.record_inbound_traffic(32);
946        assert_eq!((initiator.tx_packets(), initiator.tx_bytes()), (1, 48));
947        assert_eq!((initiator.rx_packets(), initiator.rx_bytes()), (1, 32));
948        assert!(responder.rtt().is_some());
949        assert_eq!(actions.len(), 2);
950    }
951
952    #[test]
953    fn test_encrypt_decrypt_after_handshake() {
954        let mut rng_id = make_rng(0x01);
955        let dest_sig_prv = Ed25519PrivateKey::generate(&mut rng_id);
956        let dest_sig_pub_bytes = dest_sig_prv.public_key().public_bytes();
957        let dest_hash = [0xDD; 16];
958
959        let mut rng_init = make_rng(0x10);
960        let (mut initiator, request_data) = LinkEngine::new_initiator(
961            &dest_hash,
962            1,
963            LinkMode::Aes256Cbc,
964            Some(500),
965            100.0,
966            &mut rng_init,
967        );
968        let mut hashable = Vec::new();
969        hashable.push(0x00);
970        hashable.push(0x00);
971        hashable.extend_from_slice(&dest_hash);
972        hashable.push(0x00);
973        hashable.extend_from_slice(&request_data);
974        initiator.set_link_id_from_hashable(&hashable, request_data.len());
975
976        let mut rng_resp = make_rng(0x20);
977        let (mut responder, lrproof_data) = LinkEngine::new_responder(
978            &dest_sig_prv,
979            &dest_sig_pub_bytes,
980            &request_data,
981            &hashable,
982            &dest_hash,
983            1,
984            100.5,
985            &mut rng_resp,
986        )
987        .unwrap();
988
989        let mut rng_lrrtt = make_rng(0x30);
990        let (lrrtt_encrypted, _) = initiator
991            .handle_lrproof(&lrproof_data, &dest_sig_pub_bytes, 100.8, &mut rng_lrrtt)
992            .unwrap();
993        responder.handle_lrrtt(&lrrtt_encrypted, 101.0).unwrap();
994
995        // Now both sides are ACTIVE — test encrypt/decrypt
996        let mut rng_enc = make_rng(0x40);
997        let plaintext = b"Hello over encrypted link!";
998        let ciphertext = initiator.encrypt(plaintext, &mut rng_enc).unwrap();
999        let decrypted = responder.decrypt(&ciphertext).unwrap();
1000        assert_eq!(decrypted, plaintext);
1001
1002        // And in reverse
1003        let mut rng_enc2 = make_rng(0x50);
1004        let ciphertext2 = responder.encrypt(b"Reply!", &mut rng_enc2).unwrap();
1005        let decrypted2 = initiator.decrypt(&ciphertext2).unwrap();
1006        assert_eq!(decrypted2, b"Reply!");
1007    }
1008
1009    #[test]
1010    fn test_tick_establishment_timeout() {
1011        let mut rng = make_rng(0x10);
1012        let dest_hash = [0xDD; 16];
1013        let (mut engine, _) =
1014            LinkEngine::new_initiator(&dest_hash, 1, LinkMode::Aes256Cbc, None, 100.0, &mut rng);
1015        // Timeout = 6.0 + 6.0 * 1 = 12.0s → expires at 112.0
1016
1017        // Before timeout — no state change
1018        let actions = engine.tick(110.0);
1019        assert!(actions.is_empty());
1020
1021        // After timeout
1022        let actions = engine.tick(113.0);
1023        assert_eq!(actions.len(), 1);
1024        assert_eq!(engine.state(), LinkState::Closed);
1025    }
1026
1027    #[test]
1028    fn test_tick_stale_and_close() {
1029        let mut rng_id = make_rng(0x01);
1030        let dest_sig_prv = Ed25519PrivateKey::generate(&mut rng_id);
1031        let dest_sig_pub_bytes = dest_sig_prv.public_key().public_bytes();
1032        let dest_hash = [0xDD; 16];
1033
1034        let mut rng_init = make_rng(0x10);
1035        let (mut initiator, request_data) = LinkEngine::new_initiator(
1036            &dest_hash,
1037            1,
1038            LinkMode::Aes256Cbc,
1039            Some(500),
1040            100.0,
1041            &mut rng_init,
1042        );
1043        let mut hashable = Vec::new();
1044        hashable.push(0x00);
1045        hashable.push(0x00);
1046        hashable.extend_from_slice(&dest_hash);
1047        hashable.push(0x00);
1048        hashable.extend_from_slice(&request_data);
1049        initiator.set_link_id_from_hashable(&hashable, request_data.len());
1050
1051        let mut rng_resp = make_rng(0x20);
1052        let (_, lrproof_data) = LinkEngine::new_responder(
1053            &dest_sig_prv,
1054            &dest_sig_pub_bytes,
1055            &request_data,
1056            &hashable,
1057            &dest_hash,
1058            1,
1059            100.5,
1060            &mut rng_resp,
1061        )
1062        .unwrap();
1063
1064        let mut rng_lrrtt = make_rng(0x30);
1065        initiator
1066            .handle_lrproof(&lrproof_data, &dest_sig_pub_bytes, 100.8, &mut rng_lrrtt)
1067            .unwrap();
1068        assert_eq!(initiator.state(), LinkState::Active);
1069
1070        // Advance time past stale_time
1071        let stale_time = initiator.stale_time;
1072        let actions = initiator.tick(100.8 + stale_time + 1.0);
1073        assert_eq!(initiator.state(), LinkState::Stale);
1074        assert_eq!(actions.len(), 1);
1075
1076        // Next tick: STALE → CLOSED
1077        let actions = initiator.tick(100.8 + stale_time + 2.0);
1078        assert_eq!(initiator.state(), LinkState::Closed);
1079        assert_eq!(actions.len(), 1);
1080    }
1081
1082    #[test]
1083    fn test_needs_keepalive() {
1084        let mut rng_id = make_rng(0x01);
1085        let dest_sig_prv = Ed25519PrivateKey::generate(&mut rng_id);
1086        let dest_sig_pub_bytes = dest_sig_prv.public_key().public_bytes();
1087        let dest_hash = [0xDD; 16];
1088
1089        let mut rng_init = make_rng(0x10);
1090        let (mut initiator, request_data) = LinkEngine::new_initiator(
1091            &dest_hash,
1092            1,
1093            LinkMode::Aes256Cbc,
1094            Some(500),
1095            100.0,
1096            &mut rng_init,
1097        );
1098        let mut hashable = Vec::new();
1099        hashable.push(0x00);
1100        hashable.push(0x00);
1101        hashable.extend_from_slice(&dest_hash);
1102        hashable.push(0x00);
1103        hashable.extend_from_slice(&request_data);
1104        initiator.set_link_id_from_hashable(&hashable, request_data.len());
1105
1106        let mut rng_resp = make_rng(0x20);
1107        let (_, lrproof_data) = LinkEngine::new_responder(
1108            &dest_sig_prv,
1109            &dest_sig_pub_bytes,
1110            &request_data,
1111            &hashable,
1112            &dest_hash,
1113            1,
1114            100.5,
1115            &mut rng_resp,
1116        )
1117        .unwrap();
1118
1119        let mut rng_lrrtt = make_rng(0x30);
1120        initiator
1121            .handle_lrproof(&lrproof_data, &dest_sig_pub_bytes, 100.8, &mut rng_lrrtt)
1122            .unwrap();
1123
1124        let ka = initiator.keepalive_interval();
1125        // Not yet
1126        assert!(!initiator.needs_keepalive(100.8 + ka - 1.0));
1127        // Past keepalive
1128        assert!(initiator.needs_keepalive(100.8 + ka + 1.0));
1129    }
1130
1131    #[test]
1132    fn initiator_probes_when_outbound_is_quiet_despite_recent_inbound() {
1133        let (mut initiator, _, _) = active_link_pair();
1134        let keepalive = initiator.keepalive_interval();
1135        let now = 1_000.0;
1136
1137        initiator.record_inbound(now - 0.1);
1138        initiator.record_outbound(now - keepalive - 0.1, false);
1139
1140        assert!(initiator.needs_keepalive(now));
1141    }
1142
1143    #[test]
1144    fn initiator_probes_when_inbound_is_quiet_despite_recent_outbound() {
1145        let (mut initiator, _, _) = active_link_pair();
1146        let keepalive = initiator.keepalive_interval();
1147        let now = 1_000.0;
1148
1149        initiator.record_inbound(now - keepalive - 0.1);
1150        initiator.record_outbound(now - 0.1, false);
1151
1152        assert!(initiator.needs_keepalive(now));
1153    }
1154
1155    #[test]
1156    fn initiator_does_not_probe_when_both_directions_are_recent() {
1157        let (mut initiator, _, _) = active_link_pair();
1158        let now = 1_000.0;
1159
1160        initiator.record_inbound(now - 0.1);
1161        initiator.record_outbound(now - 0.1, false);
1162
1163        assert!(!initiator.needs_keepalive(now));
1164    }
1165
1166    #[test]
1167    fn test_needs_keepalive_responder() {
1168        let mut rng_id = make_rng(0x01);
1169        let dest_sig_prv = Ed25519PrivateKey::generate(&mut rng_id);
1170        let dest_sig_pub_bytes = dest_sig_prv.public_key().public_bytes();
1171        let dest_hash = [0xDD; 16];
1172
1173        let mut rng_init = make_rng(0x10);
1174        let (mut initiator, request_data) = LinkEngine::new_initiator(
1175            &dest_hash,
1176            1,
1177            LinkMode::Aes256Cbc,
1178            Some(500),
1179            100.0,
1180            &mut rng_init,
1181        );
1182        let mut hashable = Vec::new();
1183        hashable.push(0x00);
1184        hashable.push(0x00);
1185        hashable.extend_from_slice(&dest_hash);
1186        hashable.push(0x00);
1187        hashable.extend_from_slice(&request_data);
1188        initiator.set_link_id_from_hashable(&hashable, request_data.len());
1189
1190        let mut rng_resp = make_rng(0x20);
1191        let (mut responder, lrproof_data) = LinkEngine::new_responder(
1192            &dest_sig_prv,
1193            &dest_sig_pub_bytes,
1194            &request_data,
1195            &hashable,
1196            &dest_hash,
1197            1,
1198            100.5,
1199            &mut rng_resp,
1200        )
1201        .unwrap();
1202
1203        let mut rng_lrrtt = make_rng(0x30);
1204        let (lrrtt_encrypted, _) = initiator
1205            .handle_lrproof(&lrproof_data, &dest_sig_pub_bytes, 100.8, &mut rng_lrrtt)
1206            .unwrap();
1207        responder.handle_lrrtt(&lrrtt_encrypted, 101.0).unwrap();
1208
1209        let ka = responder.keepalive_interval();
1210        // Only the initiator emits probes. The responder replies to probes.
1211        assert!(!responder.needs_keepalive(101.0 + ka - 1.0));
1212        assert!(!responder.needs_keepalive(101.0 + ka + 1.0));
1213    }
1214
1215    #[test]
1216    fn responder_replies_only_to_probe_and_only_when_outbound_is_quiet() {
1217        let (_, mut responder, _) = active_link_pair();
1218        let keepalive = responder.keepalive_interval();
1219        let now = 1_000.0;
1220
1221        responder.record_outbound(now - keepalive - 0.1, false);
1222        assert!(responder.should_reply_keepalive(&[0xff], now));
1223        assert!(!responder.should_reply_keepalive(&[0xfe], now));
1224        assert!(!responder.should_reply_keepalive(&[], now));
1225
1226        responder.record_outbound(now - 0.1, false);
1227        assert!(!responder.should_reply_keepalive(&[0xff], now));
1228    }
1229
1230    #[test]
1231    fn initiator_never_replies_to_keepalive_probe() {
1232        let (mut initiator, _, _) = active_link_pair();
1233        let keepalive = initiator.keepalive_interval();
1234        let now = 1_000.0;
1235        initiator.record_outbound(now - keepalive - 0.1, false);
1236
1237        assert!(!initiator.should_reply_keepalive(&[0xff], now));
1238    }
1239
1240    #[test]
1241    fn packet_delivery_proofs_are_bidirectional_and_peer_authenticated() {
1242        let (initiator, responder, _) = active_link_pair();
1243        let packet_hash = [0x42; 32];
1244
1245        let initiator_proof = initiator.sign_packet_hash(&packet_hash);
1246        assert!(responder.validate_packet_proof(&packet_hash, &initiator_proof));
1247        assert!(!initiator.validate_packet_proof(&packet_hash, &initiator_proof));
1248
1249        let responder_proof = responder.sign_packet_hash(&packet_hash);
1250        assert!(initiator.validate_packet_proof(&packet_hash, &responder_proof));
1251        assert!(!responder.validate_packet_proof(&packet_hash, &responder_proof));
1252        assert!(!initiator.validate_packet_proof(&[0x43; 32], &responder_proof));
1253    }
1254
1255    #[test]
1256    fn test_teardown() {
1257        let mut rng = make_rng(0x10);
1258        let (mut engine, _) =
1259            LinkEngine::new_initiator(&[0xDD; 16], 1, LinkMode::Aes256Cbc, None, 100.0, &mut rng);
1260        let actions = engine.teardown();
1261        assert_eq!(engine.state(), LinkState::Closed);
1262        assert_eq!(actions.len(), 1);
1263
1264        // Teardown again is no-op
1265        let actions = engine.teardown();
1266        assert!(actions.is_empty());
1267    }
1268
1269    #[test]
1270    fn test_handle_teardown() {
1271        let mut rng = make_rng(0x10);
1272        let (mut engine, _) =
1273            LinkEngine::new_initiator(&[0xDD; 16], 1, LinkMode::Aes256Cbc, None, 100.0, &mut rng);
1274        let actions = engine.handle_teardown();
1275        assert_eq!(engine.state(), LinkState::Closed);
1276        assert_eq!(actions.len(), 1);
1277        match &actions[0] {
1278            LinkAction::StateChanged { reason, .. } => {
1279                assert_eq!(*reason, Some(TeardownReason::DestinationClosed));
1280            }
1281            _ => panic!("Expected StateChanged"),
1282        }
1283    }
1284
1285    #[test]
1286    fn test_identify_over_link() {
1287        let mut rng_id = make_rng(0x01);
1288        let dest_sig_prv = Ed25519PrivateKey::generate(&mut rng_id);
1289        let dest_sig_pub_bytes = dest_sig_prv.public_key().public_bytes();
1290        let dest_hash = [0xDD; 16];
1291
1292        let mut rng_init = make_rng(0x10);
1293        let (mut initiator, request_data) = LinkEngine::new_initiator(
1294            &dest_hash,
1295            1,
1296            LinkMode::Aes256Cbc,
1297            Some(500),
1298            100.0,
1299            &mut rng_init,
1300        );
1301        let mut hashable = Vec::new();
1302        hashable.push(0x00);
1303        hashable.push(0x00);
1304        hashable.extend_from_slice(&dest_hash);
1305        hashable.push(0x00);
1306        hashable.extend_from_slice(&request_data);
1307        initiator.set_link_id_from_hashable(&hashable, request_data.len());
1308
1309        let mut rng_resp = make_rng(0x20);
1310        let (mut responder, lrproof_data) = LinkEngine::new_responder(
1311            &dest_sig_prv,
1312            &dest_sig_pub_bytes,
1313            &request_data,
1314            &hashable,
1315            &dest_hash,
1316            1,
1317            100.5,
1318            &mut rng_resp,
1319        )
1320        .unwrap();
1321
1322        let mut rng_lrrtt = make_rng(0x30);
1323        let (lrrtt_encrypted, _) = initiator
1324            .handle_lrproof(&lrproof_data, &dest_sig_pub_bytes, 100.8, &mut rng_lrrtt)
1325            .unwrap();
1326        responder.handle_lrrtt(&lrrtt_encrypted, 101.0).unwrap();
1327
1328        // Create identity to identify with
1329        let mut rng_ident = make_rng(0x40);
1330        let my_identity = rns_crypto::identity::Identity::new(&mut rng_ident);
1331
1332        // Initiator identifies itself to responder
1333        let mut rng_enc = make_rng(0x50);
1334        let identify_encrypted = initiator
1335            .build_identify(&my_identity, &mut rng_enc)
1336            .unwrap();
1337
1338        let actions = responder.handle_identify(&identify_encrypted).unwrap();
1339        assert_eq!(actions.len(), 1);
1340        match &actions[0] {
1341            LinkAction::RemoteIdentified {
1342                identity_hash,
1343                public_key,
1344                ..
1345            } => {
1346                assert_eq!(identity_hash, my_identity.hash());
1347                assert_eq!(public_key, &my_identity.get_public_key().unwrap());
1348            }
1349            _ => panic!("Expected RemoteIdentified"),
1350        }
1351
1352        // Valid repeated identifies are authenticated, but identification is
1353        // one-shot and cannot replace the first identity or emit callbacks.
1354        let mut rng_repeat = make_rng(0x51);
1355        let repeated = initiator
1356            .build_identify(&my_identity, &mut rng_repeat)
1357            .unwrap();
1358        assert!(responder.handle_identify(&repeated).unwrap().is_empty());
1359
1360        let mut rng_other = make_rng(0x60);
1361        let other_identity = rns_crypto::identity::Identity::new(&mut rng_other);
1362        let mut rng_other_enc = make_rng(0x61);
1363        let other = initiator
1364            .build_identify(&other_identity, &mut rng_other_enc)
1365            .unwrap();
1366        assert!(responder.handle_identify(&other).unwrap().is_empty());
1367        assert_eq!(
1368            responder.remote_identity().map(|(hash, _)| hash),
1369            Some(my_identity.hash())
1370        );
1371
1372        // Malformed repeats are still rejected cryptographically.
1373        let mut rng_bad = make_rng(0x70);
1374        let malformed = initiator
1375            .encrypt(b"invalid identify", &mut rng_bad)
1376            .unwrap();
1377        assert!(responder.handle_identify(&malformed).is_err());
1378        assert_eq!(
1379            responder.remote_identity().map(|(hash, _)| hash),
1380            Some(my_identity.hash())
1381        );
1382    }
1383
1384    #[test]
1385    fn test_aes128_mode_handshake() {
1386        let mut rng_id = make_rng(0x01);
1387        let dest_sig_prv = Ed25519PrivateKey::generate(&mut rng_id);
1388        let dest_sig_pub_bytes = dest_sig_prv.public_key().public_bytes();
1389        let dest_hash = [0xDD; 16];
1390
1391        let mut rng_init = make_rng(0x10);
1392        let (mut initiator, request_data) = LinkEngine::new_initiator(
1393            &dest_hash,
1394            1,
1395            LinkMode::Aes128Cbc,
1396            Some(500),
1397            100.0,
1398            &mut rng_init,
1399        );
1400        let mut hashable = Vec::new();
1401        hashable.push(0x00);
1402        hashable.push(0x00);
1403        hashable.extend_from_slice(&dest_hash);
1404        hashable.push(0x00);
1405        hashable.extend_from_slice(&request_data);
1406        initiator.set_link_id_from_hashable(&hashable, request_data.len());
1407
1408        let mut rng_resp = make_rng(0x20);
1409        let (mut responder, lrproof_data) = LinkEngine::new_responder(
1410            &dest_sig_prv,
1411            &dest_sig_pub_bytes,
1412            &request_data,
1413            &hashable,
1414            &dest_hash,
1415            1,
1416            100.5,
1417            &mut rng_resp,
1418        )
1419        .unwrap();
1420
1421        let mut rng_lrrtt = make_rng(0x30);
1422        let (lrrtt_encrypted, _) = initiator
1423            .handle_lrproof(&lrproof_data, &dest_sig_pub_bytes, 100.8, &mut rng_lrrtt)
1424            .unwrap();
1425        responder.handle_lrrtt(&lrrtt_encrypted, 101.0).unwrap();
1426
1427        assert_eq!(initiator.state(), LinkState::Active);
1428        assert_eq!(responder.state(), LinkState::Active);
1429        assert_eq!(initiator.mode(), LinkMode::Aes128Cbc);
1430
1431        // Verify encrypt/decrypt works
1432        let mut rng_enc = make_rng(0x40);
1433        let ct = initiator.encrypt(b"AES128 test", &mut rng_enc).unwrap();
1434        let pt = responder.decrypt(&ct).unwrap();
1435        assert_eq!(pt, b"AES128 test");
1436    }
1437}