coap_handler_implementations/
helpers.rs

1use coap_message::{MessageOption, MutableWritableMessage, ReadableMessage};
2use coap_message_utils::option_value::Block2RequestData;
3use coap_numbers::option;
4
5use windowed_infinity::WindowedInfinity;
6
7// FIXME: Move to fallible operations (but that needs more work on block2_write_with_cf, which is
8// due for a larger overhaul anyway)
9pub(crate) fn optconvert<O: TryFrom<u16>>(option: u16) -> O {
10    option
11        .try_into()
12        .map_err(|_| "Response type can't express options required by handler")
13        .unwrap()
14}
15
16/// Provide a writer into the response message
17///
18/// Anything written into the writer is put into the message's payload, and the Block2 and ETag
19/// option of the message are set automatically based on what is written.
20///
21/// As some cleanup is required at the end of the write (eg. setting the ETag and the M flag in the
22/// Block2 option), the actual writing needs to take place inside a callback.
23///
24/// Note that only a part of the write (that which was requested by the Block2 operation) is
25/// actually persisted; the rest is discarded. When the M flag indicates that the client did not
26/// obtain the full message yet, it typically sends another request that is then processed the same
27/// way again, for a different "window".
28///
29/// The type passed in should not be relied on too much -- ideally it'd be `F: for<W:
30/// core::fmt::Write> FnOnce(&mut W) -> R`, and the signature may still change in that direction.
31pub fn block2_write<F, R>(
32    block2: Block2RequestData,
33    response: &mut impl MutableWritableMessage,
34    f: F,
35) -> R
36where
37    F: FnOnce(&mut crate::embedded_io::EmbeddedIoOrFmtWrite<'_>) -> R,
38{
39    block2_write_with_cf(block2, response, f, None)
40}
41
42#[derive(PartialEq)]
43enum Characterization {
44    Underflow,
45    Inside,
46    Overflow,
47}
48
49use Characterization::*;
50
51impl Characterization {
52    fn new(cursor: isize, buffer: &[u8]) -> Self {
53        match usize::try_from(cursor) {
54            Err(_) => Underflow,
55            Ok(i) if i == buffer.len() => Inside,
56            _ => Overflow,
57        }
58    }
59}
60
61// Not fully public because it's a crude workaround for not having zippable write injectors yet
62//
63// Also, this needs a rewrite for fallible writes (see optconvert and other unwrap uses).
64pub(crate) fn block2_write_with_cf<F, R>(
65    block2: Block2RequestData,
66    response: &mut impl MutableWritableMessage,
67    f: F,
68    cf: Option<u16>,
69) -> R
70where
71    F: FnOnce(&mut crate::embedded_io::EmbeddedIoOrFmtWrite<'_>) -> R,
72{
73    let estimated_option_size = 25; // 9 bytes ETag, up to 5 bytes Block2, up to 5 bytes Size2, 1 byte payload marker
74    let payload_budget = response.available_space() - estimated_option_size;
75    let block2 = block2
76        .shrink(payload_budget as u16)
77        .expect("Tiny buffer allocated");
78
79    response
80        .add_option(optconvert(option::ETAG), &[0, 0, 0, 0, 0, 0, 0, 0])
81        .unwrap();
82    if let Some(cf) = cf
83        && let Ok(cfopt) = option::CONTENT_FORMAT.try_into()
84    {
85        response.add_option_uint(cfopt, cf).unwrap();
86    }
87    response
88        .add_option_uint(optconvert(option::BLOCK2), block2.to_option_value(false))
89        .unwrap();
90
91    let (characterization, written, etag, ret) = {
92        let full_payload = response.payload_mut_with_len(block2.size().into()).unwrap();
93        let writer = WindowedInfinity::new(
94            &mut full_payload[..block2.size() as usize],
95            -(block2.start() as isize),
96        );
97        let etag = crc::Crc::<u64>::new(&crc::CRC_64_ECMA_182);
98        let etag = etag.digest();
99        let mut writer = crate::embedded_io::EmbeddedIoOrFmtWrite(tee_embedded_io::Tee::new(
100            writer,
101            extra_embedded_io_adapters::WritableCrc(etag),
102        ));
103
104        let ret = f(&mut writer);
105
106        let (writer, extra_embedded_io_adapters::WritableCrc(etag)) = writer.0.into_parts();
107        let written = writer.written();
108
109        (
110            Characterization::new(writer.cursor(), written),
111            written.len(),
112            etag.finalize().to_le_bytes(),
113            ret,
114        )
115    };
116
117    response.truncate(written).unwrap();
118    if characterization == Underflow {
119        unimplemented!("Report out-of-band seek");
120    }
121
122    response.mutate_options(|optnum, value| {
123        match optnum.into() {
124            option::ETAG => {
125                value.copy_from_slice(&etag);
126            }
127            option::BLOCK2 if characterization == Overflow => {
128                // set "more" flag
129                value[value.len() - 1] |= 0x08;
130            }
131            _ => (),
132        };
133    });
134
135    ret
136}
137
138/// Wrapper around a ReadableMessage that hides the Uri-Host and Uri-Path options from view
139///
140/// This is used by a [crate::HandlerBuilder] (in particular, its path-based [crate::ForkingHandler]) to free the
141/// resources from the strange duty of skipping over a critical option they are unaware of.
142// TBD: Consider removing this in favor of MaskingUriUpToPathN -- if both are used, the flash
143// consumption of having both surely outweighs the runtime overhead of decrementing while going
144// through the options, and the length is known in advance anyway.
145pub struct MaskingUriUpToPath<'m, M: ReadableMessage>(pub &'m M);
146
147impl<M: ReadableMessage> ReadableMessage for MaskingUriUpToPath<'_, M> {
148    type Code = M::Code;
149    type MessageOption<'a>
150        = M::MessageOption<'a>
151    where
152        Self: 'a;
153    type OptionsIter<'a>
154        = MaskingUriUpToPathIter<M::OptionsIter<'a>>
155    where
156        Self: 'a;
157
158    fn options(&self) -> Self::OptionsIter<'_> {
159        MaskingUriUpToPathIter(self.0.options())
160    }
161
162    fn code(&self) -> M::Code {
163        self.0.code()
164    }
165
166    fn payload(&self) -> &[u8] {
167        self.0.payload()
168    }
169}
170
171pub struct MaskingUriUpToPathIter<I>(I);
172
173impl<MO: MessageOption, I: Iterator<Item = MO>> Iterator for MaskingUriUpToPathIter<I> {
174    type Item = MO;
175
176    fn next(&mut self) -> Option<MO> {
177        loop {
178            let result = self.0.next()?;
179            match result.number() {
180                coap_numbers::option::URI_HOST => continue,
181                coap_numbers::option::URI_PATH => continue,
182                _ => return Some(result),
183            }
184        }
185    }
186}
187
188/// Like [MaskingUriUpToPath], but only consuming a given number of Uri-Path options -- suitable
189/// for ForkingTreeHandler.
190pub(crate) struct MaskingUriUpToPathN<'m, M: ReadableMessage> {
191    message: &'m M,
192    strip_paths: usize,
193}
194
195impl<'m, M: ReadableMessage> MaskingUriUpToPathN<'m, M> {
196    pub(crate) fn new(message: &'m M, strip_paths: usize) -> Self {
197        Self {
198            message,
199            strip_paths,
200        }
201    }
202}
203
204impl<M: ReadableMessage> ReadableMessage for MaskingUriUpToPathN<'_, M> {
205    type Code = M::Code;
206    type MessageOption<'a>
207        = M::MessageOption<'a>
208    where
209        Self: 'a;
210    type OptionsIter<'a>
211        = MaskingUriUpToPathNIter<M::OptionsIter<'a>>
212    where
213        Self: 'a;
214
215    fn options(&self) -> Self::OptionsIter<'_> {
216        MaskingUriUpToPathNIter {
217            inner: self.message.options(),
218            remaining_strip: self.strip_paths,
219        }
220    }
221
222    fn code(&self) -> M::Code {
223        self.message.code()
224    }
225
226    fn payload(&self) -> &[u8] {
227        self.message.payload()
228    }
229}
230
231pub struct MaskingUriUpToPathNIter<I> {
232    inner: I,
233    remaining_strip: usize,
234}
235
236impl<MO: MessageOption, I: Iterator<Item = MO>> Iterator for MaskingUriUpToPathNIter<I> {
237    type Item = MO;
238
239    fn next(&mut self) -> Option<MO> {
240        loop {
241            let result = self.inner.next()?;
242            match result.number() {
243                coap_numbers::option::URI_HOST => continue,
244                coap_numbers::option::URI_PATH if self.remaining_strip > 0 => {
245                    self.remaining_strip -= 1;
246                    continue;
247                }
248                _ => return Some(result),
249            }
250        }
251    }
252}