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