use core::fmt;
use core::str::FromStr;
#[cfg(feature = "serde")]
use serde::{Deserialize, Serialize};
#[cfg(feature = "arbitrary")]
use arbitrary::Arbitrary;
#[cfg(feature = "zeroize")]
use zeroize::Zeroize;
#[derive(Debug, Clone, Copy, PartialEq, Eq)]
#[cfg_attr(feature = "serde", derive(Serialize, Deserialize))]
#[non_exhaustive]
pub enum PortError {
PortZero,
OutOfRange(u16),
InvalidFormat,
}
impl fmt::Display for PortError {
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
match self {
Self::PortZero => write!(f, "port 0 is reserved and cannot be used"),
Self::OutOfRange(port) => write!(f, "port number {port} is out of valid range"),
Self::InvalidFormat => write!(f, "invalid port format"),
}
}
}
#[cfg(feature = "std")]
impl std::error::Error for PortError {}
#[repr(transparent)]
#[derive(Debug, Clone, Copy, PartialEq, Eq, PartialOrd, Ord, Hash)]
#[cfg_attr(feature = "serde", derive(Serialize, Deserialize))]
#[cfg_attr(feature = "arbitrary", derive(Arbitrary))]
#[cfg_attr(feature = "zeroize", derive(Zeroize))]
pub struct Port(u16);
impl Port {
pub const fn new(port: u16) -> Result<Self, PortError> {
if port == 0 {
return Err(PortError::PortZero);
}
Ok(Self(port))
}
#[must_use]
#[inline]
pub const fn as_u16(&self) -> u16 {
self.0
}
#[must_use]
#[inline]
pub const fn into_u16(self) -> u16 {
self.0
}
#[must_use]
#[inline]
pub const fn is_system_port(&self) -> bool {
self.0 >= 1 && self.0 <= 1023
}
#[must_use]
#[inline]
pub const fn is_registered_port(&self) -> bool {
self.0 >= 1024 && self.0 <= 49151
}
#[must_use]
#[inline]
pub const fn is_dynamic_port(&self) -> bool {
self.0 >= 49152
}
pub const HTTP: Self = Self(80);
pub const HTTPS: Self = Self(443);
pub const SSH: Self = Self(22);
pub const FTP: Self = Self(21);
pub const FTP_DATA: Self = Self(20);
pub const SMTP: Self = Self(25);
pub const DNS: Self = Self(53);
pub const DNS_OVER_TLS: Self = Self(853);
pub const POP3: Self = Self(110);
pub const POP3S: Self = Self(995);
pub const IMAP: Self = Self(143);
pub const IMAPS: Self = Self(993);
pub const TELNET: Self = Self(23);
pub const TELNETS: Self = Self(992);
pub const MYSQL: Self = Self(3306);
pub const POSTGRESQL: Self = Self(5432);
pub const REDIS: Self = Self(6379);
pub const MONGODB: Self = Self(27017);
pub const HTTP_ALT: Self = Self(8080);
pub const HTTPS_ALT: Self = Self(8443);
}
impl TryFrom<u16> for Port {
type Error = PortError;
fn try_from(port: u16) -> Result<Self, Self::Error> {
Self::new(port)
}
}
impl TryFrom<&str> for Port {
type Error = PortError;
fn try_from(s: &str) -> Result<Self, Self::Error> {
let port: u16 = s.parse().map_err(|_| PortError::InvalidFormat)?;
Self::new(port)
}
}
impl From<Port> for u16 {
fn from(port: Port) -> Self {
port.0
}
}
impl FromStr for Port {
type Err = PortError;
fn from_str(s: &str) -> Result<Self, Self::Err> {
let port: u16 = s.parse().map_err(|_| PortError::InvalidFormat)?;
Self::new(port)
}
}
impl fmt::Display for Port {
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
write!(f, "{}", self.0)
}
}
#[cfg(test)]
mod tests {
use super::*;
#[test]
fn test_new_valid_port() {
assert!(Port::new(80).is_ok());
assert!(Port::new(8080).is_ok());
assert!(Port::new(65535).is_ok());
}
#[test]
fn test_port_zero_rejected() {
assert_eq!(Port::new(0), Err(PortError::PortZero));
}
#[test]
fn test_as_u16() {
let port = Port::new(8080).unwrap();
assert_eq!(port.as_u16(), 8080);
}
#[test]
fn test_into_u16() {
let port = Port::new(8080).unwrap();
assert_eq!(port.into_u16(), 8080);
}
#[test]
fn test_is_system_port() {
assert!(Port::HTTP.is_system_port());
assert!(Port::HTTPS.is_system_port());
assert!(Port::SSH.is_system_port());
assert!(Port::FTP.is_system_port());
assert!(Port::SMTP.is_system_port());
assert!(Port::DNS.is_system_port());
assert!(Port::POP3.is_system_port());
assert!(Port::IMAP.is_system_port());
assert!(Port::TELNET.is_system_port());
assert!(!Port::HTTP_ALT.is_system_port());
assert!(!Port::MYSQL.is_system_port());
}
#[test]
fn test_is_registered_port() {
assert!(Port::MYSQL.is_registered_port());
assert!(Port::POSTGRESQL.is_registered_port());
assert!(Port::REDIS.is_registered_port());
assert!(Port::HTTP_ALT.is_registered_port());
assert!(!Port::HTTP.is_registered_port());
assert!(!Port::new(50000).unwrap().is_registered_port());
}
#[test]
fn test_is_dynamic_port() {
assert!(Port::new(49152).unwrap().is_dynamic_port());
assert!(Port::new(50000).unwrap().is_dynamic_port());
assert!(Port::new(65535).unwrap().is_dynamic_port());
assert!(!Port::HTTP.is_dynamic_port());
assert!(!Port::MYSQL.is_dynamic_port());
}
#[test]
fn test_port_zero_error_message() {
let err = Port::new(0).unwrap_err();
assert_eq!(format!("{err}"), "port 0 is reserved and cannot be used");
}
#[test]
fn test_service_port_constants() {
assert_eq!(Port::HTTP.as_u16(), 80);
assert_eq!(Port::HTTPS.as_u16(), 443);
assert_eq!(Port::SSH.as_u16(), 22);
assert_eq!(Port::FTP.as_u16(), 21);
assert_eq!(Port::FTP_DATA.as_u16(), 20);
assert_eq!(Port::SMTP.as_u16(), 25);
assert_eq!(Port::DNS.as_u16(), 53);
assert_eq!(Port::DNS_OVER_TLS.as_u16(), 853);
assert_eq!(Port::POP3.as_u16(), 110);
assert_eq!(Port::POP3S.as_u16(), 995);
assert_eq!(Port::IMAP.as_u16(), 143);
assert_eq!(Port::IMAPS.as_u16(), 993);
assert_eq!(Port::TELNET.as_u16(), 23);
assert_eq!(Port::TELNETS.as_u16(), 992);
assert_eq!(Port::MYSQL.as_u16(), 3306);
assert_eq!(Port::POSTGRESQL.as_u16(), 5432);
assert_eq!(Port::REDIS.as_u16(), 6379);
assert_eq!(Port::MONGODB.as_u16(), 27017);
assert_eq!(Port::HTTP_ALT.as_u16(), 8080);
assert_eq!(Port::HTTPS_ALT.as_u16(), 8443);
}
#[test]
fn test_try_from_u16() {
assert_eq!(Port::try_from(80).unwrap(), Port::HTTP);
assert_eq!(Port::try_from(8080).unwrap().as_u16(), 8080);
}
#[test]
fn test_from_port_to_u16() {
let port = Port::HTTP;
let value: u16 = port.into();
assert_eq!(value, 80);
}
#[test]
fn test_from_str() {
assert_eq!("80".parse::<Port>().unwrap(), Port::HTTP);
assert_eq!("8080".parse::<Port>().unwrap().as_u16(), 8080);
assert_eq!("65535".parse::<Port>().unwrap().as_u16(), 65535);
assert!("70000".parse::<Port>().is_err());
assert!("abc".parse::<Port>().is_err());
}
#[test]
fn test_display() {
assert_eq!(format!("{}", Port::HTTP), "80");
assert_eq!(format!("{}", Port::HTTP_ALT), "8080");
}
#[test]
fn test_equality() {
assert_eq!(Port::HTTP, Port::HTTP);
assert_eq!(Port::new(8080).unwrap(), Port::new(8080).unwrap());
assert_ne!(Port::HTTP, Port::HTTPS);
}
#[test]
fn test_ordering() {
assert!(Port::HTTP < Port::HTTPS);
assert!(Port::HTTP < Port::HTTP_ALT);
assert!(Port::HTTPS < Port::HTTP_ALT);
}
#[test]
fn test_copy() {
let port = Port::HTTP;
let port2 = port;
assert_eq!(port, port2);
}
#[test]
fn test_clone() {
let port = Port::HTTP;
let port2 = port;
assert_eq!(port, port2);
}
}