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 pub fn prepare_with<'guard, T, E, F>(&'guard mut self, prepare: F) -> Result<T, E>
144 where
145 F: FnOnce(PreparationStorage<'guard>) -> Result<T, E>,
146 {
147 sanitize_bytes(self.target.as_mut_slice());
148 sanitize_bytes(self.body.as_mut_slice());
149 prepare(PreparationStorage::new(
150 self.target.as_mut_slice(),
151 self.body.as_mut_slice(),
152 ))
153 }
154
155 #[must_use]
157 pub fn capacities(&self) -> (usize, usize) {
158 (self.target.as_slice().len(), self.body.as_slice().len())
159 }
160}
161
162impl fmt::Debug for PreparationStorageGuard<'_> {
163 fn fmt(&self, formatter: &mut fmt::Formatter<'_>) -> fmt::Result {
164 let (target_capacity, body_capacity) = self.capacities();
165 formatter
166 .debug_struct("PreparationStorageGuard")
167 .field("target_capacity", &target_capacity)
168 .field("body_capacity", &body_capacity)
169 .finish_non_exhaustive()
170 }
171}
172
173#[cfg(feature = "alloc")]
175pub struct OwnedPreparationStorage {
176 target: alloc::boxed::Box<[u8]>,
177 body: alloc::boxed::Box<[u8]>,
178}
179
180#[cfg(feature = "alloc")]
181impl OwnedPreparationStorage {
182 pub fn try_for_profile(
185 profile: PreparationCapacityProfile,
186 ) -> Result<Self, PreparationCapacityError> {
187 let target = allocate_zeroed(profile.target_bytes)?;
188 let body = allocate_zeroed(profile.body_bytes)?;
189 Ok(Self { target, body })
190 }
191
192 pub fn guard(&mut self) -> PreparationStorageGuard<'_> {
194 PreparationStorageGuard::new(&mut self.target, &mut self.body)
195 }
196
197 #[must_use]
199 pub fn capacities(&self) -> (usize, usize) {
200 (self.target.len(), self.body.len())
201 }
202}
203
204#[cfg(feature = "alloc")]
205impl Drop for OwnedPreparationStorage {
206 fn drop(&mut self) {
207 cloud_sdk_sanitization::sanitize_bytes(&mut self.target);
208 cloud_sdk_sanitization::sanitize_bytes(&mut self.body);
209 }
210}
211
212#[cfg(feature = "alloc")]
213impl fmt::Debug for OwnedPreparationStorage {
214 fn fmt(&self, formatter: &mut fmt::Formatter<'_>) -> fmt::Result {
215 let (target_capacity, body_capacity) = self.capacities();
216 formatter
217 .debug_struct("OwnedPreparationStorage")
218 .field("target_capacity", &target_capacity)
219 .field("body_capacity", &body_capacity)
220 .finish_non_exhaustive()
221 }
222}
223
224#[cfg(feature = "alloc")]
225fn allocate_zeroed(len: usize) -> Result<alloc::boxed::Box<[u8]>, PreparationCapacityError> {
226 let mut bytes = alloc::vec::Vec::new();
227 bytes
228 .try_reserve_exact(len)
229 .map_err(|_| PreparationCapacityError::AllocationFailed)?;
230 bytes.resize(len, 0);
231 Ok(bytes.into_boxed_slice())
232}
233
234#[cfg(test)]
235mod tests {
236 use crate::operation::{PreparationStorage, PrepareOperation, PreparedRequest};
237
238 use super::{PreparationCapacityError, PreparationCapacityProfile, PreparationStorageGuard};
239
240 struct ContaminateStorage;
241
242 impl PrepareOperation for ContaminateStorage {
243 type Error = ();
244
245 fn prepare<'storage>(
246 &self,
247 storage: PreparationStorage<'storage>,
248 ) -> Result<PreparedRequest<'storage>, Self::Error> {
249 let (target, body) = storage.into_parts();
250 target.fill(0xA5);
251 body.fill(0x5A);
252 Err(())
253 }
254 }
255
256 struct AssertCleared;
257
258 impl PrepareOperation for AssertCleared {
259 type Error = ();
260
261 fn prepare<'storage>(
262 &self,
263 storage: PreparationStorage<'storage>,
264 ) -> Result<PreparedRequest<'storage>, Self::Error> {
265 let (target, body) = storage.into_parts();
266 assert!(target.iter().all(|byte| *byte == 0));
267 assert!(body.iter().all(|byte| *byte == 0));
268 Err(())
269 }
270 }
271
272 #[test]
273 fn profiles_are_bounded_and_validate_both_regions() {
274 assert_eq!(
275 PreparationCapacityProfile::DEFAULT.target_bytes(),
276 crate::transport::MAX_REQUEST_TARGET_BYTES
277 );
278 assert_eq!(
279 PreparationCapacityProfile::DEFAULT.validate(8191, usize::MAX),
280 Err(PreparationCapacityError::TargetTooSmall)
281 );
282 assert_eq!(
283 PreparationCapacityProfile::DEFAULT.validate(8192, 1024),
284 Err(PreparationCapacityError::BodyTooSmall)
285 );
286 }
287
288 #[test]
289 fn borrowed_guard_clears_both_complete_buffers() {
290 let mut target = [0xA5_u8; 8];
291 let mut body = [0x5A_u8; 16];
292 {
293 let guard = PreparationStorageGuard::new(&mut target, &mut body);
294 assert_eq!(guard.capacities(), (8, 16));
295 }
296 assert_eq!(target, [0; 8]);
297 assert_eq!(body, [0; 16]);
298 }
299
300 #[test]
301 fn every_preparation_attempt_clears_complete_reused_storage_first() {
302 let mut target = [0_u8; 8];
303 let mut body = [0_u8; 16];
304 let mut guard = PreparationStorageGuard::new(&mut target, &mut body);
305
306 assert!(matches!(guard.prepare(&ContaminateStorage), Err(())));
307 assert!(matches!(guard.prepare(&AssertCleared), Err(())));
308 }
309
310 #[test]
311 fn provider_specific_preparation_retains_cleanup_ownership() {
312 let mut target = [0xA5_u8; 8];
313 let mut body = [0x5A_u8; 16];
314 {
315 let mut guard = PreparationStorageGuard::new(&mut target, &mut body);
316 let result: Result<(), ()> = guard.prepare_with(|storage| {
317 let (target, body) = storage.into_parts();
318 assert!(target.iter().all(|byte| *byte == 0));
319 assert!(body.iter().all(|byte| *byte == 0));
320 target.fill(0x11);
321 body.fill(0x22);
322 Err(())
323 });
324 assert_eq!(result, Err(()));
325 }
326 assert_eq!(target, [0; 8]);
327 assert_eq!(body, [0; 16]);
328 }
329
330 #[test]
331 fn profile_validation_failures_clear_both_complete_buffers() {
332 let mut short_target = [0xA5_u8; 8];
333 let mut target_failure_body = [0x5A_u8; 16];
334 assert!(matches!(
335 PreparationStorageGuard::for_profile(
336 &mut short_target,
337 &mut target_failure_body,
338 PreparationCapacityProfile::DEFAULT,
339 ),
340 Err(PreparationCapacityError::TargetTooSmall)
341 ));
342 assert_eq!(short_target, [0; 8]);
343 assert_eq!(target_failure_body, [0; 16]);
344
345 let mut target = [0xA5_u8; crate::transport::MAX_REQUEST_TARGET_BYTES];
346 let mut short_body = [0x5A_u8; 16];
347 assert!(matches!(
348 PreparationStorageGuard::for_profile(
349 &mut target,
350 &mut short_body,
351 PreparationCapacityProfile::DEFAULT,
352 ),
353 Err(PreparationCapacityError::BodyTooSmall)
354 ));
355 assert!(target.iter().all(|byte| *byte == 0));
356 assert_eq!(short_body, [0; 16]);
357 }
358
359 #[cfg(feature = "alloc")]
360 #[test]
361 fn owned_profiles_allocate_exact_bounded_regions() {
362 let storage =
363 super::OwnedPreparationStorage::try_for_profile(PreparationCapacityProfile::EMBEDDED);
364 assert!(storage.is_ok());
365 assert_eq!(
366 storage.map(|storage| storage.capacities()),
367 Ok((1024, super::EMBEDDED_BODY_BYTES))
368 );
369 }
370
371 #[cfg(feature = "alloc")]
372 #[test]
373 fn impossible_allocation_fails_without_panicking() {
374 assert_eq!(
375 super::allocate_zeroed(usize::MAX),
376 Err(PreparationCapacityError::AllocationFailed)
377 );
378 }
379}