coap_message_demos/
cbor.rs

1//! This module demonstrates the use of [coap_handler_implementations::TypeRenderable]
2
3use coap_handler::{Attribute, Handler, Reporting};
4use coap_handler_implementations::{
5    DeleteRenderable, FetchRenderable, GetRenderable, HandlerBuilder, IPatchRenderable,
6    PostRenderable, PutRenderable, ReportingHandlerBuilder, TypeHandler, TypeRenderable,
7    new_dispatcher, with_get_put_fetch,
8};
9use coap_message_utils::Error;
10use core::default::Default;
11use heapless::{String, Vec};
12
13/// A demo struct with several members, all of which implement minicbor traits.
14///
15/// Using TypeRenderable, a CoAP GETter and SETter are implemented. To avoid being boring, both
16/// do some checks: The setter rejects attempts to write HTML, and the getter refuses to answer if
17/// the "hidden" property is true.
18// Not going through minicbor because that has no implementations for heapless or vice versa
19#[derive(minicbor::Decode, minicbor::Encode, Clone)]
20#[cbor(map)]
21pub struct MyCBOR {
22    #[n(0)]
23    hidden: bool,
24    #[n(1)]
25    number: usize,
26    #[cbor(n(2), with = "minicbor_adapters")]
27    label: String<32>,
28    #[cbor(n(3), with = "minicbor_adapters")]
29    list: Vec<usize, 16>,
30}
31
32impl Default for MyCBOR {
33    fn default() -> Self {
34        Self {
35            hidden: false,
36            number: 32,
37            label: "Hello".try_into().unwrap(),
38            list: Vec::from_slice(&[1, 2, 3]).expect("More than 3 entries allocated"),
39        }
40    }
41}
42
43impl GetRenderable for MyCBOR {
44    type Get = MyCBOR;
45
46    fn get(&mut self) -> Result<MyCBOR, Error> {
47        if self.hidden {
48            return Err(Error::forbidden());
49        }
50        Ok(self.clone())
51    }
52}
53
54impl PutRenderable for MyCBOR {
55    fn put(&mut self, new: &MyCBOR) -> Result<(), Error> {
56        if new.label.contains('<') {
57            // No HTML injection please ;-)
58            return Err(Error::bad_request());
59        }
60        *self = new.clone();
61        Ok(())
62    }
63    type Put = MyCBOR;
64}
65
66// Doing Fetch is a bit harder because there's no trivial (mini)cbor derivations for it.
67//
68// Also, there is choice involved: We choose for this demo to fetch by sending a map key and
69// obtaining the respective value.
70
71pub struct MyCborView {
72    index: u8,
73    // We have to copy it around because we can't pass a lifetime'd thing out of FetchOut
74    // (typed_resource could be bent to make it work, but then TryingThroughMutex ceases to be
75    // possible), but due to when TypeHandler renders FETCH, that's just a short-lived copy on
76    // the stack that, given we only read later, might even be optimized away if the compiler makes
77    // maximum use of the mutex's Release semantics.
78    data: MyCBOR,
79}
80
81impl<C> minicbor::encode::Encode<C> for MyCborView {
82    fn encode<W: minicbor::encode::Write>(
83        &self,
84        e: &mut minicbor::Encoder<W>,
85        _ctx: &mut C,
86    ) -> Result<(), minicbor::encode::Error<W::Error>> {
87        match self.index {
88            0 => e.encode(self.data.hidden),
89            1 => e.encode(self.data.number),
90            2 => e.encode(self.data.label.as_str()),
91            3 => e.encode(self.data.list.as_slice()),
92            _ => e.encode(()),
93        }?;
94        Ok(())
95    }
96}
97
98impl FetchRenderable for MyCBOR {
99    type FetchIn = u8;
100    type FetchOut = MyCborView;
101
102    fn fetch(&mut self, index: &Self::FetchIn) -> Result<Self::FetchOut, Error> {
103        let data = self.clone();
104        if data.hidden && *index >= 2 {
105            // It'd be fun if we could also do granular reveal at GET, but that's more derive
106            // hacking.
107            return Err(Error::forbidden());
108        }
109        Ok(MyCborView {
110            index: *index,
111            data,
112        })
113    }
114}
115
116/// Build a handler that gives access to a single [MyCBOR] object
117///
118/// This can be built with on no_std, and thus has no external synchronization -- the MyCBOR is
119/// owned by the handler, and can only be accessed through the CoAP interface. For an alternative,
120/// see [double_cbor_with_access]().
121pub fn single_cbor_tree() -> impl Handler + Reporting {
122    let cbor: MyCBOR = Default::default();
123
124    new_dispatcher()
125        .at_with_attributes(
126            &["cbor"],
127            &[Attribute::Ct(60)],
128            TypeHandler::new_minicbor_2(with_get_put_fetch(cbor)),
129        )
130        .with_wkc()
131}
132
133/// Build a handler that gives access to a single [MyCBOR] object on two paths, and to the rest of
134/// the application
135///
136/// As the MyCBOR object is now stored in an Arc and referenced through a Mutex, it can be in two
137/// places in the tree at the same time, and even be accessed by the application at the same time.
138#[cfg(feature = "std")]
139pub fn double_cbor_with_access() -> (
140    impl Handler + Reporting,
141    std::sync::Arc<std::sync::Mutex<MyCBOR>>,
142) {
143    let cbor: std::sync::Arc<std::sync::Mutex<MyCBOR>> = Default::default();
144
145    let handler = new_dispatcher()
146        .at_with_attributes(
147            &["cbor", "1"],
148            &[],
149            TypeHandler::new_minicbor_2(with_get_put_fetch(TryingThroughMutex(cbor.clone()))),
150        )
151        .at_with_attributes(
152            &["cbor", "2"],
153            &[],
154            TypeHandler::new_minicbor_2(with_get_put_fetch(TryingThroughMutex(cbor.clone()))),
155        )
156        .with_wkc();
157
158    (handler, cbor)
159}
160
161/// Helper struct that accesses a TypeRenderable intrough an `Arc<Mutex<_>>`, thus allowing easiy
162/// simultaneous access.
163///
164/// When implementing SimpelCBORHandler, it does not wait for the lock, but rather fails with a
165/// 5.03 Service Unavailable that usually prompts the client to retry. Note that this will not
166/// happen ever if the items are just accessed through different paths on the same handler, and
167/// neither will be if they are only ever locked outside the CoAP server's main loop. (And even
168/// then, unless they're locked for long, it's very unlikely to be hit by chance).
169///
170/// TBD: Send a "Max-Age: 0" option along to indicate that the client can try again right away
171/// rather than wait the usual 60 seconds.
172///
173/// TBD: This may be a nice addition to the [coap_handler] crate in general (but needs the
174/// introduction of a `std` feature there).
175#[cfg(feature = "std")]
176pub struct TryingThroughMutex<T>(pub std::sync::Arc<std::sync::Mutex<T>>);
177
178#[cfg(feature = "std")]
179impl<T: TypeRenderable> TypeRenderable for TryingThroughMutex<T> {}
180
181#[cfg(feature = "std")]
182impl<T: GetRenderable> GetRenderable for TryingThroughMutex<T> {
183    type Get = T::Get;
184
185    fn get(&mut self) -> Result<Self::Get, Error> {
186        self.0
187            .try_lock()
188            .map_err(|_| Error::service_unavailable())?
189            .get()
190    }
191}
192
193#[cfg(feature = "std")]
194impl<T: PutRenderable> PutRenderable for TryingThroughMutex<T> {
195    type Put = T::Put;
196
197    fn put(&mut self, new: &Self::Put) -> Result<(), Error> {
198        self.0
199            .try_lock()
200            .map(|mut s| s.put(new))
201            .unwrap_or(Err(Error::service_unavailable()))
202    }
203}
204
205#[cfg(feature = "std")]
206impl<T: PostRenderable> PostRenderable for TryingThroughMutex<T> {
207    type PostIn = T::PostIn;
208    type PostOut = T::PostOut;
209
210    fn post(&mut self, request: &Self::PostIn) -> Result<Self::PostOut, Error> {
211        self.0
212            .try_lock()
213            .map(|mut s| s.post(request))
214            .unwrap_or(Err(Error::service_unavailable()))
215    }
216}
217
218#[cfg(feature = "std")]
219impl<T: DeleteRenderable> DeleteRenderable for TryingThroughMutex<T> {}
220
221#[cfg(feature = "std")]
222impl<T: FetchRenderable> FetchRenderable for TryingThroughMutex<T> {
223    type FetchIn = T::FetchIn;
224    type FetchOut = T::FetchOut;
225
226    fn fetch(&mut self, request: &Self::FetchIn) -> Result<Self::FetchOut, Error> {
227        self.0
228            .try_lock()
229            .map(|mut s| s.fetch(request))
230            .unwrap_or(Err(Error::service_unavailable()))
231    }
232}
233
234#[cfg(feature = "std")]
235impl<T: IPatchRenderable> IPatchRenderable for TryingThroughMutex<T> {
236    type IPatch = T::IPatch;
237
238    fn ipatch(&mut self, new: &Self::IPatch) -> Result<(), Error> {
239        self.0
240            .try_lock()
241            .map(|mut s| s.ipatch(new))
242            .unwrap_or(Err(Error::service_unavailable()))
243    }
244}