eui 0.1.0

Extended Unique Identifier (EUI)
Documentation
#[derive(Debug, PartialEq)]
pub struct EUI48([u8; 6]);

#[derive(Debug, PartialEq)]
pub struct EUI64([u8; 8]);

#[derive(Debug, PartialEq)]
pub enum Error {
    InvalidHexCharacter,
    InvalidStringLength,
    OddLength,
}

impl From<hex::FromHexError> for Error {
    fn from(e: hex::FromHexError) -> Self {
        match e {
            hex::FromHexError::InvalidHexCharacter { .. } => {
                Error::InvalidHexCharacter
            }
            hex::FromHexError::InvalidStringLength => Error::InvalidStringLength,
            hex::FromHexError::OddLength => Error::OddLength,
        }
    }
}

pub trait EUI {
    fn to_bytes(&self) -> &[u8];

    fn to_canonical_fmt(&self) -> String {
        self.to_bytes().iter().fold(String::new(), |acc, new| {
            if acc.is_empty() {
                format!("{:02X}", new)
            } else {
                format!("{}-{:02X}", acc, new)
            }
        })
    }

    fn to_colon_fmt(&self) -> String {
        self.to_bytes().iter().fold(String::new(), |acc, new| {
            if acc.is_empty() {
                format!("{:02X}", new)
            } else {
                format!("{}:{:02X}", acc, new)
            }
        })
    }

    fn to_dot_fmt(&self) -> String {
        self.to_bytes().chunks(2).fold(String::new(), |acc, new| {
            if acc.is_empty() {
                format!("{:02X}{:02X}", new[0], new[1])
            } else {
                format!("{}.{:02X}{:02X}", acc, new[0], new[1])
            }
        })
    }
}

impl EUI for EUI48 {
    fn to_bytes(&self) -> &[u8] {
        &self.0
    }
}

impl EUI for EUI64 {
    fn to_bytes(&self) -> &[u8] {
        &self.0
    }
}

impl std::convert::TryFrom<&str> for EUI48 {
    type Error = crate::Error;
    fn try_from(s: &str) -> Result<Self, Self::Error> {
        let s = s.replace(&['.', ':', '-'][..], "");
        let bytes = hex::decode(s).map_err(Error::from)?;

        if bytes.len() != 6 {
            return Err(Self::Error::InvalidStringLength);
        }

        let mut raw_address: [u8; 6] = Default::default();
        raw_address.copy_from_slice(bytes.as_slice());
        Ok(EUI48(raw_address))
    }
}

impl std::convert::TryFrom<&str> for EUI64 {
    type Error = crate::Error;
    fn try_from(s: &str) -> Result<Self, Self::Error> {
        let s = s.replace(&['.', ':', '-'][..], "");
        let bytes = hex::decode(s).map_err(Error::from)?;

        if bytes.len() != 8 {
            return Err(Self::Error::InvalidStringLength);
        }

        let mut raw_address: [u8; 8] = Default::default();
        raw_address.copy_from_slice(bytes.as_slice());
        Ok(EUI64(raw_address))
    }
}

#[cfg(test)]
mod tests {
    use crate::{EUI, EUI48, EUI64, Error};
    use std::convert::TryFrom;

    #[test]
    fn eui48_to_canonical_fmt() {
        let eui = EUI48([0x0A, 0x1B, 0x2C, 0x3D, 0x4E, 0x5F]);
        assert_eq!(eui.to_canonical_fmt(), "0A-1B-2C-3D-4E-5F");
    }

    #[test]
    fn eui48_to_colon_fmt() {
        let eui = EUI48([0x0A, 0x1B, 0x2C, 0x3D, 0x4E, 0x5F]);
        assert_eq!(eui.to_colon_fmt(), "0A:1B:2C:3D:4E:5F");
    }

    #[test]
    fn eui48_to_dot_fmt() {
        let eui = EUI48([0x0A, 0x1B, 0x2C, 0x3D, 0x4E, 0x5F]);
        assert_eq!(eui.to_dot_fmt(), "0A1B.2C3D.4E5F");
    }

