pica 0.1.7

Pica is a virtual UWB Controller implementing the FiRa UCI specification.
Documentation
// Copyright 2022 Google LLC
//
// Licensed under the Apache License, Version 2.0 (the "License");
// you may not use this file except in compliance with the License.
// You may obtain a copy of the License at
//
//     https://www.apache.org/licenses/LICENSE-2.0
//
// Unless required by applicable law or agreed to in writing, software
// distributed under the License is distributed on an "AS IS" BASIS,
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
// See the License for the specific language governing permissions and
// limitations under the License.

use std::fmt::Display;

use hex::FromHex;
use serde::{Deserialize, Serialize};
use thiserror::Error;

const SHORT_MAC_ADDRESS_SIZE: usize = 2;
const STRING_SHORT_MAC_ADDRESS_SIZE: usize = 2 * SHORT_MAC_ADDRESS_SIZE;

const EXTEND_MAC_ADDRESS_SIZE: usize = 8;
const STRING_EXTEND_MAC_ADDRESS_SIZE: usize = 2 * EXTEND_MAC_ADDRESS_SIZE;

#[derive(Error, Debug)]
pub enum Error {
    #[error("MacAddress has the wrong format: 0")]
    MacAddressWrongFormat(String),
}
#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash, Serialize, Deserialize)]
#[serde(try_from = "String", into = "String")]
pub enum MacAddress {
    Short([u8; SHORT_MAC_ADDRESS_SIZE]),
    Extend([u8; EXTEND_MAC_ADDRESS_SIZE]),
}

impl MacAddress {
    pub fn new(mac_address: String) -> Result<Self, Error> {
        mac_address.try_into()
    }
}

impl From<usize> for MacAddress {
    fn from(device_handle: usize) -> Self {
        let handle = device_handle as u64;
        MacAddress::Extend(handle.to_be_bytes())
    }
}

impl TryFrom<String> for MacAddress {
    type Error = Error;
    fn try_from(mac_address: String) -> std::result::Result<Self, Error> {
        let mac_address = mac_address.replace(':', "");
        let mac_address = mac_address.replace("%3A", "");
        let uwb_mac_address = match mac_address.len() {
            STRING_SHORT_MAC_ADDRESS_SIZE => MacAddress::Short(
                <[u8; SHORT_MAC_ADDRESS_SIZE]>::from_hex(mac_address)
                    .map_err(|err| Error::MacAddressWrongFormat(err.to_string()))?,
            ),
            STRING_EXTEND_MAC_ADDRESS_SIZE => MacAddress::Extend(
                <[u8; EXTEND_MAC_ADDRESS_SIZE]>::from_hex(mac_address)
                    .map_err(|err| Error::MacAddressWrongFormat(err.to_string()))?,
            ),
            _ => return Err(Error::MacAddressWrongFormat(mac_address)),
        };
        Ok(uwb_mac_address)
    }
}

impl From<&MacAddress> for String {
    fn from(mac_address: &MacAddress) -> Self {
        let to_string = |addr: &[u8]| -> String {
            let mac_address: Vec<_> = addr.iter().map(|byte| format!("{:02X}:", byte)).collect();
            let s = mac_address
                .iter()
                .flat_map(|byte| byte.chars())
                .collect::<String>();
            s.trim_end_matches(':').into()
        };
        match mac_address {
            MacAddress::Short(address) => to_string(address),
            MacAddress::Extend(address) => to_string(address),
        }
    }
}

impl From<MacAddress> for String {
    fn from(mac_address: MacAddress) -> Self {
        String::from(&mac_address)
    }
}

impl Display for MacAddress {
    fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
        write!(f, "{}", String::from(self))
    }
}

#[cfg(test)]
mod tests {
    use super::*;

    #[test]
    fn valid_mac_address() {
        let valid_mac_address = "00:11";
        assert_eq!(
            MacAddress::new(valid_mac_address.into()).unwrap(),
            MacAddress::Short([0x00, 0x11])
        );
        let valid_mac_address = "FF:77:AA:DD:EE:BB:CC:10";
        assert_eq!(
            MacAddress::new(valid_mac_address.into()).unwrap(),
            MacAddress::Extend([0xFF, 0x77, 0xAA, 0xDD, 0xEE, 0xBB, 0xCC, 0x10])
        );
    }

    #[test]
    #[should_panic]
    fn invalid_mac_address_short() {
        let invalid_mac_address = "00:11:22";
        MacAddress::new(invalid_mac_address.into()).unwrap();
    }

    #[test]
    #[should_panic]
    fn invalid_mac_address_extend() {
        let invalid_mac_address = "00:11:22:33:44:55:66";
        MacAddress::new(invalid_mac_address.into()).unwrap();
    }

    #[test]
    fn display_mac_address() {
        let extend_mac_address = "00:FF:77:AA:DD:EE:CC:45";
        let short_mac_address = "00:FF";
        assert_eq!(
            format!("{}", MacAddress::new(extend_mac_address.into()).unwrap()),
            extend_mac_address
        );
        assert_eq!(
            format!("{}", MacAddress::new(short_mac_address.into()).unwrap()),
            short_mac_address
        );
        assert_eq!(extend_mac_address.to_string(), extend_mac_address);
    }
}