coap-handler-implementations 0.6.2

Simple implementations of CoAP handlers
Documentation
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
299
300
301
302
303
304
305
306
307
308
309
310
311
312
313
314
315
316
317
318
319
320
321
322
323
324
325
326
327
328
329
330
331
332
333
334
335
336
337
338
339
340
341
342
343
344
345
346
347
348
349
350
351
352
353
354
355
356
357
358
359
360
361
362
363
364
365
366
367
368
369
370
371
372
373
374
375
376
377
378
379
380
381
382
383
384
385
386
387
388
389
390
391
392
393
394
395
396
397
398
399
400
401
402
403
404
405
406
407
408
409
410
411
412
413
414
415
416
417
418
419
420
421
422
423
424
425
426
427
428
429
430
431
//! Module containing the [TypeHandler] handler and the [TypeRenderable]
//! trait, along with the [TypeSerializer] helper trait and its corresponding implementations
//! for various serde(ish) (de)serializers.

use crate::Error;
use crate::helpers::block2_write_with_cf;
use coap_handler::Handler;
use coap_message::{
    Code as _, MessageOption, MinimalWritableMessage, MutableWritableMessage, ReadableMessage,
};
use coap_message_utils::{OptionsExt, option_value::Block2RequestData};
use coap_numbers::{code, option};
use core::marker::PhantomData;

pub(crate) mod fillers;
pub(crate) mod generated;

/// A type that (for minicbor typed handlers) represents an empty payload.
///
/// This is particularly practical for resources that have a GET and PUT handler that act on
/// serialized data, but where the POST handler just takes or return an empty payload.
pub struct Empty;

/// A Handler trait that supports various CoAP methods on a data structure that can be serialized,
/// eg. in CBOR.
///
/// A TypeRenderable implementation can be turned into a [Handler] by wrapping it in
/// [`TypeHandler::new_*`][TypeHandler].
///
/// This is a composite trait of many per-method traits because all those traits come with their
/// own associated types (and maybe constants), and as long as neither of those can be provided
/// like methods can be (i.e., added later extensibly, allowing for defaults), those are more
/// easily handled by composition.
///
/// Unless you can keep track of changes to the list of trait dependencies, use the
/// [`with_get_put_fetch()`][super::with_get_put_fetch]-style wrappers around instances, which mark
/// all other methods as unimplemented.
///
/// For different methods, the functions get called at different times in the request/response
/// handling sequence: A GET's method is called when the response is prepared, whereas a PUT's
/// method is called when the request is prepared. This ensures that for CoAP server
/// implementations that delay rendering (e.g. as part of retransmission handling), no large data
/// needs to be kept for long. The methods with both input and output data (POST, FETCH) are called
/// according to their most expected usage pattern: [`FetchRenderable::fetch()`] is called at response time
/// (because the request payload is typically a small filter expression that limits the output to
/// something that fits within a CoAP response but may still be large), whereas [`IPatchRenderable::ipatch()`]
/// is called immediately (because it is frequently used with empty outputs).
pub trait TypeRenderable:
    GetRenderable
    + PostRenderable
    + PutRenderable
    + DeleteRenderable
    + FetchRenderable
    + IPatchRenderable
{
}

/// Subset of [`TypeRenderable`] that handles the GET method.
pub trait GetRenderable {
    /// Output type of the [get()][Self::get()] method, serialized into the response payload.
    type Get;

    fn get(&mut self) -> Result<Self::Get, Error> {
        Err(Error::method_not_allowed())
    }
}

/// Subset of [`TypeRenderable`] that handles the POST method.
pub trait PostRenderable {
    /// Input type of the [post()][Self::post()] method, deserialized from the request payload.
    type PostIn;
    /// Ouptut type of the [post()][Self::post()] method, serialized into the response payload.
    ///
    /// Note that this is a size sensitive type: On CoAP servers that need to retain state between
    /// processing a request and sending a response, this type goes into that state.
    type PostOut;

    fn post(&mut self, _representation: &Self::PostIn) -> Result<Self::PostOut, Error> {
        Err(Error::method_not_allowed())
    }
}

