use crate::*;
use core::convert::TryFrom;
use core::fmt;
use core::fmt::{Debug, Display};
use core::iter::FusedIterator;
use core::num::NonZeroU16;
use core::str::FromStr;
#[cfg(feature = "alloc")]
use alloc::string::String;
#[derive(Clone, Copy, Eq, PartialEq, Hash)]
#[repr(transparent)]
pub struct HamAddr([u8; 8]);
#[derive(Debug, Clone, Copy, Eq, PartialEq, Hash)]
pub enum HamAddrType {
Empty,
Callsign,
Ipv4Multicast,
Ipv6Multicast,
Short,
Broadcast,
Reserved,
}
impl HamAddr {
pub const EMPTY: HamAddr = HamAddr([0, 0, 0, 0, 0, 0, 0, 0]);
pub const BROADCAST: HamAddr = HamAddr([0xFF, 0xFF, 0, 0, 0, 0, 0, 0]);
pub const fn new(octets: [u8; 8]) -> HamAddr {
HamAddr(octets)
}
pub const fn try_from_shortaddr(shortaddr: NonZeroU16) -> Option<HamAddr> {
if shortaddr.get() > 0x063F {
return None;
}
let bytes = shortaddr.get().to_be_bytes();
let mut ret = Self::EMPTY;
ret.0[0] = bytes[0];
ret.0[1] = bytes[1];
Some(ret)
}
pub fn from_chunks(chunks: [u16; 4]) -> HamAddr {
let mut ret = Self::EMPTY;
let mut iter_mut = ret.0.iter_mut();
let mut iter = chunks.into_iter().flat_map(u16::to_be_bytes);
for _ in 0..8 {
*iter_mut.next().unwrap() = iter.next().unwrap()
}
ret
}
pub fn try_from_slice(bytes: &[u8]) -> Result<HamAddr> {
if (bytes.len() & 1) == 1 || bytes.len() > 8 {
return Err(Error::InvalidSliceLength);
}
let mut ret = HamAddr::EMPTY;
ret.0[..bytes.len()].copy_from_slice(bytes);
Ok(ret)
}
pub fn try_from_callsign(callsign: &str) -> Result<HamAddr> {
struct StrChunkIterator<T: Iterator<Item = char> + FusedIterator>(T);
impl<T: Iterator<Item = char> + FusedIterator> Iterator for StrChunkIterator<T> {
type Item = Result<u16>;
fn next(&mut self) -> Option<Self::Item> {
let c0 = self.0.next()?;
let c1 = self.0.next().unwrap_or('\x00');
let c2 = self.0.next().unwrap_or('\x00');
Some(
HamCharChunk::try_from([c0, c1, c2])
.map(u16::from)
.map_err(Error::from),
)
}
}
if let Some('~') | None = callsign.chars().next() {
if callsign.len() <= 1 {
return Ok(HamAddr::EMPTY);
}
if callsign == "~FFFF" || callsign == "~ffff" {
return Ok(HamAddr::BROADCAST);
}
return Err(Error::UnsupportedRawNotation);
}
let mut iter = StrChunkIterator(callsign.chars());
let mut chunks = [0u16; 4];
for chunk in chunks.iter_mut() {
*chunk = iter.next().transpose()?.unwrap_or(0);
}
if iter.next().is_some() {
return Err(Error::CallsignTooLong);
}
Ok(HamAddr::from_chunks(chunks))
}
pub const fn shortaddr(&self) -> Option<NonZeroU16> {
match self.get_type() {
HamAddrType::Short => NonZeroU16::new(self.chunk(0)),
_ => None,
}
}
pub fn octets(self) -> [u8; 8] {
self.0
}
pub fn trimmed_bytes(self) -> impl Iterator<Item = u8> {
let len = self.len();
self.0.into_iter().take(len)
}
pub const fn as_slice(&self) -> &[u8] {
&self.0
}
pub fn as_trimmed_slice(&self) -> &[u8] {
&self.0[..self.len()]
}
pub const fn len(&self) -> usize {
if self.chunk(3) != 0 {
8
} else if self.chunk(2) != 0 {
6
} else if self.chunk(1) != 0 {
4
} else {
2
}
}
pub const fn chunk(&self, i: usize) -> u16 {
u16::from_be_bytes([self.0[i * 2], self.0[i * 2 + 1]])
}
pub const fn chunks(&self) -> [u16; 4] {
[self.chunk(0), self.chunk(1), self.chunk(2), self.chunk(3)]
}
pub const fn is_empty(&self) -> bool {
self.chunk(0) == 0 && self.chunk(1) == 0 && self.chunk(2) == 0 && self.chunk(3) == 0
}
pub const fn is_callsign(&self) -> bool {
match self.get_type() {
HamAddrType::Callsign => true,
_ => false,
}
}
pub const fn is_unicast(&self) -> bool {
match self.get_type() {
HamAddrType::Callsign | HamAddrType::Short => true,
_ => false,
}
}
pub const fn is_broadcast(&self) -> bool {
self.chunk(0) == 0xFFFF && self.chunk(1) == 0 && self.chunk(2) == 0 && self.chunk(3) == 0
}
pub const fn is_reserved(&self) -> bool {
match self.get_type() {
HamAddrType::Reserved => true,
_ => false,
}
}
pub const fn is_multicast(&self) -> bool {
match self.get_type() {
HamAddrType::Ipv4Multicast | HamAddrType::Ipv6Multicast => true,
_ => false,
}
}
pub const fn is_multicast_or_broadcast(&self) -> bool {
match self.get_type() {
HamAddrType::Ipv4Multicast | HamAddrType::Ipv6Multicast | HamAddrType::Broadcast => {
true
}
_ => false,
}
}
pub const fn get_type(&self) -> HamAddrType {
if self.is_empty() {
HamAddrType::Empty
} else if self.chunk(0) < 0x0640 {
if self.len() == 2 {
HamAddrType::Short
} else {
HamAddrType::Reserved
}
} else if self.chunk(0) < 0xFA00 {
let chunk = self.chunk(1);
if chunk != 0 && (chunk < 0x0640 || chunk >= 0xFA00) {
return HamAddrType::Reserved;
}
let chunk = self.chunk(2);
if chunk != 0 && (chunk < 0x0640 || chunk >= 0xFA00) {
return HamAddrType::Reserved;
}
let chunk = self.chunk(3);
if chunk != 0 && (chunk < 0x0640 || chunk >= 0xFA00) {
return HamAddrType::Reserved;
}
HamAddrType::Callsign
} else if self.is_broadcast() {
HamAddrType::Broadcast
} else if self.0[0] == 0xFA {
HamAddrType::Ipv6Multicast
} else if self.0[0] == 0xFB {
HamAddrType::Ipv4Multicast
} else {
HamAddrType::Reserved
}
}
#[cfg(feature = "alloc")]
pub fn to_addr_string(&self) -> String {
alloc::format!("{:?}", self)
}
fn callsign_len(&self) -> Option<usize> {
if self.chunk(3) != 0 {
Some(9 + HamCharChunk::try_from(self.chunk(3)).ok()?.len())
} else if self.chunk(2) != 0 {
Some(6 + HamCharChunk::try_from(self.chunk(2)).ok()?.len())
} else if self.chunk(1) != 0 {
Some(4 + HamCharChunk::try_from(self.chunk(1)).ok()?.len())
} else {
Some(HamCharChunk::try_from(self.chunk(0)).ok()?.len())
}
}
fn apply_eui48_hack(mut self) -> Option<Self> {
match self.callsign_len() {
Some(len) if len < 9 => Some(self),
Some(9) => HamCharChunk::try_from(self.chunk(2))
.unwrap()
.try_apply_eui_hack()
.and_then(|chunk| {
self.0[4..6].copy_from_slice(&u16::to_be_bytes(chunk.into()));
Some(self)
}),
_ => None,
}
}
fn apply_eui64_hack(mut self) -> Option<Self> {
match self.callsign_len() {
Some(len) if len < 12 => Some(self),
Some(12) => HamCharChunk::try_from(self.chunk(3))
.unwrap()
.try_apply_eui_hack()
.and_then(|chunk| {
self.0[6..8].copy_from_slice(&u16::to_be_bytes(chunk.into()));
Some(self)
}),
_ => None,
}
}
fn reverse_eui48_hack(mut self) -> Option<Self> {
match self.callsign_len() {
Some(len) if len < 9 => Some(self),
Some(9) => HamCharChunk::try_from(self.chunk(2))
.unwrap()
.try_reverse_eui_hack()
.and_then(|chunk| {
self.0[4..6].copy_from_slice(&u16::to_be_bytes(chunk.into()));
Some(self)
}),
_ => None,
}
}
fn reverse_eui64_hack(mut self) -> Option<Self> {
match self.callsign_len() {
Some(len) if len < 12 => Some(self),
Some(12) => HamCharChunk::try_from(self.chunk(3))
.unwrap()
.try_reverse_eui_hack()
.and_then(|chunk| {
self.0[6..8].copy_from_slice(&u16::to_be_bytes(chunk.into()));
Some(self)
}),
_ => None,
}
}
}
impl Display for HamAddr {
fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
match self.get_type() {
HamAddrType::Empty => {
write!(f, "~")
}
HamAddrType::Callsign => {
for chunk in self
.chunks()
.into_iter()
.map(|x| HamCharChunk::try_from(x).unwrap())
{
write!(f, "{}", chunk)?;
}
Ok(())
}
_ => {
write!(f, "~{:?}", self)
}
}
}
}
impl Debug for HamAddr {
fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
if f.alternate() || self.len() >= 8 {
write!(
f,
"{:04X?}-{:04X?}-{:04X?}-{:04X?}",
self.chunk(0),
self.chunk(1),
self.chunk(2),
self.chunk(3)
)
} else if self.len() >= 6 {
write!(
f,
"{:04X?}-{:04X?}-{:04X?}",
self.chunk(0),
self.chunk(1),
self.chunk(2)
)
} else if self.len() >= 4 {
write!(f, "{:04X?}-{:04X?}", self.chunk(0), self.chunk(1))
} else {
write!(f, "{:04X?}", self.chunk(0))
}
}
}
impl FromStr for HamAddr {
type Err = Error;
fn from_str(callsign: &str) -> core::result::Result<Self, Self::Err> {
HamAddr::try_from_callsign(callsign)
}
}
impl TryFrom<HamAddr> for Eui64 {
type Error = Error;
fn try_from(value: HamAddr) -> core::result::Result<Self, Self::Error> {
match value.get_type() {
HamAddrType::Empty => Ok(Eui64::EMPTY),
HamAddrType::Broadcast => Ok(Eui64::BROADCAST),
HamAddrType::Callsign => {
if let Ok(ret) = Eui48::try_from(value) {
return Ok(ret.into());
}
let value = value.apply_eui64_hack().ok_or(Error::HamAddrTooBig)?;
let mut bytes = [0u8; 8];
bytes.copy_from_slice(&value.octets()[..8]);
bytes.rotate_right(1);
bytes[0] = (bytes[0] & 0b1111_1000) | 0b0010;
Ok(Eui64::new(bytes))
}
HamAddrType::Ipv4Multicast | HamAddrType::Ipv6Multicast => {
Err(Error::MulticastEui64NotSupported)
}
kind => Err(Error::CannotConvertToEui64(kind)),
}
}
}
impl TryFrom<HamAddr> for Eui48 {
type Error = Error;
fn try_from(value: HamAddr) -> core::result::Result<Self, Self::Error> {
match value.get_type() {
HamAddrType::Empty => Ok(Eui48::EMPTY),
HamAddrType::Broadcast => Ok(Eui48::BROADCAST),
HamAddrType::Callsign => {
let value = value.apply_eui48_hack().ok_or(Error::HamAddrTooBig)?;
let mut bytes = [0u8; 6];
bytes.copy_from_slice(&value.octets()[..6]);
bytes.rotate_right(1);
bytes[0] = (bytes[0] & 0b1111_1000) | 0b0010;
Ok(Eui48::new(bytes))
}
HamAddrType::Ipv4Multicast => {
let bytes = value.as_slice();
Ok(Eui48::new([0x01, 0x00, 0x5e, bytes[3], bytes[2], bytes[1]]))
}
HamAddrType::Ipv6Multicast => {
let bytes = value.as_slice();
Ok(Eui48::new([
0xcc, 0xcc, bytes[4], bytes[3], bytes[2], bytes[1],
]))
}
kind => Err(Error::CannotConvertToEui48(kind)),
}
}
}
impl TryFrom<Eui48> for HamAddr {
type Error = Error;
fn try_from(value: Eui48) -> core::result::Result<Self, Self::Error> {
if value == Eui48::EMPTY {
return Ok(HamAddr::EMPTY);
}
if value == Eui48::BROADCAST {
return Ok(HamAddr::BROADCAST);
}
if value.0[..3] == [0x01, 0x00, 0x5e] {
return Ok(
HamAddr::try_from_slice(&[0xFB, value.0[5], value.0[4], value.0[3]]).unwrap(),
);
}
if value.0[..2] == [0xCC, 0xCC] {
return Ok(HamAddr::try_from_slice(&[
0xFA, value.0[5], value.0[4], value.0[3], value.0[2], 0x00,
])
.unwrap());
}
let mut octets = value.0;
if octets[0] & 0b111 == 0b010 {
octets[0] &= 0b1111_1101;
octets.rotate_left(1);
let mut bytes = [0; 8];
bytes[..6].copy_from_slice(&octets);
let ret = HamAddr(bytes).reverse_eui48_hack().unwrap();
match ret.get_type() {
HamAddrType::Callsign => Ok(ret),
_ => Err(Error::Eui48MissingCallsign),
}
} else {
Err(Error::Eui48MissingCallsign)
}
}
}
impl TryFrom<Eui64> for HamAddr {
type Error = Error;
fn try_from(value: Eui64) -> core::result::Result<Self, Self::Error> {
if value == Eui64::EMPTY {
return Ok(HamAddr::EMPTY);
}
if value == Eui64::BROADCAST {
return Ok(HamAddr::BROADCAST);
}
if let Some(eui48) = value.try_to_eui48() {
return HamAddr::try_from(eui48);
}
let mut bytes = value.0;
if bytes[0] & 0b111 == 0b010 {
bytes[0] &= 0b1111_1101;
bytes.rotate_left(1);
let ret = HamAddr(bytes).reverse_eui64_hack().unwrap();
match ret.get_type() {
HamAddrType::Callsign => Ok(ret),
_ => Err(Error::Eui64MissingCallsign),
}
} else {
Err(Error::Eui64MissingCallsign)
}
}
}