Skip to main content

bacnet_bip_core/node/
exchange.rs

1//! This node's own management exchanges: a request to a peer and the one
2//! response that settles it.
3//!
4//! A BVLC-Result carries no invoke ID, so a response is matched by who sent
5//! it and what it can answer. At most one exchange is live per peer and
6//! operation, and at most one per peer among the operations a success can
7//! answer, so every response has at most one exchange it fits. Each
8//! admitted exchange takes the next local generation, which its
9//! [`ExchangeId`] carries: a handle kept past its exchange's end never
10//! touches a newer one in the same slot.
11
12use core::fmt;
13use core::net::{Ipv4Addr, SocketAddrV4};
14
15use bacnet_bip_codec::{BvlcFunction, BvlcResultCode};
16use bacnet_time::{Duration, Instant};
17
18/// A management request this node can make, and expect an answer to.
19#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash)]
20pub enum Operation {
21    /// Read-Broadcast-Distribution-Table: a Read-BDT-Ack or its NAK.
22    ReadBdt,
23    /// Write-Broadcast-Distribution-Table: success or its NAK.
24    WriteBdt,
25    /// Read-Foreign-Device-Table: a Read-FDT-Ack or its NAK.
26    ReadFdt,
27    /// Delete-Foreign-Device-Table-Entry: success or its NAK.
28    DeleteFdtEntry,
29    /// Register-Foreign-Device: success or its NAK.
30    RegisterForeignDevice,
31}
32
33impl Operation {
34    /// The five operations.
35    pub const ALL: [Self; 5] = [
36        Self::ReadBdt,
37        Self::WriteBdt,
38        Self::ReadFdt,
39        Self::DeleteFdtEntry,
40        Self::RegisterForeignDevice,
41    ];
42
43    /// The operation a request of `function` is, if any.
44    #[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    /// The request's function.
57    #[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    /// The operation whose NAK is `code`, if any of the five: a
69    /// Distribute-Broadcast-To-Network NAK names none.
70    #[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    /// The ACK that answers it, for the two table reads.
86    #[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    /// The operation a table ACK of `function` answers.
96    #[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    /// Whether a success answers it. A success names no operation, so a
106    /// peer has at most one of these live at a time.
107    #[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/// One admitted exchange: its slot and its generation.
117#[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    /// The slot it occupies.
132    #[must_use]
133    pub const fn slot(self) -> usize {
134        self.slot as usize
135    }
136
137    /// Its generation, unique among the node's exchanges until the counter
138    /// wraps.
139    #[must_use]
140    pub const fn generation(self) -> u32 {
141        self.generation
142    }
143}
144
145/// Why an exchange wasn't admitted. Nothing was sent.
146#[derive(Debug, Clone, Copy, PartialEq, Eq)]
147#[non_exhaustive]
148pub enum ExchangeRefusal {
149    /// The same operation to the same peer is live.
150    SameExchange,
151    /// Another operation a success answers is live to the same peer, and a
152    /// success couldn't tell the two apart.
153    ResultClass,
154    /// Every slot is in use.
155    Full,
156    /// The message isn't one of the five requests ([`Operation`]).
157    NotARequest,
158    /// A foreign device's own BBMD, for a request a success answers
159    /// (Register-Foreign-Device, Delete-Foreign-Device-Table-Entry,
160    /// Write-Broadcast-Distribution-Table). The node owns that
161    /// registration: such a request could change the BBMD's entry behind
162    /// the lease, and its success could pass for a registration's.
163    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/// Why the caller ended an exchange itself.
185#[derive(Debug, Clone, Copy, PartialEq, Eq)]
186#[non_exhaustive]
187pub enum LocalFailure {
188    /// Encoding or sending the request failed.
189    SendFailed,
190    /// The caller gave up waiting.
191    Cancelled,
192}
193
194/// What a response that beat the request's send said, kept until the send
195/// is confirmed.
196#[derive(Debug, Clone, Copy, PartialEq, Eq)]
197pub(crate) struct Early {
198    /// A BVLC-Result's code; `None` for a table ACK.
199    pub(crate) code: Option<BvlcResultCode>,
200    /// Whether it was accepted (decoded, and taken by the policy).
201    pub(crate) valid: bool,
202}
203
204/// Where an exchange is.
205#[derive(Debug, Clone, Copy, PartialEq, Eq)]
206pub(crate) enum Phase {
207    /// No exchange.
208    Free,
209    /// Admitted; the send isn't confirmed yet.
210    Admitted,
211    /// Sent; it times out at the deadline.
212    Sent { deadline: Instant },
213}
214
215/// One slot: an exchange, or the last one's key while its late results
216/// may still arrive.
217#[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    /// An automatic registration attempt, which the lifecycle owns.
225    pub(crate) automatic: bool,
226    /// The time-to-live a Register-Foreign-Device asked for.
227    pub(crate) ttl: u16,
228    pub(crate) early: Option<Early>,
229    /// Once a sent exchange ends: until when a result for its key counts
230    /// as late.
231    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/// What a well-formed response can answer.
257#[derive(Debug, Clone, Copy, PartialEq, Eq)]
258pub(crate) enum Key {
259    /// A success: whichever operation a success answers.
260    Success,
261    /// A NAK or an ACK: this operation.
262    Operation(Operation),
263}
264
265impl Key {
266    /// The key of a response of `function` carrying `code` (a
267    /// BVLC-Result's), or `None` when it answers nothing this node asks,
268    /// such as an unknown code or a Distribute-Broadcast-To-Network NAK.
269    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/// `X` exchange slots and the generation counter.
286#[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    /// Admit an exchange of `operation` with `peer`, or say why not.
301    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    /// The slot `id` names, while its exchange is live.
342    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    /// The live exchange with `peer` that a response with `key` answers.
360    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    /// The first live exchange with `peer`, in slot order, that a response
367    /// of `function` could be for: any, for a BVLC-Result; the table read,
368    /// for an ACK. A malformed response is counted against it.
369    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    /// Whether a response with `key` from `peer` comes within one response
379    /// timeout of a sent exchange it fits ending.
380    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    /// End the exchange in `index`, and return what it was. A sent one
390    /// leaves its key for one response timeout, to tell late results apart.
391    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    /// The live automatic registration attempt, if one is in flight.
401    pub(crate) fn automatic(&self) -> Option<usize> {
402        self.slots
403            .iter()
404            .position(|slot| slot.is_live() && slot.automatic)
405    }
406
407    /// The sent exchange whose deadline comes first, and that deadline.
408    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}