/// Subset of [`TypeRenderable`] that handles the PUT method.
pub trait PutRenderable {
    /// Input type of the [put()][Self::put()] method, deserialized from the request payload.
    type Put;
    fn put(&mut self, _representation: &Self::Put) -> Result<(), Error> {
        Err(Error::method_not_allowed())
    }
}

/// Subset of [`TypeRenderable`] that handles the DELETE method.
// Note that while this could be a plain provided method (it needs no associated items that can't
// have a default), that'd cause a bit of inconsistency in the user experience.
pub trait DeleteRenderable {
    fn delete(&mut self) -> Result<(), Error> {
        Err(Error::method_not_allowed())
    }
}

/// Subset of [`TypeRenderable`] that handles the FETCH method.
pub trait FetchRenderable {
    /// Input type of the [fetch()][Self::fetch()] method, deserialized from the request payload.
    ///
    /// Note that this is a size sensitive type: On CoAP servers that need to retain state between
    /// processing a request and sending a response, this type goes into that state.
    type FetchIn;
    /// Ouptut type of the [fetch()][Self::fetch()] method, serialized into the response payload.
    type FetchOut;

    fn fetch(&mut self, _representation: &Self::FetchIn) -> Result<Self::FetchOut, Error> {
        Err(Error::method_not_allowed())
    }
}

/// Subset of [`TypeRenderable`] that handles the IPATCH method.
pub trait IPatchRenderable {
    /// Input type of the [ipatch()][Self::ipatch()] method, deserialized from the request payload.
    type IPatch;

    fn ipatch(&mut self, _representation: &Self::IPatch) -> Result<(), Error> {
        Err(Error::method_not_allowed())
    }
}

/// Keeping them hidden to stay flexible; they don't need to be named for their'e either default or
/// their users have aliases.
mod sealed {
    pub trait TypeSerializer {
        // BIG FIXME: This conflates
        // * This is the same across all methods, both for input and for output
        // * This is guided by the serializer, when really it may need to be guided by the type
        //   (even though minicbor may easily (de)serialize application/cbor or
        //   application/foo+cbor).
        const CF: Option<u16>;
    }

    pub struct MiniCBORSerialization2;
}
use sealed::*;

/// Wrapper for resource handlers that are implemented in terms of GETting, POSTing or PUTting
/// objects in CBOR format.
///
/// The wrapper handles all encoding and decoding, options processing (ignoring the critical
/// Uri-Path option under the assumption that that has been already processed by an underlying
/// request router), the corresponding errors and block-wise GETs.
///
/// More complex handlers (eg. implementing additional representations, or processing query
/// parameters into additional data available to the [TypeRenderable]) can be built by
/// forwarding to this (where any critical but already processed options would need to be masked
/// from the message's option) or taking inspiration from it.
pub struct TypeHandler<H, S: TypeSerializer>
where
    H: TypeRenderable,
{
    handler: H,
    _phantom: PhantomData<S>,
}

impl<H, S> TypeHandler<H, S>
where
    H: TypeRenderable,
    S: TypeSerializer,
{
    /// Checks whether any Accept and Content-Format options are really S::CF, and extracts the
    /// Block2 option.
    ///
    /// This is not 100% sharp because it'll let Accept and Content-Format slip through on Delete
    /// as well, but there's little harm in that.
    fn extract_options(request: &impl ReadableMessage) -> Result<Option<Block2RequestData>, Error> {
        let mut block2 = None;

        request
            .options()
            .take_block2(&mut block2)
            .filter(|o| {
                if o.number() == option::CONTENT_FORMAT || o.number() == option::ACCEPT {
                    if let Some(cf) = S::CF {
                        // If they differ, we'll keep the option for later failing
                        o.value_uint() != Some(cf)
                    } else {
                        // We don't know any content format, so we keep the option in the iterator
                        // to fail later
                        true
                    }
                } else {
                    true
                }
            })
            .ignore_elective_others()?;

        Ok(block2)
    }
}

/// Data carried around between a request and its response for [TypeHandler]s
///
/// This carries input data from FETCH and output data from POST, under the assumption that FETCH
/// generally have small input representations and POST have large output representations; see
/// [`TypeRenderable`] documentation.
pub struct TypeRequestData<FetchIn, PostOut>(TypeRequestDataE<FetchIn, PostOut>);

