use schemars::JsonSchema;
use serde::de::Error;
use serde::{Deserialize, Deserializer, Serialize};
use std::convert::TryFrom;
const DIRECTION_BIT: u16 = 0x8000;
const GATEWAY_ID_MASK: u16 = 0x7fff;
#[derive(Debug, Copy, Clone, Eq, PartialEq)]
pub enum Address {
From(GatewayID),
To(GatewayID),
}
impl From<u16> for Address {
fn from(value: u16) -> Self {
let direction = value & DIRECTION_BIT;
let id = GatewayID(value & GATEWAY_ID_MASK);
if direction == 0 {
Self::To(id)
} else {
Self::From(id)
}
}
}
impl From<Address> for u16 {
fn from(value: Address) -> Self {
match value {
Address::From(id) => id.0 | DIRECTION_BIT,
Address::To(id) => id.0,
}
}
}
impl From<[u8; 2]> for Address {
fn from(value: [u8; 2]) -> Self {
u16::from_be_bytes(value).into()
}
}
impl From<Address> for [u8; 2] {
fn from(value: Address) -> Self {
u16::from(value).to_be_bytes()
}
}
#[derive(Copy, Clone, Eq, PartialEq, Ord, PartialOrd, Serialize, JsonSchema)]
#[serde(transparent)]
pub struct GatewayID(u16);
impl GatewayID {
pub const ZERO: GatewayID = GatewayID(0);
}
impl std::fmt::Debug for GatewayID {
fn fmt(&self, f: &mut std::fmt::Formatter) -> std::fmt::Result {
f.debug_tuple("GatewayID")
.field(&format_args!("{:#04x}", self.0))
.finish()
}
}
impl std::fmt::Display for GatewayID {
fn fmt(&self, f: &mut std::fmt::Formatter) -> std::fmt::Result {
write!(f, "{:#04x}", self.0)
}
}
#[derive(thiserror::Error, Debug, Copy, Clone, Eq, PartialEq)]
#[error("invalid gateway ID {0:04x}")]
pub struct InvalidGatewayID(u16);
impl TryFrom<u16> for GatewayID {
type Error = InvalidGatewayID;
fn try_from(value: u16) -> Result<Self, Self::Error> {
if value & GATEWAY_ID_MASK != value {
Err(InvalidGatewayID(value))
} else {
Ok(GatewayID(value))
}
}
}
impl From<GatewayID> for u16 {
fn from(value: GatewayID) -> Self {
value.0
}
}
impl<'de> Deserialize<'de> for GatewayID {
fn deserialize<D>(deserializer: D) -> Result<Self, D::Error>
where
D: Deserializer<'de>,
{
let id = u16::deserialize(deserializer)?;
Self::try_from(id).map_err(D::Error::custom)
}
}
#[cfg(test)]
mod tests {
use super::*;
#[test]
fn gateway_id() {
assert_eq!(GatewayID::try_from(0), Ok(GatewayID(0)));
assert_eq!(GatewayID::try_from(1), Ok(GatewayID(1)));
assert_eq!(GatewayID::try_from(0x7fff), Ok(GatewayID(0x7fff)));
assert_eq!(GatewayID::try_from(0x8000), Err(InvalidGatewayID(0x8000)));
assert_eq!(GatewayID::try_from(0xffff), Err(InvalidGatewayID(0xffff)));
assert_eq!(u16::from(GatewayID(1)), 1);
assert_eq!(GatewayID(0x1201).to_string(), "0x1201");
}
#[test]
fn address() {
assert_eq!(Address::from([0x12, 0x01]), Address::To(GatewayID(0x1201)));
assert_eq!(
Address::from([0x92, 0x01]),
Address::From(GatewayID(0x1201))
);
assert_eq!(
[0x12, 0x01],
<[u8; 2]>::from(Address::To(GatewayID(0x1201)))
);
assert_eq!(
[0x92, 0x01],
<[u8; 2]>::from(Address::From(GatewayID(0x1201)))
);
}
#[test]
fn address_fmt() {
assert_eq!(format!("{:?}", &GatewayID(0x1201)), "GatewayID(0x1201)");
}
}