1#[cfg(feature = "alloc")]
4use core::fmt;
5
6use super::ClientWorkspace;
7use crate::operation::{DEFAULT_BODY_BYTES, EMBEDDED_BODY_BYTES, LARGE_BODY_BYTES};
8use crate::transport::{MAX_REQUEST_TARGET_BYTES, MAX_RESPONSE_HEADER_BYTES};
9
10pub const EMBEDDED_RESPONSE_BYTES: usize = 64 * 1024;
12pub const DEFAULT_RESPONSE_BYTES: usize = 1024 * 1024;
14pub const LARGE_RESPONSE_BYTES: usize = 8 * 1024 * 1024;
16
17#[derive(Clone, Copy, Debug, Eq, PartialEq)]
19pub struct ClientCapacityProfile {
20 target_bytes: usize,
21 request_body_bytes: usize,
22 response_body_bytes: usize,
23 response_header_bytes: usize,
24}
25
26impl ClientCapacityProfile {
27 pub const EMBEDDED: Self = Self::new(
29 1024,
30 EMBEDDED_BODY_BYTES,
31 EMBEDDED_RESPONSE_BYTES,
32 MAX_RESPONSE_HEADER_BYTES,
33 );
34 pub const DEFAULT: Self = Self::new(
36 MAX_REQUEST_TARGET_BYTES,
37 DEFAULT_BODY_BYTES,
38 DEFAULT_RESPONSE_BYTES,
39 MAX_RESPONSE_HEADER_BYTES,
40 );
41 pub const LARGE: Self = Self::new(
43 MAX_REQUEST_TARGET_BYTES,
44 LARGE_BODY_BYTES,
45 LARGE_RESPONSE_BYTES,
46 MAX_RESPONSE_HEADER_BYTES,
47 );
48
49 const fn new(
50 target_bytes: usize,
51 request_body_bytes: usize,
52 response_body_bytes: usize,
53 response_header_bytes: usize,
54 ) -> Self {
55 Self {
56 target_bytes,
57 request_body_bytes,
58 response_body_bytes,
59 response_header_bytes,
60 }
61 }
62
63 #[must_use]
65 pub const fn target_bytes(self) -> usize {
66 self.target_bytes
67 }
68
69 #[must_use]
71 pub const fn request_body_bytes(self) -> usize {
72 self.request_body_bytes
73 }
74
75 #[must_use]
77 pub const fn response_body_bytes(self) -> usize {
78 self.response_body_bytes
79 }
80
81 #[must_use]
83 pub const fn response_header_bytes(self) -> usize {
84 self.response_header_bytes
85 }
86
87 pub const fn validate(
89 self,
90 target_bytes: usize,
91 request_body_bytes: usize,
92 response_body_bytes: usize,
93 response_header_bytes: usize,
94 ) -> Result<(), ClientCapacityError> {
95 if target_bytes < self.target_bytes {
96 return Err(ClientCapacityError::TargetTooSmall);
97 }
98 if request_body_bytes < self.request_body_bytes {
99 return Err(ClientCapacityError::RequestBodyTooSmall);
100 }
101 if response_body_bytes < self.response_body_bytes {
102 return Err(ClientCapacityError::ResponseBodyTooSmall);
103 }
104 if response_header_bytes < self.response_header_bytes {
105 return Err(ClientCapacityError::ResponseHeadersTooSmall);
106 }
107 Ok(())
108 }
109}
110
111#[derive(Clone, Copy, Debug, Eq, PartialEq)]
113pub enum ClientCapacityError {
114 TargetTooSmall,
116 RequestBodyTooSmall,
118 ResponseBodyTooSmall,
120 ResponseHeadersTooSmall,
122 AllocationFailed,
124}
125
126impl_static_error!(ClientCapacityError,
127 Self::TargetTooSmall => "client target storage is too small",
128 Self::RequestBodyTooSmall => "client request-body storage is too small",
129 Self::ResponseBodyTooSmall => "client response-body storage is too small",
130 Self::ResponseHeadersTooSmall => "client response-header storage is too small",
131 Self::AllocationFailed => "client workspace allocation failed",
132);
133
134impl<'storage> ClientWorkspace<'storage> {
135 pub fn for_profile(
137 target: &'storage mut [u8],
138 request_body: &'storage mut [u8],
139 response_body: &'storage mut [u8],
140 response_headers: &'storage mut [u8],
141 profile: ClientCapacityProfile,
142 ) -> Result<Self, ClientCapacityError> {
143 let workspace = Self::new(target, request_body, response_body, response_headers);
144 let (target, request, response, headers) = workspace.capacities();
145 profile.validate(target, request, response, headers)?;
146 Ok(workspace)
147 }
148}
149
150#[cfg(feature = "alloc")]
152pub struct OwnedClientWorkspace {
153 target: alloc::boxed::Box<[u8]>,
154 request_body: alloc::boxed::Box<[u8]>,
155 response_body: alloc::boxed::Box<[u8]>,
156 response_headers: alloc::boxed::Box<[u8]>,
157}
158
159#[cfg(feature = "alloc")]
160impl OwnedClientWorkspace {
161 pub fn try_for_profile(profile: ClientCapacityProfile) -> Result<Self, ClientCapacityError> {
163 Ok(Self {
164 target: allocate_zeroed(profile.target_bytes)?,
165 request_body: allocate_zeroed(profile.request_body_bytes)?,
166 response_body: allocate_zeroed(profile.response_body_bytes)?,
167 response_headers: allocate_zeroed(profile.response_header_bytes)?,
168 })
169 }
170
171 pub fn workspace(&mut self) -> ClientWorkspace<'_> {
173 ClientWorkspace::new(
174 &mut self.target,
175 &mut self.request_body,
176 &mut self.response_body,
177 &mut self.response_headers,
178 )
179 }
180
181 #[must_use]
183 pub fn capacities(&self) -> (usize, usize, usize, usize) {
184 (
185 self.target.len(),
186 self.request_body.len(),
187 self.response_body.len(),
188 self.response_headers.len(),
189 )
190 }
191}
192
193#[cfg(feature = "alloc")]
194impl Drop for OwnedClientWorkspace {
195 fn drop(&mut self) {
196 cloud_sdk_sanitization::sanitize_bytes(&mut self.target);
197 cloud_sdk_sanitization::sanitize_bytes(&mut self.request_body);
198 cloud_sdk_sanitization::sanitize_bytes(&mut self.response_body);
199 cloud_sdk_sanitization::sanitize_bytes(&mut self.response_headers);
200 }
201}
202
203#[cfg(feature = "alloc")]
204impl fmt::Debug for OwnedClientWorkspace {
205 fn fmt(&self, formatter: &mut fmt::Formatter<'_>) -> fmt::Result {
206 formatter
207 .debug_struct("OwnedClientWorkspace")
208 .field("capacities", &self.capacities())
209 .finish_non_exhaustive()
210 }
211}
212
213#[cfg(feature = "alloc")]
214fn allocate_zeroed(len: usize) -> Result<alloc::boxed::Box<[u8]>, ClientCapacityError> {
215 let mut bytes = alloc::vec::Vec::new();
216 bytes
217 .try_reserve_exact(len)
218 .map_err(|_| ClientCapacityError::AllocationFailed)?;
219 bytes.resize(len, 0);
220 Ok(bytes.into_boxed_slice())
221}
222
223#[cfg(test)]
224mod tests {
225 use super::{ClientCapacityError, ClientCapacityProfile};
226 use crate::client::ClientWorkspace;
227
228 #[test]
229 fn profiles_validate_every_region_at_exact_bounds() {
230 let profile = ClientCapacityProfile::DEFAULT;
231 assert_eq!(
232 profile.validate(
233 profile.target_bytes(),
234 profile.request_body_bytes(),
235 profile.response_body_bytes(),
236 profile.response_header_bytes(),
237 ),
238 Ok(())
239 );
240 assert_eq!(
241 profile.validate(
242 profile.target_bytes(),
243 profile.request_body_bytes(),
244 profile.response_body_bytes() - 1,
245 usize::MAX,
246 ),
247 Err(ClientCapacityError::ResponseBodyTooSmall)
248 );
249 assert_eq!(
250 profile.validate(usize::MAX, usize::MAX, usize::MAX, 0),
251 Err(ClientCapacityError::ResponseHeadersTooSmall)
252 );
253 }
254
255 #[test]
256 fn rejected_profile_clears_all_borrowed_regions() {
257 let mut target = [0x11; 8];
258 let mut request = [0x22; 8];
259 let mut response = [0x33; 8];
260 let mut headers = [0x44; 8];
261 assert!(matches!(
262 ClientWorkspace::for_profile(
263 &mut target,
264 &mut request,
265 &mut response,
266 &mut headers,
267 ClientCapacityProfile::DEFAULT,
268 ),
269 Err(ClientCapacityError::TargetTooSmall)
270 ));
271 assert_eq!(target, [0; 8]);
272 assert_eq!(request, [0; 8]);
273 assert_eq!(response, [0; 8]);
274 assert_eq!(headers, [0; 8]);
275 }
276
277 #[cfg(feature = "alloc")]
278 #[test]
279 fn owned_workspace_allocates_the_exact_selected_profile() {
280 let profile = ClientCapacityProfile::EMBEDDED;
281 let workspace = super::OwnedClientWorkspace::try_for_profile(profile);
282 assert!(workspace.is_ok());
283 let Ok(workspace) = workspace else {
284 unreachable!("bounded client workspace allocation failed")
285 };
286 assert_eq!(
287 workspace.capacities(),
288 (
289 profile.target_bytes(),
290 profile.request_body_bytes(),
291 profile.response_body_bytes(),
292 profile.response_header_bytes(),
293 )
294 );
295 }
296}