    #[test]
    fn eui64_to_canonical_fmt() {
        let eui = EUI64([0x00, 0xFF, 0x0A, 0x1B, 0x2C, 0x3D, 0x4E, 0x5F]);
        assert_eq!(eui.to_canonical_fmt(), "00-FF-0A-1B-2C-3D-4E-5F");
    }

    #[test]
    fn eui64_to_colon_fmt() {
        let eui = EUI64([0x00, 0xFF, 0x0A, 0x1B, 0x2C, 0x3D, 0x4E, 0x5F]);
        assert_eq!(eui.to_colon_fmt(), "00:FF:0A:1B:2C:3D:4E:5F");
    }

    #[test]
    fn eui64_to_dot_fmt() {
        let eui = EUI64([0x00, 0xFF, 0x0A, 0x1B, 0x2C, 0x3D, 0x4E, 0x5F]);
        assert_eq!(eui.to_dot_fmt(), "00FF.0A1B.2C3D.4E5F");
    }

    #[test]
    fn eui48_from_canonical_fmt() {
        assert_eq!(
            EUI48::try_from("0A-1B-2C-3D-4E-5F").unwrap(),
            EUI48([0x0A, 0x1B, 0x2C, 0x3D, 0x4E, 0x5F])
        );
    }

    #[test]
    fn eui48_from_colon_fmt() {
        assert_eq!(
            EUI48::try_from("0A:1B:2C:3D:4E:5F").unwrap(),
            EUI48([0x0A, 0x1B, 0x2C, 0x3D, 0x4E, 0x5F])
        );
    }

    #[test]
    fn eui48_from_dot_fmt() {
        assert_eq!(
            EUI48::try_from("0A1B.2C3D.4E5F").unwrap(),
            EUI48([0x0A, 0x1B, 0x2C, 0x3D, 0x4E, 0x5F])
        );
    }

    #[test]
    fn eui64_from_canonical_fmt() {
        assert_eq!(
            EUI64::try_from("00-FF-0A-1B-2C-3D-4E-5F").unwrap(),
            EUI64([0x00, 0xFF, 0x0A, 0x1B, 0x2C, 0x3D, 0x4E, 0x5F])
        );
    }

    #[test]
    fn eui64_from_colon_fmt() {
        assert_eq!(
            EUI64::try_from("00:FF:0A:1B:2C:3D:4E:5F").unwrap(),
            EUI64([0x00, 0xFF, 0x0A, 0x1B, 0x2C, 0x3D, 0x4E, 0x5F])
        );
    }

    #[test]
    fn eui64_from_dot_fmt() {
        assert_eq!(
            EUI64::try_from("00FF.0A1B.2C3D.4E5F").unwrap(),
            EUI64([0x00, 0xFF, 0x0A, 0x1B, 0x2C, 0x3D, 0x4E, 0x5F])
        );
    }

    #[test]
    fn eui48_from_canonical_fmt_bad_character() {
        assert_eq!(
            EUI48::try_from("0A-1B-2C-3D-4x-5F"),
            Err(Error::InvalidHexCharacter)
        );
    }

    #[test]
    fn eui48_from_canonical_fmt_missing_byte() {
        assert_eq!(
            EUI48::try_from("0A-1B-2C-3D-4E"),
            Err(Error::InvalidStringLength)
        );
    }

    #[test]
    fn eui48_from_canonical_fmt_missing_character() {
        assert_eq!(
            EUI48::try_from("0A-1B-2C-3D-4E-5"),
            Err(Error::OddLength)
        );
    }

    #[test]
    fn eui64_from_canonical_fmt_bad_character() {
        assert_eq!(
            EUI64::try_from("00-FF-0A-1B-2C-3D-4x-5F"),
            Err(Error::InvalidHexCharacter)
        );
    }

    #[test]
    fn eui64_from_canonical_fmt_missing_byte() {
        assert_eq!(
            EUI64::try_from("00-FF-0A-1B-2C-3D-4E"),
            Err(Error::InvalidStringLength)
        );
    }

    #[test]
    fn eui64_from_canonical_fmt_missing_character() {
        assert_eq!(
            EUI64::try_from("00-FF-0A-1B-2C-3D-4E-5"),
            Err(Error::OddLength)
        );
    }
}