1pub mod crypto;
2pub mod handshake;
3pub mod identify;
4pub mod keepalive;
5pub mod types;
6
7use alloc::vec::Vec;
8
9use rns_crypto::ed25519::{Ed25519PrivateKey, Ed25519PublicKey};
10use rns_crypto::token::Token;
11use rns_crypto::x25519::X25519PrivateKey;
12use rns_crypto::Rng;
13
14use crate::constants::{
15 LINK_ECPUBSIZE, LINK_ESTABLISHMENT_TIMEOUT_PER_HOP, LINK_KEEPALIVE_MAX, MTU, PATHFINDER_M,
16};
17
18pub use types::{LinkAction, LinkError, LinkId, LinkMode, LinkState, TeardownReason};
19
20use crypto::{create_session_token, link_decrypt, link_encrypt};
21use handshake::{
22 build_linkrequest_data, compute_link_id, pack_rtt, parse_linkrequest_data,
23 perform_key_exchange, unpack_rtt, validate_lrproof,
24};
25use keepalive::{
26 compute_establishment_timeout, compute_keepalive, compute_stale_time, is_establishment_timeout,
27 should_go_stale, should_send_keepalive,
28};
29
30pub struct LinkEngine {
35 link_id: LinkId,
36 state: LinkState,
37 is_initiator: bool,
38 mode: LinkMode,
39
40 prv: X25519PrivateKey,
42
43 peer_pub_bytes: Option<[u8; 32]>,
45 peer_sig_pub_bytes: Option<[u8; 32]>,
46
47 derived_key: Option<Vec<u8>>,
49 token: Option<Token>,
50
51 request_time: f64,
53 activated_at: Option<f64>,
54 last_inbound: f64,
55 last_outbound: f64,
56 last_keepalive: f64,
57 last_proof: f64,
58 rtt: Option<f64>,
59 keepalive_interval: f64,
60 stale_time: f64,
61 establishment_timeout: f64,
62 expected_hops: u8,
63 rebalanced_at: Option<f64>,
65
66 tx_packets: u64,
68 rx_packets: u64,
69 tx_bytes: u64,
70 rx_bytes: u64,
71
72 remote_identity: Option<([u8; 16], [u8; 64])>,
74 destination_hash: [u8; 16],
75
76 mtu: u32,
78 mdu: usize,
79}
80
81impl LinkEngine {
82 pub fn new_initiator(
87 dest_hash: &[u8; 16],
88 hops: u8,
89 mode: LinkMode,
90 mtu: Option<u32>,
91 now: f64,
92 rng: &mut dyn Rng,
93 ) -> (Self, Vec<u8>) {
94 let prv = X25519PrivateKey::generate(rng);
95 let pub_bytes = prv.public_key().public_bytes();
96 let sig_prv = Ed25519PrivateKey::generate(rng);
97 let sig_pub_bytes = sig_prv.public_key().public_bytes();
98
99 let request_data = build_linkrequest_data(&pub_bytes, &sig_pub_bytes, mtu, mode);
100
101 let link_mtu = mtu.unwrap_or(MTU as u32);
102
103 let engine = LinkEngine {
104 link_id: [0u8; 16], state: LinkState::Pending,
106 is_initiator: true,
107 mode,
108 prv,
109 peer_pub_bytes: None,
110 peer_sig_pub_bytes: None,
111 derived_key: None,
112 token: None,
113 request_time: now,
114 activated_at: None,
115 last_inbound: now,
116 last_outbound: now,
117 last_keepalive: now,
118 last_proof: 0.0,
119 rtt: None,
120 keepalive_interval: LINK_KEEPALIVE_MAX,
121 stale_time: LINK_KEEPALIVE_MAX * 2.0,
122 establishment_timeout: compute_establishment_timeout(
123 LINK_ESTABLISHMENT_TIMEOUT_PER_HOP,
124 hops,
125 ),
126 expected_hops: if hops < PATHFINDER_M {
127 hops
128 } else {
129 PATHFINDER_M
130 },
131 rebalanced_at: None,
132 tx_packets: 0,
133 rx_packets: 0,
134 tx_bytes: 0,
135 rx_bytes: 0,
136 remote_identity: None,
137 destination_hash: *dest_hash,
138 mtu: link_mtu,
139 mdu: compute_mdu(link_mtu as usize),
140 };
141
142 (engine, request_data)
143 }
144
145 pub fn set_link_id_from_hashable(&mut self, hashable_part: &[u8], data_len: usize) {
150 let extra = data_len.saturating_sub(LINK_ECPUBSIZE);
151 self.link_id = compute_link_id(hashable_part, extra);
152 }
153
154 #[allow(clippy::too_many_arguments)]
159 pub fn new_responder(
160 owner_sig_prv: &Ed25519PrivateKey,
161 owner_sig_pub_bytes: &[u8; 32],
162 linkrequest_data: &[u8],
163 hashable_part: &[u8],
164 dest_hash: &[u8; 16],
165 hops: u8,
166 now: f64,
167 rng: &mut dyn Rng,
168 ) -> Result<(Self, Vec<u8>), LinkError> {
169 let (peer_pub, peer_sig_pub, peer_mtu, mode) = parse_linkrequest_data(linkrequest_data)?;
170
171 let extra = linkrequest_data.len().saturating_sub(LINK_ECPUBSIZE);
172 let link_id = compute_link_id(hashable_part, extra);
173
174 let prv = X25519PrivateKey::generate(rng);
176 let pub_bytes = prv.public_key().public_bytes();
177 let sig_pub_bytes = *owner_sig_pub_bytes;
178
179 let derived_key = perform_key_exchange(&prv, &peer_pub, &link_id, mode)?;
181 let token = create_session_token(&derived_key)?;
182
183 let link_mtu = peer_mtu.unwrap_or(MTU as u32);
184
185 let lrproof_data = handshake::build_lrproof(
187 &link_id,
188 &pub_bytes,
189 &sig_pub_bytes,
190 owner_sig_prv,
191 peer_mtu,
192 mode,
193 );
194
195 let engine = LinkEngine {
196 link_id,
197 state: LinkState::Handshake,
198 is_initiator: false,
199 mode,
200 prv,
201 peer_pub_bytes: Some(peer_pub),
202 peer_sig_pub_bytes: Some(peer_sig_pub),
203 derived_key: Some(derived_key),
204 token: Some(token),
205 request_time: now,
206 activated_at: None,
207 last_inbound: now,
208 last_outbound: now,
209 last_keepalive: now,
210 last_proof: 0.0,
211 rtt: None,
212 keepalive_interval: LINK_KEEPALIVE_MAX,
213 stale_time: LINK_KEEPALIVE_MAX * 2.0,
214 establishment_timeout: compute_establishment_timeout(
215 LINK_ESTABLISHMENT_TIMEOUT_PER_HOP,
216 hops,
217 ),
218 expected_hops: PATHFINDER_M,
219 rebalanced_at: None,
220 tx_packets: 0,
221 rx_packets: 0,
222 tx_bytes: 0,
223 rx_bytes: 0,
224 remote_identity: None,
225 destination_hash: *dest_hash,
226 mtu: link_mtu,
227 mdu: compute_mdu(link_mtu as usize),
228 };
229
230 Ok((engine, lrproof_data))
231 }
232
233 pub fn handle_lrproof(
238 &mut self,
239 proof_data: &[u8],
240 peer_sig_pub_bytes: &[u8; 32],
241 now: f64,
242 rng: &mut dyn Rng,
243 ) -> Result<(Vec<u8>, Vec<LinkAction>), LinkError> {
244 self.handle_lrproof_with_hops(proof_data, peer_sig_pub_bytes, None, now, rng)
245 }
246
247 pub fn handle_lrproof_with_hops(
249 &mut self,
250 proof_data: &[u8],
251 peer_sig_pub_bytes: &[u8; 32],
252 packet_hops: Option<u8>,
253 now: f64,
254 rng: &mut dyn Rng,
255 ) -> Result<(Vec<u8>, Vec<LinkAction>), LinkError> {
256 if self.state != LinkState::Pending || !self.is_initiator {
257 return Err(LinkError::InvalidState);
258 }
259
260 let peer_sig_pub = Ed25519PublicKey::from_bytes(peer_sig_pub_bytes);
261
262 let (peer_pub, confirmed_mtu, confirmed_mode) =
263 validate_lrproof(proof_data, &self.link_id, &peer_sig_pub, peer_sig_pub_bytes)?;
264
265 if confirmed_mode != self.mode {
266 return Err(LinkError::UnsupportedMode);
267 }
268
269 if let Some(hops) = packet_hops {
270 if hops != self.expected_hops && self.rebalanced_at.is_none() {
271 self.rebalanced_at = Some(now);
272 self.expected_hops = hops;
273 }
274 }
275
276 self.peer_pub_bytes = Some(peer_pub);
277 self.peer_sig_pub_bytes = Some(*peer_sig_pub_bytes);
278
279 let derived_key = perform_key_exchange(&self.prv, &peer_pub, &self.link_id, self.mode)?;
281 let token = create_session_token(&derived_key)?;
282
283 self.derived_key = Some(derived_key);
284 self.token = Some(token);
285
286 if let Some(mtu) = confirmed_mtu {
288 self.mtu = mtu;
289 self.mdu = compute_mdu(mtu as usize);
290 }
291
292 let rtt = now - self.request_time;
294 self.rtt = Some(rtt);
295 self.state = LinkState::Active;
296 self.activated_at = Some(now);
297 self.last_inbound = now;
298 self.update_keepalive();
299
300 let rtt_packed = pack_rtt(rtt);
302 let rtt_encrypted = self.encrypt(&rtt_packed, rng)?;
303
304 let actions = vec![
305 LinkAction::StateChanged {
306 link_id: self.link_id,
307 new_state: LinkState::Active,
308 reason: None,
309 },
310 LinkAction::LinkEstablished {
311 link_id: self.link_id,
312 rtt,
313 is_initiator: true,
314 },
315 ];
316
317 Ok((rtt_encrypted, actions))
318 }
319
320 pub fn handle_lrrtt(
324 &mut self,
325 encrypted_data: &[u8],
326 now: f64,
327 ) -> Result<Vec<LinkAction>, LinkError> {
328 self.handle_lrrtt_with_hops(encrypted_data, None, now)
329 }
330
331 pub fn handle_lrrtt_with_hops(
334 &mut self,
335 encrypted_data: &[u8],
336 packet_hops: Option<u8>,
337 now: f64,
338 ) -> Result<Vec<LinkAction>, LinkError> {
339 if self.state != LinkState::Handshake || self.is_initiator {
340 return Err(LinkError::InvalidState);
341 }
342
343 let plaintext = self.decrypt(encrypted_data)?;
344 let initiator_rtt = unpack_rtt(&plaintext).ok_or(LinkError::InvalidData)?;
345 if let Some(hops) = packet_hops {
346 self.expected_hops = hops;
347 }
348
349 let measured_rtt = now - self.request_time;
350 let rtt = if measured_rtt > initiator_rtt {
351 measured_rtt
352 } else {
353 initiator_rtt
354 };
355
356 self.rtt = Some(rtt);
357 self.state = LinkState::Active;
358 self.activated_at = Some(now);
359 self.last_inbound = now;
360 self.update_keepalive();
361
362 let actions = vec![
363 LinkAction::StateChanged {
364 link_id: self.link_id,
365 new_state: LinkState::Active,
366 reason: None,
367 },
368 LinkAction::LinkEstablished {
369 link_id: self.link_id,
370 rtt,
371 is_initiator: false,
372 },
373 ];
374
375 Ok(actions)
376 }
377
378 pub fn encrypt(&self, plaintext: &[u8], rng: &mut dyn Rng) -> Result<Vec<u8>, LinkError> {
380 let token = self.token.as_ref().ok_or(LinkError::NoSessionKey)?;
381 Ok(link_encrypt(token, plaintext, rng))
382 }
383
384 pub fn decrypt(&self, ciphertext: &[u8]) -> Result<Vec<u8>, LinkError> {
386 let token = self.token.as_ref().ok_or(LinkError::NoSessionKey)?;
387 link_decrypt(token, ciphertext)
388 }
389
390 pub fn build_identify(
392 &self,
393 identity: &rns_crypto::identity::Identity,
394 rng: &mut dyn Rng,
395 ) -> Result<Vec<u8>, LinkError> {
396 if self.state != LinkState::Active {
397 return Err(LinkError::InvalidState);
398 }
399 let plaintext = identify::build_identify_data(identity, &self.link_id)?;
400 self.encrypt(&plaintext, rng)
401 }
402
403 pub fn handle_identify(&mut self, encrypted_data: &[u8]) -> Result<Vec<LinkAction>, LinkError> {
407 if self.state != LinkState::Active || self.is_initiator {
408 return Err(LinkError::InvalidState);
409 }
410
411 let plaintext = self.decrypt(encrypted_data)?;
412 let (identity_hash, public_key) =
413 identify::validate_identify_data(&plaintext, &self.link_id)?;
414 if self.remote_identity.is_some() {
415 return Ok(Vec::new());
416 }
417 self.remote_identity = Some((identity_hash, public_key));
418
419 Ok(alloc::vec![LinkAction::RemoteIdentified {
420 link_id: self.link_id,
421 identity_hash,
422 public_key,
423 }])
424 }
425
426 pub fn record_inbound(&mut self, now: f64) -> Vec<LinkAction> {
430 self.last_inbound = now;
431 if self.state == LinkState::Stale {
432 self.state = LinkState::Active;
433 return alloc::vec![LinkAction::StateChanged {
434 link_id: self.link_id,
435 new_state: LinkState::Active,
436 reason: None,
437 }];
438 }
439 Vec::new()
440 }
441
442 pub fn record_proof(&mut self, now: f64) {
444 self.last_proof = now;
445 }
446
447 pub fn record_outbound(&mut self, now: f64, is_keepalive: bool) {
449 self.last_outbound = now;
450 if is_keepalive {
451 self.last_keepalive = now;
452 }
453 }
454
455 pub fn record_inbound_traffic(&mut self, data_len: usize) {
457 if self.state != LinkState::Closed {
458 self.rx_packets = self.rx_packets.saturating_add(1);
459 self.rx_bytes = self.rx_bytes.saturating_add(data_len as u64);
460 }
461 }
462
463 pub fn record_outbound_traffic(&mut self, data_len: usize) {
465 self.tx_packets = self.tx_packets.saturating_add(1);
466 self.tx_bytes = self.tx_bytes.saturating_add(data_len as u64);
467 }
468
469 pub fn tick(&mut self, now: f64) -> Vec<LinkAction> {
471 let mut actions = Vec::new();
472
473 match self.state {
474 LinkState::Pending | LinkState::Handshake => {
475 if is_establishment_timeout(self.request_time, self.establishment_timeout, now) {
476 self.state = LinkState::Closed;
477 actions.push(LinkAction::StateChanged {
478 link_id: self.link_id,
479 new_state: LinkState::Closed,
480 reason: Some(TeardownReason::Timeout),
481 });
482 }
483 }
484 LinkState::Active => {
485 let activated = self.activated_at.unwrap_or(0.0);
486 let last_inbound = self.last_inbound.max(self.last_proof).max(activated);
488
489 if should_go_stale(last_inbound, self.stale_time, now) {
490 self.state = LinkState::Stale;
491 actions.push(LinkAction::StateChanged {
492 link_id: self.link_id,
493 new_state: LinkState::Stale,
494 reason: None,
495 });
496 }
497 }
498 LinkState::Stale => {
499 self.state = LinkState::Closed;
501 actions.push(LinkAction::StateChanged {
502 link_id: self.link_id,
503 new_state: LinkState::Closed,
504 reason: Some(TeardownReason::Timeout),
505 });
506 }
507 LinkState::Closed => {}
508 }
509
510 actions
511 }
512
513 pub fn needs_keepalive(&self, now: f64) -> bool {
515 if self.state != LinkState::Active || !self.is_initiator {
516 return false;
517 }
518 let activated = self.activated_at.unwrap_or(0.0);
519 let last_inbound = self.last_inbound.max(self.last_proof).max(activated);
520
521 let inbound_quiet = now >= last_inbound + self.keepalive_interval;
526 let outbound_quiet = now >= self.last_outbound + self.keepalive_interval;
527 if !inbound_quiet && !outbound_quiet {
528 return false;
529 }
530
531 should_send_keepalive(self.last_keepalive, self.keepalive_interval, now)
532 }
533
534 pub fn should_reply_keepalive(&self, payload: &[u8], now: f64) -> bool {
541 self.state == LinkState::Active
542 && !self.is_initiator
543 && payload == [0xff]
544 && now >= self.last_outbound + self.keepalive_interval
545 }
546
547 pub fn teardown(&mut self) -> Vec<LinkAction> {
549 if self.state == LinkState::Closed {
550 return Vec::new();
551 }
552 self.state = LinkState::Closed;
553 let reason = if self.is_initiator {
554 TeardownReason::InitiatorClosed
555 } else {
556 TeardownReason::DestinationClosed
557 };
558 alloc::vec![LinkAction::StateChanged {
559 link_id: self.link_id,
560 new_state: LinkState::Closed,
561 reason: Some(reason),
562 }]
563 }
564
565 pub fn handle_teardown(&mut self) -> Vec<LinkAction> {
567 if self.state == LinkState::Closed {
568 return Vec::new();
569 }
570 self.state = LinkState::Closed;
571 let reason = if self.is_initiator {
572 TeardownReason::DestinationClosed
573 } else {
574 TeardownReason::InitiatorClosed
575 };
576 alloc::vec![LinkAction::StateChanged {
577 link_id: self.link_id,
578 new_state: LinkState::Closed,
579 reason: Some(reason),
580 }]
581 }
582
583 pub fn link_id(&self) -> &LinkId {
586 &self.link_id
587 }
588
589 pub fn state(&self) -> LinkState {
590 self.state
591 }
592
593 pub fn rtt(&self) -> Option<f64> {
594 self.rtt
595 }
596
597 pub fn mdu(&self) -> usize {
598 self.mdu
599 }
600
601 pub fn mtu(&self) -> u32 {
602 self.mtu
603 }
604
605 pub fn is_initiator(&self) -> bool {
606 self.is_initiator
607 }
608
609 pub fn mode(&self) -> LinkMode {
610 self.mode
611 }
612
613 pub fn remote_identity(&self) -> Option<&([u8; 16], [u8; 64])> {
614 self.remote_identity.as_ref()
615 }
616
617 pub fn destination_hash(&self) -> &[u8; 16] {
618 &self.destination_hash
619 }
620
621 pub fn expected_hops(&self) -> u8 {
622 self.expected_hops
623 }
624
625 pub fn rebalanced_at(&self) -> Option<f64> {
626 self.rebalanced_at
627 }
628
629 pub fn tx_packets(&self) -> u64 {
630 self.tx_packets
631 }
632
633 pub fn rx_packets(&self) -> u64 {
634 self.rx_packets
635 }
636
637 pub fn tx_bytes(&self) -> u64 {
638 self.tx_bytes
639 }
640
641 pub fn rx_bytes(&self) -> u64 {
642 self.rx_bytes
643 }
644
645 pub fn derived_key(&self) -> Option<&[u8]> {
647 self.derived_key.as_deref()
648 }
649
650 pub fn keepalive_interval(&self) -> f64 {
651 self.keepalive_interval
652 }
653
654 pub fn set_rtt(&mut self, rtt: f64) {
657 self.rtt = Some(rtt);
658 self.update_keepalive();
659 }
660
661 pub fn set_mtu(&mut self, mtu: u32) {
663 self.mtu = mtu;
664 self.mdu = compute_mdu(mtu as usize);
665 }
666
667 #[doc(hidden)]
668 pub fn clear_session_for_testing(&mut self) {
669 self.derived_key = None;
670 self.token = None;
671 }
672
673 fn update_keepalive(&mut self) {
676 if let Some(rtt) = self.rtt {
677 self.keepalive_interval = compute_keepalive(rtt);
678 self.stale_time = compute_stale_time(self.keepalive_interval);
679 }
680 }
681}
682
683fn compute_mdu(mtu: usize) -> usize {
687 use crate::constants::{AES128_BLOCKSIZE, HEADER_MINSIZE, IFAC_MIN_SIZE, TOKEN_OVERHEAD};
688 let numerator = mtu.saturating_sub(IFAC_MIN_SIZE + HEADER_MINSIZE + TOKEN_OVERHEAD);
689 (numerator / AES128_BLOCKSIZE) * AES128_BLOCKSIZE - 1
690}
691
692#[cfg(test)]
693mod tests {
694 use super::*;
695 use crate::constants::LINK_MDU;
696 use rns_crypto::FixedRng;
697
698 fn make_rng(seed: u8) -> FixedRng {
699 FixedRng::new(&[seed; 128])
700 }
701
702 fn active_link_pair() -> (LinkEngine, LinkEngine, Vec<u8>) {
703 let mut rng_id = make_rng(0x01);
704 let dest_sig_prv = Ed25519PrivateKey::generate(&mut rng_id);
705 let dest_sig_pub_bytes = dest_sig_prv.public_key().public_bytes();
706 let dest_hash = [0xDD; 16];
707
708 let mut rng_init = make_rng(0x10);
709 let (mut initiator, request_data) = LinkEngine::new_initiator(
710 &dest_hash,
711 1,
712 LinkMode::Aes256Cbc,
713 Some(500),
714 100.0,
715 &mut rng_init,
716 );
717 let mut hashable = vec![0x00, 0x00];
718 hashable.extend_from_slice(&dest_hash);
719 hashable.push(0x00);
720 hashable.extend_from_slice(&request_data);
721 initiator.set_link_id_from_hashable(&hashable, request_data.len());
722
723 let mut rng_resp = make_rng(0x20);
724 let (mut responder, lrproof_data) = LinkEngine::new_responder(
725 &dest_sig_prv,
726 &dest_sig_pub_bytes,
727 &request_data,
728 &hashable,
729 &dest_hash,
730 1,
731 100.5,
732 &mut rng_resp,
733 )
734 .unwrap();
735 let mut rng_lrrtt = make_rng(0x30);
736 let (lrrtt_encrypted, _) = initiator
737 .handle_lrproof(&lrproof_data, &dest_sig_pub_bytes, 100.8, &mut rng_lrrtt)
738 .unwrap();
739 responder.handle_lrrtt(&lrrtt_encrypted, 101.0).unwrap();
740
741 (initiator, responder, lrrtt_encrypted)
742 }
743
744 #[test]
745 fn test_compute_mdu_default() {
746 assert_eq!(compute_mdu(500), LINK_MDU);
747 }
748
749 #[test]
750 fn test_full_handshake() {
751 let mut rng_id = make_rng(0x01);
753 let dest_sig_prv = Ed25519PrivateKey::generate(&mut rng_id);
754 let dest_sig_pub_bytes = dest_sig_prv.public_key().public_bytes();
755
756 let dest_hash = [0xDD; 16];
757 let mode = LinkMode::Aes256Cbc;
758
759 let mut rng_init = make_rng(0x10);
761 let (mut initiator, request_data) =
762 LinkEngine::new_initiator(&dest_hash, 1, mode, Some(500), 100.0, &mut rng_init);
763 assert_eq!(initiator.state(), LinkState::Pending);
764
765 let mut hashable = Vec::new();
768 hashable.push(0x00); hashable.push(0x00); hashable.extend_from_slice(&dest_hash);
771 hashable.push(0x00); hashable.extend_from_slice(&request_data);
773
774 initiator.set_link_id_from_hashable(&hashable, request_data.len());
775 assert_ne!(initiator.link_id(), &[0u8; 16]);
776
777 let mut rng_resp = make_rng(0x20);
779 let (mut responder, lrproof_data) = LinkEngine::new_responder(
780 &dest_sig_prv,
781 &dest_sig_pub_bytes,
782 &request_data,
783 &hashable,
784 &dest_hash,
785 1,
786 100.5,
787 &mut rng_resp,
788 )
789 .unwrap();
790 assert_eq!(responder.state(), LinkState::Handshake);
791 assert_eq!(responder.link_id(), initiator.link_id());
792
793 let mut rng_lrrtt = make_rng(0x30);
795 assert!(initiator
796 .handle_lrproof_with_hops(
797 &[0xAA; 3],
798 &dest_sig_pub_bytes,
799 Some(8),
800 100.6,
801 &mut rng_lrrtt,
802 )
803 .is_err());
804 assert_eq!(initiator.expected_hops(), 1);
805 let mismatched_mode_proof = handshake::build_lrproof(
806 initiator.link_id(),
807 lrproof_data[64..96].try_into().unwrap(),
808 &dest_sig_pub_bytes,
809 &dest_sig_prv,
810 Some(500),
811 LinkMode::Aes128Cbc,
812 );
813 assert_eq!(
814 initiator
815 .handle_lrproof_with_hops(
816 &mismatched_mode_proof,
817 &dest_sig_pub_bytes,
818 Some(9),
819 100.65,
820 &mut rng_lrrtt,
821 )
822 .unwrap_err(),
823 LinkError::UnsupportedMode,
824 );
825 assert_eq!(initiator.expected_hops(), 1);
826 let mut invalid_lrproof = lrproof_data.clone();
827 invalid_lrproof[0] ^= 0x01;
828 assert!(initiator
829 .handle_lrproof_with_hops(
830 &invalid_lrproof,
831 &dest_sig_pub_bytes,
832 Some(7),
833 100.7,
834 &mut rng_lrrtt,
835 )
836 .is_err());
837 assert_eq!(initiator.expected_hops(), 1);
838 let (lrrtt_encrypted, actions) = initiator
839 .handle_lrproof_with_hops(
840 &lrproof_data,
841 &dest_sig_pub_bytes,
842 Some(4),
843 100.8,
844 &mut rng_lrrtt,
845 )
846 .unwrap();
847 assert_eq!(initiator.state(), LinkState::Active);
848 assert!(initiator.rtt().is_some());
849 assert_eq!(initiator.rebalanced_at(), Some(100.8));
850 assert_eq!(actions.len(), 2); let actions = responder
854 .handle_lrrtt_with_hops(&lrrtt_encrypted, Some(4), 101.0)
855 .unwrap();
856 assert_eq!(responder.state(), LinkState::Active);
857 assert_eq!(initiator.expected_hops(), 4);
858 assert_eq!(responder.expected_hops(), 4);
859 assert_eq!(responder.rebalanced_at(), None);
860
861 initiator.state = LinkState::Pending;
864 let mut repeated_rng = make_rng(0x31);
865 initiator
866 .handle_lrproof_with_hops(
867 &lrproof_data,
868 &dest_sig_pub_bytes,
869 Some(6),
870 101.1,
871 &mut repeated_rng,
872 )
873 .unwrap();
874 assert_eq!(initiator.expected_hops(), 4);
875 assert_eq!(initiator.rebalanced_at(), Some(100.8));
876
877 initiator.record_outbound_traffic(48);
878 initiator.record_inbound_traffic(32);
879 assert_eq!((initiator.tx_packets(), initiator.tx_bytes()), (1, 48));
880 assert_eq!((initiator.rx_packets(), initiator.rx_bytes()), (1, 32));
881 assert!(responder.rtt().is_some());
882 assert_eq!(actions.len(), 2);
883 }
884
885 #[test]
886 fn test_encrypt_decrypt_after_handshake() {
887 let mut rng_id = make_rng(0x01);
888 let dest_sig_prv = Ed25519PrivateKey::generate(&mut rng_id);
889 let dest_sig_pub_bytes = dest_sig_prv.public_key().public_bytes();
890 let dest_hash = [0xDD; 16];
891
892 let mut rng_init = make_rng(0x10);
893 let (mut initiator, request_data) = LinkEngine::new_initiator(
894 &dest_hash,
895 1,
896 LinkMode::Aes256Cbc,
897 Some(500),
898 100.0,
899 &mut rng_init,
900 );
901 let mut hashable = Vec::new();
902 hashable.push(0x00);
903 hashable.push(0x00);
904 hashable.extend_from_slice(&dest_hash);
905 hashable.push(0x00);
906 hashable.extend_from_slice(&request_data);
907 initiator.set_link_id_from_hashable(&hashable, request_data.len());
908
909 let mut rng_resp = make_rng(0x20);
910 let (mut responder, lrproof_data) = LinkEngine::new_responder(
911 &dest_sig_prv,
912 &dest_sig_pub_bytes,
913 &request_data,
914 &hashable,
915 &dest_hash,
916 1,
917 100.5,
918 &mut rng_resp,
919 )
920 .unwrap();
921
922 let mut rng_lrrtt = make_rng(0x30);
923 let (lrrtt_encrypted, _) = initiator
924 .handle_lrproof(&lrproof_data, &dest_sig_pub_bytes, 100.8, &mut rng_lrrtt)
925 .unwrap();
926 responder.handle_lrrtt(&lrrtt_encrypted, 101.0).unwrap();
927
928 let mut rng_enc = make_rng(0x40);
930 let plaintext = b"Hello over encrypted link!";
931 let ciphertext = initiator.encrypt(plaintext, &mut rng_enc).unwrap();
932 let decrypted = responder.decrypt(&ciphertext).unwrap();
933 assert_eq!(decrypted, plaintext);
934
935 let mut rng_enc2 = make_rng(0x50);
937 let ciphertext2 = responder.encrypt(b"Reply!", &mut rng_enc2).unwrap();
938 let decrypted2 = initiator.decrypt(&ciphertext2).unwrap();
939 assert_eq!(decrypted2, b"Reply!");
940 }
941
942 #[test]
943 fn test_tick_establishment_timeout() {
944 let mut rng = make_rng(0x10);
945 let dest_hash = [0xDD; 16];
946 let (mut engine, _) =
947 LinkEngine::new_initiator(&dest_hash, 1, LinkMode::Aes256Cbc, None, 100.0, &mut rng);
948 let actions = engine.tick(110.0);
952 assert!(actions.is_empty());
953
954 let actions = engine.tick(113.0);
956 assert_eq!(actions.len(), 1);
957 assert_eq!(engine.state(), LinkState::Closed);
958 }
959
960 #[test]
961 fn test_tick_stale_and_close() {
962 let mut rng_id = make_rng(0x01);
963 let dest_sig_prv = Ed25519PrivateKey::generate(&mut rng_id);
964 let dest_sig_pub_bytes = dest_sig_prv.public_key().public_bytes();
965 let dest_hash = [0xDD; 16];
966
967 let mut rng_init = make_rng(0x10);
968 let (mut initiator, request_data) = LinkEngine::new_initiator(
969 &dest_hash,
970 1,
971 LinkMode::Aes256Cbc,
972 Some(500),
973 100.0,
974 &mut rng_init,
975 );
976 let mut hashable = Vec::new();
977 hashable.push(0x00);
978 hashable.push(0x00);
979 hashable.extend_from_slice(&dest_hash);
980 hashable.push(0x00);
981 hashable.extend_from_slice(&request_data);
982 initiator.set_link_id_from_hashable(&hashable, request_data.len());
983
984 let mut rng_resp = make_rng(0x20);
985 let (_, lrproof_data) = LinkEngine::new_responder(
986 &dest_sig_prv,
987 &dest_sig_pub_bytes,
988 &request_data,
989 &hashable,
990 &dest_hash,
991 1,
992 100.5,
993 &mut rng_resp,
994 )
995 .unwrap();
996
997 let mut rng_lrrtt = make_rng(0x30);
998 initiator
999 .handle_lrproof(&lrproof_data, &dest_sig_pub_bytes, 100.8, &mut rng_lrrtt)
1000 .unwrap();
1001 assert_eq!(initiator.state(), LinkState::Active);
1002
1003 let stale_time = initiator.stale_time;
1005 let actions = initiator.tick(100.8 + stale_time + 1.0);
1006 assert_eq!(initiator.state(), LinkState::Stale);
1007 assert_eq!(actions.len(), 1);
1008
1009 let actions = initiator.tick(100.8 + stale_time + 2.0);
1011 assert_eq!(initiator.state(), LinkState::Closed);
1012 assert_eq!(actions.len(), 1);
1013 }
1014
1015 #[test]
1016 fn test_needs_keepalive() {
1017 let mut rng_id = make_rng(0x01);
1018 let dest_sig_prv = Ed25519PrivateKey::generate(&mut rng_id);
1019 let dest_sig_pub_bytes = dest_sig_prv.public_key().public_bytes();
1020 let dest_hash = [0xDD; 16];
1021
1022 let mut rng_init = make_rng(0x10);
1023 let (mut initiator, request_data) = LinkEngine::new_initiator(
1024 &dest_hash,
1025 1,
1026 LinkMode::Aes256Cbc,
1027 Some(500),
1028 100.0,
1029 &mut rng_init,
1030 );
1031 let mut hashable = Vec::new();
1032 hashable.push(0x00);
1033 hashable.push(0x00);
1034 hashable.extend_from_slice(&dest_hash);
1035 hashable.push(0x00);
1036 hashable.extend_from_slice(&request_data);
1037 initiator.set_link_id_from_hashable(&hashable, request_data.len());
1038
1039 let mut rng_resp = make_rng(0x20);
1040 let (_, lrproof_data) = LinkEngine::new_responder(
1041 &dest_sig_prv,
1042 &dest_sig_pub_bytes,
1043 &request_data,
1044 &hashable,
1045 &dest_hash,
1046 1,
1047 100.5,
1048 &mut rng_resp,
1049 )
1050 .unwrap();
1051
1052 let mut rng_lrrtt = make_rng(0x30);
1053 initiator
1054 .handle_lrproof(&lrproof_data, &dest_sig_pub_bytes, 100.8, &mut rng_lrrtt)
1055 .unwrap();
1056
1057 let ka = initiator.keepalive_interval();
1058 assert!(!initiator.needs_keepalive(100.8 + ka - 1.0));
1060 assert!(initiator.needs_keepalive(100.8 + ka + 1.0));
1062 }
1063
1064 #[test]
1065 fn initiator_probes_when_outbound_is_quiet_despite_recent_inbound() {
1066 let (mut initiator, _, _) = active_link_pair();
1067 let keepalive = initiator.keepalive_interval();
1068 let now = 1_000.0;
1069
1070 initiator.record_inbound(now - 0.1);
1071 initiator.record_outbound(now - keepalive - 0.1, false);
1072
1073 assert!(initiator.needs_keepalive(now));
1074 }
1075
1076 #[test]
1077 fn initiator_probes_when_inbound_is_quiet_despite_recent_outbound() {
1078 let (mut initiator, _, _) = active_link_pair();
1079 let keepalive = initiator.keepalive_interval();
1080 let now = 1_000.0;
1081
1082 initiator.record_inbound(now - keepalive - 0.1);
1083 initiator.record_outbound(now - 0.1, false);
1084
1085 assert!(initiator.needs_keepalive(now));
1086 }
1087
1088 #[test]
1089 fn initiator_does_not_probe_when_both_directions_are_recent() {
1090 let (mut initiator, _, _) = active_link_pair();
1091 let now = 1_000.0;
1092
1093 initiator.record_inbound(now - 0.1);
1094 initiator.record_outbound(now - 0.1, false);
1095
1096 assert!(!initiator.needs_keepalive(now));
1097 }
1098
1099 #[test]
1100 fn test_needs_keepalive_responder() {
1101 let mut rng_id = make_rng(0x01);
1102 let dest_sig_prv = Ed25519PrivateKey::generate(&mut rng_id);
1103 let dest_sig_pub_bytes = dest_sig_prv.public_key().public_bytes();
1104 let dest_hash = [0xDD; 16];
1105
1106 let mut rng_init = make_rng(0x10);
1107 let (mut initiator, request_data) = LinkEngine::new_initiator(
1108 &dest_hash,
1109 1,
1110 LinkMode::Aes256Cbc,
1111 Some(500),
1112 100.0,
1113 &mut rng_init,
1114 );
1115 let mut hashable = Vec::new();
1116 hashable.push(0x00);
1117 hashable.push(0x00);
1118 hashable.extend_from_slice(&dest_hash);
1119 hashable.push(0x00);
1120 hashable.extend_from_slice(&request_data);
1121 initiator.set_link_id_from_hashable(&hashable, request_data.len());
1122
1123 let mut rng_resp = make_rng(0x20);
1124 let (mut responder, lrproof_data) = LinkEngine::new_responder(
1125 &dest_sig_prv,
1126 &dest_sig_pub_bytes,
1127 &request_data,
1128 &hashable,
1129 &dest_hash,
1130 1,
1131 100.5,
1132 &mut rng_resp,
1133 )
1134 .unwrap();
1135
1136 let mut rng_lrrtt = make_rng(0x30);
1137 let (lrrtt_encrypted, _) = initiator
1138 .handle_lrproof(&lrproof_data, &dest_sig_pub_bytes, 100.8, &mut rng_lrrtt)
1139 .unwrap();
1140 responder.handle_lrrtt(&lrrtt_encrypted, 101.0).unwrap();
1141
1142 let ka = responder.keepalive_interval();
1143 assert!(!responder.needs_keepalive(101.0 + ka - 1.0));
1145 assert!(!responder.needs_keepalive(101.0 + ka + 1.0));
1146 }
1147
1148 #[test]
1149 fn responder_replies_only_to_probe_and_only_when_outbound_is_quiet() {
1150 let (_, mut responder, _) = active_link_pair();
1151 let keepalive = responder.keepalive_interval();
1152 let now = 1_000.0;
1153
1154 responder.record_outbound(now - keepalive - 0.1, false);
1155 assert!(responder.should_reply_keepalive(&[0xff], now));
1156 assert!(!responder.should_reply_keepalive(&[0xfe], now));
1157 assert!(!responder.should_reply_keepalive(&[], now));
1158
1159 responder.record_outbound(now - 0.1, false);
1160 assert!(!responder.should_reply_keepalive(&[0xff], now));
1161 }
1162
1163 #[test]
1164 fn initiator_never_replies_to_keepalive_probe() {
1165 let (mut initiator, _, _) = active_link_pair();
1166 let keepalive = initiator.keepalive_interval();
1167 let now = 1_000.0;
1168 initiator.record_outbound(now - keepalive - 0.1, false);
1169
1170 assert!(!initiator.should_reply_keepalive(&[0xff], now));
1171 }
1172
1173 #[test]
1174 fn test_teardown() {
1175 let mut rng = make_rng(0x10);
1176 let (mut engine, _) =
1177 LinkEngine::new_initiator(&[0xDD; 16], 1, LinkMode::Aes256Cbc, None, 100.0, &mut rng);
1178 let actions = engine.teardown();
1179 assert_eq!(engine.state(), LinkState::Closed);
1180 assert_eq!(actions.len(), 1);
1181
1182 let actions = engine.teardown();
1184 assert!(actions.is_empty());
1185 }
1186
1187 #[test]
1188 fn test_handle_teardown() {
1189 let mut rng = make_rng(0x10);
1190 let (mut engine, _) =
1191 LinkEngine::new_initiator(&[0xDD; 16], 1, LinkMode::Aes256Cbc, None, 100.0, &mut rng);
1192 let actions = engine.handle_teardown();
1193 assert_eq!(engine.state(), LinkState::Closed);
1194 assert_eq!(actions.len(), 1);
1195 match &actions[0] {
1196 LinkAction::StateChanged { reason, .. } => {
1197 assert_eq!(*reason, Some(TeardownReason::DestinationClosed));
1198 }
1199 _ => panic!("Expected StateChanged"),
1200 }
1201 }
1202
1203 #[test]
1204 fn test_identify_over_link() {
1205 let mut rng_id = make_rng(0x01);
1206 let dest_sig_prv = Ed25519PrivateKey::generate(&mut rng_id);
1207 let dest_sig_pub_bytes = dest_sig_prv.public_key().public_bytes();
1208 let dest_hash = [0xDD; 16];
1209
1210 let mut rng_init = make_rng(0x10);
1211 let (mut initiator, request_data) = LinkEngine::new_initiator(
1212 &dest_hash,
1213 1,
1214 LinkMode::Aes256Cbc,
1215 Some(500),
1216 100.0,
1217 &mut rng_init,
1218 );
1219 let mut hashable = Vec::new();
1220 hashable.push(0x00);
1221 hashable.push(0x00);
1222 hashable.extend_from_slice(&dest_hash);
1223 hashable.push(0x00);
1224 hashable.extend_from_slice(&request_data);
1225 initiator.set_link_id_from_hashable(&hashable, request_data.len());
1226
1227 let mut rng_resp = make_rng(0x20);
1228 let (mut responder, lrproof_data) = LinkEngine::new_responder(
1229 &dest_sig_prv,
1230 &dest_sig_pub_bytes,
1231 &request_data,
1232 &hashable,
1233 &dest_hash,
1234 1,
1235 100.5,
1236 &mut rng_resp,
1237 )
1238 .unwrap();
1239
1240 let mut rng_lrrtt = make_rng(0x30);
1241 let (lrrtt_encrypted, _) = initiator
1242 .handle_lrproof(&lrproof_data, &dest_sig_pub_bytes, 100.8, &mut rng_lrrtt)
1243 .unwrap();
1244 responder.handle_lrrtt(&lrrtt_encrypted, 101.0).unwrap();
1245
1246 let mut rng_ident = make_rng(0x40);
1248 let my_identity = rns_crypto::identity::Identity::new(&mut rng_ident);
1249
1250 let mut rng_enc = make_rng(0x50);
1252 let identify_encrypted = initiator
1253 .build_identify(&my_identity, &mut rng_enc)
1254 .unwrap();
1255
1256 let actions = responder.handle_identify(&identify_encrypted).unwrap();
1257 assert_eq!(actions.len(), 1);
1258 match &actions[0] {
1259 LinkAction::RemoteIdentified {
1260 identity_hash,
1261 public_key,
1262 ..
1263 } => {
1264 assert_eq!(identity_hash, my_identity.hash());
1265 assert_eq!(public_key, &my_identity.get_public_key().unwrap());
1266 }
1267 _ => panic!("Expected RemoteIdentified"),
1268 }
1269
1270 let mut rng_repeat = make_rng(0x51);
1273 let repeated = initiator
1274 .build_identify(&my_identity, &mut rng_repeat)
1275 .unwrap();
1276 assert!(responder.handle_identify(&repeated).unwrap().is_empty());
1277
1278 let mut rng_other = make_rng(0x60);
1279 let other_identity = rns_crypto::identity::Identity::new(&mut rng_other);
1280 let mut rng_other_enc = make_rng(0x61);
1281 let other = initiator
1282 .build_identify(&other_identity, &mut rng_other_enc)
1283 .unwrap();
1284 assert!(responder.handle_identify(&other).unwrap().is_empty());
1285 assert_eq!(
1286 responder.remote_identity().map(|(hash, _)| hash),
1287 Some(my_identity.hash())
1288 );
1289
1290 let mut rng_bad = make_rng(0x70);
1292 let malformed = initiator
1293 .encrypt(b"invalid identify", &mut rng_bad)
1294 .unwrap();
1295 assert!(responder.handle_identify(&malformed).is_err());
1296 assert_eq!(
1297 responder.remote_identity().map(|(hash, _)| hash),
1298 Some(my_identity.hash())
1299 );
1300 }
1301
1302 #[test]
1303 fn test_aes128_mode_handshake() {
1304 let mut rng_id = make_rng(0x01);
1305 let dest_sig_prv = Ed25519PrivateKey::generate(&mut rng_id);
1306 let dest_sig_pub_bytes = dest_sig_prv.public_key().public_bytes();
1307 let dest_hash = [0xDD; 16];
1308
1309 let mut rng_init = make_rng(0x10);
1310 let (mut initiator, request_data) = LinkEngine::new_initiator(
1311 &dest_hash,
1312 1,
1313 LinkMode::Aes128Cbc,
1314 Some(500),
1315 100.0,
1316 &mut rng_init,
1317 );
1318 let mut hashable = Vec::new();
1319 hashable.push(0x00);
1320 hashable.push(0x00);
1321 hashable.extend_from_slice(&dest_hash);
1322 hashable.push(0x00);
1323 hashable.extend_from_slice(&request_data);
1324 initiator.set_link_id_from_hashable(&hashable, request_data.len());
1325
1326 let mut rng_resp = make_rng(0x20);
1327 let (mut responder, lrproof_data) = LinkEngine::new_responder(
1328 &dest_sig_prv,
1329 &dest_sig_pub_bytes,
1330 &request_data,
1331 &hashable,
1332 &dest_hash,
1333 1,
1334 100.5,
1335 &mut rng_resp,
1336 )
1337 .unwrap();
1338
1339 let mut rng_lrrtt = make_rng(0x30);
1340 let (lrrtt_encrypted, _) = initiator
1341 .handle_lrproof(&lrproof_data, &dest_sig_pub_bytes, 100.8, &mut rng_lrrtt)
1342 .unwrap();
1343 responder.handle_lrrtt(&lrrtt_encrypted, 101.0).unwrap();
1344
1345 assert_eq!(initiator.state(), LinkState::Active);
1346 assert_eq!(responder.state(), LinkState::Active);
1347 assert_eq!(initiator.mode(), LinkMode::Aes128Cbc);
1348
1349 let mut rng_enc = make_rng(0x40);
1351 let ct = initiator.encrypt(b"AES128 test", &mut rng_enc).unwrap();
1352 let pt = responder.decrypt(&ct).unwrap();
1353 assert_eq!(pt, b"AES128 test");
1354 }
1355}
1356use alloc::vec;