umsh_ulcp/
lan.rs

1//! LAN bearer configuration and state (device domain).
2//!
3//! Spec: `docs/protocol/src/ulcp-lan-bearer.md`; the bearer itself is
4//! `docs/protocol/src/lan-bearer.md`.
5
6/// The default multicast group.
7pub const DEFAULT_GROUP: [u8; 4] = [239, 255, 134, 121];
8/// The default UDP port.
9pub const DEFAULT_PORT: u16 = 15056;
10/// The default ingress limit, in datagrams per minute.
11pub const DEFAULT_INGRESS_LIMIT: u16 = 120;
12/// Length of a `PROP_LAN_BEARER_GROUP` value.
13pub const GROUP_LEN: usize = 6;
14
15/// `PROP_LAN_BEARER_STATE`.
16#[derive(Clone, Copy, Debug, Default, PartialEq, Eq)]
17#[repr(u8)]
18pub enum State {
19    /// The bearer, or a virtual radio's PHY, is switched off.
20    #[default]
21    Disabled = 0,
22    /// The bearer is joined to its group and carrying frames.
23    Active = 1,
24    /// The bearer is switched on and the device has no IPv4 network to
25    /// join the group on.
26    NoNetwork = 2,
27}
28
29impl State {
30    pub const fn decode(value: u8) -> Option<Self> {
31        Some(match value {
32            0 => Self::Disabled,
33            1 => Self::Active,
34            2 => Self::NoNetwork,
35            _ => return None,
36        })
37    }
38}
39
40/// A multicast group and port, as `PROP_LAN_BEARER_GROUP` carries them. A
41/// unicast address in place of the group names the one peer of a
42/// point-to-point link.
43#[derive(Clone, Copy, Debug, PartialEq, Eq)]
44pub struct Group {
45    pub address: [u8; 4],
46    pub port: u16,
47}
48
49impl Default for Group {
50    fn default() -> Self {
51        Self {
52            address: DEFAULT_GROUP,
53            port: DEFAULT_PORT,
54        }
55    }
56}
57
58impl Group {
59    pub fn encode(self) -> [u8; GROUP_LEN] {
60        let [a, b, c, d] = self.address;
61        let [lo, hi] = self.port.to_le_bytes();
62        [a, b, c, d, lo, hi]
63    }
64
65    /// Whether the group is a multicast group (224.0.0.0/4), joined, rather
66    /// than a unicast peer.
67    pub fn is_multicast(self) -> bool {
68        self.address[0] & 0xF0 == 0xE0
69    }
70
71    /// Decode a group, refusing the unspecified and broadcast addresses and
72    /// a zero port.
73    pub fn decode(value: &[u8]) -> Option<Self> {
74        let &[a, b, c, d, lo, hi] = value else {
75            return None;
76        };
77        let port = u16::from_le_bytes([lo, hi]);
78        let address = [a, b, c, d];
79        (address != [0; 4] && address != [255; 4] && port != 0).then_some(Self { address, port })
80    }
81}
82
83#[cfg(test)]
84mod tests {
85    use super::*;
86
87    #[test]
88    fn a_group_round_trips_and_a_unicast_peer_stands_in_for_one() {
89        let group = Group::default();
90        assert_eq!(group.encode(), [239, 255, 134, 121, 0xD0, 0x3A]);
91        assert_eq!(Group::decode(&group.encode()), Some(group));
92        assert!(group.is_multicast());
93        let peer = Group::decode(&[192, 168, 1, 1, 0xD0, 0x3A]).unwrap();
94        assert!(!peer.is_multicast());
95        assert_eq!(Group::decode(&[0, 0, 0, 0, 0xD0, 0x3A]), None);
96        assert_eq!(Group::decode(&[255, 255, 255, 255, 0xD0, 0x3A]), None);
97        assert_eq!(Group::decode(&[239, 255, 134, 121, 0, 0]), None);
98        assert_eq!(Group::decode(&[239, 255, 134, 121, 1]), None);
99    }
100}