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, LINK_MTU_MAX,
16 LINK_MTU_MIN, MTU, PATHFINDER_M,
17};
18
19pub use types::{LinkAction, LinkError, LinkId, LinkMode, LinkState, TeardownReason};
20
21use crypto::{create_session_token, link_decrypt, link_decrypt_chunks, link_encrypt};
22use handshake::{
23 build_linkrequest_data, compute_link_id, pack_rtt, parse_linkrequest_data,
24 perform_key_exchange, unpack_rtt, validate_lrproof,
25};
26use keepalive::{
27 compute_establishment_timeout, compute_keepalive, compute_stale_time, is_establishment_timeout,
28 should_go_stale, should_send_keepalive,
29};
30
31pub struct LinkEngine {
36 link_id: LinkId,
37 state: LinkState,
38 is_initiator: bool,
39 mode: LinkMode,
40
41 prv: X25519PrivateKey,
43 sig_prv: Ed25519PrivateKey,
44
45 peer_pub_bytes: Option<[u8; 32]>,
47 peer_sig_pub_bytes: Option<[u8; 32]>,
48
49 derived_key: Option<Vec<u8>>,
51 token: Option<Token>,
52
53 request_time: f64,
55 activated_at: Option<f64>,
56 last_inbound: f64,
57 last_outbound: f64,
58 last_keepalive: f64,
59 last_proof: f64,
60 rtt: Option<f64>,
61 keepalive_interval: f64,
62 stale_time: f64,
63 establishment_timeout: f64,
64 expected_hops: u8,
65 rebalanced_at: Option<f64>,
67
68 tx_packets: u64,
70 rx_packets: u64,
71 tx_bytes: u64,
72 rx_bytes: u64,
73
74 remote_identity: Option<([u8; 16], [u8; 64])>,
76 destination_hash: [u8; 16],
77
78 mtu: u32,
80 mdu: usize,
81}
82
83impl LinkEngine {
84 pub fn new_initiator(
89 dest_hash: &[u8; 16],
90 hops: u8,
91 mode: LinkMode,
92 mtu: Option<u32>,
93 now: f64,
94 rng: &mut dyn Rng,
95 ) -> (Self, Vec<u8>) {
96 let prv = X25519PrivateKey::generate(rng);
97 let pub_bytes = prv.public_key().public_bytes();
98 let sig_prv = Ed25519PrivateKey::generate(rng);
99 let sig_pub_bytes = sig_prv.public_key().public_bytes();
100
101 let request_data = build_linkrequest_data(&pub_bytes, &sig_pub_bytes, mtu, mode);
102
103 let link_mtu = effective_link_mtu(mtu);
104
105 let engine = LinkEngine {
106 link_id: [0u8; 16], state: LinkState::Pending,
108 is_initiator: true,
109 mode,
110 prv,
111 sig_prv,
112 peer_pub_bytes: None,
113 peer_sig_pub_bytes: None,
114 derived_key: None,
115 token: None,
116 request_time: now,
117 activated_at: None,
118 last_inbound: now,
119 last_outbound: now,
120 last_keepalive: now,
121 last_proof: 0.0,
122 rtt: None,
123 keepalive_interval: LINK_KEEPALIVE_MAX,
124 stale_time: LINK_KEEPALIVE_MAX * 2.0,
125 establishment_timeout: compute_establishment_timeout(
126 LINK_ESTABLISHMENT_TIMEOUT_PER_HOP,
127 hops,
128 ),
129 expected_hops: if hops < PATHFINDER_M {
130 hops
131 } else {
132 PATHFINDER_M
133 },
134 rebalanced_at: None,
135 tx_packets: 0,
136 rx_packets: 0,
137 tx_bytes: 0,
138 rx_bytes: 0,
139 remote_identity: None,
140 destination_hash: *dest_hash,
141 mtu: link_mtu,
142 mdu: compute_mdu(link_mtu as usize),
143 };
144
145 (engine, request_data)
146 }
147
148 pub fn set_link_id_from_hashable(&mut self, hashable_part: &[u8], data_len: usize) {
153 let extra = data_len.saturating_sub(LINK_ECPUBSIZE);
154 self.link_id = compute_link_id(hashable_part, extra);
155 }
156
157 #[allow(clippy::too_many_arguments)]
162 pub fn new_responder(
163 owner_sig_prv: &Ed25519PrivateKey,
164 owner_sig_pub_bytes: &[u8; 32],
165 linkrequest_data: &[u8],
166 hashable_part: &[u8],
167 dest_hash: &[u8; 16],
168 hops: u8,
169 now: f64,
170 rng: &mut dyn Rng,
171 ) -> Result<(Self, Vec<u8>), LinkError> {
172 let (peer_pub, peer_sig_pub, peer_mtu, mode) = parse_linkrequest_data(linkrequest_data)?;
173
174 let extra = linkrequest_data.len().saturating_sub(LINK_ECPUBSIZE);
175 let link_id = compute_link_id(hashable_part, extra);
176
177 let prv = X25519PrivateKey::generate(rng);
179 let pub_bytes = prv.public_key().public_bytes();
180 let sig_pub_bytes = *owner_sig_pub_bytes;
181
182 let derived_key = perform_key_exchange(&prv, &peer_pub, &link_id, mode)?;
184 let token = create_session_token(&derived_key)?;
185
186 let link_mtu = effective_link_mtu(peer_mtu);
187
188 let lrproof_data = handshake::build_lrproof(
190 &link_id,
191 &pub_bytes,
192 &sig_pub_bytes,
193 owner_sig_prv,
194 peer_mtu,
195 mode,
196 );
197
198 let engine = LinkEngine {
199 link_id,
200 state: LinkState::Handshake,
201 is_initiator: false,
202 mode,
203 prv,
204 sig_prv: Ed25519PrivateKey::from_bytes(&owner_sig_prv.private_bytes()),
205 peer_pub_bytes: Some(peer_pub),
206 peer_sig_pub_bytes: Some(peer_sig_pub),
207 derived_key: Some(derived_key),
208 token: Some(token),
209 request_time: now,
210 activated_at: None,
211 last_inbound: now,
212 last_outbound: now,
213 last_keepalive: now,
214 last_proof: 0.0,
215 rtt: None,
216 keepalive_interval: LINK_KEEPALIVE_MAX,
217 stale_time: LINK_KEEPALIVE_MAX * 2.0,
218 establishment_timeout: compute_establishment_timeout(
219 LINK_ESTABLISHMENT_TIMEOUT_PER_HOP,
220 hops,
221 ),
222 expected_hops: PATHFINDER_M,
223 rebalanced_at: None,
224 tx_packets: 0,
225 rx_packets: 0,
226 tx_bytes: 0,
227 rx_bytes: 0,
228 remote_identity: None,
229 destination_hash: *dest_hash,
230 mtu: link_mtu,
231 mdu: compute_mdu(link_mtu as usize),
232 };
233
234 Ok((engine, lrproof_data))
235 }
236
237 pub fn handle_lrproof(
242 &mut self,
243 proof_data: &[u8],
244 peer_sig_pub_bytes: &[u8; 32],
245 now: f64,
246 rng: &mut dyn Rng,
247 ) -> Result<(Vec<u8>, Vec<LinkAction>), LinkError> {
248 self.handle_lrproof_with_hops(proof_data, peer_sig_pub_bytes, None, now, rng)
249 }
250
251 pub fn handle_lrproof_with_hops(
253 &mut self,
254 proof_data: &[u8],
255 peer_sig_pub_bytes: &[u8; 32],
256 packet_hops: Option<u8>,
257 now: f64,
258 rng: &mut dyn Rng,
259 ) -> Result<(Vec<u8>, Vec<LinkAction>), LinkError> {
260 if self.state != LinkState::Pending || !self.is_initiator {
261 return Err(LinkError::InvalidState);
262 }
263
264 let peer_sig_pub = Ed25519PublicKey::from_bytes(peer_sig_pub_bytes)
265 .map_err(|_| LinkError::InvalidSignature)?;
266
267 let (peer_pub, confirmed_mtu, confirmed_mode) =
268 validate_lrproof(proof_data, &self.link_id, &peer_sig_pub, peer_sig_pub_bytes)?;
269
270 if confirmed_mode != self.mode {
271 return Err(LinkError::UnsupportedMode);
272 }
273
274 if let Some(hops) = packet_hops {
275 if hops != self.expected_hops && self.rebalanced_at.is_none() {
276 self.rebalanced_at = Some(now);
277 self.expected_hops = hops;
278 }
279 }
280
281 self.peer_pub_bytes = Some(peer_pub);
282 self.peer_sig_pub_bytes = Some(*peer_sig_pub_bytes);
283
284 let derived_key = perform_key_exchange(&self.prv, &peer_pub, &self.link_id, self.mode)?;
286 let token = create_session_token(&derived_key)?;
287
288 self.derived_key = Some(derived_key);
289 self.token = Some(token);
290
291 if confirmed_mtu.is_some() {
293 let mtu = effective_link_mtu(confirmed_mtu);
294 self.mtu = mtu;
295 self.mdu = compute_mdu(mtu as usize);
296 }
297
298 let rtt = now - self.request_time;
300 self.rtt = Some(rtt);
301 self.state = LinkState::Active;
302 self.activated_at = Some(now);
303 self.last_inbound = now;
304 self.update_keepalive();
305
306 let rtt_packed = pack_rtt(rtt);
308 let rtt_encrypted = self.encrypt(&rtt_packed, rng)?;
309
310 let actions = vec![
311 LinkAction::StateChanged {
312 link_id: self.link_id,
313 new_state: LinkState::Active,
314 reason: None,
315 },
316 LinkAction::LinkEstablished {
317 link_id: self.link_id,
318 rtt,
319 is_initiator: true,
320 },
321 ];
322
323 Ok((rtt_encrypted, actions))
324 }
325
326 pub fn handle_lrrtt(
330 &mut self,
331 encrypted_data: &[u8],
332 now: f64,
333 ) -> Result<Vec<LinkAction>, LinkError> {
334 self.handle_lrrtt_with_hops(encrypted_data, None, now)
335 }
336
337 pub fn handle_lrrtt_with_hops(
340 &mut self,
341 encrypted_data: &[u8],
342 packet_hops: Option<u8>,
343 now: f64,
344 ) -> Result<Vec<LinkAction>, LinkError> {
345 if self.state != LinkState::Handshake || self.is_initiator {
346 return Err(LinkError::InvalidState);
347 }
348
349 let plaintext = self.decrypt(encrypted_data)?;
350 let initiator_rtt = unpack_rtt(&plaintext).ok_or(LinkError::InvalidData)?;
351 if let Some(hops) = packet_hops {
352 self.expected_hops = hops;
353 }
354
355 let measured_rtt = now - self.request_time;
356 let rtt = measured_rtt.max(initiator_rtt);
358
359 self.rtt = Some(rtt);
360 self.state = LinkState::Active;
361 self.activated_at = Some(now);
362 self.last_inbound = now;
363 self.update_keepalive();
364
365 let actions = vec![
366 LinkAction::StateChanged {
367 link_id: self.link_id,
368 new_state: LinkState::Active,
369 reason: None,
370 },
371 LinkAction::LinkEstablished {
372 link_id: self.link_id,
373 rtt,
374 is_initiator: false,
375 },
376 ];
377
378 Ok(actions)
379 }
380
381 pub fn encrypt(&self, plaintext: &[u8], rng: &mut dyn Rng) -> Result<Vec<u8>, LinkError> {
383 let token = self.token.as_ref().ok_or(LinkError::NoSessionKey)?;
384 Ok(link_encrypt(token, plaintext, rng))
385 }
386
387 pub fn decrypt(&self, ciphertext: &[u8]) -> Result<Vec<u8>, LinkError> {
389 let token = self.token.as_ref().ok_or(LinkError::NoSessionKey)?;
390 link_decrypt(token, ciphertext)
391 }
392
393 pub fn decrypt_chunks(&self, chunks: &[&[u8]], skip: usize) -> Result<Vec<u8>, LinkError> {
396 let token = self.token.as_ref().ok_or(LinkError::NoSessionKey)?;
397 link_decrypt_chunks(token, chunks, skip)
398 }
399
400 pub fn sign_packet_hash(&self, packet_hash: &[u8; 32]) -> [u8; 64] {
402 self.sig_prv.sign(packet_hash)
403 }
404
405 pub fn validate_packet_proof(&self, packet_hash: &[u8; 32], signature: &[u8; 64]) -> bool {
407 self.peer_sig_pub_bytes
408 .and_then(|bytes| Ed25519PublicKey::from_bytes(&bytes).ok())
409 .map(|key| key.verify(signature, packet_hash))
410 .unwrap_or(false)
411 }
412
413 pub fn build_identify(
415 &self,
416 identity: &rns_crypto::identity::Identity,
417 rng: &mut dyn Rng,
418 ) -> Result<Vec<u8>, LinkError> {
419 if self.state != LinkState::Active {
420 return Err(LinkError::InvalidState);
421 }
422 let plaintext = identify::build_identify_data(identity, &self.link_id)?;
423 self.encrypt(&plaintext, rng)
424 }
425
426 pub fn handle_identify(&mut self, encrypted_data: &[u8]) -> Result<Vec<LinkAction>, LinkError> {
430 if self.state != LinkState::Active || self.is_initiator {
431 return Err(LinkError::InvalidState);
432 }
433
434 let plaintext = self.decrypt(encrypted_data)?;
435 let (identity_hash, public_key) =
436 identify::validate_identify_data(&plaintext, &self.link_id)?;
437 if self.remote_identity.is_some() {
438 return Ok(Vec::new());
439 }
440 self.remote_identity = Some((identity_hash, public_key));
441
442 Ok(alloc::vec![LinkAction::RemoteIdentified {
443 link_id: self.link_id,
444 identity_hash,
445 public_key,
446 }])
447 }
448
449 pub fn record_inbound(&mut self, now: f64) -> Vec<LinkAction> {
453 self.last_inbound = now;
454 if self.state == LinkState::Stale {
455 self.state = LinkState::Active;
456 return alloc::vec![LinkAction::StateChanged {
457 link_id: self.link_id,
458 new_state: LinkState::Active,
459 reason: None,
460 }];
461 }
462 Vec::new()
463 }
464
465 pub fn record_proof(&mut self, now: f64) {
467 self.last_proof = now;
468 }
469
470 pub fn record_outbound(&mut self, now: f64, is_keepalive: bool) {
472 self.last_outbound = now;
473 if is_keepalive {
474 self.last_keepalive = now;
475 }
476 }
477
478 pub fn record_inbound_traffic(&mut self, data_len: usize) {
480 if self.state != LinkState::Closed {
481 self.rx_packets = self.rx_packets.saturating_add(1);
482 self.rx_bytes = self.rx_bytes.saturating_add(data_len as u64);
483 }
484 }
485
486 pub fn record_outbound_traffic(&mut self, data_len: usize) {
488 self.tx_packets = self.tx_packets.saturating_add(1);
489 self.tx_bytes = self.tx_bytes.saturating_add(data_len as u64);
490 }
491
492 pub fn tick(&mut self, now: f64) -> Vec<LinkAction> {
494 let mut actions = Vec::new();
495
496 match self.state {
497 LinkState::Pending | LinkState::Handshake => {
498 if is_establishment_timeout(self.request_time, self.establishment_timeout, now) {
499 self.state = LinkState::Closed;
500 actions.push(LinkAction::StateChanged {
501 link_id: self.link_id,
502 new_state: LinkState::Closed,
503 reason: Some(TeardownReason::Timeout),
504 });
505 }
506 }
507 LinkState::Active => {
508 let activated = self.activated_at.unwrap_or(0.0);
509 let last_inbound = self.last_inbound.max(self.last_proof).max(activated);
511
512 if should_go_stale(last_inbound, self.stale_time, now) {
513 self.state = LinkState::Stale;
514 actions.push(LinkAction::StateChanged {
515 link_id: self.link_id,
516 new_state: LinkState::Stale,
517 reason: None,
518 });
519 }
520 }
521 LinkState::Stale => {
522 self.state = LinkState::Closed;
524 actions.push(LinkAction::StateChanged {
525 link_id: self.link_id,
526 new_state: LinkState::Closed,
527 reason: Some(TeardownReason::Timeout),
528 });
529 }
530 LinkState::Closed => {}
531 }
532
533 actions
534 }
535
536 pub fn needs_keepalive(&self, now: f64) -> bool {
538 if self.state != LinkState::Active || !self.is_initiator {
539 return false;
540 }
541 let activated = self.activated_at.unwrap_or(0.0);
542 let last_inbound = self.last_inbound.max(self.last_proof).max(activated);
543
544 let inbound_quiet = now >= last_inbound + self.keepalive_interval;
549 let outbound_quiet = now >= self.last_outbound + self.keepalive_interval;
550 if !inbound_quiet && !outbound_quiet {
551 return false;
552 }
553
554 should_send_keepalive(self.last_keepalive, self.keepalive_interval, now)
555 }
556
557 pub fn should_reply_keepalive(&self, payload: &[u8], now: f64) -> bool {
564 self.state == LinkState::Active
565 && !self.is_initiator
566 && payload == [0xff]
567 && now >= self.last_outbound + self.keepalive_interval
568 }
569
570 pub fn teardown(&mut self) -> Vec<LinkAction> {
572 if self.state == LinkState::Closed {
573 return Vec::new();
574 }
575 self.state = LinkState::Closed;
576 let reason = if self.is_initiator {
577 TeardownReason::InitiatorClosed
578 } else {
579 TeardownReason::DestinationClosed
580 };
581 alloc::vec![LinkAction::StateChanged {
582 link_id: self.link_id,
583 new_state: LinkState::Closed,
584 reason: Some(reason),
585 }]
586 }
587
588 pub fn handle_teardown(&mut self) -> Vec<LinkAction> {
590 if self.state == LinkState::Closed {
591 return Vec::new();
592 }
593 self.state = LinkState::Closed;
594 let reason = if self.is_initiator {
595 TeardownReason::DestinationClosed
596 } else {
597 TeardownReason::InitiatorClosed
598 };
599 alloc::vec![LinkAction::StateChanged {
600 link_id: self.link_id,
601 new_state: LinkState::Closed,
602 reason: Some(reason),
603 }]
604 }
605
606 pub fn link_id(&self) -> &LinkId {
609 &self.link_id
610 }
611
612 pub fn state(&self) -> LinkState {
613 self.state
614 }
615
616 pub fn rtt(&self) -> Option<f64> {
617 self.rtt
618 }
619
620 pub fn mdu(&self) -> usize {
621 self.mdu
622 }
623
624 pub fn mtu(&self) -> u32 {
625 self.mtu
626 }
627
628 pub fn is_initiator(&self) -> bool {
629 self.is_initiator
630 }
631
632 pub fn mode(&self) -> LinkMode {
633 self.mode
634 }
635
636 pub fn remote_identity(&self) -> Option<&([u8; 16], [u8; 64])> {
637 self.remote_identity.as_ref()
638 }
639
640 pub fn destination_hash(&self) -> &[u8; 16] {
641 &self.destination_hash
642 }
643
644 pub fn expected_hops(&self) -> u8 {
645 self.expected_hops
646 }
647
648 pub fn rebalanced_at(&self) -> Option<f64> {
649 self.rebalanced_at
650 }
651
652 pub fn tx_packets(&self) -> u64 {
653 self.tx_packets
654 }
655
656 pub fn rx_packets(&self) -> u64 {
657 self.rx_packets
658 }
659
660 pub fn tx_bytes(&self) -> u64 {
661 self.tx_bytes
662 }
663
664 pub fn rx_bytes(&self) -> u64 {
665 self.rx_bytes
666 }
667
668 pub fn derived_key(&self) -> Option<&[u8]> {
670 self.derived_key.as_deref()
671 }
672
673 pub fn keepalive_interval(&self) -> f64 {
674 self.keepalive_interval
675 }
676
677 pub fn set_rtt(&mut self, rtt: f64) {
680 self.rtt = Some(rtt);
681 self.update_keepalive();
682 }
683
684 pub fn set_mtu(&mut self, mtu: u32) {
686 self.mtu = mtu;
687 self.mdu = compute_mdu(mtu as usize);
688 }
689
690 #[doc(hidden)]
691 pub fn clear_session_for_testing(&mut self) {
692 self.derived_key = None;
693 self.token = None;
694 }
695
696 fn update_keepalive(&mut self) {
699 if let Some(rtt) = self.rtt {
700 self.keepalive_interval = compute_keepalive(rtt);
701 self.stale_time = compute_stale_time(self.keepalive_interval);
702 }
703 }
704}
705
706fn compute_mdu(mtu: usize) -> usize {
712 use crate::constants::{AES128_BLOCKSIZE, HEADER_MINSIZE, IFAC_MIN_SIZE, TOKEN_OVERHEAD};
713 let numerator = mtu.saturating_sub(IFAC_MIN_SIZE + HEADER_MINSIZE + TOKEN_OVERHEAD);
714 ((numerator / AES128_BLOCKSIZE) * AES128_BLOCKSIZE).saturating_sub(1)
715}
716
717fn effective_link_mtu(signalled_mtu: Option<u32>) -> u32 {
721 signalled_mtu
722 .filter(|mtu| *mtu != 0)
723 .map(|mtu| mtu.clamp(LINK_MTU_MIN, LINK_MTU_MAX))
724 .unwrap_or(MTU as u32)
725}
726use alloc::vec;
727
728#[cfg(test)]
729mod tests {
730 use super::*;
731 use crate::constants::LINK_MDU;
732 use rns_crypto::FixedRng;
733
734 fn make_rng(seed: u8) -> FixedRng {
735 FixedRng::new(&[seed; 128])
736 }
737
738 fn active_link_pair() -> (LinkEngine, LinkEngine, Vec<u8>) {
739 let mut rng_id = make_rng(0x01);
740 let dest_sig_prv = Ed25519PrivateKey::generate(&mut rng_id);
741 let dest_sig_pub_bytes = dest_sig_prv.public_key().public_bytes();
742 let dest_hash = [0xDD; 16];
743
744 let mut rng_init = make_rng(0x10);
745 let (mut initiator, request_data) = LinkEngine::new_initiator(
746 &dest_hash,
747 1,
748 LinkMode::Aes256Cbc,
749 Some(500),
750 100.0,
751 &mut rng_init,
752 );
753 let mut hashable = vec![0x00, 0x00];
754 hashable.extend_from_slice(&dest_hash);
755 hashable.push(0x00);
756 hashable.extend_from_slice(&request_data);
757 initiator.set_link_id_from_hashable(&hashable, request_data.len());
758
759 let mut rng_resp = make_rng(0x20);
760 let (mut responder, lrproof_data) = LinkEngine::new_responder(
761 &dest_sig_prv,
762 &dest_sig_pub_bytes,
763 &request_data,
764 &hashable,
765 &dest_hash,
766 1,
767 100.5,
768 &mut rng_resp,
769 )
770 .unwrap();
771 let mut rng_lrrtt = make_rng(0x30);
772 let (lrrtt_encrypted, _) = initiator
773 .handle_lrproof(&lrproof_data, &dest_sig_pub_bytes, 100.8, &mut rng_lrrtt)
774 .unwrap();
775 responder.handle_lrrtt(&lrrtt_encrypted, 101.0).unwrap();
776
777 (initiator, responder, lrrtt_encrypted)
778 }
779
780 #[test]
781 fn test_compute_mdu_default() {
782 assert_eq!(compute_mdu(500), LINK_MDU);
783 }
784
785 #[test]
786 fn zero_mtu_signalling_falls_back_to_reticulum_mtu() {
787 let mut rng_id = make_rng(0x01);
788 let dest_sig_prv = Ed25519PrivateKey::generate(&mut rng_id);
789 let dest_sig_pub_bytes = dest_sig_prv.public_key().public_bytes();
790 let dest_hash = [0xDD; 16];
791
792 let mut rng_init = make_rng(0x10);
793 let (mut initiator, request_data) = LinkEngine::new_initiator(
794 &dest_hash,
795 1,
796 LinkMode::Aes256Cbc,
797 Some(0),
798 100.0,
799 &mut rng_init,
800 );
801 let mut hashable = vec![0x00, 0x00];
802 hashable.extend_from_slice(&dest_hash);
803 hashable.push(0x00);
804 hashable.extend_from_slice(&request_data);
805 initiator.set_link_id_from_hashable(&hashable, request_data.len());
806
807 let mut rng_resp = make_rng(0x20);
808 let (responder, lrproof_data) = LinkEngine::new_responder(
809 &dest_sig_prv,
810 &dest_sig_pub_bytes,
811 &request_data,
812 &hashable,
813 &dest_hash,
814 1,
815 100.5,
816 &mut rng_resp,
817 )
818 .unwrap();
819
820 let mut rng_lrrtt = make_rng(0x30);
821 initiator
822 .handle_lrproof(&lrproof_data, &dest_sig_pub_bytes, 100.8, &mut rng_lrrtt)
823 .unwrap();
824
825 assert_eq!(initiator.mtu(), MTU as u32);
826 assert_eq!(responder.mtu(), MTU as u32);
827 assert_eq!(initiator.mdu(), LINK_MDU);
828 assert_eq!(responder.mdu(), LINK_MDU);
829 }
830
831 #[test]
832 fn test_full_handshake() {
833 let mut rng_id = make_rng(0x01);
835 let dest_sig_prv = Ed25519PrivateKey::generate(&mut rng_id);
836 let dest_sig_pub_bytes = dest_sig_prv.public_key().public_bytes();
837
838 let dest_hash = [0xDD; 16];
839 let mode = LinkMode::Aes256Cbc;
840
841 let mut rng_init = make_rng(0x10);
843 let (mut initiator, request_data) =
844 LinkEngine::new_initiator(&dest_hash, 1, mode, Some(500), 100.0, &mut rng_init);
845 assert_eq!(initiator.state(), LinkState::Pending);
846
847 let mut hashable = Vec::new();
850 hashable.push(0x00); hashable.push(0x00); hashable.extend_from_slice(&dest_hash);
853 hashable.push(0x00); hashable.extend_from_slice(&request_data);
855
856 initiator.set_link_id_from_hashable(&hashable, request_data.len());
857 assert_ne!(initiator.link_id(), &[0u8; 16]);
858
859 let mut rng_resp = make_rng(0x20);
861 let (mut responder, lrproof_data) = LinkEngine::new_responder(
862 &dest_sig_prv,
863 &dest_sig_pub_bytes,
864 &request_data,
865 &hashable,
866 &dest_hash,
867 1,
868 100.5,
869 &mut rng_resp,
870 )
871 .unwrap();
872 assert_eq!(responder.state(), LinkState::Handshake);
873 assert_eq!(responder.link_id(), initiator.link_id());
874
875 let mut rng_lrrtt = make_rng(0x30);
877 assert!(initiator
878 .handle_lrproof_with_hops(
879 &[0xAA; 3],
880 &dest_sig_pub_bytes,
881 Some(8),
882 100.6,
883 &mut rng_lrrtt,
884 )
885 .is_err());
886 assert_eq!(initiator.expected_hops(), 1);
887 let mismatched_mode_proof = handshake::build_lrproof(
888 initiator.link_id(),
889 lrproof_data[64..96].try_into().unwrap(),
890 &dest_sig_pub_bytes,
891 &dest_sig_prv,
892 Some(500),
893 LinkMode::Aes128Cbc,
894 );
895 assert_eq!(
896 initiator
897 .handle_lrproof_with_hops(
898 &mismatched_mode_proof,
899 &dest_sig_pub_bytes,
900 Some(9),
901 100.65,
902 &mut rng_lrrtt,
903 )
904 .unwrap_err(),
905 LinkError::UnsupportedMode,
906 );
907 assert_eq!(initiator.expected_hops(), 1);
908 let mut invalid_lrproof = lrproof_data.clone();
909 invalid_lrproof[0] ^= 0x01;
910 assert!(initiator
911 .handle_lrproof_with_hops(
912 &invalid_lrproof,
913 &dest_sig_pub_bytes,
914 Some(7),
915 100.7,
916 &mut rng_lrrtt,
917 )
918 .is_err());
919 assert_eq!(initiator.expected_hops(), 1);
920 let (lrrtt_encrypted, actions) = initiator
921 .handle_lrproof_with_hops(
922 &lrproof_data,
923 &dest_sig_pub_bytes,
924 Some(4),
925 100.8,
926 &mut rng_lrrtt,
927 )
928 .unwrap();
929 assert_eq!(initiator.state(), LinkState::Active);
930 assert!(initiator.rtt().is_some());
931 assert_eq!(initiator.rebalanced_at(), Some(100.8));
932 assert_eq!(actions.len(), 2); let actions = responder
936 .handle_lrrtt_with_hops(&lrrtt_encrypted, Some(4), 101.0)
937 .unwrap();
938 assert_eq!(responder.state(), LinkState::Active);
939 assert_eq!(initiator.expected_hops(), 4);
940 assert_eq!(responder.expected_hops(), 4);
941 assert_eq!(responder.rebalanced_at(), None);
942
943 initiator.state = LinkState::Pending;
946 let mut repeated_rng = make_rng(0x31);
947 initiator
948 .handle_lrproof_with_hops(
949 &lrproof_data,
950 &dest_sig_pub_bytes,
951 Some(6),
952 101.1,
953 &mut repeated_rng,
954 )
955 .unwrap();
956 assert_eq!(initiator.expected_hops(), 4);
957 assert_eq!(initiator.rebalanced_at(), Some(100.8));
958
959 initiator.record_outbound_traffic(48);
960 initiator.record_inbound_traffic(32);
961 assert_eq!((initiator.tx_packets(), initiator.tx_bytes()), (1, 48));
962 assert_eq!((initiator.rx_packets(), initiator.rx_bytes()), (1, 32));
963 assert!(responder.rtt().is_some());
964 assert_eq!(actions.len(), 2);
965 }
966
967 #[test]
968 fn test_encrypt_decrypt_after_handshake() {
969 let mut rng_id = make_rng(0x01);
970 let dest_sig_prv = Ed25519PrivateKey::generate(&mut rng_id);
971 let dest_sig_pub_bytes = dest_sig_prv.public_key().public_bytes();
972 let dest_hash = [0xDD; 16];
973
974 let mut rng_init = make_rng(0x10);
975 let (mut initiator, request_data) = LinkEngine::new_initiator(
976 &dest_hash,
977 1,
978 LinkMode::Aes256Cbc,
979 Some(500),
980 100.0,
981 &mut rng_init,
982 );
983 let mut hashable = Vec::new();
984 hashable.push(0x00);
985 hashable.push(0x00);
986 hashable.extend_from_slice(&dest_hash);
987 hashable.push(0x00);
988 hashable.extend_from_slice(&request_data);
989 initiator.set_link_id_from_hashable(&hashable, request_data.len());
990
991 let mut rng_resp = make_rng(0x20);
992 let (mut responder, lrproof_data) = LinkEngine::new_responder(
993 &dest_sig_prv,
994 &dest_sig_pub_bytes,
995 &request_data,
996 &hashable,
997 &dest_hash,
998 1,
999 100.5,
1000 &mut rng_resp,
1001 )
1002 .unwrap();
1003
1004 let mut rng_lrrtt = make_rng(0x30);
1005 let (lrrtt_encrypted, _) = initiator
1006 .handle_lrproof(&lrproof_data, &dest_sig_pub_bytes, 100.8, &mut rng_lrrtt)
1007 .unwrap();
1008 responder.handle_lrrtt(&lrrtt_encrypted, 101.0).unwrap();
1009
1010 let mut rng_enc = make_rng(0x40);
1012 let plaintext = b"Hello over encrypted link!";
1013 let ciphertext = initiator.encrypt(plaintext, &mut rng_enc).unwrap();
1014 let decrypted = responder.decrypt(&ciphertext).unwrap();
1015 assert_eq!(decrypted, plaintext);
1016
1017 let mut rng_enc2 = make_rng(0x50);
1019 let ciphertext2 = responder.encrypt(b"Reply!", &mut rng_enc2).unwrap();
1020 let decrypted2 = initiator.decrypt(&ciphertext2).unwrap();
1021 assert_eq!(decrypted2, b"Reply!");
1022 }
1023
1024 #[test]
1025 fn test_tick_establishment_timeout() {
1026 let mut rng = make_rng(0x10);
1027 let dest_hash = [0xDD; 16];
1028 let (mut engine, _) =
1029 LinkEngine::new_initiator(&dest_hash, 1, LinkMode::Aes256Cbc, None, 100.0, &mut rng);
1030 let actions = engine.tick(110.0);
1034 assert!(actions.is_empty());
1035
1036 let actions = engine.tick(113.0);
1038 assert_eq!(actions.len(), 1);
1039 assert_eq!(engine.state(), LinkState::Closed);
1040 }
1041
1042 #[test]
1043 fn test_tick_stale_and_close() {
1044 let mut rng_id = make_rng(0x01);
1045 let dest_sig_prv = Ed25519PrivateKey::generate(&mut rng_id);
1046 let dest_sig_pub_bytes = dest_sig_prv.public_key().public_bytes();
1047 let dest_hash = [0xDD; 16];
1048
1049 let mut rng_init = make_rng(0x10);
1050 let (mut initiator, request_data) = LinkEngine::new_initiator(
1051 &dest_hash,
1052 1,
1053 LinkMode::Aes256Cbc,
1054 Some(500),
1055 100.0,
1056 &mut rng_init,
1057 );
1058 let mut hashable = Vec::new();
1059 hashable.push(0x00);
1060 hashable.push(0x00);
1061 hashable.extend_from_slice(&dest_hash);
1062 hashable.push(0x00);
1063 hashable.extend_from_slice(&request_data);
1064 initiator.set_link_id_from_hashable(&hashable, request_data.len());
1065
1066 let mut rng_resp = make_rng(0x20);
1067 let (_, lrproof_data) = LinkEngine::new_responder(
1068 &dest_sig_prv,
1069 &dest_sig_pub_bytes,
1070 &request_data,
1071 &hashable,
1072 &dest_hash,
1073 1,
1074 100.5,
1075 &mut rng_resp,
1076 )
1077 .unwrap();
1078
1079 let mut rng_lrrtt = make_rng(0x30);
1080 initiator
1081 .handle_lrproof(&lrproof_data, &dest_sig_pub_bytes, 100.8, &mut rng_lrrtt)
1082 .unwrap();
1083 assert_eq!(initiator.state(), LinkState::Active);
1084
1085 let stale_time = initiator.stale_time;
1087 let actions = initiator.tick(100.8 + stale_time + 1.0);
1088 assert_eq!(initiator.state(), LinkState::Stale);
1089 assert_eq!(actions.len(), 1);
1090
1091 let actions = initiator.tick(100.8 + stale_time + 2.0);
1093 assert_eq!(initiator.state(), LinkState::Closed);
1094 assert_eq!(actions.len(), 1);
1095 }
1096
1097 #[test]
1098 fn test_needs_keepalive() {
1099 let mut rng_id = make_rng(0x01);
1100 let dest_sig_prv = Ed25519PrivateKey::generate(&mut rng_id);
1101 let dest_sig_pub_bytes = dest_sig_prv.public_key().public_bytes();
1102 let dest_hash = [0xDD; 16];
1103
1104 let mut rng_init = make_rng(0x10);
1105 let (mut initiator, request_data) = LinkEngine::new_initiator(
1106 &dest_hash,
1107 1,
1108 LinkMode::Aes256Cbc,
1109 Some(500),
1110 100.0,
1111 &mut rng_init,
1112 );
1113 let mut hashable = Vec::new();
1114 hashable.push(0x00);
1115 hashable.push(0x00);
1116 hashable.extend_from_slice(&dest_hash);
1117 hashable.push(0x00);
1118 hashable.extend_from_slice(&request_data);
1119 initiator.set_link_id_from_hashable(&hashable, request_data.len());
1120
1121 let mut rng_resp = make_rng(0x20);
1122 let (_, lrproof_data) = LinkEngine::new_responder(
1123 &dest_sig_prv,
1124 &dest_sig_pub_bytes,
1125 &request_data,
1126 &hashable,
1127 &dest_hash,
1128 1,
1129 100.5,
1130 &mut rng_resp,
1131 )
1132 .unwrap();
1133
1134 let mut rng_lrrtt = make_rng(0x30);
1135 initiator
1136 .handle_lrproof(&lrproof_data, &dest_sig_pub_bytes, 100.8, &mut rng_lrrtt)
1137 .unwrap();
1138
1139 let ka = initiator.keepalive_interval();
1140 assert!(!initiator.needs_keepalive(100.8 + ka - 1.0));
1142 assert!(initiator.needs_keepalive(100.8 + ka + 1.0));
1144 }
1145
1146 #[test]
1147 fn initiator_probes_when_outbound_is_quiet_despite_recent_inbound() {
1148 let (mut initiator, _, _) = active_link_pair();
1149 let keepalive = initiator.keepalive_interval();
1150 let now = 1_000.0;
1151
1152 initiator.record_inbound(now - 0.1);
1153 initiator.record_outbound(now - keepalive - 0.1, false);
1154
1155 assert!(initiator.needs_keepalive(now));
1156 }
1157
1158 #[test]
1159 fn initiator_probes_when_inbound_is_quiet_despite_recent_outbound() {
1160 let (mut initiator, _, _) = active_link_pair();
1161 let keepalive = initiator.keepalive_interval();
1162 let now = 1_000.0;
1163
1164 initiator.record_inbound(now - keepalive - 0.1);
1165 initiator.record_outbound(now - 0.1, false);
1166
1167 assert!(initiator.needs_keepalive(now));
1168 }
1169
1170 #[test]
1171 fn initiator_does_not_probe_when_both_directions_are_recent() {
1172 let (mut initiator, _, _) = active_link_pair();
1173 let now = 1_000.0;
1174
1175 initiator.record_inbound(now - 0.1);
1176 initiator.record_outbound(now - 0.1, false);
1177
1178 assert!(!initiator.needs_keepalive(now));
1179 }
1180
1181 #[test]
1182 fn test_needs_keepalive_responder() {
1183 let mut rng_id = make_rng(0x01);
1184 let dest_sig_prv = Ed25519PrivateKey::generate(&mut rng_id);
1185 let dest_sig_pub_bytes = dest_sig_prv.public_key().public_bytes();
1186 let dest_hash = [0xDD; 16];
1187
1188 let mut rng_init = make_rng(0x10);
1189 let (mut initiator, request_data) = LinkEngine::new_initiator(
1190 &dest_hash,
1191 1,
1192 LinkMode::Aes256Cbc,
1193 Some(500),
1194 100.0,
1195 &mut rng_init,
1196 );
1197 let mut hashable = Vec::new();
1198 hashable.push(0x00);
1199 hashable.push(0x00);
1200 hashable.extend_from_slice(&dest_hash);
1201 hashable.push(0x00);
1202 hashable.extend_from_slice(&request_data);
1203 initiator.set_link_id_from_hashable(&hashable, request_data.len());
1204
1205 let mut rng_resp = make_rng(0x20);
1206 let (mut responder, lrproof_data) = LinkEngine::new_responder(
1207 &dest_sig_prv,
1208 &dest_sig_pub_bytes,
1209 &request_data,
1210 &hashable,
1211 &dest_hash,
1212 1,
1213 100.5,
1214 &mut rng_resp,
1215 )
1216 .unwrap();
1217
1218 let mut rng_lrrtt = make_rng(0x30);
1219 let (lrrtt_encrypted, _) = initiator
1220 .handle_lrproof(&lrproof_data, &dest_sig_pub_bytes, 100.8, &mut rng_lrrtt)
1221 .unwrap();
1222 responder.handle_lrrtt(&lrrtt_encrypted, 101.0).unwrap();
1223
1224 let ka = responder.keepalive_interval();
1225 assert!(!responder.needs_keepalive(101.0 + ka - 1.0));
1227 assert!(!responder.needs_keepalive(101.0 + ka + 1.0));
1228 }
1229
1230 #[test]
1231 fn responder_replies_only_to_probe_and_only_when_outbound_is_quiet() {
1232 let (_, mut responder, _) = active_link_pair();
1233 let keepalive = responder.keepalive_interval();
1234 let now = 1_000.0;
1235
1236 responder.record_outbound(now - keepalive - 0.1, false);
1237 assert!(responder.should_reply_keepalive(&[0xff], now));
1238 assert!(!responder.should_reply_keepalive(&[0xfe], now));
1239 assert!(!responder.should_reply_keepalive(&[], now));
1240
1241 responder.record_outbound(now - 0.1, false);
1242 assert!(!responder.should_reply_keepalive(&[0xff], now));
1243 }
1244
1245 #[test]
1246 fn initiator_never_replies_to_keepalive_probe() {
1247 let (mut initiator, _, _) = active_link_pair();
1248 let keepalive = initiator.keepalive_interval();
1249 let now = 1_000.0;
1250 initiator.record_outbound(now - keepalive - 0.1, false);
1251
1252 assert!(!initiator.should_reply_keepalive(&[0xff], now));
1253 }
1254
1255 #[test]
1256 fn packet_delivery_proofs_are_bidirectional_and_peer_authenticated() {
1257 let (initiator, responder, _) = active_link_pair();
1258 let packet_hash = [0x42; 32];
1259
1260 let initiator_proof = initiator.sign_packet_hash(&packet_hash);
1261 assert!(responder.validate_packet_proof(&packet_hash, &initiator_proof));
1262 assert!(!initiator.validate_packet_proof(&packet_hash, &initiator_proof));
1263
1264 let responder_proof = responder.sign_packet_hash(&packet_hash);
1265 assert!(initiator.validate_packet_proof(&packet_hash, &responder_proof));
1266 assert!(!responder.validate_packet_proof(&packet_hash, &responder_proof));
1267 assert!(!initiator.validate_packet_proof(&[0x43; 32], &responder_proof));
1268 }
1269
1270 #[test]
1271 fn test_teardown() {
1272 let mut rng = make_rng(0x10);
1273 let (mut engine, _) =
1274 LinkEngine::new_initiator(&[0xDD; 16], 1, LinkMode::Aes256Cbc, None, 100.0, &mut rng);
1275 let actions = engine.teardown();
1276 assert_eq!(engine.state(), LinkState::Closed);
1277 assert_eq!(actions.len(), 1);
1278
1279 let actions = engine.teardown();
1281 assert!(actions.is_empty());
1282 }
1283
1284 #[test]
1285 fn test_handle_teardown() {
1286 let mut rng = make_rng(0x10);
1287 let (mut engine, _) =
1288 LinkEngine::new_initiator(&[0xDD; 16], 1, LinkMode::Aes256Cbc, None, 100.0, &mut rng);
1289 let actions = engine.handle_teardown();
1290 assert_eq!(engine.state(), LinkState::Closed);
1291 assert_eq!(actions.len(), 1);
1292 match &actions[0] {
1293 LinkAction::StateChanged { reason, .. } => {
1294 assert_eq!(*reason, Some(TeardownReason::DestinationClosed));
1295 }
1296 _ => panic!("Expected StateChanged"),
1297 }
1298 }
1299
1300 #[test]
1301 fn test_identify_over_link() {
1302 let mut rng_id = make_rng(0x01);
1303 let dest_sig_prv = Ed25519PrivateKey::generate(&mut rng_id);
1304 let dest_sig_pub_bytes = dest_sig_prv.public_key().public_bytes();
1305 let dest_hash = [0xDD; 16];
1306
1307 let mut rng_init = make_rng(0x10);
1308 let (mut initiator, request_data) = LinkEngine::new_initiator(
1309 &dest_hash,
1310 1,
1311 LinkMode::Aes256Cbc,
1312 Some(500),
1313 100.0,
1314 &mut rng_init,
1315 );
1316 let mut hashable = Vec::new();
1317 hashable.push(0x00);
1318 hashable.push(0x00);
1319 hashable.extend_from_slice(&dest_hash);
1320 hashable.push(0x00);
1321 hashable.extend_from_slice(&request_data);
1322 initiator.set_link_id_from_hashable(&hashable, request_data.len());
1323
1324 let mut rng_resp = make_rng(0x20);
1325 let (mut responder, lrproof_data) = LinkEngine::new_responder(
1326 &dest_sig_prv,
1327 &dest_sig_pub_bytes,
1328 &request_data,
1329 &hashable,
1330 &dest_hash,
1331 1,
1332 100.5,
1333 &mut rng_resp,
1334 )
1335 .unwrap();
1336
1337 let mut rng_lrrtt = make_rng(0x30);
1338 let (lrrtt_encrypted, _) = initiator
1339 .handle_lrproof(&lrproof_data, &dest_sig_pub_bytes, 100.8, &mut rng_lrrtt)
1340 .unwrap();
1341 responder.handle_lrrtt(&lrrtt_encrypted, 101.0).unwrap();
1342
1343 let mut rng_ident = make_rng(0x40);
1345 let my_identity = rns_crypto::identity::Identity::new(&mut rng_ident);
1346
1347 let mut rng_enc = make_rng(0x50);
1349 let identify_encrypted = initiator
1350 .build_identify(&my_identity, &mut rng_enc)
1351 .unwrap();
1352
1353 let actions = responder.handle_identify(&identify_encrypted).unwrap();
1354 assert_eq!(actions.len(), 1);
1355 match &actions[0] {
1356 LinkAction::RemoteIdentified {
1357 identity_hash,
1358 public_key,
1359 ..
1360 } => {
1361 assert_eq!(identity_hash, my_identity.hash());
1362 assert_eq!(public_key, &my_identity.get_public_key().unwrap());
1363 }
1364 _ => panic!("Expected RemoteIdentified"),
1365 }
1366
1367 let mut rng_repeat = make_rng(0x51);
1370 let repeated = initiator
1371 .build_identify(&my_identity, &mut rng_repeat)
1372 .unwrap();
1373 assert!(responder.handle_identify(&repeated).unwrap().is_empty());
1374
1375 let mut rng_other = make_rng(0x60);
1376 let other_identity = rns_crypto::identity::Identity::new(&mut rng_other);
1377 let mut rng_other_enc = make_rng(0x61);
1378 let other = initiator
1379 .build_identify(&other_identity, &mut rng_other_enc)
1380 .unwrap();
1381 assert!(responder.handle_identify(&other).unwrap().is_empty());
1382 assert_eq!(
1383 responder.remote_identity().map(|(hash, _)| hash),
1384 Some(my_identity.hash())
1385 );
1386
1387 let mut rng_bad = make_rng(0x70);
1389 let malformed = initiator
1390 .encrypt(b"invalid identify", &mut rng_bad)
1391 .unwrap();
1392 assert!(responder.handle_identify(&malformed).is_err());
1393 assert_eq!(
1394 responder.remote_identity().map(|(hash, _)| hash),
1395 Some(my_identity.hash())
1396 );
1397 }
1398
1399 #[test]
1400 fn test_aes128_mode_handshake() {
1401 let mut rng_id = make_rng(0x01);
1402 let dest_sig_prv = Ed25519PrivateKey::generate(&mut rng_id);
1403 let dest_sig_pub_bytes = dest_sig_prv.public_key().public_bytes();
1404 let dest_hash = [0xDD; 16];
1405
1406 let mut rng_init = make_rng(0x10);
1407 let (mut initiator, request_data) = LinkEngine::new_initiator(
1408 &dest_hash,
1409 1,
1410 LinkMode::Aes128Cbc,
1411 Some(500),
1412 100.0,
1413 &mut rng_init,
1414 );
1415 let mut hashable = Vec::new();
1416 hashable.push(0x00);
1417 hashable.push(0x00);
1418 hashable.extend_from_slice(&dest_hash);
1419 hashable.push(0x00);
1420 hashable.extend_from_slice(&request_data);
1421 initiator.set_link_id_from_hashable(&hashable, request_data.len());
1422
1423 let mut rng_resp = make_rng(0x20);
1424 let (mut responder, lrproof_data) = LinkEngine::new_responder(
1425 &dest_sig_prv,
1426 &dest_sig_pub_bytes,
1427 &request_data,
1428 &hashable,
1429 &dest_hash,
1430 1,
1431 100.5,
1432 &mut rng_resp,
1433 )
1434 .unwrap();
1435
1436 let mut rng_lrrtt = make_rng(0x30);
1437 let (lrrtt_encrypted, _) = initiator
1438 .handle_lrproof(&lrproof_data, &dest_sig_pub_bytes, 100.8, &mut rng_lrrtt)
1439 .unwrap();
1440 responder.handle_lrrtt(&lrrtt_encrypted, 101.0).unwrap();
1441
1442 assert_eq!(initiator.state(), LinkState::Active);
1443 assert_eq!(responder.state(), LinkState::Active);
1444 assert_eq!(initiator.mode(), LinkMode::Aes128Cbc);
1445
1446 let mut rng_enc = make_rng(0x40);
1448 let ct = initiator.encrypt(b"AES128 test", &mut rng_enc).unwrap();
1449 let pt = responder.decrypt(&ct).unwrap();
1450 assert_eq!(pt, b"AES128 test");
1451 }
1452
1453 fn handshake_pair_with_mtu(mtu: Option<u32>) -> (LinkEngine, LinkEngine) {
1456 let mut rng_id = make_rng(0x01);
1457 let dest_sig_prv = Ed25519PrivateKey::generate(&mut rng_id);
1458 let dest_sig_pub_bytes = dest_sig_prv.public_key().public_bytes();
1459 let dest_hash = [0xDD; 16];
1460
1461 let mut rng_init = make_rng(0x10);
1462 let (mut initiator, request_data) = LinkEngine::new_initiator(
1463 &dest_hash,
1464 1,
1465 LinkMode::Aes256Cbc,
1466 mtu,
1467 100.0,
1468 &mut rng_init,
1469 );
1470 let mut hashable = vec![0x00, 0x00];
1471 hashable.extend_from_slice(&dest_hash);
1472 hashable.push(0x00);
1473 hashable.extend_from_slice(&request_data);
1474 initiator.set_link_id_from_hashable(&hashable, request_data.len());
1475
1476 let mut rng_resp = make_rng(0x20);
1477 let (responder, lrproof_data) = LinkEngine::new_responder(
1478 &dest_sig_prv,
1479 &dest_sig_pub_bytes,
1480 &request_data,
1481 &hashable,
1482 &dest_hash,
1483 1,
1484 100.5,
1485 &mut rng_resp,
1486 )
1487 .unwrap();
1488
1489 let mut rng_lrrtt = make_rng(0x30);
1490 initiator
1491 .handle_lrproof(&lrproof_data, &dest_sig_pub_bytes, 100.8, &mut rng_lrrtt)
1492 .unwrap();
1493 (initiator, responder)
1494 }
1495
1496 fn raw_rtt_payload(rtt: f64) -> Vec<u8> {
1497 let mut data = vec![0xcb];
1498 data.extend_from_slice(&rtt.to_be_bytes());
1499 data
1500 }
1501
1502 #[test]
1503 fn compute_mdu_keeps_values_for_regular_mtus() {
1504 assert_eq!(compute_mdu(500), 431);
1505 assert_eq!(compute_mdu(508), 431);
1506 assert_eq!(compute_mdu(1064), 991);
1507 assert_eq!(compute_mdu(8192), 8111);
1508 assert_eq!(compute_mdu(262_144), 262_063);
1509 assert_eq!(compute_mdu(LINK_MTU_MAX as usize), 2_097_071);
1510 }
1511
1512 #[test]
1513 fn compute_mdu_saturates_for_small_mtus() {
1514 for mtu in [0usize, 1, 50, 67, 68, 83] {
1515 assert_eq!(compute_mdu(mtu), 0, "mtu {mtu}");
1516 }
1517 assert_eq!(compute_mdu(LINK_MTU_MIN as usize), 15);
1518 }
1519
1520 #[test]
1521 fn effective_link_mtu_is_clamped_and_mdu_monotone() {
1522 assert_eq!(effective_link_mtu(None), MTU as u32);
1523 assert_eq!(effective_link_mtu(Some(0)), MTU as u32);
1524 assert_eq!(effective_link_mtu(Some(1)), LINK_MTU_MIN);
1525 assert_eq!(effective_link_mtu(Some(50)), LINK_MTU_MIN);
1526 assert_eq!(effective_link_mtu(Some(83)), LINK_MTU_MIN);
1527 assert_eq!(effective_link_mtu(Some(84)), 84);
1528 assert_eq!(effective_link_mtu(Some(500)), 500);
1529 assert_eq!(effective_link_mtu(Some(LINK_MTU_MAX)), LINK_MTU_MAX);
1530 assert_eq!(effective_link_mtu(Some(u32::MAX)), LINK_MTU_MAX);
1531
1532 let mut prev = 0usize;
1533 for mtu in [1u32, 50, 83, 84, 500, LINK_MTU_MAX] {
1534 let mdu = compute_mdu(effective_link_mtu(Some(mtu)) as usize);
1535 assert!(mdu >= 15, "mtu {mtu} gave mdu {mdu}");
1536 assert!(mdu >= prev, "mdu not monotone at mtu {mtu}");
1537 prev = mdu;
1538 }
1539 }
1540
1541 #[test]
1542 fn small_signalled_mtu_is_raised_to_minimum_on_both_sides() {
1543 let (initiator, mut responder) = handshake_pair_with_mtu(Some(70));
1544 assert_eq!(responder.mtu(), LINK_MTU_MIN);
1545 assert_eq!(responder.mdu(), 15);
1546 assert_eq!(initiator.mtu(), LINK_MTU_MIN);
1547 assert_eq!(initiator.mdu(), 15);
1548 assert_eq!(initiator.state(), LinkState::Active);
1549
1550 let mut rng = make_rng(0x40);
1551 let lrrtt = initiator.encrypt(&pack_rtt(0.3), &mut rng).unwrap();
1552 responder.handle_lrrtt(&lrrtt, 101.0).unwrap();
1553 assert_eq!(responder.state(), LinkState::Active);
1554
1555 let ct = initiator.encrypt(b"hello", &mut rng).unwrap();
1556 assert_eq!(responder.decrypt(&ct).unwrap(), b"hello");
1557 }
1558
1559 #[test]
1560 fn signalled_mtu_edge_values_do_not_panic() {
1561 for mtu in [0u32, 1, 50, 83, 84, 500, LINK_MTU_MAX] {
1562 let (initiator, responder) = handshake_pair_with_mtu(Some(mtu));
1563 assert!(responder.mtu() >= LINK_MTU_MIN);
1564 assert!(responder.mdu() >= 15);
1565 assert_eq!(initiator.mtu(), responder.mtu());
1566 assert_eq!(initiator.mdu(), responder.mdu());
1567 }
1568 }
1569
1570 #[test]
1571 fn non_finite_or_negative_lrrtt_is_rejected() {
1572 for bad in [f64::NAN, f64::INFINITY, f64::NEG_INFINITY, -1.0] {
1573 let (initiator, mut responder) = handshake_pair_with_mtu(Some(500));
1574 let mut rng = make_rng(0x40);
1575 let lrrtt = initiator.encrypt(&raw_rtt_payload(bad), &mut rng).unwrap();
1576 assert!(
1577 matches!(
1578 responder.handle_lrrtt(&lrrtt, 101.0),
1579 Err(LinkError::InvalidData)
1580 ),
1581 "rtt {bad}"
1582 );
1583 assert_eq!(responder.state(), LinkState::Handshake);
1584 assert_eq!(responder.rtt(), None);
1585 assert!(responder.keepalive_interval.is_finite());
1586 assert!(responder.stale_time.is_finite());
1587
1588 let deadline = responder.request_time + responder.establishment_timeout;
1590 let actions = responder.tick(deadline + 1.0);
1591 assert_eq!(responder.state(), LinkState::Closed);
1592 assert_eq!(actions.len(), 1);
1593 }
1594 }
1595
1596 #[test]
1597 fn valid_lrrtt_updates_rtt_and_timers() {
1598 let (initiator, mut responder) = handshake_pair_with_mtu(Some(500));
1599 let mut rng = make_rng(0x40);
1600 let lrrtt = initiator.encrypt(&pack_rtt(0.9), &mut rng).unwrap();
1602 responder.handle_lrrtt(&lrrtt, 101.0).unwrap();
1603 assert_eq!(responder.state(), LinkState::Active);
1604 assert_eq!(responder.rtt(), Some(0.9));
1605 assert_eq!(responder.keepalive_interval, compute_keepalive(0.9));
1606 assert_eq!(
1607 responder.stale_time,
1608 compute_stale_time(compute_keepalive(0.9))
1609 );
1610 }
1611
1612 #[test]
1613 fn non_finite_rtt_keeps_timers_finite_and_link_goes_stale() {
1614 let (_, mut responder, _) = active_link_pair();
1615 for bad in [f64::NAN, f64::INFINITY, f64::NEG_INFINITY] {
1616 responder.set_rtt(bad);
1617 assert!(responder.keepalive_interval.is_finite(), "rtt {bad}");
1618 assert!(responder.stale_time.is_finite(), "rtt {bad}");
1619 assert!(responder.stale_time <= LINK_KEEPALIVE_MAX * 2.0);
1620 }
1621
1622 let stale_time = responder.stale_time;
1623 responder.tick(101.0 + stale_time + 1.0);
1624 assert_eq!(responder.state(), LinkState::Stale);
1625 }
1626}