structured_asn1/memory/
shared_string.rs1use super::{HashWork, Memory, MemoryError, Work, allocator::SharedText};
4use core::{
5 borrow::Borrow,
6 fmt,
7 hash::{Hash, Hasher},
8 ops::Deref,
9};
10
11#[derive(Clone)]
51pub struct SharedString {
52 text: SharedText,
53}
54
55impl SharedString {
56 pub fn try_from_str(text: &str, execution: &impl Work) -> Result<Self, MemoryError> {
60 Ok(Self {
61 text: SharedText::new(text, execution)?,
62 })
63 }
64
65 #[must_use]
67 pub fn memory(&self) -> &Memory {
68 self.text.memory()
69 }
70
71 #[must_use]
73 pub fn len(&self) -> usize {
74 self.text.len()
75 }
76
77 #[must_use]
79 pub fn is_empty(&self) -> bool {
80 self.len() == 0
81 }
82
83 #[must_use]
85 pub fn allocated_bytes(&self) -> usize {
86 self.text.allocated_bytes()
87 }
88
89 #[must_use]
91 pub fn as_str(&self) -> &str {
92 self.text.as_str()
93 }
94
95 #[must_use]
97 pub fn as_bytes(&self) -> &[u8] {
98 self.as_str().as_bytes()
99 }
100
101 #[must_use]
103 pub fn ptr_eq(left: &Self, right: &Self) -> bool {
104 left.text.ptr_eq(&right.text)
105 }
106}
107
108impl Deref for SharedString {
109 type Target = str;
110 fn deref(&self) -> &str {
111 self.as_str()
112 }
113}
114impl Borrow<str> for SharedString {
115 fn borrow(&self) -> &str {
116 self.as_str()
117 }
118}
119impl AsRef<str> for SharedString {
120 fn as_ref(&self) -> &str {
121 self.as_str()
122 }
123}
124impl fmt::Debug for SharedString {
125 fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
126 self.as_str().fmt(f)
127 }
128}
129impl fmt::Display for SharedString {
130 fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
131 f.write_str(self.as_str())
132 }
133}
134impl PartialEq for SharedString {
135 fn eq(&self, other: &Self) -> bool {
136 Self::ptr_eq(self, other) || self.as_str() == other.as_str()
137 }
138}
139impl Eq for SharedString {}
140impl Hash for SharedString {
141 fn hash<H: Hasher>(&self, state: &mut H) {
142 self.as_str().hash(state);
143 }
144}
145impl HashWork for SharedString {
146 fn hash_work(&self) -> Result<u64, MemoryError> {
147 (self.len() as u64)
148 .checked_add(1)
149 .ok_or(MemoryError::WorkExceeded)
150 }
151}
152
153#[cfg(test)]
154mod tests {
155 use super::*;
156 use crate::memory::MemoryAccount;
157 use crate::{Encoding, Limits, Parser};
158 use alloc::{format, sync::Arc};
159
160 fn memory(storage: usize, work: usize) -> Memory {
161 Memory::new(Arc::new(MemoryAccount::new(storage, work)))
162 }
163
164 #[test]
165 fn shared_string_exact_layout_and_work_include_header_padding_and_copy() {
166 for value in ["", "a", "Ж", "𐀀🦀", "123456789"] {
169 let probe = memory(4096, 4096);
170 let text = SharedString::try_from_str(value, &probe).unwrap();
171 let bytes = text.allocated_bytes();
172 assert_eq!(probe.used(), bytes);
173 assert_eq!(probe.work_used(), bytes + value.len());
174 drop(text);
175 assert_eq!(probe.used(), 0);
176 for (storage, work, expected) in [
177 (bytes - 1, usize::MAX, MemoryError::LimitExceeded),
178 (bytes, bytes + value.len() - 1, MemoryError::WorkExceeded),
179 ] {
180 let denied = memory(storage, work);
181 assert_eq!(
182 SharedString::try_from_str(value, &denied).unwrap_err(),
183 expected
184 );
185 assert_eq!(denied.used(), 0);
186 }
187 let exact = memory(bytes, bytes + value.len());
188 let text = SharedString::try_from_str(value, &exact).unwrap();
189 assert_eq!(text.as_str(), value);
190 assert_eq!(text.as_bytes(), value.as_bytes());
191 assert_eq!(text.is_empty(), value.is_empty());
192 let clone = text.clone();
193 assert!(SharedString::ptr_eq(&text, &clone));
194 assert_eq!(exact.used(), bytes);
195 assert_eq!(exact.work_used(), bytes + value.len());
196 drop(text);
197 assert_eq!(clone.as_str(), value);
198 drop(clone);
199 assert_eq!(exact.used(), 0);
200 }
201 }
202
203 #[test]
204 fn shared_string_local_exact_copy_and_layout_admission_share_one_execution() {
205 let probe = memory(4096, 4096);
208 let text = SharedString::try_from_str("abc", &probe).unwrap();
209 let needed = text.allocated_bytes() + text.len();
210 drop(text);
211 for local in [needed - 1, needed] {
212 let storage = memory(4096, 1_000_000);
213 let parser = Parser::new(
214 Encoding::Der,
215 Limits {
216 max_work: local as u64,
217 ..Limits::default()
218 },
219 storage.clone(),
220 );
221 let reader = parser.read(&[], 0).unwrap();
222 let result = SharedString::try_from_str("abc", &reader.execution());
223 if local == needed {
224 let text = result.unwrap();
225 assert!(text.memory().same_account(&storage));
226 assert_eq!(text.as_str(), "abc");
227 drop(text);
228 } else {
229 assert_eq!(result.unwrap_err(), MemoryError::WorkExceeded);
230 }
231 assert_eq!(storage.used(), 0);
232 }
233 }
234
235 #[test]
236 fn shared_string_views_hashes_and_lifetimes_preserve_immutable_utf8() {
237 use core::hash::BuildHasher;
240 let owner = memory(4096, 4096);
241 let left = SharedString::try_from_str("Straße 🦀", &owner).unwrap();
242 let right = SharedString::try_from_str("Straße 🦀", &owner).unwrap();
243 assert!(!SharedString::ptr_eq(&left, &right));
244 assert_eq!(left, right);
245 assert_eq!(Borrow::<str>::borrow(&left), "Straße 🦀");
246 assert_eq!(AsRef::<str>::as_ref(&left), "Straße 🦀");
247 let hasher = hashbrown::DefaultHashBuilder::default();
248 assert_eq!(hasher.hash_one(&left), hasher.hash_one(left.as_str()));
249 assert_eq!(left.hash_work().unwrap(), left.len() as u64 + 1);
250 assert_eq!(format!("{left}"), "Straße 🦀");
251 assert_eq!(format!("{left:?}"), "\"Straße 🦀\"");
252 let account = Arc::downgrade(&owner.account);
253 drop(owner);
254 assert!(account.upgrade().is_some());
255 drop(left);
256 assert!(account.upgrade().is_some());
257 drop(right);
258 assert!(account.upgrade().is_none());
259 }
260}