1use alloc::string::String;
2
3use umsh_core::{NodeHint, options::OptionDecoder, options::OptionEncoder};
4
5use crate::{EncodeError, ParseError};
6
7fn parse_utf8(input: &[u8]) -> Result<&str, ParseError> {
8 core::str::from_utf8(input).map_err(|_| ParseError::InvalidUtf8)
9}
10
11#[derive(Clone, Copy, Debug, PartialEq, Eq)]
13pub enum MessageType {
14 Basic = 0,
15 Status = 1,
16 ResendRequest = 2,
17}
18
19impl MessageType {
20 fn from_byte(value: u8) -> Result<Self, ParseError> {
21 match value {
22 0 => Ok(Self::Basic),
23 1 => Ok(Self::Status),
24 2 => Ok(Self::ResendRequest),
25 _ => Err(ParseError::InvalidMessageType(value)),
26 }
27 }
28}
29
30#[derive(Clone, Copy, Debug, PartialEq, Eq)]
31pub struct Fragment {
32 pub index: u8,
33 pub count: u8,
34}
35
36#[derive(Clone, Copy, Debug, PartialEq, Eq)]
37pub struct MessageSequence {
38 pub message_id: u8,
39 pub fragment: Option<Fragment>,
40}
41
42#[derive(Clone, Copy, Debug, PartialEq, Eq)]
43pub enum Regarding {
44 Unicast {
45 message_id: u8,
46 },
47 Multicast {
48 message_id: u8,
49 source_prefix: NodeHint,
50 },
51}
52
53#[derive(Clone, Copy, Debug, PartialEq, Eq)]
54pub struct TextMessage<'a> {
55 pub message_type: MessageType,
56 pub sender_handle: Option<&'a str>,
57 pub sequence: Option<MessageSequence>,
58 pub sequence_reset: bool,
59 pub regarding: Option<Regarding>,
60 pub editing: Option<u8>,
61 pub bg_color: Option<[u8; 3]>,
62 pub text_color: Option<[u8; 3]>,
63 pub body: &'a str,
64}
65
66#[derive(Clone, Debug, PartialEq, Eq)]
67pub struct OwnedTextMessage {
68 pub message_type: MessageType,
69 pub sender_handle: Option<String>,
70 pub sequence: Option<MessageSequence>,
71 pub sequence_reset: bool,
72 pub regarding: Option<Regarding>,
73 pub editing: Option<u8>,
74 pub bg_color: Option<[u8; 3]>,
75 pub text_color: Option<[u8; 3]>,
76 pub body: String,
77}
78
79impl OwnedTextMessage {
80 pub fn as_borrowed(&self) -> TextMessage<'_> {
81 TextMessage {
82 message_type: self.message_type,
83 sender_handle: self.sender_handle.as_deref(),
84 sequence: self.sequence,
85 sequence_reset: self.sequence_reset,
86 regarding: self.regarding,
87 editing: self.editing,
88 bg_color: self.bg_color,
89 text_color: self.text_color,
90 body: &self.body,
91 }
92 }
93}
94
95impl From<TextMessage<'_>> for OwnedTextMessage {
96 fn from(value: TextMessage<'_>) -> Self {
97 Self {
98 message_type: value.message_type,
99 sender_handle: value.sender_handle.map(String::from),
100 sequence: value.sequence,
101 sequence_reset: value.sequence_reset,
102 regarding: value.regarding,
103 editing: value.editing,
104 bg_color: value.bg_color,
105 text_color: value.text_color,
106 body: String::from(value.body),
107 }
108 }
109}
110
111pub fn parse(payload: &[u8]) -> Result<TextMessage<'_>, ParseError> {
112 let mut decoder = OptionDecoder::new(payload);
113 let mut message_type = MessageType::Basic;
114 let mut sender_handle = None;
115 let mut sequence = None;
116 let mut sequence_reset = false;
117 let mut regarding = None;
118 let mut editing = None;
119 let mut bg_color = None;
120 let mut text_color = None;
121
122 while let Some(item) = decoder.next() {
123 let (number, value) = item?;
124 match number {
125 0 => {
126 message_type = if value.is_empty() {
127 MessageType::Basic
128 } else if value.len() == 1 {
129 MessageType::from_byte(value[0])?
130 } else {
131 return Err(ParseError::InvalidOptionValue);
132 };
133 }
134 1 => sender_handle = Some(parse_utf8(value)?),
135 2 => {
136 sequence = Some(match value {
137 [message_id] => MessageSequence {
138 message_id: *message_id,
139 fragment: None,
140 },
141 [message_id, index, count] if *count >= 2 => MessageSequence {
142 message_id: *message_id,
143 fragment: Some(Fragment {
144 index: *index,
145 count: *count,
146 }),
147 },
148 _ => return Err(ParseError::InvalidOptionValue),
149 });
150 }
151 3 => {
152 if !value.is_empty() {
153 return Err(ParseError::InvalidOptionValue);
154 }
155 sequence_reset = true;
156 }
157 4 => {
158 regarding = Some(match value {
159 [message_id] => Regarding::Unicast {
160 message_id: *message_id,
161 },
162 [message_id, a, b, c] => Regarding::Multicast {
163 message_id: *message_id,
164 source_prefix: NodeHint([*a, *b, *c]),
165 },
166 _ => return Err(ParseError::InvalidOptionValue),
167 });
168 }
169 5 => {
170 editing = match value {
171 [message_id] => Some(*message_id),
172 _ => return Err(ParseError::InvalidOptionValue),
173 };
174 }
175 6 => {
176 bg_color = match value {
177 [r, g, b] => Some([*r, *g, *b]),
178 _ => return Err(ParseError::InvalidOptionValue),
179 };
180 }
181 7 => {
182 text_color = match value {
183 [r, g, b] => Some([*r, *g, *b]),
184 _ => return Err(ParseError::InvalidOptionValue),
185 };
186 }
187 _ => {}
188 }
189 }
190
191 let body = parse_utf8(decoder.remainder())?;
192 Ok(TextMessage {
193 message_type,
194 sender_handle,
195 sequence,
196 sequence_reset,
197 regarding,
198 editing,
199 bg_color,
200 text_color,
201 body,
202 })
203}
204
205pub fn encode(msg: &TextMessage<'_>, buf: &mut [u8]) -> Result<usize, EncodeError> {
206 let mut encoder = OptionEncoder::new(buf);
207
208 if msg.message_type != MessageType::Basic {
209 encoder.put(0, &[msg.message_type as u8])?;
210 }
211 if let Some(handle) = msg.sender_handle {
212 encoder.put(1, handle.as_bytes())?;
213 }
214 if let Some(sequence) = msg.sequence {
215 let mut seq_buf = [0u8; 3];
216 let seq_len = if let Some(fragment) = sequence.fragment {
217 if fragment.count < 2 {
218 return Err(EncodeError::InvalidField);
219 }
220 seq_buf = [sequence.message_id, fragment.index, fragment.count];
221 3
222 } else {
223 seq_buf[0] = sequence.message_id;
224 1
225 };
226 encoder.put(2, &seq_buf[..seq_len])?;
227 }
228 if msg.sequence_reset {
229 encoder.put(3, &[])?;
230 }
231 if let Some(regarding) = msg.regarding {
232 let mut regarding_buf = [0u8; 4];
233 let regarding_len = match regarding {
234 Regarding::Unicast { message_id } => {
235 regarding_buf[0] = message_id;
236 1
237 }
238 Regarding::Multicast {
239 message_id,
240 source_prefix,
241 } => {
242 regarding_buf = [
243 message_id,
244 source_prefix.0[0],
245 source_prefix.0[1],
246 source_prefix.0[2],
247 ];
248 4
249 }
250 };
251 encoder.put(4, ®arding_buf[..regarding_len])?;
252 }
253 if let Some(editing) = msg.editing {
254 encoder.put(5, &[editing])?;
255 }
256 if let Some(color) = msg.bg_color {
257 encoder.put(6, &color)?;
258 }
259 if let Some(color) = msg.text_color {
260 encoder.put(7, &color)?;
261 }
262
263 encoder.end_marker()?;
264 let prefix_len = encoder.finish();
265 if buf.len().saturating_sub(prefix_len) < msg.body.len() {
266 return Err(EncodeError::BufferTooSmall);
267 }
268 buf[prefix_len..prefix_len + msg.body.len()].copy_from_slice(msg.body.as_bytes());
269 Ok(prefix_len + msg.body.len())
270}
271
272#[cfg(test)]
273mod tests {
274 use super::*;
275
276 #[test]
277 fn basic_text_message_round_trips() {
278 let message = TextMessage {
279 message_type: MessageType::Basic,
280 sender_handle: None,
281 sequence: None,
282 sequence_reset: false,
283 regarding: None,
284 editing: None,
285 bg_color: None,
286 text_color: None,
287 body: "hello",
288 };
289
290 let mut buf = [0u8; 64];
291 let len = encode(&message, &mut buf).expect("encode should succeed");
292 let parsed = parse(&buf[..len]).expect("parse should succeed");
293
294 assert_eq!(parsed, message);
295 }
296}