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");
}
}