1use core::fmt;
13use core::net::{Ipv4Addr, SocketAddrV4};
14
15use bacnet_bip_codec::{BvlcFunction, BvlcResultCode};
16use bacnet_time::{Duration, Instant};
17
18#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash)]
20pub enum Operation {
21 ReadBdt,
23 WriteBdt,
25 ReadFdt,
27 DeleteFdtEntry,
29 RegisterForeignDevice,
31}
32
33impl Operation {
34 pub const ALL: [Self; 5] = [
36 Self::ReadBdt,
37 Self::WriteBdt,
38 Self::ReadFdt,
39 Self::DeleteFdtEntry,
40 Self::RegisterForeignDevice,
41 ];
42
43 #[must_use]
45 pub const fn of(function: BvlcFunction) -> Option<Self> {
46 Some(match function {
47 BvlcFunction::READ_BROADCAST_DISTRIBUTION_TABLE => Self::ReadBdt,
48 BvlcFunction::WRITE_BROADCAST_DISTRIBUTION_TABLE => Self::WriteBdt,
49 BvlcFunction::READ_FOREIGN_DEVICE_TABLE => Self::ReadFdt,
50 BvlcFunction::DELETE_FOREIGN_DEVICE_TABLE_ENTRY => Self::DeleteFdtEntry,
51 BvlcFunction::REGISTER_FOREIGN_DEVICE => Self::RegisterForeignDevice,
52 _ => return None,
53 })
54 }
55
56 #[must_use]
58 pub const fn function(self) -> BvlcFunction {
59 match self {
60 Self::ReadBdt => BvlcFunction::READ_BROADCAST_DISTRIBUTION_TABLE,
61 Self::WriteBdt => BvlcFunction::WRITE_BROADCAST_DISTRIBUTION_TABLE,
62 Self::ReadFdt => BvlcFunction::READ_FOREIGN_DEVICE_TABLE,
63 Self::DeleteFdtEntry => BvlcFunction::DELETE_FOREIGN_DEVICE_TABLE_ENTRY,
64 Self::RegisterForeignDevice => BvlcFunction::REGISTER_FOREIGN_DEVICE,
65 }
66 }
67
68 #[must_use]
71 pub const fn of_nak(code: BvlcResultCode) -> Option<Self> {
72 let mut index = 0;
73 while index < Self::ALL.len() {
74 let operation = Self::ALL[index];
75 if let Some(nak) = operation.function().nak()
76 && nak.to_raw() == code.to_raw()
77 {
78 return Some(operation);
79 }
80 index += 1;
81 }
82 None
83 }
84
85 #[must_use]
87 pub const fn ack(self) -> Option<BvlcFunction> {
88 match self {
89 Self::ReadBdt => Some(BvlcFunction::READ_BROADCAST_DISTRIBUTION_TABLE_ACK),
90 Self::ReadFdt => Some(BvlcFunction::READ_FOREIGN_DEVICE_TABLE_ACK),
91 _ => None,
92 }
93 }
94
95 #[must_use]
97 pub const fn of_ack(function: BvlcFunction) -> Option<Self> {
98 match function {
99 BvlcFunction::READ_BROADCAST_DISTRIBUTION_TABLE_ACK => Some(Self::ReadBdt),
100 BvlcFunction::READ_FOREIGN_DEVICE_TABLE_ACK => Some(Self::ReadFdt),
101 _ => None,
102 }
103 }
104
105 #[must_use]
108 pub const fn answered_by_success(self) -> bool {
109 matches!(
110 self,
111 Self::WriteBdt | Self::DeleteFdtEntry | Self::RegisterForeignDevice
112 )
113 }
114}
115
116#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash)]
118pub struct ExchangeId {
119 slot: u8,
120 generation: u32,
121}
122
123impl ExchangeId {
124 pub(crate) const fn new(slot: usize, generation: u32) -> Self {
125 Self {
126 slot: slot as u8,
127 generation,
128 }
129 }
130
131 #[must_use]
133 pub const fn slot(self) -> usize {
134 self.slot as usize
135 }
136
137 #[must_use]
140 pub const fn generation(self) -> u32 {
141 self.generation
142 }
143}
144
145#[derive(Debug, Clone, Copy, PartialEq, Eq)]
147#[non_exhaustive]
148pub enum ExchangeRefusal {
149 SameExchange,
151 ResultClass,
154 Full,
156 NotARequest,
158 ManagedRegistration,
164}
165
166impl fmt::Display for ExchangeRefusal {
167 fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
168 f.write_str(match self {
169 Self::SameExchange => "the same BVLC request to this peer is in flight",
170 Self::ResultClass => {
171 "another BVLC request answered by success is in flight to this peer"
172 }
173 Self::Full => "every BVLC exchange slot is in use",
174 Self::NotARequest => "not a BVLC management request",
175 Self::ManagedRegistration => {
176 "the foreign-device registration with this BBMD is managed automatically"
177 }
178 })
179 }
180}
181
182impl core::error::Error for ExchangeRefusal {}
183
184#[derive(Debug, Clone, Copy, PartialEq, Eq)]
186#[non_exhaustive]
187pub enum LocalFailure {
188 SendFailed,
190 Cancelled,
192}
193
194#[derive(Debug, Clone, Copy, PartialEq, Eq)]
197pub(crate) struct Early {
198 pub(crate) code: Option<BvlcResultCode>,
200 pub(crate) valid: bool,
202}
203
204#[derive(Debug, Clone, Copy, PartialEq, Eq)]
206pub(crate) enum Phase {
207 Free,
209 Admitted,
211 Sent { deadline: Instant },
213}
214
215#[derive(Debug, Clone, Copy, PartialEq, Eq)]
218pub(crate) struct Slot {
219 pub(crate) phase: Phase,
220 pub(crate) peer: SocketAddrV4,
221 pub(crate) operation: Operation,
222 pub(crate) generation: u32,
223 pub(crate) timeout: Duration,
224 pub(crate) automatic: bool,
226 pub(crate) ttl: u16,
228 pub(crate) early: Option<Early>,
229 pub(crate) late_until: Option<Instant>,
232}
233
234impl Slot {
235 const FREE: Self = Self {
236 phase: Phase::Free,
237 peer: SocketAddrV4::new(Ipv4Addr::UNSPECIFIED, 0),
238 operation: Operation::ReadBdt,
239 generation: 0,
240 timeout: Duration::ZERO,
241 automatic: false,
242 ttl: 0,
243 early: None,
244 late_until: None,
245 };
246
247 fn is_live(&self) -> bool {
248 self.phase != Phase::Free
249 }
250
251 fn id(&self, slot: usize) -> ExchangeId {
252 ExchangeId::new(slot, self.generation)
253 }
254}
255
256#[derive(Debug, Clone, Copy, PartialEq, Eq)]
258pub(crate) enum Key {
259 Success,
261 Operation(Operation),
263}
264
265impl Key {
266 pub(crate) fn of(function: BvlcFunction, code: Option<BvlcResultCode>) -> Option<Self> {
270 match code {
271 Some(BvlcResultCode::SUCCESSFUL_COMPLETION) => Some(Self::Success),
272 Some(code) => Operation::of_nak(code).map(Self::Operation),
273 None => Operation::of_ack(function).map(Self::Operation),
274 }
275 }
276
277 fn fits(self, operation: Operation) -> bool {
278 match self {
279 Self::Success => operation.answered_by_success(),
280 Self::Operation(own) => own == operation,
281 }
282 }
283}
284
285#[derive(Debug, Clone)]
287pub(crate) struct Exchanges<const X: usize> {
288 slots: [Slot; X],
289 generation: u32,
290}
291
292impl<const X: usize> Exchanges<X> {
293 pub(crate) const fn new() -> Self {
294 Self {
295 slots: [Slot::FREE; X],
296 generation: 0,
297 }
298 }
299
300 pub(crate) fn admit(
302 &mut self,
303 peer: SocketAddrV4,
304 operation: Operation,
305 timeout: Duration,
306 automatic: bool,
307 ttl: u16,
308 ) -> Result<ExchangeId, ExchangeRefusal> {
309 for slot in self
310 .slots
311 .iter()
312 .filter(|slot| slot.is_live() && slot.peer == peer)
313 {
314 if slot.operation == operation {
315 return Err(ExchangeRefusal::SameExchange);
316 }
317 if operation.answered_by_success() && slot.operation.answered_by_success() {
318 return Err(ExchangeRefusal::ResultClass);
319 }
320 }
321 let index = self
322 .slots
323 .iter()
324 .position(|slot| !slot.is_live())
325 .ok_or(ExchangeRefusal::Full)?;
326 self.generation = self.generation.wrapping_add(1);
327 self.slots[index] = Slot {
328 phase: Phase::Admitted,
329 peer,
330 operation,
331 generation: self.generation,
332 timeout,
333 automatic,
334 ttl,
335 early: None,
336 late_until: None,
337 };
338 Ok(self.slots[index].id(index))
339 }
340
341 pub(crate) fn live(&self, id: ExchangeId) -> Option<usize> {
343 let slot = self.slots.get(id.slot())?;
344 (slot.is_live() && slot.generation == id.generation).then_some(id.slot())
345 }
346
347 pub(crate) fn slot(&self, index: usize) -> &Slot {
348 &self.slots[index]
349 }
350
351 pub(crate) fn slot_mut(&mut self, index: usize) -> &mut Slot {
352 &mut self.slots[index]
353 }
354
355 pub(crate) fn id(&self, index: usize) -> ExchangeId {
356 self.slots[index].id(index)
357 }
358
359 pub(crate) fn find(&self, peer: SocketAddrV4, key: Key) -> Option<usize> {
361 self.slots
362 .iter()
363 .position(|slot| slot.is_live() && slot.peer == peer && key.fits(slot.operation))
364 }
365
366 pub(crate) fn first_with(&self, peer: SocketAddrV4, function: BvlcFunction) -> Option<usize> {
370 let read = Operation::of_ack(function);
371 self.slots.iter().position(|slot| {
372 slot.is_live()
373 && slot.peer == peer
374 && (function == BvlcFunction::BVLC_RESULT || Some(slot.operation) == read)
375 })
376 }
377
378 pub(crate) fn is_late(&self, peer: SocketAddrV4, key: Key, now: Instant) -> bool {
381 self.slots.iter().any(|slot| {
382 !slot.is_live()
383 && slot.peer == peer
384 && key.fits(slot.operation)
385 && slot.late_until.is_some_and(|until| now < until)
386 })
387 }
388
389 pub(crate) fn retire(&mut self, index: usize, now: Instant) -> Slot {
392 let ended = self.slots[index];
393 let slot = &mut self.slots[index];
394 slot.late_until = matches!(ended.phase, Phase::Sent { .. }).then(|| now + ended.timeout);
395 slot.phase = Phase::Free;
396 slot.early = None;
397 ended
398 }
399
400 pub(crate) fn automatic(&self) -> Option<usize> {
402 self.slots
403 .iter()
404 .position(|slot| slot.is_live() && slot.automatic)
405 }
406
407 pub(crate) fn next_deadline(&self) -> Option<(usize, Instant)> {
409 let mut first: Option<(usize, Instant)> = None;
410 for (index, slot) in self.slots.iter().enumerate() {
411 if let Phase::Sent { deadline } = slot.phase
412 && first.is_none_or(|(_, earliest)| deadline < earliest)
413 {
414 first = Some((index, deadline));
415 }
416 }
417 first
418 }
419}