Skip to main content

base64_ng/v2/
append.rs

1//! Rollback-capable append operations for controlled allocation containers.
2
3use alloc::{string::String, vec::Vec};
4
5use super::{
6    contracts::BackendFault,
7    ordinary::OneShotError,
8    specifications::{Base64, Codec},
9};
10
11impl<S: Codec> Base64<S> {
12    /// Appends encoded output to `destination` transactionally.
13    ///
14    /// The existing prefix and entry length are restored on every returned
15    /// crate error and during unwinding. Capacity growth is not rolled back,
16    /// and process-aborting allocation failure is outside this guarantee.
17    /// Returns the number of appended bytes.
18    pub fn encode_append(
19        &self,
20        input: &[u8],
21        destination: &mut String,
22    ) -> Result<usize, OneShotError> {
23        self.encode_append_inner(
24            input,
25            destination,
26            |output, required| {
27                output
28                    .try_reserve_exact(required)
29                    .map_err(|_| OneShotError::AllocationFailed {
30                        requested: required,
31                    })
32            },
33            |_, _| Ok(()),
34        )
35    }
36
37    /// Appends decoded output to `destination` transactionally.
38    ///
39    /// Strict validation and exact sizing precede allocation and mutation.
40    /// The existing prefix and entry length are restored on every returned
41    /// crate error and during unwinding. Returns the appended plaintext bytes.
42    pub fn decode_append(
43        &self,
44        input: &[u8],
45        destination: &mut Vec<u8>,
46    ) -> Result<usize, OneShotError> {
47        self.decode_append_inner(
48            input,
49            destination,
50            |output, required| {
51                output
52                    .try_reserve_exact(required)
53                    .map_err(|_| OneShotError::AllocationFailed {
54                        requested: required,
55                    })
56            },
57            |_| Ok(()),
58        )
59    }
60
61    fn encode_append_inner<R, H>(
62        &self,
63        input: &[u8],
64        destination: &mut String,
65        reserve: R,
66        mut after_chunk: H,
67    ) -> Result<usize, OneShotError>
68    where
69        R: FnOnce(&mut String, usize) -> Result<(), OneShotError>,
70        H: FnMut(&mut String, usize) -> Result<(), OneShotError>,
71    {
72        let required = self.encoded_len(input.len())?;
73        destination
74            .len()
75            .checked_add(required)
76            .ok_or(OneShotError::LengthOverflow)?;
77        reserve(destination, required)?;
78
79        let mut rollback = StringRollback::new(destination);
80        for chunk in self.encoded_chunks(input)? {
81            let text = chunk
82                .as_str()
83                .map_err(|_| OneShotError::Backend(BackendFault::ImpossibleState))?;
84            rollback.destination().push_str(text);
85            after_chunk(rollback.destination(), text.len())?;
86        }
87        rollback.commit();
88        Ok(required)
89    }
90
91    fn decode_append_inner<R, H>(
92        &self,
93        input: &[u8],
94        destination: &mut Vec<u8>,
95        reserve: R,
96        after_decode: H,
97    ) -> Result<usize, OneShotError>
98    where
99        R: FnOnce(&mut Vec<u8>, usize) -> Result<(), OneShotError>,
100        H: FnOnce(&mut Vec<u8>) -> Result<(), OneShotError>,
101    {
102        let required = self.decoded_len(input)?;
103        let original_len = destination.len();
104        let total = original_len
105            .checked_add(required)
106            .ok_or(OneShotError::LengthOverflow)?;
107        reserve(destination, required)?;
108
109        let mut rollback = VecRollback::new(destination);
110        rollback.destination().resize(total, 0);
111        self.decode_into(input, &mut rollback.destination()[original_len..total])?;
112        after_decode(rollback.destination())?;
113        rollback.commit();
114        Ok(required)
115    }
116
117    #[cfg(test)]
118    pub(super) fn encode_append_with_hooks<R, H>(
119        &self,
120        input: &[u8],
121        destination: &mut String,
122        reserve: R,
123        after_chunk: H,
124    ) -> Result<usize, OneShotError>
125    where
126        R: FnOnce(&mut String, usize) -> Result<(), OneShotError>,
127        H: FnMut(&mut String, usize) -> Result<(), OneShotError>,
128    {
129        self.encode_append_inner(input, destination, reserve, after_chunk)
130    }
131
132    #[cfg(test)]
133    pub(super) fn decode_append_with_hooks<R, H>(
134        &self,
135        input: &[u8],
136        destination: &mut Vec<u8>,
137        reserve: R,
138        after_decode: H,
139    ) -> Result<usize, OneShotError>
140    where
141        R: FnOnce(&mut Vec<u8>, usize) -> Result<(), OneShotError>,
142        H: FnOnce(&mut Vec<u8>) -> Result<(), OneShotError>,
143    {
144        self.decode_append_inner(input, destination, reserve, after_decode)
145    }
146}
147
148struct StringRollback<'a> {
149    destination: &'a mut String,
150    original_len: usize,
151    committed: bool,
152}
153
154impl<'a> StringRollback<'a> {
155    fn new(destination: &'a mut String) -> Self {
156        let original_len = destination.len();
157        Self {
158            destination,
159            original_len,
160            committed: false,
161        }
162    }
163
164    fn destination(&mut self) -> &mut String {
165        self.destination
166    }
167
168    fn commit(mut self) {
169        self.committed = true;
170    }
171}
172
173impl Drop for StringRollback<'_> {
174    fn drop(&mut self) {
175        if !self.committed {
176            self.destination.truncate(self.original_len);
177        }
178    }
179}
180
181struct VecRollback<'a> {
182    destination: &'a mut Vec<u8>,
183    original_len: usize,
184    committed: bool,
185}
186
187impl<'a> VecRollback<'a> {
188    fn new(destination: &'a mut Vec<u8>) -> Self {
189        let original_len = destination.len();
190        Self {
191            destination,
192            original_len,
193            committed: false,
194        }
195    }
196
197    fn destination(&mut self) -> &mut Vec<u8> {
198        self.destination
199    }
200
201    fn commit(mut self) {
202        self.committed = true;
203    }
204}
205
206impl Drop for VecRollback<'_> {
207    fn drop(&mut self) {
208        if !self.committed {
209            self.destination.truncate(self.original_len);
210        }
211    }
212}