1
// SPDX-License-Identifier: BSD-2-Clause
2
/*
3
 * Copyright © The libcoap-rs Contributors, all rights reserved.
4
 * This file is part of the libcoap-rs project, see the README file for
5
 * general information on this project and the NOTICE.md and LICENSE files
6
 * for information regarding copyright ownership and terms of use.
7
 *
8
 * message/response.rs - Types wrapping messages into responses.
9
 */
10

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

            
365
699
    fn as_message(&self) -> &CoapMessage {
366
699
        &self.pdu
367
699
    }
368

            
369
233
    fn as_message_mut(&mut self) -> &mut CoapMessage {
370
233
        &mut self.pdu
371
233
    }
372
}