use alloc::collections::BTreeMap;
use core::{
fmt,
fmt::Display,
net::{IpAddr, Ipv4Addr, Ipv6Addr},
num::{NonZero, NonZeroU32},
str::FromStr,
};
use serde::{Deserializer, Serializer};
pub const DEFAULT_STUN_PORT: u16 = 3478;
#[derive(
Clone, Copy, Debug, PartialEq, Eq, PartialOrd, Ord, Hash, serde::Serialize, serde::Deserialize,
)]
pub struct RegionId(NonZero<u32>);
impl Display for RegionId {
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
write!(f, "{}", self.0.get())
}
}
impl From<NonZero<u32>> for RegionId {
fn from(value: NonZero<u32>) -> Self {
Self(value)
}
}
impl From<RegionId> for NonZeroU32 {
fn from(value: RegionId) -> Self {
value.0
}
}
impl From<RegionId> for u32 {
fn from(value: RegionId) -> Self {
value.0.get()
}
}
#[derive(Clone, Debug, PartialEq, Eq, serde::Serialize, serde::Deserialize)]
#[serde(rename_all = "PascalCase")]
pub struct DerpServer<'a> {
#[serde(borrow)]
pub name: &'a str,
#[serde(rename = "RegionID")]
pub region_id: RegionId,
#[serde(rename = "HostName", borrow)]
pub hostname: &'a str,
#[serde(borrow, default)]
pub cert_name: Option<&'a str>,
#[serde(rename = "IPv4", default)]
pub ipv4: IpUsage<Ipv4Addr>,
#[serde(rename = "IPv6", default)]
pub ipv6: IpUsage<Ipv6Addr>,
#[serde(default, rename = "STUNPort")]
pub stun_port: StunPort,
#[serde(default, rename = "STUNOnly")]
pub stun_only: bool,
#[serde(default, rename = "DERPPort")]
pub derp_port: u16,
#[serde(default)]
pub insecure_for_tests: bool,
#[serde(default, rename = "STUNTestIP")]
pub stun_test_ip: Option<IpAddr>,
#[serde(default)]
pub can_port_80: bool,
}
#[derive(Debug, Copy, Clone, PartialEq, Eq)]
pub enum StunPort {
Disabled,
Port(u16),
}
impl Default for StunPort {
fn default() -> Self {
StunPort::Port(DEFAULT_STUN_PORT)
}
}
impl From<StunPort> for Option<u16> {
fn from(value: StunPort) -> Self {
(&value).into()
}
}
impl From<&StunPort> for Option<u16> {
fn from(value: &StunPort) -> Self {
match value {
StunPort::Disabled => None,
StunPort::Port(x) => Some(*x),
}
}
}
impl<'de> serde::Deserialize<'de> for StunPort {
fn deserialize<D>(deserializer: D) -> Result<Self, D::Error>
where
D: Deserializer<'de>,
{
let p = <Option<i32>>::deserialize(deserializer)?;
match p {
None | Some(0) => Ok(Default::default()),
Some(-1) => Ok(StunPort::Disabled),
Some(x) if x > 0 && x <= u16::MAX as i32 => Ok(StunPort::Port(x as u16)),
_ => Err(serde::de::Error::custom("invalid stun port")),
}
}
}
impl serde::Serialize for StunPort {
fn serialize<S>(&self, serializer: S) -> Result<S::Ok, S::Error>
where
S: Serializer,
{
match self {
Self::Disabled => serializer.serialize_i32(-1),
Self::Port(DEFAULT_STUN_PORT) => serializer.serialize_i32(0),
Self::Port(p) => serializer.serialize_i32(*p as i32),
}
}
}
#[derive(Debug, Copy, Clone, PartialEq, Eq, Default)]
pub enum IpUsage<T> {
Disable,
#[default]
UseDns,
FixedAddr(T),
}
impl<'de, T> serde::Deserialize<'de> for IpUsage<T>
where
T: FromStr,
{
fn deserialize<D>(deserializer: D) -> Result<Self, D::Error>
where
D: Deserializer<'de>,
{
let s = <Option<&'de str>>::deserialize(deserializer)?;
let s = match s {
None | Some("") => {
return Ok(IpUsage::UseDns);
}
Some(s) => s,
};
match s.parse::<T>() {
Ok(ip) => Ok(IpUsage::FixedAddr(ip)),
Err(_) => Ok(IpUsage::Disable),
}
}
}
impl<T> serde::Serialize for IpUsage<T>
where
T: Display,
{
fn serialize<S>(&self, serializer: S) -> Result<S::Ok, S::Error>
where
S: Serializer,
{
match self {
Self::Disable => serializer.serialize_str("none"),
Self::UseDns => serializer.serialize_str(""),
Self::FixedAddr(t) => serializer.serialize_str(&alloc::format!("{t}")),
}
}
}
#[derive(Clone, Debug, Default, serde::Serialize, serde::Deserialize)]
#[serde(rename_all = "PascalCase", default)]
pub struct HomeParams {
pub region_score: BTreeMap<RegionId, f64>,
}
#[derive(Clone, Debug, serde::Serialize, serde::Deserialize)]
#[serde(rename_all = "PascalCase")]
pub struct Region<'a> {
#[serde(rename = "RegionID")]
pub id: RegionId,
#[serde(rename = "RegionCode", borrow)]
pub code: &'a str,
#[serde(rename = "RegionName", borrow)]
pub name: &'a str,
#[serde(default)]
pub latitude: f64,
#[serde(default)]
pub longitude: f64,
#[deprecated = "use no_measure_no_home instead"]
#[serde(default)]
pub avoid: bool,
#[serde(default)]
pub no_measure_no_home: bool,
#[serde(default)]
pub nodes: alloc::vec::Vec<DerpServer<'a>>,
}
#[derive(Clone, Debug, Default, serde::Serialize, serde::Deserialize)]
#[serde(rename_all = "PascalCase", default)]
pub struct DerpMap<'a> {
pub home_params: Option<HomeParams>,
#[serde(borrow)]
pub regions: BTreeMap<RegionId, Region<'a>>,
pub omit_default_regions: bool,
}
#[cfg(test)]
mod test {
use super::*;
#[test]
fn ip_usage() {
fn d<T>(s: &str) -> IpUsage<T>
where
T: FromStr,
{
serde_json::from_str::<IpUsage<T>>(s).unwrap()
}
assert_eq!(d::<Ipv4Addr>(r#""""#), IpUsage::UseDns);
assert_eq!(d::<Ipv4Addr>(r#""none""#), IpUsage::Disable);
assert_eq!(
d::<Ipv4Addr>(r#""1.2.3.4""#),
IpUsage::FixedAddr(Ipv4Addr::new(1, 2, 3, 4))
);
}
}