Skip to main content

bacnet_bip_core/node/
actions.rs

1//! What a [`Node`](super::Node) asks its caller to do, and the fixed queue
2//! that holds it until polled.
3
4use core::net::SocketAddrV4;
5
6use bacnet_bip_codec::Bvll;
7
8use super::exchange::ExchangeId;
9use super::registration::RegistrationEvent;
10
11/// Something for the caller to do. Poll
12/// [`Node::poll_action`](super::Node::poll_action) until [`Action::Idle`]
13/// after every call into the node.
14#[derive(Debug, Clone, Copy, PartialEq, Eq)]
15pub enum Action {
16    /// Nothing more to do.
17    Idle,
18    /// Send a Register-Foreign-Device ([`Action::message`]) to `to`, then
19    /// report it with [`Node::exchange_sent`](super::Node::exchange_sent)
20    /// or [`Node::exchange_failed`](super::Node::exchange_failed).
21    Register {
22        /// The attempt's exchange.
23        exchange: ExchangeId,
24        /// The BBMD.
25        to: SocketAddrV4,
26        /// The time-to-live to ask for.
27        ttl: u16,
28    },
29    /// A manual exchange's response timeout passed unanswered.
30    ExchangeEnded {
31        /// The exchange.
32        exchange: ExchangeId,
33    },
34    /// Send the broadcasts held for the first registration attempt, in the
35    /// order they were held.
36    ReleaseHeld,
37    /// Drop the broadcasts held for the first registration attempt: the
38    /// BBMD refused it.
39    DiscardHeld,
40    /// A change in the foreign device's registration.
41    Registration(RegistrationEvent),
42}
43
44impl Action {
45    /// The message a [`Register`](Action::Register) sends.
46    #[must_use]
47    pub const fn message(&self) -> Option<Bvll<'static>> {
48        match *self {
49            Self::Register { ttl, .. } => Some(Bvll::RegisterForeignDevice { ttl }),
50            _ => None,
51        }
52    }
53}
54
55/// Registration events one call can raise, at most: an attempt's end and
56/// the lease's lapse, or the lapse and an answer.
57const EVENTS: usize = 3;
58
59/// Pending actions, in fixed storage that can't overflow: one ended mark
60/// per exchange slot, a short list of registration events, one verdict on
61/// the held broadcasts (the first attempt ends once), and one registration
62/// attempt (one is in flight at a time). They are polled in that order.
63#[derive(Debug, Clone)]
64pub(crate) struct Actions<const X: usize> {
65    /// Per slot, the generation of an exchange that timed out.
66    ended: [Option<u32>; X],
67    events: [Option<RegistrationEvent>; EVENTS],
68    held: Option<bool>,
69    register: Option<(ExchangeId, SocketAddrV4, u16)>,
70}
71
72impl<const X: usize> Actions<X> {
73    pub(crate) const fn new() -> Self {
74        Self {
75            ended: [None; X],
76            events: [None; EVENTS],
77            held: None,
78            register: None,
79        }
80    }
81
82    pub(crate) fn ended(&mut self, exchange: ExchangeId) {
83        self.ended[exchange.slot()] = Some(exchange.generation());
84    }
85
86    /// Queue `event`; the newest replaces the last if the list is full.
87    pub(crate) fn event(&mut self, event: RegistrationEvent) {
88        match self.events.iter_mut().find(|slot| slot.is_none()) {
89            Some(slot) => *slot = Some(event),
90            None => self.events[EVENTS - 1] = Some(event),
91        }
92    }
93
94    pub(crate) fn held(&mut self, release: bool) {
95        self.held = Some(release);
96    }
97
98    pub(crate) fn register(&mut self, exchange: ExchangeId, to: SocketAddrV4, ttl: u16) {
99        self.register = Some((exchange, to, ttl));
100    }
101
102    /// Drop a queued registration attempt for `exchange`, which ended
103    /// before the caller polled it.
104    pub(crate) fn forget_register(&mut self, exchange: ExchangeId) {
105        if self
106            .register
107            .is_some_and(|(queued, _, _)| queued == exchange)
108        {
109            self.register = None;
110        }
111    }
112
113    pub(crate) fn poll(&mut self) -> Action {
114        for (slot, ended) in self.ended.iter_mut().enumerate() {
115            if let Some(generation) = ended.take() {
116                return Action::ExchangeEnded {
117                    exchange: ExchangeId::new(slot, generation),
118                };
119            }
120        }
121        if let Some(event) = self.events[0].take() {
122            // The emptied slot moves to the back one swap at a time:
123            // `rotate_left` compiles to a memmove call, 0.8-1.4 KB of code
124            // on a microcontroller.
125            for index in 1..EVENTS {
126                self.events.swap(index - 1, index);
127            }
128            return Action::Registration(event);
129        }
130        if let Some(release) = self.held.take() {
131            return if release {
132                Action::ReleaseHeld
133            } else {
134                Action::DiscardHeld
135            };
136        }
137        match self.register.take() {
138            Some((exchange, to, ttl)) => Action::Register { exchange, to, ttl },
139            None => Action::Idle,
140        }
141    }
142}
143
144#[cfg(test)]
145mod tests {
146    use bacnet_time::Instant;
147
148    use super::{Action, Actions, EVENTS, RegistrationEvent};
149
150    const fn renewed(secs: u64) -> RegistrationEvent {
151        RegistrationEvent::Renewed {
152            until: Instant::from_secs(secs),
153        }
154    }
155
156    /// Every event `actions` holds, in the order polled.
157    fn polled(actions: &mut Actions<1>) -> [Option<RegistrationEvent>; EVENTS + 1] {
158        let mut out = [None; EVENTS + 1];
159        for slot in &mut out {
160            match actions.poll() {
161                Action::Registration(event) => *slot = Some(event),
162                Action::Idle => break,
163                other => panic!("{other:?}"),
164            }
165        }
166        out
167    }
168
169    #[test]
170    fn events_are_polled_in_order() {
171        let mut actions = Actions::<1>::new();
172        for secs in 1..=3 {
173            actions.event(renewed(secs));
174        }
175        assert_eq!(
176            polled(&mut actions),
177            [Some(renewed(1)), Some(renewed(2)), Some(renewed(3)), None]
178        );
179        // Polling frees the slots for the next ones.
180        actions.event(RegistrationEvent::Lapsed);
181        actions.event(RegistrationEvent::Unanswered);
182        let next = [RegistrationEvent::Lapsed, RegistrationEvent::Unanswered];
183        assert_eq!(
184            polled(&mut actions),
185            [Some(next[0]), Some(next[1]), None, None]
186        );
187    }
188
189    #[test]
190    fn the_newest_event_replaces_the_last_when_the_list_is_full() {
191        let mut actions = Actions::<1>::new();
192        for secs in 1..=4 {
193            actions.event(renewed(secs));
194        }
195        assert_eq!(
196            polled(&mut actions),
197            [Some(renewed(1)), Some(renewed(2)), Some(renewed(4)), None]
198        );
199    }
200}