enum TypeRequestDataE<FetchIn, PostOut> {
    Get(Block2RequestData), // GET to be processed later, but all request opions were in order
    Fetch(Block2RequestData, FetchIn),
    Post(PostOut),
    // FIXME: Those could really go together with the Error in a single variant, but then it's also
    // convenient for Error to be really only errors -- make Error a type alias for Response<const
    // bool CanBeOk, const bool CanBeError>? And may we just abuse ExtractRequestError to also
    // carry it without any other ill-effect?
    DoneCode(u8), // All done, just a response to emit -- if POST/PUT has been processed, or GET had a bad accept/option
}
use self::TypeRequestDataE::{DoneCode, Fetch, Get, Post};

// FIXME for all the below: deduplicate (but not sure how, without HKTs) -- and some alterations
// have accumulated

trait ToTypedResourceError {
    fn into_general_error(self, total_len: usize) -> Error;
}

impl<'b, C> minicbor_2::decode::Decode<'b, C> for Empty {
    fn decode(
        _d: &mut minicbor_2::decode::Decoder<'b>,
        _ctx: &mut C,
    ) -> Result<Self, minicbor_2::decode::Error> {
        Err(minicbor_2::decode::Error::message("No element expected").at(0))
    }

    fn nil() -> Option<Self> {
        Some(Empty)
    }
}

impl<C> minicbor_2::encode::Encode<C> for Empty {
    fn encode<W: minicbor_2::encode::Write>(
        &self,
        _e: &mut minicbor_2::Encoder<W>,
        _ctx: &mut C,
    ) -> Result<(), minicbor_2::encode::Error<W::Error>> {
        Ok(())
    }
}

impl<C> minicbor_2::encode::CborLen<C> for Empty {
    fn cbor_len(&self, _ctx: &mut C) -> usize {
        0
    }
}

