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}