diff --git a/.travis.yml b/.travis.yml index b77e2d4..46de22e 100644 --- a/.travis.yml +++ b/.travis.yml @@ -1,8 +1,13 @@ language: rust -# TODO: put stable and beta back in once slice_pattern lands in stable rust: - # - stable - # - beta - - nightly +- stable +- beta +- nightly cache: cargo + +script: +- cargo test --verbose +- cargo build --features serde +- cargo test --features std +- cargo test --features unstable_ip diff --git a/Cargo.toml b/Cargo.toml index 535ce32..4ceeecb 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -2,10 +2,15 @@ name = "no-std-net" description = "Rust's std::net... without the 'std'." version = "0.5.0" -authors = [ "M@ Dunlap " ] -categories = [ "embedded", "network-programming", "no-std" ] +authors = ["M@ Dunlap "] +categories = [ + "embedded", + "network-programming", + "no-std", +] repository = "https://github.com/dunmatt/no-std-net" license = "MIT" +readme = "README.md" [badges] maintenance = { status = "actively-developed" } @@ -18,7 +23,9 @@ serde = { version = "^1", default-features = false, optional = true } serde_test = "^1" [features] -default = [ ] - +# Makes the library act as a facade to std::net types +std = [] +# Like `#![feature(ip)]`, see https://github.com/rust-lang/rust/issues/27709 +unstable_ip = [] # Deprecated. Does nothing. -i128 = [ ] +i128 = [] diff --git a/src/addr.rs b/src/addr.rs index 3e62ab7..6ef9f72 100644 --- a/src/addr.rs +++ b/src/addr.rs @@ -2,9 +2,18 @@ // They hold the copyright (http://rust-lang.org/COPYRIGHT) and whatever other rights, but this // crate is MIT licensed also, so it's all good. -use core::result::Result; -use core::{iter, option, slice}; -use {IpAddr, Ipv4Addr, Ipv6Addr}; +#[cfg(all(test, not(target_os = "emscripten")))] +mod tests; + +use core::cmp::Ordering; +use core::fmt::{self, Write}; +use core::hash; +use core::iter; +use core::option; +use core::slice; + +use super::helper::WriteHelper; +use super::{IpAddr, Ipv4Addr, Ipv6Addr}; /// An internet socket address, either IPv4 or IPv6. /// @@ -12,9 +21,7 @@ use {IpAddr, Ipv4Addr, Ipv6Addr}; /// as possibly some version-dependent additional information. See [`SocketAddrV4`]'s and /// [`SocketAddrV6`]'s respective documentation for more details. /// -/// [IP address]: ../../no-std-net/enum.IpAddr.html -/// [`SocketAddrV4`]: ../../no-std-net/struct.SocketAddrV4.html -/// [`SocketAddrV6`]: ../../no-std-net/struct.SocketAddrV6.html +/// [IP address]: IpAddr /// /// # Examples /// @@ -27,7 +34,7 @@ use {IpAddr, Ipv4Addr, Ipv6Addr}; /// assert_eq!(socket.port(), 8080); /// assert_eq!(socket.is_ipv4(), true); /// ``` -#[derive(Copy, Clone, PartialEq, Eq, Hash, Debug)] +#[derive(Copy, Clone, PartialEq, Eq, Hash, PartialOrd, Ord)] pub enum SocketAddr { /// An IPv4 socket address. V4(SocketAddrV4), @@ -35,10 +42,67 @@ pub enum SocketAddr { V6(SocketAddrV6), } +/// An IPv4 socket address. +/// +/// IPv4 socket addresses consist of an [`IPv4` address] and a 16-bit port number, as +/// stated in [IETF RFC 793]. +/// +/// See [`SocketAddr`] for a type encompassing both IPv4 and IPv6 socket addresses. +/// +/// [IETF RFC 793]: https://tools.ietf.org/html/rfc793 +/// [`IPv4` address]: Ipv4Addr +/// +/// # Examples +/// +/// ``` +/// use no_std_net::{Ipv4Addr, SocketAddrV4}; +/// +/// let socket = SocketAddrV4::new(Ipv4Addr::new(127, 0, 0, 1), 8080); +/// +/// assert_eq!("127.0.0.1:8080".parse(), Ok(socket)); +/// assert_eq!(socket.ip(), &Ipv4Addr::new(127, 0, 0, 1)); +/// assert_eq!(socket.port(), 8080); +/// ``` +#[derive(Copy)] +pub struct SocketAddrV4 { + addr: Ipv4Addr, + port: u16, +} + +/// An IPv6 socket address. +/// +/// IPv6 socket addresses consist of an [`IPv6` address], a 16-bit port number, as well +/// as fields containing the traffic class, the flow label, and a scope identifier +/// (see [IETF RFC 2553, Section 3.3] for more details). +/// +/// See [`SocketAddr`] for a type encompassing both IPv4 and IPv6 socket addresses. +/// +/// [IETF RFC 2553, Section 3.3]: https://tools.ietf.org/html/rfc2553#section-3.3 +/// [`IPv6` address]: Ipv6Addr +/// +/// # Examples +/// +/// ``` +/// use no_std_net::{Ipv6Addr, SocketAddrV6}; +/// +/// let socket = SocketAddrV6::new(Ipv6Addr::new(0x2001, 0xdb8, 0, 0, 0, 0, 0, 1), 8080, 0, 0); +/// +/// assert_eq!("[2001:db8::1]:8080".parse(), Ok(socket)); +/// assert_eq!(socket.ip(), &Ipv6Addr::new(0x2001, 0xdb8, 0, 0, 0, 0, 0, 1)); +/// assert_eq!(socket.port(), 8080); +/// ``` +#[derive(Copy)] +pub struct SocketAddrV6 { + addr: Ipv6Addr, + port: u16, + flow_info: u32, + scope_id: u32, +} + impl SocketAddr { /// Creates a new socket address from an [IP address] and a port number. /// - /// [IP address]: ../../no-std-net/enum.IpAddr.html + /// [IP address]: IpAddr /// /// # Examples /// @@ -129,91 +193,48 @@ impl SocketAddr { } /// Returns [`true`] if the [IP address] in this `SocketAddr` is an - /// [IPv4 address], and [`false`] otherwise. + /// [`IPv4` address], and [`false`] otherwise. /// - /// [`true`]: ../../std/primitive.bool.html - /// [`false`]: ../../std/primitive.bool.html - /// [IP address]: ../../no-std-net/enum.IpAddr.html - /// [IPv4 address]: ../../no-std-net/enum.IpAddr.html#variant.V4 + /// [IP address]: IpAddr + /// [`IPv4` address]: IpAddr::V4 /// /// # Examples /// /// ``` /// use no_std_net::{IpAddr, Ipv4Addr, SocketAddr}; /// - /// fn main() { - /// let socket = SocketAddr::new(IpAddr::V4(Ipv4Addr::new(127, 0, 0, 1)), 8080); - /// assert_eq!(socket.is_ipv4(), true); - /// assert_eq!(socket.is_ipv6(), false); - /// } + /// let socket = SocketAddr::new(IpAddr::V4(Ipv4Addr::new(127, 0, 0, 1)), 8080); + /// assert_eq!(socket.is_ipv4(), true); + /// assert_eq!(socket.is_ipv6(), false); /// ``` pub const fn is_ipv4(&self) -> bool { - match *self { - SocketAddr::V4(_) => true, - SocketAddr::V6(_) => false, - } + matches!(*self, SocketAddr::V4(_)) } /// Returns [`true`] if the [IP address] in this `SocketAddr` is an - /// [IPv6 address], and [`false`] otherwise. + /// [`IPv6` address], and [`false`] otherwise. /// - /// [`true`]: ../../std/primitive.bool.html - /// [`false`]: ../../std/primitive.bool.html - /// [IP address]: ../../no-std-net/enum.IpAddr.html - /// [IPv6 address]: ../../no-std-net/enum.IpAddr.html#variant.V6 + /// [IP address]: IpAddr + /// [`IPv6` address]: IpAddr::V6 /// /// # Examples /// /// ``` /// use no_std_net::{IpAddr, Ipv6Addr, SocketAddr}; /// - /// fn main() { - /// let socket = SocketAddr::new( - /// IpAddr::V6(Ipv6Addr::new(0, 0, 0, 0, 0, 65535, 0, 1)), 8080); - /// assert_eq!(socket.is_ipv4(), false); - /// assert_eq!(socket.is_ipv6(), true); - /// } + /// let socket = SocketAddr::new(IpAddr::V6(Ipv6Addr::new(0, 0, 0, 0, 0, 65535, 0, 1)), 8080); + /// assert_eq!(socket.is_ipv4(), false); + /// assert_eq!(socket.is_ipv6(), true); /// ``` pub const fn is_ipv6(&self) -> bool { - match *self { - SocketAddr::V4(_) => false, - SocketAddr::V6(_) => true, - } + matches!(*self, SocketAddr::V6(_)) } } -/// An IPv4 socket address. -/// -/// IPv4 socket addresses consist of an [IPv4 address] and a 16-bit port number, as -/// stated in [IETF RFC 793]. -/// -/// See [`SocketAddr`] for a type encompassing both IPv4 and IPv6 socket addresses. -/// -/// [IETF RFC 793]: https://tools.ietf.org/html/rfc793 -/// [IPv4 address]: ../../no-std-net/struct.Ipv4Addr.html -/// [`SocketAddr`]: ../../no-std-net/enum.SocketAddr.html -/// -/// # Examples -/// -/// ``` -/// use no_std_net::{Ipv4Addr, SocketAddrV4}; -/// -/// let socket = SocketAddrV4::new(Ipv4Addr::new(127, 0, 0, 1), 8080); -/// -/// assert_eq!("127.0.0.1:8080".parse(), Ok(socket)); -/// assert_eq!(socket.ip(), &Ipv4Addr::new(127, 0, 0, 1)); -/// assert_eq!(socket.port(), 8080); -/// ``` -#[derive(Copy, Clone, PartialEq, Eq, Hash)] -pub struct SocketAddrV4 { - addr: Ipv4Addr, - port: u16, -} - impl SocketAddrV4 { - /// Creates a new socket address from an [IPv4 address] and a port number. + /// Creates a new socket address from an [`IPv4` address] and a port number. /// - /// [IPv4 address]: ../../no-std-net/struct.Ipv4Addr.html + /// [`IPv4` address]: Ipv4Addr /// /// # Examples /// @@ -288,46 +309,15 @@ impl SocketAddrV4 { } } -/// An IPv6 socket address. -/// -/// IPv6 socket addresses consist of an [Ipv6 address], a 16-bit port number, as well -/// as fields containing the traffic class, the flow label, and a scope identifier -/// (see [IETF RFC 2553, Section 3.3] for more details). -/// -/// See [`SocketAddr`] for a type encompassing both IPv4 and IPv6 socket addresses. -/// -/// [IETF RFC 2553, Section 3.3]: https://tools.ietf.org/html/rfc2553#section-3.3 -/// [IPv6 address]: ../../no-std-net/struct.Ipv6Addr.html -/// [`SocketAddr`]: ../../no-std-net/enum.SocketAddr.html -/// -/// # Examples -/// -/// ``` -/// use no_std_net::{Ipv6Addr, SocketAddrV6}; -/// -/// let socket = SocketAddrV6::new(Ipv6Addr::new(0x2001, 0xdb8, 0, 0, 0, 0, 0, 1), 8080, 0, 0); -/// -/// assert_eq!("[2001:db8::1]:8080".parse(), Ok(socket)); -/// assert_eq!(socket.ip(), &Ipv6Addr::new(0x2001, 0xdb8, 0, 0, 0, 0, 0, 1)); -/// assert_eq!(socket.port(), 8080); -/// ``` -#[derive(Copy, Clone, PartialEq, Eq, Hash)] -pub struct SocketAddrV6 { - addr: Ipv6Addr, - port: u16, - flow_info: u32, - scope_id: u32, -} - impl SocketAddrV6 { - /// Creates a new socket address from an [IPv6 address], a 16-bit port number, + /// Creates a new socket address from an [`IPv6` address], a 16-bit port number, /// and the `flowinfo` and `scope_id` fields. /// /// For more information on the meaning and layout of the `flowinfo` and `scope_id` /// parameters, see [IETF RFC 2553, Section 3.3]. /// /// [IETF RFC 2553, Section 3.3]: https://tools.ietf.org/html/rfc2553#section-3.3 - /// [IPv6 address]: ../../no-std-net/struct.Ipv6Addr.html + /// [`IPv6` address]: Ipv6Addr /// /// # Examples /// @@ -429,9 +419,7 @@ impl SocketAddrV6 { /// Changes the flow information associated with this socket address. /// - /// See the [`flowinfo`] method's documentation for more details. - /// - /// [`flowinfo`]: #method.flowinfo + /// See [`SocketAddrV6::flowinfo`]'s documentation for more details. /// /// # Examples /// @@ -465,11 +453,9 @@ impl SocketAddrV6 { self.scope_id } - /// Change the scope ID associated with this socket address. + /// Changes the scope ID associated with this socket address. /// - /// See the [`scope_id`] method's documentation for more details. - /// - /// [`scope_id`]: #method.scope_id + /// See [`SocketAddrV6::scope_id`]'s documentation for more details. /// /// # Examples /// @@ -486,25 +472,33 @@ impl SocketAddrV6 { } impl From for SocketAddr { + /// Converts a [`SocketAddrV4`] into a [`SocketAddr::V4`]. fn from(sock4: SocketAddrV4) -> SocketAddr { SocketAddr::V4(sock4) } } impl From for SocketAddr { + /// Converts a [`SocketAddrV6`] into a [`SocketAddr::V6`]. fn from(sock6: SocketAddrV6) -> SocketAddr { SocketAddr::V6(sock6) } } impl> From<(I, u16)> for SocketAddr { + /// Converts a tuple struct (Into<[`IpAddr`]>, `u16`) into a [`SocketAddr`]. + /// + /// This conversion creates a [`SocketAddr::V4`] for a [`IpAddr::V4`] + /// and creates a [`SocketAddr::V6`] for a [`IpAddr::V6`]. + /// + /// `u16` is treated as port of the newly created [`SocketAddr`]. fn from(pieces: (I, u16)) -> SocketAddr { SocketAddr::new(pieces.0.into(), pieces.1) } } -impl ::fmt::Display for SocketAddr { - fn fmt(&self, f: &mut ::fmt::Formatter) -> ::fmt::Result { +impl fmt::Display for SocketAddr { + fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { match *self { SocketAddr::V4(ref a) => a.fmt(f), SocketAddr::V6(ref a) => a.fmt(f), @@ -512,27 +506,147 @@ impl ::fmt::Display for SocketAddr { } } -impl ::fmt::Display for SocketAddrV4 { - fn fmt(&self, f: &mut ::fmt::Formatter) -> ::fmt::Result { - write!(f, "{}:{}", self.ip(), self.port()) +impl fmt::Debug for SocketAddr { + fn fmt(&self, fmt: &mut fmt::Formatter<'_>) -> fmt::Result { + fmt::Display::fmt(self, fmt) + } +} + +impl fmt::Display for SocketAddrV4 { + fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { + // Fast path: if there's no alignment stuff, write to the output buffer + // directly + if f.precision().is_none() && f.width().is_none() { + write!(f, "{}:{}", self.ip(), self.port()) + } else { + const IPV4_SOCKET_BUF_LEN: usize = (3 * 4) // the segments + + 3 // the separators + + 1 + 5; // the port + let mut buf = [0; IPV4_SOCKET_BUF_LEN]; + let mut buf_slice = WriteHelper::new(&mut buf[..]); + + // Unwrap is fine because writing to a sufficiently-sized + // buffer is infallible + write!(buf_slice, "{}:{}", self.ip(), self.port()).unwrap(); + let len = IPV4_SOCKET_BUF_LEN - buf_slice.into_raw().len(); + + // This unsafe is OK because we know what is being written to the buffer + let buf = unsafe { core::str::from_utf8_unchecked(&buf[..len]) }; + f.pad(buf) + } } } -impl ::fmt::Debug for SocketAddrV4 { - fn fmt(&self, fmt: &mut ::fmt::Formatter) -> ::fmt::Result { - ::fmt::Display::fmt(self, fmt) +impl fmt::Debug for SocketAddrV4 { + fn fmt(&self, fmt: &mut fmt::Formatter<'_>) -> fmt::Result { + fmt::Display::fmt(self, fmt) } } -impl ::fmt::Display for SocketAddrV6 { - fn fmt(&self, f: &mut ::fmt::Formatter) -> ::fmt::Result { - write!(f, "[{}]:{}", self.ip(), self.port()) +impl fmt::Display for SocketAddrV6 { + fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { + // Fast path: if there's no alignment stuff, write to the output + // buffer directly + if f.precision().is_none() && f.width().is_none() { + match self.scope_id() { + 0 => write!(f, "[{}]:{}", self.ip(), self.port()), + scope_id => write!(f, "[{}%{}]:{}", self.ip(), scope_id, self.port()), + } + } else { + const IPV6_SOCKET_BUF_LEN: usize = (4 * 8) // The address + + 7 // The colon separators + + 2 // The brackets + + 1 + 10 // The scope id + + 1 + 5; // The port + + let mut buf = [0; IPV6_SOCKET_BUF_LEN]; + let mut buf_slice = WriteHelper::new(&mut buf[..]); + + match self.scope_id() { + 0 => write!(buf_slice, "[{}]:{}", self.ip(), self.port()), + scope_id => write!(buf_slice, "[{}%{}]:{}", self.ip(), scope_id, self.port()), + } + // Unwrap is fine because writing to a sufficiently-sized + // buffer is infallible + .unwrap(); + let len = IPV6_SOCKET_BUF_LEN - buf_slice.into_raw().len(); + + // This unsafe is OK because we know what is being written to the buffer + let buf = unsafe { core::str::from_utf8_unchecked(&buf[..len]) }; + f.pad(buf) + } } } -impl ::fmt::Debug for SocketAddrV6 { - fn fmt(&self, fmt: &mut ::fmt::Formatter) -> ::fmt::Result { - ::fmt::Display::fmt(self, fmt) +impl fmt::Debug for SocketAddrV6 { + fn fmt(&self, fmt: &mut fmt::Formatter<'_>) -> fmt::Result { + fmt::Display::fmt(self, fmt) + } +} + +impl Clone for SocketAddrV4 { + fn clone(&self) -> SocketAddrV4 { + *self + } +} +impl Clone for SocketAddrV6 { + fn clone(&self) -> SocketAddrV6 { + *self + } +} + +impl PartialEq for SocketAddrV4 { + fn eq(&self, other: &SocketAddrV4) -> bool { + self.port == other.port && self.addr == other.addr + } +} +impl PartialEq for SocketAddrV6 { + fn eq(&self, other: &SocketAddrV6) -> bool { + self.port == other.port + && self.addr == other.addr + && self.flow_info == other.flow_info + && self.scope_id == other.scope_id + } +} +impl Eq for SocketAddrV4 {} +impl Eq for SocketAddrV6 {} + +impl PartialOrd for SocketAddrV4 { + fn partial_cmp(&self, other: &SocketAddrV4) -> Option { + Some(self.cmp(other)) + } +} + +impl PartialOrd for SocketAddrV6 { + fn partial_cmp(&self, other: &SocketAddrV6) -> Option { + Some(self.cmp(other)) + } +} + +impl Ord for SocketAddrV4 { + fn cmp(&self, other: &SocketAddrV4) -> Ordering { + self.ip() + .cmp(other.ip()) + .then(self.port().cmp(&other.port())) + } +} + +impl Ord for SocketAddrV6 { + fn cmp(&self, other: &SocketAddrV6) -> Ordering { + self.ip() + .cmp(other.ip()) + .then(self.port().cmp(&other.port())) + } +} + +impl hash::Hash for SocketAddrV4 { + fn hash(&self, s: &mut H) { + (self.port, self.addr).hash(s) + } +} +impl hash::Hash for SocketAddrV6 { + fn hash(&self, s: &mut H) { + (self.port, self.addr, self.flow_info, self.scope_id).hash(s) } } @@ -540,7 +654,7 @@ impl ::fmt::Debug for SocketAddrV6 { /// [`SocketAddr`] values. /// /// This trait is used for generic address resolution when constructing network -/// objects. By default it is implemented for the following types: +/// objects. By default it is implemented for the following types: /// /// * [`SocketAddr`]: [`to_socket_addrs`] is the identity function. /// @@ -548,9 +662,9 @@ impl ::fmt::Debug for SocketAddrV6 { /// `(`[`Ipv4Addr`]`, `[`u16`]`)`, `(`[`Ipv6Addr`]`, `[`u16`]`)`: /// [`to_socket_addrs`] constructs a [`SocketAddr`] trivially. /// -/// * `(`[`&str`]`, `[`u16`]`)`: the string should be either a string representation +/// * `(`[`&str`]`, `[`u16`]`)`: [`&str`] should be either a string representation /// of an [`IpAddr`] address as expected by [`FromStr`] implementation or a host -/// name. +/// name. [`u16`] is the port number. /// /// * [`&str`]: the string should be either a string representation of a /// [`SocketAddr`] as expected by its [`FromStr`] implementation or a string like @@ -562,27 +676,58 @@ impl ::fmt::Debug for SocketAddrV6 { /// the other: for simple uses a string like `"localhost:12345"` is much nicer /// than manual construction of the corresponding [`SocketAddr`], but sometimes /// [`SocketAddr`] value is *the* main source of the address, and converting it to -/// some other type (e.g. a string) just for it to be converted back to +/// some other type (e.g., a string) just for it to be converted back to /// [`SocketAddr`] in constructor methods is pointless. /// /// Addresses returned by the operating system that are not IP addresses are /// silently ignored. /// -/// [`FromStr`]: ../../std/str/trait.FromStr.html -/// [`IpAddr`]: ../../no-std-net/enum.IpAddr.html -/// [`Ipv4Addr`]: ../../no-std-net/struct.Ipv4Addr.html -/// [`Ipv6Addr`]: ../../no-std-net/struct.Ipv6Addr.html -/// [`SocketAddr`]: ../../no-std-net/enum.SocketAddr.html -/// [`SocketAddrV4`]: ../../no-std-net/struct.SocketAddrV4.html -/// [`SocketAddrV6`]: ../../no-std-net/struct.SocketAddrV6.html -/// [`&str`]: ../../std/primitive.str.html -/// [`TcpStream`]: ../../no-std-net/struct.TcpStream.html -/// [`to_socket_addrs`]: #tymethod.to_socket_addrs -/// [`UdpSocket`]: ../../no-std-net/struct.UdpSocket.html -/// [`u16`]: ../../std/primitive.u16.html +/// [`FromStr`]: core::str::FromStr +/// [`&str`]: str +#[cfg_attr(feature = "std", doc = "[`TcpStream`]: std::net::TcpStream")] +#[cfg_attr( + not(feature = "std"), + doc = "[`TcpStream`]: https://doc.rust-lang.org/std/net/struct.TcpStream.html" +)] +/// [`to_socket_addrs`]: ToSocketAddrs::to_socket_addrs +#[cfg_attr(feature = "std", doc = "[`UdpSocket`]: std::net::UdpSocket")] +#[cfg_attr( + not(feature = "std"), + doc = "[`UdpSocket`]: https://doc.rust-lang.org/std/net/struct.UdpSocket.html" +)] +/// +/// # Examples +/// +/// Creating a [`SocketAddr`] iterator that yields one item: +/// +/// ``` +/// use no_std_net::{ToSocketAddrs, SocketAddr}; /// +/// let addr = SocketAddr::from(([127, 0, 0, 1], 443)); +/// let mut addrs_iter = addr.to_socket_addrs().unwrap(); +/// +/// assert_eq!(Some(addr), addrs_iter.next()); +/// assert!(addrs_iter.next().is_none()); +/// ``` +/// +/// Creating a [`SocketAddr`] iterator that yields multiple items: +/// +/// ``` +/// use no_std_net::{SocketAddr, ToSocketAddrs}; +/// +/// let addr1 = SocketAddr::from(([0, 0, 0, 0], 80)); +/// let addr2 = SocketAddr::from(([127, 0, 0, 1], 443)); +/// let addrs = vec![addr1, addr2]; +/// +/// let mut addrs_iter = (&addrs[..]).to_socket_addrs().unwrap(); +/// +/// assert_eq!(Some(addr1), addrs_iter.next()); +/// assert_eq!(Some(addr2), addrs_iter.next()); +/// assert!(addrs_iter.next().is_none()); +/// ``` pub trait ToSocketAddrs { - /// Returned iterator over socket addresses which this type may correspond to. + /// Returned iterator over socket addresses which this type may correspond + /// to. type Iter: Iterator; /// Converts this object to an iterator of resolved `SocketAddr`s. @@ -590,11 +735,13 @@ pub trait ToSocketAddrs { /// The returned iterator may not actually yield any values depending on the /// outcome of any resolution performed. /// - /// Note that this function may block the current thread while resolution is performed. + /// Note that this function may block the current thread while resolution is + /// performed. fn to_socket_addrs(&self) -> Result; } /// This is a placeholder for the core::result::Result type parameter, it is unused. +#[derive(Debug)] pub enum ToSocketAddrError {} impl ToSocketAddrs for SocketAddr { diff --git a/src/addr/tests.rs b/src/addr/tests.rs new file mode 100644 index 0000000..da1c1d1 --- /dev/null +++ b/src/addr/tests.rs @@ -0,0 +1,179 @@ +use crate::test::tsa; +use crate::*; + +#[test] +fn to_socket_addr_ipaddr_u16() { + let a = Ipv4Addr::new(77, 88, 21, 11); + let p = 12345; + let e = SocketAddr::V4(SocketAddrV4::new(a, p)); + assert_eq!(Ok(vec![e]), tsa((a, p))); +} + +#[test] +fn set_ip() { + fn ip4(low: u8) -> Ipv4Addr { + Ipv4Addr::new(77, 88, 21, low) + } + fn ip6(low: u16) -> Ipv6Addr { + Ipv6Addr::new(0x2a02, 0x6b8, 0, 1, 0, 0, 0, low) + } + + let mut v4 = SocketAddrV4::new(ip4(11), 80); + assert_eq!(v4.ip(), &ip4(11)); + v4.set_ip(ip4(12)); + assert_eq!(v4.ip(), &ip4(12)); + + let mut addr = SocketAddr::V4(v4); + assert_eq!(addr.ip(), IpAddr::V4(ip4(12))); + addr.set_ip(IpAddr::V4(ip4(13))); + assert_eq!(addr.ip(), IpAddr::V4(ip4(13))); + addr.set_ip(IpAddr::V6(ip6(14))); + assert_eq!(addr.ip(), IpAddr::V6(ip6(14))); + + let mut v6 = SocketAddrV6::new(ip6(1), 80, 0, 0); + assert_eq!(v6.ip(), &ip6(1)); + v6.set_ip(ip6(2)); + assert_eq!(v6.ip(), &ip6(2)); + + let mut addr = SocketAddr::V6(v6); + assert_eq!(addr.ip(), IpAddr::V6(ip6(2))); + addr.set_ip(IpAddr::V6(ip6(3))); + assert_eq!(addr.ip(), IpAddr::V6(ip6(3))); + addr.set_ip(IpAddr::V4(ip4(4))); + assert_eq!(addr.ip(), IpAddr::V4(ip4(4))); +} + +#[test] +fn set_port() { + let mut v4 = SocketAddrV4::new(Ipv4Addr::new(77, 88, 21, 11), 80); + assert_eq!(v4.port(), 80); + v4.set_port(443); + assert_eq!(v4.port(), 443); + + let mut addr = SocketAddr::V4(v4); + assert_eq!(addr.port(), 443); + addr.set_port(8080); + assert_eq!(addr.port(), 8080); + + let mut v6 = SocketAddrV6::new(Ipv6Addr::new(0x2a02, 0x6b8, 0, 1, 0, 0, 0, 1), 80, 0, 0); + assert_eq!(v6.port(), 80); + v6.set_port(443); + assert_eq!(v6.port(), 443); + + let mut addr = SocketAddr::V6(v6); + assert_eq!(addr.port(), 443); + addr.set_port(8080); + assert_eq!(addr.port(), 8080); +} + +#[test] +fn set_flowinfo() { + let mut v6 = SocketAddrV6::new(Ipv6Addr::new(0x2a02, 0x6b8, 0, 1, 0, 0, 0, 1), 80, 10, 0); + assert_eq!(v6.flowinfo(), 10); + v6.set_flowinfo(20); + assert_eq!(v6.flowinfo(), 20); +} + +#[test] +fn set_scope_id() { + let mut v6 = SocketAddrV6::new(Ipv6Addr::new(0x2a02, 0x6b8, 0, 1, 0, 0, 0, 1), 80, 0, 10); + assert_eq!(v6.scope_id(), 10); + v6.set_scope_id(20); + assert_eq!(v6.scope_id(), 20); +} + +#[test] +fn is_v4() { + let v4 = SocketAddr::V4(SocketAddrV4::new(Ipv4Addr::new(77, 88, 21, 11), 80)); + assert!(v4.is_ipv4()); + assert!(!v4.is_ipv6()); +} + +#[test] +fn is_v6() { + let v6 = SocketAddr::V6(SocketAddrV6::new( + Ipv6Addr::new(0x2a02, 0x6b8, 0, 1, 0, 0, 0, 1), + 80, + 10, + 0, + )); + assert!(!v6.is_ipv4()); + assert!(v6.is_ipv6()); +} + +#[test] +fn socket_v4_to_str() { + let socket = SocketAddrV4::new(Ipv4Addr::new(192, 168, 0, 1), 8080); + + assert_eq!(format!("{}", socket), "192.168.0.1:8080"); + assert_eq!(format!("{:<20}", socket), "192.168.0.1:8080 "); + assert_eq!(format!("{:>20}", socket), " 192.168.0.1:8080"); + assert_eq!(format!("{:^20}", socket), " 192.168.0.1:8080 "); + assert_eq!(format!("{:.10}", socket), "192.168.0."); +} + +#[test] +fn socket_v6_to_str() { + let mut socket = SocketAddrV6::new(Ipv6Addr::new(0x2a02, 0x6b8, 0, 1, 0, 0, 0, 1), 53, 0, 0); + + assert_eq!(format!("{}", socket), "[2a02:6b8:0:1::1]:53"); + assert_eq!(format!("{:<24}", socket), "[2a02:6b8:0:1::1]:53 "); + assert_eq!(format!("{:>24}", socket), " [2a02:6b8:0:1::1]:53"); + assert_eq!(format!("{:^24}", socket), " [2a02:6b8:0:1::1]:53 "); + assert_eq!(format!("{:.15}", socket), "[2a02:6b8:0:1::"); + + socket.set_scope_id(5); + + assert_eq!(format!("{}", socket), "[2a02:6b8:0:1::1%5]:53"); + assert_eq!(format!("{:<24}", socket), "[2a02:6b8:0:1::1%5]:53 "); + assert_eq!(format!("{:>24}", socket), " [2a02:6b8:0:1::1%5]:53"); + assert_eq!(format!("{:^24}", socket), " [2a02:6b8:0:1::1%5]:53 "); + assert_eq!(format!("{:.18}", socket), "[2a02:6b8:0:1::1%5"); +} + +#[test] +fn compare() { + let v4_1 = "224.120.45.1:23456".parse::().unwrap(); + let v4_2 = "224.210.103.5:12345".parse::().unwrap(); + let v4_3 = "224.210.103.5:23456".parse::().unwrap(); + let v6_1 = "[2001:db8:f00::1002]:23456" + .parse::() + .unwrap(); + let v6_2 = "[2001:db8:f00::2001]:12345" + .parse::() + .unwrap(); + let v6_3 = "[2001:db8:f00::2001]:23456" + .parse::() + .unwrap(); + + // equality + assert_eq!(v4_1, v4_1); + assert_eq!(v6_1, v6_1); + assert_eq!(SocketAddr::V4(v4_1), SocketAddr::V4(v4_1)); + assert_eq!(SocketAddr::V6(v6_1), SocketAddr::V6(v6_1)); + assert!(v4_1 != v4_2); + assert!(v6_1 != v6_2); + + // compare different addresses + assert!(v4_1 < v4_2); + assert!(v6_1 < v6_2); + assert!(v4_2 > v4_1); + assert!(v6_2 > v6_1); + + // compare the same address with different ports + assert!(v4_2 < v4_3); + assert!(v6_2 < v6_3); + assert!(v4_3 > v4_2); + assert!(v6_3 > v6_2); + + // compare different addresses with the same port + assert!(v4_1 < v4_3); + assert!(v6_1 < v6_3); + assert!(v4_3 > v4_1); + assert!(v6_3 > v6_1); + + // compare with an inferred right-hand side + assert_eq!(v4_1, "224.120.45.1:23456".parse().unwrap()); + assert_eq!(v6_1, "[2001:db8:f00::1002]:23456".parse().unwrap()); + assert_eq!(SocketAddr::V4(v4_1), "224.120.45.1:23456".parse().unwrap()); +} diff --git a/src/helper.rs b/src/helper.rs new file mode 100644 index 0000000..eff1b74 --- /dev/null +++ b/src/helper.rs @@ -0,0 +1,29 @@ +use core::cmp; +use core::fmt::{self, Write}; +use core::mem; + +/// A partial reimplementation of `impl std::io::Write for &mut [u8]`. +/// +/// There are probably simpler ways to do this, this was the first thing that +/// came to mind. +pub(crate) struct WriteHelper<'a>(&'a mut [u8]); + +impl<'a> WriteHelper<'a> { + pub(crate) fn new(inner: &'a mut [u8]) -> Self { + Self(inner) + } + + pub(crate) fn into_raw(self) -> &'a mut [u8] { + self.0 + } +} + +impl<'a> Write for WriteHelper<'a> { + fn write_str(&mut self, data: &str) -> fmt::Result { + let amt = cmp::min(data.len(), self.0.len()); + let (a, b) = mem::replace(&mut self.0, &mut []).split_at_mut(amt); + a.copy_from_slice(&data[..amt].as_bytes()); + self.0 = b; + Ok(()) + } +} diff --git a/src/ip.rs b/src/ip.rs index 649cd70..96d76b6 100644 --- a/src/ip.rs +++ b/src/ip.rs @@ -2,19 +2,21 @@ // They hold the copyright (http://rust-lang.org/COPYRIGHT) and whatever other rights, but this // crate is MIT licensed also, so it's all good. +// Tests for this module +#[cfg(all(test, not(target_os = "emscripten")))] +mod tests; + use core::cmp::Ordering; +use core::fmt::{self, Write}; +use core::hash; -// TODO: copy the parsers over from https://github.com/rust-lang/rust/blob/master/src/libstd/net/parser.rs -// and update all the tests +use super::helper::WriteHelper; /// An IP address, either IPv4 or IPv6. /// /// This enum can contain either an [`Ipv4Addr`] or an [`Ipv6Addr`], see their /// respective documentation for more details. /// -/// [`Ipv4Addr`]: ../../no-std-net/struct.Ipv4Addr.html -/// [`Ipv6Addr`]: ../../no-std-net/struct.Ipv6Addr.html -/// /// # Examples /// /// ``` @@ -29,7 +31,7 @@ use core::cmp::Ordering; /// assert_eq!(localhost_v4.is_ipv6(), false); /// assert_eq!(localhost_v4.is_ipv4(), true); /// ``` -#[derive(Copy, Clone, Eq, PartialEq, Debug, Hash, PartialOrd, Ord)] +#[derive(Copy, Clone, Eq, PartialEq, Hash, PartialOrd, Ord)] pub enum IpAddr { /// An IPv4 address. V4(Ipv4Addr), @@ -45,14 +47,15 @@ pub enum IpAddr { /// See [`IpAddr`] for a type encompassing both IPv4 and IPv6 addresses. /// /// [IETF RFC 791]: https://tools.ietf.org/html/rfc791 -/// [`IpAddr`]: ../../no-std-net/enum.IpAddr.html /// /// # Textual representation /// /// `Ipv4Addr` provides a [`FromStr`] implementation. The four octets are in decimal /// notation, divided by `.` (this is called "dot-decimal notation"). +/// Notably, octal numbers and hexadecimal numbers are not allowed per [IETF RFC 6943]. /// -/// [`FromStr`]: https://doc.rust-lang.org/core/str/trait.FromStr.html +/// [IETF RFC 6943]: https://tools.ietf.org/html/rfc6943#section-3.1.1 +/// [`FromStr`]: core::str::FromStr /// /// # Examples /// @@ -63,12 +66,11 @@ pub enum IpAddr { /// assert_eq!("127.0.0.1".parse(), Ok(localhost)); /// assert_eq!(localhost.is_loopback(), true); /// ``` -#[derive(Clone, Copy, Eq, PartialEq, Hash, PartialOrd, Ord)] +#[derive(Copy)] pub struct Ipv4Addr { // Octets stored in transmit order. inner: [u8; 4], } -// TODO: clean up all the links in the documentation! /// An IPv6 address. /// @@ -78,7 +80,6 @@ pub struct Ipv4Addr { /// See [`IpAddr`] for a type encompassing both IPv4 and IPv6 addresses. /// /// [IETF RFC 4291]: https://tools.ietf.org/html/rfc4291 -/// [`IpAddr`]: ../../no-std-net/enum.IpAddr.html /// /// # Textual representation /// @@ -87,7 +88,7 @@ pub struct Ipv4Addr { /// notation, and segments are separated by `:`. For more information, see /// [IETF RFC 5952]. /// -/// [`FromStr`]: https://doc.rust-lang.org/core/str/trait.FromStr.html +/// [`FromStr`]: core::str::FromStr /// [IETF RFC 5952]: https://tools.ietf.org/html/rfc5952 /// /// # Examples @@ -99,7 +100,7 @@ pub struct Ipv4Addr { /// assert_eq!("::1".parse(), Ok(localhost)); /// assert_eq!(localhost.is_loopback(), true); /// ``` -#[derive(Clone, Copy, Eq, PartialEq, Hash, PartialOrd, Ord)] +#[derive(Copy)] pub struct Ipv6Addr { // Octets stored in transmit order. inner: [u8; 16], @@ -107,6 +108,7 @@ pub struct Ipv6Addr { #[allow(missing_docs)] #[derive(Copy, PartialEq, Eq, Clone, Hash, Debug)] +#[cfg(feature = "unstable_ip")] pub enum Ipv6MulticastScope { InterfaceLocal, LinkLocal, @@ -120,12 +122,8 @@ pub enum Ipv6MulticastScope { impl IpAddr { /// Returns [`true`] for the special 'unspecified' address. /// - /// See the documentation for [`Ipv4Addr::is_unspecified`][IPv4] and - /// [`Ipv6Addr::is_unspecified`][IPv6] for more details. - /// - /// [IPv4]: ../../no-std-net/struct.Ipv4Addr.html#method.is_unspecified - /// [IPv6]: ../../no-std-net/struct.Ipv6Addr.html#method.is_unspecified - /// [`true`]: https://doc.rust-lang.org/std/primitive.bool.html + /// See the documentation for [`Ipv4Addr::is_unspecified()`] and + /// [`Ipv6Addr::is_unspecified()`] for more details. /// /// # Examples /// @@ -135,21 +133,18 @@ impl IpAddr { /// assert_eq!(IpAddr::V4(Ipv4Addr::new(0, 0, 0, 0)).is_unspecified(), true); /// assert_eq!(IpAddr::V6(Ipv6Addr::new(0, 0, 0, 0, 0, 0, 0, 0)).is_unspecified(), true); /// ``` + #[inline] pub const fn is_unspecified(&self) -> bool { - match *self { - IpAddr::V4(ref a) => a.is_unspecified(), - IpAddr::V6(ref a) => a.is_unspecified(), + match self { + IpAddr::V4(ip) => ip.is_unspecified(), + IpAddr::V6(ip) => ip.is_unspecified(), } } /// Returns [`true`] if this is a loopback address. /// - /// See the documentation for [`Ipv4Addr::is_loopback`][IPv4] and - /// [`Ipv6Addr::is_loopback`][IPv6] for more details. - /// - /// [IPv4]: ../../no-std-net/struct.Ipv4Addr.html#method.is_loopback - /// [IPv6]: ../../no-std-net/struct.Ipv6Addr.html#method.is_loopback - /// [`true`]: https://doc.rust-lang.org/std/primitive.bool.html + /// See the documentation for [`Ipv4Addr::is_loopback()`] and + /// [`Ipv6Addr::is_loopback()`] for more details. /// /// # Examples /// @@ -159,48 +154,42 @@ impl IpAddr { /// assert_eq!(IpAddr::V4(Ipv4Addr::new(127, 0, 0, 1)).is_loopback(), true); /// assert_eq!(IpAddr::V6(Ipv6Addr::new(0, 0, 0, 0, 0, 0, 0, 0x1)).is_loopback(), true); /// ``` + #[inline] pub const fn is_loopback(&self) -> bool { - match *self { - IpAddr::V4(ref a) => a.is_loopback(), - IpAddr::V6(ref a) => a.is_loopback(), + match self { + IpAddr::V4(ip) => ip.is_loopback(), + IpAddr::V6(ip) => ip.is_loopback(), } } /// Returns [`true`] if the address appears to be globally routable. /// - /// See the documentation for [`Ipv4Addr::is_global`][IPv4] and - /// [`Ipv6Addr::is_global`][IPv6] for more details. - /// - /// [IPv4]: ../../no-std-net/struct.Ipv4Addr.html#method.is_global - /// [IPv6]: ../../no-std-net/struct.Ipv6Addr.html#method.is_global - /// [`true`]: https://doc.rust-lang.org/std/primitive.bool.html + /// See the documentation for [`Ipv4Addr::is_global()`] and + /// [`Ipv6Addr::is_global()`] for more details. /// /// # Examples /// /// ``` + /// // Requires `unstable_ip` feature + /// /// use no_std_net::{IpAddr, Ipv4Addr, Ipv6Addr}; /// - /// fn main() { - /// assert_eq!(IpAddr::V4(Ipv4Addr::new(80, 9, 12, 3)).is_global(), true); - /// assert_eq!(IpAddr::V6(Ipv6Addr::new(0, 0, 0x1c9, 0, 0, 0xafc8, 0, 0x1)).is_global(), - /// true); - /// } + /// assert_eq!(IpAddr::V4(Ipv4Addr::new(80, 9, 12, 3)).is_global(), true); + /// assert_eq!(IpAddr::V6(Ipv6Addr::new(0, 0, 0x1c9, 0, 0, 0xafc8, 0, 0x1)).is_global(), true); /// ``` + #[cfg(feature = "unstable_ip")] + #[inline] pub const fn is_global(&self) -> bool { - match *self { - IpAddr::V4(ref a) => a.is_global(), - IpAddr::V6(ref a) => a.is_global(), + match self { + IpAddr::V4(ip) => ip.is_global(), + IpAddr::V6(ip) => ip.is_global(), } } /// Returns [`true`] if this is a multicast address. /// - /// See the documentation for [`Ipv4Addr::is_multicast`][IPv4] and - /// [`Ipv6Addr::is_multicast`][IPv6] for more details. - /// - /// [IPv4]: ../../no-std-net/struct.Ipv4Addr.html#method.is_multicast - /// [IPv6]: ../../no-std-net/struct.Ipv6Addr.html#method.is_multicast - /// [`true`]: https://doc.rust-lang.org/std/primitive.bool.html + /// See the documentation for [`Ipv4Addr::is_multicast()`] and + /// [`Ipv6Addr::is_multicast()`] for more details. /// /// # Examples /// @@ -210,276 +199,75 @@ impl IpAddr { /// assert_eq!(IpAddr::V4(Ipv4Addr::new(224, 254, 0, 0)).is_multicast(), true); /// assert_eq!(IpAddr::V6(Ipv6Addr::new(0xff00, 0, 0, 0, 0, 0, 0, 0)).is_multicast(), true); /// ``` + #[inline] pub const fn is_multicast(&self) -> bool { - match *self { - IpAddr::V4(ref a) => a.is_multicast(), - IpAddr::V6(ref a) => a.is_multicast(), + match self { + IpAddr::V4(ip) => ip.is_multicast(), + IpAddr::V6(ip) => ip.is_multicast(), } } /// Returns [`true`] if this address is in a range designated for documentation. /// - /// See the documentation for [`Ipv4Addr::is_documentation`][IPv4] and - /// [`Ipv6Addr::is_documentation`][IPv6] for more details. - /// - /// [IPv4]: ../../no-std-net/struct.Ipv4Addr.html#method.is_documentation - /// [IPv6]: ../../no-std-net/struct.Ipv6Addr.html#method.is_documentation - /// [`true`]: https://doc.rust-lang.org/std/primitive.bool.html + /// See the documentation for [`Ipv4Addr::is_documentation()`] and + /// [`Ipv6Addr::is_documentation()`] for more details. /// /// # Examples /// /// ``` + /// // Requires `unstable_ip` feature + /// /// use no_std_net::{IpAddr, Ipv4Addr, Ipv6Addr}; /// - /// fn main() { - /// assert_eq!(IpAddr::V4(Ipv4Addr::new(203, 0, 113, 6)).is_documentation(), true); - /// assert_eq!(IpAddr::V6(Ipv6Addr::new(0x2001, 0xdb8, 0, 0, 0, 0, 0, 0)) - /// .is_documentation(), true); - /// } + /// assert_eq!(IpAddr::V4(Ipv4Addr::new(203, 0, 113, 6)).is_documentation(), true); + /// assert_eq!( + /// IpAddr::V6(Ipv6Addr::new(0x2001, 0xdb8, 0, 0, 0, 0, 0, 0)).is_documentation(), + /// true + /// ); /// ``` + #[cfg(feature = "unstable_ip")] + #[inline] pub const fn is_documentation(&self) -> bool { - match *self { - IpAddr::V4(ref a) => a.is_documentation(), - IpAddr::V6(ref a) => a.is_documentation(), + match self { + IpAddr::V4(ip) => ip.is_documentation(), + IpAddr::V6(ip) => ip.is_documentation(), } } - /// Returns [`true`] if this address is an [IPv4 address], and [`false`] otherwise. + /// Returns [`true`] if this address is an [`IPv4` address], and [`false`] + /// otherwise. /// - /// [`true`]: https://doc.rust-lang.org/std/primitive.bool.html - /// [`false`]: https://doc.rust-lang.org/std/primitive.bool.html - /// [IPv4 address]: #variant.V4 + /// [`IPv4` address]: IpAddr::V4 /// /// # Examples /// /// ``` /// use no_std_net::{IpAddr, Ipv4Addr, Ipv6Addr}; /// - /// fn main() { - /// assert_eq!(IpAddr::V4(Ipv4Addr::new(203, 0, 113, 6)).is_ipv4(), true); - /// assert_eq!(IpAddr::V6(Ipv6Addr::new(0x2001, 0xdb8, 0, 0, 0, 0, 0, 0)).is_ipv4(), - /// false); - /// } + /// assert_eq!(IpAddr::V4(Ipv4Addr::new(203, 0, 113, 6)).is_ipv4(), true); + /// assert_eq!(IpAddr::V6(Ipv6Addr::new(0x2001, 0xdb8, 0, 0, 0, 0, 0, 0)).is_ipv4(), false); /// ``` + #[inline] pub const fn is_ipv4(&self) -> bool { - match *self { - IpAddr::V4(_) => true, - IpAddr::V6(_) => false, - } + matches!(self, IpAddr::V4(_)) } - /// Returns [`true`] if this address is an [IPv6 address], and [`false`] otherwise. + /// Returns [`true`] if this address is an [`IPv6` address], and [`false`] + /// otherwise. /// - /// [`true`]: https://doc.rust-lang.org/std/primitive.bool.html - /// [`false`]: https://doc.rust-lang.org/std/primitive.bool.html - /// [IPv6 address]: #variant.V6 + /// [`IPv6` address]: IpAddr::V6 /// /// # Examples /// /// ``` /// use no_std_net::{IpAddr, Ipv4Addr, Ipv6Addr}; /// - /// fn main() { - /// assert_eq!(IpAddr::V4(Ipv4Addr::new(203, 0, 113, 6)).is_ipv6(), false); - /// assert_eq!(IpAddr::V6(Ipv6Addr::new(0x2001, 0xdb8, 0, 0, 0, 0, 0, 0)).is_ipv6(), - /// true); - /// } + /// assert_eq!(IpAddr::V4(Ipv4Addr::new(203, 0, 113, 6)).is_ipv6(), false); + /// assert_eq!(IpAddr::V6(Ipv6Addr::new(0x2001, 0xdb8, 0, 0, 0, 0, 0, 0)).is_ipv6(), true); /// ``` + #[inline] pub const fn is_ipv6(&self) -> bool { - match *self { - IpAddr::V4(_) => false, - IpAddr::V6(_) => true, - } - } -} - -impl ::fmt::Display for IpAddr { - fn fmt(&self, fmt: &mut ::fmt::Formatter) -> ::fmt::Result { - match *self { - IpAddr::V4(ref a) => a.fmt(fmt), - IpAddr::V6(ref a) => a.fmt(fmt), - } - } -} - -impl From<[u8; 4]> for Ipv4Addr { - /// Creates an `Ipv4Addr` from a four element byte array. - /// - /// # Examples - /// - /// ``` - /// use no_std_net::Ipv4Addr; - /// - /// let addr = Ipv4Addr::from([13u8, 12u8, 11u8, 10u8]); - /// assert_eq!(Ipv4Addr::new(13, 12, 11, 10), addr); - /// ``` - fn from(octets: [u8; 4]) -> Ipv4Addr { - Ipv4Addr::new(octets[0], octets[1], octets[2], octets[3]) - } -} - -impl From<[u8; 4]> for IpAddr { - /// Creates an `IpAddr::V4` from a four element byte array. - /// - /// # Examples - /// - /// ``` - /// use no_std_net::{IpAddr, Ipv4Addr}; - /// - /// let addr = IpAddr::from([13u8, 12u8, 11u8, 10u8]); - /// assert_eq!(IpAddr::V4(Ipv4Addr::new(13, 12, 11, 10)), addr); - /// ``` - fn from(octets: [u8; 4]) -> IpAddr { - IpAddr::V4(Ipv4Addr::from(octets)) - } -} - -impl From<[u8; 16]> for Ipv6Addr { - /// Creates an `Ipv6Addr` from a sixteen element byte array. - /// - /// # Examples - /// - /// ``` - /// use no_std_net::Ipv6Addr; - /// - /// let addr = Ipv6Addr::from([ - /// 25u8, 24u8, 23u8, 22u8, 21u8, 20u8, 19u8, 18u8, - /// 17u8, 16u8, 15u8, 14u8, 13u8, 12u8, 11u8, 10u8, - /// ]); - /// assert_eq!( - /// Ipv6Addr::new( - /// 0x1918, 0x1716, - /// 0x1514, 0x1312, - /// 0x1110, 0x0f0e, - /// 0x0d0c, 0x0b0a - /// ), - /// addr - /// ); - /// ``` - fn from(octets: [u8; 16]) -> Ipv6Addr { - Ipv6Addr { inner: octets } - } -} - -impl From<[u16; 8]> for Ipv6Addr { - /// Creates an `Ipv6Addr` from an eight element 16-bit array. - /// - /// # Examples - /// - /// ``` - /// use no_std_net::Ipv6Addr; - /// - /// let addr = Ipv6Addr::from([ - /// 525u16, 524u16, 523u16, 522u16, - /// 521u16, 520u16, 519u16, 518u16, - /// ]); - /// assert_eq!( - /// Ipv6Addr::new( - /// 0x20d, 0x20c, - /// 0x20b, 0x20a, - /// 0x209, 0x208, - /// 0x207, 0x206 - /// ), - /// addr - /// ); - /// ``` - fn from(segments: [u16; 8]) -> Ipv6Addr { - let [a, b, c, d, e, f, g, h] = segments; - Ipv6Addr::new(a, b, c, d, e, f, g, h) - } -} - -impl From<[u8; 16]> for IpAddr { - /// Creates an `IpAddr::V6` from a sixteen element byte array. - /// - /// # Examples - /// - /// ``` - /// use no_std_net::{IpAddr, Ipv6Addr}; - /// - /// let addr = IpAddr::from([ - /// 25u8, 24u8, 23u8, 22u8, 21u8, 20u8, 19u8, 18u8, - /// 17u8, 16u8, 15u8, 14u8, 13u8, 12u8, 11u8, 10u8, - /// ]); - /// assert_eq!( - /// IpAddr::V6(Ipv6Addr::new( - /// 0x1918, 0x1716, - /// 0x1514, 0x1312, - /// 0x1110, 0x0f0e, - /// 0x0d0c, 0x0b0a - /// )), - /// addr - /// ); - /// ``` - fn from(octets: [u8; 16]) -> IpAddr { - IpAddr::V6(Ipv6Addr::from(octets)) - } -} - -impl From<[u16; 8]> for IpAddr { - /// Creates an `IpAddr::V6` from an eight element 16-bit array. - /// - /// # Examples - /// - /// ``` - /// use no_std_net::{IpAddr, Ipv6Addr}; - /// - /// let addr = IpAddr::from([ - /// 525u16, 524u16, 523u16, 522u16, - /// 521u16, 520u16, 519u16, 518u16, - /// ]); - /// assert_eq!( - /// IpAddr::V6(Ipv6Addr::new( - /// 0x20d, 0x20c, - /// 0x20b, 0x20a, - /// 0x209, 0x208, - /// 0x207, 0x206 - /// )), - /// addr - /// ); - /// ``` - fn from(segments: [u16; 8]) -> IpAddr { - IpAddr::V6(Ipv6Addr::from(segments)) - } -} - -impl From for IpAddr { - /// Copies this address to a new `IpAddr::V4`. - /// - /// # Examples - /// - /// ``` - /// use no_std_net::{IpAddr, Ipv4Addr}; - /// - /// let addr = Ipv4Addr::new(127, 0, 0, 1); - /// - /// assert_eq!( - /// IpAddr::V4(addr), - /// IpAddr::from(addr) - /// ) - /// ``` - fn from(ipv4: Ipv4Addr) -> IpAddr { - IpAddr::V4(ipv4) - } -} - -impl From for IpAddr { - /// Copies this address to a new `IpAddr::V6`. - /// - /// # Examples - /// - /// ``` - /// use no_std_net::{IpAddr, Ipv6Addr}; - /// - /// let addr = Ipv6Addr::new(0, 0, 0, 0, 0, 0xffff, 0xc00a, 0x2ff); - /// - /// assert_eq!( - /// IpAddr::V6(addr), - /// IpAddr::from(addr) - /// ); - /// ``` - fn from(ipv6: Ipv6Addr) -> IpAddr { - IpAddr::V6(ipv6) + matches!(self, IpAddr::V6(_)) } } @@ -495,39 +283,51 @@ impl Ipv4Addr { /// /// let addr = Ipv4Addr::new(127, 0, 0, 1); /// ``` + #[inline] pub const fn new(a: u8, b: u8, c: u8, d: u8) -> Ipv4Addr { Ipv4Addr { inner: [a, b, c, d], } } - /// Creates a new IPv4 address with the address pointing to localhost: 127.0.0.1. + /// An IPv4 address with the address pointing to localhost: `127.0.0.1` /// /// # Examples /// /// ``` /// use no_std_net::Ipv4Addr; /// - /// let addr = Ipv4Addr::localhost(); + /// let addr = Ipv4Addr::LOCALHOST; /// assert_eq!(addr, Ipv4Addr::new(127, 0, 0, 1)); /// ``` - pub const fn localhost() -> Ipv4Addr { - Ipv4Addr::new(127, 0, 0, 1) - } + pub const LOCALHOST: Self = Ipv4Addr::new(127, 0, 0, 1); - /// Creates a new IPv4 address representing an unspecified address: 0.0.0.0 + /// An IPv4 address representing an unspecified address: `0.0.0.0` + /// + /// This corresponds to the constant `INADDR_ANY` in other languages. /// /// # Examples /// /// ``` /// use no_std_net::Ipv4Addr; /// - /// let addr = Ipv4Addr::unspecified(); + /// let addr = Ipv4Addr::UNSPECIFIED; /// assert_eq!(addr, Ipv4Addr::new(0, 0, 0, 0)); /// ``` - pub const fn unspecified() -> Ipv4Addr { - Ipv4Addr::new(0, 0, 0, 0) - } + #[doc(alias = "INADDR_ANY")] + pub const UNSPECIFIED: Self = Ipv4Addr::new(0, 0, 0, 0); + + /// An IPv4 address representing the broadcast address: `255.255.255.255` + /// + /// # Examples + /// + /// ``` + /// use no_std_net::Ipv4Addr; + /// + /// let addr = Ipv4Addr::BROADCAST; + /// assert_eq!(addr, Ipv4Addr::new(255, 255, 255, 255)); + /// ``` + pub const BROADCAST: Self = Ipv4Addr::new(255, 255, 255, 255); /// Returns the four eight-bit integers that make up this address. /// @@ -539,17 +339,17 @@ impl Ipv4Addr { /// let addr = Ipv4Addr::new(127, 0, 0, 1); /// assert_eq!(addr.octets(), [127, 0, 0, 1]); /// ``` + #[inline] pub const fn octets(&self) -> [u8; 4] { - [self.inner[0], self.inner[1], self.inner[2], self.inner[3]] + self.inner } - /// Returns [`true`] for the special 'unspecified' address (0.0.0.0). + /// Returns [`true`] for the special 'unspecified' address (`0.0.0.0`). /// /// This property is defined in _UNIX Network Programming, Second Edition_, /// W. Richard Stevens, p. 891; see also [ip7]. /// /// [ip7]: http://man7.org/linux/man-pages/man7/ip.7.html - /// [`true`]: https://doc.rust-lang.org/std/primitive.bool.html /// /// # Examples /// @@ -559,16 +359,16 @@ impl Ipv4Addr { /// assert_eq!(Ipv4Addr::new(0, 0, 0, 0).is_unspecified(), true); /// assert_eq!(Ipv4Addr::new(45, 22, 13, 197).is_unspecified(), false); /// ``` + #[inline] pub const fn is_unspecified(&self) -> bool { self.inner[0] == 0 && self.inner[1] == 0 && self.inner[2] == 0 && self.inner[3] == 0 } - /// Returns [`true`] if this is a loopback address (127.0.0.0/8). + /// Returns [`true`] if this is a loopback address (`127.0.0.0/8`). /// /// This property is defined by [IETF RFC 1122]. /// /// [IETF RFC 1122]: https://tools.ietf.org/html/rfc1122 - /// [`true`]: https://doc.rust-lang.org/std/primitive.bool.html /// /// # Examples /// @@ -578,20 +378,20 @@ impl Ipv4Addr { /// assert_eq!(Ipv4Addr::new(127, 0, 0, 1).is_loopback(), true); /// assert_eq!(Ipv4Addr::new(45, 22, 13, 197).is_loopback(), false); /// ``` + #[inline] pub const fn is_loopback(&self) -> bool { - self.inner[0] == 127 + self.octets()[0] == 127 } /// Returns [`true`] if this is a private address. /// /// The private address ranges are defined in [IETF RFC 1918] and include: /// - /// - 10.0.0.0/8 - /// - 172.16.0.0/12 - /// - 192.168.0.0/16 + /// - `10.0.0.0/8` + /// - `172.16.0.0/12` + /// - `192.168.0.0/16` /// /// [IETF RFC 1918]: https://tools.ietf.org/html/rfc1918 - /// [`true`]: https://doc.rust-lang.org/std/primitive.bool.html /// /// # Examples /// @@ -606,21 +406,21 @@ impl Ipv4Addr { /// assert_eq!(Ipv4Addr::new(192, 168, 0, 2).is_private(), true); /// assert_eq!(Ipv4Addr::new(192, 169, 0, 2).is_private(), false); /// ``` + #[inline] pub const fn is_private(&self) -> bool { - match (self.inner[0], self.inner[1]) { - (10, _) => true, - (172, b) if 16 <= b && b <= 31 => true, - (192, 168) => true, + match self.octets() { + [10, ..] => true, + [172, b, ..] if b >= 16 && b <= 31 => true, + [192, 168, ..] => true, _ => false, } } - /// Returns [`true`] if the address is link-local (169.254.0.0/16). + /// Returns [`true`] if the address is link-local (`169.254.0.0/16`). /// /// This property is defined by [IETF RFC 3927]. /// /// [IETF RFC 3927]: https://tools.ietf.org/html/rfc3927 - /// [`true`]: https://doc.rust-lang.org/std/primitive.bool.html /// /// # Examples /// @@ -631,149 +431,323 @@ impl Ipv4Addr { /// assert_eq!(Ipv4Addr::new(169, 254, 10, 65).is_link_local(), true); /// assert_eq!(Ipv4Addr::new(16, 89, 10, 65).is_link_local(), false); /// ``` + #[inline] pub const fn is_link_local(&self) -> bool { - self.inner[0] == 169 && self.inner[1] == 254 + matches!(self.octets(), [169, 254, ..]) } /// Returns [`true`] if the address appears to be globally routable. /// See [iana-ipv4-special-registry][ipv4-sr]. /// - /// The following return false: + /// The following return [`false`]: /// - /// - private address (10.0.0.0/8, 172.16.0.0/12 and 192.168.0.0/16) - /// - the loopback address (127.0.0.0/8) - /// - the link-local address (169.254.0.0/16) - /// - the broadcast address (255.255.255.255/32) - /// - test addresses used for documentation (192.0.2.0/24, 198.51.100.0/24 and 203.0.113.0/24) - /// - the unspecified address (0.0.0.0) + /// - private addresses (see [`Ipv4Addr::is_private()`]) + /// - the loopback address (see [`Ipv4Addr::is_loopback()`]) + /// - the link-local address (see [`Ipv4Addr::is_link_local()`]) + /// - the broadcast address (see [`Ipv4Addr::is_broadcast()`]) + /// - addresses used for documentation (see [`Ipv4Addr::is_documentation()`]) + /// - the unspecified address (see [`Ipv4Addr::is_unspecified()`]), and the whole + /// `0.0.0.0/8` block + /// - addresses reserved for future protocols (see + /// [`Ipv4Addr::is_ietf_protocol_assignment()`], except + /// `192.0.0.9/32` and `192.0.0.10/32` which are globally routable + /// - addresses reserved for future use (see [`Ipv4Addr::is_reserved()`] + /// - addresses reserved for networking devices benchmarking (see + /// [`Ipv4Addr::is_benchmarking()`]) /// /// [ipv4-sr]: https://www.iana.org/assignments/iana-ipv4-special-registry/iana-ipv4-special-registry.xhtml - /// [`true`]: https://doc.rust-lang.org/std/primitive.bool.html /// /// # Examples /// /// ``` + /// // Requires `unstable_ip` feature + /// /// use no_std_net::Ipv4Addr; /// - /// fn main() { - /// assert_eq!(Ipv4Addr::new(10, 254, 0, 0).is_global(), false); - /// assert_eq!(Ipv4Addr::new(192, 168, 10, 65).is_global(), false); - /// assert_eq!(Ipv4Addr::new(172, 16, 10, 65).is_global(), false); - /// assert_eq!(Ipv4Addr::new(0, 0, 0, 0).is_global(), false); - /// assert_eq!(Ipv4Addr::new(80, 9, 12, 3).is_global(), true); - /// } + /// // private addresses are not global + /// assert_eq!(Ipv4Addr::new(10, 254, 0, 0).is_global(), false); + /// assert_eq!(Ipv4Addr::new(192, 168, 10, 65).is_global(), false); + /// assert_eq!(Ipv4Addr::new(172, 16, 10, 65).is_global(), false); + /// + /// // the 0.0.0.0/8 block is not global + /// assert_eq!(Ipv4Addr::new(0, 1, 2, 3).is_global(), false); + /// // in particular, the unspecified address is not global + /// assert_eq!(Ipv4Addr::new(0, 0, 0, 0).is_global(), false); + /// + /// // the loopback address is not global + /// assert_eq!(Ipv4Addr::new(127, 0, 0, 1).is_global(), false); + /// + /// // link local addresses are not global + /// assert_eq!(Ipv4Addr::new(169, 254, 45, 1).is_global(), false); + /// + /// // the broadcast address is not global + /// assert_eq!(Ipv4Addr::new(255, 255, 255, 255).is_global(), false); + /// + /// // the address space designated for documentation is not global + /// assert_eq!(Ipv4Addr::new(192, 0, 2, 255).is_global(), false); + /// assert_eq!(Ipv4Addr::new(198, 51, 100, 65).is_global(), false); + /// assert_eq!(Ipv4Addr::new(203, 0, 113, 6).is_global(), false); + /// + /// // shared addresses are not global + /// assert_eq!(Ipv4Addr::new(100, 100, 0, 0).is_global(), false); + /// + /// // addresses reserved for protocol assignment are not global + /// assert_eq!(Ipv4Addr::new(192, 0, 0, 0).is_global(), false); + /// assert_eq!(Ipv4Addr::new(192, 0, 0, 255).is_global(), false); + /// + /// // addresses reserved for future use are not global + /// assert_eq!(Ipv4Addr::new(250, 10, 20, 30).is_global(), false); + /// + /// // addresses reserved for network devices benchmarking are not global + /// assert_eq!(Ipv4Addr::new(198, 18, 0, 0).is_global(), false); + /// + /// // All the other addresses are global + /// assert_eq!(Ipv4Addr::new(1, 1, 1, 1).is_global(), true); + /// assert_eq!(Ipv4Addr::new(80, 9, 12, 3).is_global(), true); /// ``` + #[cfg(feature = "unstable_ip")] + #[inline] pub const fn is_global(&self) -> bool { + // check if this address is 192.0.0.9 or 192.0.0.10. These addresses are the only two + // globally routable addresses in the 192.0.0.0/24 range. + if u32::from_be_bytes(self.octets()) == 0xc0000009 + || u32::from_be_bytes(self.octets()) == 0xc000000a + { + return true; + } !self.is_private() && !self.is_loopback() && !self.is_link_local() && !self.is_broadcast() && !self.is_documentation() - && !self.is_unspecified() + && !self.is_shared() + && !self.is_ietf_protocol_assignment() + && !self.is_reserved() + && !self.is_benchmarking() + // Make sure the address is not in 0.0.0.0/8 + && self.octets()[0] != 0 } - /// Returns [`true`] if this is a multicast address (224.0.0.0/4). - /// - /// Multicast addresses have a most significant octet between 224 and 239, - /// and is defined by [IETF RFC 5771]. + /// Returns [`true`] if this address is part of the Shared Address Space defined in + /// [IETF RFC 6598] (`100.64.0.0/10`). /// - /// [IETF RFC 5771]: https://tools.ietf.org/html/rfc5771 - /// [`true`]: https://doc.rust-lang.org/std/primitive.bool.html + /// [IETF RFC 6598]: https://tools.ietf.org/html/rfc6598 /// /// # Examples /// /// ``` + /// // Requires `unstable_ip` feature /// use no_std_net::Ipv4Addr; /// - /// assert_eq!(Ipv4Addr::new(224, 254, 0, 0).is_multicast(), true); - /// assert_eq!(Ipv4Addr::new(236, 168, 10, 65).is_multicast(), true); - /// assert_eq!(Ipv4Addr::new(172, 16, 10, 65).is_multicast(), false); + /// assert_eq!(Ipv4Addr::new(100, 64, 0, 0).is_shared(), true); + /// assert_eq!(Ipv4Addr::new(100, 127, 255, 255).is_shared(), true); + /// assert_eq!(Ipv4Addr::new(100, 128, 0, 0).is_shared(), false); /// ``` - pub const fn is_multicast(&self) -> bool { - self.inner[0] & 0xF0 == 0xE0 + #[cfg(feature = "unstable_ip")] + #[inline] + pub const fn is_shared(&self) -> bool { + self.octets()[0] == 100 && (self.octets()[1] & 0b1100_0000 == 0b0100_0000) } - /// Returns [`true`] if this is a broadcast address (255.255.255.255). + /// Returns [`true`] if this address is part of `192.0.0.0/24`, which is reserved to + /// IANA for IETF protocol assignments, as documented in [IETF RFC 6890]. /// - /// A broadcast address has all octets set to 255 as defined in [IETF RFC 919]. + /// Note that parts of this block are in use: /// - /// [IETF RFC 919]: https://tools.ietf.org/html/rfc919 - /// [`true`]: https://doc.rust-lang.org/std/primitive.bool.html + /// - `192.0.0.8/32` is the "IPv4 dummy address" (see [IETF RFC 7600]) + /// - `192.0.0.9/32` is the "Port Control Protocol Anycast" (see [IETF RFC 7723]) + /// - `192.0.0.10/32` is used for NAT traversal (see [IETF RFC 8155]) + /// + /// [IETF RFC 6890]: https://tools.ietf.org/html/rfc6890 + /// [IETF RFC 7600]: https://tools.ietf.org/html/rfc7600 + /// [IETF RFC 7723]: https://tools.ietf.org/html/rfc7723 + /// [IETF RFC 8155]: https://tools.ietf.org/html/rfc8155 /// /// # Examples /// /// ``` + /// // Requires `unstable_ip` feature /// use no_std_net::Ipv4Addr; /// - /// assert_eq!(Ipv4Addr::new(255, 255, 255, 255).is_broadcast(), true); - /// assert_eq!(Ipv4Addr::new(236, 168, 10, 65).is_broadcast(), false); + /// assert_eq!(Ipv4Addr::new(192, 0, 0, 0).is_ietf_protocol_assignment(), true); + /// assert_eq!(Ipv4Addr::new(192, 0, 0, 8).is_ietf_protocol_assignment(), true); + /// assert_eq!(Ipv4Addr::new(192, 0, 0, 9).is_ietf_protocol_assignment(), true); + /// assert_eq!(Ipv4Addr::new(192, 0, 0, 255).is_ietf_protocol_assignment(), true); + /// assert_eq!(Ipv4Addr::new(192, 0, 1, 0).is_ietf_protocol_assignment(), false); + /// assert_eq!(Ipv4Addr::new(191, 255, 255, 255).is_ietf_protocol_assignment(), false); /// ``` - pub const fn is_broadcast(&self) -> bool { - self.inner[0] == 255 && self.inner[1] == 255 && self.inner[2] == 255 && self.inner[3] == 255 + #[cfg(feature = "unstable_ip")] + #[inline] + pub const fn is_ietf_protocol_assignment(&self) -> bool { + self.octets()[0] == 192 && self.octets()[1] == 0 && self.octets()[2] == 0 } - /// Returns [`true`] if this address is in a range designated for documentation. - /// - /// This is defined in [IETF RFC 5737]: - /// - /// - 192.0.2.0/24 (TEST-NET-1) - /// - 198.51.100.0/24 (TEST-NET-2) - /// - 203.0.113.0/24 (TEST-NET-3) + /// Returns [`true`] if this address part of the `198.18.0.0/15` range, which is reserved for + /// network devices benchmarking. This range is defined in [IETF RFC 2544] as `192.18.0.0` + /// through `198.19.255.255` but [errata 423] corrects it to `198.18.0.0/15`. /// - /// [IETF RFC 5737]: https://tools.ietf.org/html/rfc5737 - /// [`true`]: https://doc.rust-lang.org/std/primitive.bool.html + /// [IETF RFC 2544]: https://tools.ietf.org/html/rfc2544 + /// [errata 423]: https://www.rfc-editor.org/errata/eid423 /// /// # Examples /// /// ``` + /// // Requires `unstable_ip` feature /// use no_std_net::Ipv4Addr; /// - /// assert_eq!(Ipv4Addr::new(192, 0, 2, 255).is_documentation(), true); - /// assert_eq!(Ipv4Addr::new(198, 51, 100, 65).is_documentation(), true); - /// assert_eq!(Ipv4Addr::new(203, 0, 113, 6).is_documentation(), true); - /// assert_eq!(Ipv4Addr::new(193, 34, 17, 19).is_documentation(), false); + /// assert_eq!(Ipv4Addr::new(198, 17, 255, 255).is_benchmarking(), false); + /// assert_eq!(Ipv4Addr::new(198, 18, 0, 0).is_benchmarking(), true); + /// assert_eq!(Ipv4Addr::new(198, 19, 255, 255).is_benchmarking(), true); + /// assert_eq!(Ipv4Addr::new(198, 20, 0, 0).is_benchmarking(), false); /// ``` - pub const fn is_documentation(&self) -> bool { - match (self.inner[0], self.inner[1], self.inner[2]) { - (192, 0, 2) => true, - (198, 51, 100) => true, - (203, 0, 113) => true, - _ => false, - } + #[cfg(feature = "unstable_ip")] + #[inline] + pub const fn is_benchmarking(&self) -> bool { + self.octets()[0] == 198 && (self.octets()[1] & 0xfe) == 18 } - /// Converts this address to an IPv4-compatible [IPv6 address]. + /// Returns [`true`] if this address is reserved by IANA for future use. [IETF RFC 1112] + /// defines the block of reserved addresses as `240.0.0.0/4`. This range normally includes the + /// broadcast address `255.255.255.255`, but this implementation explicitly excludes it, since + /// it is obviously not reserved for future use. /// - /// a.b.c.d becomes ::a.b.c.d + /// [IETF RFC 1112]: https://tools.ietf.org/html/rfc1112 /// - /// [IPv6 address]: ../../no-std-net/struct.Ipv6Addr.html + /// # Warning + /// + /// As IANA assigns new addresses, this method will be + /// updated. This may result in non-reserved addresses being + /// treated as reserved in code that relies on an outdated version + /// of this method. /// /// # Examples /// /// ``` - /// use no_std_net::{Ipv4Addr, Ipv6Addr}; + /// // Requires `unstable_ip` feature + /// use no_std_net::Ipv4Addr; + /// + /// assert_eq!(Ipv4Addr::new(240, 0, 0, 0).is_reserved(), true); + /// assert_eq!(Ipv4Addr::new(255, 255, 255, 254).is_reserved(), true); /// - /// assert_eq!(Ipv4Addr::new(192, 0, 2, 255).to_ipv6_compatible(), - /// Ipv6Addr::new(0, 0, 0, 0, 0, 0, 49152, 767)); + /// assert_eq!(Ipv4Addr::new(239, 255, 255, 255).is_reserved(), false); + /// // The broadcast address is not considered as reserved for future use by this implementation + /// assert_eq!(Ipv4Addr::new(255, 255, 255, 255).is_reserved(), false); /// ``` - pub const fn to_ipv6_compatible(&self) -> Ipv6Addr { - Ipv6Addr::new( + #[cfg(feature = "unstable_ip")] + #[inline] + pub const fn is_reserved(&self) -> bool { + self.octets()[0] & 240 == 240 && !self.is_broadcast() + } + + /// Returns [`true`] if this is a multicast address (`224.0.0.0/4`). + /// + /// Multicast addresses have a most significant octet between `224` and `239`, + /// and is defined by [IETF RFC 5771]. + /// + /// [IETF RFC 5771]: https://tools.ietf.org/html/rfc5771 + /// + /// # Examples + /// + /// ``` + /// use no_std_net::Ipv4Addr; + /// + /// assert_eq!(Ipv4Addr::new(224, 254, 0, 0).is_multicast(), true); + /// assert_eq!(Ipv4Addr::new(236, 168, 10, 65).is_multicast(), true); + /// assert_eq!(Ipv4Addr::new(172, 16, 10, 65).is_multicast(), false); + /// ``` + #[inline] + pub const fn is_multicast(&self) -> bool { + self.octets()[0] >= 224 && self.octets()[0] <= 239 + } + + /// Returns [`true`] if this is a broadcast address (`255.255.255.255`). + /// + /// A broadcast address has all octets set to `255` as defined in [IETF RFC 919]. + /// + /// [IETF RFC 919]: https://tools.ietf.org/html/rfc919 + /// + /// # Examples + /// + /// ``` + /// use no_std_net::Ipv4Addr; + /// + /// assert_eq!(Ipv4Addr::new(255, 255, 255, 255).is_broadcast(), true); + /// assert_eq!(Ipv4Addr::new(236, 168, 10, 65).is_broadcast(), false); + /// ``` + #[inline] + pub const fn is_broadcast(&self) -> bool { + u32::from_be_bytes(self.octets()) == u32::from_be_bytes(Self::BROADCAST.octets()) + } + + /// Returns [`true`] if this address is in a range designated for documentation. + /// + /// This is defined in [IETF RFC 5737]: + /// + /// - `192.0.2.0/24` (TEST-NET-1) + /// - `198.51.100.0/24` (TEST-NET-2) + /// - `203.0.113.0/24` (TEST-NET-3) + /// + /// [IETF RFC 5737]: https://tools.ietf.org/html/rfc5737 + /// + /// # Examples + /// + /// ``` + /// use no_std_net::Ipv4Addr; + /// + /// assert_eq!(Ipv4Addr::new(192, 0, 2, 255).is_documentation(), true); + /// assert_eq!(Ipv4Addr::new(198, 51, 100, 65).is_documentation(), true); + /// assert_eq!(Ipv4Addr::new(203, 0, 113, 6).is_documentation(), true); + /// assert_eq!(Ipv4Addr::new(193, 34, 17, 19).is_documentation(), false); + /// ``` + #[inline] + pub const fn is_documentation(&self) -> bool { + match self.octets() { + [192, 0, 2, _] => true, + [198, 51, 100, _] => true, + [203, 0, 113, _] => true, + _ => false, + } + } + + /// Converts this address to an IPv4-compatible [`IPv6` address]. + /// + /// `a.b.c.d` becomes `::a.b.c.d` + /// + /// This isn't typically the method you want; these addresses don't typically + /// function on modern systems. Use `to_ipv6_mapped` instead. + /// + /// [`IPv6` address]: Ipv6Addr + /// + /// # Examples + /// + /// ``` + /// use no_std_net::{Ipv4Addr, Ipv6Addr}; + /// + /// assert_eq!( + /// Ipv4Addr::new(192, 0, 2, 255).to_ipv6_compatible(), + /// Ipv6Addr::new(0, 0, 0, 0, 0, 0, 0xc000, 0x2ff) + /// ); + /// ``` + #[inline] + pub const fn to_ipv6_compatible(&self) -> Ipv6Addr { + let [a, b, c, d] = self.octets(); + Ipv6Addr::new( 0, 0, 0, 0, 0, 0, - ((self.inner[0] as u16) << 8) | self.inner[1] as u16, - ((self.inner[2] as u16) << 8) | self.inner[3] as u16, + ((a as u16) << 8) | b as u16, + ((c as u16) << 8) | d as u16, ) } - /// Converts this address to an IPv4-mapped [IPv6 address]. + /// Converts this address to an IPv4-mapped [`IPv6` address]. /// - /// a.b.c.d becomes ::ffff:a.b.c.d + /// `a.b.c.d` becomes `::ffff:a.b.c.d` /// - /// [IPv6 address]: ../../no-std-net/struct.Ipv6Addr.html + /// [`IPv6` address]: Ipv6Addr /// /// # Examples /// @@ -781,9 +755,11 @@ impl Ipv4Addr { /// use no_std_net::{Ipv4Addr, Ipv6Addr}; /// /// assert_eq!(Ipv4Addr::new(192, 0, 2, 255).to_ipv6_mapped(), - /// Ipv6Addr::new(0, 0, 0, 0, 0, 65535, 49152, 767)); + /// Ipv6Addr::new(0, 0, 0, 0, 0, 0xffff, 0xc000, 0x2ff)); /// ``` + #[inline] pub const fn to_ipv6_mapped(&self) -> Ipv6Addr { + let [a, b, c, d] = self.octets(); Ipv6Addr::new( 0, 0, @@ -791,82 +767,257 @@ impl Ipv4Addr { 0, 0, 0xffff, - ((self.inner[0] as u16) << 8) | self.inner[1] as u16, - ((self.inner[2] as u16) << 8) | self.inner[3] as u16, + ((a as u16) << 8) | b as u16, + ((c as u16) << 8) | d as u16, ) } } -impl ::fmt::Display for Ipv4Addr { - fn fmt(&self, fmt: &mut ::fmt::Formatter) -> ::fmt::Result { - write!( - fmt, - "{}.{}.{}.{}", - self.inner[0], self.inner[1], self.inner[2], self.inner[3] - ) +impl fmt::Display for IpAddr { + fn fmt(&self, fmt: &mut fmt::Formatter<'_>) -> fmt::Result { + match self { + IpAddr::V4(ip) => ip.fmt(fmt), + IpAddr::V6(ip) => ip.fmt(fmt), + } } } -impl ::fmt::Debug for Ipv4Addr { - fn fmt(&self, fmt: &mut ::fmt::Formatter) -> ::fmt::Result { - ::fmt::Display::fmt(self, fmt) +impl fmt::Debug for IpAddr { + fn fmt(&self, fmt: &mut fmt::Formatter<'_>) -> fmt::Result { + fmt::Display::fmt(self, fmt) + } +} + +impl From for IpAddr { + /// Copies this address to a new `IpAddr::V4`. + /// + /// # Examples + /// + /// ``` + /// use no_std_net::{IpAddr, Ipv4Addr}; + /// + /// let addr = Ipv4Addr::new(127, 0, 0, 1); + /// + /// assert_eq!( + /// IpAddr::V4(addr), + /// IpAddr::from(addr) + /// ) + /// ``` + #[inline] + fn from(ipv4: Ipv4Addr) -> IpAddr { + IpAddr::V4(ipv4) + } +} + +impl From for IpAddr { + /// Copies this address to a new `IpAddr::V6`. + /// + /// # Examples + /// + /// ``` + /// use no_std_net::{IpAddr, Ipv6Addr}; + /// + /// let addr = Ipv6Addr::new(0, 0, 0, 0, 0, 0xffff, 0xc00a, 0x2ff); + /// + /// assert_eq!( + /// IpAddr::V6(addr), + /// IpAddr::from(addr) + /// ); + /// ``` + #[inline] + fn from(ipv6: Ipv6Addr) -> IpAddr { + IpAddr::V6(ipv6) + } +} + +impl fmt::Display for Ipv4Addr { + fn fmt(&self, fmt: &mut fmt::Formatter<'_>) -> fmt::Result { + let octets = self.octets(); + // Fast Path: if there's no alignment stuff, write directly to the buffer + if fmt.precision().is_none() && fmt.width().is_none() { + write!( + fmt, + "{}.{}.{}.{}", + octets[0], octets[1], octets[2], octets[3] + ) + } else { + const IPV4_BUF_LEN: usize = 15; // Long enough for the longest possible IPv4 address + let mut buf = [0u8; IPV4_BUF_LEN]; + let mut buf_slice = WriteHelper::new(&mut buf[..]); + + // Note: The call to write should never fail, hence the unwrap + write!( + buf_slice, + "{}.{}.{}.{}", + octets[0], octets[1], octets[2], octets[3] + ) + .unwrap(); + let len = IPV4_BUF_LEN - buf_slice.into_raw().len(); + + // This unsafe is OK because we know what is being written to the buffer + let buf = unsafe { core::str::from_utf8_unchecked(&buf[..len]) }; + fmt.pad(buf) + } + } +} + +impl fmt::Debug for Ipv4Addr { + fn fmt(&self, fmt: &mut fmt::Formatter<'_>) -> fmt::Result { + fmt::Display::fmt(self, fmt) + } +} + +impl Clone for Ipv4Addr { + #[inline] + fn clone(&self) -> Ipv4Addr { + *self + } +} + +impl PartialEq for Ipv4Addr { + #[inline] + fn eq(&self, other: &Ipv4Addr) -> bool { + self.inner == other.inner } } impl PartialEq for IpAddr { + #[inline] fn eq(&self, other: &Ipv4Addr) -> bool { - match *self { - IpAddr::V4(ref v4) => v4 == other, + match self { + IpAddr::V4(v4) => v4 == other, IpAddr::V6(_) => false, } } } impl PartialEq for Ipv4Addr { + #[inline] fn eq(&self, other: &IpAddr) -> bool { - match *other { - IpAddr::V4(ref v4) => self == v4, + match other { + IpAddr::V4(v4) => self == v4, IpAddr::V6(_) => false, } } } +impl Eq for Ipv4Addr {} + +impl hash::Hash for Ipv4Addr { + #[inline] + fn hash(&self, s: &mut H) { + self.inner.hash(s) + } +} + +impl PartialOrd for Ipv4Addr { + #[inline] + fn partial_cmp(&self, other: &Ipv4Addr) -> Option { + Some(self.cmp(other)) + } +} + impl PartialOrd for IpAddr { + #[inline] fn partial_cmp(&self, other: &Ipv4Addr) -> Option { - match *self { - IpAddr::V4(ref v4) => v4.partial_cmp(other), + match self { + IpAddr::V4(v4) => v4.partial_cmp(other), IpAddr::V6(_) => Some(Ordering::Greater), } } } impl PartialOrd for Ipv4Addr { + #[inline] fn partial_cmp(&self, other: &IpAddr) -> Option { - match *other { - IpAddr::V4(ref v4) => self.partial_cmp(v4), + match other { + IpAddr::V4(v4) => self.partial_cmp(v4), IpAddr::V6(_) => Some(Ordering::Less), } } } +impl Ord for Ipv4Addr { + #[inline] + fn cmp(&self, other: &Ipv4Addr) -> Ordering { + // Compare as native endian + u32::from_be_bytes(self.inner).cmp(&u32::from_be_bytes(other.inner)) + } +} + impl From for u32 { + /// Converts an `Ipv4Addr` into a host byte order `u32`. + /// + /// # Examples + /// + /// ``` + /// use no_std_net::Ipv4Addr; + /// + /// let addr = Ipv4Addr::new(0xca, 0xfe, 0xba, 0xbe); + /// assert_eq!(0xcafebabe, u32::from(addr)); + /// ``` + #[inline] fn from(ip: Ipv4Addr) -> u32 { - u32::from_be_bytes(ip.inner) + let ip = ip.octets(); + u32::from_be_bytes(ip) } } impl From for Ipv4Addr { + /// Converts a host byte order `u32` into an `Ipv4Addr`. + /// + /// # Examples + /// + /// ``` + /// use no_std_net::Ipv4Addr; + /// + /// let addr = Ipv4Addr::from(0xcafebabe); + /// assert_eq!(Ipv4Addr::new(0xca, 0xfe, 0xba, 0xbe), addr); + /// ``` + #[inline] fn from(ip: u32) -> Ipv4Addr { - Ipv4Addr { - inner: u32::to_be_bytes(ip), - } + Ipv4Addr::from(ip.to_be_bytes()) + } +} + +impl From<[u8; 4]> for Ipv4Addr { + /// Creates an `Ipv4Addr` from a four element byte array. + /// + /// # Examples + /// + /// ``` + /// use no_std_net::Ipv4Addr; + /// + /// let addr = Ipv4Addr::from([13u8, 12u8, 11u8, 10u8]); + /// assert_eq!(Ipv4Addr::new(13, 12, 11, 10), addr); + /// ``` + #[inline] + fn from(octets: [u8; 4]) -> Ipv4Addr { + Ipv4Addr::new(octets[0], octets[1], octets[2], octets[3]) + } +} + +impl From<[u8; 4]> for IpAddr { + /// Creates an `IpAddr::V4` from a four element byte array. + /// + /// # Examples + /// + /// ``` + /// use no_std_net::{IpAddr, Ipv4Addr}; + /// + /// let addr = IpAddr::from([13u8, 12u8, 11u8, 10u8]); + /// assert_eq!(IpAddr::V4(Ipv4Addr::new(13, 12, 11, 10)), addr); + /// ``` + #[inline] + fn from(octets: [u8; 4]) -> IpAddr { + IpAddr::V4(Ipv4Addr::from(octets)) } } impl Ipv6Addr { /// Creates a new IPv6 address from eight 16-bit segments. /// - /// The result will represent the IP address a:b:c:d:e:f:g:h. + /// The result will represent the IP address `a:b:c:d:e:f:g:h`. /// /// # Examples /// @@ -875,6 +1026,7 @@ impl Ipv6Addr { /// /// let addr = Ipv6Addr::new(0, 0, 0, 0, 0, 0xffff, 0xc00a, 0x2ff); /// ``` + #[inline] pub const fn new(a: u16, b: u16, c: u16, d: u16, e: u16, f: u16, g: u16, h: u16) -> Ipv6Addr { Ipv6Addr { inner: [ @@ -898,59 +1050,29 @@ impl Ipv6Addr { } } - /// Creates a new IPv6 address representing localhost: `::1`. + /// An IPv6 address representing localhost: `::1`. /// /// # Examples /// /// ``` /// use no_std_net::Ipv6Addr; /// - /// let addr = Ipv6Addr::localhost(); + /// let addr = Ipv6Addr::LOCALHOST; /// assert_eq!(addr, Ipv6Addr::new(0, 0, 0, 0, 0, 0, 0, 1)); /// ``` - pub const fn localhost() -> Ipv6Addr { - Ipv6Addr::new(0, 0, 0, 0, 0, 0, 0, 1) - } + pub const LOCALHOST: Self = Ipv6Addr::new(0, 0, 0, 0, 0, 0, 0, 1); - /// Creates a new IPv6 address representing the unspecified address: `::` + /// An IPv6 address representing the unspecified address: `::` /// /// # Examples /// /// ``` /// use no_std_net::Ipv6Addr; /// - /// let addr = Ipv6Addr::unspecified(); + /// let addr = Ipv6Addr::UNSPECIFIED; /// assert_eq!(addr, Ipv6Addr::new(0, 0, 0, 0, 0, 0, 0, 0)); /// ``` - pub const fn unspecified() -> Ipv6Addr { - Ipv6Addr::new(0, 0, 0, 0, 0, 0, 0, 0) - } - - /// Returns the first 16-bit segment that makes up this address. - /// - /// # Examples - /// - /// ``` - /// use no_std_net::Ipv6Addr; - /// - /// assert_eq!(Ipv6Addr::new(0x0011, 0x2233, 0, 0, 0, 0, 0, 0).first_segment(), 0x11); - /// ``` - pub const fn first_segment(&self) -> u16 { - (self.inner[0] as u16) << 8 | (self.inner[1] as u16) - } - - /// Returns the second 16-bit segment that makes up this address. - /// - /// # Examples - /// - /// ``` - /// use no_std_net::Ipv6Addr; - /// - /// assert_eq!(Ipv6Addr::new(0x0011, 0x2233, 0, 0, 0, 0, 0, 0).second_segment(), 0x2233); - /// ``` - pub const fn second_segment(&self) -> u16 { - (self.inner[2] as u16) << 8 | (self.inner[3] as u16) - } + pub const UNSPECIFIED: Self = Ipv6Addr::new(0, 0, 0, 0, 0, 0, 0, 0); /// Returns the eight 16-bit segments that make up this address. /// @@ -962,8 +1084,9 @@ impl Ipv6Addr { /// assert_eq!(Ipv6Addr::new(0, 0, 0, 0, 0, 0xffff, 0xc00a, 0x2ff).segments(), /// [0, 0, 0, 0, 0, 0xffff, 0xc00a, 0x2ff]); /// ``` + #[inline] pub const fn segments(&self) -> [u16; 8] { - let arr = &self.inner; + let arr = self.octets(); [ (arr[0] as u16) << 8 | (arr[1] as u16), (arr[2] as u16) << 8 | (arr[3] as u16), @@ -976,12 +1099,11 @@ impl Ipv6Addr { ] } - /// Returns [`true`] for the special 'unspecified' address (::). + /// Returns [`true`] for the special 'unspecified' address (`::`). /// /// This property is defined in [IETF RFC 4291]. /// /// [IETF RFC 4291]: https://tools.ietf.org/html/rfc4291 - /// [`true`]: https://doc.rust-lang.org/std/primitive.bool.html /// /// # Examples /// @@ -991,15 +1113,9 @@ impl Ipv6Addr { /// assert_eq!(Ipv6Addr::new(0, 0, 0, 0, 0, 0xffff, 0xc00a, 0x2ff).is_unspecified(), false); /// assert_eq!(Ipv6Addr::new(0, 0, 0, 0, 0, 0, 0, 0).is_unspecified(), true); /// ``` + #[inline] pub const fn is_unspecified(&self) -> bool { - let mut i = 0; - while i < 16 { - if self.inner[i] != 0 { - return false; - } - i += 1 - } - true + u128::from_be_bytes(self.octets()) == u128::from_be_bytes(Ipv6Addr::UNSPECIFIED.octets()) } /// Returns [`true`] if this is a loopback address (::1). @@ -1007,7 +1123,6 @@ impl Ipv6Addr { /// This property is defined in [IETF RFC 4291]. /// /// [IETF RFC 4291]: https://tools.ietf.org/html/rfc4291 - /// [`true`]: https://doc.rust-lang.org/std/primitive.bool.html /// /// # Examples /// @@ -1017,16 +1132,9 @@ impl Ipv6Addr { /// assert_eq!(Ipv6Addr::new(0, 0, 0, 0, 0, 0xffff, 0xc00a, 0x2ff).is_loopback(), false); /// assert_eq!(Ipv6Addr::new(0, 0, 0, 0, 0, 0, 0, 0x1).is_loopback(), true); /// ``` + #[inline] pub const fn is_loopback(&self) -> bool { - let seg = self.segments(); - seg[0] == 0 - && seg[1] == 0 - && seg[2] == 0 - && seg[3] == 0 - && seg[4] == 0 - && seg[5] == 0 - && seg[6] == 0 - && seg[7] == 1 + u128::from_be_bytes(self.octets()) == u128::from_be_bytes(Ipv6Addr::LOCALHOST.octets()) } /// Returns [`true`] if the address appears to be globally routable. @@ -1034,23 +1142,22 @@ impl Ipv6Addr { /// The following return [`false`]: /// /// - the loopback address - /// - link-local, site-local, and unique local unicast addresses + /// - link-local and unique local unicast addresses /// - interface-, link-, realm-, admin- and site-local multicast addresses /// - /// [`true`]: https://doc.rust-lang.org/std/primitive.bool.html - /// [`false`]: https://doc.rust-lang.org/std/primitive.bool.html - /// /// # Examples /// /// ``` + /// // Requires `unstable_ip` feature + /// /// use no_std_net::Ipv6Addr; /// - /// fn main() { - /// assert_eq!(Ipv6Addr::new(0, 0, 0, 0, 0, 0xffff, 0xc00a, 0x2ff).is_global(), true); - /// assert_eq!(Ipv6Addr::new(0, 0, 0, 0, 0, 0, 0, 0x1).is_global(), false); - /// assert_eq!(Ipv6Addr::new(0, 0, 0x1c9, 0, 0, 0xafc8, 0, 0x1).is_global(), true); - /// } + /// assert_eq!(Ipv6Addr::new(0, 0, 0, 0, 0, 0xffff, 0xc00a, 0x2ff).is_global(), true); + /// assert_eq!(Ipv6Addr::new(0, 0, 0, 0, 0, 0, 0, 0x1).is_global(), false); + /// assert_eq!(Ipv6Addr::new(0, 0, 0x1c9, 0, 0, 0xafc8, 0, 0x1).is_global(), true); /// ``` + #[cfg(feature = "unstable_ip")] + #[inline] pub const fn is_global(&self) -> bool { match self.multicast_scope() { Some(Ipv6MulticastScope::Global) => true, @@ -1059,91 +1166,166 @@ impl Ipv6Addr { } } - /// Returns [`true`] if this is a unique local address (fc00::/7). + /// Returns [`true`] if this is a unique local address (`fc00::/7`). /// /// This property is defined in [IETF RFC 4193]. /// /// [IETF RFC 4193]: https://tools.ietf.org/html/rfc4193 - /// [`true`]: https://doc.rust-lang.org/std/primitive.bool.html /// /// # Examples /// /// ``` + /// // Requires `unstable_ip` feature + /// /// use no_std_net::Ipv6Addr; /// - /// fn main() { - /// assert_eq!(Ipv6Addr::new(0, 0, 0, 0, 0, 0xffff, 0xc00a, 0x2ff).is_unique_local(), - /// false); - /// assert_eq!(Ipv6Addr::new(0xfc02, 0, 0, 0, 0, 0, 0, 0).is_unique_local(), true); - /// } + /// assert_eq!(Ipv6Addr::new(0, 0, 0, 0, 0, 0xffff, 0xc00a, 0x2ff).is_unique_local(), false); + /// assert_eq!(Ipv6Addr::new(0xfc02, 0, 0, 0, 0, 0, 0, 0).is_unique_local(), true); /// ``` + #[cfg(feature = "unstable_ip")] + #[inline] pub const fn is_unique_local(&self) -> bool { - self.inner[0] & 0xfe == 0xfc + (self.segments()[0] & 0xfe00) == 0xfc00 } - /// Returns [`true`] if the address is unicast and link-local (fe80::/10). - /// - /// This property is defined in [IETF RFC 4291]. + /// Returns [`true`] if this is a unicast address, as defined by [IETF RFC 4291]. + /// Any address that is not a [multicast address] (`ff00::/8`) is unicast. /// /// [IETF RFC 4291]: https://tools.ietf.org/html/rfc4291 - /// [`true`]: https://doc.rust-lang.org/std/primitive.bool.html + /// [multicast address]: Ipv6Addr::is_multicast /// /// # Examples /// /// ``` + /// // Requires `unstable_ip` feature + /// /// use no_std_net::Ipv6Addr; /// - /// fn main() { - /// assert_eq!(Ipv6Addr::new(0, 0, 0, 0, 0, 0xffff, 0xc00a, 0x2ff).is_unicast_link_local(), - /// false); - /// assert_eq!(Ipv6Addr::new(0xfe8a, 0, 0, 0, 0, 0, 0, 0).is_unicast_link_local(), true); - /// } + /// // The unspecified and loopback addresses are unicast. + /// assert_eq!(Ipv6Addr::UNSPECIFIED.is_unicast(), true); + /// assert_eq!(Ipv6Addr::LOCALHOST.is_unicast(), true); + /// + /// // Any address that is not a multicast address (`ff00::/8`) is unicast. + /// assert_eq!(Ipv6Addr::new(0x2001, 0xdb8, 0, 0, 0, 0, 0, 0).is_unicast(), true); + /// assert_eq!(Ipv6Addr::new(0xff00, 0, 0, 0, 0, 0, 0, 0).is_unicast(), false); + /// ``` + #[cfg(feature = "unstable_ip")] + #[inline] + pub const fn is_unicast(&self) -> bool { + !self.is_multicast() + } + + /// Returns `true` if the address is a unicast address with link-local scope, + /// as defined in [RFC 4291]. + /// + /// A unicast address has link-local scope if it has the prefix `fe80::/10`, as per [RFC 4291 section 2.4]. + /// Note that this encompasses more addresses than those defined in [RFC 4291 section 2.5.6], + /// which describes "Link-Local IPv6 Unicast Addresses" as having the following stricter format: + /// + /// ```text + /// | 10 bits | 54 bits | 64 bits | + /// +----------+-------------------------+----------------------------+ + /// |1111111010| 0 | interface ID | + /// +----------+-------------------------+----------------------------+ /// ``` + /// So while currently the only addresses with link-local scope an application will encounter are all in `fe80::/64`, + /// this might change in the future with the publication of new standards. More addresses in `fe80::/10` could be allocated, + /// and those addresses will have link-local scope. + /// + /// Also note that while [RFC 4291 section 2.5.3] mentions about the [loopback address] (`::1`) that "it is treated as having Link-Local scope", + /// this does not mean that the loopback address actually has link-local scope and this method will return `false` on it. + /// + /// [RFC 4291]: https://tools.ietf.org/html/rfc4291 + /// [RFC 4291 section 2.4]: https://tools.ietf.org/html/rfc4291#section-2.4 + /// [RFC 4291 section 2.5.3]: https://tools.ietf.org/html/rfc4291#section-2.5.3 + /// [RFC 4291 section 2.5.6]: https://tools.ietf.org/html/rfc4291#section-2.5.6 + /// [loopback address]: Ipv6Addr::LOCALHOST + /// + /// # Examples + /// + /// ``` + /// // Requires `unstable_ip` feature + /// + /// use no_std_net::Ipv6Addr; + /// + /// // The loopback address (`::1`) does not actually have link-local scope. + /// assert_eq!(Ipv6Addr::LOCALHOST.is_unicast_link_local(), false); + /// + /// // Only addresses in `fe80::/10` have link-local scope. + /// assert_eq!(Ipv6Addr::new(0x2001, 0xdb8, 0, 0, 0, 0, 0, 0).is_unicast_link_local(), false); + /// assert_eq!(Ipv6Addr::new(0xfe80, 0, 0, 0, 0, 0, 0, 0).is_unicast_link_local(), true); + /// + /// // Addresses outside the stricter `fe80::/64` also have link-local scope. + /// assert_eq!(Ipv6Addr::new(0xfe80, 0, 0, 1, 0, 0, 0, 0).is_unicast_link_local(), true); + /// assert_eq!(Ipv6Addr::new(0xfe81, 0, 0, 0, 0, 0, 0, 0).is_unicast_link_local(), true); + /// ``` + #[cfg(feature = "unstable_ip")] + #[inline] pub const fn is_unicast_link_local(&self) -> bool { - self.first_segment() & 0xffc0 == 0xfe80 + (self.segments()[0] & 0xffc0) == 0xfe80 } - /// Returns [`true`] if this is a deprecated unicast site-local address - /// (fec0::/10). + /// Returns [`true`] if this is a deprecated unicast site-local address (`fec0::/10`). The + /// unicast site-local address format is defined in [RFC 4291 section 2.5.7] as: + /// + /// ```no_rust + /// | 10 | + /// | bits | 54 bits | 64 bits | + /// +----------+-------------------------+----------------------------+ + /// |1111111011| subnet ID | interface ID | + /// +----------+-------------------------+----------------------------+ + /// ``` /// - /// [`true`]: https://doc.rust-lang.org/std/primitive.bool.html + /// [RFC 4291 section 2.5.7]: https://tools.ietf.org/html/rfc4291#section-2.5.7 /// /// # Examples /// /// ``` + /// // Requires `unstable_ip` feature + /// /// use no_std_net::Ipv6Addr; /// - /// fn main() { - /// assert_eq!(Ipv6Addr::new(0, 0, 0, 0, 0, 0xffff, 0xc00a, 0x2ff).is_unicast_site_local(), - /// false); - /// assert_eq!(Ipv6Addr::new(0xfec2, 0, 0, 0, 0, 0, 0, 0).is_unicast_site_local(), true); - /// } + /// assert_eq!( + /// Ipv6Addr::new(0, 0, 0, 0, 0, 0xffff, 0xc00a, 0x2ff).is_unicast_site_local(), + /// false + /// ); + /// assert_eq!(Ipv6Addr::new(0xfec2, 0, 0, 0, 0, 0, 0, 0).is_unicast_site_local(), true); /// ``` + /// + /// # Warning + /// + /// As per [RFC 3879], the whole `fec0::/10` prefix is + /// deprecated. New software must not support site-local + /// addresses. + /// + /// [RFC 3879]: https://tools.ietf.org/html/rfc3879 + #[cfg(feature = "unstable_ip")] + #[inline] pub const fn is_unicast_site_local(&self) -> bool { - self.first_segment() & 0xffc0 == 0xfec0 + (self.segments()[0] & 0xffc0) == 0xfec0 } /// Returns [`true`] if this is an address reserved for documentation - /// (2001:db8::/32). + /// (`2001:db8::/32`). /// /// This property is defined in [IETF RFC 3849]. /// /// [IETF RFC 3849]: https://tools.ietf.org/html/rfc3849 - /// [`true`]: https://doc.rust-lang.org/std/primitive.bool.html /// /// # Examples /// /// ``` + /// // Requires `unstable_ip` feature + /// /// use no_std_net::Ipv6Addr; /// - /// fn main() { - /// assert_eq!(Ipv6Addr::new(0, 0, 0, 0, 0, 0xffff, 0xc00a, 0x2ff).is_documentation(), - /// false); - /// assert_eq!(Ipv6Addr::new(0x2001, 0xdb8, 0, 0, 0, 0, 0, 0).is_documentation(), true); - /// } + /// assert_eq!(Ipv6Addr::new(0, 0, 0, 0, 0, 0xffff, 0xc00a, 0x2ff).is_documentation(), false); + /// assert_eq!(Ipv6Addr::new(0x2001, 0xdb8, 0, 0, 0, 0, 0, 0).is_documentation(), true); /// ``` + #[cfg(feature = "unstable_ip")] + #[inline] pub const fn is_documentation(&self) -> bool { - self.first_segment() == 0x2001 && self.second_segment() == 0xdb8 + (self.segments()[0] == 0x2001) && (self.segments()[1] == 0xdb8) } /// Returns [`true`] if the address is a globally routable unicast address. @@ -1152,29 +1334,36 @@ impl Ipv6Addr { /// /// - the loopback address /// - the link-local addresses - /// - the (deprecated) site-local addresses /// - unique local addresses /// - the unspecified address /// - the address range reserved for documentation /// - /// [`true`]: https://doc.rust-lang.org/std/primitive.bool.html + /// This method returns [`true`] for site-local addresses as per [RFC 4291 section 2.5.7] + /// + /// ```no_rust + /// The special behavior of [the site-local unicast] prefix defined in [RFC3513] must no longer + /// be supported in new implementations (i.e., new implementations must treat this prefix as + /// Global Unicast). + /// ``` + /// + /// [RFC 4291 section 2.5.7]: https://tools.ietf.org/html/rfc4291#section-2.5.7 /// /// # Examples /// /// ``` + /// // Requires `unstable_ip` feature + /// /// use no_std_net::Ipv6Addr; /// - /// fn main() { - /// assert_eq!(Ipv6Addr::new(0x2001, 0xdb8, 0, 0, 0, 0, 0, 0).is_unicast_global(), false); - /// assert_eq!(Ipv6Addr::new(0, 0, 0, 0, 0, 0xffff, 0xc00a, 0x2ff).is_unicast_global(), - /// true); - /// } + /// assert_eq!(Ipv6Addr::new(0x2001, 0xdb8, 0, 0, 0, 0, 0, 0).is_unicast_global(), false); + /// assert_eq!(Ipv6Addr::new(0, 0, 0, 0, 0, 0xffff, 0xc00a, 0x2ff).is_unicast_global(), true); /// ``` + #[cfg(feature = "unstable_ip")] + #[inline] pub const fn is_unicast_global(&self) -> bool { - !self.is_multicast() + self.is_unicast() && !self.is_loopback() && !self.is_unicast_link_local() - && !self.is_unicast_site_local() && !self.is_unique_local() && !self.is_unspecified() && !self.is_documentation() @@ -1185,17 +1374,21 @@ impl Ipv6Addr { /// # Examples /// /// ``` + /// // Requires `unstable_ip` feature + /// /// use no_std_net::{Ipv6Addr, Ipv6MulticastScope}; /// - /// fn main() { - /// assert_eq!(Ipv6Addr::new(0xff0e, 0, 0, 0, 0, 0, 0, 0).multicast_scope(), - /// Some(Ipv6MulticastScope::Global)); - /// assert_eq!(Ipv6Addr::new(0, 0, 0, 0, 0, 0xffff, 0xc00a, 0x2ff).multicast_scope(), None); - /// } + /// assert_eq!( + /// Ipv6Addr::new(0xff0e, 0, 0, 0, 0, 0, 0, 0).multicast_scope(), + /// Some(Ipv6MulticastScope::Global) + /// ); + /// assert_eq!(Ipv6Addr::new(0, 0, 0, 0, 0, 0xffff, 0xc00a, 0x2ff).multicast_scope(), None); /// ``` + #[cfg(feature = "unstable_ip")] + #[inline] pub const fn multicast_scope(&self) -> Option { if self.is_multicast() { - match self.inner[1] & 0x0F { + match self.segments()[0] & 0x000f { 1 => Some(Ipv6MulticastScope::InterfaceLocal), 2 => Some(Ipv6MulticastScope::LinkLocal), 3 => Some(Ipv6MulticastScope::RealmLocal), @@ -1210,12 +1403,11 @@ impl Ipv6Addr { } } - /// Returns [`true`] if this is a multicast address (ff00::/8). + /// Returns [`true`] if this is a multicast address (`ff00::/8`). /// /// This property is defined by [IETF RFC 4291]. /// /// [IETF RFC 4291]: https://tools.ietf.org/html/rfc4291 - /// [`true`]: https://doc.rust-lang.org/std/primitive.bool.html /// /// # Examples /// @@ -1225,17 +1417,49 @@ impl Ipv6Addr { /// assert_eq!(Ipv6Addr::new(0xff00, 0, 0, 0, 0, 0, 0, 0).is_multicast(), true); /// assert_eq!(Ipv6Addr::new(0, 0, 0, 0, 0, 0xffff, 0xc00a, 0x2ff).is_multicast(), false); /// ``` + #[inline] pub const fn is_multicast(&self) -> bool { - self.inner[0] == 0xff + (self.segments()[0] & 0xff00) == 0xff00 } - /// Converts this address to an [IPv4 address]. Returns [`None`] if this address is + /// Converts this address to an [`IPv4` address] if it's an "IPv4-mapped IPv6 address" + /// defined in [IETF RFC 4291 section 2.5.5.2], otherwise returns [`None`]. + /// + /// `::ffff:a.b.c.d` becomes `a.b.c.d`. + /// All addresses *not* starting with `::ffff` will return `None`. + /// + /// [`IPv4` address]: Ipv4Addr + /// [IETF RFC 4291 section 2.5.5.2]: https://tools.ietf.org/html/rfc4291#section-2.5.5.2 + /// + /// # Examples + /// + /// ``` + /// // Requires `unstable_ip` feature + /// + /// use no_std_net::{Ipv4Addr, Ipv6Addr}; + /// + /// assert_eq!(Ipv6Addr::new(0xff00, 0, 0, 0, 0, 0, 0, 0).to_ipv4_mapped(), None); + /// assert_eq!(Ipv6Addr::new(0, 0, 0, 0, 0, 0xffff, 0xc00a, 0x2ff).to_ipv4_mapped(), + /// Some(Ipv4Addr::new(192, 10, 2, 255))); + /// assert_eq!(Ipv6Addr::new(0, 0, 0, 0, 0, 0, 0, 1).to_ipv4_mapped(), None); + /// ``` + #[cfg(feature = "unstable_ip")] + #[inline] + pub const fn to_ipv4_mapped(&self) -> Option { + match self.octets() { + [0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0xff, 0xff, a, b, c, d] => { + Some(Ipv4Addr::new(a, b, c, d)) + } + _ => None, + } + } + + /// Converts this address to an [`IPv4` address]. Returns [`None`] if this address is /// neither IPv4-compatible or IPv4-mapped. /// - /// ::a.b.c.d and ::ffff:a.b.c.d become a.b.c.d + /// `::a.b.c.d` and `::ffff:a.b.c.d` become `a.b.c.d` /// - /// [IPv4 address]: ../../no-std-net/struct.Ipv4Addr.html - /// [`None`]: https://doc.rust-lang.org/core/option/enum.Option.html#variant.None + /// [`IPv4` address]: Ipv4Addr /// /// # Examples /// @@ -1248,15 +1472,14 @@ impl Ipv6Addr { /// assert_eq!(Ipv6Addr::new(0, 0, 0, 0, 0, 0, 0, 1).to_ipv4(), /// Some(Ipv4Addr::new(0, 0, 0, 1))); /// ``` + #[inline] pub const fn to_ipv4(&self) -> Option { - match self.segments() { - [0, 0, 0, 0, 0, f, g, h] if f == 0 || f == 0xffff => Some(Ipv4Addr::new( - (g >> 8) as u8, - g as u8, - (h >> 8) as u8, - h as u8, - )), - _ => None, + if let [0, 0, 0, 0, 0, 0 | 0xffff, ab, cd] = self.segments() { + let [a, b] = ab.to_be_bytes(); + let [c, d] = cd.to_be_bytes(); + Some(Ipv4Addr::new(a, b, c, d)) + } else { + None } } @@ -1268,152 +1491,190 @@ impl Ipv6Addr { /// assert_eq!(Ipv6Addr::new(0xff00, 0, 0, 0, 0, 0, 0, 0).octets(), /// [255, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0]); /// ``` + #[inline] pub const fn octets(&self) -> [u8; 16] { - [ - self.inner[0], - self.inner[1], - self.inner[2], - self.inner[3], - self.inner[4], - self.inner[5], - self.inner[6], - self.inner[7], - self.inner[8], - self.inner[9], - self.inner[10], - self.inner[11], - self.inner[12], - self.inner[13], - self.inner[14], - self.inner[15], - ] + self.inner } } -impl ::fmt::Display for Ipv6Addr { - fn fmt(&self, fmt: &mut ::fmt::Formatter) -> ::fmt::Result { - match self.segments() { - // We need special cases for :: and ::1, otherwise they're formatted - // as ::0.0.0.[01] - [0, 0, 0, 0, 0, 0, 0, 0] => write!(fmt, "::"), - [0, 0, 0, 0, 0, 0, 0, 1] => write!(fmt, "::1"), - // Ipv4 Compatible address - [0, 0, 0, 0, 0, 0, g, h] => write!( - fmt, - "::{}.{}.{}.{}", - (g >> 8) as u8, - g as u8, - (h >> 8) as u8, - h as u8 - ), - // Ipv4-Mapped address - [0, 0, 0, 0, 0, 0xffff, g, h] => write!( - fmt, - "::ffff:{}.{}.{}.{}", - (g >> 8) as u8, - g as u8, - (h >> 8) as u8, - h as u8 - ), - _ => { - fn find_zero_slice(segments: &[u16; 8]) -> (usize, usize) { - let mut longest_span_len = 0; - let mut longest_span_at = 0; - let mut cur_span_len = 0; - let mut cur_span_at = 0; - - for i in 0..8 { - if segments[i] == 0 { - if cur_span_len == 0 { - cur_span_at = i; +/// Write an Ipv6Addr, conforming to the canonical style described by +/// [RFC 5952](https://tools.ietf.org/html/rfc5952). +impl fmt::Display for Ipv6Addr { + fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { + // If there are no alignment requirements, write out the IP address to + // f. Otherwise, write it to a local buffer, then use f.pad. + if f.precision().is_none() && f.width().is_none() { + let segments = self.segments(); + + // Special case for :: and ::1; otherwise they get written with the + // IPv4 formatter + if self.is_unspecified() { + f.write_str("::") + } else if self.is_loopback() { + f.write_str("::1") + } else if let Some(ipv4) = self.to_ipv4() { + match segments[5] { + // IPv4 Compatible address + 0 => write!(f, "::{}", ipv4), + // IPv4 Mapped address + 0xffff => write!(f, "::ffff:{}", ipv4), + _ => unreachable!(), + } + } else { + #[derive(Copy, Clone, Default)] + struct Span { + start: usize, + len: usize, + } + + // Find the inner 0 span + let zeroes = { + let mut longest = Span::default(); + let mut current = Span::default(); + + for (i, &segment) in segments.iter().enumerate() { + if segment == 0 { + if current.len == 0 { + current.start = i; } - cur_span_len += 1; + current.len += 1; - if cur_span_len > longest_span_len { - longest_span_len = cur_span_len; - longest_span_at = cur_span_at; + if current.len > longest.len { + longest = current; } } else { - cur_span_len = 0; - cur_span_at = 0; + current = Span::default(); } } - (longest_span_at, longest_span_len) - } - - let (zeros_at, zeros_len) = find_zero_slice(&self.segments()); - - if zeros_len > 1 { - fn fmt_subslice(segments: &[u16], fmt: &mut ::fmt::Formatter) -> ::fmt::Result { - if !segments.is_empty() { - write!(fmt, "{:x}", segments[0])?; - for &seg in &segments[1..] { - write!(fmt, ":{:x}", seg)?; - } + longest + }; + + /// Write a colon-separated part of the address + #[inline] + fn fmt_subslice(f: &mut fmt::Formatter<'_>, chunk: &[u16]) -> fmt::Result { + if let Some((first, tail)) = chunk.split_first() { + write!(f, "{:x}", first)?; + for segment in tail { + f.write_char(':')?; + write!(f, "{:x}", segment)?; } - Ok(()) } + Ok(()) + } - fmt_subslice(&self.segments()[..zeros_at], fmt)?; - fmt.write_str("::")?; - fmt_subslice(&self.segments()[zeros_at + zeros_len..], fmt) + if zeroes.len > 1 { + fmt_subslice(f, &segments[..zeroes.start])?; + f.write_str("::")?; + fmt_subslice(f, &segments[zeroes.start + zeroes.len..]) } else { - let &[a, b, c, d, e, f, g, h] = &self.segments(); - write!( - fmt, - "{:x}:{:x}:{:x}:{:x}:{:x}:{:x}:{:x}:{:x}", - a, b, c, d, e, f, g, h - ) + fmt_subslice(f, &segments) } } + } else { + // Slow path: write the address to a local buffer, the use f.pad. + // Defined recursively by using the fast path to write to the + // buffer. + + // This is the largest possible size of an IPv6 address + const IPV6_BUF_LEN: usize = (4 * 8) + 7; + let mut buf = [0u8; IPV6_BUF_LEN]; + let mut buf_slice = WriteHelper::new(&mut buf[..]); + + // Note: This call to write should never fail, so unwrap is okay. + write!(buf_slice, "{}", self).unwrap(); + let len = IPV6_BUF_LEN - buf_slice.into_raw().len(); + + // This is safe because we know exactly what can be in this buffer + let buf = unsafe { core::str::from_utf8_unchecked(&buf[..len]) }; + f.pad(buf) } } } -impl ::fmt::Debug for Ipv6Addr { - fn fmt(&self, fmt: &mut ::fmt::Formatter) -> ::fmt::Result { - ::fmt::Display::fmt(self, fmt) +impl fmt::Debug for Ipv6Addr { + fn fmt(&self, fmt: &mut fmt::Formatter<'_>) -> fmt::Result { + fmt::Display::fmt(self, fmt) + } +} + +impl Clone for Ipv6Addr { + #[inline] + fn clone(&self) -> Ipv6Addr { + *self + } +} + +impl PartialEq for Ipv6Addr { + #[inline] + fn eq(&self, other: &Ipv6Addr) -> bool { + self.inner == other.inner } } impl PartialEq for Ipv6Addr { + #[inline] fn eq(&self, other: &IpAddr) -> bool { - match *other { + match other { IpAddr::V4(_) => false, - IpAddr::V6(ref v6) => self == v6, + IpAddr::V6(v6) => self == v6, } } } impl PartialEq for IpAddr { + #[inline] fn eq(&self, other: &Ipv6Addr) -> bool { - match *self { + match self { IpAddr::V4(_) => false, - IpAddr::V6(ref v6) => v6 == other, + IpAddr::V6(v6) => v6 == other, } } } +impl Eq for Ipv6Addr {} + +impl hash::Hash for Ipv6Addr { + #[inline] + fn hash(&self, s: &mut H) { + self.inner.hash(s) + } +} + +impl PartialOrd for Ipv6Addr { + #[inline] + fn partial_cmp(&self, other: &Ipv6Addr) -> Option { + Some(self.cmp(other)) + } +} + impl PartialOrd for IpAddr { + #[inline] fn partial_cmp(&self, other: &Ipv6Addr) -> Option { - match *self { + match self { IpAddr::V4(_) => Some(Ordering::Less), - IpAddr::V6(ref v6) => v6.partial_cmp(other), + IpAddr::V6(v6) => v6.partial_cmp(other), } } } impl PartialOrd for Ipv6Addr { + #[inline] fn partial_cmp(&self, other: &IpAddr) -> Option { - match *other { + match other { IpAddr::V4(_) => Some(Ordering::Greater), - IpAddr::V6(ref v6) => self.partial_cmp(v6), + IpAddr::V6(v6) => self.partial_cmp(v6), } } } +impl Ord for Ipv6Addr { + #[inline] + fn cmp(&self, other: &Ipv6Addr) -> Ordering { + self.segments().cmp(&other.segments()) + } +} impl From for u128 { /// Convert an `Ipv6Addr` into a host byte order `u128`. /// @@ -1428,11 +1689,12 @@ impl From for u128 { /// ); /// assert_eq!(0x102030405060708090A0B0C0D0E0F00D_u128, u128::from(addr)); /// ``` + #[inline] fn from(ip: Ipv6Addr) -> u128 { - u128::from_be_bytes(ip.inner) + let ip = ip.octets(); + u128::from_be_bytes(ip) } } - impl From for Ipv6Addr { /// Convert a host byte order `u128` into an `Ipv6Addr`. /// @@ -1449,11 +1711,121 @@ impl From for Ipv6Addr { /// ), /// addr); /// ``` + #[inline] fn from(ip: u128) -> Ipv6Addr { - Ipv6Addr { - inner: u128::to_be_bytes(ip), - } + Ipv6Addr::from(ip.to_be_bytes()) + } +} + +impl From<[u8; 16]> for Ipv6Addr { + /// Creates an `Ipv6Addr` from a sixteen element byte array. + /// + /// # Examples + /// + /// ``` + /// use no_std_net::Ipv6Addr; + /// + /// let addr = Ipv6Addr::from([ + /// 25u8, 24u8, 23u8, 22u8, 21u8, 20u8, 19u8, 18u8, + /// 17u8, 16u8, 15u8, 14u8, 13u8, 12u8, 11u8, 10u8, + /// ]); + /// assert_eq!( + /// Ipv6Addr::new( + /// 0x1918, 0x1716, + /// 0x1514, 0x1312, + /// 0x1110, 0x0f0e, + /// 0x0d0c, 0x0b0a + /// ), + /// addr + /// ); + /// ``` + #[inline] + fn from(octets: [u8; 16]) -> Ipv6Addr { + Ipv6Addr { inner: octets } } } -// TODO: add module tests here! +impl From<[u16; 8]> for Ipv6Addr { + /// Creates an `Ipv6Addr` from an eight element 16-bit array. + /// + /// # Examples + /// + /// ``` + /// use no_std_net::Ipv6Addr; + /// + /// let addr = Ipv6Addr::from([ + /// 525u16, 524u16, 523u16, 522u16, + /// 521u16, 520u16, 519u16, 518u16, + /// ]); + /// assert_eq!( + /// Ipv6Addr::new( + /// 0x20d, 0x20c, + /// 0x20b, 0x20a, + /// 0x209, 0x208, + /// 0x207, 0x206 + /// ), + /// addr + /// ); + /// ``` + #[inline] + fn from(segments: [u16; 8]) -> Ipv6Addr { + let [a, b, c, d, e, f, g, h] = segments; + Ipv6Addr::new(a, b, c, d, e, f, g, h) + } +} + +impl From<[u8; 16]> for IpAddr { + /// Creates an `IpAddr::V6` from a sixteen element byte array. + /// + /// # Examples + /// + /// ``` + /// use no_std_net::{IpAddr, Ipv6Addr}; + /// + /// let addr = IpAddr::from([ + /// 25u8, 24u8, 23u8, 22u8, 21u8, 20u8, 19u8, 18u8, + /// 17u8, 16u8, 15u8, 14u8, 13u8, 12u8, 11u8, 10u8, + /// ]); + /// assert_eq!( + /// IpAddr::V6(Ipv6Addr::new( + /// 0x1918, 0x1716, + /// 0x1514, 0x1312, + /// 0x1110, 0x0f0e, + /// 0x0d0c, 0x0b0a + /// )), + /// addr + /// ); + /// ``` + #[inline] + fn from(octets: [u8; 16]) -> IpAddr { + IpAddr::V6(Ipv6Addr::from(octets)) + } +} + +impl From<[u16; 8]> for IpAddr { + /// Creates an `IpAddr::V6` from an eight element 16-bit array. + /// + /// # Examples + /// + /// ``` + /// use no_std_net::{IpAddr, Ipv6Addr}; + /// + /// let addr = IpAddr::from([ + /// 525u16, 524u16, 523u16, 522u16, + /// 521u16, 520u16, 519u16, 518u16, + /// ]); + /// assert_eq!( + /// IpAddr::V6(Ipv6Addr::new( + /// 0x20d, 0x20c, + /// 0x20b, 0x20a, + /// 0x209, 0x208, + /// 0x207, 0x206 + /// )), + /// addr + /// ); + /// ``` + #[inline] + fn from(segments: [u16; 8]) -> IpAddr { + IpAddr::V6(Ipv6Addr::from(segments)) + } +} diff --git a/src/ip/tests.rs b/src/ip/tests.rs new file mode 100644 index 0000000..13a5d8e --- /dev/null +++ b/src/ip/tests.rs @@ -0,0 +1,1090 @@ +// A lot of things are behind cfg(feature = "unstable_ip") +#![allow(unused_variables)] +use alloc::string::ToString; +use core::str::FromStr; + +use crate::test::{sa4, sa6, tsa}; +use crate::*; + +#[test] +fn test_from_str_ipv4() { + assert_eq!(Ok(Ipv4Addr::new(127, 0, 0, 1)), "127.0.0.1".parse()); + assert_eq!( + Ok(Ipv4Addr::new(255, 255, 255, 255)), + "255.255.255.255".parse() + ); + assert_eq!(Ok(Ipv4Addr::new(0, 0, 0, 0)), "0.0.0.0".parse()); + + // out of range + let none: Option = "256.0.0.1".parse().ok(); + assert_eq!(None, none); + // too short + let none: Option = "255.0.0".parse().ok(); + assert_eq!(None, none); + // too long + let none: Option = "255.0.0.1.2".parse().ok(); + assert_eq!(None, none); + // no number between dots + let none: Option = "255.0..1".parse().ok(); + assert_eq!(None, none); +} + +#[test] +fn test_from_str_ipv6() { + assert_eq!( + Ok(Ipv6Addr::new(0, 0, 0, 0, 0, 0, 0, 0)), + "0:0:0:0:0:0:0:0".parse() + ); + assert_eq!( + Ok(Ipv6Addr::new(0, 0, 0, 0, 0, 0, 0, 1)), + "0:0:0:0:0:0:0:1".parse() + ); + + assert_eq!(Ok(Ipv6Addr::new(0, 0, 0, 0, 0, 0, 0, 1)), "::1".parse()); + assert_eq!(Ok(Ipv6Addr::new(0, 0, 0, 0, 0, 0, 0, 0)), "::".parse()); + + assert_eq!( + Ok(Ipv6Addr::new(0x2a02, 0x6b8, 0, 0, 0, 0, 0x11, 0x11)), + "2a02:6b8::11:11".parse() + ); + + // too long group + let none: Option = "::00000".parse().ok(); + assert_eq!(None, none); + // too short + let none: Option = "1:2:3:4:5:6:7".parse().ok(); + assert_eq!(None, none); + // too long + let none: Option = "1:2:3:4:5:6:7:8:9".parse().ok(); + assert_eq!(None, none); + // triple colon + let none: Option = "1:2:::6:7:8".parse().ok(); + assert_eq!(None, none); + // two double colons + let none: Option = "1:2::6::8".parse().ok(); + assert_eq!(None, none); + // `::` indicating zero groups of zeros + let none: Option = "1:2:3:4::5:6:7:8".parse().ok(); + assert_eq!(None, none); +} + +#[test] +fn test_from_str_ipv4_in_ipv6() { + assert_eq!( + Ok(Ipv6Addr::new(0, 0, 0, 0, 0, 0, 49152, 545)), + "::192.0.2.33".parse() + ); + assert_eq!( + Ok(Ipv6Addr::new(0, 0, 0, 0, 0, 0xFFFF, 49152, 545)), + "::FFFF:192.0.2.33".parse() + ); + assert_eq!( + Ok(Ipv6Addr::new(0x64, 0xff9b, 0, 0, 0, 0, 49152, 545)), + "64:ff9b::192.0.2.33".parse() + ); + assert_eq!( + Ok(Ipv6Addr::new( + 0x2001, 0xdb8, 0x122, 0xc000, 0x2, 0x2100, 49152, 545 + )), + "2001:db8:122:c000:2:2100:192.0.2.33".parse() + ); + + // colon after v4 + let none: Option = "::127.0.0.1:".parse().ok(); + assert_eq!(None, none); + // not enough groups + let none: Option = "1.2.3.4.5:127.0.0.1".parse().ok(); + assert_eq!(None, none); + // too many groups + let none: Option = "1.2.3.4.5:6:7:127.0.0.1".parse().ok(); + assert_eq!(None, none); +} + +#[test] +fn test_from_str_socket_addr() { + assert_eq!( + Ok(sa4(Ipv4Addr::new(77, 88, 21, 11), 80)), + "77.88.21.11:80".parse() + ); + assert_eq!( + Ok(SocketAddrV4::new(Ipv4Addr::new(77, 88, 21, 11), 80)), + "77.88.21.11:80".parse() + ); + assert_eq!( + Ok(sa6(Ipv6Addr::new(0x2a02, 0x6b8, 0, 1, 0, 0, 0, 1), 53)), + "[2a02:6b8:0:1::1]:53".parse() + ); + assert_eq!( + Ok(SocketAddrV6::new( + Ipv6Addr::new(0x2a02, 0x6b8, 0, 1, 0, 0, 0, 1), + 53, + 0, + 0 + )), + "[2a02:6b8:0:1::1]:53".parse() + ); + assert_eq!( + Ok(sa6(Ipv6Addr::new(0, 0, 0, 0, 0, 0, 0x7F00, 1), 22)), + "[::127.0.0.1]:22".parse() + ); + assert_eq!( + Ok(SocketAddrV6::new( + Ipv6Addr::new(0, 0, 0, 0, 0, 0, 0x7F00, 1), + 22, + 0, + 0 + )), + "[::127.0.0.1]:22".parse() + ); + + // without port + let none: Option = "127.0.0.1".parse().ok(); + assert_eq!(None, none); + // without port + let none: Option = "127.0.0.1:".parse().ok(); + assert_eq!(None, none); + // wrong brackets around v4 + let none: Option = "[127.0.0.1]:22".parse().ok(); + assert_eq!(None, none); + // port out of range + let none: Option = "127.0.0.1:123456".parse().ok(); + assert_eq!(None, none); +} + +#[test] +fn ipv4_addr_to_string() { + assert_eq!(Ipv4Addr::new(127, 0, 0, 1).to_string(), "127.0.0.1"); + // Short address + assert_eq!(Ipv4Addr::new(1, 1, 1, 1).to_string(), "1.1.1.1"); + // Long address + assert_eq!( + Ipv4Addr::new(127, 127, 127, 127).to_string(), + "127.127.127.127" + ); + + // Test padding + assert_eq!( + &format!("{:16}", Ipv4Addr::new(1, 1, 1, 1)), + "1.1.1.1 " + ); + assert_eq!( + &format!("{:>16}", Ipv4Addr::new(1, 1, 1, 1)), + " 1.1.1.1" + ); +} + +#[test] +fn ipv6_addr_to_string() { + // ipv4-mapped address + let a1 = Ipv6Addr::new(0, 0, 0, 0, 0, 0xffff, 0xc000, 0x280); + assert_eq!(a1.to_string(), "::ffff:192.0.2.128"); + + // ipv4-compatible address + let a1 = Ipv6Addr::new(0, 0, 0, 0, 0, 0, 0xc000, 0x280); + assert_eq!(a1.to_string(), "::192.0.2.128"); + + // v6 address with no zero segments + assert_eq!( + Ipv6Addr::new(8, 9, 10, 11, 12, 13, 14, 15).to_string(), + "8:9:a:b:c:d:e:f" + ); + + // longest possible IPv6 length + assert_eq!( + Ipv6Addr::new(0x1111, 0x2222, 0x3333, 0x4444, 0x5555, 0x6666, 0x7777, 0x8888).to_string(), + "1111:2222:3333:4444:5555:6666:7777:8888" + ); + // padding + assert_eq!( + &format!("{:20}", Ipv6Addr::new(1, 2, 3, 4, 5, 6, 7, 8)), + "1:2:3:4:5:6:7:8 " + ); + assert_eq!( + &format!("{:>20}", Ipv6Addr::new(1, 2, 3, 4, 5, 6, 7, 8)), + " 1:2:3:4:5:6:7:8" + ); + + // reduce a single run of zeros + assert_eq!( + "ae::ffff:102:304", + Ipv6Addr::new(0xae, 0, 0, 0, 0, 0xffff, 0x0102, 0x0304).to_string() + ); + + // don't reduce just a single zero segment + assert_eq!( + "1:2:3:4:5:6:0:8", + Ipv6Addr::new(1, 2, 3, 4, 5, 6, 0, 8).to_string() + ); + + // 'any' address + assert_eq!("::", Ipv6Addr::new(0, 0, 0, 0, 0, 0, 0, 0).to_string()); + + // loopback address + assert_eq!("::1", Ipv6Addr::new(0, 0, 0, 0, 0, 0, 0, 1).to_string()); + + // ends in zeros + assert_eq!("1::", Ipv6Addr::new(1, 0, 0, 0, 0, 0, 0, 0).to_string()); + + // two runs of zeros, second one is longer + assert_eq!( + "1:0:0:4::8", + Ipv6Addr::new(1, 0, 0, 4, 0, 0, 0, 8).to_string() + ); + + // two runs of zeros, equal length + assert_eq!( + "1::4:5:0:0:8", + Ipv6Addr::new(1, 0, 0, 4, 5, 0, 0, 8).to_string() + ); + + // don't prefix `0x` to each segment in `dbg!`. + assert_eq!( + "1::4:5:0:0:8", + &format!("{:#?}", Ipv6Addr::new(1, 0, 0, 4, 5, 0, 0, 8)) + ); +} + +#[test] +fn ipv4_to_ipv6() { + assert_eq!( + Ipv6Addr::new(0, 0, 0, 0, 0, 0xffff, 0x1234, 0x5678), + Ipv4Addr::new(0x12, 0x34, 0x56, 0x78).to_ipv6_mapped() + ); + assert_eq!( + Ipv6Addr::new(0, 0, 0, 0, 0, 0, 0x1234, 0x5678), + Ipv4Addr::new(0x12, 0x34, 0x56, 0x78).to_ipv6_compatible() + ); +} + +#[cfg(feature = "unstable_ip")] +#[test] +fn ipv6_to_ipv4_mapped() { + assert_eq!( + Ipv6Addr::new(0, 0, 0, 0, 0, 0xffff, 0x1234, 0x5678).to_ipv4_mapped(), + Some(Ipv4Addr::new(0x12, 0x34, 0x56, 0x78)) + ); + assert_eq!( + Ipv6Addr::new(0, 0, 0, 0, 0, 0, 0x1234, 0x5678).to_ipv4_mapped(), + None + ); +} + +#[test] +fn ipv6_to_ipv4() { + assert_eq!( + Ipv6Addr::new(0, 0, 0, 0, 0, 0xffff, 0x1234, 0x5678).to_ipv4(), + Some(Ipv4Addr::new(0x12, 0x34, 0x56, 0x78)) + ); + assert_eq!( + Ipv6Addr::new(0, 0, 0, 0, 0, 0, 0x1234, 0x5678).to_ipv4(), + Some(Ipv4Addr::new(0x12, 0x34, 0x56, 0x78)) + ); + assert_eq!( + Ipv6Addr::new(0, 0, 1, 0, 0, 0, 0x1234, 0x5678).to_ipv4(), + None + ); +} + +#[test] +fn ip_properties() { + macro_rules! ip { + ($s:expr) => { + IpAddr::from_str($s).unwrap() + }; + } + + macro_rules! check { + ($s:expr) => { + check!($s, 0); + }; + + ($s:expr, $mask:expr) => {{ + let unspec: u8 = 1 << 0; + let loopback: u8 = 1 << 1; + let global: u8 = 1 << 2; + let multicast: u8 = 1 << 3; + let doc: u8 = 1 << 4; + + if ($mask & unspec) == unspec { + assert!(ip!($s).is_unspecified()); + } else { + assert!(!ip!($s).is_unspecified()); + } + + if ($mask & loopback) == loopback { + assert!(ip!($s).is_loopback()); + } else { + assert!(!ip!($s).is_loopback()); + } + + #[cfg(feature = "unstable_ip")] + { + if ($mask & global) == global { + assert!(ip!($s).is_global()); + } else { + assert!(!ip!($s).is_global()); + } + } + + if ($mask & multicast) == multicast { + assert!(ip!($s).is_multicast()); + } else { + assert!(!ip!($s).is_multicast()); + } + + #[cfg(feature = "unstable_ip")] + { + if ($mask & doc) == doc { + assert!(ip!($s).is_documentation()); + } else { + assert!(!ip!($s).is_documentation()); + } + } + }}; + } + + let unspec: u8 = 1 << 0; + let loopback: u8 = 1 << 1; + let global: u8 = 1 << 2; + let multicast: u8 = 1 << 3; + let doc: u8 = 1 << 4; + + check!("0.0.0.0", unspec); + check!("0.0.0.1"); + check!("0.1.0.0"); + check!("10.9.8.7"); + check!("127.1.2.3", loopback); + check!("172.31.254.253"); + check!("169.254.253.242"); + check!("192.0.2.183", doc); + check!("192.1.2.183", global); + check!("192.168.254.253"); + check!("198.51.100.0", doc); + check!("203.0.113.0", doc); + check!("203.2.113.0", global); + check!("224.0.0.0", global | multicast); + check!("239.255.255.255", global | multicast); + check!("255.255.255.255"); + // make sure benchmarking addresses are not global + check!("198.18.0.0"); + check!("198.18.54.2"); + check!("198.19.255.255"); + // make sure addresses reserved for protocol assignment are not global + check!("192.0.0.0"); + check!("192.0.0.255"); + check!("192.0.0.100"); + // make sure reserved addresses are not global + check!("240.0.0.0"); + check!("251.54.1.76"); + check!("254.255.255.255"); + // make sure shared addresses are not global + check!("100.64.0.0"); + check!("100.127.255.255"); + check!("100.100.100.0"); + + check!("::", unspec); + check!("::1", loopback); + check!("::0.0.0.2", global); + check!("1::", global); + check!("fc00::"); + check!("fdff:ffff::"); + check!("fe80:ffff::"); + check!("febf:ffff::"); + check!("fec0::", global); + check!("ff01::", multicast); + check!("ff02::", multicast); + check!("ff03::", multicast); + check!("ff04::", multicast); + check!("ff05::", multicast); + check!("ff08::", multicast); + check!("ff0e::", global | multicast); + check!("2001:db8:85a3::8a2e:370:7334", doc); + check!("102:304:506:708:90a:b0c:d0e:f10", global); +} + +#[test] +fn ipv4_properties() { + macro_rules! ip { + ($s:expr) => { + Ipv4Addr::from_str($s).unwrap() + }; + } + + macro_rules! check { + ($s:expr) => { + check!($s, 0); + }; + + ($s:expr, $mask:expr) => {{ + let unspec: u16 = 1 << 0; + let loopback: u16 = 1 << 1; + let private: u16 = 1 << 2; + let link_local: u16 = 1 << 3; + let global: u16 = 1 << 4; + let multicast: u16 = 1 << 5; + let broadcast: u16 = 1 << 6; + let documentation: u16 = 1 << 7; + let benchmarking: u16 = 1 << 8; + let ietf_protocol_assignment: u16 = 1 << 9; + let reserved: u16 = 1 << 10; + let shared: u16 = 1 << 11; + + if ($mask & unspec) == unspec { + assert!(ip!($s).is_unspecified()); + } else { + assert!(!ip!($s).is_unspecified()); + } + + if ($mask & loopback) == loopback { + assert!(ip!($s).is_loopback()); + } else { + assert!(!ip!($s).is_loopback()); + } + + if ($mask & private) == private { + assert!(ip!($s).is_private()); + } else { + assert!(!ip!($s).is_private()); + } + + if ($mask & link_local) == link_local { + assert!(ip!($s).is_link_local()); + } else { + assert!(!ip!($s).is_link_local()); + } + + #[cfg(feature = "unstable_ip")] + { + if ($mask & global) == global { + assert!(ip!($s).is_global()); + } else { + assert!(!ip!($s).is_global()); + } + } + + if ($mask & multicast) == multicast { + assert!(ip!($s).is_multicast()); + } else { + assert!(!ip!($s).is_multicast()); + } + + if ($mask & broadcast) == broadcast { + assert!(ip!($s).is_broadcast()); + } else { + assert!(!ip!($s).is_broadcast()); + } + + if ($mask & documentation) == documentation { + assert!(ip!($s).is_documentation()); + } else { + assert!(!ip!($s).is_documentation()); + } + + #[cfg(feature = "unstable_ip")] + { + if ($mask & benchmarking) == benchmarking { + assert!(ip!($s).is_benchmarking()); + } else { + assert!(!ip!($s).is_benchmarking()); + } + + if ($mask & ietf_protocol_assignment) == ietf_protocol_assignment { + assert!(ip!($s).is_ietf_protocol_assignment()); + } else { + assert!(!ip!($s).is_ietf_protocol_assignment()); + } + + if ($mask & reserved) == reserved { + assert!(ip!($s).is_reserved()); + } else { + assert!(!ip!($s).is_reserved()); + } + + if ($mask & shared) == shared { + assert!(ip!($s).is_shared()); + } else { + assert!(!ip!($s).is_shared()); + } + } + }}; + } + + let unspec: u16 = 1 << 0; + let loopback: u16 = 1 << 1; + let private: u16 = 1 << 2; + let link_local: u16 = 1 << 3; + let global: u16 = 1 << 4; + let multicast: u16 = 1 << 5; + let broadcast: u16 = 1 << 6; + let documentation: u16 = 1 << 7; + let benchmarking: u16 = 1 << 8; + let ietf_protocol_assignment: u16 = 1 << 9; + let reserved: u16 = 1 << 10; + let shared: u16 = 1 << 11; + + check!("0.0.0.0", unspec); + check!("0.0.0.1"); + check!("0.1.0.0"); + check!("10.9.8.7", private); + check!("127.1.2.3", loopback); + check!("172.31.254.253", private); + check!("169.254.253.242", link_local); + check!("192.0.2.183", documentation); + check!("192.1.2.183", global); + check!("192.168.254.253", private); + check!("198.51.100.0", documentation); + check!("203.0.113.0", documentation); + check!("203.2.113.0", global); + check!("224.0.0.0", global | multicast); + check!("239.255.255.255", global | multicast); + check!("255.255.255.255", broadcast); + check!("198.18.0.0", benchmarking); + check!("198.18.54.2", benchmarking); + check!("198.19.255.255", benchmarking); + check!("192.0.0.0", ietf_protocol_assignment); + check!("192.0.0.255", ietf_protocol_assignment); + check!("192.0.0.100", ietf_protocol_assignment); + check!("240.0.0.0", reserved); + check!("251.54.1.76", reserved); + check!("254.255.255.255", reserved); + check!("100.64.0.0", shared); + check!("100.127.255.255", shared); + check!("100.100.100.0", shared); +} + +#[test] +fn ipv6_properties() { + macro_rules! ip { + ($s:expr) => { + Ipv6Addr::from_str($s).unwrap() + }; + } + + macro_rules! check { + ($s:expr, &[$($octet:expr),*], $mask:expr) => { + assert_eq!($s, ip!($s).to_string()); + let octets = &[$($octet),*]; + assert_eq!(&ip!($s).octets(), octets); + assert_eq!(Ipv6Addr::from(*octets), ip!($s)); + + let unspecified: u16 = 1 << 0; + let loopback: u16 = 1 << 1; + let unique_local: u16 = 1 << 2; + let global: u16 = 1 << 3; + let unicast_link_local: u16 = 1 << 4; + let unicast_site_local: u16 = 1 << 6; + let unicast_global: u16 = 1 << 7; + let documentation: u16 = 1 << 8; + let multicast_interface_local: u16 = 1 << 9; + let multicast_link_local: u16 = 1 << 10; + let multicast_realm_local: u16 = 1 << 11; + let multicast_admin_local: u16 = 1 << 12; + let multicast_site_local: u16 = 1 << 13; + let multicast_organization_local: u16 = 1 << 14; + let multicast_global: u16 = 1 << 15; + let multicast: u16 = multicast_interface_local + | multicast_admin_local + | multicast_global + | multicast_link_local + | multicast_realm_local + | multicast_site_local + | multicast_organization_local; + + if ($mask & unspecified) == unspecified { + assert!(ip!($s).is_unspecified()); + } else { + assert!(!ip!($s).is_unspecified()); + } + if ($mask & loopback) == loopback { + assert!(ip!($s).is_loopback()); + } else { + assert!(!ip!($s).is_loopback()); + } + #[cfg(feature = "unstable_ip")] + { + if ($mask & unique_local) == unique_local { + assert!(ip!($s).is_unique_local()); + } else { + assert!(!ip!($s).is_unique_local()); + } + if ($mask & global) == global { + assert!(ip!($s).is_global()); + } else { + assert!(!ip!($s).is_global()); + } + if ($mask & unicast_link_local) == unicast_link_local { + assert!(ip!($s).is_unicast_link_local()); + } else { + assert!(!ip!($s).is_unicast_link_local()); + } + if ($mask & unicast_site_local) == unicast_site_local { + assert!(ip!($s).is_unicast_site_local()); + } else { + assert!(!ip!($s).is_unicast_site_local()); + } + if ($mask & unicast_global) == unicast_global { + assert!(ip!($s).is_unicast_global()); + } else { + assert!(!ip!($s).is_unicast_global()); + } + if ($mask & documentation) == documentation { + assert!(ip!($s).is_documentation()); + } else { + assert!(!ip!($s).is_documentation()); + } + if ($mask & multicast) != 0 { + assert!(ip!($s).multicast_scope().is_some()); + assert!(ip!($s).is_multicast()); + } else { + assert!(ip!($s).multicast_scope().is_none()); + assert!(!ip!($s).is_multicast()); + } + if ($mask & multicast_interface_local) == multicast_interface_local { + assert_eq!(ip!($s).multicast_scope().unwrap(), + Ipv6MulticastScope::InterfaceLocal); + } + if ($mask & multicast_link_local) == multicast_link_local { + assert_eq!(ip!($s).multicast_scope().unwrap(), + Ipv6MulticastScope::LinkLocal); + } + if ($mask & multicast_realm_local) == multicast_realm_local { + assert_eq!(ip!($s).multicast_scope().unwrap(), + Ipv6MulticastScope::RealmLocal); + } + if ($mask & multicast_admin_local) == multicast_admin_local { + assert_eq!(ip!($s).multicast_scope().unwrap(), + Ipv6MulticastScope::AdminLocal); + } + if ($mask & multicast_site_local) == multicast_site_local { + assert_eq!(ip!($s).multicast_scope().unwrap(), + Ipv6MulticastScope::SiteLocal); + } + if ($mask & multicast_organization_local) == multicast_organization_local { + assert_eq!(ip!($s).multicast_scope().unwrap(), + Ipv6MulticastScope::OrganizationLocal); + } + if ($mask & multicast_global) == multicast_global { + assert_eq!(ip!($s).multicast_scope().unwrap(), + Ipv6MulticastScope::Global); + } + } + } + } + + let unspecified: u16 = 1 << 0; + let loopback: u16 = 1 << 1; + let unique_local: u16 = 1 << 2; + let global: u16 = 1 << 3; + let unicast_link_local: u16 = 1 << 4; + let unicast_site_local: u16 = 1 << 6; + let unicast_global: u16 = 1 << 7; + let documentation: u16 = 1 << 8; + let multicast_interface_local: u16 = 1 << 9; + let multicast_link_local: u16 = 1 << 10; + let multicast_realm_local: u16 = 1 << 11; + let multicast_admin_local: u16 = 1 << 12; + let multicast_site_local: u16 = 1 << 13; + let multicast_organization_local: u16 = 1 << 14; + let multicast_global: u16 = 1 << 15; + + check!( + "::", + &[0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0], + unspecified + ); + + check!( + "::1", + &[0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 1], + loopback + ); + + check!( + "::0.0.0.2", + &[0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 2], + global | unicast_global + ); + + check!( + "1::", + &[0, 1, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0], + global | unicast_global + ); + + check!( + "fc00::", + &[0xfc, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0], + unique_local + ); + + check!( + "fdff:ffff::", + &[0xfd, 0xff, 0xff, 0xff, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0], + unique_local + ); + + check!( + "fe80:ffff::", + &[0xfe, 0x80, 0xff, 0xff, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0], + unicast_link_local + ); + + check!( + "fe80::", + &[0xfe, 0x80, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0], + unicast_link_local + ); + + check!( + "febf:ffff::", + &[0xfe, 0xbf, 0xff, 0xff, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0], + unicast_link_local + ); + + check!( + "febf::", + &[0xfe, 0xbf, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0], + unicast_link_local + ); + + check!( + "febf:ffff:ffff:ffff:ffff:ffff:ffff:ffff", + &[ + 0xfe, 0xbf, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, + 0xff, 0xff + ], + unicast_link_local + ); + + check!( + "fe80::ffff:ffff:ffff:ffff", + &[ + 0xfe, 0x80, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, + 0xff, 0xff + ], + unicast_link_local + ); + + check!( + "fe80:0:0:1::", + &[0xfe, 0x80, 0, 0, 0, 0, 0, 1, 0, 0, 0, 0, 0, 0, 0, 0], + unicast_link_local + ); + + check!( + "fec0::", + &[0xfe, 0xc0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0], + unicast_site_local | unicast_global | global + ); + + check!( + "ff01::", + &[0xff, 1, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0], + multicast_interface_local + ); + + check!( + "ff02::", + &[0xff, 2, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0], + multicast_link_local + ); + + check!( + "ff03::", + &[0xff, 3, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0], + multicast_realm_local + ); + + check!( + "ff04::", + &[0xff, 4, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0], + multicast_admin_local + ); + + check!( + "ff05::", + &[0xff, 5, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0], + multicast_site_local + ); + + check!( + "ff08::", + &[0xff, 8, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0], + multicast_organization_local + ); + + check!( + "ff0e::", + &[0xff, 0xe, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0], + multicast_global | global + ); + + check!( + "2001:db8:85a3::8a2e:370:7334", + &[0x20, 1, 0xd, 0xb8, 0x85, 0xa3, 0, 0, 0, 0, 0x8a, 0x2e, 3, 0x70, 0x73, 0x34], + documentation + ); + + check!( + "102:304:506:708:90a:b0c:d0e:f10", + &[1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13, 14, 15, 16], + global | unicast_global + ); +} + +#[test] +fn to_socket_addr_socketaddr() { + let a = sa4(Ipv4Addr::new(77, 88, 21, 11), 12345); + assert_eq!(Ok(vec![a]), tsa(a)); +} + +#[test] +fn test_ipv4_to_int() { + let a = Ipv4Addr::new(0x11, 0x22, 0x33, 0x44); + assert_eq!(u32::from(a), 0x11223344); +} + +#[test] +fn test_int_to_ipv4() { + let a = Ipv4Addr::new(0x11, 0x22, 0x33, 0x44); + assert_eq!(Ipv4Addr::from(0x11223344), a); +} + +#[test] +fn test_ipv6_to_int() { + let a = Ipv6Addr::new( + 0x1122, 0x3344, 0x5566, 0x7788, 0x99aa, 0xbbcc, 0xddee, 0xff11, + ); + assert_eq!(u128::from(a), 0x112233445566778899aabbccddeeff11u128); +} + +#[test] +fn test_int_to_ipv6() { + let a = Ipv6Addr::new( + 0x1122, 0x3344, 0x5566, 0x7788, 0x99aa, 0xbbcc, 0xddee, 0xff11, + ); + assert_eq!(Ipv6Addr::from(0x112233445566778899aabbccddeeff11u128), a); +} + +#[test] +fn ipv4_from_constructors() { + assert_eq!(Ipv4Addr::LOCALHOST, Ipv4Addr::new(127, 0, 0, 1)); + assert!(Ipv4Addr::LOCALHOST.is_loopback()); + assert_eq!(Ipv4Addr::UNSPECIFIED, Ipv4Addr::new(0, 0, 0, 0)); + assert!(Ipv4Addr::UNSPECIFIED.is_unspecified()); + assert_eq!(Ipv4Addr::BROADCAST, Ipv4Addr::new(255, 255, 255, 255)); + assert!(Ipv4Addr::BROADCAST.is_broadcast()); +} + +#[test] +fn ipv6_from_contructors() { + assert_eq!(Ipv6Addr::LOCALHOST, Ipv6Addr::new(0, 0, 0, 0, 0, 0, 0, 1)); + assert!(Ipv6Addr::LOCALHOST.is_loopback()); + assert_eq!(Ipv6Addr::UNSPECIFIED, Ipv6Addr::new(0, 0, 0, 0, 0, 0, 0, 0)); + assert!(Ipv6Addr::UNSPECIFIED.is_unspecified()); +} + +#[test] +fn ipv4_from_octets() { + assert_eq!(Ipv4Addr::from([127, 0, 0, 1]), Ipv4Addr::new(127, 0, 0, 1)) +} + +#[test] +fn ipv6_from_segments() { + let from_u16s = Ipv6Addr::from([ + 0x0011, 0x2233, 0x4455, 0x6677, 0x8899, 0xaabb, 0xccdd, 0xeeff, + ]); + let new = Ipv6Addr::new( + 0x0011, 0x2233, 0x4455, 0x6677, 0x8899, 0xaabb, 0xccdd, 0xeeff, + ); + assert_eq!(new, from_u16s); +} + +#[test] +fn ipv6_from_octets() { + let from_u16s = Ipv6Addr::from([ + 0x0011, 0x2233, 0x4455, 0x6677, 0x8899, 0xaabb, 0xccdd, 0xeeff, + ]); + let from_u8s = Ipv6Addr::from([ + 0x00, 0x11, 0x22, 0x33, 0x44, 0x55, 0x66, 0x77, 0x88, 0x99, 0xaa, 0xbb, 0xcc, 0xdd, 0xee, + 0xff, + ]); + assert_eq!(from_u16s, from_u8s); +} + +#[test] +fn cmp() { + let v41 = Ipv4Addr::new(100, 64, 3, 3); + let v42 = Ipv4Addr::new(192, 0, 2, 2); + let v61 = "2001:db8:f00::1002".parse::().unwrap(); + let v62 = "2001:db8:f00::2001".parse::().unwrap(); + assert!(v41 < v42); + assert!(v61 < v62); + + assert_eq!(v41, IpAddr::V4(v41)); + assert_eq!(v61, IpAddr::V6(v61)); + assert!(v41 != IpAddr::V4(v42)); + assert!(v61 != IpAddr::V6(v62)); + + assert!(v41 < IpAddr::V4(v42)); + assert!(v61 < IpAddr::V6(v62)); + assert!(IpAddr::V4(v41) < v42); + assert!(IpAddr::V6(v61) < v62); + + assert!(v41 < IpAddr::V6(v61)); + assert!(IpAddr::V4(v41) < v61); +} + +#[test] +fn is_v4() { + let ip = IpAddr::V4(Ipv4Addr::new(100, 64, 3, 3)); + assert!(ip.is_ipv4()); + assert!(!ip.is_ipv6()); +} + +#[test] +fn is_v6() { + let ip = IpAddr::V6(Ipv6Addr::new(0, 0, 0, 0, 0, 0xffff, 0x1234, 0x5678)); + assert!(!ip.is_ipv4()); + assert!(ip.is_ipv6()); +} + +#[test] +fn ipv4_const() { + // test that the methods of `Ipv4Addr` are usable in a const context + + const IP_ADDRESS: Ipv4Addr = Ipv4Addr::new(127, 0, 0, 1); + assert_eq!(IP_ADDRESS, Ipv4Addr::LOCALHOST); + + const OCTETS: [u8; 4] = IP_ADDRESS.octets(); + assert_eq!(OCTETS, [127, 0, 0, 1]); + + const IS_UNSPECIFIED: bool = IP_ADDRESS.is_unspecified(); + assert!(!IS_UNSPECIFIED); + + const IS_LOOPBACK: bool = IP_ADDRESS.is_loopback(); + assert!(IS_LOOPBACK); + + const IS_PRIVATE: bool = IP_ADDRESS.is_private(); + assert!(!IS_PRIVATE); + + const IS_LINK_LOCAL: bool = IP_ADDRESS.is_link_local(); + assert!(!IS_LINK_LOCAL); + + #[cfg(feature = "unstable_ip")] + { + const IS_GLOBAL: bool = IP_ADDRESS.is_global(); + assert!(!IS_GLOBAL); + + const IS_SHARED: bool = IP_ADDRESS.is_shared(); + assert!(!IS_SHARED); + + const IS_IETF_PROTOCOL_ASSIGNMENT: bool = IP_ADDRESS.is_ietf_protocol_assignment(); + assert!(!IS_IETF_PROTOCOL_ASSIGNMENT); + + const IS_BENCHMARKING: bool = IP_ADDRESS.is_benchmarking(); + assert!(!IS_BENCHMARKING); + + const IS_RESERVED: bool = IP_ADDRESS.is_reserved(); + assert!(!IS_RESERVED); + } + + const IS_MULTICAST: bool = IP_ADDRESS.is_multicast(); + assert!(!IS_MULTICAST); + + const IS_BROADCAST: bool = IP_ADDRESS.is_broadcast(); + assert!(!IS_BROADCAST); + + const IS_DOCUMENTATION: bool = IP_ADDRESS.is_documentation(); + assert!(!IS_DOCUMENTATION); + + const IP_V6_COMPATIBLE: Ipv6Addr = IP_ADDRESS.to_ipv6_compatible(); + assert_eq!( + IP_V6_COMPATIBLE, + Ipv6Addr::from([0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 127, 0, 0, 1]) + ); + + const IP_V6_MAPPED: Ipv6Addr = IP_ADDRESS.to_ipv6_mapped(); + assert_eq!( + IP_V6_MAPPED, + Ipv6Addr::from([0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 255, 255, 127, 0, 0, 1]) + ); +} + +#[test] +fn ipv6_const() { + // test that the methods of `Ipv6Addr` are usable in a const context + + const IP_ADDRESS: Ipv6Addr = Ipv6Addr::new(0, 0, 0, 0, 0, 0, 0, 1); + assert_eq!(IP_ADDRESS, Ipv6Addr::LOCALHOST); + + const SEGMENTS: [u16; 8] = IP_ADDRESS.segments(); + assert_eq!(SEGMENTS, [0, 0, 0, 0, 0, 0, 0, 1]); + + const OCTETS: [u8; 16] = IP_ADDRESS.octets(); + assert_eq!(OCTETS, [0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 1]); + + const IS_UNSPECIFIED: bool = IP_ADDRESS.is_unspecified(); + assert!(!IS_UNSPECIFIED); + + const IS_LOOPBACK: bool = IP_ADDRESS.is_loopback(); + assert!(IS_LOOPBACK); + + #[cfg(feature = "unstable_ip")] + { + const IS_GLOBAL: bool = IP_ADDRESS.is_global(); + assert!(!IS_GLOBAL); + + const IS_UNIQUE_LOCAL: bool = IP_ADDRESS.is_unique_local(); + assert!(!IS_UNIQUE_LOCAL); + + const IS_UNICAST_LINK_LOCAL: bool = IP_ADDRESS.is_unicast_link_local(); + assert!(!IS_UNICAST_LINK_LOCAL); + + const IS_UNICAST_SITE_LOCAL: bool = IP_ADDRESS.is_unicast_site_local(); + assert!(!IS_UNICAST_SITE_LOCAL); + + const IS_DOCUMENTATION: bool = IP_ADDRESS.is_documentation(); + assert!(!IS_DOCUMENTATION); + + const IS_UNICAST_GLOBAL: bool = IP_ADDRESS.is_unicast_global(); + assert!(!IS_UNICAST_GLOBAL); + + const MULTICAST_SCOPE: Option = IP_ADDRESS.multicast_scope(); + assert_eq!(MULTICAST_SCOPE, None); + } + + const IS_MULTICAST: bool = IP_ADDRESS.is_multicast(); + assert!(!IS_MULTICAST); + + const IP_V4: Option = IP_ADDRESS.to_ipv4(); + assert_eq!(IP_V4.unwrap(), Ipv4Addr::new(0, 0, 0, 1)); +} + +#[test] +fn ip_const() { + // test that the methods of `IpAddr` are usable in a const context + + const IP_ADDRESS: IpAddr = IpAddr::V4(Ipv4Addr::LOCALHOST); + + const IS_UNSPECIFIED: bool = IP_ADDRESS.is_unspecified(); + assert!(!IS_UNSPECIFIED); + + const IS_LOOPBACK: bool = IP_ADDRESS.is_loopback(); + assert!(IS_LOOPBACK); + + #[cfg(feature = "unstable_ip")] + { + const IS_GLOBAL: bool = IP_ADDRESS.is_global(); + assert!(!IS_GLOBAL); + } + + const IS_MULTICAST: bool = IP_ADDRESS.is_multicast(); + assert!(!IS_MULTICAST); + + const IS_IP_V4: bool = IP_ADDRESS.is_ipv4(); + assert!(IS_IP_V4); + + const IS_IP_V6: bool = IP_ADDRESS.is_ipv6(); + assert!(!IS_IP_V6); +} diff --git a/src/lib.rs b/src/lib.rs index cf0cdf1..8f110fb 100644 --- a/src/lib.rs +++ b/src/lib.rs @@ -23,44 +23,68 @@ //! with networking objects like [`TcpListener`], [`TcpStream`] or [`UdpSocket`] //! * Other types are return or parameter types for various methods in this module //! -//! [`IpAddr`]: ../../no-std-net/enum.IpAddr.html -//! [`Ipv4Addr`]: ../../no-std-net/struct.Ipv4Addr.html -//! [`Ipv6Addr`]: ../../no-std-net/struct.Ipv6Addr.html -//! [`SocketAddr`]: ../../std/net/enum.SocketAddr.html -//! [`SocketAddrV4`]: ../../std/net/struct.SocketAddrV4.html -//! [`SocketAddrV6`]: ../../std/net/struct.SocketAddrV6.html -//! [`TcpListener`]: ../../std/net/struct.TcpListener.html -//! [`TcpStream`]: ../../std/net/struct.TcpStream.html -//! [`ToSocketAddrs`]: ../../std/net/trait.ToSocketAddrs.html -//! [`UdpSocket`]: ../../std/net/struct.UdpSocket.html - -// TODO: figure out how to put links into rustdocs and update the above - +#![cfg_attr(feature = "std", doc = "[`TcpListener`]: std::net::TcpListener")] +#![cfg_attr(feature = "std", doc = "[`TcpStream`]: std::net::TcpStream")] +#![cfg_attr(feature = "std", doc = "[`UdpSocket`]: std::net::UdpSocket")] +#![cfg_attr( + not(feature = "std"), + doc = "[`TcpListener`]: https://doc.rust-lang.org/std/net/struct.TcpListener.html" +)] +#![cfg_attr( + not(feature = "std"), + doc = "[`TcpStream`]: https://doc.rust-lang.org/std/net/struct.TcpStream.html" +)] +#![cfg_attr( + not(feature = "std"), + doc = "[`UdpSocket`]: https://doc.rust-lang.org/std/net/struct.UdpSocket.html" +)] #![no_std] #![deny( - dead_code, - missing_docs, - unused_imports, - unused_must_use, - unused_parens, - unused_qualifications, - warnings, + dead_code, + missing_docs, + unused_imports, + unused_must_use, + unused_parens, + unused_qualifications, + warnings )] -#![forbid(unsafe_code)] - -use core::fmt; +#![cfg_attr(all(feature = "std", feature = "unstable_ip"), feature(ip))] +#[cfg(not(feature = "std"))] mod addr; +#[cfg(not(feature = "std"))] +mod helper; +#[cfg(not(feature = "std"))] mod ip; +#[cfg(not(feature = "std"))] mod parser; +#[cfg(all(not(feature = "std"), test))] +mod test; + +#[cfg(all(not(feature = "std"), test))] +#[macro_use] +extern crate alloc; -#[cfg(feature = "serde")] +#[cfg(all(not(feature = "std"), feature = "serde"))] extern crate serde; -#[cfg(feature = "serde")] +#[cfg(all(not(feature = "std"), feature = "serde"))] mod de; -#[cfg(feature = "serde")] +#[cfg(all(not(feature = "std"), feature = "serde"))] mod ser; +#[cfg(not(feature = "std"))] +pub use addr::{SocketAddr, SocketAddrV4, SocketAddrV6, ToSocketAddrs}; +#[cfg(all(not(feature = "std"), feature = "unstable_ip"))] +pub use ip::Ipv6MulticastScope; +#[cfg(not(feature = "std"))] +pub use ip::{IpAddr, Ipv4Addr, Ipv6Addr}; -pub use addr::{ SocketAddr, SocketAddrV4, SocketAddrV6, ToSocketAddrs }; -pub use ip::{ IpAddr, Ipv4Addr, Ipv6Addr, Ipv6MulticastScope }; +// Re-export std::net types when std is available +#[cfg(feature = "std")] +extern crate std; +#[cfg(all(feature = "std", feature = "unstable_ip"))] +pub use std::net::Ipv6MulticastScope; +#[cfg(feature = "std")] +pub use std::net::{ + IpAddr, Ipv4Addr, Ipv6Addr, SocketAddr, SocketAddrV4, SocketAddrV6, ToSocketAddrs, +}; diff --git a/src/parser.rs b/src/parser.rs index 10f07af..5c64419 100644 --- a/src/parser.rs +++ b/src/parser.rs @@ -4,333 +4,323 @@ //! A private parser implementation of IPv4, IPv6, and socket addresses. //! -//! This module is "publicly exported" through the `FromStr` implementations below. +//! This module is "publicly exported" through the `FromStr` implementations +//! below. +#[cfg(test)] +mod tests; + +use core::convert::TryInto; +use core::fmt; use core::str::FromStr; -use ::{IpAddr, Ipv4Addr, Ipv6Addr, SocketAddr, SocketAddrV4, SocketAddrV6}; + +use super::{IpAddr, Ipv4Addr, Ipv6Addr, SocketAddr, SocketAddrV4, SocketAddrV6}; + +trait ReadNumberHelper: Sized { + const ZERO: Self; + fn checked_mul(&self, other: u32) -> Option; + fn checked_add(&self, other: u32) -> Option; +} + +macro_rules! impl_helper { + ($($t:ty)*) => ($(impl ReadNumberHelper for $t { + const ZERO: Self = 0; + #[inline] + fn checked_mul(&self, other: u32) -> Option { + Self::checked_mul(*self, other.try_into().ok()?) + } + #[inline] + fn checked_add(&self, other: u32) -> Option { + Self::checked_add(*self, other.try_into().ok()?) + } + })*) +} + +impl_helper! { u8 u16 u32 } struct Parser<'a> { - // parsing as ASCII, so can use byte array - s: &'a [u8], - pos: usize, + // Parsing as ASCII, so can use byte array. + state: &'a [u8], } impl<'a> Parser<'a> { - fn new(s: &'a str) -> Parser<'a> { + fn new(input: &'a str) -> Parser<'a> { Parser { - s: s.as_bytes(), - pos: 0, + state: input.as_bytes(), } } - fn is_eof(&self) -> bool { - self.pos == self.s.len() - } - - // Commit only if parser returns Some - fn read_atomically(&mut self, cb: F) -> Option where - F: FnOnce(&mut Parser) -> Option, + /// Run a parser, and restore the pre-parse state if it fails. + fn read_atomically(&mut self, inner: F) -> Option + where + F: FnOnce(&mut Parser<'_>) -> Option, { - let pos = self.pos; - let r = cb(self); - if r.is_none() { - self.pos = pos; + let state = self.state; + let result = inner(self); + if result.is_none() { + self.state = state; } - r + result } - // Commit only if parser read till EOF - fn read_till_eof(&mut self, cb: F) -> Option where - F: FnOnce(&mut Parser) -> Option, + /// Run a parser, but fail if the entire input wasn't consumed. + /// Doesn't run atomically. + fn parse_with(&mut self, inner: F) -> Result + where + F: FnOnce(&mut Parser<'_>) -> Option, { - self.read_atomically(move |p| { - match cb(p) { - Some(x) => if p.is_eof() {Some(x)} else {None}, - None => None, - } - }) + let result = inner(self); + if self.state.is_empty() { result } else { None }.ok_or(AddrParseError(())) } - // Apply 3 parsers sequentially - fn read_seq_3(&mut self, - pa: PA, - pb: PB, - pc: PC) - -> Option<(A, B, C)> where - PA: FnOnce(&mut Parser) -> Option, - PB: FnOnce(&mut Parser) -> Option, - PC: FnOnce(&mut Parser) -> Option, - { - self.read_atomically(move |p| { - let a = pa(p); - let b = if a.is_some() { pb(p) } else { None }; - let c = if b.is_some() { pc(p) } else { None }; - match (a, b, c) { - (Some(a), Some(b), Some(c)) => Some((a, b, c)), - _ => None - } - }) + /// Peek the next character from the input + fn peek_char(&self) -> Option { + self.state.first().map(|&b| char::from(b)) } - // Read next char + /// Read the next character from the input fn read_char(&mut self) -> Option { - if self.is_eof() { - None - } else { - let r = self.s[self.pos] as char; - self.pos += 1; - Some(r) - } + self.state.split_first().map(|(&b, tail)| { + self.state = tail; + char::from(b) + }) } - // Return char and advance iff next char is equal to requested - fn read_given_char(&mut self, c: char) -> Option { + #[must_use] + /// Read the next character from the input if it matches the target. + fn read_given_char(&mut self, target: char) -> Option<()> { self.read_atomically(|p| { - match p.read_char() { - Some(next) if next == c => Some(next), - _ => None, - } + p.read_char() + .and_then(|c| if c == target { Some(()) } else { None }) }) } - // Read digit - fn read_digit(&mut self, radix: u8) -> Option { - fn parse_digit(c: char, radix: u8) -> Option { - let c = c as u8; - // assuming radix is either 10 or 16 - if c >= b'0' && c <= b'9' { - Some(c - b'0') - } else if radix > 10 && c >= b'a' && c < b'a' + (radix - 10) { - Some(c - b'a' + 10) - } else if radix > 10 && c >= b'A' && c < b'A' + (radix - 10) { - Some(c - b'A' + 10) - } else { - None + /// Helper for reading separators in an indexed loop. Reads the separator + /// character iff index > 0, then runs the parser. When used in a loop, + /// the separator character will only be read on index > 0 (see + /// read_ipv4_addr for an example) + fn read_separator(&mut self, sep: char, index: usize, inner: F) -> Option + where + F: FnOnce(&mut Parser<'_>) -> Option, + { + self.read_atomically(move |p| { + if index > 0 { + p.read_given_char(sep)?; } - } - - self.read_atomically(|p| { - p.read_char().and_then(|c| parse_digit(c, radix)) + inner(p) }) } - fn read_number_impl(&mut self, radix: u8, max_digits: u32, upto: u32) -> Option { - let mut r = 0; - let mut digit_count = 0; - loop { - match self.read_digit(radix) { - Some(d) => { - r = r * (radix as u32) + (d as u32); - digit_count += 1; - if digit_count > max_digits || r >= upto { - return None - } - } - None => { - if digit_count == 0 { - return None - } else { - return Some(r) + // Read a number off the front of the input in the given radix, stopping + // at the first non-digit character or eof. Fails if the number has more + // digits than max_digits or if there is no number. + fn read_number( + &mut self, + radix: u32, + max_digits: Option, + ) -> Option { + self.read_atomically(move |p| { + let mut result = T::ZERO; + let mut digit_count = 0; + + while let Some(digit) = p.read_atomically(|p| p.read_char()?.to_digit(radix)) { + result = result.checked_mul(radix)?; + result = result.checked_add(digit)?; + digit_count += 1; + if let Some(max_digits) = max_digits { + if digit_count > max_digits { + return None; } } - }; - } - } - - // Read number, failing if max_digits of number value exceeded - fn read_number(&mut self, radix: u8, max_digits: u32, upto: u32) -> Option { - self.read_atomically(|p| p.read_number_impl(radix, max_digits, upto)) - } - - fn read_ipv4_addr_impl(&mut self) -> Option { - let mut bs = [0; 4]; - let mut i = 0; - while i < 4 { - if i != 0 && self.read_given_char('.').is_none() { - return None; } - bs[i] = self.read_number(10, 3, 0x100).map(|n| n as u8)?; - i += 1; - } - Some(Ipv4Addr::new(bs[0], bs[1], bs[2], bs[3])) + if digit_count == 0 { + None + } else { + Some(result) + } + }) } - // Read IPv4 address + /// Read an IPv4 address. fn read_ipv4_addr(&mut self) -> Option { - self.read_atomically(|p| p.read_ipv4_addr_impl()) - } + self.read_atomically(|p| { + let mut groups = [0; 4]; + + for (i, slot) in groups.iter_mut().enumerate() { + *slot = p.read_separator('.', i, |p| { + // Disallow octal number in IP string. + // https://tools.ietf.org/html/rfc6943#section-3.1.1 + match (p.peek_char(), p.read_number(10, None)) { + (Some('0'), Some(number)) if number != 0 => None, + (_, number) => number, + } + })?; + } - fn read_ipv6_addr_impl(&mut self) -> Option { - fn ipv6_addr_from_head_tail(head: &[u16], tail: &[u16]) -> Ipv6Addr { - assert!(head.len() + tail.len() <= 8); - let mut gs = [0; 8]; - gs[..head.len()].copy_from_slice(head); - gs[(8 - tail.len()) .. 8].copy_from_slice(tail); - Ipv6Addr::new(gs[0], gs[1], gs[2], gs[3], gs[4], gs[5], gs[6], gs[7]) - } + Some(groups.into()) + }) + } - fn read_groups(p: &mut Parser, groups: &mut [u16; 8], limit: usize) - -> (usize, bool) { - let mut i = 0; - while i < limit { + /// Read an IPv6 Address. + fn read_ipv6_addr(&mut self) -> Option { + /// Read a chunk of an IPv6 address into `groups`. Returns the number + /// of groups read, along with a bool indicating if an embedded + /// trailing IPv4 address was read. Specifically, read a series of + /// colon-separated IPv6 groups (0x0000 - 0xFFFF), with an optional + /// trailing embedded IPv4 address. + fn read_groups(p: &mut Parser<'_>, groups: &mut [u16]) -> (usize, bool) { + let limit = groups.len(); + + for (i, slot) in groups.iter_mut().enumerate() { + // Try to read a trailing embedded IPv4 address. There must be + // at least two groups left. if i < limit - 1 { - let ipv4 = p.read_atomically(|p| { - if i == 0 || p.read_given_char(':').is_some() { - p.read_ipv4_addr() - } else { - None - } - }); + let ipv4 = p.read_separator(':', i, |p| p.read_ipv4_addr()); + if let Some(v4_addr) = ipv4 { - let octets = v4_addr.octets(); - groups[i + 0] = ((octets[0] as u16) << 8) | (octets[1] as u16); - groups[i + 1] = ((octets[2] as u16) << 8) | (octets[3] as u16); + let [one, two, three, four] = v4_addr.octets(); + groups[i + 0] = u16::from_be_bytes([one, two]); + groups[i + 1] = u16::from_be_bytes([three, four]); return (i + 2, true); } } - let group = p.read_atomically(|p| { - if i == 0 || p.read_given_char(':').is_some() { - p.read_number(16, 4, 0x10000).map(|n| n as u16) - } else { - None - } - }); + let group = p.read_separator(':', i, |p| p.read_number(16, Some(4))); + match group { - Some(g) => groups[i] = g, - None => return (i, false) + Some(g) => *slot = g, + None => return (i, false), } - i += 1; } - (i, false) + (groups.len(), false) } - let mut head = [0; 8]; - let (head_size, head_ipv4) = read_groups(self, &mut head, 8); + self.read_atomically(|p| { + // Read the front part of the address; either the whole thing, or up + // to the first :: + let mut head = [0; 8]; + let (head_size, head_ipv4) = read_groups(p, &mut head); - if head_size == 8 { - return Some(Ipv6Addr::new( - head[0], head[1], head[2], head[3], - head[4], head[5], head[6], head[7])) - } + if head_size == 8 { + return Some(head.into()); + } - // IPv4 part is not allowed before `::` - if head_ipv4 { - return None - } + // IPv4 part is not allowed before `::` + if head_ipv4 { + return None; + } - // read `::` if previous code parsed less than 8 groups - if !self.read_given_char(':').is_some() || !self.read_given_char(':').is_some() { - return None; - } + // Read `::` if previous code parsed less than 8 groups. + // `::` indicates one or more groups of 16 bits of zeros. + p.read_given_char(':')?; + p.read_given_char(':')?; + + // Read the back part of the address. The :: must contain at least one + // set of zeroes, so our max length is 7. + let mut tail = [0; 7]; + let limit = 8 - (head_size + 1); + let (tail_size, _) = read_groups(p, &mut tail[..limit]); + + // Concat the head and tail of the IP address + head[(8 - tail_size)..8].copy_from_slice(&tail[..tail_size]); + + Some(head.into()) + }) + } - let mut tail = [0; 8]; - // `::` indicates one or more groups of 16 bits of zeros - let limit = 8 - (head_size + 1); - let (tail_size, _) = read_groups(self, &mut tail, limit); - Some(ipv6_addr_from_head_tail(&head[..head_size], &tail[..tail_size])) + /// Read an IP Address, either IPv4 or IPv6. + fn read_ip_addr(&mut self) -> Option { + self.read_ipv4_addr() + .map(IpAddr::V4) + .or_else(move || self.read_ipv6_addr().map(IpAddr::V6)) } - fn read_ipv6_addr(&mut self) -> Option { - self.read_atomically(|p| p.read_ipv6_addr_impl()) + /// Read a `:` followed by a port in base 10. + fn read_port(&mut self) -> Option { + self.read_atomically(|p| { + p.read_given_char(':')?; + p.read_number(10, None) + }) } + /// Read a `%` followed by a scope ID in base 10. + fn read_scope_id(&mut self) -> Option { + self.read_atomically(|p| { + p.read_given_char('%')?; + p.read_number(10, None) + }) + } + + /// Read an IPv4 address with a port. fn read_socket_addr_v4(&mut self) -> Option { - let ip_addr = |p: &mut Parser| p.read_ipv4_addr(); - let colon = |p: &mut Parser| p.read_given_char(':'); - let port = |p: &mut Parser| { - p.read_number(10, 5, 0x10000).map(|n| n as u16) - }; - - self.read_seq_3(ip_addr, colon, port).map(|t| { - let (ip, _, port): (Ipv4Addr, char, u16) = t; - SocketAddrV4::new(ip, port) + self.read_atomically(|p| { + let ip = p.read_ipv4_addr()?; + let port = p.read_port()?; + Some(SocketAddrV4::new(ip, port)) }) } + /// Read an IPv6 address with a port. fn read_socket_addr_v6(&mut self) -> Option { - let ip_addr = |p: &mut Parser| { - let open_br = |p: &mut Parser| p.read_given_char('['); - let ip_addr = |p: &mut Parser| p.read_ipv6_addr(); - let clos_br = |p: &mut Parser| p.read_given_char(']'); - p.read_seq_3(open_br, ip_addr, clos_br).map(|t| t.1) - }; - let colon = |p: &mut Parser| p.read_given_char(':'); - let port = |p: &mut Parser| { - p.read_number(10, 5, 0x10000).map(|n| n as u16) - }; - - self.read_seq_3(ip_addr, colon, port).map(|t| { - let (ip, _, port): (Ipv6Addr, char, u16) = t; - SocketAddrV6::new(ip, port, 0, 0) + self.read_atomically(|p| { + p.read_given_char('[')?; + let ip = p.read_ipv6_addr()?; + let scope_id = p.read_scope_id().unwrap_or(0); + p.read_given_char(']')?; + + let port = p.read_port()?; + Some(SocketAddrV6::new(ip, port, 0, scope_id)) }) } + + /// Read an IP address with a port + fn read_socket_addr(&mut self) -> Option { + self.read_socket_addr_v4() + .map(SocketAddr::V4) + .or_else(|| self.read_socket_addr_v6().map(SocketAddr::V6)) + } } impl FromStr for IpAddr { type Err = AddrParseError; fn from_str(s: &str) -> Result { - if let Some(addr) = Parser::new(s).read_till_eof(|p| p.read_ipv4_addr()) { - Ok(IpAddr::V4(addr)) - } else if let Some(addr) = Parser::new(s).read_till_eof(|p| p.read_ipv6_addr()) { - Ok(IpAddr::V6(addr)) - } else { - Err(AddrParseError(())) - } + Parser::new(s).parse_with(|p| p.read_ip_addr()) } } impl FromStr for Ipv4Addr { type Err = AddrParseError; fn from_str(s: &str) -> Result { - match Parser::new(s).read_till_eof(|p| p.read_ipv4_addr()) { - Some(s) => Ok(s), - None => Err(AddrParseError(())) - } + Parser::new(s).parse_with(|p| p.read_ipv4_addr()) } } impl FromStr for Ipv6Addr { type Err = AddrParseError; fn from_str(s: &str) -> Result { - match Parser::new(s).read_till_eof(|p| p.read_ipv6_addr()) { - Some(s) => Ok(s), - None => Err(AddrParseError(())) - } + Parser::new(s).parse_with(|p| p.read_ipv6_addr()) } } impl FromStr for SocketAddrV4 { type Err = AddrParseError; fn from_str(s: &str) -> Result { - match Parser::new(s).read_till_eof(|p| p.read_socket_addr_v4()) { - Some(s) => Ok(s), - None => Err(AddrParseError(())), - } + Parser::new(s).parse_with(|p| p.read_socket_addr_v4()) } } impl FromStr for SocketAddrV6 { type Err = AddrParseError; fn from_str(s: &str) -> Result { - match Parser::new(s).read_till_eof(|p| p.read_socket_addr_v6()) { - Some(s) => Ok(s), - None => Err(AddrParseError(())), - } + Parser::new(s).parse_with(|p| p.read_socket_addr_v6()) } } impl FromStr for SocketAddr { type Err = AddrParseError; fn from_str(s: &str) -> Result { - if let Some(addr) = Parser::new(s).read_till_eof(|p| p.read_socket_addr_v4()) { - Ok(SocketAddr::V4(addr)) - } else if let Some(addr) = Parser::new(s).read_till_eof(|p| p.read_socket_addr_v6()) { - Ok(SocketAddr::V6(addr)) - } else { - Err(AddrParseError(())) - } + Parser::new(s).parse_with(|p| p.read_socket_addr()) } } @@ -358,19 +348,11 @@ impl FromStr for SocketAddr { /// // No problem, the `panic!` message has disappeared. /// let _foo: SocketAddr = "127.0.0.1:8080".parse().expect("unreachable panic"); /// ``` -/// -/// [`FromStr`]: ../../std/str/trait.FromStr.html -/// [`IpAddr`]: ../../no-std-net/enum.IpAddr.html -/// [`Ipv4Addr`]: ../../no-std-net/struct.Ipv4Addr.html -/// [`Ipv6Addr`]: ../../no-std-net/struct.Ipv6Addr.html -/// [`SocketAddr`]: ../../no-std-net/enum.SocketAddr.html -/// [`SocketAddrV4`]: ../../no-std-net/struct.SocketAddrV4.html -/// [`SocketAddrV6`]: ../../no-std-net/struct.SocketAddrV6.html #[derive(Debug, Clone, PartialEq, Eq)] pub struct AddrParseError(()); -impl ::fmt::Display for AddrParseError { - fn fmt(&self, fmt: &mut ::fmt::Formatter) -> ::fmt::Result { +impl fmt::Display for AddrParseError { + fn fmt(&self, fmt: &mut fmt::Formatter<'_>) -> fmt::Result { fmt.write_str("invalid IP address syntax") } } diff --git a/src/parser/tests.rs b/src/parser/tests.rs new file mode 100644 index 0000000..0368a99 --- /dev/null +++ b/src/parser/tests.rs @@ -0,0 +1,155 @@ +// FIXME: These tests are all excellent candidates for AFL fuzz testing +use crate::{IpAddr, Ipv4Addr, Ipv6Addr, SocketAddr, SocketAddrV4, SocketAddrV6}; +use core::str::FromStr; + +const PORT: u16 = 8080; +const SCOPE_ID: u32 = 1337; + +const IPV4: Ipv4Addr = Ipv4Addr::new(192, 168, 0, 1); +const IPV4_STR: &str = "192.168.0.1"; +const IPV4_STR_PORT: &str = "192.168.0.1:8080"; +const IPV4_STR_WITH_OCTAL: &str = "0127.0.0.1"; +const IPV4_STR_WITH_HEX: &str = "0x10.0.0.1"; + +const IPV6: Ipv6Addr = Ipv6Addr::new(0x2001, 0xdb8, 0, 0, 0, 0, 0xc0a8, 0x1); +const IPV6_STR_FULL: &str = "2001:db8:0:0:0:0:c0a8:1"; +const IPV6_STR_COMPRESS: &str = "2001:db8::c0a8:1"; +const IPV6_STR_V4: &str = "2001:db8::192.168.0.1"; +const IPV6_STR_V4_WITH_OCTAL: &str = "2001:db8::0127.0.0.1"; +const IPV6_STR_V4_WITH_HEX: &str = "2001:db8::0x10.0.0.1"; +const IPV6_STR_PORT: &str = "[2001:db8::c0a8:1]:8080"; +const IPV6_STR_PORT_SCOPE_ID: &str = "[2001:db8::c0a8:1%1337]:8080"; + +#[test] +fn parse_ipv4() { + let result: Ipv4Addr = IPV4_STR.parse().unwrap(); + assert_eq!(result, IPV4); + + assert!(Ipv4Addr::from_str(IPV4_STR_PORT).is_err()); + assert!(Ipv4Addr::from_str(IPV4_STR_WITH_OCTAL).is_err()); + assert!(Ipv4Addr::from_str(IPV4_STR_WITH_HEX).is_err()); + assert!(Ipv4Addr::from_str(IPV6_STR_FULL).is_err()); + assert!(Ipv4Addr::from_str(IPV6_STR_COMPRESS).is_err()); + assert!(Ipv4Addr::from_str(IPV6_STR_V4).is_err()); + assert!(Ipv4Addr::from_str(IPV6_STR_PORT).is_err()); +} + +#[test] +fn parse_ipv6() { + let result: Ipv6Addr = IPV6_STR_FULL.parse().unwrap(); + assert_eq!(result, IPV6); + + let result: Ipv6Addr = IPV6_STR_COMPRESS.parse().unwrap(); + assert_eq!(result, IPV6); + + let result: Ipv6Addr = IPV6_STR_V4.parse().unwrap(); + assert_eq!(result, IPV6); + + assert!(Ipv6Addr::from_str(IPV6_STR_V4_WITH_OCTAL).is_err()); + assert!(Ipv6Addr::from_str(IPV6_STR_V4_WITH_HEX).is_err()); + assert!(Ipv6Addr::from_str(IPV4_STR).is_err()); + assert!(Ipv6Addr::from_str(IPV4_STR_PORT).is_err()); + assert!(Ipv6Addr::from_str(IPV6_STR_PORT).is_err()); +} + +#[test] +fn parse_ip() { + let result: IpAddr = IPV4_STR.parse().unwrap(); + assert_eq!(result, IpAddr::from(IPV4)); + + let result: IpAddr = IPV6_STR_FULL.parse().unwrap(); + assert_eq!(result, IpAddr::from(IPV6)); + + let result: IpAddr = IPV6_STR_COMPRESS.parse().unwrap(); + assert_eq!(result, IpAddr::from(IPV6)); + + let result: IpAddr = IPV6_STR_V4.parse().unwrap(); + assert_eq!(result, IpAddr::from(IPV6)); + + assert!(IpAddr::from_str(IPV4_STR_PORT).is_err()); + assert!(IpAddr::from_str(IPV6_STR_PORT).is_err()); +} + +#[test] +fn parse_socket_v4() { + let result: SocketAddrV4 = IPV4_STR_PORT.parse().unwrap(); + assert_eq!(result, SocketAddrV4::new(IPV4, PORT)); + + assert!(SocketAddrV4::from_str(IPV4_STR).is_err()); + assert!(SocketAddrV4::from_str(IPV6_STR_FULL).is_err()); + assert!(SocketAddrV4::from_str(IPV6_STR_COMPRESS).is_err()); + assert!(SocketAddrV4::from_str(IPV6_STR_V4).is_err()); + assert!(SocketAddrV4::from_str(IPV6_STR_PORT).is_err()); +} + +#[test] +fn parse_socket_v6() { + assert_eq!( + IPV6_STR_PORT.parse(), + Ok(SocketAddrV6::new(IPV6, PORT, 0, 0)) + ); + assert_eq!( + IPV6_STR_PORT_SCOPE_ID.parse(), + Ok(SocketAddrV6::new(IPV6, PORT, 0, SCOPE_ID)) + ); + + assert!(SocketAddrV6::from_str(IPV4_STR).is_err()); + assert!(SocketAddrV6::from_str(IPV4_STR_PORT).is_err()); + assert!(SocketAddrV6::from_str(IPV6_STR_FULL).is_err()); + assert!(SocketAddrV6::from_str(IPV6_STR_COMPRESS).is_err()); + assert!(SocketAddrV6::from_str(IPV6_STR_V4).is_err()); +} + +#[test] +fn parse_socket() { + let result: SocketAddr = IPV4_STR_PORT.parse().unwrap(); + assert_eq!(result, SocketAddr::from((IPV4, PORT))); + + let result: SocketAddr = IPV6_STR_PORT.parse().unwrap(); + assert_eq!(result, SocketAddr::from((IPV6, PORT))); + + assert!(SocketAddr::from_str(IPV4_STR).is_err()); + assert!(SocketAddr::from_str(IPV6_STR_FULL).is_err()); + assert!(SocketAddr::from_str(IPV6_STR_COMPRESS).is_err()); + assert!(SocketAddr::from_str(IPV6_STR_V4).is_err()); +} + +#[test] +fn ipv6_corner_cases() { + let result: Ipv6Addr = "1::".parse().unwrap(); + assert_eq!(result, Ipv6Addr::new(1, 0, 0, 0, 0, 0, 0, 0)); + + let result: Ipv6Addr = "1:1::".parse().unwrap(); + assert_eq!(result, Ipv6Addr::new(1, 1, 0, 0, 0, 0, 0, 0)); + + let result: Ipv6Addr = "::1".parse().unwrap(); + assert_eq!(result, Ipv6Addr::new(0, 0, 0, 0, 0, 0, 0, 1)); + + let result: Ipv6Addr = "::1:1".parse().unwrap(); + assert_eq!(result, Ipv6Addr::new(0, 0, 0, 0, 0, 0, 1, 1)); + + let result: Ipv6Addr = "::".parse().unwrap(); + assert_eq!(result, Ipv6Addr::new(0, 0, 0, 0, 0, 0, 0, 0)); + + let result: Ipv6Addr = "::192.168.0.1".parse().unwrap(); + assert_eq!(result, Ipv6Addr::new(0, 0, 0, 0, 0, 0, 0xc0a8, 0x1)); + + let result: Ipv6Addr = "::1:192.168.0.1".parse().unwrap(); + assert_eq!(result, Ipv6Addr::new(0, 0, 0, 0, 0, 1, 0xc0a8, 0x1)); + + let result: Ipv6Addr = "1:1:1:1:1:1:192.168.0.1".parse().unwrap(); + assert_eq!(result, Ipv6Addr::new(1, 1, 1, 1, 1, 1, 0xc0a8, 0x1)); +} + +// Things that might not seem like failures but are +#[test] +fn ipv6_corner_failures() { + // No IP address before the :: + assert!(Ipv6Addr::from_str("1:192.168.0.1::").is_err()); + + // :: must have at least 1 set of zeroes + assert!(Ipv6Addr::from_str("1:1:1:1::1:1:1:1").is_err()); + + // Need brackets for a port + assert!(SocketAddrV6::from_str("1:1:1:1:1:1:1:1:8080").is_err()); +} diff --git a/src/test.rs b/src/test.rs new file mode 100644 index 0000000..0d70929 --- /dev/null +++ b/src/test.rs @@ -0,0 +1,18 @@ +use alloc::vec::Vec; + +use super::{Ipv4Addr, Ipv6Addr, SocketAddr, SocketAddrV4, SocketAddrV6, ToSocketAddrs}; + +pub fn sa4(a: Ipv4Addr, p: u16) -> SocketAddr { + SocketAddr::V4(SocketAddrV4::new(a, p)) +} + +pub fn sa6(a: Ipv6Addr, p: u16) -> SocketAddr { + SocketAddr::V6(SocketAddrV6::new(a, p, 0, 0)) +} + +pub fn tsa(a: A) -> Result, ()> { + match a.to_socket_addrs() { + Ok(a) => Ok(a.collect()), + Err(_) => Err(()), + } +}