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}