Skip to main content

cloud_sdk/transport/
retained.rs

1//! Cleanup-owning response identifiers retained beyond a response guard.
2
3use core::fmt;
4
5use cloud_sdk_sanitization::{SecretBuffer, sanitize_bytes, sanitize_value};
6
7/// Maximum opaque provider request-identifier length.
8pub const MAX_REQUEST_ID_BYTES: usize = 1024;
9
10/// Request-identifier admission or transfer failure.
11#[derive(Clone, Copy, Debug, Eq, PartialEq)]
12pub enum RetainedMetadataError {
13    /// A present request identifier was empty.
14    EmptyRequestId,
15    /// The request identifier exceeds the SDK hard limit.
16    RequestIdTooLong,
17    /// The caller's retention limit is smaller than the identifier.
18    RetentionLimitExceeded,
19    /// Operation policy does not permit retention beyond the response guard.
20    RetentionForbidden,
21}
22
23impl_static_error!(RetainedMetadataError,
24    Self::EmptyRequestId => "response request identifier is empty",
25    Self::RequestIdTooLong => "response request identifier exceeds the hard limit",
26    Self::RetentionLimitExceeded => "response request identifier exceeds the retention limit",
27    Self::RetentionForbidden => "operation policy forbids retaining the request identifier",
28);
29
30#[derive(Clone, Copy)]
31pub(crate) struct ProtectedRequestId {
32    start: u16,
33    len: u16,
34}
35
36impl ProtectedRequestId {
37    pub(crate) fn new(start: u16, len: u16) -> Result<Self, RetainedMetadataError> {
38        let len_usize = usize::from(len);
39        if len_usize == 0 {
40            return Err(RetainedMetadataError::EmptyRequestId);
41        }
42        if len_usize > MAX_REQUEST_ID_BYTES {
43            return Err(RetainedMetadataError::RequestIdTooLong);
44        }
45        Ok(Self { start, len })
46    }
47
48    pub(crate) const fn start(self) -> u16 {
49        self.start
50    }
51
52    pub(crate) const fn len(self) -> u16 {
53        self.len
54    }
55}
56
57impl fmt::Debug for ProtectedRequestId {
58    fn fmt(&self, formatter: &mut fmt::Formatter<'_>) -> fmt::Result {
59        formatter.write_str("ProtectedRequestId([redacted])")
60    }
61}
62
63/// Request identifier deliberately retained beyond a checked response guard.
64///
65/// This type is intentionally neither `Copy` nor `Clone`. Access is
66/// closure-scoped and the complete caller-owned destination is
67/// volatile-cleared on drop.
68///
69/// ```compile_fail
70/// use cloud_sdk::transport::RetainedResponseMetadata;
71///
72/// fn require_copy<T: Copy>() {}
73/// require_copy::<RetainedResponseMetadata<'static>>();
74/// ```
75pub struct RetainedResponseMetadata<'storage> {
76    request_id: SecretBuffer<'storage>,
77    request_id_len: usize,
78}
79
80impl<'storage> RetainedResponseMetadata<'storage> {
81    pub(crate) fn empty_for_core(storage: &'storage mut [u8]) -> Self {
82        sanitize_bytes(storage);
83        Self {
84            request_id: SecretBuffer::new(storage),
85            request_id_len: 0,
86        }
87    }
88
89    pub(crate) fn write_request_id(&mut self, source: &[u8]) -> Result<(), RetainedMetadataError> {
90        let target = self
91            .request_id
92            .as_mut_slice()
93            .get_mut(..source.len())
94            .ok_or(RetainedMetadataError::RetentionLimitExceeded)?;
95        target.copy_from_slice(source);
96        self.request_id_len = source.len();
97        Ok(())
98    }
99
100    /// Reports whether a request identifier was retained.
101    #[must_use]
102    pub const fn has_request_id(&self) -> bool {
103        self.request_id_len != 0
104    }
105
106    /// Runs a closure with the retained opaque request-identifier bytes.
107    pub fn with_request_id<R>(&self, inspect: impl FnOnce(Option<&[u8]>) -> R) -> R {
108        let value = self.has_request_id().then(|| {
109            self.request_id
110                .as_slice()
111                .get(..self.request_id_len)
112                .unwrap_or_default()
113        });
114        inspect(value)
115    }
116}
117
118impl Drop for RetainedResponseMetadata<'_> {
119    fn drop(&mut self) {
120        sanitize_value(&mut self.request_id_len);
121    }
122}
123
124impl fmt::Debug for RetainedResponseMetadata<'_> {
125    fn fmt(&self, formatter: &mut fmt::Formatter<'_>) -> fmt::Result {
126        formatter
127            .debug_struct("RetainedResponseMetadata")
128            .field("request_id", &"[redacted]")
129            .finish()
130    }
131}