use std::net::{IpAddr, SocketAddr};
use std::sync::Arc;
use sipx_sip::headers::Via;
use sipx_sip::transaction::Reliability;
#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash)]
pub enum TransportKind {
Udp,
Tcp,
Tls,
Ws,
Wss,
Quic,
}
impl TransportKind {
#[must_use]
pub fn reliability(self) -> Reliability {
match self {
Self::Udp => Reliability::Unreliable,
_ => Reliability::Reliable,
}
}
#[must_use]
pub fn as_str(self) -> &'static str {
match self {
Self::Udp => "UDP",
Self::Tcp => "TCP",
Self::Tls => "TLS",
Self::Ws => "WS",
Self::Wss => "WSS",
Self::Quic => "QUIC",
}
}
#[must_use]
pub fn is_secure(self) -> bool {
matches!(self, Self::Tls | Self::Wss | Self::Quic)
}
#[must_use]
pub fn default_port(self) -> u16 {
match self {
Self::Udp | Self::Tcp => 5060,
Self::Tls | Self::Quic => 5061,
Self::Ws => 80,
Self::Wss => 443,
}
}
#[must_use]
pub fn parse(token: &[u8]) -> Option<Self> {
match token.to_ascii_uppercase().as_slice() {
b"UDP" => Some(Self::Udp),
b"TCP" => Some(Self::Tcp),
b"TLS" => Some(Self::Tls),
b"WS" => Some(Self::Ws),
b"WSS" => Some(Self::Wss),
b"QUIC" => Some(Self::Quic),
_ => None,
}
}
}
#[derive(Debug, Clone, PartialEq, Eq, Hash)]
pub struct Target {
pub addr: SocketAddr,
pub transport: TransportKind,
pub verify_as: Option<Arc<str>>,
pub path: Option<Arc<str>>,
}
impl Target {
#[must_use]
pub fn new(addr: SocketAddr, transport: TransportKind) -> Self {
Self {
addr,
transport,
verify_as: None,
path: None,
}
}
#[must_use]
pub fn udp(addr: SocketAddr) -> Self {
Self::new(addr, TransportKind::Udp)
}
#[must_use]
pub fn verifying(mut self, name: impl AsRef<str>) -> Self {
self.verify_as = Some(Arc::from(name.as_ref()));
self
}
#[must_use]
pub fn at_path(mut self, path: impl AsRef<str>) -> Self {
let path = path.as_ref();
self.path = Some(if path.starts_with('/') {
Arc::from(path)
} else {
Arc::from(format!("/{path}"))
});
self
}
#[must_use]
pub fn connection(&self) -> ConnectionKey {
ConnectionKey {
peer: self.addr,
transport: self.transport,
identity: self.verify_as.clone(),
path: if matches!(self.transport, TransportKind::Ws | TransportKind::Wss) {
self.path.clone()
} else {
None
},
}
}
}
#[derive(Debug, Clone, PartialEq, Eq, Hash)]
pub struct ConnectionKey {
pub peer: SocketAddr,
pub transport: TransportKind,
pub identity: Option<Arc<str>>,
pub path: Option<Arc<str>>,
}
impl ConnectionKey {
#[must_use]
pub fn new(peer: SocketAddr, transport: TransportKind) -> Self {
Self {
peer,
transport,
identity: None,
path: None,
}
}
#[must_use]
pub fn ws_path(&self) -> &str {
self.path.as_deref().unwrap_or("/")
}
}
#[must_use]
pub fn response_destination(via: &Via, source: SocketAddr, transport: TransportKind) -> Target {
if let Some(maddr) = via.maddr()
&& let Some(addr) = parse_host(maddr)
{
let port = via.port.unwrap_or_else(|| transport.default_port());
return Target::new(SocketAddr::new(addr, port), transport);
}
let observed_port = via
.rport()
.flatten()
.and_then(|v| std::str::from_utf8(v).ok())
.and_then(|v| v.parse::<u16>().ok());
if let Some(received) = via.received()
&& let Some(addr) = parse_host(received)
{
let port = observed_port
.or(via.port)
.unwrap_or_else(|| transport.default_port());
return Target::new(SocketAddr::new(addr, port), transport);
}
if let sipx_sip::Host::Ip(ip) = &via.host {
let port = observed_port
.or(via.port)
.unwrap_or_else(|| transport.default_port());
return Target::new(SocketAddr::new(*ip, port), transport);
}
Target::new(source, transport)
}
fn parse_host(raw: &[u8]) -> Option<IpAddr> {
std::str::from_utf8(raw).ok()?.parse().ok()
}
#[cfg(test)]
#[allow(
clippy::unwrap_used,
clippy::expect_used,
clippy::panic,
clippy::indexing_slicing
)]
mod tests {
use super::*;
fn via(text: &str) -> Via {
Via::parse_one(text.as_bytes()).expect("a valid Via")
}
fn source() -> SocketAddr {
"203.0.113.9:41234".parse().expect("a valid address")
}
#[test]
fn a_plain_via_goes_to_its_sent_by() {
let target = response_destination(
&via("SIP/2.0/UDP 192.0.2.1:5060;branch=z9hG4bKx"),
source(),
TransportKind::Udp,
);
assert_eq!(target.addr.to_string(), "192.0.2.1:5060");
}
#[test]
fn a_sent_by_without_a_port_uses_the_transport_default() {
assert_eq!(
response_destination(
&via("SIP/2.0/UDP 192.0.2.1;branch=z9hG4bKx"),
source(),
TransportKind::Udp
)
.addr
.port(),
5060
);
assert_eq!(
response_destination(
&via("SIP/2.0/TLS 192.0.2.1;branch=z9hG4bKx"),
source(),
TransportKind::Tls
)
.addr
.port(),
5061
);
}
#[test]
fn an_observed_rport_is_used_even_without_a_received() {
let target = response_destination(
&via("SIP/2.0/UDP 203.0.113.9:5060;rport=41234;branch=z9hG4bKx"),
source(),
TransportKind::Udp,
);
assert_eq!(target.addr.to_string(), "203.0.113.9:41234");
}
#[test]
fn received_and_rport_override_the_sent_by() {
let target = response_destination(
&via("SIP/2.0/UDP 10.0.0.5:5060;received=203.0.113.9;rport=41234;branch=z9hG4bKx"),
source(),
TransportKind::Udp,
);
assert_eq!(target.addr.to_string(), "203.0.113.9:41234");
}
#[test]
fn received_without_rport_uses_the_sent_by_port() {
let target = response_destination(
&via("SIP/2.0/UDP 10.0.0.5:5070;received=203.0.113.9;branch=z9hG4bKx"),
source(),
TransportKind::Udp,
);
assert_eq!(target.addr.to_string(), "203.0.113.9:5070");
}
#[test]
fn maddr_wins_over_everything() {
let target = response_destination(
&via("SIP/2.0/UDP 10.0.0.5:5060;maddr=192.0.2.99;received=203.0.113.9;branch=z9hG4bKx"),
source(),
TransportKind::Udp,
);
assert_eq!(target.addr.ip().to_string(), "192.0.2.99");
}
#[test]
fn a_hostname_sent_by_falls_back_to_the_source() {
let target = response_destination(
&via("SIP/2.0/UDP client.example.com;branch=z9hG4bKx"),
source(),
TransportKind::Udp,
);
assert_eq!(target.addr, source());
}
#[test]
fn transports_have_their_rfc_default_ports() {
assert_eq!(TransportKind::Udp.default_port(), 5060);
assert_eq!(TransportKind::Tcp.default_port(), 5060);
assert_eq!(TransportKind::Tls.default_port(), 5061);
assert_eq!(TransportKind::Ws.default_port(), 80);
assert_eq!(TransportKind::Wss.default_port(), 443);
assert_eq!(TransportKind::Quic.default_port(), 5061);
}
fn peer() -> SocketAddr {
"127.0.0.1:8088".parse().expect("a valid address")
}
#[test]
fn a_target_asks_for_the_root_unless_it_says_otherwise() {
let target = Target::new(peer(), TransportKind::Ws);
assert_eq!(target.path, None);
assert_eq!(target.connection().ws_path(), "/");
}
#[test]
fn a_target_can_name_the_resource_it_wants() {
let target = Target::new(peer(), TransportKind::Ws).at_path("/ws");
assert_eq!(target.path.as_deref(), Some("/ws"));
assert_eq!(target.connection().ws_path(), "/ws");
}
#[test]
fn a_resource_name_missing_its_leading_slash_gets_one() {
for named in ["ws", "/ws"] {
assert_eq!(
Target::new(peer(), TransportKind::Ws)
.at_path(named)
.connection()
.ws_path(),
"/ws"
);
}
assert_eq!(
Target::new(peer(), TransportKind::Ws)
.at_path("")
.connection()
.ws_path(),
"/",
"naming nothing is naming the root"
);
}
#[test]
fn two_resources_on_one_address_are_two_connections() {
let one = Target::new(peer(), TransportKind::Ws).at_path("/ws");
let other = Target::new(peer(), TransportKind::Ws).at_path("/sip");
assert_ne!(one.connection(), other.connection());
assert_ne!(
one.connection(),
Target::new(peer(), TransportKind::Ws).connection(),
"the root is a resource like any other"
);
}
#[test]
fn only_udp_is_unreliable() {
assert_eq!(TransportKind::Udp.reliability(), Reliability::Unreliable);
for t in [
TransportKind::Tcp,
TransportKind::Tls,
TransportKind::Ws,
TransportKind::Wss,
TransportKind::Quic,
] {
assert_eq!(t.reliability(), Reliability::Reliable);
}
}
#[test]
fn quic_is_a_secure_reliable_via_transport() {
assert_eq!(TransportKind::parse(b"QUIC"), Some(TransportKind::Quic));
assert_eq!(TransportKind::Quic.as_str(), "QUIC");
assert!(TransportKind::Quic.is_secure());
}
#[test]
fn quic_pool_keys_keep_verified_names_and_transports_separate() {
let one = Target::new(peer(), TransportKind::Quic)
.verifying("one.example")
.connection();
let two = Target::new(peer(), TransportKind::Quic)
.verifying("two.example")
.connection();
let tls = Target::new(peer(), TransportKind::Tls)
.verifying("one.example")
.connection();
assert_ne!(one, two, "Q15: two authenticated names are two connections");
assert_ne!(one, tls, "Q16: QUIC and TLS cannot share a connection");
}
#[test]
fn quic_pool_keys_never_include_a_websocket_resource() {
let plain = Target::new(peer(), TransportKind::Quic)
.verifying("one.example")
.connection();
let with_irrelevant_path = Target::new(peer(), TransportKind::Quic)
.verifying("one.example")
.at_path("/ws")
.connection();
assert_eq!(plain, with_irrelevant_path);
assert_eq!(plain.path, None);
}
}