lowram-ipsum 1.0.1

Adds IP addresses. Suitable for no_std/memory constrained environments.
Documentation
#![no_std]

use core::{
    array,
    net::{IpAddr, Ipv4Addr, Ipv6Addr},
    ops::Add,
};

/// Wrapper for adding `IpAddr`, `Ipv4Addr` and `Ipv6Addr`.
/// We love the orphan rule around here.
///
/// # Example
///
/// ```
/// let sum: Ipv4addr = Ipsum(Ipv4Addr::new(1, 2, 3, 4)) + Ipsum(Ipv4Addr::new(1, 2, 3, 255));
/// ```
pub struct Ipsum<T>(pub T);

impl Add for Ipsum<Ipv4Addr> {
    type Output = Ipv4Addr;

    fn add(self, rhs: Self) -> Self::Output {
        let inner = self.0;
        let rhs = rhs.0;
        let mut it = inner
            .octets()
            .into_iter()
            .zip(rhs.octets().into_iter())
            .map(|(o1, o2)| o1.saturating_add(o2));
        let res: [u8; 4] = array::from_fn(|_| it.next().unwrap());
        res.into()
    }
}

impl Add for Ipsum<Ipv6Addr> {
    type Output = Ipv6Addr;

    fn add(self, rhs: Self) -> Self::Output {
        let inner = self.0;
        let rhs = rhs.0;
        let mut it = inner
            .octets()
            .into_iter()
            .zip(rhs.octets().into_iter())
            .map(|(o1, o2)| o1.saturating_add(o2));
        let res: [u8; 16] = array::from_fn(|_| it.next().unwrap());
        res.into()
    }
}

impl Add for Ipsum<IpAddr> {
    type Output = IpAddr;

    fn add(self, rhs: Self) -> Self::Output {
        match (self.0, rhs.0) {
            (IpAddr::V4(lhs), IpAddr::V4(rhs)) => IpAddr::V4(Ipsum(lhs) + Ipsum(rhs)),
            (IpAddr::V4(lhs), IpAddr::V6(rhs)) => {
                IpAddr::V4(Ipsum(lhs) + Ipsum(rhs.to_ipv4().unwrap()))
            }
            (IpAddr::V6(lhs), IpAddr::V4(rhs)) => {
                IpAddr::V4(Ipsum(lhs.to_ipv4().unwrap()) + Ipsum(rhs))
            }
            (IpAddr::V6(lhs), IpAddr::V6(rhs)) => IpAddr::V6(Ipsum(lhs) + Ipsum(rhs)),
        }
    }
}

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

    use super::*;

    #[test]
    fn v4() {
        let result = Ipsum(Ipv4Addr::new(1, 2, 3, 4)) + Ipsum(Ipv4Addr::new(1, 2, 3, 255));
        assert_eq!(result, Ipv4Addr::new(2, 4, 6, 255));
    }

    #[test]
    fn v6() {
        let result = Ipsum(Ipv6Addr::new(0x2001, 0xdb8, 0, 0, 0, 0, 0, 0))
            + Ipsum(Ipv6Addr::new(0x2001, 0xdb8, 1, 2, 3, 4, 5, u16::MAX));
        let parts1 = 0x2001u16.to_be_bytes();
        let parts2 = 0xdb8u16.to_be_bytes();
        assert_eq!(
            result,
            Ipv6Addr::new(
                u16::from_be_bytes([parts1[0].saturating_mul(2), parts1[1].saturating_mul(2)]),
                u16::from_be_bytes([parts2[0].saturating_mul(2), parts2[1].saturating_mul(2)]),
                1,
                2,
                3,
                4,
                5,
                u16::MAX
            )
        );
    }

    #[test]
    fn mixed() {
        let result = Ipsum(IpAddr::from([127, 0, 0, 1]))
            + Ipsum(Ipv4Addr::new(192, 168, 2, 4).to_ipv6_mapped().into());

        assert_eq!(result, Ipv4Addr::new(127u8.saturating_add(192), 168, 2, 5));
    }

    #[test]
    #[should_panic]
    fn panics() {
        let _kbye = Ipsum(IpAddr::from([127, 0, 0, 1]))
            + Ipsum(Ipv6Addr::new(0xff00, 0, 0, 0, 0, 0, 0, 0).into());
    }
}