tor_cell/slicewriter.rs
1//! A Writer that can put its results into an buffer of known byte size without
2//! changing that size.
3//!
4//! TODO: This API is crate-private in tor-cell because tor-cell is the only one
5//! to use it -- and only uses it in one place. Its existence may be an argument
6//! for refactoring the Writer API entirely.
7//!
8//! NOTE: This will likely change or go away in the future.
9
10use thiserror::Error;
11
12use tor_bytes::Writer;
13
14/// An error that occurred while trying to unwrap a SliceWriter.
15#[non_exhaustive]
16#[derive(Clone, Debug, Error)]
17pub(crate) enum SliceWriterError {
18 /// We've tried to write more than would fit into a fixed-size slice
19 #[error("Tried to write more than would fit into a fixed-size slice.")]
20 Truncated,
21}
22
23/// An object that supports writing into a byte-slice of fixed size.
24///
25/// Since the writer API does not allow all `write_*` functions to report errors,
26/// this type defers any truncated-data errors until you try to retrieve the
27/// inner data.
28///
29//
30// TODO: in theory we could have a version of this that used MaybeUninit, but I
31// don't think that would be worth it.
32pub(crate) struct SliceWriter<T> {
33 /// The object we're writing into. Must have fewer than usize::LEN bytes.
34 data: T,
35 /// Our current write position within that object.
36 offset: usize,
37}
38
39impl<T> Writer for SliceWriter<T>
40where
41 T: AsMut<[u8]>,
42{
43 fn write_all(&mut self, b: &[u8]) {
44 let new_offset = self.offset.saturating_add(b.len());
45 if new_offset <= self.data.as_mut().len() {
46 // Note that if we reach this case, the addition was not saturating.
47 self.data.as_mut()[self.offset..new_offset].copy_from_slice(b);
48 self.offset = new_offset;
49 } else {
50 self.offset = usize::MAX;
51 }
52 }
53}
54impl<T> SliceWriter<T>
55where
56 T: AsMut<[u8]>,
57{
58 /// Move this writer ahead by `n` bytes, leaving the original data unchanged.
59 pub(crate) fn advance(&mut self, n: usize) {
60 let new_offset = self.offset.saturating_add(n);
61 if new_offset <= self.data.as_mut().len() {
62 self.offset = new_offset;
63 } else {
64 self.offset = usize::MAX;
65 }
66 }
67}
68impl<T> SliceWriter<T> {
69 /// Construct a new SliceWriter
70 ///
71 /// Typically, you would want to use this on a type that implements
72 /// `AsMut<[u8]>`, or else it won't be very useful.
73 ///
74 /// Preexisting bytes in the `data` object will be unchanged, unless you use
75 /// the [`Writer`] API to write to them.
76 pub(crate) fn new(data: T) -> Self {
77 Self { data, offset: 0 }
78 }
79
80 /// Try to extract the data from this `SliceWriter`.
81 ///
82 /// On success (if we did not write "off the end" of the underlying object),
83 /// return the object and the number of bytes we wrote into it. (Bytes
84 /// after that position are unchanged.)
85 ///
86 /// On failure (if we tried to write too much), return an error.
87 pub(crate) fn try_unwrap(self) -> Result<(T, usize), SliceWriterError> {
88 let offset = self.offset()?;
89 Ok((self.data, offset))
90 }
91
92 /// Return the number of bytes written into this `SliceWriter` so far,
93 /// or an error if it has overflowed.
94 pub(crate) fn offset(&self) -> Result<usize, SliceWriterError> {
95 if self.offset != usize::MAX {
96 Ok(self.offset)
97 } else {
98 Err(SliceWriterError::Truncated)
99 }
100 }
101
102 /// `debug_assert` that this writer is at position `n`.
103 ///
104 /// # Panics
105 ///
106 /// Panics if debugging assertions are enabled and the write has overflowed,
107 /// or is at a position other than `n`.
108 #[inline]
109 pub(crate) fn assert_offset_is(&self, n: usize) {
110 debug_assert_eq!(self.offset, n);
111 }
112}
113
114#[cfg(test)]
115mod test {
116 // @@ begin test lint list maintained by maint/add_warning @@
117 #![allow(clippy::bool_assert_comparison)]
118 #![allow(clippy::clone_on_copy)]
119 #![allow(clippy::dbg_macro)]
120 #![allow(clippy::mixed_attributes_style)]
121 #![allow(clippy::print_stderr)]
122 #![allow(clippy::print_stdout)]
123 #![allow(clippy::single_char_pattern)]
124 #![allow(clippy::unwrap_used)]
125 #![allow(clippy::unchecked_duration_subtraction)]
126 #![allow(clippy::useless_vec)]
127 #![allow(clippy::needless_pass_by_value)]
128 //! <!-- @@ end test lint list maintained by maint/add_warning @@ -->
129 use super::*;
130
131 #[test]
132 fn basics() {
133 let mut w = SliceWriter::new([0_u8; 16]);
134 w.write_u8(b'h');
135 w.write_u16(0x656c);
136 w.write_u32(0x6c6f2077);
137 w.write_all(b"orld!");
138 let (a, len) = w.try_unwrap().unwrap();
139
140 assert_eq!(a.as_ref(), b"hello world!\0\0\0\0");
141 assert_eq!(len, 12);
142 }
143
144 #[test]
145 fn full_is_ok() {
146 let mut w = SliceWriter::new([0_u8; 4]);
147 w.write_u8(1);
148 w.write_u16(0x0203);
149 w.write_u8(4);
150 let (a, len) = w.try_unwrap().unwrap();
151
152 assert_eq!(a.as_ref(), [1, 2, 3, 4]);
153 assert_eq!(len, 4);
154 }
155
156 #[test]
157 fn too_full_is_not_ok() {
158 let mut w = SliceWriter::new([0_u8; 5]);
159 w.write_u32(12);
160 w.write_u32(12);
161 assert!(matches!(w.try_unwrap(), Err(SliceWriterError::Truncated)));
162 }
163}