use core::fmt;
#[non_exhaustive]
#[derive(Clone, PartialEq, Eq, Hash)]
pub struct DeviceAddress {
pub can_id: Option<u8>,
pub uuid: Option<Vec<u8>>,
pub transport_device: Option<usize>,
}
impl DeviceAddress {
pub fn can_id(id: u8) -> Self {
DeviceAddress {
can_id: Some(id),
uuid: None,
transport_device: None,
}
}
pub fn uuid(uuid: impl Into<Vec<u8>>) -> Self {
DeviceAddress {
can_id: None,
uuid: Some(uuid.into()),
transport_device: None,
}
}
#[must_use]
pub fn with_transport_device(mut self, idx: usize) -> Self {
self.transport_device = Some(idx);
self
}
pub fn as_can_id(&self) -> Option<u8> {
self.can_id
}
pub fn as_uuid(&self) -> Option<&[u8]> {
self.uuid.as_deref()
}
pub fn is_can_id(&self) -> bool {
self.can_id.is_some()
}
pub fn is_uuid(&self) -> bool {
self.uuid.is_some()
}
pub fn matches(&self, can_id: u8, uuid: Option<&[u8]>) -> bool {
if self.can_id.is_none() && self.uuid.is_none() {
return false;
}
let can_match = self.can_id.is_none_or(|id| id == can_id);
let uuid_match = match (&self.uuid, uuid) {
(Some(prefix), Some(device_uuid)) => device_uuid.starts_with(prefix),
(Some(_), None) => false,
(None, _) => true,
};
can_match && uuid_match
}
}
impl fmt::Debug for DeviceAddress {
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
write!(f, "DeviceAddress(can_id={:?}", self.can_id)?;
if let Some(uuid) = &self.uuid {
write!(f, ", uuid=")?;
for (i, byte) in uuid.iter().enumerate() {
if i > 0 {
write!(f, ":")?;
}
write!(f, "{:02x}", byte)?;
}
}
if let Some(td) = self.transport_device {
write!(f, ", td={}", td)?;
}
write!(f, ")")
}
}
impl fmt::Display for DeviceAddress {
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
match (&self.can_id, &self.uuid) {
(Some(id), None) => write!(f, "{}", id),
(None, Some(uuid)) => {
for (i, byte) in uuid.iter().enumerate() {
if i > 0 {
write!(f, ":")?;
}
write!(f, "{:02x}", byte)?;
}
Ok(())
}
(Some(id), Some(uuid)) => {
write!(f, "{}(", id)?;
for (i, byte) in uuid.iter().enumerate() {
if i > 0 {
write!(f, ":")?;
}
write!(f, "{:02x}", byte)?;
}
write!(f, ")")
}
(None, None) => write!(f, "<empty>"),
}
}
}
impl From<u8> for DeviceAddress {
fn from(id: u8) -> Self {
DeviceAddress::can_id(id)
}
}
impl From<i32> for DeviceAddress {
fn from(id: i32) -> Self {
DeviceAddress::can_id(id.clamp(0, 127) as u8)
}
}
impl From<&[u8]> for DeviceAddress {
fn from(uuid: &[u8]) -> Self {
DeviceAddress::uuid(uuid)
}
}
impl<const N: usize> From<[u8; N]> for DeviceAddress {
fn from(uuid: [u8; N]) -> Self {
DeviceAddress::uuid(uuid)
}
}
impl From<Vec<u8>> for DeviceAddress {
fn from(uuid: Vec<u8>) -> Self {
DeviceAddress::uuid(uuid)
}
}
impl core::str::FromStr for DeviceAddress {
type Err = String;
fn from_str(s: &str) -> Result<Self, Self::Err> {
if let Ok(id) = s.parse::<u8>() {
return Ok(DeviceAddress::can_id(id));
}
let hex_str: String = s.chars().filter(|c| *c != ':' && *c != '-').collect();
if hex_str.len() % 2 != 0 {
return Err(format!("invalid address: {}", s));
}
let bytes: Result<Vec<u8>, _> = (0..hex_str.len())
.step_by(2)
.map(|i| u8::from_str_radix(&hex_str[i..i + 2], 16))
.collect();
match bytes {
Ok(uuid) if !uuid.is_empty() => Ok(DeviceAddress::uuid(uuid)),
_ => Err(format!("invalid address: {}", s)),
}
}
}
#[cfg(test)]
mod tests {
use super::*;
#[test]
fn test_can_id_address() {
let addr = DeviceAddress::can_id(1);
assert_eq!(addr.as_can_id(), Some(1));
assert_eq!(addr.as_uuid(), None);
assert!(addr.is_can_id());
assert!(!addr.is_uuid());
assert!(addr.transport_device.is_none());
}
#[test]
fn test_uuid_address() {
let uuid = [0x01, 0x02, 0x03, 0x04];
let addr = DeviceAddress::uuid(uuid);
assert_eq!(addr.as_can_id(), None);
assert_eq!(addr.as_uuid(), Some(&[0x01, 0x02, 0x03, 0x04][..]));
assert!(!addr.is_can_id());
assert!(addr.is_uuid());
}
#[test]
fn test_with_transport_device() {
let addr = DeviceAddress::can_id(1).with_transport_device(2);
assert_eq!(addr.can_id, Some(1));
assert_eq!(addr.transport_device, Some(2));
}
#[test]
fn test_from_u8() {
let addr: DeviceAddress = 5u8.into();
assert_eq!(addr, DeviceAddress::can_id(5));
}
#[test]
fn test_from_i32() {
let addr: DeviceAddress = 5i32.into();
assert_eq!(addr, DeviceAddress::can_id(5));
}
#[test]
fn test_from_array() {
let addr: DeviceAddress = [0x01, 0x02, 0x03, 0x04].into();
assert_eq!(addr.as_uuid(), Some(&[0x01, 0x02, 0x03, 0x04][..]));
}
#[test]
fn test_matches_can_id() {
let addr = DeviceAddress::can_id(5);
assert!(addr.matches(5, None));
assert!(!addr.matches(6, None));
assert!(addr.matches(5, Some(&[0x01, 0x02])));
}
#[test]
fn test_matches_uuid() {
let addr = DeviceAddress::uuid([0x01, 0x02]);
assert!(!addr.matches(5, None));
assert!(addr.matches(5, Some(&[0x01, 0x02, 0x03, 0x04])));
assert!(!addr.matches(5, Some(&[0x02, 0x03, 0x04, 0x05])));
}
#[test]
fn test_matches_both() {
let addr = DeviceAddress {
can_id: Some(5),
uuid: Some(vec![0x01, 0x02]),
transport_device: None,
};
assert!(addr.matches(5, Some(&[0x01, 0x02, 0x03])));
assert!(!addr.matches(6, Some(&[0x01, 0x02, 0x03])));
assert!(!addr.matches(5, Some(&[0x02, 0x03])));
assert!(!addr.matches(5, None));
}
#[test]
fn test_parse_can_id() {
let addr: DeviceAddress = "5".parse().unwrap();
assert_eq!(addr, DeviceAddress::can_id(5));
let addr: DeviceAddress = "127".parse().unwrap();
assert_eq!(addr, DeviceAddress::can_id(127));
}
#[test]
fn test_parse_uuid_with_colons() {
let addr: DeviceAddress = "01:02:03:04".parse().unwrap();
assert_eq!(addr.as_uuid(), Some(&[0x01, 0x02, 0x03, 0x04][..]));
}
#[test]
fn test_parse_uuid_without_colons() {
let addr: DeviceAddress = "01020304".parse().unwrap();
assert_eq!(addr.as_uuid(), Some(&[0x01, 0x02, 0x03, 0x04][..]));
}
#[test]
fn test_display() {
assert_eq!(format!("{}", DeviceAddress::can_id(5)), "5");
assert_eq!(
format!("{}", DeviceAddress::uuid([0x01, 0x02, 0x03])),
"01:02:03"
);
}
#[test]
fn test_display_both() {
let addr = DeviceAddress {
can_id: Some(5),
uuid: Some(vec![0x01, 0x02]),
transport_device: None,
};
assert_eq!(format!("{}", addr), "5(01:02)");
}
#[test]
fn test_debug() {
assert_eq!(
format!("{:?}", DeviceAddress::can_id(5)),
"DeviceAddress(can_id=Some(5))"
);
assert_eq!(
format!("{:?}", DeviceAddress::uuid([0x01, 0x02])),
"DeviceAddress(can_id=None, uuid=01:02)"
);
assert_eq!(
format!("{:?}", DeviceAddress::can_id(5).with_transport_device(0)),
"DeviceAddress(can_id=Some(5), td=0)"
);
}
#[test]
fn test_equality_with_transport_device() {
let a = DeviceAddress::can_id(5);
let b = DeviceAddress::can_id(5).with_transport_device(0);
assert_ne!(a, b);
}
}