coap_lite/
request.rs

1use alloc::{
2    string::{String, ToString},
3    vec::Vec,
4};
5use core::convert::TryFrom;
6
7use crate::{
8    error::{HandlingError, IncompatibleOptionValueFormat, InvalidObserve},
9    header::{MessageClass, RequestType as Method},
10    option_value::OptionValueString,
11    packet::{CoapOption, ObserveOption, Packet},
12    response::CoapResponse,
13    ContentFormat,
14};
15
16/// The CoAP request.
17#[derive(Clone, Debug, PartialEq)]
18pub struct CoapRequest<Endpoint> {
19    pub message: Packet,
20    pub response: Option<CoapResponse>,
21    pub source: Option<Endpoint>,
22}
23
24impl<Endpoint> CoapRequest<Endpoint> {
25    /// Creates a new request.
26    pub fn new() -> CoapRequest<Endpoint> {
27        Default::default()
28    }
29
30    /// Creates a request from a packet.
31    pub fn from_packet(
32        packet: Packet,
33        source: Endpoint,
34    ) -> CoapRequest<Endpoint> {
35        CoapRequest {
36            response: CoapResponse::new(&packet),
37            message: packet,
38            source: Some(source),
39        }
40    }
41
42    /// Applies the given error to the request and returns true if that was
43    /// successful.
44    pub fn apply_from_error(&mut self, error: HandlingError) -> bool {
45        if let Some(reply) = &mut self.response {
46            if let Some(code) = error.code {
47                let message = &mut reply.message;
48                message.header.code = MessageClass::Response(code);
49                message.set_content_format(ContentFormat::TextPlain);
50                message.payload = error.message.into_bytes();
51                return true;
52            }
53        }
54        false
55    }
56
57    /// Sets the method.
58    pub fn set_method(&mut self, method: Method) {
59        self.message.header.code = MessageClass::Request(method);
60    }
61
62    /// Returns the method.
63    pub fn get_method(&self) -> &Method {
64        match self.message.header.code {
65            MessageClass::Request(Method::Get) => &Method::Get,
66            MessageClass::Request(Method::Post) => &Method::Post,
67            MessageClass::Request(Method::Put) => &Method::Put,
68            MessageClass::Request(Method::Delete) => &Method::Delete,
69            MessageClass::Request(Method::Fetch) => &Method::Fetch,
70            MessageClass::Request(Method::Patch) => &Method::Patch,
71            MessageClass::Request(Method::IPatch) => &Method::IPatch,
72            _ => &Method::UnKnown,
73        }
74    }
75
76    /// Sets the path.
77    pub fn set_path(&mut self, path: &str) {
78        self.message.clear_option(CoapOption::UriPath);
79
80        let segs = path.split('/');
81        for (i, s) in segs.enumerate() {
82            if i == 0 && s.is_empty() {
83                continue;
84            }
85
86            self.message
87                .add_option(CoapOption::UriPath, s.as_bytes().to_vec());
88        }
89    }
90
91    /// Returns the path.
92    pub fn get_path(&self) -> String {
93        match self.message.get_option(CoapOption::UriPath) {
94            Some(options) => {
95                let mut vec = Vec::new();
96                for option in options.iter() {
97                    if let Ok(seg) = core::str::from_utf8(option) {
98                        vec.push(seg);
99                    }
100                }
101                vec.join("/")
102            }
103            _ => "".to_string(),
104        }
105    }
106
107    /// Returns the path as a vector (as it is encoded in CoAP rather than in
108    /// HTTP-style paths).
109    pub fn get_path_as_vec(
110        &self,
111    ) -> Result<Vec<String>, IncompatibleOptionValueFormat> {
112        self.message
113            .get_options_as::<OptionValueString>(CoapOption::UriPath)
114            .map_or_else(
115                || Ok(vec![]),
116                |paths| {
117                    paths
118                        .into_iter()
119                        .map(|segment_result| {
120                            segment_result.map(|segment| segment.0)
121                        })
122                        .collect::<Result<Vec<_>, _>>()
123                },
124            )
125    }
126
127    /// Returns the flag in the Observe option or InvalidObserve if the flag
128    /// was provided but not understood.
129    pub fn get_observe_flag(
130        &self,
131    ) -> Option<Result<ObserveOption, InvalidObserve>> {
132        self.message.get_observe_value().map(|observe| {
133            observe
134                .map(|value| usize::try_from(value).unwrap())
135                .map_or(Err(InvalidObserve), |value| {
136                    ObserveOption::try_from(value)
137                })
138        })
139    }
140
141    /// Sets the flag in the Observe option.
142    pub fn set_observe_flag(&mut self, flag: ObserveOption) {
143        let value = u32::try_from(usize::from(flag)).unwrap();
144        self.message.set_observe_value(value);
145    }
146}
147
148impl<Endpoint> Default for CoapRequest<Endpoint> {
149    fn default() -> Self {
150        CoapRequest {
151            response: None,
152            message: Packet::new(),
153            source: None,
154        }
155    }
156}
157
158#[cfg(test)]
159mod test {
160    use super::*;
161    use crate::header::MessageType;
162
163    struct Endpoint(String);
164
165    #[test]
166    fn test_request_create() {
167        let mut packet = Packet::new();
168        let mut request1: CoapRequest<Endpoint> = CoapRequest::new();
169
170        packet.set_token(vec![0x17, 0x38]);
171        request1.message.set_token(vec![0x17, 0x38]);
172
173        packet.add_option(CoapOption::UriPath, b"test-interface".to_vec());
174        request1
175            .message
176            .add_option(CoapOption::UriPath, b"test-interface".to_vec());
177
178        packet.header.message_id = 42;
179        request1.message.header.message_id = 42;
180
181        packet.header.set_version(2);
182        request1.message.header.set_version(2);
183
184        packet.header.set_type(MessageType::Confirmable);
185        request1.message.header.set_type(MessageType::Confirmable);
186
187        packet.header.set_code("0.04");
188        request1.message.header.set_code("0.04");
189
190        let endpoint = Endpoint(String::from("127.0.0.1:1234"));
191        let request2 = CoapRequest::from_packet(packet, endpoint);
192
193        assert_eq!(
194            request1.message.to_bytes().unwrap(),
195            request2.message.to_bytes().unwrap()
196        );
197    }
198
199    #[test]
200    fn test_method() {
201        let mut request: CoapRequest<Endpoint> = CoapRequest::new();
202
203        request.message.header.set_code("0.01");
204        assert_eq!(&Method::Get, request.get_method());
205
206        request.message.header.set_code("0.02");
207        assert_eq!(&Method::Post, request.get_method());
208
209        request.message.header.set_code("0.03");
210        assert_eq!(&Method::Put, request.get_method());
211
212        request.message.header.set_code("0.04");
213        assert_eq!(&Method::Delete, request.get_method());
214
215        request.message.header.set_code("0.06");
216        assert_eq!(&Method::Patch, request.get_method());
217
218        request.set_method(Method::Get);
219        assert_eq!("0.01", request.message.header.get_code());
220
221        request.set_method(Method::Post);
222        assert_eq!("0.02", request.message.header.get_code());
223
224        request.set_method(Method::Put);
225        assert_eq!("0.03", request.message.header.get_code());
226
227        request.set_method(Method::Delete);
228        assert_eq!("0.04", request.message.header.get_code());
229
230        request.set_method(Method::IPatch);
231        assert_eq!("0.07", request.message.header.get_code());
232    }
233
234    #[test]
235    fn test_path() {
236        let mut request: CoapRequest<Endpoint> = CoapRequest::new();
237
238        let path = "test-interface";
239        request
240            .message
241            .add_option(CoapOption::UriPath, path.as_bytes().to_vec());
242        assert_eq!(path, request.get_path());
243
244        let path2 = "test-interface2";
245        request.set_path(path2);
246        assert_eq!(
247            path2.as_bytes().to_vec(),
248            *request
249                .message
250                .get_option(CoapOption::UriPath)
251                .unwrap()
252                .front()
253                .unwrap()
254        );
255
256        request.set_path("/test-interface2");
257        assert_eq!(
258            path2.as_bytes().to_vec(),
259            *request
260                .message
261                .get_option(CoapOption::UriPath)
262                .unwrap()
263                .front()
264                .unwrap()
265        );
266
267        let path3 = "test-interface2/";
268        request.set_path(path3);
269        assert_eq!(path3, request.get_path());
270    }
271
272    #[test]
273    fn test_path_as_vec() {
274        let mut request: CoapRequest<Endpoint> = CoapRequest::new();
275
276        let path = "test-interface";
277        request
278            .message
279            .add_option(CoapOption::UriPath, path.as_bytes().to_vec());
280        assert_eq!(Ok(vec![path.to_string()]), request.get_path_as_vec());
281
282        request.set_path("/test-interface/second/third");
283        assert_eq!(
284            Ok(["test-interface", "second", "third"]
285                .map(|x| x.to_string())
286                .to_vec()),
287            request.get_path_as_vec()
288        );
289
290        let bogus_path: Vec<u8> = vec![0xfe, 0xfe, 0xff, 0xff];
291        request.message.clear_option(CoapOption::UriPath);
292        request.message.add_option(CoapOption::UriPath, bogus_path);
293        request
294            .get_path_as_vec()
295            .expect_err("must be a utf-8 decoding error");
296    }
297
298    #[test]
299    fn test_unknown_observe_flag() {
300        let mut request: CoapRequest<Endpoint> = CoapRequest::new();
301
302        request.message.set_observe_value(32);
303        let expected = Some(Err(InvalidObserve));
304        let actual = request.get_observe_flag();
305        assert_eq!(actual, expected);
306    }
307
308    #[test]
309    fn test_garbage_in_observe_field() {
310        let mut request: CoapRequest<Endpoint> = CoapRequest::new();
311
312        request
313            .message
314            .add_option(CoapOption::Observe, b"bunch of nonsense".to_vec());
315        let expected = Some(Err(InvalidObserve));
316        let actual = request.get_observe_flag();
317        assert_eq!(actual, expected);
318    }
319}