use serde::{Deserialize, Serialize};
#[derive(Debug, Clone, Serialize, Deserialize, PartialEq)]
pub struct NatConfig {
#[serde(default)]
pub enabled: bool,
#[serde(default = "default_stun_servers")]
pub stun_servers: Vec<StunServerConfig>,
#[serde(default)]
pub turn_servers: Vec<TurnServerConfig>,
#[serde(default = "default_hole_punch_timeout")]
pub hole_punch_timeout_secs: u64,
#[serde(default = "default_stun_refresh_interval")]
pub stun_refresh_interval_secs: u64,
#[serde(default = "default_max_candidate_pairs")]
pub max_candidate_pairs: usize,
#[serde(default)]
pub relay_server: Option<RelayServerConfig>,
}
#[derive(Debug, Clone, Serialize, Deserialize, PartialEq)]
pub struct StunServerConfig {
pub address: String,
#[serde(default)]
pub label: Option<String>,
}
#[derive(Debug, Clone, Serialize, Deserialize, PartialEq)]
pub struct TurnServerConfig {
pub address: String,
pub username: String,
pub credential: String,
#[serde(default)]
pub region: Option<String>,
}
#[derive(Debug, Clone, Serialize, Deserialize, PartialEq)]
pub struct RelayServerConfig {
pub listen_port: u16,
pub external_addr: String,
#[serde(default = "default_max_relay_sessions")]
pub max_sessions: usize,
}
fn default_stun_servers() -> Vec<StunServerConfig> {
vec![
StunServerConfig {
address: "stun.l.google.com:19302".to_string(),
label: Some("Google STUN 1".to_string()),
},
StunServerConfig {
address: "stun1.l.google.com:19302".to_string(),
label: Some("Google STUN 2".to_string()),
},
]
}
fn default_hole_punch_timeout() -> u64 {
15
}
fn default_stun_refresh_interval() -> u64 {
60
}
fn default_max_candidate_pairs() -> usize {
10
}
fn default_max_relay_sessions() -> usize {
100
}
impl Default for NatConfig {
fn default() -> Self {
Self {
enabled: false,
stun_servers: default_stun_servers(),
turn_servers: Vec::new(),
hole_punch_timeout_secs: default_hole_punch_timeout(),
stun_refresh_interval_secs: default_stun_refresh_interval(),
max_candidate_pairs: default_max_candidate_pairs(),
relay_server: None,
}
}
}
#[cfg(test)]
mod tests {
use super::*;
#[test]
fn test_nat_config_default() {
let config = NatConfig::default();
assert!(!config.enabled);
assert_eq!(config.stun_servers.len(), 2);
assert!(config.turn_servers.is_empty());
assert_eq!(config.hole_punch_timeout_secs, 15);
assert_eq!(config.stun_refresh_interval_secs, 60);
assert_eq!(config.max_candidate_pairs, 10);
assert!(config.relay_server.is_none());
}
#[test]
fn test_nat_config_serialization_roundtrip() {
let config = NatConfig::default();
let json = serde_json::to_string_pretty(&config).unwrap();
let deserialized: NatConfig = serde_json::from_str(&json).unwrap();
assert_eq!(deserialized.stun_servers.len(), 2);
assert_eq!(deserialized.hole_punch_timeout_secs, 15);
}
#[test]
fn test_nat_config_deserialize_empty_uses_defaults() {
let json = r#"{"enabled": true}"#;
let config: NatConfig = serde_json::from_str(json).unwrap();
assert!(config.enabled);
assert_eq!(config.stun_servers.len(), 2);
assert_eq!(config.hole_punch_timeout_secs, 15);
}
#[test]
fn test_turn_server_config() {
let json = r#"{
"address": "turn.example.com:3478",
"username": "user",
"credential": "pass",
"region": "us-east"
}"#;
let config: TurnServerConfig = serde_json::from_str(json).unwrap();
assert_eq!(config.address, "turn.example.com:3478");
assert_eq!(config.region, Some("us-east".to_string()));
}
#[test]
fn test_relay_server_config() {
let json = r#"{
"listen_port": 3478,
"external_addr": "1.2.3.4:3478"
}"#;
let config: RelayServerConfig = serde_json::from_str(json).unwrap();
assert_eq!(config.listen_port, 3478);
assert_eq!(config.max_sessions, 100);
}
}