use core::fmt;
use core::net::{Ipv4Addr, SocketAddrV4};
use bacnet_bip_codec::{BvlcFunction, BvlcResultCode};
use bacnet_time::{Duration, Instant};
#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash)]
pub enum Operation {
ReadBdt,
WriteBdt,
ReadFdt,
DeleteFdtEntry,
RegisterForeignDevice,
}
impl Operation {
pub const ALL: [Self; 5] = [
Self::ReadBdt,
Self::WriteBdt,
Self::ReadFdt,
Self::DeleteFdtEntry,
Self::RegisterForeignDevice,
];
#[must_use]
pub const fn of(function: BvlcFunction) -> Option<Self> {
Some(match function {
BvlcFunction::READ_BROADCAST_DISTRIBUTION_TABLE => Self::ReadBdt,
BvlcFunction::WRITE_BROADCAST_DISTRIBUTION_TABLE => Self::WriteBdt,
BvlcFunction::READ_FOREIGN_DEVICE_TABLE => Self::ReadFdt,
BvlcFunction::DELETE_FOREIGN_DEVICE_TABLE_ENTRY => Self::DeleteFdtEntry,
BvlcFunction::REGISTER_FOREIGN_DEVICE => Self::RegisterForeignDevice,
_ => return None,
})
}
#[must_use]
pub const fn function(self) -> BvlcFunction {
match self {
Self::ReadBdt => BvlcFunction::READ_BROADCAST_DISTRIBUTION_TABLE,
Self::WriteBdt => BvlcFunction::WRITE_BROADCAST_DISTRIBUTION_TABLE,
Self::ReadFdt => BvlcFunction::READ_FOREIGN_DEVICE_TABLE,
Self::DeleteFdtEntry => BvlcFunction::DELETE_FOREIGN_DEVICE_TABLE_ENTRY,
Self::RegisterForeignDevice => BvlcFunction::REGISTER_FOREIGN_DEVICE,
}
}
#[must_use]
pub const fn of_nak(code: BvlcResultCode) -> Option<Self> {
let mut index = 0;
while index < Self::ALL.len() {
let operation = Self::ALL[index];
if let Some(nak) = operation.function().nak() {
if nak.to_raw() == code.to_raw() {
return Some(operation);
}
}
index += 1;
}
None
}
#[must_use]
pub const fn ack(self) -> Option<BvlcFunction> {
match self {
Self::ReadBdt => Some(BvlcFunction::READ_BROADCAST_DISTRIBUTION_TABLE_ACK),
Self::ReadFdt => Some(BvlcFunction::READ_FOREIGN_DEVICE_TABLE_ACK),
_ => None,
}
}
#[must_use]
pub const fn of_ack(function: BvlcFunction) -> Option<Self> {
match function {
BvlcFunction::READ_BROADCAST_DISTRIBUTION_TABLE_ACK => Some(Self::ReadBdt),
BvlcFunction::READ_FOREIGN_DEVICE_TABLE_ACK => Some(Self::ReadFdt),
_ => None,
}
}
#[must_use]
pub const fn answered_by_success(self) -> bool {
matches!(
self,
Self::WriteBdt | Self::DeleteFdtEntry | Self::RegisterForeignDevice
)
}
}
#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash)]
pub struct ExchangeId {
slot: u8,
generation: u32,
}
impl ExchangeId {
pub(crate) const fn new(slot: usize, generation: u32) -> Self {
Self {
slot: slot as u8,
generation,
}
}
#[must_use]
pub const fn slot(self) -> usize {
self.slot as usize
}
#[must_use]
pub const fn generation(self) -> u32 {
self.generation
}
}
#[derive(Debug, Clone, Copy, PartialEq, Eq)]
#[non_exhaustive]
pub enum ExchangeRefusal {
SameExchange,
ResultClass,
Full,
NotARequest,
ManagedRegistration,
}
impl fmt::Display for ExchangeRefusal {
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
f.write_str(match self {
Self::SameExchange => "the same BVLC request to this peer is in flight",
Self::ResultClass => {
"another BVLC request answered by success is in flight to this peer"
}
Self::Full => "every BVLC exchange slot is in use",
Self::NotARequest => "not a BVLC management request",
Self::ManagedRegistration => {
"the foreign-device registration with this BBMD is managed automatically"
}
})
}
}
impl core::error::Error for ExchangeRefusal {}
#[derive(Debug, Clone, Copy, PartialEq, Eq)]
#[non_exhaustive]
pub enum LocalFailure {
SendFailed,
Cancelled,
}
#[derive(Debug, Clone, Copy, PartialEq, Eq)]
pub(crate) struct Early {
pub(crate) code: Option<BvlcResultCode>,
pub(crate) valid: bool,
}
#[derive(Debug, Clone, Copy, PartialEq, Eq)]
pub(crate) enum Phase {
Free,
Admitted,
Sent { deadline: Instant },
}
#[derive(Debug, Clone, Copy, PartialEq, Eq)]
pub(crate) struct Slot {
pub(crate) phase: Phase,
pub(crate) peer: SocketAddrV4,
pub(crate) operation: Operation,
pub(crate) generation: u32,
pub(crate) timeout: Duration,
pub(crate) automatic: bool,
pub(crate) ttl: u16,
pub(crate) early: Option<Early>,
pub(crate) late_until: Option<Instant>,
}
impl Slot {
const FREE: Self = Self {
phase: Phase::Free,
peer: SocketAddrV4::new(Ipv4Addr::UNSPECIFIED, 0),
operation: Operation::ReadBdt,
generation: 0,
timeout: Duration::ZERO,
automatic: false,
ttl: 0,
early: None,
late_until: None,
};
fn is_live(&self) -> bool {
self.phase != Phase::Free
}
fn id(&self, slot: usize) -> ExchangeId {
ExchangeId::new(slot, self.generation)
}
}
#[derive(Debug, Clone, Copy, PartialEq, Eq)]
pub(crate) enum Key {
Success,
Operation(Operation),
}
impl Key {
pub(crate) fn of(function: BvlcFunction, code: Option<BvlcResultCode>) -> Option<Self> {
match code {
Some(BvlcResultCode::SUCCESSFUL_COMPLETION) => Some(Self::Success),
Some(code) => Operation::of_nak(code).map(Self::Operation),
None => Operation::of_ack(function).map(Self::Operation),
}
}
fn fits(self, operation: Operation) -> bool {
match self {
Self::Success => operation.answered_by_success(),
Self::Operation(own) => own == operation,
}
}
}
#[derive(Debug, Clone)]
pub(crate) struct Exchanges<const X: usize> {
slots: [Slot; X],
generation: u32,
}
impl<const X: usize> Exchanges<X> {
pub(crate) const fn new() -> Self {
Self {
slots: [Slot::FREE; X],
generation: 0,
}
}
pub(crate) fn admit(
&mut self,
peer: SocketAddrV4,
operation: Operation,
timeout: Duration,
automatic: bool,
ttl: u16,
) -> Result<ExchangeId, ExchangeRefusal> {
for slot in self
.slots
.iter()
.filter(|slot| slot.is_live() && slot.peer == peer)
{
if slot.operation == operation {
return Err(ExchangeRefusal::SameExchange);
}
if operation.answered_by_success() && slot.operation.answered_by_success() {
return Err(ExchangeRefusal::ResultClass);
}
}
let index = self
.slots
.iter()
.position(|slot| !slot.is_live())
.ok_or(ExchangeRefusal::Full)?;
self.generation = self.generation.wrapping_add(1);
self.slots[index] = Slot {
phase: Phase::Admitted,
peer,
operation,
generation: self.generation,
timeout,
automatic,
ttl,
early: None,
late_until: None,
};
Ok(self.slots[index].id(index))
}
pub(crate) fn live(&self, id: ExchangeId) -> Option<usize> {
let slot = self.slots.get(id.slot())?;
(slot.is_live() && slot.generation == id.generation).then_some(id.slot())
}
pub(crate) fn slot(&self, index: usize) -> &Slot {
&self.slots[index]
}
pub(crate) fn slot_mut(&mut self, index: usize) -> &mut Slot {
&mut self.slots[index]
}
pub(crate) fn id(&self, index: usize) -> ExchangeId {
self.slots[index].id(index)
}
pub(crate) fn find(&self, peer: SocketAddrV4, key: Key) -> Option<usize> {
self.slots
.iter()
.position(|slot| slot.is_live() && slot.peer == peer && key.fits(slot.operation))
}
pub(crate) fn first_with(&self, peer: SocketAddrV4, function: BvlcFunction) -> Option<usize> {
let read = Operation::of_ack(function);
self.slots.iter().position(|slot| {
slot.is_live()
&& slot.peer == peer
&& (function == BvlcFunction::BVLC_RESULT || Some(slot.operation) == read)
})
}
pub(crate) fn is_late(&self, peer: SocketAddrV4, key: Key, now: Instant) -> bool {
self.slots.iter().any(|slot| {
!slot.is_live()
&& slot.peer == peer
&& key.fits(slot.operation)
&& slot.late_until.is_some_and(|until| now < until)
})
}
pub(crate) fn retire(&mut self, index: usize, now: Instant) -> Slot {
let ended = self.slots[index];
let slot = &mut self.slots[index];
slot.late_until = matches!(ended.phase, Phase::Sent { .. }).then(|| now + ended.timeout);
slot.phase = Phase::Free;
slot.early = None;
ended
}
pub(crate) fn next_deadline(&self) -> Option<(usize, Instant)> {
let mut first: Option<(usize, Instant)> = None;
for (index, slot) in self.slots.iter().enumerate() {
if let Phase::Sent { deadline } = slot.phase {
if first.is_none_or(|(_, earliest)| deadline < earliest) {
first = Some((index, deadline));
}
}
}
first
}
}