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}