internet 0.1.0

Network library for rust
Documentation
//! Transport Port encoding.
//!
//! Encoding is supported for the following structures:
//!
//!  - [`Port`]
//!

use std::fmt;
use std::num::ParseIntError;
use std::str::FromStr;

use crate::{Buf, BufMut, BufResult, Codec, Cursor};

/// A port number following [IANA service-names-port-numbers].
///
/// A 16-bit number used to identify a transport-layer endpoint on a host.
///
/// [IANA service-names-port-numbers]: https://www.iana.org/assignments/service-names-port-numbers
#[derive(Debug, Clone, Copy, PartialEq, Eq, PartialOrd, Ord, Hash)]
#[repr(transparent)]
pub struct Port(pub u16);

impl Port {
    /// Creates a new port from a u16 value.
    pub const fn new(port: u16) -> Self {
        Port(port)
    }

    /// Returns the port number as a u16.
    pub const fn as_u16(self) -> u16 {
        self.0
    }

    /// Returns true if this is a system port (0-1023).
    pub const fn is_system(self) -> bool {
        self.0 <= 1023
    }

    /// Returns true if this is a user port (1024-49151).
    pub const fn is_user(self) -> bool {
        self.0 >= 1024 && self.0 <= 49151
    }

    /// Returns true if this is a dynamic/private port (49152-65535).
    pub const fn is_dynamic(self) -> bool {
        self.0 >= 49152
    }
}

impl Default for Port {
    fn default() -> Self {
        Self(0)
    }
}

impl From<u16> for Port {
    #[inline]
    fn from(val: u16) -> Self {
        Port(val)
    }
}

impl From<Port> for u16 {
    #[inline]
    fn from(port: Port) -> Self {
        port.0
    }
}

impl fmt::Display for Port {
    fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
        write!(f, "{}", self.0)
    }
}

impl FromStr for Port {
    type Err = ParseIntError;
    fn from_str(s: &str) -> Result<Self, Self::Err> {
        s.parse::<u16>().map(Port)
    }
}

impl Codec for Port {
    fn encode<W: BufMut>(&self, writer: &mut Cursor<W>, _: ()) -> BufResult<()> {
        self.0.encode(writer, ())
    }

    fn decode<R: Buf>(reader: &mut Cursor<R>, _: ()) -> BufResult<Self> {
        Ok(Self(u16::decode(reader, ())?))
    }
}

#[cfg(test)]
mod tests {
    use core::fmt::Debug;

    use super::Port;
    use crate::{Codec, Cursor};

    fn codec_roundtrip<T: Codec<C> + Debug + Eq, C: Copy>(
        etalon_struct: T,
        etalon_bytes: &[u8],
        context: C,
    ) {
        let mut encoded_bytes = vec![];
        {
            let writer = &mut Cursor::new(&mut encoded_bytes);
            etalon_struct.encode(writer, context).unwrap();
        }
        assert_eq!(etalon_bytes, &encoded_bytes);

        let decoded_struct = {
            let reader = &mut Cursor::new(&mut encoded_bytes);
            T::decode(reader, context).unwrap()
        };
        assert_eq!(etalon_struct, decoded_struct);

        encoded_bytes.fill(0x00);
        {
            let writer = &mut Cursor::new(&mut encoded_bytes);
            decoded_struct.encode(writer, context).unwrap();
        }
        assert_eq!(etalon_bytes, &encoded_bytes);
    }

    #[test]
    fn port() {
        let etalon_bytes = &[0x00, 0x50]; // Port 80
        let etalon_struct = Port::new(80);

        codec_roundtrip(etalon_struct, etalon_bytes, ());
    }

    #[test]
    fn port_ranges() {
        let system_port = Port::new(80);
        assert!(system_port.is_system());
        assert!(!system_port.is_user());
        assert!(!system_port.is_dynamic());

        let user_port = Port::new(8080);
        assert!(!user_port.is_system());
        assert!(user_port.is_user());
        assert!(!user_port.is_dynamic());

        let dynamic_port = Port::new(50000);
        assert!(!dynamic_port.is_system());
        assert!(!dynamic_port.is_user());
        assert!(dynamic_port.is_dynamic());
    }
}