1use core::fmt;
4
5use cloud_sdk_sanitization::{SecretBuffer, sanitize_bytes};
6
7use super::{PreparationStorage, PrepareOperation, PreparedRequest};
8use crate::transport::MAX_REQUEST_TARGET_BYTES;
9
10pub const EMBEDDED_BODY_BYTES: usize = 16 * 1024;
12pub const DEFAULT_BODY_BYTES: usize = 1024 * 1024;
14pub const LARGE_BODY_BYTES: usize = 8 * 1024 * 1024;
16
17#[derive(Clone, Copy, Debug, Eq, PartialEq)]
19pub struct PreparationCapacityProfile {
20 target_bytes: usize,
21 body_bytes: usize,
22}
23
24impl PreparationCapacityProfile {
25 pub const EMBEDDED: Self = Self::new(1024, EMBEDDED_BODY_BYTES);
27 pub const DEFAULT: Self = Self::new(MAX_REQUEST_TARGET_BYTES, DEFAULT_BODY_BYTES);
29 pub const LARGE: Self = Self::new(MAX_REQUEST_TARGET_BYTES, LARGE_BODY_BYTES);
31
32 const fn new(target_bytes: usize, body_bytes: usize) -> Self {
33 Self {
34 target_bytes,
35 body_bytes,
36 }
37 }
38
39 #[must_use]
41 pub const fn target_bytes(self) -> usize {
42 self.target_bytes
43 }
44
45 #[must_use]
47 pub const fn body_bytes(self) -> usize {
48 self.body_bytes
49 }
50
51 pub const fn validate(
53 self,
54 target_bytes: usize,
55 body_bytes: usize,
56 ) -> Result<(), PreparationCapacityError> {
57 if target_bytes < self.target_bytes {
58 return Err(PreparationCapacityError::TargetTooSmall);
59 }
60 if body_bytes < self.body_bytes {
61 return Err(PreparationCapacityError::BodyTooSmall);
62 }
63 Ok(())
64 }
65}
66
67#[derive(Clone, Copy, Debug, Eq, PartialEq)]
69pub enum PreparationCapacityError {
70 TargetTooSmall,
72 BodyTooSmall,
74 AllocationFailed,
76}
77
78impl_static_error!(PreparationCapacityError,
79 Self::TargetTooSmall => "preparation target storage is too small",
80 Self::BodyTooSmall => "preparation body storage is too small",
81 Self::AllocationFailed => "preparation storage allocation failed",
82);
83
84pub struct PreparationStorageGuard<'storage> {
92 target: SecretBuffer<'storage>,
93 body: SecretBuffer<'storage>,
94}
95
96impl<'storage> PreparationStorageGuard<'storage> {
97 #[must_use]
99 pub const fn new(target: &'storage mut [u8], body: &'storage mut [u8]) -> Self {
100 Self {
101 target: SecretBuffer::new(target),
102 body: SecretBuffer::new(body),
103 }
104 }
105
106 pub fn for_profile(
108 target: &'storage mut [u8],
109 body: &'storage mut [u8],
110 profile: PreparationCapacityProfile,
111 ) -> Result<Self, PreparationCapacityError> {
112 let guard = Self::new(target, body);
113 let (target_bytes, body_bytes) = guard.capacities();
114 profile.validate(target_bytes, body_bytes)?;
115 Ok(guard)
116 }
117
118 pub fn prepare<'guard, O>(
124 &'guard mut self,
125 operation: &O,
126 ) -> Result<PreparedRequest<'guard>, O::Error>
127 where
128 O: PrepareOperation,
129 {
130 sanitize_bytes(self.target.as_mut_slice());
131 sanitize_bytes(self.body.as_mut_slice());
132 operation.prepare(PreparationStorage::new(
133 self.target.as_mut_slice(),
134 self.body.as_mut_slice(),
135 ))
136 }
137
138 #[must_use]
140 pub fn capacities(&self) -> (usize, usize) {
141 (self.target.as_slice().len(), self.body.as_slice().len())
142 }
143}
144
145impl fmt::Debug for PreparationStorageGuard<'_> {
146 fn fmt(&self, formatter: &mut fmt::Formatter<'_>) -> fmt::Result {
147 let (target_capacity, body_capacity) = self.capacities();
148 formatter
149 .debug_struct("PreparationStorageGuard")
150 .field("target_capacity", &target_capacity)
151 .field("body_capacity", &body_capacity)
152 .finish_non_exhaustive()
153 }
154}
155
156#[cfg(feature = "alloc")]
158pub struct OwnedPreparationStorage {
159 target: alloc::boxed::Box<[u8]>,
160 body: alloc::boxed::Box<[u8]>,
161}
162
163#[cfg(feature = "alloc")]
164impl OwnedPreparationStorage {
165 pub fn try_for_profile(
168 profile: PreparationCapacityProfile,
169 ) -> Result<Self, PreparationCapacityError> {
170 let target = allocate_zeroed(profile.target_bytes)?;
171 let body = allocate_zeroed(profile.body_bytes)?;
172 Ok(Self { target, body })
173 }
174
175 pub fn guard(&mut self) -> PreparationStorageGuard<'_> {
177 PreparationStorageGuard::new(&mut self.target, &mut self.body)
178 }
179
180 #[must_use]
182 pub fn capacities(&self) -> (usize, usize) {
183 (self.target.len(), self.body.len())
184 }
185}
186
187#[cfg(feature = "alloc")]
188impl Drop for OwnedPreparationStorage {
189 fn drop(&mut self) {
190 cloud_sdk_sanitization::sanitize_bytes(&mut self.target);
191 cloud_sdk_sanitization::sanitize_bytes(&mut self.body);
192 }
193}
194
195#[cfg(feature = "alloc")]
196impl fmt::Debug for OwnedPreparationStorage {
197 fn fmt(&self, formatter: &mut fmt::Formatter<'_>) -> fmt::Result {
198 let (target_capacity, body_capacity) = self.capacities();
199 formatter
200 .debug_struct("OwnedPreparationStorage")
201 .field("target_capacity", &target_capacity)
202 .field("body_capacity", &body_capacity)
203 .finish_non_exhaustive()
204 }
205}
206
207#[cfg(feature = "alloc")]
208fn allocate_zeroed(len: usize) -> Result<alloc::boxed::Box<[u8]>, PreparationCapacityError> {
209 let mut bytes = alloc::vec::Vec::new();
210 bytes
211 .try_reserve_exact(len)
212 .map_err(|_| PreparationCapacityError::AllocationFailed)?;
213 bytes.resize(len, 0);
214 Ok(bytes.into_boxed_slice())
215}
216
217#[cfg(test)]
218mod tests {
219 use crate::operation::{PreparationStorage, PrepareOperation, PreparedRequest};
220
221 use super::{PreparationCapacityError, PreparationCapacityProfile, PreparationStorageGuard};
222
223 struct ContaminateStorage;
224
225 impl PrepareOperation for ContaminateStorage {
226 type Error = ();
227
228 fn prepare<'storage>(
229 &self,
230 storage: PreparationStorage<'storage>,
231 ) -> Result<PreparedRequest<'storage>, Self::Error> {
232 let (target, body) = storage.into_parts();
233 target.fill(0xA5);
234 body.fill(0x5A);
235 Err(())
236 }
237 }
238
239 struct AssertCleared;
240
241 impl PrepareOperation for AssertCleared {
242 type Error = ();
243
244 fn prepare<'storage>(
245 &self,
246 storage: PreparationStorage<'storage>,
247 ) -> Result<PreparedRequest<'storage>, Self::Error> {
248 let (target, body) = storage.into_parts();
249 assert!(target.iter().all(|byte| *byte == 0));
250 assert!(body.iter().all(|byte| *byte == 0));
251 Err(())
252 }
253 }
254
255 #[test]
256 fn profiles_are_bounded_and_validate_both_regions() {
257 assert_eq!(
258 PreparationCapacityProfile::DEFAULT.target_bytes(),
259 crate::transport::MAX_REQUEST_TARGET_BYTES
260 );
261 assert_eq!(
262 PreparationCapacityProfile::DEFAULT.validate(8191, usize::MAX),
263 Err(PreparationCapacityError::TargetTooSmall)
264 );
265 assert_eq!(
266 PreparationCapacityProfile::DEFAULT.validate(8192, 1024),
267 Err(PreparationCapacityError::BodyTooSmall)
268 );
269 }
270
271 #[test]
272 fn borrowed_guard_clears_both_complete_buffers() {
273 let mut target = [0xA5_u8; 8];
274 let mut body = [0x5A_u8; 16];
275 {
276 let guard = PreparationStorageGuard::new(&mut target, &mut body);
277 assert_eq!(guard.capacities(), (8, 16));
278 }
279 assert_eq!(target, [0; 8]);
280 assert_eq!(body, [0; 16]);
281 }
282
283 #[test]
284 fn every_preparation_attempt_clears_complete_reused_storage_first() {
285 let mut target = [0_u8; 8];
286 let mut body = [0_u8; 16];
287 let mut guard = PreparationStorageGuard::new(&mut target, &mut body);
288
289 assert!(matches!(guard.prepare(&ContaminateStorage), Err(())));
290 assert!(matches!(guard.prepare(&AssertCleared), Err(())));
291 }
292
293 #[test]
294 fn profile_validation_failures_clear_both_complete_buffers() {
295 let mut short_target = [0xA5_u8; 8];
296 let mut target_failure_body = [0x5A_u8; 16];
297 assert!(matches!(
298 PreparationStorageGuard::for_profile(
299 &mut short_target,
300 &mut target_failure_body,
301 PreparationCapacityProfile::DEFAULT,
302 ),
303 Err(PreparationCapacityError::TargetTooSmall)
304 ));
305 assert_eq!(short_target, [0; 8]);
306 assert_eq!(target_failure_body, [0; 16]);
307
308 let mut target = [0xA5_u8; crate::transport::MAX_REQUEST_TARGET_BYTES];
309 let mut short_body = [0x5A_u8; 16];
310 assert!(matches!(
311 PreparationStorageGuard::for_profile(
312 &mut target,
313 &mut short_body,
314 PreparationCapacityProfile::DEFAULT,
315 ),
316 Err(PreparationCapacityError::BodyTooSmall)
317 ));
318 assert!(target.iter().all(|byte| *byte == 0));
319 assert_eq!(short_body, [0; 16]);
320 }
321
322 #[cfg(feature = "alloc")]
323 #[test]
324 fn owned_profiles_allocate_exact_bounded_regions() {
325 let storage =
326 super::OwnedPreparationStorage::try_for_profile(PreparationCapacityProfile::EMBEDDED);
327 assert!(storage.is_ok());
328 assert_eq!(
329 storage.map(|storage| storage.capacities()),
330 Ok((1024, super::EMBEDDED_BODY_BYTES))
331 );
332 }
333
334 #[cfg(feature = "alloc")]
335 #[test]
336 fn impossible_allocation_fails_without_panicking() {
337 assert_eq!(
338 super::allocate_zeroed(usize::MAX),
339 Err(PreparationCapacityError::AllocationFailed)
340 );
341 }
342}