1#![no_std]
2
3use core::fmt::Debug;
4
5use heapless::{String, Vec};
6
7pub const MAX_BODY_SIZE: usize = 2 * 1024;
8
9const fn u16_to_u8s(input: u16) -> [u8; 2] {
21 [
22 (input & (u8::MAX as u16)) as u8,
23 ((input >> 8) & (u8::MAX as u16)) as u8,
24 ]
25}
26
27#[derive(Debug)]
28pub struct MessageBuilder {
29 headers: Vec<(String<32>, String<128>), 16>,
30 body: Option<String<MAX_BODY_SIZE>>,
31}
32
33impl Default for MessageBuilder {
34 fn default() -> Self {
35 Self::new()
36 }
37}
38
39impl MessageBuilder {
40 pub fn new() -> Self {
41 Self {
42 headers: Vec::new(),
43 body: None,
44 }
45 }
46
47 pub fn add_header(mut self, key: String<32>, value: String<128>) -> Option<Self> {
48 self.headers.push((key, value)).ok()?;
49 Some(self)
50 }
51
52 pub fn set_body(mut self, body: String<MAX_BODY_SIZE>) -> Self {
53 self.body = Some(body);
54 self
55 }
56
57 pub fn build(self) -> Option<Message> {
58 Some(Message {
59 header: String::from(
60 self.headers
61 .into_iter()
62 .map(|(k, v)| {
63 let mut out: String<260> = String::new();
64 out.push_str(k.as_str()).ok()?;
65 out.push_str(": ").ok()?;
66 out.push_str(v.as_str()).ok()?;
67 Some(out)
68 })
69 .try_fold(String::new(), |mut v, b| {
70 v.push_str(&b?).ok()?;
71 Some(v)
72 })?,
73 ),
74 body: self.body.unwrap_or(String::new()),
75 })
76 }
77}
78
79#[derive(Clone, PartialEq)]
80pub struct Message {
81 pub header: String<MAX_BODY_SIZE>,
82 pub body: String<MAX_BODY_SIZE>,
83}
84
85impl Debug for Message {
86 fn fmt(&self, f: &mut core::fmt::Formatter<'_>) -> core::fmt::Result {
87 f.write_fmt(format_args!(
88 "[0x1][0x2][{}][{:?}][0x3][0x2][{:?}][0x4]",
89 self.header.clone().into_bytes().len(),
90 self.header,
91 self.body
92 ))
93 }
94}
95
96impl Message {
97 pub fn to_bytes(self) -> Option<Vec<u8, { MAX_BODY_SIZE * 2 }>> {
98 let mut out = Vec::new();
99
100 out.push(0x1).ok()?;
101 out.push(0x2).ok()?;
102
103 let header_len = u16_to_u8s(self.header.clone().into_bytes().len() as u16 + 1);
104
105 out.push(header_len[0]).ok()?;
106 out.push(header_len[1]).ok()?;
107
108 out.extend_from_slice(self.header.as_bytes()).ok()?;
109
110 out.push(0x0).ok()?;
111
112 out.push(0x3).ok()?;
113 out.push(0x2).ok()?;
114
115 out.extend_from_slice(self.body.as_bytes()).ok()?;
116
117 out.push(0x0).ok()?;
118
119 out.push(0x4).ok()?;
120
121 Some(out)
122 }
123
124 pub fn from_bytes(input: &[u8]) -> Option<Self> {
125 if input[0] != 0x1 || input[1] != 0x2 || input[input.len() - 1] != 0x4 {
126 return None;
127 }
128
129 let mut header_len_bytes = [0u8; 2];
130 header_len_bytes.copy_from_slice(&input[2..4]);
131 let header_len = u16::from_le_bytes(header_len_bytes) as usize;
132
133 let header = String::from_iter(
134 input[4..4 + header_len]
135 .iter()
136 .map(|&a| a as char)
137 .take_while(|x| *x != '\0'),
138 );
139
140 let header_end = 4 + header_len;
141
142 let body_start = input[header_end..].iter().position(|&a| a == 0x2)? + header_end;
143
144 let body = String::from_iter(
145 input[body_start + 1..input.len() - 2]
146 .iter()
147 .map(|&a| a as char),
148 );
149
150 let message = Self { header, body };
151
152 Some(message)
153 }
154}
155
156#[cfg(test)]
157mod test {
158 use core::str::FromStr;
159 use std::println;
160
161 use heapless::String;
162
163 extern crate std;
164
165 #[test]
166 fn test_bytes() {
167 let message = super::MessageBuilder::new()
168 .add_header(
169 String::from_str("Content-Type").unwrap(),
170 String::from_str("text/html").unwrap(),
171 )
172 .unwrap()
173 .set_body(String::from_str("<html><body><h1>Hello, world!</h1></body></html>").unwrap())
174 .build()
175 .unwrap();
176
177 let bytes = message.clone().to_bytes().unwrap();
178 println!("{:?}", bytes);
179 let message2 = super::Message::from_bytes(&bytes).unwrap();
180
181 assert_eq!(message, message2);
182 }
183
184 #[test]
185 fn test_header() {
186 let message = [
187 1, 2, 20, 0, 82, 101, 113, 117, 101, 115, 116, 45, 68, 97, 116, 97, 58, 32, 112, 104,
188 97, 115, 101, 115, 0, 3, 2, 0, 4, 0, 0, 2,
189 ]
190 .to_vec();
191
192 let end = message.iter().position(|&a| a == 0x4).unwrap();
193
194 let message = message[..end + 1].to_vec();
195
196 let message = super::Message::from_bytes(&message);
197
198 assert!(message.is_some());
199
200 assert_eq!(
201 message,
202 Some(super::Message {
203 header: String::from_str("Request-Data: phases").unwrap(),
204 body: String::from_str("").unwrap(),
205 })
206 );
207 }
208}