use core::fmt;
#[derive(Debug, Clone, PartialEq, Eq)]
pub enum AddressParseError {
InvalidFormat,
InvalidSegment,
OutOfRange,
}
impl fmt::Display for AddressParseError {
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
match self {
Self::InvalidFormat => f.write_str("invalid address format"),
Self::InvalidSegment => f.write_str("invalid numeric segment"),
Self::OutOfRange => f.write_str("segment value out of range"),
}
}
}
impl core::error::Error for AddressParseError {}
#[derive(Clone, Copy, PartialEq, Eq, Hash)]
#[cfg_attr(feature = "serde", derive(serde::Serialize, serde::Deserialize))]
#[cfg_attr(
feature = "serde",
serde(into = "alloc::string::String", try_from = "alloc::string::String")
)]
pub struct IndividualAddress(u16);
impl IndividualAddress {
#[inline]
pub const fn from_raw(raw: u16) -> Self {
Self(raw)
}
pub const fn new(area: u8, line: u8, device: u8) -> Result<Self, AddressParseError> {
if area > 15 || line > 15 {
return Err(AddressParseError::OutOfRange);
}
Ok(Self(
((area as u16) << 12) | ((line as u16) << 8) | (device as u16),
))
}
#[inline]
pub const fn raw(self) -> u16 {
self.0
}
#[inline]
pub const fn area(self) -> u8 {
(self.0 >> 12) as u8 & 0x0F
}
#[inline]
pub const fn line(self) -> u8 {
(self.0 >> 8) as u8 & 0x0F
}
#[inline]
pub const fn device(self) -> u8 {
(self.0 & 0xFF) as u8
}
#[inline]
pub const fn to_bytes(self) -> [u8; 2] {
self.0.to_be_bytes()
}
#[inline]
pub const fn from_bytes(bytes: [u8; 2]) -> Self {
Self(u16::from_be_bytes(bytes))
}
}
impl fmt::Display for IndividualAddress {
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
write!(f, "{}.{}.{}", self.area(), self.line(), self.device())
}
}
impl fmt::Debug for IndividualAddress {
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
write!(f, "IndividualAddress({self})")
}
}
impl core::str::FromStr for IndividualAddress {
type Err = AddressParseError;
fn from_str(s: &str) -> Result<Self, Self::Err> {
let mut parts = s.splitn(3, '.');
let area = next_segment(&mut parts)?;
let line = next_segment(&mut parts)?;
let device = next_segment(&mut parts)?;
if parts.next().is_some() {
return Err(AddressParseError::InvalidFormat);
}
Self::new(area, line, device)
}
}
#[derive(Clone, Copy, PartialEq, Eq, Hash)]
#[cfg_attr(feature = "serde", derive(serde::Serialize, serde::Deserialize))]
#[cfg_attr(
feature = "serde",
serde(into = "alloc::string::String", try_from = "alloc::string::String")
)]
pub struct GroupAddress(u16);
impl GroupAddress {
#[inline]
pub const fn from_raw(raw: u16) -> Self {
Self(raw)
}
pub const fn new_3level(main: u8, middle: u8, sub: u8) -> Result<Self, AddressParseError> {
if main > 31 || middle > 7 {
return Err(AddressParseError::OutOfRange);
}
Ok(Self(
((main as u16) << 11) | ((middle as u16) << 8) | (sub as u16),
))
}
pub const fn new_2level(main: u8, sub: u16) -> Result<Self, AddressParseError> {
if main > 31 || sub > 2047 {
return Err(AddressParseError::OutOfRange);
}
Ok(Self(((main as u16) << 11) | sub))
}
#[inline]
pub const fn raw(self) -> u16 {
self.0
}
#[inline]
pub const fn main(self) -> u8 {
(self.0 >> 11) as u8 & 0x1F
}
#[inline]
pub const fn middle(self) -> u8 {
(self.0 >> 8) as u8 & 0x07
}
#[inline]
pub const fn sub(self) -> u8 {
(self.0 & 0xFF) as u8
}
#[inline]
pub const fn sub_2level(self) -> u16 {
self.0 & 0x07FF
}
#[inline]
pub const fn to_bytes(self) -> [u8; 2] {
self.0.to_be_bytes()
}
#[inline]
pub const fn from_bytes(bytes: [u8; 2]) -> Self {
Self(u16::from_be_bytes(bytes))
}
}
impl fmt::Display for GroupAddress {
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
write!(f, "{}/{}/{}", self.main(), self.middle(), self.sub())
}
}
impl fmt::Debug for GroupAddress {
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
write!(f, "GroupAddress({self})")
}
}
impl core::str::FromStr for GroupAddress {
type Err = AddressParseError;
fn from_str(s: &str) -> Result<Self, Self::Err> {
let parts: alloc::vec::Vec<&str> = s.splitn(4, '/').collect();
match parts.len() {
2 => {
let main = parse_u8(parts[0])?;
let sub = parse_u16(parts[1])?;
Self::new_2level(main, sub)
}
3 => {
let main = parse_u8(parts[0])?;
let middle = parse_u8(parts[1])?;
let sub = parse_u8(parts[2])?;
Self::new_3level(main, middle, sub)
}
_ => Err(AddressParseError::InvalidFormat),
}
}
}
#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash)]
pub enum DestinationAddress {
Individual(IndividualAddress),
Group(GroupAddress),
}
impl fmt::Display for DestinationAddress {
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
match self {
Self::Individual(addr) => write!(f, "{addr}"),
Self::Group(addr) => write!(f, "{addr}"),
}
}
}
impl From<IndividualAddress> for alloc::string::String {
fn from(addr: IndividualAddress) -> Self {
alloc::format!("{addr}")
}
}
impl TryFrom<alloc::string::String> for IndividualAddress {
type Error = AddressParseError;
fn try_from(s: alloc::string::String) -> Result<Self, Self::Error> {
s.parse()
}
}
impl From<GroupAddress> for alloc::string::String {
fn from(addr: GroupAddress) -> Self {
alloc::format!("{addr}")
}
}
impl TryFrom<alloc::string::String> for GroupAddress {
type Error = AddressParseError;
fn try_from(s: alloc::string::String) -> Result<Self, Self::Error> {
s.parse()
}
}
fn next_segment<'a>(parts: &mut impl Iterator<Item = &'a str>) -> Result<u8, AddressParseError> {
let s = parts.next().ok_or(AddressParseError::InvalidFormat)?;
parse_u8(s)
}
fn parse_u8(s: &str) -> Result<u8, AddressParseError> {
s.parse::<u8>()
.map_err(|_| AddressParseError::InvalidSegment)
}
fn parse_u16(s: &str) -> Result<u16, AddressParseError> {
s.parse::<u16>()
.map_err(|_| AddressParseError::InvalidSegment)
}
#[cfg(test)]
#[allow(clippy::unwrap_used, clippy::expect_used)]
mod tests {
use super::*;
use core::str::FromStr;
#[test]
fn individual_roundtrip_components() {
let addr = IndividualAddress::new(1, 1, 1).unwrap();
assert_eq!(addr.area(), 1);
assert_eq!(addr.line(), 1);
assert_eq!(addr.device(), 1);
assert_eq!(addr.raw(), 0x1101);
}
#[test]
fn individual_max_values() {
let addr = IndividualAddress::new(15, 15, 255).unwrap();
assert_eq!(addr.raw(), 0xFFFF);
}
#[test]
fn individual_out_of_range() {
assert!(IndividualAddress::new(16, 0, 0).is_err());
assert!(IndividualAddress::new(0, 16, 0).is_err());
}
#[test]
fn individual_display_parse_roundtrip() {
let addr = IndividualAddress::new(1, 2, 3).unwrap();
let s = alloc::format!("{addr}");
assert_eq!(s, "1.2.3");
let parsed = IndividualAddress::from_str(&s).unwrap();
assert_eq!(addr, parsed);
}
#[test]
fn individual_bytes_roundtrip() {
let addr = IndividualAddress::new(1, 1, 1).unwrap();
let bytes = addr.to_bytes();
assert_eq!(IndividualAddress::from_bytes(bytes), addr);
}
#[test]
fn individual_from_raw() {
let addr = IndividualAddress::from_raw(0x1001);
assert_eq!(addr.area(), 1);
assert_eq!(addr.line(), 0);
assert_eq!(addr.device(), 1);
}
#[test]
fn group_3level_roundtrip() {
let addr = GroupAddress::new_3level(1, 0, 1).unwrap();
assert_eq!(addr.main(), 1);
assert_eq!(addr.middle(), 0);
assert_eq!(addr.sub(), 1);
assert_eq!(addr.raw(), 0x0801);
}
#[test]
fn group_3level_max() {
let addr = GroupAddress::new_3level(31, 7, 255).unwrap();
assert_eq!(addr.raw(), 0xFFFF);
}
#[test]
fn group_3level_out_of_range() {
assert!(GroupAddress::new_3level(32, 0, 0).is_err());
assert!(GroupAddress::new_3level(0, 8, 0).is_err());
}
#[test]
fn group_2level_roundtrip() {
let addr = GroupAddress::new_2level(1, 1).unwrap();
assert_eq!(addr.main(), 1);
assert_eq!(addr.sub_2level(), 1);
}
#[test]
fn group_2level_out_of_range() {
assert!(GroupAddress::new_2level(32, 0).is_err());
assert!(GroupAddress::new_2level(0, 2048).is_err());
}
#[test]
fn group_display_3level() {
let addr = GroupAddress::new_3level(1, 2, 3).unwrap();
assert_eq!(alloc::format!("{addr}"), "1/2/3");
}
#[test]
fn group_parse_3level() {
let addr = GroupAddress::from_str("1/0/1").unwrap();
assert_eq!(addr.main(), 1);
assert_eq!(addr.middle(), 0);
assert_eq!(addr.sub(), 1);
}
#[test]
fn group_parse_2level() {
let addr = GroupAddress::from_str("1/1").unwrap();
assert_eq!(addr.main(), 1);
assert_eq!(addr.sub_2level(), 1);
}
#[test]
fn group_bytes_roundtrip() {
let addr = GroupAddress::new_3level(1, 0, 1).unwrap();
let bytes = addr.to_bytes();
assert_eq!(GroupAddress::from_bytes(bytes), addr);
}
#[test]
fn group_wire_encoding_matches_cpp() {
let addr = GroupAddress::new_3level(1, 0, 1).unwrap();
assert_eq!(addr.to_bytes(), [0x08, 0x01]);
let addr = GroupAddress::new_3level(31, 7, 255).unwrap();
assert_eq!(addr.to_bytes(), [0xFF, 0xFF]);
let addr = GroupAddress::new_3level(0, 0, 0).unwrap();
assert_eq!(addr.to_bytes(), [0x00, 0x00]);
}
#[test]
fn destination_display() {
let ind = DestinationAddress::Individual(IndividualAddress::new(1, 1, 1).unwrap());
assert_eq!(alloc::format!("{ind}"), "1.1.1");
let grp = DestinationAddress::Group(GroupAddress::new_3level(1, 0, 1).unwrap());
assert_eq!(alloc::format!("{grp}"), "1/0/1");
}
#[test]
fn parse_invalid_format() {
assert!(IndividualAddress::from_str("1.2").is_err());
assert!(IndividualAddress::from_str("1.2.3.4").is_err());
assert!(IndividualAddress::from_str("").is_err());
assert!(GroupAddress::from_str("").is_err());
assert!(GroupAddress::from_str("1").is_err());
assert!(GroupAddress::from_str("1/2/3/4").is_err());
}
#[test]
fn parse_invalid_segment() {
assert!(IndividualAddress::from_str("a.b.c").is_err());
assert!(GroupAddress::from_str("a/b/c").is_err());
}
}