cloud_sdk/rate_limit/
extension.rs1use core::fmt;
2
3use cloud_sdk_sanitization::{sanitize_bytes, sanitize_value};
4
5pub const MAX_QUOTA_EXTENSION_NAME_BYTES: usize = 32;
7pub const MAX_QUOTA_EXTENSION_VALUE_BYTES: usize = 64;
9
10#[derive(Clone, Eq, PartialEq)]
18pub struct QuotaExtension {
19 name: [u8; MAX_QUOTA_EXTENSION_NAME_BYTES],
20 name_len: u8,
21 value: [u8; MAX_QUOTA_EXTENSION_VALUE_BYTES],
22 value_len: u8,
23}
24
25impl QuotaExtension {
26 pub fn new(name: &[u8], value: &[u8]) -> Result<Self, QuotaExtensionError> {
28 validate(name, MAX_QUOTA_EXTENSION_NAME_BYTES, true)?;
29 validate(value, MAX_QUOTA_EXTENSION_VALUE_BYTES, false)?;
30 let mut result = Self {
31 name: [0; MAX_QUOTA_EXTENSION_NAME_BYTES],
32 name_len: u8::try_from(name.len()).map_err(|_| QuotaExtensionError::NameTooLong)?,
33 value: [0; MAX_QUOTA_EXTENSION_VALUE_BYTES],
34 value_len: u8::try_from(value.len()).map_err(|_| QuotaExtensionError::ValueTooLong)?,
35 };
36 let name_target = result
37 .name
38 .get_mut(..name.len())
39 .ok_or(QuotaExtensionError::NameTooLong)?;
40 name_target.copy_from_slice(name);
41 let value_target = result
42 .value
43 .get_mut(..value.len())
44 .ok_or(QuotaExtensionError::ValueTooLong)?;
45 value_target.copy_from_slice(value);
46 Ok(result)
47 }
48
49 #[must_use]
51 pub fn name(&self) -> &[u8] {
52 self.name
53 .get(..usize::from(self.name_len))
54 .unwrap_or_default()
55 }
56
57 #[must_use]
59 pub fn value(&self) -> &[u8] {
60 self.value
61 .get(..usize::from(self.value_len))
62 .unwrap_or_default()
63 }
64
65 fn clear(&mut self) {
66 sanitize_bytes(&mut self.name);
67 sanitize_bytes(&mut self.value);
68 sanitize_value(&mut self.name_len);
69 sanitize_value(&mut self.value_len);
70 }
71}
72
73impl Drop for QuotaExtension {
74 fn drop(&mut self) {
75 self.clear();
76 }
77}
78
79impl fmt::Debug for QuotaExtension {
80 fn fmt(&self, formatter: &mut fmt::Formatter<'_>) -> fmt::Result {
81 formatter
82 .debug_struct("QuotaExtension")
83 .field("name", &self.name())
84 .field("value", &"[redacted]")
85 .finish()
86 }
87}
88
89fn validate(value: &[u8], maximum: usize, name: bool) -> Result<(), QuotaExtensionError> {
90 if value.is_empty() {
91 return Err(if name {
92 QuotaExtensionError::NameEmpty
93 } else {
94 QuotaExtensionError::ValueEmpty
95 });
96 }
97 if value.len() > maximum {
98 return Err(if name {
99 QuotaExtensionError::NameTooLong
100 } else {
101 QuotaExtensionError::ValueTooLong
102 });
103 }
104 if !value.iter().all(|byte| byte.is_ascii_graphic()) {
105 return Err(QuotaExtensionError::InvalidByte);
106 }
107 Ok(())
108}
109
110#[derive(Clone, Copy, Debug, Eq, PartialEq)]
112pub enum QuotaExtensionError {
113 NameEmpty,
115 NameTooLong,
117 ValueEmpty,
119 ValueTooLong,
121 InvalidByte,
123}
124
125impl_static_error!(QuotaExtensionError,
126 Self::NameEmpty => "quota extension name is empty",
127 Self::NameTooLong => "quota extension name exceeds its length limit",
128 Self::ValueEmpty => "quota extension value is empty",
129 Self::ValueTooLong => "quota extension value exceeds its length limit",
130 Self::InvalidByte => "quota extension contains an invalid byte",
131);
132
133#[cfg(test)]
134mod tests {
135 use super::QuotaExtension;
136
137 #[test]
138 fn explicit_cleanup_clears_complete_name_value_and_lengths() {
139 let extension = QuotaExtension::new(b"partition", b"account-42");
140 assert!(extension.is_ok());
141 let Ok(mut extension) = extension else {
142 return;
143 };
144
145 extension.clear();
146
147 assert!(extension.name.iter().all(|byte| *byte == 0));
148 assert!(extension.value.iter().all(|byte| *byte == 0));
149 assert_eq!(extension.name_len, 0);
150 assert_eq!(extension.value_len, 0);
151 }
152}