use std::{net::Ipv4Addr, num::NonZeroU16, time::Duration};
use n0_error::stack_error;
use super::{nat_pmp, pcp, upnp};
use crate::Protocol;
pub(super) trait PortMapped: std::fmt::Debug + Unpin {
fn external(&self) -> (Ipv4Addr, NonZeroU16);
fn half_lifetime(&self) -> Duration;
}
#[derive(derive_more::Debug)]
pub enum Mapping {
Upnp(upnp::Mapping),
Pcp(pcp::Mapping),
NatPmp(nat_pmp::Mapping),
}
#[allow(missing_docs)]
#[stack_error(derive, add_meta, from_sources)]
#[non_exhaustive]
pub enum Error {
#[error("PCP mapping failed")]
Pcp { source: pcp::Error },
#[error("NAT-PMP mapping failed")]
NatPmp { source: nat_pmp::Error },
#[error("UPnP mapping failed")]
Upnp { source: upnp::Error },
}
impl Mapping {
pub(crate) async fn new_pcp(
protocol: Protocol,
local_ip: Ipv4Addr,
local_port: NonZeroU16,
gateway: Ipv4Addr,
external_addr: Option<(Ipv4Addr, NonZeroU16)>,
) -> Result<Self, Error> {
pcp::Mapping::new(protocol, local_ip, local_port, gateway, external_addr)
.await
.map(Self::Pcp)
.map_err(Error::from)
}
pub(crate) async fn new_nat_pmp(
protocol: Protocol,
local_ip: Ipv4Addr,
local_port: NonZeroU16,
gateway: Ipv4Addr,
external_addr: Option<(Ipv4Addr, NonZeroU16)>,
) -> Result<Self, Error> {
nat_pmp::Mapping::new(
protocol,
local_ip,
local_port,
gateway,
external_addr.map(|(_addr, port)| port),
)
.await
.map(Self::NatPmp)
.map_err(Error::from)
}
pub(crate) async fn new_upnp(
protocol: Protocol,
local_ip: Ipv4Addr,
local_port: NonZeroU16,
gateway: Option<upnp::Gateway>,
external_port: Option<NonZeroU16>,
) -> Result<Self, Error> {
upnp::Mapping::new(protocol, local_ip, local_port, gateway, external_port)
.await
.map(Self::Upnp)
.map_err(Error::from)
}
pub(crate) async fn release(self) -> Result<(), Error> {
match self {
Mapping::Upnp(m) => m.release().await?,
Mapping::Pcp(m) => m.release().await?,
Mapping::NatPmp(m) => m.release().await?,
}
Ok(())
}
}
impl PortMapped for Mapping {
fn external(&self) -> (Ipv4Addr, NonZeroU16) {
match self {
Mapping::Upnp(m) => m.external(),
Mapping::Pcp(m) => m.external(),
Mapping::NatPmp(m) => m.external(),
}
}
fn half_lifetime(&self) -> Duration {
match self {
Mapping::Upnp(m) => m.half_lifetime(),
Mapping::Pcp(m) => m.half_lifetime(),
Mapping::NatPmp(m) => m.half_lifetime(),
}
}
}