coap_handler_implementations/
typed_resource.rs

1//! Module containing the [TypeHandler] handler and the [TypeRenderable]
2//! trait, along with the [TypeSerializer] helper trait and its corresponding implementations
3//! for various serde(ish) (de)serializers.
4
5use crate::Error;
6use crate::helpers::block2_write_with_cf;
7use coap_handler::Handler;
8use coap_message::{
9    Code as _, MessageOption, MinimalWritableMessage, MutableWritableMessage, ReadableMessage,
10};
11use coap_message_utils::{OptionsExt, option_value::Block2RequestData};
12use coap_numbers::{code, option};
13use core::marker::PhantomData;
14
15pub(crate) mod fillers;
16pub(crate) mod generated;
17
18/// A type that (for minicbor typed handlers) represents an empty payload.
19///
20/// This is particularly practical for resources that have a GET and PUT handler that act on
21/// serialized data, but where the POST handler just takes or return an empty payload.
22pub struct Empty;
23
24/// A Handler trait that supports various CoAP methods on a data structure that can be serialized,
25/// eg. in CBOR.
26///
27/// A TypeRenderable implementation can be turned into a [Handler] by wrapping it in
28/// [`TypeHandler::new_*`][TypeHandler].
29///
30/// This is a composite trait of many per-method traits because all those traits come with their
31/// own associated types (and maybe constants), and as long as neither of those can be provided
32/// like methods can be (i.e., added later extensibly, allowing for defaults), those are more
33/// easily handled by composition.
34///
35/// Unless you can keep track of changes to the list of trait dependencies, use the
36/// [`with_get_put_fetch()`][super::with_get_put_fetch]-style wrappers around instances, which mark
37/// all other methods as unimplemented.
38///
39/// For different methods, the functions get called at different times in the request/response
40/// handling sequence: A GET's method is called when the response is prepared, whereas a PUT's
41/// method is called when the request is prepared. This ensures that for CoAP server
42/// implementations that delay rendering (e.g. as part of retransmission handling), no large data
43/// needs to be kept for long. The methods with both input and output data (POST, FETCH) are called
44/// according to their most expected usage pattern: [`FetchRenderable::fetch()`] is called at response time
45/// (because the request payload is typically a small filter expression that limits the output to
46/// something that fits within a CoAP response but may still be large), whereas [`IPatchRenderable::ipatch()`]
47/// is called immediately (because it is frequently used with empty outputs).
48pub trait TypeRenderable:
49    GetRenderable
50    + PostRenderable
51    + PutRenderable
52    + DeleteRenderable
53    + FetchRenderable
54    + IPatchRenderable
55{
56}
57
58/// Subset of [`TypeRenderable`] that handles the GET method.
59pub trait GetRenderable {
60    /// Output type of the [get()][Self::get()] method, serialized into the response payload.
61    type Get;
62
63    fn get(&mut self) -> Result<Self::Get, Error> {
64        Err(Error::method_not_allowed())
65    }
66}
67
68/// Subset of [`TypeRenderable`] that handles the POST method.
69pub trait PostRenderable {
70    /// Input type of the [post()][Self::post()] method, deserialized from the request payload.
71    type PostIn;
72    /// Ouptut type of the [post()][Self::post()] method, serialized into the response payload.
73    ///
74    /// Note that this is a size sensitive type: On CoAP servers that need to retain state between
75    /// processing a request and sending a response, this type goes into that state.
76    type PostOut;
77
78    fn post(&mut self, _representation: &Self::PostIn) -> Result<Self::PostOut, Error> {
79        Err(Error::method_not_allowed())
80    }
81}
82
83/// Subset of [`TypeRenderable`] that handles the PUT method.
84pub trait PutRenderable {
85    /// Input type of the [put()][Self::put()] method, deserialized from the request payload.
86    type Put;
87    fn put(&mut self, _representation: &Self::Put) -> Result<(), Error> {
88        Err(Error::method_not_allowed())
89    }
90}
91
92/// Subset of [`TypeRenderable`] that handles the DELETE method.
93// Note that while this could be a plain provided method (it needs no associated items that can't
94// have a default), that'd cause a bit of inconsistency in the user experience.
95pub trait DeleteRenderable {
96    fn delete(&mut self) -> Result<(), Error> {
97        Err(Error::method_not_allowed())
98    }
99}
100
101/// Subset of [`TypeRenderable`] that handles the FETCH method.
102pub trait FetchRenderable {
103    /// Input type of the [fetch()][Self::fetch()] method, deserialized from the request payload.
104    ///
105    /// Note that this is a size sensitive type: On CoAP servers that need to retain state between
106    /// processing a request and sending a response, this type goes into that state.
107    type FetchIn;
108    /// Ouptut type of the [fetch()][Self::fetch()] method, serialized into the response payload.
109    type FetchOut;
110
111    fn fetch(&mut self, _representation: &Self::FetchIn) -> Result<Self::FetchOut, Error> {
112        Err(Error::method_not_allowed())
113    }
114}
115
116/// Subset of [`TypeRenderable`] that handles the IPATCH method.
117pub trait IPatchRenderable {
118    /// Input type of the [ipatch()][Self::ipatch()] method, deserialized from the request payload.
119    type IPatch;
120
121    fn ipatch(&mut self, _representation: &Self::IPatch) -> Result<(), Error> {
122        Err(Error::method_not_allowed())
123    }
124}
125
126/// Keeping them hidden to stay flexible; they don't need to be named for their'e either default or
127/// their users have aliases.
128mod sealed {
129    pub trait TypeSerializer {
130        // BIG FIXME: This conflates
131        // * This is the same across all methods, both for input and for output
132        // * This is guided by the serializer, when really it may need to be guided by the type
133        //   (even though minicbor may easily (de)serialize application/cbor or
134        //   application/foo+cbor).
135        const CF: Option<u16>;
136    }
137
138    pub struct MiniCBORSerialization2;
139}
140use sealed::*;
141
142/// Wrapper for resource handlers that are implemented in terms of GETting, POSTing or PUTting
143/// objects in CBOR format.
144///
145/// The wrapper handles all encoding and decoding, options processing (ignoring the critical
146/// Uri-Path option under the assumption that that has been already processed by an underlying
147/// request router), the corresponding errors and block-wise GETs.
148///
149/// More complex handlers (eg. implementing additional representations, or processing query
150/// parameters into additional data available to the [TypeRenderable]) can be built by
151/// forwarding to this (where any critical but already processed options would need to be masked
152/// from the message's option) or taking inspiration from it.
153pub struct TypeHandler<H, S: TypeSerializer>
154where
155    H: TypeRenderable,
156{
157    handler: H,
158    _phantom: PhantomData<S>,
159}
160
161impl<H, S> TypeHandler<H, S>
162where
163    H: TypeRenderable,
164    S: TypeSerializer,
165{
166    /// Checks whether any Accept and Content-Format options are really S::CF, and extracts the
167    /// Block2 option.
168    ///
169    /// This is not 100% sharp because it'll let Accept and Content-Format slip through on Delete
170    /// as well, but there's little harm in that.
171    fn extract_options(request: &impl ReadableMessage) -> Result<Option<Block2RequestData>, Error> {
172        let mut block2 = None;
173
174        request
175            .options()
176            .take_block2(&mut block2)
177            .filter(|o| {
178                if o.number() == option::CONTENT_FORMAT || o.number() == option::ACCEPT {
179                    if let Some(cf) = S::CF {
180                        // If they differ, we'll keep the option for later failing
181                        o.value_uint() != Some(cf)
182                    } else {
183                        // We don't know any content format, so we keep the option in the iterator
184                        // to fail later
185                        true
186                    }
187                } else {
188                    true
189                }
190            })
191            .ignore_elective_others()?;
192
193        Ok(block2)
194    }
195}
196
197/// Data carried around between a request and its response for [TypeHandler]s
198///
199/// This carries input data from FETCH and output data from POST, under the assumption that FETCH
200/// generally have small input representations and POST have large output representations; see
201/// [`TypeRenderable`] documentation.
202pub struct TypeRequestData<FetchIn, PostOut>(TypeRequestDataE<FetchIn, PostOut>);
203
204enum TypeRequestDataE<FetchIn, PostOut> {
205    Get(Block2RequestData), // GET to be processed later, but all request opions were in order
206    Fetch(Block2RequestData, FetchIn),
207    Post(PostOut),
208    // FIXME: Those could really go together with the Error in a single variant, but then it's also
209    // convenient for Error to be really only errors -- make Error a type alias for Response<const
210    // bool CanBeOk, const bool CanBeError>? And may we just abuse ExtractRequestError to also
211    // carry it without any other ill-effect?
212    DoneCode(u8), // All done, just a response to emit -- if POST/PUT has been processed, or GET had a bad accept/option
213}
214use self::TypeRequestDataE::{DoneCode, Fetch, Get, Post};
215
216// FIXME for all the below: deduplicate (but not sure how, without HKTs) -- and some alterations
217// have accumulated
218
219trait ToTypedResourceError {
220    fn into_general_error(self, total_len: usize) -> Error;
221}
222
223impl<'b, C> minicbor_2::decode::Decode<'b, C> for Empty {
224    fn decode(
225        _d: &mut minicbor_2::decode::Decoder<'b>,
226        _ctx: &mut C,
227    ) -> Result<Self, minicbor_2::decode::Error> {
228        Err(minicbor_2::decode::Error::message("No element expected").at(0))
229    }
230
231    fn nil() -> Option<Self> {
232        Some(Empty)
233    }
234}
235
236impl<H> Handler for TypeHandler<H, MiniCBORSerialization2>
237where
238    H: TypeRenderable,
239    H::Get: minicbor_2::Encode<()>,
240    H::PostIn: for<'de> minicbor_2::Decode<'de, ()>,
241    H::PostOut: minicbor_2::Encode<()> + minicbor_2::CborLen<()>,
242    H::Put: for<'de> minicbor_2::Decode<'de, ()>,
243    H::FetchIn: for<'de> minicbor_2::Decode<'de, ()>,
244    H::FetchOut: minicbor_2::Encode<()>,
245    H::IPatch: for<'de> minicbor_2::Decode<'de, ()>,
246{
247    type RequestData = TypeRequestData<H::FetchIn, H::PostOut>;
248    type ExtractRequestError = Error;
249    type BuildResponseError<M: MinimalWritableMessage> = Error;
250
251    fn extract_request_data<M: ReadableMessage>(
252        &mut self,
253        request: &M,
254    ) -> Result<Self::RequestData, Error> {
255        let block2 = Self::extract_options(request)?;
256
257        if matches!(
258            request.code().into(),
259            code::DELETE | code::POST | code::PUT | code::IPATCH
260        ) && block2.is_some()
261        {
262            return Err(Error::bad_option(coap_numbers::option::BLOCK2));
263        }
264
265        Ok(TypeRequestData(match request.code().into() {
266            code::DELETE => {
267                self.handler.delete()?;
268                DoneCode(code::DELETED)
269            }
270            code::GET => Get(block2.unwrap_or_default()),
271            code::POST => {
272                use minicbor_2::decode::Decode;
273
274                let payload = request.payload();
275                match (payload, H::PostIn::nil()) {
276                    (b"", Some(nil)) => Post(self.handler.post(&nil)?),
277                    (payload, _) => {
278                        let parsed: H::PostIn =
279                            minicbor_2::decode(payload).map_err(|deserialize_error| {
280                                deserialize_error.into_general_error(payload.len())
281                            })?;
282                        Post(self.handler.post(&parsed)?)
283                    }
284                }
285            }
286            code::PUT => {
287                let payload = request.payload();
288
289                let parsed: H::Put = minicbor_2::decode(payload).map_err(|deserialize_error| {
290                    deserialize_error.into_general_error(payload.len())
291                })?;
292                self.handler.put(&parsed)?;
293                DoneCode(code::CHANGED)
294            }
295            code::FETCH => {
296                let payload = request.payload();
297
298                let parsed: H::FetchIn =
299                    minicbor_2::decode(payload).map_err(|deserialize_error| {
300                        deserialize_error.into_general_error(payload.len())
301                    })?;
302                Fetch(block2.unwrap_or_default(), parsed)
303            }
304            code::IPATCH => {
305                let payload = request.payload();
306
307                let parsed: H::IPatch =
308                    minicbor_2::decode(payload).map_err(|deserialize_error| {
309                        deserialize_error.into_general_error(payload.len())
310                    })?;
311                self.handler.ipatch(&parsed)?;
312                DoneCode(code::CHANGED)
313            }
314            _ => return Err(Error::method_not_allowed()),
315        }))
316    }
317
318    fn estimate_length(&mut self, request: &Self::RequestData) -> usize {
319        match &request.0 {
320            DoneCode(_) => 4,
321            Get(block) => (block.size() + 25).into(), // FIXME: hard-coded copied over from block2_write_with_cf's estimated overhead
322            // FIXME: We could set something here, but practically, nothing looks into this anyway.
323            _ => 1200,
324        }
325    }
326
327    fn build_response<M: MutableWritableMessage>(
328        &mut self,
329        response: &mut M,
330        request: Self::RequestData,
331    ) -> Result<(), Error> {
332        match request.0 {
333            DoneCode(c) => response.set_code(M::Code::new(c).map_err(Error::from_unionerror)?),
334            Get(block2) => {
335                let repr = self.handler.get()?;
336                response.set_code(M::Code::new(code::CONTENT).map_err(Error::from_unionerror)?);
337                block2_write_with_cf(
338                    block2,
339                    response,
340                    |win| minicbor_2::encode(&repr, minicbor_adapters::WriteToEmbeddedIo(win)),
341                    MiniCBORSerialization2::CF,
342                )
343                .map_err(|_| Error::internal_server_error())?;
344            }
345            Fetch(block2, fetch) => {
346                let repr = self.handler.fetch(&fetch)?;
347                response.set_code(M::Code::new(code::CONTENT).map_err(Error::from_unionerror)?);
348                block2_write_with_cf(
349                    block2,
350                    response,
351                    |win| minicbor_2::encode(&repr, minicbor_adapters::WriteToEmbeddedIo(win)),
352                    MiniCBORSerialization2::CF,
353                )
354                .map_err(|_| Error::internal_server_error())?;
355            }
356            Post(post_out) => {
357                response.set_code(M::Code::new(code::CHANGED).map_err(Error::from_unionerror)?);
358                // We're lacking the context for reassembly, so we have pack the response in one
359                // packet or bail.
360                let len = minicbor_2::len(&post_out);
361                let payload = response
362                    .payload_mut_with_len(len)
363                    .map_err(|_| Error::internal_server_error())?;
364                let mut encoder = minicbor_2::Encoder::new(payload);
365                encoder
366                    .encode(&post_out)
367                    .map_err(|_| Error::internal_server_error())?;
368            }
369        };
370        Ok(())
371    }
372}
373
374impl TypeSerializer for MiniCBORSerialization2 {
375    // FIXME: Allow differentiation by methods
376    const CF: Option<u16> = coap_numbers::content_format::from_str("application/cbor");
377}
378
379impl<H> TypeHandler<H, MiniCBORSerialization2>
380where
381    H: TypeRenderable,
382    H::Get: minicbor_2::Encode<()>,
383    H::PostIn: for<'de> minicbor_2::Decode<'de, ()>,
384    H::PostOut: minicbor_2::Encode<()>,
385    H::Put: for<'de> minicbor_2::Decode<'de, ()>,
386    H::FetchIn: for<'de> minicbor_2::Decode<'de, ()>,
387    H::FetchOut: minicbor_2::Encode<()>,
388    H::IPatch: for<'de> minicbor_2::Decode<'de, ()>,
389{
390    /// Wrap a handler through minicbor 2.x
391    pub fn new_minicbor_2(handler: H) -> Self {
392        TypeHandler {
393            handler,
394            _phantom: PhantomData,
395        }
396    }
397}
398
399impl ToTypedResourceError for minicbor_2::decode::Error {
400    fn into_general_error(self, total_len: usize) -> Error {
401        let mut error = Error::bad_request();
402        if let Some(position) = self.position() {
403            error = Error::bad_request_with_rbep(position);
404        }
405        if self.is_end_of_input() {
406            // Data is not set, but it's convenient to point that out in the error response just in
407            // case something goes wrong with block-wise transfers
408            error = Error::bad_request_with_rbep(total_len);
409        };
410        if self.is_type_mismatch() {
411            error = error.with_title("Type mismatch")
412        }
413        error
414    }
415}