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, stale_close_timeout,
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 peer_sig_pub: Option<Ed25519PublicKey>,
50
51 derived_key: Option<Vec<u8>>,
53 token: Option<Token>,
54
55 request_time: f64,
57 activated_at: Option<f64>,
58 last_inbound: f64,
59 last_outbound: f64,
60 last_keepalive: f64,
61 last_proof: f64,
62 rtt: Option<f64>,
63 keepalive_interval: f64,
64 stale_time: f64,
65 stale_since: Option<f64>,
68 establishment_timeout: f64,
69 expected_hops: u8,
70 rebalanced_at: Option<f64>,
72
73 tx_packets: u64,
75 rx_packets: u64,
76 tx_bytes: u64,
77 rx_bytes: u64,
78
79 remote_identity: Option<([u8; 16], [u8; 64])>,
81 destination_hash: [u8; 16],
82
83 mtu: u32,
85 mdu: usize,
86}
87
88impl LinkEngine {
89 pub fn new_initiator(
94 dest_hash: &[u8; 16],
95 hops: u8,
96 mode: LinkMode,
97 mtu: Option<u32>,
98 now: f64,
99 rng: &mut dyn Rng,
100 ) -> (Self, Vec<u8>) {
101 let prv = X25519PrivateKey::generate(rng);
102 let sig_prv = Ed25519PrivateKey::generate(rng);
103 Self::new_initiator_with_keys(dest_hash, hops, mode, mtu, now, prv, sig_prv)
104 }
105
106 pub fn new_initiator_stamped(
109 dest_hash: &[u8; 16],
110 hops: u8,
111 mode: LinkMode,
112 mtu: Option<u32>,
113 now: f64,
114 rng: &mut dyn Rng,
115 stamp_cost: crate::stamp::StampCost,
116 ) -> (Self, Vec<u8>) {
117 let sig_prv = Ed25519PrivateKey::generate(rng);
118 let sig_pub_bytes = sig_prv.public_key().public_bytes();
119 let mut material = [0u8; 48];
120 material[..16].copy_from_slice(dest_hash);
121 material[16..].copy_from_slice(&sig_pub_bytes);
122 let (prv, _public, _value) =
123 rns_crypto::identity::generate_stamped_key(rng, &material, stamp_cost);
124 Self::new_initiator_with_keys(dest_hash, hops, mode, mtu, now, prv, sig_prv)
125 }
126
127 #[allow(clippy::too_many_arguments)]
132 pub fn new_initiator_with_keys(
133 dest_hash: &[u8; 16],
134 hops: u8,
135 mode: LinkMode,
136 mtu: Option<u32>,
137 now: f64,
138 prv: X25519PrivateKey,
139 sig_prv: Ed25519PrivateKey,
140 ) -> (Self, Vec<u8>) {
141 let pub_bytes = prv.public_key().public_bytes();
142 let sig_pub_bytes = sig_prv.public_key().public_bytes();
143
144 let request_data = build_linkrequest_data(&pub_bytes, &sig_pub_bytes, mtu, mode);
145
146 let link_mtu = effective_link_mtu(mtu);
147
148 let engine = LinkEngine {
149 link_id: [0u8; 16], state: LinkState::Pending,
151 is_initiator: true,
152 mode,
153 prv,
154 sig_prv,
155 peer_pub_bytes: None,
156 peer_sig_pub_bytes: None,
157 peer_sig_pub: None,
158 derived_key: None,
159 token: None,
160 request_time: now,
161 activated_at: None,
162 last_inbound: now,
163 last_outbound: now,
164 last_keepalive: now,
165 last_proof: 0.0,
166 rtt: None,
167 keepalive_interval: LINK_KEEPALIVE_MAX,
168 stale_time: LINK_KEEPALIVE_MAX * 2.0,
169 stale_since: None,
170 establishment_timeout: compute_establishment_timeout(
171 LINK_ESTABLISHMENT_TIMEOUT_PER_HOP,
172 hops,
173 ),
174 expected_hops: if hops < PATHFINDER_M {
175 hops
176 } else {
177 PATHFINDER_M
178 },
179 rebalanced_at: None,
180 tx_packets: 0,
181 rx_packets: 0,
182 tx_bytes: 0,
183 rx_bytes: 0,
184 remote_identity: None,
185 destination_hash: *dest_hash,
186 mtu: link_mtu,
187 mdu: compute_mdu(link_mtu as usize),
188 };
189
190 (engine, request_data)
191 }
192
193 pub fn set_link_id_from_hashable(&mut self, hashable_part: &[u8], data_len: usize) {
198 let extra = data_len.saturating_sub(LINK_ECPUBSIZE);
199 self.link_id = compute_link_id(hashable_part, extra);
200 }
201
202 #[allow(clippy::too_many_arguments)]
207 pub fn new_responder(
208 owner_sig_prv: &Ed25519PrivateKey,
209 owner_sig_pub_bytes: &[u8; 32],
210 linkrequest_data: &[u8],
211 hashable_part: &[u8],
212 dest_hash: &[u8; 16],
213 hops: u8,
214 now: f64,
215 rng: &mut dyn Rng,
216 ) -> Result<(Self, Vec<u8>), LinkError> {
217 let (peer_pub, peer_sig_pub, peer_mtu, mode) = parse_linkrequest_data(linkrequest_data)?;
218
219 let extra = linkrequest_data.len().saturating_sub(LINK_ECPUBSIZE);
220 let link_id = compute_link_id(hashable_part, extra);
221
222 let prv = X25519PrivateKey::generate(rng);
224 let pub_bytes = prv.public_key().public_bytes();
225 let sig_pub_bytes = *owner_sig_pub_bytes;
226
227 let derived_key = perform_key_exchange(&prv, &peer_pub, &link_id, mode)?;
229 let token = create_session_token(&derived_key)?;
230
231 let link_mtu = effective_link_mtu(peer_mtu);
232
233 let lrproof_data = handshake::build_lrproof(
235 &link_id,
236 &pub_bytes,
237 &sig_pub_bytes,
238 owner_sig_prv,
239 peer_mtu,
240 mode,
241 );
242
243 let engine = LinkEngine {
244 link_id,
245 state: LinkState::Handshake,
246 is_initiator: false,
247 mode,
248 prv,
249 sig_prv: Ed25519PrivateKey::from_bytes(&owner_sig_prv.private_bytes()),
250 peer_pub_bytes: Some(peer_pub),
251 peer_sig_pub_bytes: Some(peer_sig_pub),
252 peer_sig_pub: Ed25519PublicKey::from_bytes(&peer_sig_pub).ok(),
253 derived_key: Some(derived_key),
254 token: Some(token),
255 request_time: now,
256 activated_at: None,
257 last_inbound: now,
258 last_outbound: now,
259 last_keepalive: now,
260 last_proof: 0.0,
261 rtt: None,
262 keepalive_interval: LINK_KEEPALIVE_MAX,
263 stale_time: LINK_KEEPALIVE_MAX * 2.0,
264 stale_since: None,
265 establishment_timeout: compute_establishment_timeout(
266 LINK_ESTABLISHMENT_TIMEOUT_PER_HOP,
267 hops,
268 ),
269 expected_hops: PATHFINDER_M,
270 rebalanced_at: None,
271 tx_packets: 0,
272 rx_packets: 0,
273 tx_bytes: 0,
274 rx_bytes: 0,
275 remote_identity: None,
276 destination_hash: *dest_hash,
277 mtu: link_mtu,
278 mdu: compute_mdu(link_mtu as usize),
279 };
280
281 Ok((engine, lrproof_data))
282 }
283
284 pub fn handle_lrproof(
289 &mut self,
290 proof_data: &[u8],
291 peer_sig_pub_bytes: &[u8; 32],
292 now: f64,
293 rng: &mut dyn Rng,
294 ) -> Result<(Vec<u8>, Vec<LinkAction>), LinkError> {
295 self.handle_lrproof_with_hops(proof_data, peer_sig_pub_bytes, None, now, rng)
296 }
297
298 pub fn handle_lrproof_with_hops(
300 &mut self,
301 proof_data: &[u8],
302 peer_sig_pub_bytes: &[u8; 32],
303 packet_hops: Option<u8>,
304 now: f64,
305 rng: &mut dyn Rng,
306 ) -> Result<(Vec<u8>, Vec<LinkAction>), LinkError> {
307 if self.state != LinkState::Pending || !self.is_initiator {
308 return Err(LinkError::InvalidState);
309 }
310
311 let peer_sig_pub = Ed25519PublicKey::from_bytes(peer_sig_pub_bytes)
312 .map_err(|_| LinkError::InvalidSignature)?;
313
314 let (peer_pub, confirmed_mtu, confirmed_mode) =
315 validate_lrproof(proof_data, &self.link_id, &peer_sig_pub, peer_sig_pub_bytes)?;
316
317 if confirmed_mode != self.mode {
318 return Err(LinkError::UnsupportedMode);
319 }
320
321 if let Some(hops) = packet_hops {
322 if hops != self.expected_hops && self.rebalanced_at.is_none() {
323 self.rebalanced_at = Some(now);
324 self.expected_hops = hops;
325 }
326 }
327
328 self.peer_pub_bytes = Some(peer_pub);
329 self.peer_sig_pub_bytes = Some(*peer_sig_pub_bytes);
330 self.peer_sig_pub = Some(peer_sig_pub);
331
332 let derived_key = perform_key_exchange(&self.prv, &peer_pub, &self.link_id, self.mode)?;
334 let token = create_session_token(&derived_key)?;
335
336 self.derived_key = Some(derived_key);
337 self.token = Some(token);
338
339 if confirmed_mtu.is_some() {
341 let mtu = effective_link_mtu(confirmed_mtu);
342 self.mtu = mtu;
343 self.mdu = compute_mdu(mtu as usize);
344 }
345
346 let rtt = now - self.request_time;
348 self.rtt = Some(rtt);
349 self.state = LinkState::Active;
350 self.activated_at = Some(now);
351 self.last_inbound = now;
352 self.update_keepalive();
353
354 let rtt_packed = pack_rtt(rtt);
356 let rtt_encrypted = self.encrypt(&rtt_packed, rng)?;
357
358 let actions = vec![
359 LinkAction::StateChanged {
360 link_id: self.link_id,
361 new_state: LinkState::Active,
362 reason: None,
363 },
364 LinkAction::LinkEstablished {
365 link_id: self.link_id,
366 rtt,
367 is_initiator: true,
368 },
369 ];
370
371 Ok((rtt_encrypted, actions))
372 }
373
374 pub fn handle_lrrtt(
378 &mut self,
379 encrypted_data: &[u8],
380 now: f64,
381 ) -> Result<Vec<LinkAction>, LinkError> {
382 self.handle_lrrtt_with_hops(encrypted_data, None, now)
383 }
384
385 pub fn handle_lrrtt_with_hops(
388 &mut self,
389 encrypted_data: &[u8],
390 packet_hops: Option<u8>,
391 now: f64,
392 ) -> Result<Vec<LinkAction>, LinkError> {
393 if self.state != LinkState::Handshake || self.is_initiator {
394 return Err(LinkError::InvalidState);
395 }
396
397 let plaintext = self.decrypt(encrypted_data)?;
398 let initiator_rtt = unpack_rtt(&plaintext).ok_or(LinkError::InvalidData)?;
399 if let Some(hops) = packet_hops {
400 self.expected_hops = hops;
401 }
402
403 let measured_rtt = now - self.request_time;
404 let rtt = measured_rtt.max(initiator_rtt);
406
407 self.rtt = Some(rtt);
408 self.state = LinkState::Active;
409 self.activated_at = Some(now);
410 self.last_inbound = now;
411 self.update_keepalive();
412
413 let actions = vec![
414 LinkAction::StateChanged {
415 link_id: self.link_id,
416 new_state: LinkState::Active,
417 reason: None,
418 },
419 LinkAction::LinkEstablished {
420 link_id: self.link_id,
421 rtt,
422 is_initiator: false,
423 },
424 ];
425
426 Ok(actions)
427 }
428
429 pub fn encrypt(&self, plaintext: &[u8], rng: &mut dyn Rng) -> Result<Vec<u8>, LinkError> {
431 let token = self.token.as_ref().ok_or(LinkError::NoSessionKey)?;
432 Ok(link_encrypt(token, plaintext, rng))
433 }
434
435 pub fn encrypt_untagged(
438 &self,
439 plaintext: &[u8],
440 rng: &mut dyn Rng,
441 ) -> Result<(Vec<u8>, rns_crypto::hmac::HmacSha256), LinkError> {
442 let token = self.token.as_ref().ok_or(LinkError::NoSessionKey)?;
443 Ok(token.encrypt_untagged(plaintext, rng))
444 }
445
446 pub fn can_encrypt(&self) -> bool {
448 self.token.is_some()
449 }
450
451 pub fn encrypted_len(&self, plaintext_len: usize) -> usize {
454 Token::token_len(plaintext_len)
455 }
456
457 pub fn encrypt_into(
460 &self,
461 plaintext: &[u8],
462 rng: &mut dyn Rng,
463 out: &mut [u8],
464 ) -> Result<(), LinkError> {
465 let token = self.token.as_ref().ok_or(LinkError::NoSessionKey)?;
466 token.encrypt_into(plaintext, rng, out);
467 Ok(())
468 }
469
470 pub fn decrypt(&self, ciphertext: &[u8]) -> Result<Vec<u8>, LinkError> {
472 let token = self.token.as_ref().ok_or(LinkError::NoSessionKey)?;
473 link_decrypt(token, ciphertext)
474 }
475
476 pub fn decrypt_into(&self, ciphertext: &[u8], out: &mut Vec<u8>) -> Result<(), LinkError> {
479 let token = self.token.as_ref().ok_or(LinkError::NoSessionKey)?;
480 token
481 .decrypt_into(ciphertext, out)
482 .map_err(|_| LinkError::CryptoError)
483 }
484
485 pub fn decrypt_chunks(&self, chunks: &[&[u8]], skip: usize) -> Result<Vec<u8>, LinkError> {
488 let token = self.token.as_ref().ok_or(LinkError::NoSessionKey)?;
489 link_decrypt_chunks(token, chunks, skip)
490 }
491
492 pub fn decrypt_stream(&self, skip: usize) -> Option<rns_crypto::token::TokenDecryptStream> {
494 Some(self.token.as_ref()?.decrypt_stream(skip))
495 }
496
497 pub fn sign_packet_hash(&self, packet_hash: &[u8; 32]) -> [u8; 64] {
499 self.sig_prv.sign(packet_hash)
500 }
501
502 pub fn signing_key(&self) -> &Ed25519PrivateKey {
504 &self.sig_prv
505 }
506
507 pub fn peer_signing_key(&self) -> Option<&Ed25519PublicKey> {
509 self.peer_sig_pub.as_ref()
510 }
511
512 pub fn validate_packet_proof(&self, packet_hash: &[u8; 32], signature: &[u8; 64]) -> bool {
514 self.peer_sig_pub
515 .as_ref()
516 .is_some_and(|key| key.verify(signature, packet_hash))
517 }
518
519 pub fn build_identify(
521 &self,
522 identity: &rns_crypto::identity::Identity,
523 rng: &mut dyn Rng,
524 ) -> Result<Vec<u8>, LinkError> {
525 if self.state != LinkState::Active {
526 return Err(LinkError::InvalidState);
527 }
528 let plaintext = identify::build_identify_data(identity, &self.link_id)?;
529 self.encrypt(&plaintext, rng)
530 }
531
532 pub fn handle_identify(&mut self, encrypted_data: &[u8]) -> Result<Vec<LinkAction>, LinkError> {
536 if self.state != LinkState::Active || self.is_initiator {
537 return Err(LinkError::InvalidState);
538 }
539
540 let plaintext = self.decrypt(encrypted_data)?;
541 let (identity_hash, public_key) =
542 identify::validate_identify_data(&plaintext, &self.link_id)?;
543 if self.remote_identity.is_some() {
544 return Ok(Vec::new());
545 }
546 self.remote_identity = Some((identity_hash, public_key));
547
548 Ok(alloc::vec![LinkAction::RemoteIdentified {
549 link_id: self.link_id,
550 identity_hash,
551 public_key,
552 }])
553 }
554
555 pub fn record_inbound(&mut self, now: f64) -> Vec<LinkAction> {
559 self.last_inbound = now;
560 if self.state == LinkState::Stale {
561 self.state = LinkState::Active;
562 self.stale_since = None;
563 return alloc::vec![LinkAction::StateChanged {
564 link_id: self.link_id,
565 new_state: LinkState::Active,
566 reason: None,
567 }];
568 }
569 Vec::new()
570 }
571
572 pub fn record_proof(&mut self, now: f64) {
580 self.last_proof = now;
581 }
582
583 pub fn record_outbound(&mut self, now: f64, is_keepalive: bool) {
585 self.last_outbound = now;
586 if is_keepalive {
587 self.last_keepalive = now;
588 }
589 }
590
591 pub fn record_inbound_traffic(&mut self, data_len: usize) {
593 if self.state != LinkState::Closed {
594 self.rx_packets = self.rx_packets.saturating_add(1);
595 self.rx_bytes = self.rx_bytes.saturating_add(data_len as u64);
596 }
597 }
598
599 pub fn record_outbound_traffic(&mut self, data_len: usize) {
601 self.tx_packets = self.tx_packets.saturating_add(1);
602 self.tx_bytes = self.tx_bytes.saturating_add(data_len as u64);
603 }
604
605 pub fn tick(&mut self, now: f64) -> Vec<LinkAction> {
607 let mut actions = Vec::new();
608
609 match self.state {
610 LinkState::Pending | LinkState::Handshake => {
611 if is_establishment_timeout(self.request_time, self.establishment_timeout, now) {
612 self.state = LinkState::Closed;
613 actions.push(LinkAction::StateChanged {
614 link_id: self.link_id,
615 new_state: LinkState::Closed,
616 reason: Some(TeardownReason::Timeout),
617 });
618 }
619 }
620 LinkState::Active => {
621 let activated = self.activated_at.unwrap_or(0.0);
622 let last_inbound = self.last_inbound.max(self.last_proof).max(activated);
624
625 if should_go_stale(last_inbound, self.stale_time, now) {
626 self.state = LinkState::Stale;
627 self.stale_since = Some(now);
628 actions.push(LinkAction::StateChanged {
629 link_id: self.link_id,
630 new_state: LinkState::Stale,
631 reason: None,
632 });
633 }
634 }
635 LinkState::Stale => {
636 let rtt = self.rtt.filter(|rtt| rtt.is_finite()).unwrap_or(0.0);
640 let stale_since = self.stale_since.unwrap_or(now);
641 if now >= stale_since + stale_close_timeout(rtt) {
642 self.state = LinkState::Closed;
643 self.stale_since = None;
644 actions.push(LinkAction::StateChanged {
645 link_id: self.link_id,
646 new_state: LinkState::Closed,
647 reason: Some(TeardownReason::Timeout),
648 });
649 }
650 }
651 LinkState::Closed => {}
652 }
653
654 actions
655 }
656
657 pub fn needs_keepalive(&self, now: f64) -> bool {
659 if self.state != LinkState::Active || !self.is_initiator {
660 return false;
661 }
662 let activated = self.activated_at.unwrap_or(0.0);
663 let last_inbound = self.last_inbound.max(self.last_proof).max(activated);
664
665 let inbound_quiet = now >= last_inbound + self.keepalive_interval;
670 let outbound_quiet = now >= self.last_outbound + self.keepalive_interval;
671 if !inbound_quiet && !outbound_quiet {
672 return false;
673 }
674
675 should_send_keepalive(self.last_keepalive, self.keepalive_interval, now)
676 }
677
678 pub fn should_reply_keepalive(&self, payload: &[u8], now: f64) -> bool {
685 self.state == LinkState::Active
686 && !self.is_initiator
687 && payload == [0xff]
688 && now >= self.last_outbound + self.keepalive_interval
689 }
690
691 pub fn teardown(&mut self) -> Vec<LinkAction> {
693 if self.state == LinkState::Closed {
694 return Vec::new();
695 }
696 self.state = LinkState::Closed;
697 let reason = if self.is_initiator {
698 TeardownReason::InitiatorClosed
699 } else {
700 TeardownReason::DestinationClosed
701 };
702 alloc::vec![LinkAction::StateChanged {
703 link_id: self.link_id,
704 new_state: LinkState::Closed,
705 reason: Some(reason),
706 }]
707 }
708
709 pub fn handle_teardown(&mut self) -> Vec<LinkAction> {
711 if self.state == LinkState::Closed {
712 return Vec::new();
713 }
714 self.state = LinkState::Closed;
715 let reason = if self.is_initiator {
716 TeardownReason::DestinationClosed
717 } else {
718 TeardownReason::InitiatorClosed
719 };
720 alloc::vec![LinkAction::StateChanged {
721 link_id: self.link_id,
722 new_state: LinkState::Closed,
723 reason: Some(reason),
724 }]
725 }
726
727 pub fn link_id(&self) -> &LinkId {
730 &self.link_id
731 }
732
733 pub fn state(&self) -> LinkState {
734 self.state
735 }
736
737 pub fn rtt(&self) -> Option<f64> {
738 self.rtt
739 }
740
741 pub fn mdu(&self) -> usize {
742 self.mdu
743 }
744
745 pub fn mtu(&self) -> u32 {
746 self.mtu
747 }
748
749 pub fn is_initiator(&self) -> bool {
750 self.is_initiator
751 }
752
753 pub fn activated_at(&self) -> Option<f64> {
755 self.activated_at
756 }
757
758 pub fn mode(&self) -> LinkMode {
759 self.mode
760 }
761
762 pub fn remote_identity(&self) -> Option<&([u8; 16], [u8; 64])> {
763 self.remote_identity.as_ref()
764 }
765
766 pub fn destination_hash(&self) -> &[u8; 16] {
767 &self.destination_hash
768 }
769
770 pub fn expected_hops(&self) -> u8 {
771 self.expected_hops
772 }
773
774 pub fn rebalanced_at(&self) -> Option<f64> {
775 self.rebalanced_at
776 }
777
778 pub fn tx_packets(&self) -> u64 {
779 self.tx_packets
780 }
781
782 pub fn rx_packets(&self) -> u64 {
783 self.rx_packets
784 }
785
786 pub fn tx_bytes(&self) -> u64 {
787 self.tx_bytes
788 }
789
790 pub fn rx_bytes(&self) -> u64 {
791 self.rx_bytes
792 }
793
794 pub fn derived_key(&self) -> Option<&[u8]> {
796 self.derived_key.as_deref()
797 }
798
799 pub fn keepalive_interval(&self) -> f64 {
800 self.keepalive_interval
801 }
802
803 pub fn last_proof(&self) -> f64 {
805 self.last_proof
806 }
807
808 pub fn set_rtt(&mut self, rtt: f64) {
811 self.rtt = Some(rtt);
812 self.update_keepalive();
813 }
814
815 pub fn set_mtu(&mut self, mtu: u32) {
817 self.mtu = mtu;
818 self.mdu = compute_mdu(mtu as usize);
819 }
820
821 #[doc(hidden)]
822 pub fn clear_session_for_testing(&mut self) {
823 self.derived_key = None;
824 self.token = None;
825 }
826
827 fn update_keepalive(&mut self) {
830 if let Some(rtt) = self.rtt {
831 self.keepalive_interval = compute_keepalive(rtt);
832 self.stale_time = compute_stale_time(self.keepalive_interval);
833 }
834 }
835}
836
837fn compute_mdu(mtu: usize) -> usize {
843 use crate::constants::{AES128_BLOCKSIZE, HEADER_MINSIZE, IFAC_MIN_SIZE, TOKEN_OVERHEAD};
844 let numerator = mtu.saturating_sub(IFAC_MIN_SIZE + HEADER_MINSIZE + TOKEN_OVERHEAD);
845 ((numerator / AES128_BLOCKSIZE) * AES128_BLOCKSIZE).saturating_sub(1)
846}
847
848fn effective_link_mtu(signalled_mtu: Option<u32>) -> u32 {
852 signalled_mtu
853 .filter(|mtu| *mtu != 0)
854 .map(|mtu| mtu.clamp(LINK_MTU_MIN, LINK_MTU_MAX))
855 .unwrap_or(MTU as u32)
856}
857use alloc::vec;
858
859#[cfg(test)]
860mod tests {
861 use super::*;
862 use crate::constants::{LINK_MDU, LINK_STALE_GRACE};
863 use rns_crypto::FixedRng;
864
865 fn make_rng(seed: u8) -> FixedRng {
866 FixedRng::new(&[seed; 128])
867 }
868
869 fn active_link_pair() -> (LinkEngine, LinkEngine, Vec<u8>) {
870 let mut rng_id = make_rng(0x01);
871 let dest_sig_prv = Ed25519PrivateKey::generate(&mut rng_id);
872 let dest_sig_pub_bytes = dest_sig_prv.public_key().public_bytes();
873 let dest_hash = [0xDD; 16];
874
875 let mut rng_init = make_rng(0x10);
876 let (mut initiator, request_data) = LinkEngine::new_initiator(
877 &dest_hash,
878 1,
879 LinkMode::Aes256Cbc,
880 Some(500),
881 100.0,
882 &mut rng_init,
883 );
884 let mut hashable = vec![0x00, 0x00];
885 hashable.extend_from_slice(&dest_hash);
886 hashable.push(0x00);
887 hashable.extend_from_slice(&request_data);
888 initiator.set_link_id_from_hashable(&hashable, request_data.len());
889
890 let mut rng_resp = make_rng(0x20);
891 let (mut responder, lrproof_data) = LinkEngine::new_responder(
892 &dest_sig_prv,
893 &dest_sig_pub_bytes,
894 &request_data,
895 &hashable,
896 &dest_hash,
897 1,
898 100.5,
899 &mut rng_resp,
900 )
901 .unwrap();
902 let mut rng_lrrtt = make_rng(0x30);
903 let (lrrtt_encrypted, _) = initiator
904 .handle_lrproof(&lrproof_data, &dest_sig_pub_bytes, 100.8, &mut rng_lrrtt)
905 .unwrap();
906 responder.handle_lrrtt(&lrrtt_encrypted, 101.0).unwrap();
907
908 (initiator, responder, lrrtt_encrypted)
909 }
910
911 #[test]
912 fn test_compute_mdu_default() {
913 assert_eq!(compute_mdu(500), LINK_MDU);
914 }
915
916 #[test]
917 fn zero_mtu_signalling_falls_back_to_reticulum_mtu() {
918 let mut rng_id = make_rng(0x01);
919 let dest_sig_prv = Ed25519PrivateKey::generate(&mut rng_id);
920 let dest_sig_pub_bytes = dest_sig_prv.public_key().public_bytes();
921 let dest_hash = [0xDD; 16];
922
923 let mut rng_init = make_rng(0x10);
924 let (mut initiator, request_data) = LinkEngine::new_initiator(
925 &dest_hash,
926 1,
927 LinkMode::Aes256Cbc,
928 Some(0),
929 100.0,
930 &mut rng_init,
931 );
932 let mut hashable = vec![0x00, 0x00];
933 hashable.extend_from_slice(&dest_hash);
934 hashable.push(0x00);
935 hashable.extend_from_slice(&request_data);
936 initiator.set_link_id_from_hashable(&hashable, request_data.len());
937
938 let mut rng_resp = make_rng(0x20);
939 let (responder, lrproof_data) = LinkEngine::new_responder(
940 &dest_sig_prv,
941 &dest_sig_pub_bytes,
942 &request_data,
943 &hashable,
944 &dest_hash,
945 1,
946 100.5,
947 &mut rng_resp,
948 )
949 .unwrap();
950
951 let mut rng_lrrtt = make_rng(0x30);
952 initiator
953 .handle_lrproof(&lrproof_data, &dest_sig_pub_bytes, 100.8, &mut rng_lrrtt)
954 .unwrap();
955
956 assert_eq!(initiator.mtu(), MTU as u32);
957 assert_eq!(responder.mtu(), MTU as u32);
958 assert_eq!(initiator.mdu(), LINK_MDU);
959 assert_eq!(responder.mdu(), LINK_MDU);
960 }
961
962 #[test]
963 fn test_full_handshake() {
964 let mut rng_id = make_rng(0x01);
966 let dest_sig_prv = Ed25519PrivateKey::generate(&mut rng_id);
967 let dest_sig_pub_bytes = dest_sig_prv.public_key().public_bytes();
968
969 let dest_hash = [0xDD; 16];
970 let mode = LinkMode::Aes256Cbc;
971
972 let mut rng_init = make_rng(0x10);
974 let (mut initiator, request_data) =
975 LinkEngine::new_initiator(&dest_hash, 1, mode, Some(500), 100.0, &mut rng_init);
976 assert_eq!(initiator.state(), LinkState::Pending);
977
978 let mut hashable = Vec::new();
981 hashable.push(0x00); hashable.push(0x00); hashable.extend_from_slice(&dest_hash);
984 hashable.push(0x00); hashable.extend_from_slice(&request_data);
986
987 initiator.set_link_id_from_hashable(&hashable, request_data.len());
988 assert_ne!(initiator.link_id(), &[0u8; 16]);
989
990 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 assert_eq!(responder.state(), LinkState::Handshake);
1004 assert_eq!(responder.link_id(), initiator.link_id());
1005
1006 let mut rng_lrrtt = make_rng(0x30);
1008 assert!(initiator
1009 .handle_lrproof_with_hops(
1010 &[0xAA; 3],
1011 &dest_sig_pub_bytes,
1012 Some(8),
1013 100.6,
1014 &mut rng_lrrtt,
1015 )
1016 .is_err());
1017 assert_eq!(initiator.expected_hops(), 1);
1018 let mismatched_mode_proof = handshake::build_lrproof(
1019 initiator.link_id(),
1020 lrproof_data[64..96].try_into().unwrap(),
1021 &dest_sig_pub_bytes,
1022 &dest_sig_prv,
1023 Some(500),
1024 LinkMode::Aes128Cbc,
1025 );
1026 assert_eq!(
1027 initiator
1028 .handle_lrproof_with_hops(
1029 &mismatched_mode_proof,
1030 &dest_sig_pub_bytes,
1031 Some(9),
1032 100.65,
1033 &mut rng_lrrtt,
1034 )
1035 .unwrap_err(),
1036 LinkError::UnsupportedMode,
1037 );
1038 assert_eq!(initiator.expected_hops(), 1);
1039 let mut invalid_lrproof = lrproof_data.clone();
1040 invalid_lrproof[0] ^= 0x01;
1041 assert!(initiator
1042 .handle_lrproof_with_hops(
1043 &invalid_lrproof,
1044 &dest_sig_pub_bytes,
1045 Some(7),
1046 100.7,
1047 &mut rng_lrrtt,
1048 )
1049 .is_err());
1050 assert_eq!(initiator.expected_hops(), 1);
1051 let (lrrtt_encrypted, actions) = initiator
1052 .handle_lrproof_with_hops(
1053 &lrproof_data,
1054 &dest_sig_pub_bytes,
1055 Some(4),
1056 100.8,
1057 &mut rng_lrrtt,
1058 )
1059 .unwrap();
1060 assert_eq!(initiator.state(), LinkState::Active);
1061 assert!(initiator.rtt().is_some());
1062 assert_eq!(initiator.rebalanced_at(), Some(100.8));
1063 assert_eq!(actions.len(), 2); let actions = responder
1067 .handle_lrrtt_with_hops(&lrrtt_encrypted, Some(4), 101.0)
1068 .unwrap();
1069 assert_eq!(responder.state(), LinkState::Active);
1070 assert_eq!(initiator.expected_hops(), 4);
1071 assert_eq!(responder.expected_hops(), 4);
1072 assert_eq!(responder.rebalanced_at(), None);
1073
1074 initiator.state = LinkState::Pending;
1077 let mut repeated_rng = make_rng(0x31);
1078 initiator
1079 .handle_lrproof_with_hops(
1080 &lrproof_data,
1081 &dest_sig_pub_bytes,
1082 Some(6),
1083 101.1,
1084 &mut repeated_rng,
1085 )
1086 .unwrap();
1087 assert_eq!(initiator.expected_hops(), 4);
1088 assert_eq!(initiator.rebalanced_at(), Some(100.8));
1089
1090 initiator.record_outbound_traffic(48);
1091 initiator.record_inbound_traffic(32);
1092 assert_eq!((initiator.tx_packets(), initiator.tx_bytes()), (1, 48));
1093 assert_eq!((initiator.rx_packets(), initiator.rx_bytes()), (1, 32));
1094 assert!(responder.rtt().is_some());
1095 assert_eq!(actions.len(), 2);
1096 }
1097
1098 #[test]
1099 fn test_encrypt_decrypt_after_handshake() {
1100 let mut rng_id = make_rng(0x01);
1101 let dest_sig_prv = Ed25519PrivateKey::generate(&mut rng_id);
1102 let dest_sig_pub_bytes = dest_sig_prv.public_key().public_bytes();
1103 let dest_hash = [0xDD; 16];
1104
1105 let mut rng_init = make_rng(0x10);
1106 let (mut initiator, request_data) = LinkEngine::new_initiator(
1107 &dest_hash,
1108 1,
1109 LinkMode::Aes256Cbc,
1110 Some(500),
1111 100.0,
1112 &mut rng_init,
1113 );
1114 let mut hashable = Vec::new();
1115 hashable.push(0x00);
1116 hashable.push(0x00);
1117 hashable.extend_from_slice(&dest_hash);
1118 hashable.push(0x00);
1119 hashable.extend_from_slice(&request_data);
1120 initiator.set_link_id_from_hashable(&hashable, request_data.len());
1121
1122 let mut rng_resp = make_rng(0x20);
1123 let (mut responder, lrproof_data) = LinkEngine::new_responder(
1124 &dest_sig_prv,
1125 &dest_sig_pub_bytes,
1126 &request_data,
1127 &hashable,
1128 &dest_hash,
1129 1,
1130 100.5,
1131 &mut rng_resp,
1132 )
1133 .unwrap();
1134
1135 let mut rng_lrrtt = make_rng(0x30);
1136 let (lrrtt_encrypted, _) = initiator
1137 .handle_lrproof(&lrproof_data, &dest_sig_pub_bytes, 100.8, &mut rng_lrrtt)
1138 .unwrap();
1139 responder.handle_lrrtt(&lrrtt_encrypted, 101.0).unwrap();
1140
1141 let mut rng_enc = make_rng(0x40);
1143 let plaintext = b"Hello over encrypted link!";
1144 let ciphertext = initiator.encrypt(plaintext, &mut rng_enc).unwrap();
1145 let decrypted = responder.decrypt(&ciphertext).unwrap();
1146 assert_eq!(decrypted, plaintext);
1147
1148 let mut rng_enc2 = make_rng(0x50);
1150 let ciphertext2 = responder.encrypt(b"Reply!", &mut rng_enc2).unwrap();
1151 let decrypted2 = initiator.decrypt(&ciphertext2).unwrap();
1152 assert_eq!(decrypted2, b"Reply!");
1153 }
1154
1155 #[test]
1156 fn test_tick_establishment_timeout() {
1157 let mut rng = make_rng(0x10);
1158 let dest_hash = [0xDD; 16];
1159 let (mut engine, _) =
1160 LinkEngine::new_initiator(&dest_hash, 1, LinkMode::Aes256Cbc, None, 100.0, &mut rng);
1161 let actions = engine.tick(110.0);
1165 assert!(actions.is_empty());
1166
1167 let actions = engine.tick(113.0);
1169 assert_eq!(actions.len(), 1);
1170 assert_eq!(engine.state(), LinkState::Closed);
1171 }
1172
1173 #[test]
1174 fn test_tick_stale_and_close() {
1175 let mut rng_id = make_rng(0x01);
1176 let dest_sig_prv = Ed25519PrivateKey::generate(&mut rng_id);
1177 let dest_sig_pub_bytes = dest_sig_prv.public_key().public_bytes();
1178 let dest_hash = [0xDD; 16];
1179
1180 let mut rng_init = make_rng(0x10);
1181 let (mut initiator, request_data) = LinkEngine::new_initiator(
1182 &dest_hash,
1183 1,
1184 LinkMode::Aes256Cbc,
1185 Some(500),
1186 100.0,
1187 &mut rng_init,
1188 );
1189 let mut hashable = Vec::new();
1190 hashable.push(0x00);
1191 hashable.push(0x00);
1192 hashable.extend_from_slice(&dest_hash);
1193 hashable.push(0x00);
1194 hashable.extend_from_slice(&request_data);
1195 initiator.set_link_id_from_hashable(&hashable, request_data.len());
1196
1197 let mut rng_resp = make_rng(0x20);
1198 let (_, lrproof_data) = LinkEngine::new_responder(
1199 &dest_sig_prv,
1200 &dest_sig_pub_bytes,
1201 &request_data,
1202 &hashable,
1203 &dest_hash,
1204 1,
1205 100.5,
1206 &mut rng_resp,
1207 )
1208 .unwrap();
1209
1210 let mut rng_lrrtt = make_rng(0x30);
1211 initiator
1212 .handle_lrproof(&lrproof_data, &dest_sig_pub_bytes, 100.8, &mut rng_lrrtt)
1213 .unwrap();
1214 assert_eq!(initiator.state(), LinkState::Active);
1215
1216 let stale_time = initiator.stale_time;
1218 let went_stale = 100.8 + stale_time + 1.0;
1219 let actions = initiator.tick(went_stale);
1220 assert_eq!(initiator.state(), LinkState::Stale);
1221 assert_eq!(actions.len(), 1);
1222
1223 let grace = stale_close_timeout(initiator.rtt().unwrap());
1226 assert!(grace >= LINK_STALE_GRACE);
1227 let actions = initiator.tick(went_stale + grace - 0.1);
1228 assert_eq!(initiator.state(), LinkState::Stale);
1229 assert!(actions.is_empty());
1230
1231 let actions = initiator.tick(went_stale + grace);
1232 assert_eq!(initiator.state(), LinkState::Closed);
1233 assert_eq!(actions.len(), 1);
1234 assert!(matches!(
1235 actions[0],
1236 LinkAction::StateChanged {
1237 new_state: LinkState::Closed,
1238 reason: Some(TeardownReason::Timeout),
1239 ..
1240 }
1241 ));
1242 }
1243
1244 #[test]
1245 fn inbound_during_stale_grace_reactivates_the_link() {
1246 let (mut initiator, _, _) = active_link_pair();
1247 let stale_time = initiator.stale_time;
1248 let went_stale = 101.0 + stale_time + 1.0;
1249 initiator.tick(went_stale);
1250 assert_eq!(initiator.state(), LinkState::Stale);
1251
1252 let actions = initiator.record_inbound(went_stale + 1.0);
1253 assert_eq!(initiator.state(), LinkState::Active);
1254 assert_eq!(actions.len(), 1);
1255
1256 let grace = stale_close_timeout(initiator.rtt().unwrap());
1258 assert!(initiator.tick(went_stale + grace + 1.0).is_empty());
1259 assert_eq!(initiator.state(), LinkState::Active);
1260 initiator.tick(went_stale + 1.0 + stale_time);
1261 assert_eq!(initiator.state(), LinkState::Stale);
1262 }
1263
1264 #[test]
1265 fn received_proofs_keep_an_initiator_from_going_stale() {
1266 let (mut initiator, _, _) = active_link_pair();
1267 let stale_time = initiator.stale_time;
1268 initiator.record_proof(101.0 + stale_time - 1.0);
1270 assert!(initiator.tick(101.0 + stale_time + 1.0).is_empty());
1271 assert_eq!(initiator.state(), LinkState::Active);
1272
1273 initiator.tick(101.0 + 2.0 * stale_time);
1275 assert_eq!(initiator.state(), LinkState::Stale);
1276 initiator.record_proof(101.0 + 2.0 * stale_time + 1.0);
1277 assert_eq!(initiator.state(), LinkState::Stale);
1278 }
1279
1280 #[test]
1281 fn test_needs_keepalive() {
1282 let mut rng_id = make_rng(0x01);
1283 let dest_sig_prv = Ed25519PrivateKey::generate(&mut rng_id);
1284 let dest_sig_pub_bytes = dest_sig_prv.public_key().public_bytes();
1285 let dest_hash = [0xDD; 16];
1286
1287 let mut rng_init = make_rng(0x10);
1288 let (mut initiator, request_data) = LinkEngine::new_initiator(
1289 &dest_hash,
1290 1,
1291 LinkMode::Aes256Cbc,
1292 Some(500),
1293 100.0,
1294 &mut rng_init,
1295 );
1296 let mut hashable = Vec::new();
1297 hashable.push(0x00);
1298 hashable.push(0x00);
1299 hashable.extend_from_slice(&dest_hash);
1300 hashable.push(0x00);
1301 hashable.extend_from_slice(&request_data);
1302 initiator.set_link_id_from_hashable(&hashable, request_data.len());
1303
1304 let mut rng_resp = make_rng(0x20);
1305 let (_, lrproof_data) = LinkEngine::new_responder(
1306 &dest_sig_prv,
1307 &dest_sig_pub_bytes,
1308 &request_data,
1309 &hashable,
1310 &dest_hash,
1311 1,
1312 100.5,
1313 &mut rng_resp,
1314 )
1315 .unwrap();
1316
1317 let mut rng_lrrtt = make_rng(0x30);
1318 initiator
1319 .handle_lrproof(&lrproof_data, &dest_sig_pub_bytes, 100.8, &mut rng_lrrtt)
1320 .unwrap();
1321
1322 let ka = initiator.keepalive_interval();
1323 assert!(!initiator.needs_keepalive(100.8 + ka - 1.0));
1325 assert!(initiator.needs_keepalive(100.8 + ka + 1.0));
1327 }
1328
1329 #[test]
1330 fn initiator_probes_when_outbound_is_quiet_despite_recent_inbound() {
1331 let (mut initiator, _, _) = active_link_pair();
1332 let keepalive = initiator.keepalive_interval();
1333 let now = 1_000.0;
1334
1335 initiator.record_inbound(now - 0.1);
1336 initiator.record_outbound(now - keepalive - 0.1, false);
1337
1338 assert!(initiator.needs_keepalive(now));
1339 }
1340
1341 #[test]
1342 fn initiator_probes_when_inbound_is_quiet_despite_recent_outbound() {
1343 let (mut initiator, _, _) = active_link_pair();
1344 let keepalive = initiator.keepalive_interval();
1345 let now = 1_000.0;
1346
1347 initiator.record_inbound(now - keepalive - 0.1);
1348 initiator.record_outbound(now - 0.1, false);
1349
1350 assert!(initiator.needs_keepalive(now));
1351 }
1352
1353 #[test]
1354 fn initiator_does_not_probe_when_both_directions_are_recent() {
1355 let (mut initiator, _, _) = active_link_pair();
1356 let now = 1_000.0;
1357
1358 initiator.record_inbound(now - 0.1);
1359 initiator.record_outbound(now - 0.1, false);
1360
1361 assert!(!initiator.needs_keepalive(now));
1362 }
1363
1364 #[test]
1365 fn test_needs_keepalive_responder() {
1366 let mut rng_id = make_rng(0x01);
1367 let dest_sig_prv = Ed25519PrivateKey::generate(&mut rng_id);
1368 let dest_sig_pub_bytes = dest_sig_prv.public_key().public_bytes();
1369 let dest_hash = [0xDD; 16];
1370
1371 let mut rng_init = make_rng(0x10);
1372 let (mut initiator, request_data) = LinkEngine::new_initiator(
1373 &dest_hash,
1374 1,
1375 LinkMode::Aes256Cbc,
1376 Some(500),
1377 100.0,
1378 &mut rng_init,
1379 );
1380 let mut hashable = Vec::new();
1381 hashable.push(0x00);
1382 hashable.push(0x00);
1383 hashable.extend_from_slice(&dest_hash);
1384 hashable.push(0x00);
1385 hashable.extend_from_slice(&request_data);
1386 initiator.set_link_id_from_hashable(&hashable, request_data.len());
1387
1388 let mut rng_resp = make_rng(0x20);
1389 let (mut responder, lrproof_data) = LinkEngine::new_responder(
1390 &dest_sig_prv,
1391 &dest_sig_pub_bytes,
1392 &request_data,
1393 &hashable,
1394 &dest_hash,
1395 1,
1396 100.5,
1397 &mut rng_resp,
1398 )
1399 .unwrap();
1400
1401 let mut rng_lrrtt = make_rng(0x30);
1402 let (lrrtt_encrypted, _) = initiator
1403 .handle_lrproof(&lrproof_data, &dest_sig_pub_bytes, 100.8, &mut rng_lrrtt)
1404 .unwrap();
1405 responder.handle_lrrtt(&lrrtt_encrypted, 101.0).unwrap();
1406
1407 let ka = responder.keepalive_interval();
1408 assert!(!responder.needs_keepalive(101.0 + ka - 1.0));
1410 assert!(!responder.needs_keepalive(101.0 + ka + 1.0));
1411 }
1412
1413 #[test]
1414 fn responder_replies_only_to_probe_and_only_when_outbound_is_quiet() {
1415 let (_, mut responder, _) = active_link_pair();
1416 let keepalive = responder.keepalive_interval();
1417 let now = 1_000.0;
1418
1419 responder.record_outbound(now - keepalive - 0.1, false);
1420 assert!(responder.should_reply_keepalive(&[0xff], now));
1421 assert!(!responder.should_reply_keepalive(&[0xfe], now));
1422 assert!(!responder.should_reply_keepalive(&[], now));
1423
1424 responder.record_outbound(now - 0.1, false);
1425 assert!(!responder.should_reply_keepalive(&[0xff], now));
1426 }
1427
1428 #[test]
1429 fn initiator_never_replies_to_keepalive_probe() {
1430 let (mut initiator, _, _) = active_link_pair();
1431 let keepalive = initiator.keepalive_interval();
1432 let now = 1_000.0;
1433 initiator.record_outbound(now - keepalive - 0.1, false);
1434
1435 assert!(!initiator.should_reply_keepalive(&[0xff], now));
1436 }
1437
1438 #[test]
1439 fn packet_delivery_proofs_are_bidirectional_and_peer_authenticated() {
1440 let (initiator, responder, _) = active_link_pair();
1441 let packet_hash = [0x42; 32];
1442
1443 let initiator_proof = initiator.sign_packet_hash(&packet_hash);
1444 assert!(responder.validate_packet_proof(&packet_hash, &initiator_proof));
1445 assert!(!initiator.validate_packet_proof(&packet_hash, &initiator_proof));
1446
1447 let responder_proof = responder.sign_packet_hash(&packet_hash);
1448 assert!(initiator.validate_packet_proof(&packet_hash, &responder_proof));
1449 assert!(!responder.validate_packet_proof(&packet_hash, &responder_proof));
1450 assert!(!initiator.validate_packet_proof(&[0x43; 32], &responder_proof));
1451 }
1452
1453 #[test]
1454 fn test_teardown() {
1455 let mut rng = make_rng(0x10);
1456 let (mut engine, _) =
1457 LinkEngine::new_initiator(&[0xDD; 16], 1, LinkMode::Aes256Cbc, None, 100.0, &mut rng);
1458 let actions = engine.teardown();
1459 assert_eq!(engine.state(), LinkState::Closed);
1460 assert_eq!(actions.len(), 1);
1461
1462 let actions = engine.teardown();
1464 assert!(actions.is_empty());
1465 }
1466
1467 #[test]
1468 fn test_handle_teardown() {
1469 let mut rng = make_rng(0x10);
1470 let (mut engine, _) =
1471 LinkEngine::new_initiator(&[0xDD; 16], 1, LinkMode::Aes256Cbc, None, 100.0, &mut rng);
1472 let actions = engine.handle_teardown();
1473 assert_eq!(engine.state(), LinkState::Closed);
1474 assert_eq!(actions.len(), 1);
1475 match &actions[0] {
1476 LinkAction::StateChanged { reason, .. } => {
1477 assert_eq!(*reason, Some(TeardownReason::DestinationClosed));
1478 }
1479 _ => panic!("Expected StateChanged"),
1480 }
1481 }
1482
1483 #[test]
1484 fn test_identify_over_link() {
1485 let mut rng_id = make_rng(0x01);
1486 let dest_sig_prv = Ed25519PrivateKey::generate(&mut rng_id);
1487 let dest_sig_pub_bytes = dest_sig_prv.public_key().public_bytes();
1488 let dest_hash = [0xDD; 16];
1489
1490 let mut rng_init = make_rng(0x10);
1491 let (mut initiator, request_data) = LinkEngine::new_initiator(
1492 &dest_hash,
1493 1,
1494 LinkMode::Aes256Cbc,
1495 Some(500),
1496 100.0,
1497 &mut rng_init,
1498 );
1499 let mut hashable = Vec::new();
1500 hashable.push(0x00);
1501 hashable.push(0x00);
1502 hashable.extend_from_slice(&dest_hash);
1503 hashable.push(0x00);
1504 hashable.extend_from_slice(&request_data);
1505 initiator.set_link_id_from_hashable(&hashable, request_data.len());
1506
1507 let mut rng_resp = make_rng(0x20);
1508 let (mut responder, lrproof_data) = LinkEngine::new_responder(
1509 &dest_sig_prv,
1510 &dest_sig_pub_bytes,
1511 &request_data,
1512 &hashable,
1513 &dest_hash,
1514 1,
1515 100.5,
1516 &mut rng_resp,
1517 )
1518 .unwrap();
1519
1520 let mut rng_lrrtt = make_rng(0x30);
1521 let (lrrtt_encrypted, _) = initiator
1522 .handle_lrproof(&lrproof_data, &dest_sig_pub_bytes, 100.8, &mut rng_lrrtt)
1523 .unwrap();
1524 responder.handle_lrrtt(&lrrtt_encrypted, 101.0).unwrap();
1525
1526 let mut rng_ident = make_rng(0x40);
1528 let my_identity = rns_crypto::identity::Identity::new(&mut rng_ident);
1529
1530 let mut rng_enc = make_rng(0x50);
1532 let identify_encrypted = initiator
1533 .build_identify(&my_identity, &mut rng_enc)
1534 .unwrap();
1535
1536 let actions = responder.handle_identify(&identify_encrypted).unwrap();
1537 assert_eq!(actions.len(), 1);
1538 match &actions[0] {
1539 LinkAction::RemoteIdentified {
1540 identity_hash,
1541 public_key,
1542 ..
1543 } => {
1544 assert_eq!(identity_hash, my_identity.hash());
1545 assert_eq!(public_key, &my_identity.get_public_key().unwrap());
1546 }
1547 _ => panic!("Expected RemoteIdentified"),
1548 }
1549
1550 let mut rng_repeat = make_rng(0x51);
1553 let repeated = initiator
1554 .build_identify(&my_identity, &mut rng_repeat)
1555 .unwrap();
1556 assert!(responder.handle_identify(&repeated).unwrap().is_empty());
1557
1558 let mut rng_other = make_rng(0x60);
1559 let other_identity = rns_crypto::identity::Identity::new(&mut rng_other);
1560 let mut rng_other_enc = make_rng(0x61);
1561 let other = initiator
1562 .build_identify(&other_identity, &mut rng_other_enc)
1563 .unwrap();
1564 assert!(responder.handle_identify(&other).unwrap().is_empty());
1565 assert_eq!(
1566 responder.remote_identity().map(|(hash, _)| hash),
1567 Some(my_identity.hash())
1568 );
1569
1570 let mut rng_bad = make_rng(0x70);
1572 let malformed = initiator
1573 .encrypt(b"invalid identify", &mut rng_bad)
1574 .unwrap();
1575 assert!(responder.handle_identify(&malformed).is_err());
1576 assert_eq!(
1577 responder.remote_identity().map(|(hash, _)| hash),
1578 Some(my_identity.hash())
1579 );
1580 }
1581
1582 #[test]
1583 fn test_aes128_mode_handshake() {
1584 let mut rng_id = make_rng(0x01);
1585 let dest_sig_prv = Ed25519PrivateKey::generate(&mut rng_id);
1586 let dest_sig_pub_bytes = dest_sig_prv.public_key().public_bytes();
1587 let dest_hash = [0xDD; 16];
1588
1589 let mut rng_init = make_rng(0x10);
1590 let (mut initiator, request_data) = LinkEngine::new_initiator(
1591 &dest_hash,
1592 1,
1593 LinkMode::Aes128Cbc,
1594 Some(500),
1595 100.0,
1596 &mut rng_init,
1597 );
1598 let mut hashable = Vec::new();
1599 hashable.push(0x00);
1600 hashable.push(0x00);
1601 hashable.extend_from_slice(&dest_hash);
1602 hashable.push(0x00);
1603 hashable.extend_from_slice(&request_data);
1604 initiator.set_link_id_from_hashable(&hashable, request_data.len());
1605
1606 let mut rng_resp = make_rng(0x20);
1607 let (mut responder, lrproof_data) = LinkEngine::new_responder(
1608 &dest_sig_prv,
1609 &dest_sig_pub_bytes,
1610 &request_data,
1611 &hashable,
1612 &dest_hash,
1613 1,
1614 100.5,
1615 &mut rng_resp,
1616 )
1617 .unwrap();
1618
1619 let mut rng_lrrtt = make_rng(0x30);
1620 let (lrrtt_encrypted, _) = initiator
1621 .handle_lrproof(&lrproof_data, &dest_sig_pub_bytes, 100.8, &mut rng_lrrtt)
1622 .unwrap();
1623 responder.handle_lrrtt(&lrrtt_encrypted, 101.0).unwrap();
1624
1625 assert_eq!(initiator.state(), LinkState::Active);
1626 assert_eq!(responder.state(), LinkState::Active);
1627 assert_eq!(initiator.mode(), LinkMode::Aes128Cbc);
1628
1629 let mut rng_enc = make_rng(0x40);
1631 let ct = initiator.encrypt(b"AES128 test", &mut rng_enc).unwrap();
1632 let pt = responder.decrypt(&ct).unwrap();
1633 assert_eq!(pt, b"AES128 test");
1634 }
1635
1636 fn handshake_pair_with_mtu(mtu: Option<u32>) -> (LinkEngine, LinkEngine) {
1639 let mut rng_id = make_rng(0x01);
1640 let dest_sig_prv = Ed25519PrivateKey::generate(&mut rng_id);
1641 let dest_sig_pub_bytes = dest_sig_prv.public_key().public_bytes();
1642 let dest_hash = [0xDD; 16];
1643
1644 let mut rng_init = make_rng(0x10);
1645 let (mut initiator, request_data) = LinkEngine::new_initiator(
1646 &dest_hash,
1647 1,
1648 LinkMode::Aes256Cbc,
1649 mtu,
1650 100.0,
1651 &mut rng_init,
1652 );
1653 let mut hashable = vec![0x00, 0x00];
1654 hashable.extend_from_slice(&dest_hash);
1655 hashable.push(0x00);
1656 hashable.extend_from_slice(&request_data);
1657 initiator.set_link_id_from_hashable(&hashable, request_data.len());
1658
1659 let mut rng_resp = make_rng(0x20);
1660 let (responder, lrproof_data) = LinkEngine::new_responder(
1661 &dest_sig_prv,
1662 &dest_sig_pub_bytes,
1663 &request_data,
1664 &hashable,
1665 &dest_hash,
1666 1,
1667 100.5,
1668 &mut rng_resp,
1669 )
1670 .unwrap();
1671
1672 let mut rng_lrrtt = make_rng(0x30);
1673 initiator
1674 .handle_lrproof(&lrproof_data, &dest_sig_pub_bytes, 100.8, &mut rng_lrrtt)
1675 .unwrap();
1676 (initiator, responder)
1677 }
1678
1679 fn raw_rtt_payload(rtt: f64) -> Vec<u8> {
1680 let mut data = vec![0xcb];
1681 data.extend_from_slice(&rtt.to_be_bytes());
1682 data
1683 }
1684
1685 #[test]
1686 fn compute_mdu_keeps_values_for_regular_mtus() {
1687 assert_eq!(compute_mdu(500), 431);
1688 assert_eq!(compute_mdu(508), 431);
1689 assert_eq!(compute_mdu(1064), 991);
1690 assert_eq!(compute_mdu(8192), 8111);
1691 assert_eq!(compute_mdu(262_144), 262_063);
1692 assert_eq!(compute_mdu(LINK_MTU_MAX as usize), 2_097_071);
1693 }
1694
1695 #[test]
1696 fn compute_mdu_saturates_for_small_mtus() {
1697 for mtu in [0usize, 1, 50, 67, 68, 83] {
1698 assert_eq!(compute_mdu(mtu), 0, "mtu {mtu}");
1699 }
1700 assert_eq!(compute_mdu(LINK_MTU_MIN as usize), 15);
1701 }
1702
1703 #[test]
1704 fn effective_link_mtu_is_clamped_and_mdu_monotone() {
1705 assert_eq!(effective_link_mtu(None), MTU as u32);
1706 assert_eq!(effective_link_mtu(Some(0)), MTU as u32);
1707 assert_eq!(effective_link_mtu(Some(1)), LINK_MTU_MIN);
1708 assert_eq!(effective_link_mtu(Some(50)), LINK_MTU_MIN);
1709 assert_eq!(effective_link_mtu(Some(83)), LINK_MTU_MIN);
1710 assert_eq!(effective_link_mtu(Some(84)), 84);
1711 assert_eq!(effective_link_mtu(Some(500)), 500);
1712 assert_eq!(effective_link_mtu(Some(LINK_MTU_MAX)), LINK_MTU_MAX);
1713 assert_eq!(effective_link_mtu(Some(u32::MAX)), LINK_MTU_MAX);
1714
1715 let mut prev = 0usize;
1716 for mtu in [1u32, 50, 83, 84, 500, LINK_MTU_MAX] {
1717 let mdu = compute_mdu(effective_link_mtu(Some(mtu)) as usize);
1718 assert!(mdu >= 15, "mtu {mtu} gave mdu {mdu}");
1719 assert!(mdu >= prev, "mdu not monotone at mtu {mtu}");
1720 prev = mdu;
1721 }
1722 }
1723
1724 #[test]
1725 fn small_signalled_mtu_is_raised_to_minimum_on_both_sides() {
1726 let (initiator, mut responder) = handshake_pair_with_mtu(Some(70));
1727 assert_eq!(responder.mtu(), LINK_MTU_MIN);
1728 assert_eq!(responder.mdu(), 15);
1729 assert_eq!(initiator.mtu(), LINK_MTU_MIN);
1730 assert_eq!(initiator.mdu(), 15);
1731 assert_eq!(initiator.state(), LinkState::Active);
1732
1733 let mut rng = make_rng(0x40);
1734 let lrrtt = initiator.encrypt(&pack_rtt(0.3), &mut rng).unwrap();
1735 responder.handle_lrrtt(&lrrtt, 101.0).unwrap();
1736 assert_eq!(responder.state(), LinkState::Active);
1737
1738 let ct = initiator.encrypt(b"hello", &mut rng).unwrap();
1739 assert_eq!(responder.decrypt(&ct).unwrap(), b"hello");
1740 }
1741
1742 #[test]
1743 fn signalled_mtu_edge_values_do_not_panic() {
1744 for mtu in [0u32, 1, 50, 83, 84, 500, LINK_MTU_MAX] {
1745 let (initiator, responder) = handshake_pair_with_mtu(Some(mtu));
1746 assert!(responder.mtu() >= LINK_MTU_MIN);
1747 assert!(responder.mdu() >= 15);
1748 assert_eq!(initiator.mtu(), responder.mtu());
1749 assert_eq!(initiator.mdu(), responder.mdu());
1750 }
1751 }
1752
1753 #[test]
1754 fn non_finite_or_negative_lrrtt_is_rejected() {
1755 for bad in [f64::NAN, f64::INFINITY, f64::NEG_INFINITY, -1.0] {
1756 let (initiator, mut responder) = handshake_pair_with_mtu(Some(500));
1757 let mut rng = make_rng(0x40);
1758 let lrrtt = initiator.encrypt(&raw_rtt_payload(bad), &mut rng).unwrap();
1759 assert!(
1760 matches!(
1761 responder.handle_lrrtt(&lrrtt, 101.0),
1762 Err(LinkError::InvalidData)
1763 ),
1764 "rtt {bad}"
1765 );
1766 assert_eq!(responder.state(), LinkState::Handshake);
1767 assert_eq!(responder.rtt(), None);
1768 assert!(responder.keepalive_interval.is_finite());
1769 assert!(responder.stale_time.is_finite());
1770
1771 let deadline = responder.request_time + responder.establishment_timeout;
1773 let actions = responder.tick(deadline + 1.0);
1774 assert_eq!(responder.state(), LinkState::Closed);
1775 assert_eq!(actions.len(), 1);
1776 }
1777 }
1778
1779 #[test]
1780 fn valid_lrrtt_updates_rtt_and_timers() {
1781 let (initiator, mut responder) = handshake_pair_with_mtu(Some(500));
1782 let mut rng = make_rng(0x40);
1783 let lrrtt = initiator.encrypt(&pack_rtt(0.9), &mut rng).unwrap();
1785 responder.handle_lrrtt(&lrrtt, 101.0).unwrap();
1786 assert_eq!(responder.state(), LinkState::Active);
1787 assert_eq!(responder.rtt(), Some(0.9));
1788 assert_eq!(responder.keepalive_interval, compute_keepalive(0.9));
1789 assert_eq!(
1790 responder.stale_time,
1791 compute_stale_time(compute_keepalive(0.9))
1792 );
1793 }
1794
1795 #[test]
1796 fn non_finite_rtt_keeps_timers_finite_and_link_goes_stale() {
1797 let (_, mut responder, _) = active_link_pair();
1798 for bad in [f64::NAN, f64::INFINITY, f64::NEG_INFINITY] {
1799 responder.set_rtt(bad);
1800 assert!(responder.keepalive_interval.is_finite(), "rtt {bad}");
1801 assert!(responder.stale_time.is_finite(), "rtt {bad}");
1802 assert!(responder.stale_time <= LINK_KEEPALIVE_MAX * 2.0);
1803 }
1804
1805 let stale_time = responder.stale_time;
1806 responder.tick(101.0 + stale_time + 1.0);
1807 assert_eq!(responder.state(), LinkState::Stale);
1808 }
1809
1810 #[test]
1811 fn stamped_link_request_carries_required_value() {
1812 let mut rng = rns_crypto::OsRng;
1813 let dest_hash = [0xAB; 16];
1814 let (_engine, data) = LinkEngine::new_initiator_stamped(
1815 &dest_hash,
1816 1,
1817 LinkMode::Aes256Cbc,
1818 None,
1819 0.0,
1820 &mut rng,
1821 crate::stamp::StampCost::from_whole(4),
1822 );
1823 let value = handshake::link_request_stamp_value(&dest_hash, &data).unwrap();
1824 assert!(value.meets(crate::stamp::StampCost::from_whole(4)));
1825 }
1826
1827 #[test]
1828 fn initiator_with_keys_uses_the_supplied_keys() {
1829 let mut rng = rns_crypto::OsRng;
1830 let dest_hash = [0xAC; 16];
1831 let prv = X25519PrivateKey::generate(&mut rng);
1832 let sig_prv = Ed25519PrivateKey::generate(&mut rng);
1833 let expected_pub = prv.public_key().public_bytes();
1834 let expected_sig_pub = sig_prv.public_key().public_bytes();
1835
1836 let (engine, data) = LinkEngine::new_initiator_with_keys(
1837 &dest_hash,
1838 1,
1839 LinkMode::Aes256Cbc,
1840 None,
1841 0.0,
1842 prv,
1843 sig_prv,
1844 );
1845 let (peer_pub, peer_sig_pub, mtu, mode) = handshake::parse_linkrequest_data(&data).unwrap();
1846 assert_eq!(peer_pub, expected_pub);
1847 assert_eq!(peer_sig_pub, expected_sig_pub);
1848 assert_eq!(mtu, None);
1849 assert_eq!(mode, LinkMode::Aes256Cbc);
1850 assert!(engine.is_initiator());
1851 assert_eq!(engine.state(), LinkState::Pending);
1852 }
1853}