1use super::Oid;
4use crate::error::{Error, ErrorCode, Path, Result};
5use crate::memory::{Memory, MemoryError, OwnedVec, Work};
6
7#[derive(Debug)]
13pub struct OwnedOid {
14 bytes: OwnedVec<u8>,
15}
16
17impl OwnedOid {
18 pub fn parse(text: &str, execution: &Memory) -> Result<Self> {
29 Self::parse_in(text, execution)
30 }
31
32 pub fn parse_in<W: Work>(text: &str, execution: &W) -> Result<Self> {
37 let work = u64::try_from(text.len())
38 .map_err(|_| Error::resource(MemoryError::WorkExceeded, 0))?
39 .checked_mul(4)
40 .and_then(|work| work.checked_add(1))
41 .ok_or_else(|| Error::resource(MemoryError::WorkExceeded, 0))?;
42 execution
43 .charge_work(work)
44 .map_err(|reason| Error::resource(reason, 0))?;
45 let root = validate(text)?;
46 let mut bytes = execution.vector();
47 let mut groups = execution.vector();
48 let mut offset = 2;
49 for (position, component) in text.split('.').enumerate() {
50 if position == 0 {
51 continue;
52 }
53 groups.clear();
54 groups
55 .try_push(0u8)
56 .map_err(|reason| Error::resource(reason, offset))?;
57 for digit in component.bytes() {
58 multiply(&mut groups, digit - b'0', execution, offset)?;
59 }
60 if position == 1 {
61 add_root(&mut groups, root * 40, execution, offset)?;
62 }
63 execution
64 .charge_work(groups.len() as u64)
65 .map_err(|reason| Error::resource(reason, offset))?;
66 for at in (0..groups.len()).rev() {
67 let byte = if at == 0 {
68 groups[at]
69 } else {
70 groups[at] | 0x80
71 };
72 bytes
73 .try_push(byte)
74 .map_err(|reason| Error::resource(reason, offset))?;
75 }
76 offset += component.len() as u64 + 1;
77 }
78 Ok(Self {
79 bytes: bytes.into_owned(),
80 })
81 }
82
83 pub fn from_oid(value: Oid<'_>, execution: &Memory) -> core::result::Result<Self, MemoryError> {
86 Self::from_oid_in(value, execution)
87 }
88
89 pub fn from_oid_in<W: Work>(
92 value: Oid<'_>,
93 execution: &W,
94 ) -> core::result::Result<Self, MemoryError> {
95 let mut bytes = execution.vector();
96 bytes.try_extend_from_slice(value.as_bytes())?;
97 Ok(Self {
98 bytes: bytes.into_owned(),
99 })
100 }
101
102 #[must_use]
104 pub fn as_oid(&self) -> Oid<'_> {
105 Oid(&self.bytes)
106 }
107}
108
109fn malformed(offset: usize) -> Error {
110 Error::new(ErrorCode::MalformedOid, offset as u64, Path::default())
111}
112
113fn validate(text: &str) -> Result<u8> {
114 let mut offset = 0;
118 let mut count = 0;
119 let mut root = 0;
120 for component in text.split('.') {
121 let bytes = component.as_bytes();
122 if bytes.is_empty() {
123 return Err(malformed(offset));
124 }
125 if bytes.len() > 1 && bytes[0] == b'0' {
126 return Err(malformed(offset));
127 }
128 for (at, byte) in bytes.iter().enumerate() {
129 if !byte.is_ascii_digit() {
130 return Err(malformed(offset + at));
131 }
132 }
133 if count == 0 {
134 if bytes.len() != 1 || bytes[0] > b'2' {
135 return Err(malformed(offset));
136 }
137 root = bytes[0] - b'0';
138 } else if count == 1
139 && root < 2
140 && (bytes.len() > 2 || (bytes.len() == 2 && bytes > &b"39"[..]))
141 {
142 return Err(malformed(offset));
143 }
144 count += 1;
145 offset += bytes.len() + 1;
146 }
147 if count < 2 {
148 return Err(malformed(text.len()));
149 }
150 Ok(root)
151}
152
153fn admit<W: Work>(groups: &OwnedVec<u8, W>, execution: &W, offset: u64) -> Result<()> {
154 let work = (groups.len() as u64)
155 .checked_mul(3)
156 .and_then(|work| work.checked_add(1))
157 .ok_or_else(|| Error::resource(MemoryError::WorkExceeded, offset))?;
158 execution
159 .charge_work(work)
160 .map_err(|reason| Error::resource(reason, offset))
161}
162
163fn multiply<W: Work>(
164 groups: &mut OwnedVec<u8, W>,
165 digit: u8,
166 execution: &W,
167 offset: u64,
168) -> Result<()> {
169 admit(groups, execution, offset)?;
170 let mut carry = u16::from(digit);
171 for group in &mut **groups {
172 let current = u16::from(*group) * 10 + carry;
173 *group = u8::try_from(current & 0x7f).expect("seven-bit group");
174 carry = current >> 7;
175 }
176 while carry != 0 {
177 groups
178 .try_push(u8::try_from(carry & 0x7f).expect("seven-bit carry"))
179 .map_err(|reason| Error::resource(reason, offset))?;
180 carry >>= 7;
181 }
182 Ok(())
183}
184
185fn add_root<W: Work>(
186 groups: &mut OwnedVec<u8, W>,
187 root: u8,
188 execution: &W,
189 offset: u64,
190) -> Result<()> {
191 admit(groups, execution, offset)?;
192 let mut carry = u16::from(root);
193 for group in &mut **groups {
194 if carry == 0 {
195 break;
196 }
197 let current = u16::from(*group) + carry;
198 *group = u8::try_from(current & 0x7f).expect("seven-bit group");
199 carry = current >> 7;
200 }
201 if carry != 0 {
202 groups
203 .try_push(u8::try_from(carry).expect("one-bit carry"))
204 .map_err(|reason| Error::resource(reason, offset))?;
205 }
206 Ok(())
207}
208
209#[cfg(test)]
210mod tests {
211 use super::*;
212 use crate::memory::MemoryAccount;
213 use alloc::sync::Arc;
214
215 fn memory(storage: usize, work: usize) -> Memory {
216 Memory::new(Arc::new(MemoryAccount::new(storage, work)))
217 }
218
219 #[test]
220 fn decimal_conversion_uses_current_local_work_and_retains_only_its_allocation() {
221 use crate::{Encoding, Limits, Parser};
224 let account = memory(4096, 1_000_000);
225 let text = "2.1844674407370955161618446744073709551616";
226 {
227 let parser = Parser::new(
228 Encoding::Der,
229 Limits {
230 max_work: 350,
231 ..Limits::default()
232 },
233 account.clone(),
234 );
235 let reader = parser.read(&[], 700).unwrap();
236 let error = OwnedOid::parse_in(text, &reader.execution()).unwrap_err();
237 assert_eq!(error.code(), ErrorCode::LimitExceeded(crate::Limit::Work));
238 assert_eq!(account.used(), 0);
239 }
240 let value = {
241 let parser = Parser::der(account.clone());
242 let reader = parser.read(&[], 700).unwrap();
243 OwnedOid::parse_in(text, &reader.execution()).unwrap()
244 };
245 assert_eq!(account.used(), value.bytes.capacity());
246 assert!(account.used() > 0);
247 let aggregate = memory(4096, 350);
248 let parser = Parser::der(aggregate.clone());
249 let reader = parser.read(&[], 900).unwrap();
250 let error = OwnedOid::parse_in(text, &reader.execution()).unwrap_err();
251 assert_eq!(error.code(), ErrorCode::LimitExceeded(crate::Limit::Work));
252 assert_eq!(aggregate.used(), 0);
253 drop(value);
254 assert_eq!(account.used(), 0);
255 }
256
257 #[test]
258 fn owned_decimal_oids_preserve_arbitrary_width_and_joint_root() {
259 for text in [
262 "0.0",
263 "1.39",
264 "2.0",
265 "2.18446744073709551616",
266 "1.2.10889035741470030830827987437816582766593",
267 ] {
268 let execution = memory(4096, 1_000_000);
269 let value = OwnedOid::parse(text, &execution).unwrap();
270 let rendered = value.as_oid().text(&execution).unwrap();
271 assert_eq!(rendered.as_str(&execution).unwrap(), text);
272 drop(rendered);
273 assert_eq!(execution.used(), value.bytes.capacity());
274 drop(value);
275 assert_eq!(execution.used(), 0);
276 }
277 }
278
279 #[test]
280 fn decimal_oid_syntax_refusal_has_no_retained_allocation() {
281 for (text, offset) in [
284 ("", 0),
285 ("1", 1),
286 ("1.", 2),
287 ("3.1", 0),
288 ("1.40", 2),
289 ("0.100", 2),
290 ("2.01", 2),
291 ("2.1..3", 4),
292 ("2.1x", 3),
293 (" 2.1", 0),
294 ] {
295 let execution = memory(0, 1024);
296 let error = OwnedOid::parse(text, &execution).unwrap_err();
297 assert_eq!(error.code(), ErrorCode::MalformedOid);
298 assert_eq!(error.offset(), offset);
299 assert_eq!(execution.used(), 0);
300 }
301 }
302
303 #[test]
304 fn owned_oid_refusal_releases_partial_conversion_and_copy_storage() {
305 let execution = memory(4096, 350);
308 let error =
309 OwnedOid::parse("2.1844674407370955161618446744073709551616", &execution).unwrap_err();
310 assert_eq!(error.code(), ErrorCode::LimitExceeded(crate::Limit::Work));
311 assert_eq!(execution.used(), 0);
312 let storage = memory(128, 10_000);
313 let original = OwnedOid::parse("1.2.840.113549", &storage).unwrap();
314 let used = storage.used();
315 let refused = memory(0, 1000);
316 assert_eq!(
317 OwnedOid::from_oid(original.as_oid(), &refused).unwrap_err(),
318 MemoryError::LimitExceeded
319 );
320 assert_eq!(refused.used(), 0);
321 assert_eq!(storage.used(), used);
322 assert_eq!(
323 original.as_oid(),
324 Oid::from_bytes(&[0x2a, 0x86, 0x48, 0x86, 0xf7, 0x0d])
325 );
326 }
327}