Skip to main content

cloud_sdk/transport/header/
response.rs

1use core::fmt;
2
3use super::{
4    HeaderError, HeaderSensitivity, MAX_RESPONSE_HEADER_BYTES, MAX_RESPONSE_HEADERS,
5    encoded_line_len, validate_name, validate_response_value,
6};
7
8#[derive(Clone, Copy, Debug, Eq, PartialEq)]
9struct HeaderRange {
10    name_start: u16,
11    name_len: u8,
12    value_start: u16,
13    value_len: u16,
14    sensitivity: HeaderSensitivity,
15}
16
17const EMPTY_RANGE: HeaderRange = HeaderRange {
18    name_start: 0,
19    name_len: 0,
20    value_start: 0,
21    value_len: 0,
22    sensitivity: HeaderSensitivity::Public,
23};
24
25/// Borrowed view into one retained response header.
26///
27/// Ordinary equality is intentionally unavailable because the value may be
28/// sensitive.
29///
30/// ```compile_fail
31/// use cloud_sdk::transport::{HeaderSensitivity, ResponseHeaders};
32///
33/// let mut headers = ResponseHeaders::new();
34/// headers.try_push("x-secret", b"secret", HeaderSensitivity::Sensitive).unwrap();
35/// let header = headers.get("x-secret").unwrap();
36/// let _ = header == header;
37/// ```
38#[derive(Clone, Copy)]
39pub struct ResponseHeader<'a> {
40    name: &'a str,
41    value: &'a [u8],
42    sensitivity: HeaderSensitivity,
43}
44
45impl<'a> ResponseHeader<'a> {
46    /// Returns the exact retained name.
47    #[must_use]
48    pub const fn name(self) -> &'a str {
49        self.name
50    }
51
52    /// Returns the exact retained value bytes.
53    #[must_use]
54    pub const fn value(self) -> &'a [u8] {
55        self.value
56    }
57
58    /// Returns the value sensitivity.
59    #[must_use]
60    pub const fn sensitivity(self) -> HeaderSensitivity {
61        self.sensitivity
62    }
63}
64
65impl fmt::Debug for ResponseHeader<'_> {
66    fn fmt(&self, formatter: &mut fmt::Formatter<'_>) -> fmt::Result {
67        formatter
68            .debug_struct("ResponseHeader")
69            .field("name", &self.name)
70            .field("value", &"[redacted]")
71            .field("sensitivity", &self.sensitivity)
72            .finish()
73    }
74}
75
76/// Owned fixed-capacity response-header metadata.
77///
78/// Ordinary equality is intentionally unavailable because retained values may
79/// be sensitive.
80///
81/// ```compile_fail
82/// use cloud_sdk::transport::ResponseHeaders;
83///
84/// let headers = ResponseHeaders::new();
85/// let _ = headers == headers;
86/// ```
87#[derive(Clone, Copy)]
88pub struct ResponseHeaders {
89    bytes: [u8; MAX_RESPONSE_HEADER_BYTES],
90    ranges: [HeaderRange; MAX_RESPONSE_HEADERS],
91    bytes_len: usize,
92    count: usize,
93    encoded_len: usize,
94}
95
96impl ResponseHeaders {
97    /// Creates an empty response-header collection.
98    #[must_use]
99    pub const fn new() -> Self {
100        Self {
101            bytes: [0; MAX_RESPONSE_HEADER_BYTES],
102            ranges: [EMPTY_RANGE; MAX_RESPONSE_HEADERS],
103            bytes_len: 0,
104            count: 0,
105            encoded_len: 0,
106        }
107    }
108
109    /// Appends one header atomically after validating every bound.
110    pub fn try_push(
111        &mut self,
112        name: &str,
113        value: &[u8],
114        sensitivity: HeaderSensitivity,
115    ) -> Result<(), HeaderError> {
116        validate_name(name)?;
117        validate_response_value(value)?;
118        if self.count >= MAX_RESPONSE_HEADERS {
119            return Err(HeaderError::TooManyHeaders);
120        }
121        if self
122            .iter()
123            .any(|header| header.name.eq_ignore_ascii_case(name))
124        {
125            return Err(HeaderError::DuplicateName);
126        }
127        let line_len = encoded_line_len(name.len(), value.len())?;
128        let encoded_len = self
129            .encoded_len
130            .checked_add(line_len)
131            .ok_or(HeaderError::AggregateTooLarge)?;
132        if encoded_len > MAX_RESPONSE_HEADER_BYTES {
133            return Err(HeaderError::AggregateTooLarge);
134        }
135        let stored_len = name
136            .len()
137            .checked_add(value.len())
138            .ok_or(HeaderError::AggregateTooLarge)?;
139        let end = self
140            .bytes_len
141            .checked_add(stored_len)
142            .ok_or(HeaderError::AggregateTooLarge)?;
143        let name_start =
144            u16::try_from(self.bytes_len).map_err(|_| HeaderError::AggregateTooLarge)?;
145        let value_start = self
146            .bytes_len
147            .checked_add(name.len())
148            .and_then(|offset| u16::try_from(offset).ok())
149            .ok_or(HeaderError::AggregateTooLarge)?;
150        let range = HeaderRange {
151            name_start,
152            name_len: u8::try_from(name.len()).map_err(|_| HeaderError::NameTooLong)?,
153            value_start,
154            value_len: u16::try_from(value.len()).map_err(|_| HeaderError::ValueTooLong)?,
155            sensitivity,
156        };
157        let next_count = self
158            .count
159            .checked_add(1)
160            .ok_or(HeaderError::TooManyHeaders)?;
161        let slot = self
162            .ranges
163            .get_mut(self.count)
164            .ok_or(HeaderError::TooManyHeaders)?;
165        let region = self
166            .bytes
167            .get_mut(self.bytes_len..end)
168            .ok_or(HeaderError::AggregateTooLarge)?;
169        let (name_out, value_out) = region.split_at_mut(name.len());
170        name_out.copy_from_slice(name.as_bytes());
171        value_out.copy_from_slice(value);
172        *slot = range;
173        self.bytes_len = end;
174        self.encoded_len = encoded_len;
175        self.count = next_count;
176        Ok(())
177    }
178
179    /// Returns the retained header count.
180    #[must_use]
181    pub const fn len(&self) -> usize {
182        self.count
183    }
184
185    /// Reports whether no headers are retained.
186    #[must_use]
187    pub const fn is_empty(&self) -> bool {
188        self.count == 0
189    }
190
191    /// Returns the aggregate encoded field-line length.
192    #[must_use]
193    pub const fn encoded_len(&self) -> usize {
194        self.encoded_len
195    }
196
197    /// Iterates in retained order.
198    pub fn iter(&self) -> impl Iterator<Item = ResponseHeader<'_>> {
199        self.ranges
200            .get(..self.count)
201            .unwrap_or_default()
202            .iter()
203            .filter_map(|range| self.view(*range))
204    }
205
206    /// Finds a retained header by ASCII case-insensitive name.
207    #[must_use]
208    pub fn get(&self, name: &str) -> Option<ResponseHeader<'_>> {
209        self.iter()
210            .find(|header| header.name.eq_ignore_ascii_case(name))
211    }
212
213    fn view(&self, range: HeaderRange) -> Option<ResponseHeader<'_>> {
214        let name_start = usize::from(range.name_start);
215        let name_end = name_start.checked_add(usize::from(range.name_len))?;
216        let value_start = usize::from(range.value_start);
217        let value_end = value_start.checked_add(usize::from(range.value_len))?;
218        let name = self
219            .bytes
220            .get(name_start..name_end)
221            .and_then(|bytes| core::str::from_utf8(bytes).ok())?;
222        let value = self.bytes.get(value_start..value_end)?;
223        Some(ResponseHeader {
224            name,
225            value,
226            sensitivity: range.sensitivity,
227        })
228    }
229}
230
231impl Default for ResponseHeaders {
232    fn default() -> Self {
233        Self::new()
234    }
235}
236
237impl fmt::Debug for ResponseHeaders {
238    fn fmt(&self, formatter: &mut fmt::Formatter<'_>) -> fmt::Result {
239        formatter
240            .debug_struct("ResponseHeaders")
241            .field("count", &self.count)
242            .field("encoded_len", &self.encoded_len)
243            .field("values", &"[redacted]")
244            .finish()
245    }
246}