1
// SPDX-License-Identifier: BSD-2-Clause
2
/*
3
 * response.rs - Types wrapping messages into responses.
4
 * This file is part of the libcoap-rs crate, see the README and LICENSE files for
5
 * more information and terms of use.
6
 * Copyright © 2021-2023 The NAMIB Project Developers, all rights reserved.
7
 * See the README as well as the LICENSE file for more information.
8
 */
9

            
10
use crate::error::{MessageConversionError, MessageTypeError, OptionValueError};
11
use crate::message::{CoapMessage, CoapMessageCommon, CoapOption, construct_path_string, construct_query_string};
12
use crate::protocol::{
13
    CoapMessageCode, CoapMessageType, CoapOptionType, CoapResponseCode, ContentFormat, Echo, ETag, MaxAge, Observe,
14
};
15
use crate::types::CoapUri;
16

            
17
#[derive(Debug, Clone, Eq, PartialEq)]
18
pub struct CoapResponse {
19
    pdu: CoapMessage,
20
    content_format: Option<ContentFormat>,
21
    max_age: Option<MaxAge>,
22
    etag: Option<ETag>,
23
    echo: Option<Echo>,
24
    location: Option<CoapUri>,
25
    observe: Option<Observe>,
26
}
27

            
28
impl CoapResponse {
29
    /// Creates a new CoAP response with the given message type and code.
30
    ///
31
    /// Returns an error if the given message type is not allowed for CoAP responses (the allowed
32
    /// message types are [CoapMessageType::Con] and [CoapMessageType::Non] and [CoapMessageType::Ack]).
33
    pub fn new(type_: CoapMessageType, code: CoapResponseCode) -> Result<CoapResponse, MessageTypeError> {
34
        match type_ {
35
            CoapMessageType::Con | CoapMessageType::Non | CoapMessageType::Ack => {},
36
            v => return Err(MessageTypeError::InvalidForMessageCode(v)),
37
        }
38
        Ok(CoapResponse {
39
            pdu: CoapMessage::new(type_, code.into()),
40
            content_format: None,
41
            max_age: None,
42
            etag: None,
43
            echo: None,
44
            location: None,
45
            observe: None,
46
        })
47
    }
48

            
49
    /// Returns the "Max-Age" option value for this response.
50
    pub fn max_age(&self) -> Option<MaxAge> {
51
        self.max_age
52
    }
53

            
54
    /// Sets the "Max-Age" option value for this response.
55
    ///
56
    /// This option indicates the maximum time a response may be cached (in seconds).
57
    ///
58
    /// See [RFC 7252, Section 5.10.5](https://datatracker.ietf.org/doc/html/rfc7252#section-5.10.5)
59
    /// for more information.
60
    pub fn set_max_age(&mut self, max_age: Option<MaxAge>) {
61
        self.max_age = max_age
62
    }
63

            
64
    /// Returns the "Content-Format" option value for this request.
65
    pub fn content_format(&self) -> Option<ContentFormat> {
66
        self.content_format
67
    }
68

            
69
    /// Sets the "Content-Format" option value for this response.
70
    ///
71
    /// This option indicates the content format of the body of this message.
72
    ///
73
    /// See [RFC 7252, Section 5.10.3](https://datatracker.ietf.org/doc/html/rfc7252#section-5.10.3)
74
    /// for more information.
75
    pub fn set_content_format(&mut self, content_format: Option<ContentFormat>) {
76
        self.content_format = content_format;
77
    }
78

            
79
    /// Returns the "ETag" option value for this request.
80
    pub fn etag(&self) -> Option<&ETag> {
81
        self.etag.as_ref()
82
    }
83

            
84
    /// Sets the "ETag" option value for this response.
85
    ///
86
    /// This option can be used by clients to request a specific representation of the requested
87
    /// resource.
88
    ///
89
    /// The server may send an ETag value alongside a response, which the client can then set here
90
    /// to request the given representation.
91
    ///
92
    /// See [RFC 7252, Section 5.10.6](https://datatracker.ietf.org/doc/html/rfc7252#section-5.10.6)
93
    /// for more information.
94
    pub fn set_etag(&mut self, etag: Option<ETag>) {
95
        self.etag = etag
96
    }
97

            
98
    /// Returns the "Echo" option value for this request.
99
    pub fn echo(&self) -> Option<&Echo> {
100
        self.echo.as_ref()
101
    }
102

            
103
    /// Sets the "Echo" option value for this response.
104
    ///
105
    /// This option can be used by servers to ensure that a request is recent.
106
    ///
107
    /// The client should include the provided option value in its next request.
108
    ///
109
    /// As handling echo options on the client side is done automatically by libcoap, this option
110
    /// is not accessible in [CoapRequest], see `man coap_send` for more information.
111
    ///
112
    /// See [RFC 9175, Section 2.2](https://datatracker.ietf.org/doc/html/rfc9175#section-2.2)
113
    /// for more information.
114
    pub fn set_echo(&mut self, echo: Option<Echo>) {
115
        self.echo = echo
116
    }
117

            
118
    /// Returns the "Observe" option value for this request.
119
    pub fn observe(&self) -> Option<Observe> {
120
        self.observe
121
    }
122

            
123
    /// Sets the "Observe" option value for this response.
124
    ///
125
    /// This option indicates that this response is a notification for a previously requested
126
    /// resource observation.
127
    ///
128
    /// This option is defined in [RFC 7641](https://datatracker.ietf.org/doc/html/rfc7641) and is
129
    /// not part of the main CoAP spec. Some peers may therefore not support this option.
130
    pub fn set_observe(&mut self, observe: Option<Observe>) {
131
        self.observe = observe;
132
    }
133

            
134
    /// Returns the "Location" option value for this request.
135
    pub fn location(&self) -> Option<&CoapUri> {
136
        self.location.as_ref()
137
    }
138

            
139
    /// Sets the "Location-Path" and "Location-Query" option values for this response.
140
    ///
141
    /// These options indicate a relative URI for a resource created in response of a POST or PUT
142
    /// request.
143
    ///
144
    /// The supplied URI must be relative to the requested path and must therefore also not contain
145
    /// a scheme, host or port. Also, each path component must be smaller than 255 characters.
146
    ///
147
    /// If an invalid URI is provided, an [OptionValueError] is returned
148
    ///
149
    /// See [RFC 7252, Section 5.10.7](https://datatracker.ietf.org/doc/html/rfc7252#section-5.10.7)
150
    /// for more information.
151
    pub fn set_location<U: Into<CoapUri>>(&mut self, uri: Option<U>) -> Result<(), OptionValueError> {
152
        let uri = uri.map(Into::into);
153
        if let Some(uri) = uri {
154
            self.location = Some(uri)
155
        }
156
        Ok(())
157
    }
158

            
159
    /// Converts this request into a [CoapMessage] that can be sent over a [CoapSession](crate::session::CoapSession).
160
233
    pub fn into_message(mut self) -> CoapMessage {
161
233
        if let Some(loc) = self.location {
162
            loc.into_options().into_iter().for_each(|v| self.pdu.add_option(v));
163
233
        }
164
233
        if let Some(max_age) = self.max_age {
165
            self.pdu.add_option(CoapOption::MaxAge(max_age));
166
233
        }
167
233
        if let Some(content_format) = self.content_format {
168
            self.pdu.add_option(CoapOption::ContentFormat(content_format));
169
233
        }
170
233
        if let Some(etag) = self.etag {
171
            self.pdu.add_option(CoapOption::ETag(etag));
172
233
        }
173
233
        if let Some(observe) = self.observe {
174
            self.pdu.add_option(CoapOption::Observe(observe));
175
233
        }
176
233
        self.pdu
177
233
    }
178

            
179
    /// Parses the given [CoapMessage] into a CoapResponse.
180
    ///
181
    /// Returns a [MessageConversionError] if the provided PDU cannot be parsed into a response.
182
466
    pub fn from_message(pdu: CoapMessage) -> Result<CoapResponse, MessageConversionError> {
183
466
        let mut location_path = None;
184
466
        let mut location_query = None;
185
466
        let mut max_age = None;
186
466
        let mut etag = None;
187
466
        let mut echo = None;
188
466
        let mut observe = None;
189
466
        let mut content_format = None;
190
466
        let mut additional_opts = Vec::new();
191
466
        for option in pdu.options_iter() {
192
            match option {
193
                CoapOption::LocationPath(value) => {
194
                    if location_path.is_none() {
195
                        location_path = Some(Vec::new());
196
                    }
197
                    location_path.as_mut().unwrap().push(value.clone());
198
                },
199
                CoapOption::LocationQuery(value) => {
200
                    if location_query.is_none() {
201
                        location_query = Some(Vec::new());
202
                    }
203
                    location_query.as_mut().unwrap().push(value.clone());
204
                },
205
                CoapOption::ETag(value) => {
206
                    if etag.is_some() {
207
                        return Err(MessageConversionError::NonRepeatableOptionRepeated(
208
                            CoapOptionType::ETag,
209
                        ));
210
                    }
211
                    etag = Some(value.clone());
212
                },
213
                CoapOption::MaxAge(value) => {
214
                    if max_age.is_some() {
215
                        return Err(MessageConversionError::NonRepeatableOptionRepeated(
216
                            CoapOptionType::MaxAge,
217
                        ));
218
                    }
219
                    max_age = Some(*value);
220
                },
221
                CoapOption::Observe(value) => {
222
                    if observe.is_some() {
223
                        return Err(MessageConversionError::NonRepeatableOptionRepeated(
224
                            CoapOptionType::Observe,
225
                        ));
226
                    }
227
                    observe = Some(*value)
228
                },
229
                CoapOption::IfMatch(_) => {
230
                    return Err(MessageConversionError::InvalidOptionForMessageType(
231
                        CoapOptionType::IfMatch,
232
                    ));
233
                },
234
                CoapOption::IfNoneMatch => {
235
                    return Err(MessageConversionError::InvalidOptionForMessageType(
236
                        CoapOptionType::IfNoneMatch,
237
                    ));
238
                },
239
                CoapOption::UriHost(_) => {
240
                    return Err(MessageConversionError::InvalidOptionForMessageType(
241
                        CoapOptionType::UriHost,
242
                    ));
243
                },
244
                CoapOption::UriPort(_) => {
245
                    return Err(MessageConversionError::InvalidOptionForMessageType(
246
                        CoapOptionType::UriPort,
247
                    ));
248
                },
249
                CoapOption::UriPath(_) => {
250
                    return Err(MessageConversionError::InvalidOptionForMessageType(
251
                        CoapOptionType::UriPath,
252
                    ));
253
                },
254
                CoapOption::UriQuery(_) => {
255
                    return Err(MessageConversionError::InvalidOptionForMessageType(
256
                        CoapOptionType::UriQuery,
257
                    ));
258
                },
259
                CoapOption::ProxyUri(_) => {
260
                    return Err(MessageConversionError::InvalidOptionForMessageType(
261
                        CoapOptionType::ProxyUri,
262
                    ));
263
                },
264
                CoapOption::ProxyScheme(_) => {
265
                    return Err(MessageConversionError::InvalidOptionForMessageType(
266
                        CoapOptionType::ProxyScheme,
267
                    ));
268
                },
269
                CoapOption::ContentFormat(value) => {
270
                    if content_format.is_some() {
271
                        return Err(MessageConversionError::NonRepeatableOptionRepeated(
272
                            CoapOptionType::ContentFormat,
273
                        ));
274
                    }
275
                    content_format = Some(*value)
276
                },
277
                CoapOption::Accept(_) => {
278
                    return Err(MessageConversionError::InvalidOptionForMessageType(
279
                        CoapOptionType::Accept,
280
                    ));
281
                },
282
                CoapOption::Size1(_) => {
283
                    return Err(MessageConversionError::InvalidOptionForMessageType(
284
                        CoapOptionType::Size1,
285
                    ));
286
                },
287
                CoapOption::Size2(_) => {},
288
                CoapOption::Block1(_) => {
289
                    return Err(MessageConversionError::InvalidOptionForMessageType(
290
                        CoapOptionType::Block1,
291
                    ));
292
                },
293
                CoapOption::Block2(_) => {},
294
                CoapOption::QBlock1(_) => {},
295
                CoapOption::QBlock2(_) => {},
296
                CoapOption::HopLimit(_) => {
297
                    return Err(MessageConversionError::InvalidOptionForMessageType(
298
                        CoapOptionType::HopLimit,
299
                    ));
300
                },
301
                CoapOption::NoResponse(_) => {
302
                    return Err(MessageConversionError::InvalidOptionForMessageType(
303
                        CoapOptionType::NoResponse,
304
                    ));
305
                },
306

            
307
                // Handling of echo options is automatically done by libcoap (see man coap_send)
308
                CoapOption::Echo(v) => {
309
                    if echo.is_some() {
310
                        return Err(MessageConversionError::NonRepeatableOptionRepeated(
311
                            CoapOptionType::Echo,
312
                        ));
313
                    }
314
                    echo = Some(v.clone());
315
                },
316
                // Handling of request tag options is automatically done by libcoap (see man
317
                // coap_send)
318
                CoapOption::RTag(_) => {},
319
                // OSCORE is currently not supported, and even if it should probably be handled by
320
                // libcoap, so I'm unsure whether we have to expose this.
321
                CoapOption::Oscore(_) => {},
322
                CoapOption::Other(n, v) => additional_opts.push(CoapOption::Other(*n, v.clone())),
323
            }
324
        }
325
466
        let location = if location_path.is_some() || location_query.is_some() {
326
            let path_str = location_path.map(construct_path_string);
327
            let query_str = location_query.map(construct_query_string);
328
            Some(CoapUri::new_relative(
329
                path_str.as_ref().map(|v| v.as_bytes()),
330
                query_str.as_ref().map(|v| v.as_bytes()),
331
            )?)
332
        } else {
333
466
            None
334
        };
335
466
        Ok(CoapResponse {
336
466
            pdu,
337
466
            content_format,
338
466
            max_age,
339
466
            etag,
340
466
            echo,
341
466
            location,
342
466
            observe,
343
466
        })
344
466
    }
345
}
346

            
347
impl CoapMessageCommon for CoapResponse {
348
    /// Sets the message code of this response.
349
    ///
350
    /// # Panics
351
    ///
352
    /// Panics if the provided message code is not a response code.
353
25
    fn set_code<C: Into<CoapMessageCode>>(&mut self, code: C) {
354
25
        match code.into() {
355
25
            CoapMessageCode::Response(req) => self.pdu.set_code(CoapMessageCode::Response(req)),
356
            CoapMessageCode::Request(_) | CoapMessageCode::Empty => {
357
                panic!("attempted to set message code of response to value that is not a response code")
358
            },
359
        }
360
25
    }
361

            
362
699
    fn as_message(&self) -> &CoapMessage {
363
699
        &self.pdu
364
699
    }
365

            
366
233
    fn as_message_mut(&mut self) -> &mut CoapMessage {
367
233
        &mut self.pdu
368
233
    }
369
}