1use alloc::vec::Vec;
16
17use rns_crypto::hkdf::hkdf;
18use rns_crypto::Rng;
19
20use crate::msgpack::{self, Value};
21
22use super::types::*;
23
24pub fn derive_punch_token(
28 derived_key: &[u8],
29 session_id: &[u8; 16],
30) -> Result<[u8; 32], HolePunchError> {
31 let result = hkdf(32, derived_key, Some(session_id), Some(b"rns-holepunch-v1"))
32 .map_err(|_| HolePunchError::NoDerivedKey)?;
33 let mut token = [0u8; 32];
34 token.copy_from_slice(&result);
35 Ok(token)
36}
37
38pub struct HolePunchEngine {
40 link_id: [u8; 16],
41 session_id: [u8; 16],
42 state: HolePunchState,
43 is_initiator: bool,
44
45 our_public_endpoint: Option<Endpoint>,
47
48 peer_public_endpoint: Option<Endpoint>,
52
53 facilitator_addr: Option<Endpoint>,
57
58 punch_token: [u8; 32],
60
61 probe_addr: Option<Endpoint>,
63
64 probe_protocol: ProbeProtocol,
66
67 state_entered_at: f64,
69}
70
71impl HolePunchEngine {
72 pub fn validate_upgrade_request(payload: &[u8]) -> Result<(), HolePunchError> {
74 decode_upgrade_request(payload).map(|_| ())
75 }
76
77 pub fn new(
79 link_id: [u8; 16],
80 probe_addr: Option<Endpoint>,
81 probe_protocol: ProbeProtocol,
82 ) -> Self {
83 HolePunchEngine {
84 link_id,
85 session_id: [0u8; 16],
86 state: HolePunchState::Idle,
87 is_initiator: false,
88 our_public_endpoint: None,
89 peer_public_endpoint: None,
90 facilitator_addr: None,
91 punch_token: [0u8; 32],
92 probe_addr,
93 probe_protocol,
94 state_entered_at: 0.0,
95 }
96 }
97
98 pub fn state(&self) -> HolePunchState {
99 self.state
100 }
101
102 pub fn session_id(&self) -> &[u8; 16] {
103 &self.session_id
104 }
105
106 pub fn is_initiator(&self) -> bool {
107 self.is_initiator
108 }
109
110 pub fn punch_token(&self) -> &[u8; 32] {
111 &self.punch_token
112 }
113
114 pub fn set_facilitator(&mut self, addr: Endpoint) {
120 self.facilitator_addr = Some(addr);
121 }
122
123 pub fn peer_public_endpoint(&self) -> Option<&Endpoint> {
125 self.peer_public_endpoint.as_ref()
126 }
127
128 pub fn propose(
135 &mut self,
136 derived_key: &[u8],
137 now: f64,
138 rng: &mut dyn Rng,
139 ) -> Result<Vec<HolePunchAction>, HolePunchError> {
140 if self.state != HolePunchState::Idle {
141 return Err(HolePunchError::InvalidState);
142 }
143
144 let mut session_id = [0u8; 16];
146 rng.fill_bytes(&mut session_id);
147 self.session_id = session_id;
148 self.is_initiator = true;
149
150 self.punch_token = derive_punch_token(derived_key, &session_id)?;
152
153 let probe_addr = self.probe_addr.clone().ok_or(HolePunchError::NoProbeAddr)?;
154 self.facilitator_addr = Some(probe_addr.clone());
155
156 self.state = HolePunchState::Discovering;
158 self.state_entered_at = now;
159
160 Ok(alloc::vec![HolePunchAction::DiscoverEndpoints {
161 probe_addr,
162 protocol: self.probe_protocol
163 }])
164 }
165
166 pub fn endpoints_discovered(
171 &mut self,
172 public_endpoint: Endpoint,
173 now: f64,
174 ) -> Result<Vec<HolePunchAction>, HolePunchError> {
175 if self.state != HolePunchState::Discovering {
176 return Err(HolePunchError::InvalidState);
177 }
178
179 self.our_public_endpoint = Some(public_endpoint.clone());
180
181 if self.is_initiator {
182 let facilitator = self
184 .facilitator_addr
185 .clone()
186 .ok_or(HolePunchError::NoProbeAddr)?;
187
188 let payload = encode_upgrade_request(
189 &self.session_id,
190 &facilitator,
191 &public_endpoint,
192 self.probe_protocol,
193 );
194
195 self.state = HolePunchState::Proposing;
196 self.state_entered_at = now;
197
198 Ok(alloc::vec![HolePunchAction::SendSignal {
199 link_id: self.link_id,
200 msgtype: UPGRADE_REQUEST,
201 payload,
202 }])
203 } else {
204 let payload = encode_upgrade_ready(&self.session_id, &public_endpoint);
206
207 let peer_public = self
208 .peer_public_endpoint
209 .clone()
210 .ok_or(HolePunchError::InvalidState)?;
211
212 self.state = HolePunchState::Punching;
213 self.state_entered_at = now;
214
215 Ok(alloc::vec![
216 HolePunchAction::SendSignal {
217 link_id: self.link_id,
218 msgtype: UPGRADE_READY,
219 payload,
220 },
221 HolePunchAction::StartUdpPunch {
222 peer_public,
223 punch_token: self.punch_token,
224 session_id: self.session_id,
225 },
226 ])
227 }
228 }
229
230 pub fn handle_signal(
234 &mut self,
235 msgtype: u16,
236 payload: &[u8],
237 derived_key: Option<&[u8]>,
238 now: f64,
239 ) -> Result<Vec<HolePunchAction>, HolePunchError> {
240 match msgtype {
241 UPGRADE_REQUEST => self.handle_upgrade_request(payload, derived_key, now),
242 UPGRADE_ACCEPT => self.handle_upgrade_accept(payload, now),
243 UPGRADE_REJECT => self.handle_upgrade_reject(payload, now),
244 UPGRADE_READY => self.handle_upgrade_ready(payload, now),
245 UPGRADE_COMPLETE => self.handle_upgrade_complete(payload, now),
246 _ => Err(HolePunchError::InvalidPayload),
247 }
248 }
249
250 pub fn punch_succeeded(&mut self, now: f64) -> Result<Vec<HolePunchAction>, HolePunchError> {
254 if self.state != HolePunchState::Punching {
255 return Err(HolePunchError::InvalidState);
256 }
257
258 self.state = HolePunchState::Connected;
259 self.state_entered_at = now;
260
261 Ok(alloc::vec![HolePunchAction::Succeeded {
262 session_id: self.session_id,
263 },])
264 }
265
266 pub fn punch_failed(&mut self, now: f64) -> Result<Vec<HolePunchAction>, HolePunchError> {
270 if self.state != HolePunchState::Punching {
271 return Err(HolePunchError::InvalidState);
272 }
273
274 self.state = HolePunchState::Failed;
275 self.state_entered_at = now;
276
277 Ok(alloc::vec![HolePunchAction::Failed {
278 session_id: self.session_id,
279 reason: FAIL_TIMEOUT,
280 },])
281 }
282
283 pub fn tick(&mut self, now: f64) -> Vec<HolePunchAction> {
285 let elapsed = now - self.state_entered_at;
286 match self.state {
287 HolePunchState::Discovering if elapsed > DISCOVER_TIMEOUT => {
288 self.state = HolePunchState::Failed;
289 self.state_entered_at = now;
290 alloc::vec![HolePunchAction::Failed {
291 session_id: self.session_id,
292 reason: FAIL_PROBE,
293 }]
294 }
295 HolePunchState::Proposing if elapsed > PROPOSE_TIMEOUT => {
296 self.state = HolePunchState::Failed;
297 self.state_entered_at = now;
298 alloc::vec![HolePunchAction::Failed {
299 session_id: self.session_id,
300 reason: FAIL_TIMEOUT,
301 }]
302 }
303 HolePunchState::WaitingReady if elapsed > READY_TIMEOUT => {
304 self.state = HolePunchState::Failed;
305 self.state_entered_at = now;
306 alloc::vec![HolePunchAction::Failed {
307 session_id: self.session_id,
308 reason: FAIL_TIMEOUT,
309 }]
310 }
311 HolePunchState::Punching if elapsed > PUNCH_TIMEOUT => {
312 self.state = HolePunchState::Failed;
313 self.state_entered_at = now;
314 alloc::vec![HolePunchAction::Failed {
315 session_id: self.session_id,
316 reason: FAIL_TIMEOUT,
317 }]
318 }
319 _ => Vec::new(),
320 }
321 }
322
323 pub fn build_reject(
327 link_id: [u8; 16],
328 request_payload: &[u8],
329 reason: u8,
330 ) -> Result<HolePunchAction, HolePunchError> {
331 let (session_id, _, _, _) = decode_upgrade_request(request_payload)?;
332 let payload = encode_upgrade_reject(&session_id, reason);
333 Ok(HolePunchAction::SendSignal {
334 link_id,
335 msgtype: UPGRADE_REJECT,
336 payload,
337 })
338 }
339
340 pub fn reset(&mut self) {
342 self.state = HolePunchState::Idle;
343 self.session_id = [0u8; 16];
344 self.is_initiator = false;
345 self.our_public_endpoint = None;
346 self.peer_public_endpoint = None;
347 self.facilitator_addr = None;
348 self.punch_token = [0u8; 32];
349 self.probe_protocol = ProbeProtocol::Rnsp;
350 self.state_entered_at = 0.0;
351 }
352
353 fn handle_upgrade_request(
360 &mut self,
361 payload: &[u8],
362 derived_key: Option<&[u8]>,
363 now: f64,
364 ) -> Result<Vec<HolePunchAction>, HolePunchError> {
365 if self.state != HolePunchState::Idle {
366 let (session_id, _, _, _) = decode_upgrade_request(payload)?;
368 let reject_payload = encode_upgrade_reject(&session_id, REJECT_BUSY);
369 return Ok(alloc::vec![HolePunchAction::SendSignal {
370 link_id: self.link_id,
371 msgtype: UPGRADE_REJECT,
372 payload: reject_payload,
373 }]);
374 }
375
376 let derived_key = derived_key.ok_or(HolePunchError::NoDerivedKey)?;
377 let (session_id, facilitator, initiator_public, protocol) =
378 decode_upgrade_request(payload)?;
379
380 self.session_id = session_id;
381 self.is_initiator = false;
382 self.probe_protocol = protocol;
383 self.punch_token = derive_punch_token(derived_key, &session_id)?;
384
385 self.peer_public_endpoint = Some(initiator_public);
387
388 self.facilitator_addr = Some(facilitator.clone());
390
391 self.state = HolePunchState::Discovering;
392 self.state_entered_at = now;
393
394 let accept_payload = encode_upgrade_accept(&session_id);
396
397 Ok(alloc::vec![
398 HolePunchAction::SendSignal {
399 link_id: self.link_id,
400 msgtype: UPGRADE_ACCEPT,
401 payload: accept_payload,
402 },
403 HolePunchAction::DiscoverEndpoints {
404 probe_addr: facilitator,
405 protocol
406 },
407 ])
408 }
409
410 fn handle_upgrade_accept(
414 &mut self,
415 payload: &[u8],
416 now: f64,
417 ) -> Result<Vec<HolePunchAction>, HolePunchError> {
418 if self.state != HolePunchState::Proposing || !self.is_initiator {
419 return Err(HolePunchError::InvalidState);
420 }
421
422 let session_id = decode_upgrade_accept(payload)?;
423 if session_id != self.session_id {
424 return Err(HolePunchError::SessionMismatch);
425 }
426
427 self.state = HolePunchState::WaitingReady;
428 self.state_entered_at = now;
429
430 Ok(Vec::new())
431 }
432
433 fn handle_upgrade_reject(
435 &mut self,
436 payload: &[u8],
437 now: f64,
438 ) -> Result<Vec<HolePunchAction>, HolePunchError> {
439 if self.state != HolePunchState::Proposing {
440 return Err(HolePunchError::InvalidState);
441 }
442
443 let (session_id, reason) = decode_upgrade_reject(payload)?;
444 if session_id != self.session_id {
445 return Err(HolePunchError::SessionMismatch);
446 }
447
448 self.state = HolePunchState::Failed;
449 self.state_entered_at = now;
450
451 Ok(alloc::vec![HolePunchAction::Failed {
452 session_id: self.session_id,
453 reason,
454 }])
455 }
456
457 fn handle_upgrade_ready(
462 &mut self,
463 payload: &[u8],
464 now: f64,
465 ) -> Result<Vec<HolePunchAction>, HolePunchError> {
466 if self.state != HolePunchState::WaitingReady || !self.is_initiator {
467 return Err(HolePunchError::InvalidState);
468 }
469
470 let (session_id, responder_public) = decode_upgrade_ready(payload)?;
471 if session_id != self.session_id {
472 return Err(HolePunchError::SessionMismatch);
473 }
474
475 self.peer_public_endpoint = Some(responder_public.clone());
476
477 self.state = HolePunchState::Punching;
478 self.state_entered_at = now;
479
480 Ok(alloc::vec![HolePunchAction::StartUdpPunch {
481 peer_public: responder_public,
482 punch_token: self.punch_token,
483 session_id: self.session_id,
484 }])
485 }
486
487 fn handle_upgrade_complete(
489 &mut self,
490 payload: &[u8],
491 now: f64,
492 ) -> Result<Vec<HolePunchAction>, HolePunchError> {
493 if self.state != HolePunchState::Punching && self.state != HolePunchState::Connected {
494 return Err(HolePunchError::InvalidState);
495 }
496
497 let session_id = decode_session_only(payload)?;
498 if session_id != self.session_id {
499 return Err(HolePunchError::SessionMismatch);
500 }
501
502 if self.state == HolePunchState::Connected {
503 return Ok(Vec::new());
505 }
506
507 self.state = HolePunchState::Connected;
508 self.state_entered_at = now;
509
510 Ok(alloc::vec![HolePunchAction::Succeeded {
511 session_id: self.session_id,
512 }])
513 }
514}
515
516fn encode_upgrade_request(
519 session_id: &[u8; 16],
520 facilitator: &Endpoint,
521 initiator_public: &Endpoint,
522 protocol: ProbeProtocol,
523) -> Vec<u8> {
524 let mut fields = alloc::vec![
525 (
526 Value::Str(alloc::string::String::from("s")),
527 Value::Bin(session_id.to_vec())
528 ),
529 (
530 Value::Str(alloc::string::String::from("f")),
531 encode_endpoint(facilitator)
532 ),
533 (
534 Value::Str(alloc::string::String::from("a")),
535 encode_endpoint(initiator_public)
536 ),
537 ];
538 if protocol != ProbeProtocol::Rnsp {
540 fields.push((
541 Value::Str(alloc::string::String::from("p")),
542 Value::UInt(protocol as u64),
543 ));
544 }
545 let val = Value::Map(fields);
546 msgpack::pack(&val)
547}
548
549fn decode_upgrade_request(
550 data: &[u8],
551) -> Result<([u8; 16], Endpoint, Endpoint, ProbeProtocol), HolePunchError> {
552 let (val, _) = msgpack::unpack(data).map_err(|_| HolePunchError::InvalidPayload)?;
553 let session_id = extract_session_id(&val)?;
554 let facilitator = val
555 .map_get("f")
556 .and_then(decode_endpoint)
557 .ok_or(HolePunchError::InvalidPayload)?;
558 let initiator_public = val
559 .map_get("a")
560 .and_then(decode_endpoint)
561 .ok_or(HolePunchError::InvalidPayload)?;
562 let protocol = val
564 .map_get("p")
565 .and_then(|v| v.as_uint())
566 .map(|p| match p {
567 1 => ProbeProtocol::Stun,
568 _ => ProbeProtocol::Rnsp,
569 })
570 .unwrap_or(ProbeProtocol::Rnsp);
571 Ok((session_id, facilitator, initiator_public, protocol))
572}
573
574fn encode_upgrade_accept(session_id: &[u8; 16]) -> Vec<u8> {
575 let val = Value::Map(alloc::vec![(
576 Value::Str(alloc::string::String::from("s")),
577 Value::Bin(session_id.to_vec())
578 ),]);
579 msgpack::pack(&val)
580}
581
582fn decode_upgrade_accept(data: &[u8]) -> Result<[u8; 16], HolePunchError> {
583 let (val, _) = msgpack::unpack(data).map_err(|_| HolePunchError::InvalidPayload)?;
584 extract_session_id(&val)
585}
586
587fn encode_upgrade_reject(session_id: &[u8; 16], reason: u8) -> Vec<u8> {
588 let val = Value::Map(alloc::vec![
589 (
590 Value::Str(alloc::string::String::from("s")),
591 Value::Bin(session_id.to_vec())
592 ),
593 (
594 Value::Str(alloc::string::String::from("r")),
595 Value::UInt(reason as u64)
596 ),
597 ]);
598 msgpack::pack(&val)
599}
600
601fn decode_upgrade_reject(data: &[u8]) -> Result<([u8; 16], u8), HolePunchError> {
602 let (val, _) = msgpack::unpack(data).map_err(|_| HolePunchError::InvalidPayload)?;
603 let session_id = extract_session_id(&val)?;
604 let reason = val
605 .map_get("r")
606 .and_then(|v| v.as_uint())
607 .ok_or(HolePunchError::InvalidPayload)? as u8;
608 Ok((session_id, reason))
609}
610
611fn encode_upgrade_ready(session_id: &[u8; 16], responder_public: &Endpoint) -> Vec<u8> {
612 let val = Value::Map(alloc::vec![
613 (
614 Value::Str(alloc::string::String::from("s")),
615 Value::Bin(session_id.to_vec())
616 ),
617 (
618 Value::Str(alloc::string::String::from("a")),
619 encode_endpoint(responder_public)
620 ),
621 ]);
622 msgpack::pack(&val)
623}
624
625fn decode_upgrade_ready(data: &[u8]) -> Result<([u8; 16], Endpoint), HolePunchError> {
626 let (val, _) = msgpack::unpack(data).map_err(|_| HolePunchError::InvalidPayload)?;
627 let session_id = extract_session_id(&val)?;
628 let responder_public = val
629 .map_get("a")
630 .and_then(decode_endpoint)
631 .ok_or(HolePunchError::InvalidPayload)?;
632 Ok((session_id, responder_public))
633}
634
635fn encode_endpoint(ep: &Endpoint) -> Value {
636 Value::Array(alloc::vec![
637 Value::Bin(ep.addr.clone()),
638 Value::UInt(ep.port as u64),
639 ])
640}
641
642fn decode_endpoint(val: &Value) -> Option<Endpoint> {
643 let arr = val.as_array()?;
644 if arr.len() < 2 {
645 return None;
646 }
647 let addr = arr[0].as_bin()?.to_vec();
648 let port = arr[1].as_uint()? as u16;
649 Some(Endpoint { addr, port })
650}
651
652#[cfg(test)]
653fn encode_session_only(session_id: &[u8; 16]) -> Vec<u8> {
654 let val = Value::Map(alloc::vec![(
655 Value::Str(alloc::string::String::from("s")),
656 Value::Bin(session_id.to_vec())
657 ),]);
658 msgpack::pack(&val)
659}
660
661fn decode_session_only(data: &[u8]) -> Result<[u8; 16], HolePunchError> {
662 let (val, _) = msgpack::unpack(data).map_err(|_| HolePunchError::InvalidPayload)?;
663 extract_session_id(&val)
664}
665
666fn extract_session_id(val: &Value) -> Result<[u8; 16], HolePunchError> {
667 let bin = val
668 .map_get("s")
669 .and_then(|v| v.as_bin())
670 .ok_or(HolePunchError::InvalidPayload)?;
671 if bin.len() != 16 {
672 return Err(HolePunchError::InvalidPayload);
673 }
674 let mut id = [0u8; 16];
675 id.copy_from_slice(bin);
676 Ok(id)
677}
678
679#[cfg(test)]
680mod tests {
681 use super::*;
682 use rns_crypto::FixedRng;
683
684 fn make_rng(seed: u8) -> FixedRng {
685 FixedRng::new(&[seed; 128])
686 }
687
688 fn test_derived_key() -> Vec<u8> {
689 vec![0xAA; 32]
690 }
691
692 fn test_probe_addr() -> Endpoint {
693 Endpoint {
694 addr: vec![127, 0, 0, 1],
695 port: 4343,
696 }
697 }
698
699 fn test_public_addr_a() -> Endpoint {
700 Endpoint {
701 addr: vec![1, 2, 3, 4],
702 port: 41000,
703 }
704 }
705
706 fn test_public_addr_b() -> Endpoint {
707 Endpoint {
708 addr: vec![5, 6, 7, 8],
709 port: 52000,
710 }
711 }
712
713 #[test]
714 fn test_propose_initiator_discovers_first() {
715 let link_id = [0x11; 16];
716 let derived_key = test_derived_key();
717 let mut rng = make_rng(0x42);
718
719 let mut initiator =
720 HolePunchEngine::new(link_id, Some(test_probe_addr()), ProbeProtocol::Rnsp);
721 let actions = initiator.propose(&derived_key, 100.0, &mut rng).unwrap();
722
723 assert_eq!(initiator.state(), HolePunchState::Discovering);
725 assert_eq!(actions.len(), 1);
726 assert!(matches!(
727 &actions[0],
728 HolePunchAction::DiscoverEndpoints { .. }
729 ));
730 }
731
732 #[test]
733 fn test_initiator_sends_request_after_discovery() {
734 let link_id = [0x11; 16];
735 let derived_key = test_derived_key();
736 let mut rng = make_rng(0x42);
737
738 let mut initiator =
739 HolePunchEngine::new(link_id, Some(test_probe_addr()), ProbeProtocol::Rnsp);
740 initiator.propose(&derived_key, 100.0, &mut rng).unwrap();
741
742 let actions = initiator
744 .endpoints_discovered(test_public_addr_a(), 101.0)
745 .unwrap();
746
747 assert_eq!(initiator.state(), HolePunchState::Proposing);
749 assert_eq!(actions.len(), 1);
750 match &actions[0] {
751 HolePunchAction::SendSignal {
752 msgtype, payload, ..
753 } => {
754 assert_eq!(*msgtype, UPGRADE_REQUEST);
755 let (sid, facilitator, init_pub, _proto) = decode_upgrade_request(payload).unwrap();
757 assert_eq!(sid, *initiator.session_id());
758 assert_eq!(facilitator, test_probe_addr());
759 assert_eq!(init_pub, test_public_addr_a());
760 }
761 _ => panic!("Expected SendSignal(UPGRADE_REQUEST)"),
762 }
763 }
764
765 #[test]
766 fn test_full_asymmetric_flow() {
767 let link_id = [0x22; 16];
768 let derived_key = test_derived_key();
769 let mut rng = make_rng(0x42);
770
771 let mut initiator =
773 HolePunchEngine::new(link_id, Some(test_probe_addr()), ProbeProtocol::Rnsp);
774 initiator.propose(&derived_key, 100.0, &mut rng).unwrap();
775 let actions = initiator
776 .endpoints_discovered(test_public_addr_a(), 101.0)
777 .unwrap();
778
779 let request_payload = match &actions[0] {
780 HolePunchAction::SendSignal { payload, .. } => payload.clone(),
781 _ => panic!(),
782 };
783
784 let mut responder = HolePunchEngine::new(link_id, None, ProbeProtocol::Rnsp); let actions = responder
787 .handle_signal(UPGRADE_REQUEST, &request_payload, Some(&derived_key), 102.0)
788 .unwrap();
789
790 assert_eq!(responder.state(), HolePunchState::Discovering);
791 assert_eq!(actions.len(), 2); let accept_payload = match &actions[0] {
794 HolePunchAction::SendSignal {
795 msgtype, payload, ..
796 } => {
797 assert_eq!(*msgtype, UPGRADE_ACCEPT);
798 payload.clone()
799 }
800 _ => panic!("Expected UPGRADE_ACCEPT"),
801 };
802
803 match &actions[1] {
805 HolePunchAction::DiscoverEndpoints { probe_addr, .. } => {
806 assert_eq!(*probe_addr, test_probe_addr()); }
808 _ => panic!("Expected DiscoverEndpoints"),
809 }
810
811 let actions = initiator
813 .handle_signal(UPGRADE_ACCEPT, &accept_payload, None, 103.0)
814 .unwrap();
815 assert_eq!(initiator.state(), HolePunchState::WaitingReady);
816 assert!(actions.is_empty()); let actions = responder
820 .endpoints_discovered(test_public_addr_b(), 104.0)
821 .unwrap();
822
823 assert_eq!(responder.state(), HolePunchState::Punching);
824 assert_eq!(actions.len(), 2); let ready_payload = match &actions[0] {
827 HolePunchAction::SendSignal {
828 msgtype, payload, ..
829 } => {
830 assert_eq!(*msgtype, UPGRADE_READY);
831 payload.clone()
832 }
833 _ => panic!("Expected UPGRADE_READY"),
834 };
835 assert!(matches!(&actions[1], HolePunchAction::StartUdpPunch { .. }));
836
837 let actions = initiator
839 .handle_signal(UPGRADE_READY, &ready_payload, None, 105.0)
840 .unwrap();
841
842 assert_eq!(initiator.state(), HolePunchState::Punching);
843 assert_eq!(actions.len(), 1);
844 match &actions[0] {
845 HolePunchAction::StartUdpPunch { peer_public, .. } => {
846 assert_eq!(*peer_public, test_public_addr_b());
847 }
848 _ => panic!("Expected StartUdpPunch"),
849 }
850
851 assert_eq!(initiator.punch_token(), responder.punch_token());
853 }
854
855 #[test]
856 fn test_punch_success() {
857 let link_id = [0x33; 16];
858 let derived_key = test_derived_key();
859 let mut rng = make_rng(0x42);
860
861 let mut engine =
862 HolePunchEngine::new(link_id, Some(test_probe_addr()), ProbeProtocol::Rnsp);
863 engine.propose(&derived_key, 100.0, &mut rng).unwrap();
864 engine.state = HolePunchState::Punching;
865
866 let actions = engine.punch_succeeded(105.0).unwrap();
867 assert_eq!(engine.state(), HolePunchState::Connected);
868 assert_eq!(actions.len(), 1);
869 assert!(matches!(&actions[0], HolePunchAction::Succeeded { .. }));
870 }
871
872 #[test]
873 fn test_punch_failed() {
874 let link_id = [0x44; 16];
875 let derived_key = test_derived_key();
876 let mut rng = make_rng(0x42);
877
878 let mut engine =
879 HolePunchEngine::new(link_id, Some(test_probe_addr()), ProbeProtocol::Rnsp);
880 engine.propose(&derived_key, 100.0, &mut rng).unwrap();
881 engine.state = HolePunchState::Punching;
882
883 let actions = engine.punch_failed(120.0).unwrap();
884 assert_eq!(engine.state(), HolePunchState::Failed);
885 assert_eq!(actions.len(), 1);
886 assert!(matches!(&actions[0], HolePunchAction::Failed { .. }));
887 }
888
889 #[test]
890 fn test_reject_when_busy() {
891 let link_id = [0x55; 16];
892 let derived_key = test_derived_key();
893 let mut rng = make_rng(0x42);
894
895 let mut proposer =
897 HolePunchEngine::new(link_id, Some(test_probe_addr()), ProbeProtocol::Rnsp);
898 proposer.propose(&derived_key, 100.0, &mut rng).unwrap();
899 let actions = proposer
900 .endpoints_discovered(test_public_addr_a(), 101.0)
901 .unwrap();
902 let request_payload = match &actions[0] {
903 HolePunchAction::SendSignal { payload, .. } => payload.clone(),
904 _ => panic!(),
905 };
906
907 let mut responder =
909 HolePunchEngine::new(link_id, Some(test_probe_addr()), ProbeProtocol::Rnsp);
910 responder.state = HolePunchState::Discovering;
911
912 let actions = responder
913 .handle_signal(UPGRADE_REQUEST, &request_payload, Some(&derived_key), 102.0)
914 .unwrap();
915
916 assert_eq!(actions.len(), 1);
918 match &actions[0] {
919 HolePunchAction::SendSignal { msgtype, .. } => {
920 assert_eq!(*msgtype, UPGRADE_REJECT);
921 }
922 _ => panic!("Expected UPGRADE_REJECT"),
923 }
924 }
925
926 #[test]
927 fn test_initiator_receives_reject() {
928 let link_id = [0x66; 16];
929 let derived_key = test_derived_key();
930 let mut rng = make_rng(0x42);
931
932 let mut initiator =
933 HolePunchEngine::new(link_id, Some(test_probe_addr()), ProbeProtocol::Rnsp);
934 initiator.propose(&derived_key, 100.0, &mut rng).unwrap();
935 initiator
936 .endpoints_discovered(test_public_addr_a(), 101.0)
937 .unwrap();
938 assert_eq!(initiator.state(), HolePunchState::Proposing);
939
940 let session_id = *initiator.session_id();
941 let reject_payload = encode_upgrade_reject(&session_id, REJECT_POLICY);
942
943 let actions = initiator
944 .handle_signal(UPGRADE_REJECT, &reject_payload, None, 102.0)
945 .unwrap();
946
947 assert_eq!(initiator.state(), HolePunchState::Failed);
948 assert_eq!(actions.len(), 1);
949 assert!(
950 matches!(&actions[0], HolePunchAction::Failed { reason, .. } if *reason == REJECT_POLICY)
951 );
952 }
953
954 #[test]
955 fn test_discover_timeout() {
956 let link_id = [0x77; 16];
957 let derived_key = test_derived_key();
958 let mut rng = make_rng(0x42);
959
960 let mut engine =
961 HolePunchEngine::new(link_id, Some(test_probe_addr()), ProbeProtocol::Rnsp);
962 engine.propose(&derived_key, 100.0, &mut rng).unwrap();
963 assert_eq!(engine.state(), HolePunchState::Discovering);
964
965 let actions = engine.tick(100.0 + DISCOVER_TIMEOUT - 1.0);
967 assert!(actions.is_empty());
968
969 let actions = engine.tick(100.0 + DISCOVER_TIMEOUT + 1.0);
971 assert_eq!(engine.state(), HolePunchState::Failed);
972 assert!(
973 matches!(&actions[0], HolePunchAction::Failed { reason, .. } if *reason == FAIL_PROBE)
974 );
975 }
976
977 #[test]
978 fn test_propose_timeout() {
979 let link_id = [0x88; 16];
980 let derived_key = test_derived_key();
981 let mut rng = make_rng(0x42);
982
983 let mut engine =
984 HolePunchEngine::new(link_id, Some(test_probe_addr()), ProbeProtocol::Rnsp);
985 engine.propose(&derived_key, 100.0, &mut rng).unwrap();
986 engine
987 .endpoints_discovered(test_public_addr_a(), 101.0)
988 .unwrap();
989 assert_eq!(engine.state(), HolePunchState::Proposing);
990
991 let actions = engine.tick(101.0 + PROPOSE_TIMEOUT + 1.0);
993 assert_eq!(engine.state(), HolePunchState::Failed);
994 assert!(
995 matches!(&actions[0], HolePunchAction::Failed { reason, .. } if *reason == FAIL_TIMEOUT)
996 );
997 }
998
999 #[test]
1000 fn test_waiting_ready_timeout() {
1001 let link_id = [0x99; 16];
1002 let derived_key = test_derived_key();
1003 let mut rng = make_rng(0x42);
1004
1005 let mut engine =
1006 HolePunchEngine::new(link_id, Some(test_probe_addr()), ProbeProtocol::Rnsp);
1007 engine.propose(&derived_key, 200.0, &mut rng).unwrap();
1008 engine
1009 .endpoints_discovered(test_public_addr_a(), 201.0)
1010 .unwrap();
1011 engine.state = HolePunchState::WaitingReady;
1012 engine.state_entered_at = 202.0;
1013
1014 let actions = engine.tick(202.0 + READY_TIMEOUT + 1.0);
1016 assert_eq!(engine.state(), HolePunchState::Failed);
1017 assert!(
1018 matches!(&actions[0], HolePunchAction::Failed { reason, .. } if *reason == FAIL_TIMEOUT)
1019 );
1020 }
1021
1022 #[test]
1023 fn test_punch_timeout() {
1024 let link_id = [0xAA; 16];
1025 let derived_key = test_derived_key();
1026 let mut rng = make_rng(0x42);
1027
1028 let mut engine =
1029 HolePunchEngine::new(link_id, Some(test_probe_addr()), ProbeProtocol::Rnsp);
1030 engine.propose(&derived_key, 100.0, &mut rng).unwrap();
1031 engine.state = HolePunchState::Punching;
1032 engine.state_entered_at = 200.0;
1033
1034 let actions = engine.tick(200.0 + PUNCH_TIMEOUT - 1.0);
1036 assert!(actions.is_empty());
1037
1038 let _actions = engine.tick(200.0 + PUNCH_TIMEOUT + 1.0);
1040 assert_eq!(engine.state(), HolePunchState::Failed);
1041 }
1042
1043 #[test]
1044 fn test_message_serialization_roundtrip() {
1045 let session_id = [0xAB; 16];
1046
1047 let facilitator = test_probe_addr();
1049 let init_pub = test_public_addr_a();
1050 let data =
1051 encode_upgrade_request(&session_id, &facilitator, &init_pub, ProbeProtocol::Rnsp);
1052 let (sid, f, a, proto) = decode_upgrade_request(&data).unwrap();
1053 assert_eq!(sid, session_id);
1054 assert_eq!(f, facilitator);
1055 assert_eq!(a, init_pub);
1056 assert_eq!(proto, ProbeProtocol::Rnsp);
1057
1058 let data = encode_upgrade_accept(&session_id);
1060 let sid = decode_upgrade_accept(&data).unwrap();
1061 assert_eq!(sid, session_id);
1062
1063 let data = encode_upgrade_reject(&session_id, REJECT_POLICY);
1065 let (sid, r) = decode_upgrade_reject(&data).unwrap();
1066 assert_eq!(sid, session_id);
1067 assert_eq!(r, REJECT_POLICY);
1068
1069 let resp_pub = test_public_addr_b();
1071 let data = encode_upgrade_ready(&session_id, &resp_pub);
1072 let (sid, rp) = decode_upgrade_ready(&data).unwrap();
1073 assert_eq!(sid, session_id);
1074 assert_eq!(rp, resp_pub);
1075
1076 let data = encode_session_only(&session_id);
1078 let sid = decode_session_only(&data).unwrap();
1079 assert_eq!(sid, session_id);
1080 }
1081
1082 #[test]
1083 fn test_punch_token_derivation_consistency() {
1084 let derived_key = vec![0xBB; 32];
1085 let session_id = [0xCC; 16];
1086
1087 let token1 = derive_punch_token(&derived_key, &session_id).unwrap();
1088 let token2 = derive_punch_token(&derived_key, &session_id).unwrap();
1089 assert_eq!(token1, token2);
1090
1091 let session_id2 = [0xDD; 16];
1093 let token3 = derive_punch_token(&derived_key, &session_id2).unwrap();
1094 assert_ne!(token1, token3);
1095 }
1096
1097 #[test]
1098 fn test_reset() {
1099 let link_id = [0xBB; 16];
1100 let derived_key = test_derived_key();
1101 let mut rng = make_rng(0x42);
1102
1103 let mut engine =
1104 HolePunchEngine::new(link_id, Some(test_probe_addr()), ProbeProtocol::Rnsp);
1105 engine.propose(&derived_key, 100.0, &mut rng).unwrap();
1106 assert_eq!(engine.state(), HolePunchState::Discovering);
1107
1108 engine.reset();
1109 assert_eq!(engine.state(), HolePunchState::Idle);
1110 assert_eq!(engine.session_id(), &[0u8; 16]);
1111 }
1112
1113 #[test]
1114 fn test_build_reject_static() {
1115 let link_id = [0xCC; 16];
1116 let derived_key = test_derived_key();
1117 let mut rng = make_rng(0x42);
1118
1119 let mut proposer =
1120 HolePunchEngine::new(link_id, Some(test_probe_addr()), ProbeProtocol::Rnsp);
1121 proposer.propose(&derived_key, 100.0, &mut rng).unwrap();
1122 let actions = proposer
1123 .endpoints_discovered(test_public_addr_a(), 101.0)
1124 .unwrap();
1125 let request_payload = match &actions[0] {
1126 HolePunchAction::SendSignal { payload, .. } => payload.clone(),
1127 _ => panic!(),
1128 };
1129
1130 let action =
1131 HolePunchEngine::build_reject(link_id, &request_payload, REJECT_POLICY).unwrap();
1132 match action {
1133 HolePunchAction::SendSignal { msgtype, .. } => {
1134 assert_eq!(msgtype, UPGRADE_REJECT);
1135 }
1136 _ => panic!("Expected SendSignal(UPGRADE_REJECT)"),
1137 }
1138 }
1139
1140 #[test]
1141 fn test_responder_needs_no_probe_addr() {
1142 let link_id = [0xDD; 16];
1144 let derived_key = test_derived_key();
1145 let mut rng = make_rng(0x42);
1146
1147 let mut initiator =
1149 HolePunchEngine::new(link_id, Some(test_probe_addr()), ProbeProtocol::Rnsp);
1150 initiator.propose(&derived_key, 100.0, &mut rng).unwrap();
1151 let actions = initiator
1152 .endpoints_discovered(test_public_addr_a(), 101.0)
1153 .unwrap();
1154 let request_payload = match &actions[0] {
1155 HolePunchAction::SendSignal { payload, .. } => payload.clone(),
1156 _ => panic!(),
1157 };
1158
1159 let mut responder = HolePunchEngine::new(link_id, None, ProbeProtocol::Rnsp);
1161 let actions = responder
1162 .handle_signal(UPGRADE_REQUEST, &request_payload, Some(&derived_key), 102.0)
1163 .unwrap();
1164
1165 assert_eq!(responder.state(), HolePunchState::Discovering);
1167 assert_eq!(actions.len(), 2);
1168 assert!(
1169 matches!(&actions[0], HolePunchAction::SendSignal { msgtype, .. } if *msgtype == UPGRADE_ACCEPT)
1170 );
1171 assert!(matches!(
1172 &actions[1],
1173 HolePunchAction::DiscoverEndpoints { .. }
1174 ));
1175 }
1176
1177 #[test]
1178 fn test_stun_protocol_in_upgrade_request_roundtrip() {
1179 let session_id = [0xAB; 16];
1180 let facilitator = test_probe_addr();
1181 let init_pub = test_public_addr_a();
1182
1183 let data =
1185 encode_upgrade_request(&session_id, &facilitator, &init_pub, ProbeProtocol::Stun);
1186 let (sid, f, a, proto) = decode_upgrade_request(&data).unwrap();
1187 assert_eq!(sid, session_id);
1188 assert_eq!(f, facilitator);
1189 assert_eq!(a, init_pub);
1190 assert_eq!(proto, ProbeProtocol::Stun);
1191 }
1192
1193 #[test]
1194 fn test_rnsp_protocol_omits_p_field() {
1195 let session_id = [0xAB; 16];
1196 let facilitator = test_probe_addr();
1197 let init_pub = test_public_addr_a();
1198
1199 let data =
1201 encode_upgrade_request(&session_id, &facilitator, &init_pub, ProbeProtocol::Rnsp);
1202 let (sid, f, a, proto) = decode_upgrade_request(&data).unwrap();
1203 assert_eq!(sid, session_id);
1204 assert_eq!(f, facilitator);
1205 assert_eq!(a, init_pub);
1206 assert_eq!(proto, ProbeProtocol::Rnsp);
1207 }
1208
1209 #[test]
1210 fn test_backward_compat_decode_without_p_field() {
1211 let session_id = [0xAB; 16];
1213 let facilitator = test_probe_addr();
1214 let init_pub = test_public_addr_a();
1215
1216 let val = Value::Map(alloc::vec![
1218 (
1219 Value::Str(alloc::string::String::from("s")),
1220 Value::Bin(session_id.to_vec())
1221 ),
1222 (
1223 Value::Str(alloc::string::String::from("f")),
1224 encode_endpoint(&facilitator)
1225 ),
1226 (
1227 Value::Str(alloc::string::String::from("a")),
1228 encode_endpoint(&init_pub)
1229 ),
1230 ]);
1231 let data = msgpack::pack(&val);
1232
1233 let (sid, f, a, proto) = decode_upgrade_request(&data).unwrap();
1234 assert_eq!(sid, session_id);
1235 assert_eq!(f, facilitator);
1236 assert_eq!(a, init_pub);
1237 assert_eq!(proto, ProbeProtocol::Rnsp); }
1239
1240 #[test]
1241 fn test_stun_initiator_responder_gets_stun_protocol() {
1242 let link_id = [0xEE; 16];
1243 let derived_key = test_derived_key();
1244 let mut rng = make_rng(0x42);
1245
1246 let mut initiator =
1248 HolePunchEngine::new(link_id, Some(test_probe_addr()), ProbeProtocol::Stun);
1249 let actions = initiator.propose(&derived_key, 100.0, &mut rng).unwrap();
1250
1251 match &actions[0] {
1253 HolePunchAction::DiscoverEndpoints { protocol, .. } => {
1254 assert_eq!(*protocol, ProbeProtocol::Stun);
1255 }
1256 _ => panic!("Expected DiscoverEndpoints"),
1257 }
1258
1259 let actions = initiator
1260 .endpoints_discovered(test_public_addr_a(), 101.0)
1261 .unwrap();
1262 let request_payload = match &actions[0] {
1263 HolePunchAction::SendSignal { payload, .. } => payload.clone(),
1264 _ => panic!(),
1265 };
1266
1267 let mut responder = HolePunchEngine::new(link_id, None, ProbeProtocol::Rnsp);
1269 let actions = responder
1270 .handle_signal(UPGRADE_REQUEST, &request_payload, Some(&derived_key), 102.0)
1271 .unwrap();
1272
1273 match &actions[1] {
1275 HolePunchAction::DiscoverEndpoints { protocol, .. } => {
1276 assert_eq!(*protocol, ProbeProtocol::Stun);
1277 }
1278 _ => panic!("Expected DiscoverEndpoints"),
1279 }
1280 }
1281}