windowed_infinity/
lib.rs

1#![cfg_attr(not(feature = "std"), no_std)]
2//! This crate provides the [WindowedInfinity] struct and implementations of various traits to
3//! write to it.
4//!
5//! Its primary purpose is to wrap a small buffer such that writes to it advance a cursor over a larger
6//! imaginary buffer, only persisting writes to the small buffer. After the buffer has been
7//! processed, a new WindowedInfinity can be set up and the writing process repeated. This is
8//! wasteful when the writes are computationally expensive, but convenient when operations only
9//! rarely exceed the buffer.
10//!
11//! A typical practical example of a WindowedInfinity application is the implementation of CoAP
12//! block-wise transfer according to [RFC7959](https://tools.ietf.org/html/rfc7959); a simpler
13//! example is available in the `demo.rs` example.
14//!
15//! Features
16//! --------
17//!
18//! The set of traits implemented by [WindowedInfinity] depends on the configured cargo features:
19//!
20//! * With the `std` feature, it implements [`std::io::Write`]
21//! * With the `with_serde_cbor` feature, it uses [`serde_cbor`]'s trait unsealing feature to
22//!   implement its [`Write`][serde_cbor::ser::Write] trait.
23//! * Likewise, there are features for ciborium and minicbor. The minicbor version is a bit special
24//!   in that there is both a `with_minicbor` / `with_minicbor_0_19` feature.
25//! * Starting at `with_minicbor_0_19`, features carry a version. This allows users of different
26//!   minicbor versions to coexist in the same crate, and moreover ensures that the dependencies
27//!   expressed in the Cargo.toml files to describe the requirements precisely.
28//! * With the `with_embedded_io_0_4`, `_0_6` and `_async_0_6` features, the Write trait of
29//!   `embedded-io` / `-async` is implemented.
30//!
31//! Crate size
32//! ----------
33//!
34//! Compared to the original plan of "doing one thing, and doing that right", this crate has grown
35//! a bit, in that it contains trait implementations for several serializers, and extra mechanism
36//! for combining the own writer with others (from cryptographic digests or CRCs). Both these are
37//! temporary -- once there is a 1.0 version of embedded-io, the Tee will be split out into a
38//! dedicated crate (with only compatibility re-exports and constructors / destructors remaining),
39//! and once serializers start using the stable embedded-io, no more writer implementations will
40//! need to be added.
41
42mod tee;
43mod wrappers;
44
45/// Return type for [`WindowedInfinity::tee_digest()`]
46///
47/// This implements all the same writers as [`WindowedInfinity`], and can be destructured
48/// `.into_windowed_and_digest(self) -> (WindowedInfinity, D)`.
49pub type TeeForDigest<'a, D> = tee::Tee<WindowedInfinity<'a>, wrappers::WritableDigest<D>>;
50/// Return type for [`WindowedInfinity::tee_crc32()`] and 64
51///
52/// This implements all the same writers as [`WindowedInfinity`], and can be destructured
53/// `.into_windowed_and_crc(self) -> (WindowedInfinity, crc::Digest<'c, W>)`.
54pub type TeeForCrc<'a, 'c, W> = tee::Tee<WindowedInfinity<'a>, wrappers::WritableCrc<'c, W>>;
55
56/// A local trait standing in for embedded_io::blocking::Write while we don't have a stable version
57/// to depend on and re-export publicly
58///
59/// The semantics of write are those of the write_all of embedded_io; the error would be in a
60/// separate trait there.
61trait MyWrite {
62    type Error;
63    fn write(&mut self, buf: &[u8]) -> Result<(), Self::Error>;
64    // Present even if not called because this should migrate to embedded_io which has that.
65    #[allow(dead_code)]
66    fn flush(&mut self) -> Result<(), Self::Error>;
67}
68
69/// A WindowedInfinity represents an infinite writable space. A small section of it is mapped to a
70/// &mut [u8] to which writes are forwarded; writes to the area outside only advance a cursor.
71pub struct WindowedInfinity<'a> {
72    view: &'a mut [u8],
73    cursor: isize,
74}
75
76impl<'a> WindowedInfinity<'a> {
77    /// Create a new infinity with the window passed as view. The cursor parameter indicates where
78    /// (in the index space of the view) the infinity's write operations should start, and is
79    /// typically either 0 or negative.
80    pub fn new(view: &'a mut [u8], cursor: isize) -> Self {
81        WindowedInfinity { view, cursor }
82    }
83
84    /// Report the current write cursor position in the index space of the view.
85    ///
86    /// This typically used at the end of an infinity's life time to see whether the view needs to
87    /// be truncated before further processing, and whether there was any data discarded after the
88    /// view.
89    pub fn cursor(&self) -> isize {
90        self.cursor
91    }
92
93    #[deprecated(note = "Renamed to .cursor()")]
94    pub fn get_cursor(&self) -> isize {
95        self.cursor()
96    }
97
98    /// At the current cursor position, insert the given data.
99    ///
100    /// The operation is always successful, and at least changes the write cursor.
101    pub fn write(&mut self, data: &[u8]) {
102        let start = self.cursor;
103        // FIXME determine overflowing and wrapping behavior
104        self.cursor += data.len() as isize;
105        let end = self.cursor;
106
107        if end <= 0 {
108            // Not in view yet
109            return;
110        }
111
112        if start >= self.view.len() as isize {
113            // Already out of view
114            return;
115        }
116
117        #[rustfmt::skip]
118        let (fronttrim, start) = if start < 0 {
119            (-start, 0)
120        } else {
121            (0, start)
122        };
123        let data = &data[fronttrim as usize..];
124
125        let overshoot = start + data.len() as isize - self.view.len() as isize;
126        let (tailtrim, end) = if overshoot > 0 {
127            (overshoot, end - overshoot)
128        } else {
129            (0, end)
130        };
131        let data = &data[..data.len() - tailtrim as usize];
132        self.view[start as usize..end as usize].copy_from_slice(data);
133    }
134
135    /// Obtain the written content inside the window, if any.
136    ///
137    /// The slices could be made to have a longer lifetime if there is demand for that by using the
138    /// `sealingslice` crate.
139    pub fn written(&self) -> &[u8] {
140        if self.cursor > 0 {
141            // The unwrap_or case is only triggered in the pathological zero-length-view case.
142            self.view.chunks(self.cursor as usize).next().unwrap_or(&[])
143        } else {
144            &[]
145        }
146    }
147
148    #[deprecated(note = "Renamed to .written()")]
149    pub fn get_written(&self) -> &[u8] {
150        self.written()
151    }
152
153    /// Create a Tee (a T-shaped writer) that writes both to this WindowedInfinity and some
154    /// [digest::Digest].
155    ///
156    /// The resulting type implements all the same writers as the WindowedInfinity, and offers an
157    /// `into_windowed_and_digest(self) -> (WindowedInfinity, Digest)` to get both back after
158    /// writing as completed.
159    pub fn tee_digest<D: digest::Digest>(self) -> TeeForDigest<'a, D> {
160        tee::Tee {
161            w1: self,
162            w2: wrappers::WritableDigest(D::new()),
163        }
164    }
165
166    /// Create a Tee (a T-shaped writer) that writes both to this WindowedInfinity and some
167    /// [crc::Digest].
168    ///
169    /// This is limited to u64 CRCs due to <https://github.com/mrhooray/crc-rs/issues/79>, and
170    /// indirectly the availability of const traits.
171    pub fn tee_crc64<'c>(self, crc: &'c crc::Crc<u64>) -> TeeForCrc<'a, 'c, u64> {
172        tee::Tee {
173            w1: self,
174            w2: wrappers::WritableCrc(crc.digest()),
175        }
176    }
177
178    /// Create a Tee (a T-shaped writer) that writes both to this WindowedInfinity and some
179    /// [crc::Digest].
180    ///
181    /// This is limited to u32 CRCs due to <https://github.com/mrhooray/crc-rs/issues/79>, and
182    /// indirectly the availability of const traits.
183    pub fn tee_crc32<'c>(self, crc: &'c crc::Crc<u32>) -> TeeForCrc<'a, 'c, u32> {
184        tee::Tee {
185            w1: self,
186            w2: wrappers::WritableCrc(crc.digest()),
187        }
188    }
189}
190
191impl MyWrite for WindowedInfinity<'_> {
192    type Error = core::convert::Infallible;
193
194    fn write(&mut self, buf: &[u8]) -> Result<(), Self::Error> {
195        self.write(buf);
196        Ok(())
197    }
198
199    fn flush(&mut self) -> Result<(), Self::Error> {
200        Ok(())
201    }
202}
203
204#[cfg(feature = "std")]
205impl std::io::Write for WindowedInfinity<'_> {
206    fn write(&mut self, buf: &[u8]) -> std::io::Result<usize> {
207        self.write(buf);
208        // As far as success is concerned, everything was written; that not all of it (or none of
209        // it) may have made its way to memory is immaterial.
210        Ok(buf.len())
211    }
212
213    fn flush(&mut self) -> std::io::Result<()> {
214        Ok(())
215    }
216}
217
218impl core::fmt::Write for WindowedInfinity<'_> {
219    fn write_str(&mut self, s: &str) -> core::fmt::Result {
220        self.write(s.as_bytes());
221        Ok(())
222    }
223}
224
225#[cfg(feature = "serde_cbor")]
226impl serde_cbor::ser::Write for WindowedInfinity<'_> {
227    // To be changed to ! once that's stable and implements Into-all
228    type Error = serde_cbor::error::Error;
229
230    fn write_all(&mut self, buf: &[u8]) -> Result<(), serde_cbor::error::Error> {
231        self.write(buf);
232        Ok(())
233    }
234}
235
236#[allow(unused_macros)] // may be used depending on features enabled
237macro_rules! impl_minicbor_write_for_windowedinfinity {
238    ($c:ident) => {
239        impl<'a> $c::encode::Write for WindowedInfinity<'a> {
240            type Error = core::convert::Infallible;
241
242            fn write_all(&mut self, buf: &[u8]) -> Result<(), Self::Error> {
243                self.write(buf);
244                Ok(())
245            }
246        }
247    };
248}
249
250#[cfg(feature = "with_minicbor")]
251impl_minicbor_write_for_windowedinfinity!(minicbor);
252#[cfg(feature = "with_minicbor_0_19")]
253impl_minicbor_write_for_windowedinfinity!(minicbor_0_19);
254#[cfg(feature = "with_minicbor_0_24")]
255impl_minicbor_write_for_windowedinfinity!(minicbor_0_24);
256#[cfg(feature = "with_minicbor_0_25")]
257impl_minicbor_write_for_windowedinfinity!(minicbor_0_25);
258#[cfg(feature = "with_minicbor_0_26")]
259impl_minicbor_write_for_windowedinfinity!(minicbor_0_26);
260
261#[cfg(feature = "with_ciborium")]
262impl ciborium_io::Write for WindowedInfinity<'_> {
263    type Error = core::convert::Infallible;
264
265    fn write_all(&mut self, buf: &[u8]) -> Result<(), Self::Error> {
266        self.write(buf);
267        Ok(())
268    }
269
270    fn flush(&mut self) -> Result<(), Self::Error> {
271        Ok(())
272    }
273}
274
275#[cfg(feature = "with_embedded_io_0_4")]
276impl embedded_io::Io for WindowedInfinity<'_> {
277    type Error = core::convert::Infallible;
278}
279
280#[cfg(feature = "with_embedded_io_0_4")]
281impl embedded_io::blocking::Write for WindowedInfinity<'_> {
282    fn write(&mut self, buf: &[u8]) -> Result<usize, Self::Error> {
283        self.write(buf);
284
285        Ok(buf.len())
286    }
287
288    fn flush(&mut self) -> Result<(), Self::Error> {
289        Ok(())
290    }
291}
292
293// Shared between embedded-io 0.6 and embedded-io-async 0.6 as the latter uses a re-export
294#[cfg(feature = "embedded_io_0_6")]
295impl embedded_io_0_6::ErrorType for WindowedInfinity<'_> {
296    type Error = core::convert::Infallible;
297}
298
299#[cfg(feature = "with_embedded_io_0_6")]
300impl embedded_io_0_6::Write for WindowedInfinity<'_> {
301    fn write(&mut self, buf: &[u8]) -> Result<usize, Self::Error> {
302        self.write(buf);
303
304        Ok(buf.len())
305    }
306
307    fn flush(&mut self) -> Result<(), Self::Error> {
308        Ok(())
309    }
310}
311
312#[cfg(feature = "with_embedded_io_async_0_6")]
313impl embedded_io_async_0_6::Write for WindowedInfinity<'_> {
314    async fn write(&mut self, buf: &[u8]) -> Result<usize, core::convert::Infallible> {
315        self.write(buf);
316
317        Ok(buf.len())
318    }
319}
320
321#[cfg(test)]
322mod tests {
323    use super::WindowedInfinity;
324
325    #[test]
326    fn zero_length() {
327        let mut data: [u8; 0] = [];
328        let mut writer = WindowedInfinity::new(&mut data, -10);
329        writer.write(&[42; 20]);
330        assert_eq!(writer.cursor(), 10);
331        assert_eq!(writer.written(), &[]);
332    }
333
334    #[test]
335    fn single_write() {
336        let mut data: [u8; 5] = [0; 5];
337        let mut writer = WindowedInfinity::new(&mut data, -10);
338        writer.write(&[42; 20]);
339        assert_eq!(writer.cursor(), 10);
340        assert_eq!(writer.written(), &[42; 5]);
341        assert_eq!(data, [42; 5]);
342    }
343
344    #[test]
345    fn small_chunks() {
346        let mut data: [u8; 5] = [0; 5];
347        let mut writer = WindowedInfinity::new(&mut data, -10);
348        for i in 0..10 {
349            writer.write(&[i as u8; 2]);
350            assert_eq!(writer.cursor(), -10 + (i + 1) * 2);
351            if i == 5 {
352                assert_eq!(writer.written(), &[5; 2]);
353            }
354        }
355        assert_eq!(writer.written(), [5, 5, 6, 6, 7]);
356        assert_eq!(data, [5, 5, 6, 6, 7]);
357    }
358
359    #[cfg(feature = "std")]
360    #[test]
361    fn single_write_std() {
362        let mut data: [u8; 5] = [0; 5];
363        let mut writer = WindowedInfinity::new(&mut data, -10);
364        std::io::Write::write(&mut writer, &[42; 20]).unwrap();
365        assert_eq!(writer.cursor(), 10);
366    }
367
368    #[cfg(feature = "with_serde_cbor")]
369    #[test]
370    fn single_write_cbor() {
371        use serde::ser::Serialize;
372
373        let mut data: [u8; 5] = [0; 5];
374        let mut writer = WindowedInfinity::new(&mut data, -10);
375        let cbordata = ["Hello World"];
376        cbordata
377            .serialize(&mut serde_cbor::ser::Serializer::new(&mut writer))
378            .unwrap();
379        assert_eq!(writer.cursor(), 3);
380    }
381
382    #[allow(unused_macros)] // may be used depending on features enabled
383    macro_rules! single_write_minicbor {
384        ($f:ident, $c:ident) => {
385            #[test]
386            fn $f() {
387                let mut data: [u8; 5] = [0; 5];
388                let mut writer = WindowedInfinity::new(&mut data, -10);
389                $c::encode(["Hello World"], &mut writer).unwrap();
390                assert_eq!(writer.cursor(), 3);
391            }
392        };
393    }
394
395    #[cfg(feature = "with_minicbor")]
396    single_write_minicbor!(single_write_minicbor, minicbor);
397    #[cfg(feature = "with_minicbor_0_19")]
398    single_write_minicbor!(single_write_minicbor_0_19, minicbor_0_19);
399    #[cfg(feature = "with_minicbor_0_24")]
400    single_write_minicbor!(single_write_minicbor_0_24, minicbor_0_24);
401    #[cfg(feature = "with_minicbor_0_25")]
402    single_write_minicbor!(single_write_minicbor_0_25, minicbor_0_25);
403    #[cfg(feature = "with_minicbor_0_26")]
404    single_write_minicbor!(single_write_minicbor_0_26, minicbor_0_26);
405
406    #[cfg(feature = "with_ciborium")]
407    #[test]
408    fn single_write_cborium() {
409        use ciborium_ll;
410
411        let mut data: [u8; 5] = [0; 5];
412        let mut writer = WindowedInfinity::new(&mut data, -10);
413        let mut encoder = ciborium_ll::Encoder::from(&mut writer);
414        encoder.push(ciborium_ll::Header::Array(Some(1))).unwrap();
415        encoder.text("Hello World", None).unwrap();
416        assert_eq!(writer.cursor(), 3);
417    }
418
419    #[cfg(feature = "with_embedded_io_0_4")]
420    #[test]
421    fn write_embedded_io_blocking() {
422        use embedded_io::blocking::Write;
423
424        let mut data: [u8; 5] = [0; 5];
425        let mut writer = WindowedInfinity::new(&mut data, -10);
426        writer.write_all(b"Hello ").unwrap();
427        writer.flush().unwrap();
428
429        writer.write_fmt(format_args!("Worl{}", "d")).unwrap();
430        // It's 3 in other places b/c they encode a CBOR array and a string header, and we're just
431        // writing the text.
432        assert_eq!(writer.cursor(), 1);
433    }
434
435    #[cfg(feature = "with_embedded_io_0_6")]
436    #[test]
437    fn write_embedded_io_0_6() {
438        use embedded_io_0_6::Write;
439
440        let mut data: [u8; 5] = [0; 5];
441        let mut writer = WindowedInfinity::new(&mut data, -10);
442        writer.write_all(b"Hello ").unwrap();
443        writer.flush().unwrap();
444
445        writer.write_fmt(format_args!("Worl{}", "d")).unwrap();
446        // It's 3 in other places b/c they encode a CBOR array and a string header, and we're just
447        // writing the text.
448        assert_eq!(writer.cursor(), 1);
449    }
450
451    #[cfg(feature = "with_embedded_io_async_0_6")]
452    #[tokio::test]
453    async fn write_embedded_io_async_0_6() {
454        use embedded_io_async_0_6::Write;
455
456        let mut data: [u8; 5] = [0; 5];
457        let mut writer = WindowedInfinity::new(&mut data, -10);
458        writer.write_all(b"Hello World").await.unwrap();
459        writer.flush().await.unwrap();
460
461        // It's 3 in other places b/c they encode a CBOR array and a string header, and we're just
462        // writing the text.
463        assert_eq!(writer.cursor(), 1);
464    }
465}