1#[inline]
25pub fn xorshift64(state: &mut u64) -> u64 {
26 let mut x = *state;
27 x ^= x << 13;
28 x ^= x >> 7;
29 x ^= x << 17;
30 *state = x;
31 x
32}
33
34#[inline]
36pub fn fnv1a_64(data: &[u8]) -> u64 {
37 let mut h: u64 = 14_695_981_039_346_656_037;
38 for &b in data {
39 h ^= b as u64;
40 h = h.wrapping_mul(1_099_511_628_211);
41 }
42 h
43}
44
45#[derive(Debug, Clone, PartialEq, Eq)]
51pub enum NatType {
52 OpenInternet,
54 FullCone,
56 RestrictedCone,
58 PortRestrictedCone,
60 Symmetric,
62 Unknown,
64}
65
66#[derive(Debug, Clone, PartialEq, Eq, Hash)]
72pub enum CandidateType {
73 Host,
75 ServerReflexive,
77 PeerReflexive,
79 Relayed,
81}
82
83#[derive(Debug, Clone, PartialEq, Eq)]
85pub struct CandidateAddress {
86 pub address: String,
88 pub port: u16,
90 pub candidate_type: CandidateType,
92 pub priority: u32,
94 pub foundation: String,
96}
97
98impl CandidateAddress {
99 pub fn new(
101 address: impl Into<String>,
102 port: u16,
103 candidate_type: CandidateType,
104 priority: u32,
105 ) -> Self {
106 let address = address.into();
107 let raw = format!("{address}:{port}");
108 let foundation = format!("{:016x}", fnv1a_64(raw.as_bytes()));
109 Self {
110 address,
111 port,
112 candidate_type,
113 priority,
114 foundation,
115 }
116 }
117
118 pub fn key(&self) -> String {
120 format!("{}:{}", self.address, self.port)
121 }
122}
123
124#[derive(Debug, Clone, PartialEq, Eq)]
130pub enum PairState {
131 Waiting,
133 InProgress,
135 Succeeded,
137 Failed,
139 Frozen,
141}
142
143#[derive(Debug, Clone)]
145pub struct IcePair {
146 pub local: CandidateAddress,
148 pub remote: CandidateAddress,
150 pub state: PairState,
152 pub priority: u64,
155 pub nominated: bool,
157}
158
159impl IcePair {
160 pub fn new(local: CandidateAddress, remote: CandidateAddress) -> Self {
162 let g = local.priority as u64;
163 let d = remote.priority as u64;
164 let priority = (1u64 << 32) * g.min(d) + 2 * g.max(d) + if g > d { 1 } else { 0 };
165 Self {
166 local,
167 remote,
168 state: PairState::Waiting,
169 priority,
170 nominated: false,
171 }
172 }
173
174 pub fn key(&self) -> String {
176 format!("{} -> {}", self.local.key(), self.remote.key())
177 }
178}
179
180#[derive(Debug, Clone, PartialEq, Eq)]
186pub enum StunMessageType {
187 BindingRequest,
189 BindingResponse,
191 BindingError,
193 AllocateRequest,
195 AllocateResponse,
197}
198
199impl StunMessageType {
200 fn to_u16(&self) -> u16 {
201 match self {
202 Self::BindingRequest => 0x0001,
203 Self::BindingResponse => 0x0101,
204 Self::BindingError => 0x0111,
205 Self::AllocateRequest => 0x0003,
206 Self::AllocateResponse => 0x0103,
207 }
208 }
209
210 fn from_u16(v: u16) -> Option<Self> {
211 match v {
212 0x0001 => Some(Self::BindingRequest),
213 0x0101 => Some(Self::BindingResponse),
214 0x0111 => Some(Self::BindingError),
215 0x0003 => Some(Self::AllocateRequest),
216 0x0103 => Some(Self::AllocateResponse),
217 _ => None,
218 }
219 }
220}
221
222#[derive(Debug, Clone, PartialEq, Eq)]
224pub enum StunAttribute {
225 MappedAddress(String, u16),
227 XorMappedAddress(String, u16),
229 Username(String),
231 Realm(String),
233 ErrorCode(u16, String),
235 Fingerprint(u32),
237 Lifetime(u32),
239}
240
241#[derive(Debug, Clone)]
243pub struct StunMessage {
244 pub msg_type: StunMessageType,
246 pub transaction_id: [u8; 12],
248 pub attributes: Vec<StunAttribute>,
250}
251
252impl StunMessage {
253 pub fn new(msg_type: StunMessageType, seed: u64) -> Self {
256 let mut state = if seed == 0 { 0xdeadbeef_cafebabe } else { seed };
257 let mut tx = [0u8; 12];
258 let a = xorshift64(&mut state).to_le_bytes();
259 let b = xorshift64(&mut state).to_le_bytes();
260 tx[..8].copy_from_slice(&a);
261 tx[8..12].copy_from_slice(&b[..4]);
262 Self {
263 msg_type,
264 transaction_id: tx,
265 attributes: Vec::new(),
266 }
267 }
268}
269
270#[derive(Debug, Clone)]
276pub struct TraversalConfig {
277 pub stun_servers: Vec<String>,
279 pub turn_servers: Vec<String>,
281 pub check_interval_us: u64,
283 pub nomination_timeout_us: u64,
285 pub max_pairs: usize,
287}
288
289impl Default for TraversalConfig {
290 fn default() -> Self {
291 Self {
292 stun_servers: vec![
293 "stun.l.google.com:19302".to_string(),
294 "stun1.l.google.com:19302".to_string(),
295 ],
296 turn_servers: Vec::new(),
297 check_interval_us: 20_000, nomination_timeout_us: 500_000, max_pairs: 100,
300 }
301 }
302}
303
304#[derive(Debug, Clone, Default)]
306pub struct TraversalStats {
307 pub candidates_gathered: usize,
309 pub pairs_checked: usize,
311 pub pairs_succeeded: usize,
313 pub pairs_failed: usize,
315 pub nominated_pair: Option<String>,
317 pub nat_type: String,
319}
320
321#[derive(Debug, Clone, PartialEq, Eq)]
327pub enum TraversalError {
328 NoValidPair,
330 CandidateGatheringFailed(String),
332 StunError(String),
334 TurnAllocationFailed(String),
336 ChecklistExhausted,
338}
339
340impl std::fmt::Display for TraversalError {
341 fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
342 match self {
343 Self::NoValidPair => write!(f, "no valid ICE pair found"),
344 Self::CandidateGatheringFailed(msg) => {
345 write!(f, "candidate gathering failed: {msg}")
346 }
347 Self::StunError(msg) => write!(f, "STUN error: {msg}"),
348 Self::TurnAllocationFailed(msg) => {
349 write!(f, "TURN allocation failed: {msg}")
350 }
351 Self::ChecklistExhausted => write!(f, "check list exhausted without success"),
352 }
353 }
354}
355
356impl std::error::Error for TraversalError {}
357
358const STUN_MAGIC: u32 = 0x2112A442;
364
365const ATTR_MAPPED_ADDRESS: u16 = 0x0001;
367const ATTR_USERNAME: u16 = 0x0006;
368const ATTR_ERROR_CODE: u16 = 0x0009;
369const ATTR_LIFETIME: u16 = 0x000D;
370const ATTR_REALM: u16 = 0x0014;
371const ATTR_XOR_MAPPED_ADDRESS: u16 = 0x0020;
372const ATTR_FINGERPRINT: u16 = 0x8028;
373
374fn encode_string_attr(buf: &mut Vec<u8>, attr_type: u16, s: &str) {
380 let bytes = s.as_bytes();
381 let len = bytes.len() as u16;
382 buf.extend_from_slice(&attr_type.to_be_bytes());
383 buf.extend_from_slice(&len.to_be_bytes());
384 buf.extend_from_slice(bytes);
385 let pad = (4 - (bytes.len() % 4)) % 4;
387 buf.extend(std::iter::repeat_n(0u8, pad));
388}
389
390fn encode_address_attr(buf: &mut Vec<u8>, attr_type: u16, addr: &str, port: u16, xor: bool) {
393 let octets: [u8; 4] = parse_ipv4(addr).unwrap_or([0, 0, 0, 0]);
395 let (enc_port, enc_octets) = if xor {
396 let xp = port ^ ((STUN_MAGIC >> 16) as u16);
397 let magic_bytes = STUN_MAGIC.to_be_bytes();
398 let xo = [
399 octets[0] ^ magic_bytes[0],
400 octets[1] ^ magic_bytes[1],
401 octets[2] ^ magic_bytes[2],
402 octets[3] ^ magic_bytes[3],
403 ];
404 (xp, xo)
405 } else {
406 (port, octets)
407 };
408 buf.extend_from_slice(&attr_type.to_be_bytes());
410 buf.extend_from_slice(&8u16.to_be_bytes()); buf.push(0x00); buf.push(0x01); buf.extend_from_slice(&enc_port.to_be_bytes());
414 buf.extend_from_slice(&enc_octets);
415}
416
417fn parse_ipv4(addr: &str) -> Option<[u8; 4]> {
419 let parts: Vec<&str> = addr.split('.').collect();
420 if parts.len() != 4 {
421 return None;
422 }
423 let a: u8 = parts[0].parse().ok()?;
424 let b: u8 = parts[1].parse().ok()?;
425 let c: u8 = parts[2].parse().ok()?;
426 let d: u8 = parts[3].parse().ok()?;
427 Some([a, b, c, d])
428}
429
430fn format_ipv4(octets: [u8; 4]) -> String {
432 format!("{}.{}.{}.{}", octets[0], octets[1], octets[2], octets[3])
433}
434
435pub struct NatTraversalManager {
444 config: TraversalConfig,
445 local_candidates: Vec<CandidateAddress>,
446 remote_candidates: Vec<CandidateAddress>,
447 check_pairs: Vec<IcePair>,
448 stats: TraversalStats,
449}
450
451impl NatTraversalManager {
452 pub fn new(config: TraversalConfig) -> Self {
454 Self {
455 config,
456 local_candidates: Vec::new(),
457 remote_candidates: Vec::new(),
458 check_pairs: Vec::new(),
459 stats: TraversalStats::default(),
460 }
461 }
462
463 pub fn add_local_candidate(&mut self, addr: CandidateAddress) -> Result<(), TraversalError> {
467 if addr.address.is_empty() {
468 return Err(TraversalError::CandidateGatheringFailed(
469 "empty address".to_string(),
470 ));
471 }
472 self.local_candidates.push(addr);
473 self.stats.candidates_gathered = self.local_candidates.len() + self.remote_candidates.len();
474 Ok(())
475 }
476
477 pub fn add_remote_candidate(&mut self, addr: CandidateAddress) -> Result<(), TraversalError> {
479 if addr.address.is_empty() {
480 return Err(TraversalError::CandidateGatheringFailed(
481 "empty address".to_string(),
482 ));
483 }
484 self.remote_candidates.push(addr);
485 self.stats.candidates_gathered = self.local_candidates.len() + self.remote_candidates.len();
486 Ok(())
487 }
488
489 pub fn form_check_pairs(&mut self) -> Vec<IcePair> {
495 let mut pairs: Vec<IcePair> = self
496 .local_candidates
497 .iter()
498 .flat_map(|l| {
499 self.remote_candidates
500 .iter()
501 .map(move |r| IcePair::new(l.clone(), r.clone()))
502 })
503 .collect();
504
505 pairs.sort_unstable_by_key(|p| std::cmp::Reverse(p.priority));
507 pairs.truncate(self.config.max_pairs);
508 self.check_pairs = pairs.clone();
509 pairs
510 }
511
512 pub fn detect_nat_type(candidates: &[CandidateAddress]) -> NatType {
527 if candidates.is_empty() {
528 return NatType::Unknown;
529 }
530
531 let has_host = candidates
532 .iter()
533 .any(|c| c.candidate_type == CandidateType::Host);
534 let has_relayed = candidates
535 .iter()
536 .any(|c| c.candidate_type == CandidateType::Relayed);
537 let reflexive: Vec<&CandidateAddress> = candidates
538 .iter()
539 .filter(|c| c.candidate_type == CandidateType::ServerReflexive)
540 .collect();
541
542 if !has_host && reflexive.is_empty() && !has_relayed {
543 return NatType::Unknown;
544 }
545
546 if has_host && reflexive.is_empty() && !has_relayed {
547 return NatType::OpenInternet;
548 }
549
550 if has_relayed && reflexive.is_empty() {
551 return NatType::PortRestrictedCone;
552 }
553
554 if reflexive.len() > 1 {
557 let first = reflexive[0];
558 let symmetric = reflexive
559 .iter()
560 .any(|c| c.address != first.address || c.port != first.port);
561 if symmetric {
562 return NatType::Symmetric;
563 }
564 }
565
566 if !reflexive.is_empty() && !has_relayed {
569 return NatType::FullCone;
570 }
571
572 if !reflexive.is_empty() && has_relayed {
573 return NatType::RestrictedCone;
574 }
575
576 NatType::Unknown
577 }
578
579 pub fn check_pair(&self, pair: &mut IcePair, _current_ts: u64) -> PairState {
590 pair.state = PairState::InProgress;
592
593 let both_reachable = matches!(
594 (&pair.local.candidate_type, &pair.remote.candidate_type),
595 (CandidateType::Host, CandidateType::Host)
596 | (CandidateType::Host, CandidateType::ServerReflexive)
597 | (CandidateType::Host, CandidateType::PeerReflexive)
598 | (CandidateType::Host, CandidateType::Relayed)
599 | (CandidateType::ServerReflexive, CandidateType::Host)
600 | (
601 CandidateType::ServerReflexive,
602 CandidateType::ServerReflexive
603 )
604 | (CandidateType::ServerReflexive, CandidateType::PeerReflexive)
605 | (CandidateType::ServerReflexive, CandidateType::Relayed)
606 | (CandidateType::PeerReflexive, CandidateType::Host)
607 | (CandidateType::PeerReflexive, CandidateType::ServerReflexive)
608 | (CandidateType::PeerReflexive, CandidateType::PeerReflexive)
609 | (CandidateType::PeerReflexive, CandidateType::Relayed)
610 | (CandidateType::Relayed, CandidateType::Host)
611 | (CandidateType::Relayed, CandidateType::ServerReflexive)
612 | (CandidateType::Relayed, CandidateType::PeerReflexive)
613 | (CandidateType::Relayed, CandidateType::Relayed)
614 );
615
616 if both_reachable {
617 pair.state = PairState::Succeeded;
618 } else {
619 pair.state = PairState::Failed;
620 }
621 pair.state.clone()
622 }
623
624 pub fn nominate_best_pair(&mut self) -> Result<IcePair, TraversalError> {
629 let best = self
630 .check_pairs
631 .iter_mut()
632 .filter(|p| p.state == PairState::Succeeded)
633 .max_by_key(|p| p.priority);
634
635 match best {
636 Some(pair) => {
637 pair.nominated = true;
638 self.stats.nominated_pair = Some(pair.key());
639 Ok(pair.clone())
640 }
641 None => Err(TraversalError::NoValidPair),
642 }
643 }
644
645 pub fn run_checks(&mut self, current_ts: u64) -> Vec<(String, PairState)> {
650 let mut results = Vec::new();
651 for pair in &mut self.check_pairs {
652 if matches!(pair.state, PairState::Waiting | PairState::InProgress) {
653 let key = pair.key();
656 let new_state = {
657 pair.state = PairState::InProgress;
658 let both_reachable = matches!(
659 (&pair.local.candidate_type, &pair.remote.candidate_type),
660 (CandidateType::Host, _)
661 | (CandidateType::ServerReflexive, _)
662 | (CandidateType::PeerReflexive, _)
663 | (CandidateType::Relayed, _)
664 );
665 if both_reachable {
666 pair.state = PairState::Succeeded;
667 } else {
668 pair.state = PairState::Failed;
669 }
670 pair.state.clone()
671 };
672 self.stats.pairs_checked += 1;
673 match &new_state {
674 PairState::Succeeded => self.stats.pairs_succeeded += 1,
675 PairState::Failed => self.stats.pairs_failed += 1,
676 _ => {}
677 }
678 results.push((key, new_state));
679 let _ = current_ts; }
681 }
682 results
683 }
684
685 pub fn encode_stun_message(&self, msg: &StunMessage) -> Vec<u8> {
705 let mut attr_buf: Vec<u8> = Vec::new();
707 for attr in &msg.attributes {
708 self.encode_attribute(&mut attr_buf, attr);
709 }
710
711 let mut out = Vec::with_capacity(20 + attr_buf.len());
712 out.extend_from_slice(&msg.msg_type.to_u16().to_be_bytes());
713 out.extend_from_slice(&(attr_buf.len() as u16).to_be_bytes());
714 out.extend_from_slice(&STUN_MAGIC.to_be_bytes());
715 out.extend_from_slice(&msg.transaction_id);
716 out.extend_from_slice(&attr_buf);
717 out
718 }
719
720 pub fn decode_stun_message(&self, data: &[u8]) -> Result<StunMessage, TraversalError> {
722 if data.len() < 20 {
723 return Err(TraversalError::StunError(format!(
724 "message too short: {} bytes (need 20)",
725 data.len()
726 )));
727 }
728
729 let msg_type_raw = u16::from_be_bytes([data[0], data[1]]);
730 let msg_len = u16::from_be_bytes([data[2], data[3]]) as usize;
731 let magic = u32::from_be_bytes([data[4], data[5], data[6], data[7]]);
732 if magic != STUN_MAGIC {
733 return Err(TraversalError::StunError(format!(
734 "invalid magic cookie: {magic:#010x}"
735 )));
736 }
737 let transaction_id: [u8; 12] = data[8..20]
738 .try_into()
739 .map_err(|_| TraversalError::StunError("failed to read transaction ID".to_string()))?;
740
741 let msg_type = StunMessageType::from_u16(msg_type_raw).ok_or_else(|| {
742 TraversalError::StunError(format!("unknown message type: {msg_type_raw:#06x}"))
743 })?;
744
745 if data.len() < 20 + msg_len {
746 return Err(TraversalError::StunError(format!(
747 "truncated message: declared {} attribute bytes but only {} available",
748 msg_len,
749 data.len() - 20
750 )));
751 }
752
753 let attr_data = &data[20..20 + msg_len];
754 let attributes = self.decode_attributes(attr_data)?;
755
756 Ok(StunMessage {
757 msg_type,
758 transaction_id,
759 attributes,
760 })
761 }
762
763 pub fn stats(&self) -> TraversalStats {
767 let mut s = self.stats.clone();
768 let nat = Self::detect_nat_type(&self.local_candidates);
770 s.nat_type = format!("{nat:?}");
771 s.candidates_gathered = self.local_candidates.len() + self.remote_candidates.len();
772 s
773 }
774
775 fn encode_attribute(&self, buf: &mut Vec<u8>, attr: &StunAttribute) {
779 match attr {
780 StunAttribute::MappedAddress(addr, port) => {
781 encode_address_attr(buf, ATTR_MAPPED_ADDRESS, addr, *port, false);
782 }
783 StunAttribute::XorMappedAddress(addr, port) => {
784 encode_address_attr(buf, ATTR_XOR_MAPPED_ADDRESS, addr, *port, true);
785 }
786 StunAttribute::Username(name) => {
787 encode_string_attr(buf, ATTR_USERNAME, name);
788 }
789 StunAttribute::Realm(realm) => {
790 encode_string_attr(buf, ATTR_REALM, realm);
791 }
792 StunAttribute::ErrorCode(code, reason) => {
793 let class = (*code / 100) as u8;
794 let number = (*code % 100) as u8;
795 let reason_bytes = reason.as_bytes();
796 let value_len = 4 + reason_bytes.len();
797 buf.extend_from_slice(&ATTR_ERROR_CODE.to_be_bytes());
798 buf.extend_from_slice(&(value_len as u16).to_be_bytes());
799 buf.extend_from_slice(&[0x00, 0x00, class, number]);
800 buf.extend_from_slice(reason_bytes);
801 let pad = (4 - (reason_bytes.len() % 4)) % 4;
802 buf.extend(std::iter::repeat_n(0u8, pad));
803 }
804 StunAttribute::Fingerprint(crc) => {
805 buf.extend_from_slice(&ATTR_FINGERPRINT.to_be_bytes());
806 buf.extend_from_slice(&4u16.to_be_bytes());
807 buf.extend_from_slice(&crc.to_be_bytes());
808 }
809 StunAttribute::Lifetime(secs) => {
810 buf.extend_from_slice(&ATTR_LIFETIME.to_be_bytes());
811 buf.extend_from_slice(&4u16.to_be_bytes());
812 buf.extend_from_slice(&secs.to_be_bytes());
813 }
814 }
815 }
816
817 fn decode_attributes(&self, data: &[u8]) -> Result<Vec<StunAttribute>, TraversalError> {
819 let mut attrs = Vec::new();
820 let mut pos = 0usize;
821
822 while pos < data.len() {
823 if pos + 4 > data.len() {
824 return Err(TraversalError::StunError(
825 "truncated attribute header".to_string(),
826 ));
827 }
828 let attr_type = u16::from_be_bytes([data[pos], data[pos + 1]]);
829 let attr_len = u16::from_be_bytes([data[pos + 2], data[pos + 3]]) as usize;
830 pos += 4;
831
832 if pos + attr_len > data.len() {
833 return Err(TraversalError::StunError(format!(
834 "attribute (type={attr_type:#06x}) value truncated: need {attr_len} bytes"
835 )));
836 }
837 let value = &data[pos..pos + attr_len];
838
839 match attr_type {
840 ATTR_MAPPED_ADDRESS => {
841 let (addr, port) = decode_address_value(value, false)?;
842 attrs.push(StunAttribute::MappedAddress(addr, port));
843 }
844 ATTR_XOR_MAPPED_ADDRESS => {
845 let (addr, port) = decode_address_value(value, true)?;
846 attrs.push(StunAttribute::XorMappedAddress(addr, port));
847 }
848 ATTR_USERNAME => {
849 let s = std::str::from_utf8(value)
850 .map_err(|e| TraversalError::StunError(format!("USERNAME utf8: {e}")))?;
851 attrs.push(StunAttribute::Username(s.to_string()));
852 }
853 ATTR_REALM => {
854 let s = std::str::from_utf8(value)
855 .map_err(|e| TraversalError::StunError(format!("REALM utf8: {e}")))?;
856 attrs.push(StunAttribute::Realm(s.to_string()));
857 }
858 ATTR_ERROR_CODE => {
859 if value.len() < 4 {
860 return Err(TraversalError::StunError(
861 "ERROR-CODE too short".to_string(),
862 ));
863 }
864 let class = value[2] as u16;
865 let number = value[3] as u16;
866 let code = class * 100 + number;
867 let reason = std::str::from_utf8(&value[4..]).map_err(|e| {
868 TraversalError::StunError(format!("ERROR-CODE reason utf8: {e}"))
869 })?;
870 attrs.push(StunAttribute::ErrorCode(code, reason.to_string()));
871 }
872 ATTR_FINGERPRINT => {
873 if value.len() < 4 {
874 return Err(TraversalError::StunError(
875 "FINGERPRINT too short".to_string(),
876 ));
877 }
878 let crc = u32::from_be_bytes([value[0], value[1], value[2], value[3]]);
879 attrs.push(StunAttribute::Fingerprint(crc));
880 }
881 ATTR_LIFETIME => {
882 if value.len() < 4 {
883 return Err(TraversalError::StunError("LIFETIME too short".to_string()));
884 }
885 let secs = u32::from_be_bytes([value[0], value[1], value[2], value[3]]);
886 attrs.push(StunAttribute::Lifetime(secs));
887 }
888 _ => {
889 }
891 }
892
893 let padded = attr_len + (4 - attr_len % 4) % 4;
895 pos += padded;
896 }
897 Ok(attrs)
898 }
899}
900
901fn decode_address_value(value: &[u8], xor: bool) -> Result<(String, u16), TraversalError> {
903 if value.len() < 8 {
904 return Err(TraversalError::StunError(
905 "address attribute too short".to_string(),
906 ));
907 }
908 let family = value[1];
910 if family != 0x01 {
911 return Err(TraversalError::StunError(format!(
912 "only IPv4 is supported (family={family:#04x})"
913 )));
914 }
915 let raw_port = u16::from_be_bytes([value[2], value[3]]);
916 let raw_octets: [u8; 4] = value[4..8]
917 .try_into()
918 .map_err(|_| TraversalError::StunError("address octet slice error".to_string()))?;
919
920 let (port, octets) = if xor {
921 let dp = raw_port ^ ((STUN_MAGIC >> 16) as u16);
922 let magic_bytes = STUN_MAGIC.to_be_bytes();
923 let do_ = [
924 raw_octets[0] ^ magic_bytes[0],
925 raw_octets[1] ^ magic_bytes[1],
926 raw_octets[2] ^ magic_bytes[2],
927 raw_octets[3] ^ magic_bytes[3],
928 ];
929 (dp, do_)
930 } else {
931 (raw_port, raw_octets)
932 };
933
934 Ok((format_ipv4(octets), port))
935}
936
937impl Default for NatTraversalManager {
938 fn default() -> Self {
939 Self::new(TraversalConfig::default())
940 }
941}
942
943pub type NtmNatType = NatType;
950
951pub type NtmNatTraversalManager = NatTraversalManager;
954
955#[cfg(test)]
960mod tests {
961 use super::*;
962
963 fn host(ip: &str, port: u16, prio: u32) -> CandidateAddress {
966 CandidateAddress::new(ip, port, CandidateType::Host, prio)
967 }
968
969 fn srflx(ip: &str, port: u16, prio: u32) -> CandidateAddress {
970 CandidateAddress::new(ip, port, CandidateType::ServerReflexive, prio)
971 }
972
973 fn relay(ip: &str, port: u16, prio: u32) -> CandidateAddress {
974 CandidateAddress::new(ip, port, CandidateType::Relayed, prio)
975 }
976
977 fn prflx(ip: &str, port: u16, prio: u32) -> CandidateAddress {
978 CandidateAddress::new(ip, port, CandidateType::PeerReflexive, prio)
979 }
980
981 fn default_manager() -> NatTraversalManager {
982 NatTraversalManager::new(TraversalConfig::default())
983 }
984
985 #[test]
988 fn test_candidate_address_new_foundation() {
989 let c = host("1.2.3.4", 5000, 100);
990 assert_eq!(c.address, "1.2.3.4");
991 assert_eq!(c.port, 5000);
992 assert_eq!(c.priority, 100);
993 assert!(!c.foundation.is_empty());
994 }
995
996 #[test]
997 fn test_candidate_foundation_deterministic() {
998 let c1 = host("1.2.3.4", 5000, 100);
999 let c2 = host("1.2.3.4", 5000, 200); assert_eq!(c1.foundation, c2.foundation);
1001 }
1002
1003 #[test]
1004 fn test_candidate_foundation_differs_with_different_addr() {
1005 let c1 = host("1.2.3.4", 5000, 100);
1006 let c2 = host("1.2.3.5", 5000, 100);
1007 assert_ne!(c1.foundation, c2.foundation);
1008 }
1009
1010 #[test]
1011 fn test_candidate_foundation_differs_with_different_port() {
1012 let c1 = host("1.2.3.4", 5000, 100);
1013 let c2 = host("1.2.3.4", 5001, 100);
1014 assert_ne!(c1.foundation, c2.foundation);
1015 }
1016
1017 #[test]
1018 fn test_candidate_key() {
1019 let c = host("10.0.0.1", 4321, 50);
1020 assert_eq!(c.key(), "10.0.0.1:4321");
1021 }
1022
1023 #[test]
1026 fn test_add_local_candidate_ok() {
1027 let mut mgr = default_manager();
1028 assert!(mgr.add_local_candidate(host("1.2.3.4", 5000, 100)).is_ok());
1029 }
1030
1031 #[test]
1032 fn test_add_remote_candidate_ok() {
1033 let mut mgr = default_manager();
1034 assert!(mgr
1035 .add_remote_candidate(srflx("5.6.7.8", 3478, 200))
1036 .is_ok());
1037 }
1038
1039 #[test]
1040 fn test_add_local_candidate_empty_address_err() {
1041 let mut mgr = default_manager();
1042 let bad = CandidateAddress {
1043 address: String::new(),
1044 port: 1234,
1045 candidate_type: CandidateType::Host,
1046 priority: 10,
1047 foundation: "abc".to_string(),
1048 };
1049 assert!(matches!(
1050 mgr.add_local_candidate(bad),
1051 Err(TraversalError::CandidateGatheringFailed(_))
1052 ));
1053 }
1054
1055 #[test]
1056 fn test_add_remote_candidate_empty_address_err() {
1057 let mut mgr = default_manager();
1058 let bad = CandidateAddress {
1059 address: String::new(),
1060 port: 1234,
1061 candidate_type: CandidateType::Host,
1062 priority: 10,
1063 foundation: "abc".to_string(),
1064 };
1065 assert!(matches!(
1066 mgr.add_remote_candidate(bad),
1067 Err(TraversalError::CandidateGatheringFailed(_))
1068 ));
1069 }
1070
1071 #[test]
1072 fn test_candidates_gathered_counter() {
1073 let mut mgr = default_manager();
1074 mgr.add_local_candidate(host("1.0.0.1", 1000, 10))
1075 .expect("test: add_local_candidate should succeed");
1076 mgr.add_local_candidate(host("1.0.0.2", 1001, 20))
1077 .expect("test: add_local_candidate should succeed");
1078 mgr.add_remote_candidate(srflx("2.0.0.1", 2000, 30))
1079 .expect("test: add_remote_candidate should succeed");
1080 assert_eq!(mgr.stats().candidates_gathered, 3);
1081 }
1082
1083 #[test]
1086 fn test_form_check_pairs_count() {
1087 let mut mgr = default_manager();
1088 mgr.add_local_candidate(host("1.0.0.1", 1000, 100))
1089 .expect("test: add_local_candidate should succeed");
1090 mgr.add_local_candidate(host("1.0.0.2", 1001, 90))
1091 .expect("test: add_local_candidate should succeed");
1092 mgr.add_remote_candidate(srflx("2.0.0.1", 2000, 80))
1093 .expect("test: add_remote_candidate should succeed");
1094 mgr.add_remote_candidate(srflx("2.0.0.2", 2001, 70))
1095 .expect("test: add_remote_candidate should succeed");
1096 let pairs = mgr.form_check_pairs();
1097 assert_eq!(pairs.len(), 4); }
1099
1100 #[test]
1101 fn test_form_check_pairs_sorted_descending() {
1102 let mut mgr = default_manager();
1103 mgr.add_local_candidate(host("1.0.0.1", 1000, 50))
1104 .expect("test: add_local_candidate should succeed");
1105 mgr.add_local_candidate(host("1.0.0.2", 1001, 200))
1106 .expect("test: add_local_candidate should succeed");
1107 mgr.add_remote_candidate(srflx("2.0.0.1", 2000, 150))
1108 .expect("test: add_remote_candidate should succeed");
1109 let pairs = mgr.form_check_pairs();
1110 for i in 1..pairs.len() {
1111 assert!(
1112 pairs[i - 1].priority >= pairs[i].priority,
1113 "pairs not sorted at index {i}"
1114 );
1115 }
1116 }
1117
1118 #[test]
1119 fn test_form_check_pairs_priority_formula() {
1120 let local = host("1.0.0.1", 1000, 200);
1123 let remote = srflx("2.0.0.1", 2000, 150);
1124 let pair = IcePair::new(local, remote);
1125 let expected: u64 = (1u64 << 32) * 150 + 2 * 200 + 1;
1126 assert_eq!(pair.priority, expected);
1127 }
1128
1129 #[test]
1130 fn test_form_check_pairs_priority_formula_equal() {
1131 let local = host("1.0.0.1", 1000, 100);
1133 let remote = srflx("2.0.0.1", 2000, 100);
1134 let pair = IcePair::new(local, remote);
1135 let expected: u64 = (1u64 << 32) * 100 + 2 * 100;
1136 assert_eq!(pair.priority, expected);
1137 }
1138
1139 #[test]
1140 fn test_form_check_pairs_capped_to_max() {
1141 let mut mgr = NatTraversalManager::new(TraversalConfig {
1142 max_pairs: 3,
1143 ..TraversalConfig::default()
1144 });
1145 for i in 0..4u16 {
1146 mgr.add_local_candidate(host("1.0.0.1", 1000 + i, 100 + i as u32))
1147 .expect("test: add_local_candidate should succeed");
1148 }
1149 mgr.add_remote_candidate(host("2.0.0.1", 2000, 50))
1150 .expect("test: add_remote_candidate should succeed");
1151 let pairs = mgr.form_check_pairs();
1152 assert_eq!(pairs.len(), 3);
1153 }
1154
1155 #[test]
1156 fn test_form_check_pairs_empty_returns_empty() {
1157 let mut mgr = default_manager();
1158 let pairs = mgr.form_check_pairs();
1159 assert!(pairs.is_empty());
1160 }
1161
1162 #[test]
1163 fn test_form_check_pairs_new_pairs_in_waiting_state() {
1164 let mut mgr = default_manager();
1165 mgr.add_local_candidate(host("1.0.0.1", 1000, 100))
1166 .expect("test: add_local_candidate should succeed");
1167 mgr.add_remote_candidate(host("2.0.0.1", 2000, 80))
1168 .expect("test: add_remote_candidate should succeed");
1169 let pairs = mgr.form_check_pairs();
1170 assert_eq!(pairs[0].state, PairState::Waiting);
1171 }
1172
1173 #[test]
1176 fn test_detect_nat_type_empty() {
1177 assert_eq!(NatTraversalManager::detect_nat_type(&[]), NatType::Unknown);
1178 }
1179
1180 #[test]
1181 fn test_detect_nat_type_only_host() {
1182 let c = [host("1.2.3.4", 5000, 100)];
1183 assert_eq!(
1184 NatTraversalManager::detect_nat_type(&c),
1185 NatType::OpenInternet
1186 );
1187 }
1188
1189 #[test]
1190 fn test_detect_nat_type_single_srflx_full_cone() {
1191 let c = [host("1.2.3.4", 5000, 100), srflx("5.6.7.8", 3478, 90)];
1192 assert_eq!(NatTraversalManager::detect_nat_type(&c), NatType::FullCone);
1193 }
1194
1195 #[test]
1196 fn test_detect_nat_type_symmetric_multiple_srflx() {
1197 let c = [
1198 srflx("5.6.7.8", 3478, 90),
1199 srflx("9.10.11.12", 4444, 85), ];
1201 assert_eq!(NatTraversalManager::detect_nat_type(&c), NatType::Symmetric);
1202 }
1203
1204 #[test]
1205 fn test_detect_nat_type_symmetric_same_ip_diff_port() {
1206 let c = [
1207 srflx("5.6.7.8", 3478, 90),
1208 srflx("5.6.7.8", 3479, 85), ];
1210 assert_eq!(NatTraversalManager::detect_nat_type(&c), NatType::Symmetric);
1211 }
1212
1213 #[test]
1214 fn test_detect_nat_type_multiple_identical_srflx() {
1215 let c = [
1216 srflx("5.6.7.8", 3478, 90),
1217 srflx("5.6.7.8", 3478, 85), ];
1219 assert_eq!(NatTraversalManager::detect_nat_type(&c), NatType::FullCone);
1220 }
1221
1222 #[test]
1223 fn test_detect_nat_type_relayed_only() {
1224 let c = [relay("5.6.7.8", 3478, 50)];
1225 assert_eq!(
1226 NatTraversalManager::detect_nat_type(&c),
1227 NatType::PortRestrictedCone
1228 );
1229 }
1230
1231 #[test]
1232 fn test_detect_nat_type_host_and_relayed() {
1233 let c = [host("1.2.3.4", 5000, 100), relay("5.6.7.8", 3478, 50)];
1234 assert_eq!(
1236 NatTraversalManager::detect_nat_type(&c),
1237 NatType::PortRestrictedCone
1238 );
1239 }
1240
1241 #[test]
1242 fn test_detect_nat_type_srflx_and_relayed_restricted_cone() {
1243 let c = [
1244 host("1.2.3.4", 5000, 100),
1245 srflx("5.6.7.8", 3478, 90),
1246 relay("5.6.7.8", 9999, 50),
1247 ];
1248 assert_eq!(
1249 NatTraversalManager::detect_nat_type(&c),
1250 NatType::RestrictedCone
1251 );
1252 }
1253
1254 #[test]
1257 fn test_check_pair_host_host_succeeds() {
1258 let mgr = default_manager();
1259 let mut pair = IcePair::new(host("1.0.0.1", 1000, 100), host("2.0.0.1", 2000, 80));
1260 let state = mgr.check_pair(&mut pair, 0);
1261 assert_eq!(state, PairState::Succeeded);
1262 assert_eq!(pair.state, PairState::Succeeded);
1263 }
1264
1265 #[test]
1266 fn test_check_pair_srflx_srflx_succeeds() {
1267 let mgr = default_manager();
1268 let mut pair = IcePair::new(srflx("1.0.0.1", 1000, 100), srflx("2.0.0.1", 2000, 80));
1269 let state = mgr.check_pair(&mut pair, 0);
1270 assert_eq!(state, PairState::Succeeded);
1271 }
1272
1273 #[test]
1274 fn test_check_pair_relayed_host_succeeds() {
1275 let mgr = default_manager();
1276 let mut pair = IcePair::new(relay("1.0.0.1", 1000, 50), host("2.0.0.1", 2000, 80));
1277 let state = mgr.check_pair(&mut pair, 0);
1278 assert_eq!(state, PairState::Succeeded);
1279 }
1280
1281 #[test]
1282 fn test_check_pair_host_prflx_succeeds() {
1283 let mgr = default_manager();
1284 let mut pair = IcePair::new(host("1.0.0.1", 1000, 100), prflx("2.0.0.1", 2000, 90));
1285 let state = mgr.check_pair(&mut pair, 0);
1286 assert_eq!(state, PairState::Succeeded);
1287 }
1288
1289 #[test]
1290 fn test_check_pair_transitions_through_in_progress() {
1291 let mgr = default_manager();
1294 let mut pair = IcePair::new(host("1.0.0.1", 1000, 100), host("2.0.0.1", 2000, 80));
1295 assert_eq!(pair.state, PairState::Waiting);
1296 let result = mgr.check_pair(&mut pair, 42_000);
1297 assert_eq!(result, PairState::Succeeded);
1298 }
1299
1300 #[test]
1301 fn test_check_pair_nominated_false_by_default() {
1302 let mgr = default_manager();
1303 let mut pair = IcePair::new(host("1.0.0.1", 1000, 100), host("2.0.0.1", 2000, 80));
1304 mgr.check_pair(&mut pair, 0);
1305 assert!(!pair.nominated);
1306 }
1307
1308 #[test]
1311 fn test_nominate_best_pair_picks_highest_priority() {
1312 let mut mgr = default_manager();
1313 mgr.add_local_candidate(host("1.0.0.1", 1000, 200))
1314 .expect("test: add_local_candidate should succeed");
1315 mgr.add_local_candidate(host("1.0.0.2", 1001, 50))
1316 .expect("test: add_local_candidate should succeed");
1317 mgr.add_remote_candidate(host("2.0.0.1", 2000, 150))
1318 .expect("test: add_remote_candidate should succeed");
1319 mgr.form_check_pairs();
1320 for pair in &mut mgr.check_pairs {
1321 pair.state = PairState::Succeeded;
1322 }
1323 let nominated = mgr
1324 .nominate_best_pair()
1325 .expect("test: nominate_best_pair should succeed with succeeded pairs");
1326 assert!(nominated.nominated);
1327 assert_eq!(nominated.local.priority, 200);
1329 }
1330
1331 #[test]
1332 fn test_nominate_best_pair_no_succeeded_pairs_err() {
1333 let mut mgr = default_manager();
1334 mgr.add_local_candidate(host("1.0.0.1", 1000, 100))
1335 .expect("test: add_local_candidate should succeed");
1336 mgr.add_remote_candidate(host("2.0.0.1", 2000, 80))
1337 .expect("test: add_remote_candidate should succeed");
1338 mgr.form_check_pairs();
1339 assert!(matches!(
1340 mgr.nominate_best_pair(),
1341 Err(TraversalError::NoValidPair)
1342 ));
1343 }
1344
1345 #[test]
1346 fn test_nominate_best_pair_marks_nominated() {
1347 let mut mgr = default_manager();
1348 mgr.add_local_candidate(host("1.0.0.1", 1000, 100))
1349 .expect("test: add_local_candidate should succeed");
1350 mgr.add_remote_candidate(host("2.0.0.1", 2000, 80))
1351 .expect("test: add_remote_candidate should succeed");
1352 mgr.form_check_pairs();
1353 mgr.check_pairs[0].state = PairState::Succeeded;
1354 let pair = mgr
1355 .nominate_best_pair()
1356 .expect("test: nominate_best_pair should succeed with succeeded pairs");
1357 assert!(pair.nominated);
1358 }
1359
1360 #[test]
1361 fn test_nominate_best_pair_updates_stats() {
1362 let mut mgr = default_manager();
1363 mgr.add_local_candidate(host("1.0.0.1", 1000, 100))
1364 .expect("test: add_local_candidate should succeed");
1365 mgr.add_remote_candidate(host("2.0.0.1", 2000, 80))
1366 .expect("test: add_remote_candidate should succeed");
1367 mgr.form_check_pairs();
1368 mgr.check_pairs[0].state = PairState::Succeeded;
1369 mgr.nominate_best_pair()
1370 .expect("test: nominate_best_pair should succeed with succeeded pairs");
1371 assert!(mgr.stats().nominated_pair.is_some());
1372 }
1373
1374 #[test]
1377 fn test_run_checks_all_waiting_checked() {
1378 let mut mgr = default_manager();
1379 mgr.add_local_candidate(host("1.0.0.1", 1000, 100))
1380 .expect("test: add_local_candidate should succeed");
1381 mgr.add_remote_candidate(host("2.0.0.1", 2000, 80))
1382 .expect("test: add_remote_candidate should succeed");
1383 mgr.form_check_pairs();
1384 let results = mgr.run_checks(0);
1385 assert_eq!(results.len(), 1);
1386 }
1387
1388 #[test]
1389 fn test_run_checks_returns_pair_key_and_state() {
1390 let mut mgr = default_manager();
1391 mgr.add_local_candidate(host("1.0.0.1", 1000, 100))
1392 .expect("test: add_local_candidate should succeed");
1393 mgr.add_remote_candidate(host("2.0.0.1", 2000, 80))
1394 .expect("test: add_remote_candidate should succeed");
1395 mgr.form_check_pairs();
1396 let results = mgr.run_checks(0);
1397 assert!(!results[0].0.is_empty());
1398 assert_eq!(results[0].1, PairState::Succeeded);
1399 }
1400
1401 #[test]
1402 fn test_run_checks_skips_frozen_pairs() {
1403 let mut mgr = default_manager();
1404 mgr.add_local_candidate(host("1.0.0.1", 1000, 100))
1405 .expect("test: add_local_candidate should succeed");
1406 mgr.add_remote_candidate(host("2.0.0.1", 2000, 80))
1407 .expect("test: add_remote_candidate should succeed");
1408 mgr.form_check_pairs();
1409 mgr.check_pairs[0].state = PairState::Frozen;
1410 let results = mgr.run_checks(0);
1411 assert!(results.is_empty());
1412 }
1413
1414 #[test]
1415 fn test_run_checks_skips_succeeded_pairs() {
1416 let mut mgr = default_manager();
1417 mgr.add_local_candidate(host("1.0.0.1", 1000, 100))
1418 .expect("test: add_local_candidate should succeed");
1419 mgr.add_remote_candidate(host("2.0.0.1", 2000, 80))
1420 .expect("test: add_remote_candidate should succeed");
1421 mgr.form_check_pairs();
1422 mgr.check_pairs[0].state = PairState::Succeeded;
1423 let results = mgr.run_checks(0);
1424 assert!(results.is_empty());
1425 }
1426
1427 #[test]
1428 fn test_run_checks_updates_stats_succeeded() {
1429 let mut mgr = default_manager();
1430 mgr.add_local_candidate(host("1.0.0.1", 1000, 100))
1431 .expect("test: add_local_candidate should succeed");
1432 mgr.add_remote_candidate(host("2.0.0.1", 2000, 80))
1433 .expect("test: add_remote_candidate should succeed");
1434 mgr.form_check_pairs();
1435 mgr.run_checks(0);
1436 let s = mgr.stats();
1437 assert_eq!(s.pairs_checked, 1);
1438 assert_eq!(s.pairs_succeeded, 1);
1439 }
1440
1441 #[test]
1442 fn test_run_checks_multiple_pairs() {
1443 let mut mgr = default_manager();
1444 mgr.add_local_candidate(host("1.0.0.1", 1000, 100))
1445 .expect("test: add_local_candidate should succeed");
1446 mgr.add_remote_candidate(host("2.0.0.1", 2000, 80))
1447 .expect("test: add_remote_candidate should succeed");
1448 mgr.add_remote_candidate(host("2.0.0.2", 2001, 70))
1449 .expect("test: add_remote_candidate should succeed");
1450 mgr.form_check_pairs();
1451 let results = mgr.run_checks(0);
1452 assert_eq!(results.len(), 2);
1453 }
1454
1455 #[test]
1458 fn test_stun_binding_request_round_trip() {
1459 let mgr = default_manager();
1460 let msg = StunMessage::new(StunMessageType::BindingRequest, 0x123456);
1461 let encoded = mgr.encode_stun_message(&msg);
1462 let decoded = mgr
1463 .decode_stun_message(&encoded)
1464 .expect("test: decode_stun_message should succeed for valid binding request");
1465 assert_eq!(decoded.msg_type, StunMessageType::BindingRequest);
1466 assert_eq!(decoded.transaction_id, msg.transaction_id);
1467 }
1468
1469 #[test]
1470 fn test_stun_binding_response_round_trip() {
1471 let mgr = default_manager();
1472 let mut msg = StunMessage::new(StunMessageType::BindingResponse, 0xABCD);
1473 msg.attributes
1474 .push(StunAttribute::MappedAddress("1.2.3.4".to_string(), 5000));
1475 let encoded = mgr.encode_stun_message(&msg);
1476 let decoded = mgr
1477 .decode_stun_message(&encoded)
1478 .expect("test: decode_stun_message should succeed for valid binding response");
1479 assert_eq!(decoded.msg_type, StunMessageType::BindingResponse);
1480 assert_eq!(decoded.attributes.len(), 1);
1481 if let StunAttribute::MappedAddress(addr, port) = &decoded.attributes[0] {
1482 assert_eq!(addr, "1.2.3.4");
1483 assert_eq!(*port, 5000);
1484 } else {
1485 panic!("expected MappedAddress");
1486 }
1487 }
1488
1489 #[test]
1490 fn test_stun_xor_mapped_address_round_trip() {
1491 let mgr = default_manager();
1492 let mut msg = StunMessage::new(StunMessageType::BindingResponse, 0xFFFF);
1493 msg.attributes.push(StunAttribute::XorMappedAddress(
1494 "192.168.1.1".to_string(),
1495 54321,
1496 ));
1497 let encoded = mgr.encode_stun_message(&msg);
1498 let decoded = mgr
1499 .decode_stun_message(&encoded)
1500 .expect("test: decode_stun_message should succeed for xor mapped address");
1501 if let StunAttribute::XorMappedAddress(addr, port) = &decoded.attributes[0] {
1502 assert_eq!(addr, "192.168.1.1");
1503 assert_eq!(*port, 54321);
1504 } else {
1505 panic!("expected XorMappedAddress");
1506 }
1507 }
1508
1509 #[test]
1510 fn test_stun_username_round_trip() {
1511 let mgr = default_manager();
1512 let mut msg = StunMessage::new(StunMessageType::BindingRequest, 1);
1513 msg.attributes
1514 .push(StunAttribute::Username("alice:bob".to_string()));
1515 let encoded = mgr.encode_stun_message(&msg);
1516 let decoded = mgr
1517 .decode_stun_message(&encoded)
1518 .expect("test: decode_stun_message should succeed for username attribute");
1519 if let StunAttribute::Username(name) = &decoded.attributes[0] {
1520 assert_eq!(name, "alice:bob");
1521 } else {
1522 panic!("expected Username");
1523 }
1524 }
1525
1526 #[test]
1527 fn test_stun_realm_round_trip() {
1528 let mgr = default_manager();
1529 let mut msg = StunMessage::new(StunMessageType::AllocateRequest, 2);
1530 msg.attributes
1531 .push(StunAttribute::Realm("example.com".to_string()));
1532 let encoded = mgr.encode_stun_message(&msg);
1533 let decoded = mgr
1534 .decode_stun_message(&encoded)
1535 .expect("test: decode_stun_message should succeed for realm attribute");
1536 if let StunAttribute::Realm(r) = &decoded.attributes[0] {
1537 assert_eq!(r, "example.com");
1538 } else {
1539 panic!("expected Realm");
1540 }
1541 }
1542
1543 #[test]
1544 fn test_stun_error_code_round_trip() {
1545 let mgr = default_manager();
1546 let mut msg = StunMessage::new(StunMessageType::BindingError, 3);
1547 msg.attributes
1548 .push(StunAttribute::ErrorCode(401, "Unauthorized".to_string()));
1549 let encoded = mgr.encode_stun_message(&msg);
1550 let decoded = mgr
1551 .decode_stun_message(&encoded)
1552 .expect("test: decode_stun_message should succeed for error code attribute");
1553 if let StunAttribute::ErrorCode(code, reason) = &decoded.attributes[0] {
1554 assert_eq!(*code, 401);
1555 assert_eq!(reason, "Unauthorized");
1556 } else {
1557 panic!("expected ErrorCode");
1558 }
1559 }
1560
1561 #[test]
1562 fn test_stun_fingerprint_round_trip() {
1563 let mgr = default_manager();
1564 let mut msg = StunMessage::new(StunMessageType::BindingRequest, 4);
1565 msg.attributes.push(StunAttribute::Fingerprint(0xDEAD_BEEF));
1566 let encoded = mgr.encode_stun_message(&msg);
1567 let decoded = mgr
1568 .decode_stun_message(&encoded)
1569 .expect("test: decode_stun_message should succeed for fingerprint attribute");
1570 if let StunAttribute::Fingerprint(crc) = decoded.attributes[0] {
1571 assert_eq!(crc, 0xDEAD_BEEF);
1572 } else {
1573 panic!("expected Fingerprint");
1574 }
1575 }
1576
1577 #[test]
1578 fn test_stun_lifetime_round_trip() {
1579 let mgr = default_manager();
1580 let mut msg = StunMessage::new(StunMessageType::AllocateResponse, 5);
1581 msg.attributes.push(StunAttribute::Lifetime(600));
1582 let encoded = mgr.encode_stun_message(&msg);
1583 let decoded = mgr
1584 .decode_stun_message(&encoded)
1585 .expect("test: decode_stun_message should succeed for lifetime attribute");
1586 if let StunAttribute::Lifetime(secs) = decoded.attributes[0] {
1587 assert_eq!(secs, 600);
1588 } else {
1589 panic!("expected Lifetime");
1590 }
1591 }
1592
1593 #[test]
1594 fn test_stun_multiple_attributes_round_trip() {
1595 let mgr = default_manager();
1596 let mut msg = StunMessage::new(StunMessageType::BindingResponse, 6);
1597 msg.attributes
1598 .push(StunAttribute::MappedAddress("10.0.0.1".to_string(), 4000));
1599 msg.attributes
1600 .push(StunAttribute::Username("u1:u2".to_string()));
1601 msg.attributes.push(StunAttribute::Fingerprint(42));
1602 let encoded = mgr.encode_stun_message(&msg);
1603 let decoded = mgr
1604 .decode_stun_message(&encoded)
1605 .expect("test: decode_stun_message should succeed for multiple attributes");
1606 assert_eq!(decoded.attributes.len(), 3);
1607 }
1608
1609 #[test]
1612 fn test_decode_stun_too_short() {
1613 let mgr = default_manager();
1614 let result = mgr.decode_stun_message(&[0u8; 10]);
1615 assert!(matches!(result, Err(TraversalError::StunError(_))));
1616 }
1617
1618 #[test]
1619 fn test_decode_stun_wrong_magic() {
1620 let mgr = default_manager();
1621 let mut buf = vec![0u8; 20];
1622 buf[0] = 0x00;
1624 buf[1] = 0x01;
1625 buf[4] = 0xFF;
1627 buf[5] = 0xFF;
1628 buf[6] = 0xFF;
1629 buf[7] = 0xFF;
1630 let result = mgr.decode_stun_message(&buf);
1631 assert!(matches!(result, Err(TraversalError::StunError(_))));
1632 }
1633
1634 #[test]
1635 fn test_decode_stun_unknown_type() {
1636 let mgr = default_manager();
1637 let mut buf = vec![0u8; 20];
1638 buf[0] = 0x00;
1640 buf[1] = 0xFF;
1641 let magic = STUN_MAGIC.to_be_bytes();
1643 buf[4] = magic[0];
1644 buf[5] = magic[1];
1645 buf[6] = magic[2];
1646 buf[7] = magic[3];
1647 let result = mgr.decode_stun_message(&buf);
1648 assert!(matches!(result, Err(TraversalError::StunError(_))));
1649 }
1650
1651 #[test]
1652 fn test_decode_stun_truncated_attributes() {
1653 let mgr = default_manager();
1654 let mut buf = vec![0u8; 20];
1655 buf[0] = 0x00;
1656 buf[1] = 0x01;
1657 let magic = STUN_MAGIC.to_be_bytes();
1658 buf[4] = magic[0];
1659 buf[5] = magic[1];
1660 buf[6] = magic[2];
1661 buf[7] = magic[3];
1662 buf[2] = 0x00;
1664 buf[3] = 0x0A;
1665 let result = mgr.decode_stun_message(&buf);
1666 assert!(matches!(result, Err(TraversalError::StunError(_))));
1667 }
1668
1669 #[test]
1672 fn test_stats_initial_state() {
1673 let mgr = default_manager();
1674 let s = mgr.stats();
1675 assert_eq!(s.candidates_gathered, 0);
1676 assert_eq!(s.pairs_checked, 0);
1677 assert_eq!(s.pairs_succeeded, 0);
1678 assert_eq!(s.pairs_failed, 0);
1679 assert!(s.nominated_pair.is_none());
1680 }
1681
1682 #[test]
1683 fn test_stats_nat_type_string_open_internet() {
1684 let mut mgr = default_manager();
1685 mgr.add_local_candidate(host("1.2.3.4", 5000, 100))
1686 .expect("test: add_local_candidate should succeed");
1687 let s = mgr.stats();
1688 assert!(s.nat_type.contains("OpenInternet"));
1689 }
1690
1691 #[test]
1692 fn test_stats_nat_type_string_unknown_no_candidates() {
1693 let mgr = default_manager();
1694 let s = mgr.stats();
1695 assert!(s.nat_type.contains("Unknown"));
1696 }
1697
1698 #[test]
1699 fn test_stats_after_full_workflow() {
1700 let mut mgr = default_manager();
1701 mgr.add_local_candidate(host("1.0.0.1", 1000, 100))
1702 .expect("test: add_local_candidate should succeed");
1703 mgr.add_remote_candidate(host("2.0.0.1", 2000, 80))
1704 .expect("test: add_remote_candidate should succeed");
1705 mgr.form_check_pairs();
1706 mgr.run_checks(0);
1707 mgr.nominate_best_pair()
1708 .expect("test: nominate_best_pair should succeed after checks");
1709 let s = mgr.stats();
1710 assert_eq!(s.pairs_checked, 1);
1711 assert_eq!(s.pairs_succeeded, 1);
1712 assert!(s.nominated_pair.is_some());
1713 }
1714
1715 #[test]
1718 fn test_traversal_error_display_no_valid_pair() {
1719 let e = TraversalError::NoValidPair;
1720 assert!(e.to_string().contains("no valid ICE pair"));
1721 }
1722
1723 #[test]
1724 fn test_traversal_error_display_gathering_failed() {
1725 let e = TraversalError::CandidateGatheringFailed("oops".to_string());
1726 assert!(e.to_string().contains("oops"));
1727 }
1728
1729 #[test]
1730 fn test_traversal_error_display_stun_error() {
1731 let e = TraversalError::StunError("bad magic".to_string());
1732 assert!(e.to_string().contains("bad magic"));
1733 }
1734
1735 #[test]
1736 fn test_traversal_error_display_turn_failed() {
1737 let e = TraversalError::TurnAllocationFailed("quota exceeded".to_string());
1738 assert!(e.to_string().contains("quota exceeded"));
1739 }
1740
1741 #[test]
1742 fn test_traversal_error_display_checklist_exhausted() {
1743 let e = TraversalError::ChecklistExhausted;
1744 assert!(e.to_string().contains("exhausted"));
1745 }
1746
1747 #[test]
1750 fn test_xorshift64_non_zero_output() {
1751 let mut s = 0xDEAD_BEEF_1234_5678u64;
1752 let v = xorshift64(&mut s);
1753 assert_ne!(v, 0);
1754 assert_ne!(s, 0xDEAD_BEEF_1234_5678u64);
1755 }
1756
1757 #[test]
1758 fn test_xorshift64_deterministic() {
1759 let mut s1 = 12345u64;
1760 let mut s2 = 12345u64;
1761 assert_eq!(xorshift64(&mut s1), xorshift64(&mut s2));
1762 }
1763
1764 #[test]
1765 fn test_fnv1a_64_empty() {
1766 assert_eq!(fnv1a_64(&[]), 14_695_981_039_346_656_037u64);
1768 }
1769
1770 #[test]
1771 fn test_fnv1a_64_known_value() {
1772 let h = fnv1a_64(b"hello");
1774 assert_ne!(h, 14_695_981_039_346_656_037u64);
1775 }
1776
1777 #[test]
1778 fn test_fnv1a_64_differs_for_different_input() {
1779 assert_ne!(fnv1a_64(b"abc"), fnv1a_64(b"def"));
1780 }
1781
1782 #[test]
1785 fn test_ice_pair_key_format() {
1786 let pair = IcePair::new(host("1.0.0.1", 1000, 100), host("2.0.0.1", 2000, 80));
1787 assert_eq!(pair.key(), "1.0.0.1:1000 -> 2.0.0.1:2000");
1788 }
1789
1790 #[test]
1791 fn test_ice_pair_initial_state_waiting() {
1792 let pair = IcePair::new(host("1.0.0.1", 1000, 100), srflx("2.0.0.1", 2000, 90));
1793 assert_eq!(pair.state, PairState::Waiting);
1794 assert!(!pair.nominated);
1795 }
1796
1797 #[test]
1800 fn test_stun_message_new_transaction_id_non_zero() {
1801 let msg = StunMessage::new(StunMessageType::BindingRequest, 999);
1802 assert!(msg.transaction_id.iter().any(|&b| b != 0));
1803 }
1804
1805 #[test]
1806 fn test_stun_message_new_zero_seed_fallback() {
1807 let msg = StunMessage::new(StunMessageType::BindingRequest, 0);
1809 assert!(msg.transaction_id.iter().any(|&b| b != 0));
1810 }
1811
1812 #[test]
1813 fn test_stun_message_all_types_encode_decode() {
1814 let mgr = default_manager();
1815 let types = [
1816 StunMessageType::BindingRequest,
1817 StunMessageType::BindingResponse,
1818 StunMessageType::BindingError,
1819 StunMessageType::AllocateRequest,
1820 StunMessageType::AllocateResponse,
1821 ];
1822 for t in &types {
1823 let msg = StunMessage::new(t.clone(), 1);
1824 let encoded = mgr.encode_stun_message(&msg);
1825 let decoded = mgr
1826 .decode_stun_message(&encoded)
1827 .expect("test: decode_stun_message should succeed for all stun message types");
1828 assert_eq!(&decoded.msg_type, t);
1829 }
1830 }
1831
1832 #[test]
1835 fn test_full_ice_workflow() {
1836 let mut mgr = NatTraversalManager::new(TraversalConfig {
1837 max_pairs: 50,
1838 ..TraversalConfig::default()
1839 });
1840
1841 mgr.add_local_candidate(host("192.168.1.1", 5000, 2130706431))
1843 .expect("test: add_local_candidate should succeed");
1844 mgr.add_local_candidate(srflx("203.0.113.1", 5001, 1694498815))
1845 .expect("test: add_local_candidate should succeed");
1846
1847 mgr.add_remote_candidate(host("10.0.0.1", 6000, 2130706431))
1849 .expect("test: add_remote_candidate should succeed");
1850 mgr.add_remote_candidate(srflx("198.51.100.1", 6001, 1694498815))
1851 .expect("test: add_remote_candidate should succeed");
1852
1853 let pairs = mgr.form_check_pairs();
1855 assert_eq!(pairs.len(), 4);
1856
1857 let results = mgr.run_checks(1_000_000);
1859 assert_eq!(results.len(), 4);
1860 assert!(results.iter().all(|(_, s)| *s == PairState::Succeeded));
1861
1862 let best = mgr
1864 .nominate_best_pair()
1865 .expect("test: nominate_best_pair should succeed in full workflow");
1866 assert!(best.nominated);
1867 assert_eq!(best.state, PairState::Succeeded);
1868
1869 let s = mgr.stats();
1871 assert_eq!(s.pairs_checked, 4);
1872 assert_eq!(s.pairs_succeeded, 4);
1873 assert!(s.nominated_pair.is_some());
1874 }
1875}