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<C> minicbor_2::encode::Encode<C> for Empty {
237    fn encode<W: minicbor_2::encode::Write>(
238        &self,
239        _e: &mut minicbor_2::Encoder<W>,
240        _ctx: &mut C,
241    ) -> Result<(), minicbor_2::encode::Error<W::Error>> {
242        Ok(())
243    }
244}
245
246impl<C> minicbor_2::encode::CborLen<C> for Empty {
247    fn cbor_len(&self, _ctx: &mut C) -> usize {
248        0
249    }
250}
251
252impl<H> Handler for TypeHandler<H, MiniCBORSerialization2>
253where
254    H: TypeRenderable,
255    H::Get: minicbor_2::Encode<()>,
256    H::PostIn: for<'de> minicbor_2::Decode<'de, ()>,
257    H::PostOut: minicbor_2::Encode<()> + minicbor_2::CborLen<()>,
258    H::Put: for<'de> minicbor_2::Decode<'de, ()>,
259    H::FetchIn: for<'de> minicbor_2::Decode<'de, ()>,
260    H::FetchOut: minicbor_2::Encode<()>,
261    H::IPatch: for<'de> minicbor_2::Decode<'de, ()>,
262{
263    type RequestData = TypeRequestData<H::FetchIn, H::PostOut>;
264    type ExtractRequestError = Error;
265    type BuildResponseError<M: MinimalWritableMessage> = Error;
266
267    fn extract_request_data<M: ReadableMessage>(
268        &mut self,
269        request: &M,
270    ) -> Result<Self::RequestData, Error> {
271        let block2 = Self::extract_options(request)?;
272
273        if matches!(
274            request.code().into(),
275            code::DELETE | code::POST | code::PUT | code::IPATCH
276        ) && block2.is_some()
277        {
278            return Err(Error::bad_option(coap_numbers::option::BLOCK2));
279        }
280
281        Ok(TypeRequestData(match request.code().into() {
282            code::DELETE => {
283                self.handler.delete()?;
284                DoneCode(code::DELETED)
285            }
286            code::GET => Get(block2.unwrap_or_default()),
287            code::POST => {
288                use minicbor_2::decode::Decode;
289
290                let payload = request.payload();
291                match (payload, H::PostIn::nil()) {
292                    (b"", Some(nil)) => Post(self.handler.post(&nil)?),
293                    (payload, _) => {
294                        let parsed: H::PostIn =
295                            minicbor_2::decode(payload).map_err(|deserialize_error| {
296                                deserialize_error.into_general_error(payload.len())
297                            })?;
298                        Post(self.handler.post(&parsed)?)
299                    }
300                }
301            }
302            code::PUT => {
303                let payload = request.payload();
304
305                let parsed: H::Put = minicbor_2::decode(payload).map_err(|deserialize_error| {
306                    deserialize_error.into_general_error(payload.len())
307                })?;
308                self.handler.put(&parsed)?;
309                DoneCode(code::CHANGED)
310            }
311            code::FETCH => {
312                let payload = request.payload();
313
314                let parsed: H::FetchIn =
315                    minicbor_2::decode(payload).map_err(|deserialize_error| {
316                        deserialize_error.into_general_error(payload.len())
317                    })?;
318                Fetch(block2.unwrap_or_default(), parsed)
319            }
320            code::IPATCH => {
321                let payload = request.payload();
322
323                let parsed: H::IPatch =
324                    minicbor_2::decode(payload).map_err(|deserialize_error| {
325                        deserialize_error.into_general_error(payload.len())
326                    })?;
327                self.handler.ipatch(&parsed)?;
328                DoneCode(code::CHANGED)
329            }
330            _ => return Err(Error::method_not_allowed()),
331        }))
332    }
333
334    fn estimate_length(&mut self, request: &Self::RequestData) -> usize {
335        match &request.0 {
336            DoneCode(_) => 4,
337            Get(block) => (block.size() + 25).into(), // FIXME: hard-coded copied over from block2_write_with_cf's estimated overhead
338            // FIXME: We could set something here, but practically, nothing looks into this anyway.
339            _ => 1200,
340        }
341    }
342
343    fn build_response<M: MutableWritableMessage>(
344        &mut self,
345        response: &mut M,
346        request: Self::RequestData,
347    ) -> Result<(), Error> {
348        match request.0 {
349            DoneCode(c) => response.set_code(M::Code::new(c).map_err(Error::from_unionerror)?),
350            Get(block2) => {
351                let repr = self.handler.get()?;
352                response.set_code(M::Code::new(code::CONTENT).map_err(Error::from_unionerror)?);
353                block2_write_with_cf(
354                    block2,
355                    response,
356                    |win| minicbor_2::encode(&repr, minicbor_adapters::WriteToEmbeddedIo(win)),
357                    MiniCBORSerialization2::CF,
358                )
359                .map_err(|_| Error::internal_server_error())?;
360            }
361            Fetch(block2, fetch) => {
362                let repr = self.handler.fetch(&fetch)?;
363                response.set_code(M::Code::new(code::CONTENT).map_err(Error::from_unionerror)?);
364                block2_write_with_cf(
365                    block2,
366                    response,
367                    |win| minicbor_2::encode(&repr, minicbor_adapters::WriteToEmbeddedIo(win)),
368                    MiniCBORSerialization2::CF,
369                )
370                .map_err(|_| Error::internal_server_error())?;
371            }
372            Post(post_out) => {
373                response.set_code(M::Code::new(code::CHANGED).map_err(Error::from_unionerror)?);
374                // We're lacking the context for reassembly, so we have pack the response in one
375                // packet or bail.
376                let len = minicbor_2::len(&post_out);
377                let payload = response
378                    .payload_mut_with_len(len)
379                    .map_err(|_| Error::internal_server_error())?;
380                let mut encoder = minicbor_2::Encoder::new(payload);
381                encoder
382                    .encode(&post_out)
383                    .map_err(|_| Error::internal_server_error())?;
384            }
385        };
386        Ok(())
387    }
388}
389
390impl TypeSerializer for MiniCBORSerialization2 {
391    // FIXME: Allow differentiation by methods
392    const CF: Option<u16> = coap_numbers::content_format::from_str("application/cbor");
393}
394
395impl<H> TypeHandler<H, MiniCBORSerialization2>
396where
397    H: TypeRenderable,
398    H::Get: minicbor_2::Encode<()>,
399    H::PostIn: for<'de> minicbor_2::Decode<'de, ()>,
400    H::PostOut: minicbor_2::Encode<()>,
401    H::Put: for<'de> minicbor_2::Decode<'de, ()>,
402    H::FetchIn: for<'de> minicbor_2::Decode<'de, ()>,
403    H::FetchOut: minicbor_2::Encode<()>,
404    H::IPatch: for<'de> minicbor_2::Decode<'de, ()>,
405{
406    /// Wrap a handler through minicbor 2.x
407    pub fn new_minicbor_2(handler: H) -> Self {
408        TypeHandler {
409            handler,
410            _phantom: PhantomData,
411        }
412    }
413}
414
415impl ToTypedResourceError for minicbor_2::decode::Error {
416    fn into_general_error(self, total_len: usize) -> Error {
417        let mut error = Error::bad_request();
418        if let Some(position) = self.position() {
419            error = Error::bad_request_with_rbep(position);
420        }
421        if self.is_end_of_input() {
422            // Data is not set, but it's convenient to point that out in the error response just in
423            // case something goes wrong with block-wise transfers
424            error = Error::bad_request_with_rbep(total_len);
425        };
426        if self.is_type_mismatch() {
427            error = error.with_title("Type mismatch")
428        }
429        error
430    }
431}