umsh_ulcp_device/
lan_bearer.rs

1//! Saved device-domain configuration for the LAN bearer.
2use umsh_ulcp::{
3    Status,
4    ids::prop,
5    lan::{DEFAULT_INGRESS_LIMIT, Group},
6};
7
8/// How a device carries the LAN bearer, if at all.
9#[derive(Clone, Copy, Debug, Default, PartialEq, Eq)]
10pub enum LanBearerRole {
11    /// No bearer: `CAP_LAN_BEARER` is absent and its properties unknown.
12    #[default]
13    None,
14    /// Beside a node, which forwards between the group and the LoRa radio.
15    /// Every bearer property and counter exists. Meaningful only alongside
16    /// [`SessionConfig::mac_node`](crate::SessionConfig::mac_node).
17    Forwarding,
18    /// The group is the device's PHY, as on a virtual radio. Only the group
19    /// and the state exist: `PROP_PHY_ENABLED` is the switch, and nothing is
20    /// forwarded to limit or count.
21    Phy,
22}
23
24/// The bearer's settings. The group and the ingress limit are held as
25/// what an operator chose, if anything: an unchosen one follows this
26/// firmware's default, and is saved as unchosen so that a later firmware
27/// with a different default brings it along.
28#[derive(Clone, Copy, Debug, Default, PartialEq, Eq)]
29pub struct LanBearerConfig {
30    pub enabled: bool,
31    group: Option<Group>,
32    ingress_limit: Option<u16>,
33}
34
35impl LanBearerConfig {
36    /// The group in effect.
37    pub fn group(&self) -> Group {
38        self.group.unwrap_or_default()
39    }
40
41    /// Datagrams per minute carried from the LAN onto the radio.
42    pub fn ingress_limit(&self) -> u16 {
43        self.ingress_limit.unwrap_or(DEFAULT_INGRESS_LIMIT)
44    }
45
46    /// Whether `key` is one of the saved configuration properties.
47    pub const fn is_config_prop(key: u32) -> bool {
48        matches!(
49            key,
50            prop::LAN_BEARER_ENABLED | prop::LAN_BEARER_GROUP | prop::LAN_BEARER_INGRESS_LIMIT
51        )
52    }
53
54    /// Whether `key` holds no choice of its own and follows the default.
55    pub fn is_default(&self, key: u32) -> bool {
56        match key {
57            prop::LAN_BEARER_GROUP => self.group.is_none(),
58            prop::LAN_BEARER_INGRESS_LIMIT => self.ingress_limit.is_none(),
59            _ => false,
60        }
61    }
62
63    /// Take one property from `saved`, choice and all.
64    pub fn restore(&mut self, key: u32, saved: &Self) {
65        match key {
66            prop::LAN_BEARER_ENABLED => self.enabled = saved.enabled,
67            prop::LAN_BEARER_GROUP => self.group = saved.group,
68            prop::LAN_BEARER_INGRESS_LIMIT => self.ingress_limit = saved.ingress_limit,
69            _ => {}
70        }
71    }
72
73    /// Apply a written value. An empty group or limit returns it to the
74    /// default.
75    pub fn set(&mut self, key: u32, value: &[u8]) -> Result<(), Status> {
76        match key {
77            prop::LAN_BEARER_ENABLED => {
78                self.enabled = match value {
79                    [0] => false,
80                    [1] => true,
81                    _ => return Err(Status::INVALID_ARGUMENT),
82                }
83            }
84            prop::LAN_BEARER_GROUP => {
85                self.group = match value {
86                    [] => None,
87                    _ => Some(Group::decode(value).ok_or(Status::INVALID_ARGUMENT)?),
88                }
89            }
90            prop::LAN_BEARER_INGRESS_LIMIT => {
91                self.ingress_limit = match value {
92                    [] => None,
93                    _ => {
94                        let limit = u16::from_le_bytes(
95                            value.try_into().map_err(|_| Status::INVALID_ARGUMENT)?,
96                        );
97                        if limit == 0 {
98                            return Err(Status::INVALID_ARGUMENT);
99                        }
100                        Some(limit)
101                    }
102                }
103            }
104            _ => return Err(Status::PROP_NOT_FOUND),
105        }
106        Ok(())
107    }
108
109    /// Encode the value in effect for one configuration property; at most
110    /// [`GROUP_LEN`](umsh_ulcp::lan::GROUP_LEN) octets.
111    pub fn encode(&self, key: u32, out: &mut [u8]) -> Option<usize> {
112        let group;
113        let limit;
114        let bytes: &[u8] = match key {
115            prop::LAN_BEARER_ENABLED => &[self.enabled as u8],
116            prop::LAN_BEARER_GROUP => {
117                group = self.group().encode();
118                &group
119            }
120            prop::LAN_BEARER_INGRESS_LIMIT => {
121                limit = self.ingress_limit().to_le_bytes();
122                &limit
123            }
124            _ => return None,
125        };
126        out.get_mut(..bytes.len())?.copy_from_slice(bytes);
127        Some(bytes.len())
128    }
129}
130
131#[cfg(test)]
132mod tests {
133    use super::*;
134
135    #[test]
136    fn an_empty_write_returns_a_setting_to_the_default() {
137        let mut config = LanBearerConfig::default();
138        assert!(config.is_default(prop::LAN_BEARER_GROUP));
139        config
140            .set(prop::LAN_BEARER_GROUP, &[239, 1, 2, 3, 0x10, 0x27])
141            .unwrap();
142        config
143            .set(prop::LAN_BEARER_INGRESS_LIMIT, &30u16.to_le_bytes())
144            .unwrap();
145        assert_eq!(config.group().address, [239, 1, 2, 3]);
146        assert_eq!(config.ingress_limit(), 30);
147        assert!(!config.is_default(prop::LAN_BEARER_GROUP));
148
149        config.set(prop::LAN_BEARER_GROUP, &[]).unwrap();
150        config.set(prop::LAN_BEARER_INGRESS_LIMIT, &[]).unwrap();
151        assert_eq!(config, LanBearerConfig::default());
152        assert_eq!(config.group(), Group::default());
153        assert_eq!(config.ingress_limit(), DEFAULT_INGRESS_LIMIT);
154        assert_eq!(
155            config.set(prop::LAN_BEARER_ENABLED, &[]),
156            Err(Status::INVALID_ARGUMENT)
157        );
158    }
159}