nutp/
lib.rs

1#![no_std]
2
3use core::fmt::Debug;
4
5use heapless::{String, Vec};
6
7pub const MAX_BODY_SIZE: usize = 2 * 1024;
8
9/// Converts a u16 to array of 2 u8s corresponding to the upper and lower 8
10/// bits respectively
11///
12/// # Arguments
13///
14/// * `input` - The u16 input that will be split.
15///
16/// # Return value
17///
18/// A array of exactly 2 u8s which correspond to the upper and lower 8 bits of
19/// `input`
20const 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}