cloud_sdk/transport/header/
response.rs1use core::fmt;
2
3use cloud_sdk_sanitization::{SecretBuffer, sanitize_bytes, sanitize_value};
4
5use super::{
6 HeaderError, HeaderSensitivity, MAX_RESPONSE_HEADER_BYTES, MAX_RESPONSE_HEADERS,
7 encoded_line_len, validate_name, validate_response_value,
8};
9use crate::transport::retained::{ProtectedRequestId, RetainedMetadataError};
10
11#[derive(Clone, Copy, Debug, Eq, PartialEq)]
12struct HeaderRange {
13 name_start: u16,
14 name_len: u8,
15 value_start: u16,
16 value_len: u16,
17 sensitivity: HeaderSensitivity,
18}
19
20const EMPTY_RANGE: HeaderRange = HeaderRange {
21 name_start: 0,
22 name_len: 0,
23 value_start: 0,
24 value_len: 0,
25 sensitivity: HeaderSensitivity::Public,
26};
27
28#[derive(Clone, Copy)]
43pub struct ResponseHeader<'a> {
44 name: &'a str,
45 value: &'a [u8],
46 sensitivity: HeaderSensitivity,
47}
48
49impl<'a> ResponseHeader<'a> {
50 #[must_use]
52 pub const fn name(self) -> &'a str {
53 self.name
54 }
55
56 #[must_use]
58 pub const fn value(self) -> &'a [u8] {
59 self.value
60 }
61
62 #[must_use]
64 pub const fn sensitivity(self) -> HeaderSensitivity {
65 self.sensitivity
66 }
67}
68
69impl fmt::Debug for ResponseHeader<'_> {
70 fn fmt(&self, formatter: &mut fmt::Formatter<'_>) -> fmt::Result {
71 formatter
72 .debug_struct("ResponseHeader")
73 .field("name", &self.name)
74 .field("value", &"[redacted]")
75 .field("sensitivity", &self.sensitivity)
76 .finish()
77 }
78}
79
80pub struct ResponseHeaders<'storage> {
93 bytes: SecretBuffer<'storage>,
94 ranges: [HeaderRange; MAX_RESPONSE_HEADERS],
95 bytes_len: usize,
96 count: usize,
97 encoded_len: usize,
98}
99
100impl<'storage> ResponseHeaders<'storage> {
101 #[must_use]
103 pub fn new(storage: &'storage mut [u8]) -> Self {
104 sanitize_bytes(storage);
105 Self {
106 bytes: SecretBuffer::new(storage),
107 ranges: [EMPTY_RANGE; MAX_RESPONSE_HEADERS],
108 bytes_len: 0,
109 count: 0,
110 encoded_len: 0,
111 }
112 }
113
114 pub fn try_push(
116 &mut self,
117 name: &str,
118 value: &[u8],
119 sensitivity: HeaderSensitivity,
120 ) -> Result<(), HeaderError> {
121 validate_name(name)?;
122 validate_response_value(value)?;
123 if self.count >= MAX_RESPONSE_HEADERS {
124 return Err(HeaderError::TooManyHeaders);
125 }
126 if self
127 .iter()
128 .any(|header| header.name.eq_ignore_ascii_case(name))
129 {
130 return Err(HeaderError::DuplicateName);
131 }
132 let line_len = encoded_line_len(name.len(), value.len())?;
133 let encoded_len = self
134 .encoded_len
135 .checked_add(line_len)
136 .ok_or(HeaderError::AggregateTooLarge)?;
137 if encoded_len > MAX_RESPONSE_HEADER_BYTES {
138 return Err(HeaderError::AggregateTooLarge);
139 }
140 let stored_len = name
141 .len()
142 .checked_add(value.len())
143 .ok_or(HeaderError::AggregateTooLarge)?;
144 let end = self
145 .bytes_len
146 .checked_add(stored_len)
147 .ok_or(HeaderError::AggregateTooLarge)?;
148 let name_start =
149 u16::try_from(self.bytes_len).map_err(|_| HeaderError::AggregateTooLarge)?;
150 let value_start = self
151 .bytes_len
152 .checked_add(name.len())
153 .and_then(|offset| u16::try_from(offset).ok())
154 .ok_or(HeaderError::AggregateTooLarge)?;
155 let range = HeaderRange {
156 name_start,
157 name_len: u8::try_from(name.len()).map_err(|_| HeaderError::NameTooLong)?,
158 value_start,
159 value_len: u16::try_from(value.len()).map_err(|_| HeaderError::ValueTooLong)?,
160 sensitivity,
161 };
162 let next_count = self
163 .count
164 .checked_add(1)
165 .ok_or(HeaderError::TooManyHeaders)?;
166 let slot = self
167 .ranges
168 .get_mut(self.count)
169 .ok_or(HeaderError::TooManyHeaders)?;
170 let region = self
171 .bytes
172 .as_mut_slice()
173 .get_mut(self.bytes_len..end)
174 .ok_or(HeaderError::AggregateTooLarge)?;
175 let (name_out, value_out) = region.split_at_mut(name.len());
176 name_out.copy_from_slice(name.as_bytes());
177 value_out.copy_from_slice(value);
178 *slot = range;
179 self.bytes_len = end;
180 self.encoded_len = encoded_len;
181 self.count = next_count;
182 Ok(())
183 }
184
185 #[must_use]
187 pub const fn len(&self) -> usize {
188 self.count
189 }
190
191 #[must_use]
193 pub const fn is_empty(&self) -> bool {
194 self.count == 0
195 }
196
197 #[must_use]
199 pub const fn encoded_len(&self) -> usize {
200 self.encoded_len
201 }
202
203 pub fn iter(&self) -> impl Iterator<Item = ResponseHeader<'_>> {
205 self.ranges
206 .get(..self.count)
207 .unwrap_or_default()
208 .iter()
209 .filter_map(|range| self.view(*range))
210 }
211
212 #[must_use]
214 pub fn get(&self, name: &str) -> Option<ResponseHeader<'_>> {
215 self.iter()
216 .find(|header| header.name.eq_ignore_ascii_case(name))
217 }
218
219 pub fn retain_copy_into<'destination>(
224 &self,
225 destination: &'destination mut [u8],
226 ) -> Result<ResponseHeaders<'destination>, HeaderError> {
227 let mut retained = ResponseHeaders::new(destination);
228 for header in self.iter() {
229 retained.try_push(header.name(), header.value(), header.sensitivity())?;
230 }
231 Ok(retained)
232 }
233
234 pub(crate) fn hide_request_id(
235 &mut self,
236 ) -> Result<Option<ProtectedRequestId>, RetainedMetadataError> {
237 let found = self
238 .ranges
239 .get(..self.count)
240 .unwrap_or_default()
241 .iter()
242 .copied()
243 .enumerate()
244 .find(|(_, range)| {
245 self.view(*range)
246 .is_some_and(|header| header.name.eq_ignore_ascii_case("x-request-id"))
247 });
248 let Some((index, range)) = found else {
249 return Ok(None);
250 };
251 let protected = ProtectedRequestId::new(range.value_start, range.value_len)?;
252 self.remove(index, range)?;
253 Ok(Some(protected))
254 }
255
256 pub(crate) fn protected_value(&self, request_id: ProtectedRequestId) -> Option<&[u8]> {
257 let start = usize::from(request_id.start());
258 let end = start.checked_add(usize::from(request_id.len()))?;
259 self.bytes.as_slice().get(start..end)
260 }
261
262 pub(crate) fn clear_protected(&mut self, request_id: ProtectedRequestId) {
263 let start = usize::from(request_id.start());
264 let end = start.saturating_add(usize::from(request_id.len()));
265 sanitize_bytes(
266 self.bytes
267 .as_mut_slice()
268 .get_mut(start..end)
269 .unwrap_or_default(),
270 );
271 }
272
273 fn remove(&mut self, index: usize, range: HeaderRange) -> Result<(), RetainedMetadataError> {
274 let name_len = usize::from(range.name_len);
275 let value_len = usize::from(range.value_len);
276 let removed_encoded_len = name_len
277 .checked_add(value_len)
278 .and_then(|length| length.checked_add(4))
279 .ok_or(RetainedMetadataError::RequestIdTooLong)?;
280 let new_encoded_len = self
281 .encoded_len
282 .checked_sub(removed_encoded_len)
283 .ok_or(RetainedMetadataError::RequestIdTooLong)?;
284 let new_count = self
285 .count
286 .checked_sub(1)
287 .ok_or(RetainedMetadataError::RequestIdTooLong)?;
288 let tail_start = index
289 .checked_add(1)
290 .ok_or(RetainedMetadataError::RequestIdTooLong)?;
291 self.ranges.copy_within(tail_start..self.count, index);
292 if let Some(last) = self.ranges.get_mut(new_count) {
293 clear_range(last);
294 }
295 self.count = new_count;
296 self.encoded_len = new_encoded_len;
297 Ok(())
298 }
299
300 pub(crate) fn clear(&mut self) {
301 sanitize_bytes(self.bytes.as_mut_slice());
302 for range in &mut self.ranges {
303 clear_range(range);
304 }
305 sanitize_value(&mut self.bytes_len);
306 sanitize_value(&mut self.count);
307 sanitize_value(&mut self.encoded_len);
308 }
309
310 fn view(&self, range: HeaderRange) -> Option<ResponseHeader<'_>> {
311 let name_start = usize::from(range.name_start);
312 let name_end = name_start.checked_add(usize::from(range.name_len))?;
313 let value_start = usize::from(range.value_start);
314 let value_end = value_start.checked_add(usize::from(range.value_len))?;
315 let name = self
316 .bytes
317 .as_slice()
318 .get(name_start..name_end)
319 .and_then(|bytes| core::str::from_utf8(bytes).ok())?;
320 let value = self.bytes.as_slice().get(value_start..value_end)?;
321 Some(ResponseHeader {
322 name,
323 value,
324 sensitivity: range.sensitivity,
325 })
326 }
327}
328
329fn clear_range(range: &mut HeaderRange) {
330 sanitize_value(&mut range.name_start);
331 sanitize_value(&mut range.name_len);
332 sanitize_value(&mut range.value_start);
333 sanitize_value(&mut range.value_len);
334 range.sensitivity = HeaderSensitivity::Public;
335}
336
337impl Drop for ResponseHeaders<'_> {
338 fn drop(&mut self) {
339 self.clear();
340 }
341}
342
343impl fmt::Debug for ResponseHeaders<'_> {
344 fn fmt(&self, formatter: &mut fmt::Formatter<'_>) -> fmt::Result {
345 formatter
346 .debug_struct("ResponseHeaders")
347 .field("count", &self.count)
348 .field("encoded_len", &self.encoded_len)
349 .field("values", &"[redacted]")
350 .finish()
351 }
352}
353
354#[cfg(test)]
355mod cleanup_tests {
356 use super::{HeaderSensitivity, ResponseHeaders};
357
358 #[test]
359 fn complete_header_storage_and_ranges_clear() {
360 let mut storage = [0xa5_u8; 128];
361 let mut headers = ResponseHeaders::new(&mut storage);
362 assert!(
363 headers
364 .try_push(
365 "x-request-id",
366 b"sensitive-id",
367 HeaderSensitivity::Sensitive
368 )
369 .is_ok()
370 );
371 headers.clear();
372 assert!(headers.bytes.as_slice().iter().all(|byte| *byte == 0));
373 assert!(headers.ranges.iter().all(|range| {
374 range.name_start == 0
375 && range.name_len == 0
376 && range.value_start == 0
377 && range.value_len == 0
378 && range.sensitivity == HeaderSensitivity::Public
379 }));
380 assert_eq!(
381 (headers.bytes_len, headers.count, headers.encoded_len),
382 (0, 0, 0)
383 );
384 }
385
386 #[test]
387 fn hiding_request_id_preserves_stable_storage_and_removes_visibility() {
388 let mut storage = [0xa5_u8; 128];
389 let mut headers = ResponseHeaders::new(&mut storage);
390 assert!(
391 headers
392 .try_push("date", b"1", HeaderSensitivity::Public)
393 .is_ok()
394 );
395 assert!(
396 headers
397 .try_push(
398 "x-request-id",
399 b"sensitive-id",
400 HeaderSensitivity::Sensitive
401 )
402 .is_ok()
403 );
404 assert!(
405 headers
406 .try_push("x-public", b"ok", HeaderSensitivity::Public)
407 .is_ok()
408 );
409
410 let pointer = headers.bytes.as_slice().as_ptr();
411 let protected = headers.hide_request_id();
412 assert!(matches!(protected, Ok(Some(_))));
413 assert_eq!(headers.len(), 2);
414 assert_eq!(headers.encoded_len(), 23);
415 assert!(headers.get("x-request-id").is_none());
416 let mut retained = headers.iter();
417 assert!(
418 retained
419 .next()
420 .is_some_and(|header| { header.name() == "date" && header.value() == b"1" })
421 );
422 assert!(
423 retained
424 .next()
425 .is_some_and(|header| { header.name() == "x-public" && header.value() == b"ok" })
426 );
427 assert!(retained.next().is_none());
428 drop(retained);
429 assert_eq!(headers.bytes.as_slice().as_ptr(), pointer);
430 assert_eq!(headers.bytes_len, 39);
431 let Ok(Some(protected)) = protected else {
432 return;
433 };
434 let mut snapshot_storage = [0xa5_u8; 128];
435 let Ok(snapshot) = headers.retain_copy_into(&mut snapshot_storage) else {
436 return;
437 };
438 assert_eq!(snapshot.len(), 2);
439 assert!(
440 snapshot
441 .bytes
442 .as_slice()
443 .windows(b"sensitive-id".len())
444 .all(|window| window != b"sensitive-id")
445 );
446 assert_eq!(
447 headers.protected_value(protected),
448 Some(b"sensitive-id".as_slice())
449 );
450 headers.clear_protected(protected);
451 assert!(
452 headers
453 .bytes
454 .as_slice()
455 .windows(b"sensitive-id".len())
456 .all(|window| window != b"sensitive-id")
457 );
458 }
459}