letterbomb 5.0.0

A fork of the classic Wii hacking tool from fail0verflow
Documentation
use crate::oui::oui_list;

use hmac::digest::Digest;
use sha1::Sha1;
use thiserror::Error;

use std::fmt::Display;
use std::str::FromStr;

#[derive(Error, Debug)]
pub enum MACError {
    #[error("MAC address is not for a Wii")]
    NotWii,
    #[error("MAC address is for emulated Wii")]
    IsEmulated,
    #[error("MAC address is malformed")]
    Malformed,
}

#[derive(Debug, Copy, Clone, Default, PartialEq, Eq, Hash)]
pub struct WiiMAC {
    a: u8,
    b: u8,
    c: u8,
    d: u8,
    e: u8,
    f: u8,
}

impl WiiMAC {
    pub fn sha1_digest(&self) -> Vec<u8> {
        let mac_bytes = [
            self.a, self.b, self.c, self.d, self.e, self.f, 0x75u8, 0x79u8, 0x79u8,
        ];
        let mut hasher = Sha1::new();
        hasher.update(mac_bytes);
        hasher.finalize().to_vec()
    }
}

impl Display for WiiMAC {
    fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
        write!(
            f,
            "{:02x}:{:02x}:{:02x}:{:02x}:{:02x}:{:02x}",
            self.a, self.b, self.c, self.d, self.e, self.f
        )
    }
}

impl FromStr for WiiMAC {
    type Err = MACError;

    fn from_str(value: &str) -> Result<Self, Self::Err> {
        let groups = value.split(":").collect::<Vec<&str>>();
        if groups.len() != 6 {
            return Err(MACError::Malformed);
        }
        let a: u8 = u8::from_str_radix(groups[0], 16).map_err(|_| MACError::Malformed)?;
        let b: u8 = u8::from_str_radix(groups[1], 16).map_err(|_| MACError::Malformed)?;
        let c: u8 = u8::from_str_radix(groups[2], 16).map_err(|_| MACError::Malformed)?;
        let d: u8 = u8::from_str_radix(groups[3], 16).map_err(|_| MACError::Malformed)?;
        let e: u8 = u8::from_str_radix(groups[4], 16).map_err(|_| MACError::Malformed)?;
        let f: u8 = u8::from_str_radix(groups[5], 16).map_err(|_| MACError::Malformed)?;
        WiiMAC::try_from((a, b, c, d, e, f))
    }
}

impl TryFrom<(u8, u8, u8, u8, u8, u8)> for WiiMAC {
    type Error = MACError;

    fn try_from(value: (u8, u8, u8, u8, u8, u8)) -> Result<Self, Self::Error> {
        if value.0 == 0
            && value.1 == 0x17
            && value.2 == 0xAB
            && value.3 == 0x99
            && value.4 == 0x99
            && value.5 == 0x99
        {
            return Err(MACError::IsEmulated);
        }
        let oui_prefix = format!("{:02x}{:02x}{:02x}", value.0, value.1, value.2);
        if !oui_list().contains(&oui_prefix.to_ascii_uppercase()) {
            return Err(MACError::NotWii);
        }

        Ok(WiiMAC {
            a: value.0,
            b: value.1,
            c: value.2,
            d: value.3,
            e: value.4,
            f: value.5,
        })
    }
}

impl From<WiiMAC> for (u8, u8, u8, u8, u8, u8) {
    fn from(value: WiiMAC) -> Self {
        (value.a, value.b, value.c, value.d, value.e, value.f)
    }
}

#[cfg(test)]
mod tests {
    use super::WiiMAC;
    use std::str::FromStr;

    #[test]
    fn mac_from_str_incomplete() {
        assert!(WiiMAC::from_str("AA:BB:CC").is_err());
    }

    #[test]
    fn mac_from_str_malformed() {
        assert!(WiiMAC::from_str("GG:00:11:22:33:44").is_err());
    }

    #[test]
    fn mac_from_str_not_wii() {
        assert!(WiiMAC::from_str("00:00:11:22:33:44").is_err());
    }

    #[test]
    fn mac_from_str_ok() {
        assert!(WiiMAC::from_str("00:17:ab:5a:6e:F5").is_ok());
    }

    #[test]
    fn mac_from_u8_ok() {
        assert!(WiiMAC::try_from((0u8, 0x17, 0xAB, 0x5A, 0x6E, 0xF5)).is_ok());
    }

    #[test]
    fn mac_display() {
        let mac = WiiMAC::try_from((0u8, 0x17, 0xAB, 0x5A, 0x6E, 0xF5)).unwrap();
        assert_eq!(mac.to_string(), "00:17:ab:5a:6e:f5");
    }
}