umsh_node/
identity.rs

1use bitflags::bitflags;
2
3use crate::app_util::{copy_into, fixed, parse_utf8, push_byte};
4use crate::{AppEncodeError, AppParseError};
5
6#[derive(Clone, Copy, Debug, PartialEq, Eq)]
7#[repr(u8)]
8pub enum NodeRole {
9    Unspecified = 0,
10    Repeater = 1,
11    Chat = 2,
12    Tracker = 3,
13    Sensor = 4,
14    Bridge = 5,
15    ChatRoom = 6,
16    TemporarySession = 7,
17}
18
19impl NodeRole {
20    fn from_byte(value: u8) -> Result<Self, AppParseError> {
21        match value {
22            0 => Ok(Self::Unspecified),
23            1 => Ok(Self::Repeater),
24            2 => Ok(Self::Chat),
25            3 => Ok(Self::Tracker),
26            4 => Ok(Self::Sensor),
27            5 => Ok(Self::Bridge),
28            6 => Ok(Self::ChatRoom),
29            7 => Ok(Self::TemporarySession),
30            _ => Err(AppParseError::InvalidRole(value)),
31        }
32    }
33}
34
35bitflags! {
36    #[derive(Clone, Copy, Debug, PartialEq, Eq)]
37    pub struct Capabilities: u8 {
38        const REPEATER = 0x01;
39        const MOBILE = 0x02;
40        const TEXT_MESSAGES = 0x04;
41        const TELEMETRY = 0x08;
42        const CHAT_ROOM = 0x10;
43        const COAP = 0x20;
44        const NAME_INCLUDED = 0x40;
45        const OPTS_INCLUDED = 0x80;
46    }
47}
48
49#[derive(Clone, Copy, Debug, PartialEq, Eq)]
50pub struct NodeIdentityPayload<'a> {
51    pub timestamp: u32,
52    pub role: NodeRole,
53    pub capabilities: Capabilities,
54    pub name: Option<&'a str>,
55    pub options: Option<&'a [u8]>,
56    pub signature: Option<&'a [u8; 64]>,
57}
58
59pub fn parse(payload: &[u8]) -> Result<NodeIdentityPayload<'_>, AppParseError> {
60    if payload.len() < 6 {
61        return Err(AppParseError::Core(umsh_core::ParseError::Truncated));
62    }
63
64    let timestamp = u32::from_be_bytes(*fixed(&payload[..4])?);
65    let role = NodeRole::from_byte(payload[4])?;
66    let capabilities = Capabilities::from_bits_truncate(payload[5]);
67    let mut remaining = &payload[6..];
68
69    let name = if capabilities.contains(Capabilities::NAME_INCLUDED) {
70        let nul = remaining
71            .iter()
72            .position(|byte| *byte == 0)
73            .ok_or(AppParseError::InvalidOptionValue)?;
74        let name = parse_utf8(&remaining[..nul])?;
75        remaining = &remaining[nul + 1..];
76        Some(name)
77    } else {
78        None
79    };
80
81    let mut signature = None;
82    let options = if capabilities.contains(Capabilities::OPTS_INCLUDED) {
83        if let Some(term) = remaining.iter().position(|byte| *byte == 0xFF) {
84            let option_bytes = &remaining[..=term];
85            let after = &remaining[term + 1..];
86            if after.is_empty() {
87                Some(option_bytes)
88            } else if after.len() == 64 {
89                signature = Some(fixed(after)?);
90                Some(option_bytes)
91            } else {
92                return Err(AppParseError::InvalidLength {
93                    expected: 64,
94                    actual: after.len(),
95                });
96            }
97        } else {
98            Some(remaining)
99        }
100    } else if remaining.is_empty() {
101        None
102    } else if remaining.len() == 64 {
103        signature = Some(fixed(remaining)?);
104        None
105    } else {
106        return Err(AppParseError::InvalidLength {
107            expected: 64,
108            actual: remaining.len(),
109        });
110    };
111
112    Ok(NodeIdentityPayload {
113        timestamp,
114        role,
115        capabilities,
116        name,
117        options,
118        signature,
119    })
120}
121
122pub fn encode(id: &NodeIdentityPayload<'_>, buf: &mut [u8]) -> Result<usize, AppEncodeError> {
123    let mut pos = 0usize;
124    let mut capabilities = id.capabilities;
125    capabilities.set(Capabilities::NAME_INCLUDED, id.name.is_some());
126    capabilities.set(Capabilities::OPTS_INCLUDED, id.options.is_some());
127
128    copy_into(buf, &mut pos, &id.timestamp.to_be_bytes())?;
129    push_byte(buf, &mut pos, id.role as u8)?;
130    push_byte(buf, &mut pos, capabilities.bits())?;
131
132    if let Some(name) = id.name {
133        copy_into(buf, &mut pos, name.as_bytes())?;
134        push_byte(buf, &mut pos, 0)?;
135    }
136
137    if let Some(options) = id.options {
138        copy_into(buf, &mut pos, options)?;
139        if id.signature.is_some() && options.last().copied() != Some(0xFF) {
140            push_byte(buf, &mut pos, 0xFF)?;
141        }
142    }
143
144    if let Some(signature) = id.signature {
145        copy_into(buf, &mut pos, signature)?;
146    }
147
148    Ok(pos)
149}