impl<H> Handler for TypeHandler<H, MiniCBORSerialization2>
where
    H: TypeRenderable,
    H::Get: minicbor_2::Encode<()>,
    H::PostIn: for<'de> minicbor_2::Decode<'de, ()>,
    H::PostOut: minicbor_2::Encode<()> + minicbor_2::CborLen<()>,
    H::Put: for<'de> minicbor_2::Decode<'de, ()>,
    H::FetchIn: for<'de> minicbor_2::Decode<'de, ()>,
    H::FetchOut: minicbor_2::Encode<()>,
    H::IPatch: for<'de> minicbor_2::Decode<'de, ()>,
{
    type RequestData = TypeRequestData<H::FetchIn, H::PostOut>;
    type ExtractRequestError = Error;
    type BuildResponseError<M: MinimalWritableMessage> = Error;

    fn extract_request_data<M: ReadableMessage>(
        &mut self,
        request: &M,
    ) -> Result<Self::RequestData, Error> {
        let block2 = Self::extract_options(request)?;

        if matches!(
            request.code().into(),
            code::DELETE | code::POST | code::PUT | code::IPATCH
        ) && block2.is_some()
        {
            return Err(Error::bad_option(coap_numbers::option::BLOCK2));
        }

        Ok(TypeRequestData(match request.code().into() {
            code::DELETE => {
                self.handler.delete()?;
                DoneCode(code::DELETED)
            }
            code::GET => Get(block2.unwrap_or_default()),
            code::POST => {
                use minicbor_2::decode::Decode;

                let payload = request.payload();
                match (payload, H::PostIn::nil()) {
                    (b"", Some(nil)) => Post(self.handler.post(&nil)?),
                    (payload, _) => {
                        let parsed: H::PostIn =
                            minicbor_2::decode(payload).map_err(|deserialize_error| {
                                deserialize_error.into_general_error(payload.len())
                            })?;
                        Post(self.handler.post(&parsed)?)
                    }
                }
            }
            code::PUT => {
                let payload = request.payload();

                let parsed: H::Put = minicbor_2::decode(payload).map_err(|deserialize_error| {
                    deserialize_error.into_general_error(payload.len())
                })?;
                self.handler.put(&parsed)?;
                DoneCode(code::CHANGED)
            }
            code::FETCH => {
                let payload = request.payload();

                let parsed: H::FetchIn =
                    minicbor_2::decode(payload).map_err(|deserialize_error| {
                        deserialize_error.into_general_error(payload.len())
                    })?;
                Fetch(block2.unwrap_or_default(), parsed)
            }
            code::IPATCH => {
                let payload = request.payload();

                let parsed: H::IPatch =
                    minicbor_2::decode(payload).map_err(|deserialize_error| {
                        deserialize_error.into_general_error(payload.len())
                    })?;
                self.handler.ipatch(&parsed)?;
                DoneCode(code::CHANGED)
            }
            _ => return Err(Error::method_not_allowed()),
        }))
    }

    fn estimate_length(&mut self, request: &Self::RequestData) -> usize {
        match &request.0 {
            DoneCode(_) => 4,
            Get(block) => (block.size() + 25).into(), // FIXME: hard-coded copied over from block2_write_with_cf's estimated overhead
            // FIXME: We could set something here, but practically, nothing looks into this anyway.
            _ => 1200,
        }
    }

    fn build_response<M: MutableWritableMessage>(
        &mut self,
        response: &mut M,
        request: Self::RequestData,
    ) -> Result<(), Error> {
        match request.0 {
            DoneCode(c) => response.set_code(M::Code::new(c).map_err(Error::from_unionerror)?),
            Get(block2) => {
                let repr = self.handler.get()?;
                response.set_code(M::Code::new(code::CONTENT).map_err(Error::from_unionerror)?);
                block2_write_with_cf(
                    block2,
                    response,
                    |win| minicbor_2::encode(&repr, minicbor_adapters::WriteToEmbeddedIo(win)),
                    MiniCBORSerialization2::CF,
                )
                .map_err(|_| Error::internal_server_error())?;
            }
            Fetch(block2, fetch) => {
                let repr = self.handler.fetch(&fetch)?;
                response.set_code(M::Code::new(code::CONTENT).map_err(Error::from_unionerror)?);
                block2_write_with_cf(
                    block2,
                    response,
                    |win| minicbor_2::encode(&repr, minicbor_adapters::WriteToEmbeddedIo(win)),
                    MiniCBORSerialization2::CF,
                )
                .map_err(|_| Error::internal_server_error())?;
            }
            Post(post_out) => {
                response.set_code(M::Code::new(code::CHANGED).map_err(Error::from_unionerror)?);
                // We're lacking the context for reassembly, so we have pack the response in one
                // packet or bail.
                let len = minicbor_2::len(&post_out);
                let payload = response
                    .payload_mut_with_len(len)
                    .map_err(|_| Error::internal_server_error())?;
                let mut encoder = minicbor_2::Encoder::new(payload);
                encoder
                    .encode(&post_out)
                    .map_err(|_| Error::internal_server_error())?;
            }
        };
        Ok(())
    }
}

impl TypeSerializer for MiniCBORSerialization2 {
    // FIXME: Allow differentiation by methods
    const CF: Option<u16> = coap_numbers::content_format::from_str("application/cbor");
}

impl<H> TypeHandler<H, MiniCBORSerialization2>
where
    H: TypeRenderable,
    H::Get: minicbor_2::Encode<()>,
    H::PostIn: for<'de> minicbor_2::Decode<'de, ()>,
    H::PostOut: minicbor_2::Encode<()>,
    H::Put: for<'de> minicbor_2::Decode<'de, ()>,
    H::FetchIn: for<'de> minicbor_2::Decode<'de, ()>,
    H::FetchOut: minicbor_2::Encode<()>,
    H::IPatch: for<'de> minicbor_2::Decode<'de, ()>,
{
    /// Wrap a handler through minicbor 2.x
    pub fn new_minicbor_2(handler: H) -> Self {
        TypeHandler {
            handler,
            _phantom: PhantomData,
        }
    }
}

impl ToTypedResourceError for minicbor_2::decode::Error {
    fn into_general_error(self, total_len: usize) -> Error {
        let mut error = Error::bad_request();
        if let Some(position) = self.position() {
            error = Error::bad_request_with_rbep(position);
        }
        if self.is_end_of_input() {
            // Data is not set, but it's convenient to point that out in the error response just in
            // case something goes wrong with block-wise transfers
            error = Error::bad_request_with_rbep(total_len);
        };
        if self.is_type_mismatch() {
            error = error.with_title("Type mismatch")
        }
        error
    }
}