1use super::Oid;
4use crate::error::{Error, ErrorCode, Path, Result};
5use crate::memory::{Memory, MemoryError, OwnedVec};
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 let work = (text.len() as u64)
30 .checked_mul(4)
31 .and_then(|work| work.checked_add(1))
32 .ok_or_else(|| Error::resource(MemoryError::WorkExceeded, 0))?;
33 execution
34 .charge_work(work)
35 .map_err(|reason| Error::resource(reason, 0))?;
36 let root = validate(text)?;
37 let mut bytes = execution.vector();
38 let mut groups = execution.vector();
39 let mut offset = 2;
40 for (position, component) in text.split('.').enumerate() {
41 if position == 0 {
42 continue;
43 }
44 groups.clear();
45 groups
46 .try_push(0u8)
47 .map_err(|reason| Error::resource(reason, offset))?;
48 for digit in component.bytes() {
49 multiply(&mut groups, digit - b'0', execution, offset)?;
50 }
51 if position == 1 {
52 add_root(&mut groups, root * 40, execution, offset)?;
53 }
54 execution
55 .charge_work(groups.len() as u64)
56 .map_err(|reason| Error::resource(reason, offset))?;
57 for at in (0..groups.len()).rev() {
58 let byte = if at == 0 {
59 groups[at]
60 } else {
61 groups[at] | 0x80
62 };
63 bytes
64 .try_push(byte)
65 .map_err(|reason| Error::resource(reason, offset))?;
66 }
67 offset += component.len() as u64 + 1;
68 }
69 Ok(Self { bytes })
70 }
71
72 pub fn from_oid(value: Oid<'_>, execution: &Memory) -> core::result::Result<Self, MemoryError> {
75 let mut bytes = execution.vector();
76 bytes.try_extend_from_slice(value.as_bytes())?;
77 Ok(Self { bytes })
78 }
79
80 #[must_use]
82 pub fn as_oid(&self) -> Oid<'_> {
83 Oid(&self.bytes)
84 }
85}
86
87fn malformed(offset: usize) -> Error {
88 Error::new(ErrorCode::MalformedOid, offset as u64, Path::default())
89}
90
91fn validate(text: &str) -> Result<u8> {
92 let mut offset = 0;
96 let mut count = 0;
97 let mut root = 0;
98 for component in text.split('.') {
99 let bytes = component.as_bytes();
100 if bytes.is_empty() {
101 return Err(malformed(offset));
102 }
103 if bytes.len() > 1 && bytes[0] == b'0' {
104 return Err(malformed(offset));
105 }
106 for (at, byte) in bytes.iter().enumerate() {
107 if !byte.is_ascii_digit() {
108 return Err(malformed(offset + at));
109 }
110 }
111 if count == 0 {
112 if bytes.len() != 1 || bytes[0] > b'2' {
113 return Err(malformed(offset));
114 }
115 root = bytes[0] - b'0';
116 } else if count == 1
117 && root < 2
118 && (bytes.len() > 2 || (bytes.len() == 2 && bytes > &b"39"[..]))
119 {
120 return Err(malformed(offset));
121 }
122 count += 1;
123 offset += bytes.len() + 1;
124 }
125 if count < 2 {
126 return Err(malformed(text.len()));
127 }
128 Ok(root)
129}
130
131fn admit(groups: &OwnedVec<u8>, execution: &Memory, offset: u64) -> Result<()> {
132 let work = (groups.len() as u64)
133 .checked_mul(3)
134 .and_then(|work| work.checked_add(1))
135 .ok_or_else(|| Error::resource(MemoryError::WorkExceeded, offset))?;
136 execution
137 .charge_work(work)
138 .map_err(|reason| Error::resource(reason, offset))
139}
140
141fn multiply(groups: &mut OwnedVec<u8>, digit: u8, execution: &Memory, offset: u64) -> Result<()> {
142 admit(groups, execution, offset)?;
143 let mut carry = u16::from(digit);
144 for group in &mut **groups {
145 let current = u16::from(*group) * 10 + carry;
146 *group = u8::try_from(current & 0x7f).expect("seven-bit group");
147 carry = current >> 7;
148 }
149 while carry != 0 {
150 groups
151 .try_push(u8::try_from(carry & 0x7f).expect("seven-bit carry"))
152 .map_err(|reason| Error::resource(reason, offset))?;
153 carry >>= 7;
154 }
155 Ok(())
156}
157
158fn add_root(groups: &mut OwnedVec<u8>, root: u8, execution: &Memory, offset: u64) -> Result<()> {
159 admit(groups, execution, offset)?;
160 let mut carry = u16::from(root);
161 for group in &mut **groups {
162 if carry == 0 {
163 break;
164 }
165 let current = u16::from(*group) + carry;
166 *group = u8::try_from(current & 0x7f).expect("seven-bit group");
167 carry = current >> 7;
168 }
169 if carry != 0 {
170 groups
171 .try_push(u8::try_from(carry).expect("one-bit carry"))
172 .map_err(|reason| Error::resource(reason, offset))?;
173 }
174 Ok(())
175}
176
177#[cfg(test)]
178mod tests {
179 use super::*;
180 use crate::memory::MemoryAccount;
181 use alloc::sync::Arc;
182
183 fn memory(storage: usize, work: usize) -> Memory {
184 Memory::new(Arc::new(MemoryAccount::new(storage, work)))
185 }
186
187 #[test]
188 fn owned_decimal_oids_preserve_arbitrary_width_and_joint_root() {
189 for text in [
192 "0.0",
193 "1.39",
194 "2.0",
195 "2.18446744073709551616",
196 "1.2.10889035741470030830827987437816582766593",
197 ] {
198 let execution = memory(4096, 1_000_000);
199 let value = OwnedOid::parse(text, &execution).unwrap();
200 let rendered = value.as_oid().text(&execution).unwrap();
201 assert_eq!(rendered.as_str(&execution).unwrap(), text);
202 drop(rendered);
203 assert_eq!(execution.used(), value.bytes.capacity());
204 drop(value);
205 assert_eq!(execution.used(), 0);
206 }
207 }
208
209 #[test]
210 fn decimal_oid_syntax_refusal_has_no_retained_allocation() {
211 for (text, offset) in [
214 ("", 0),
215 ("1", 1),
216 ("1.", 2),
217 ("3.1", 0),
218 ("1.40", 2),
219 ("0.100", 2),
220 ("2.01", 2),
221 ("2.1..3", 4),
222 ("2.1x", 3),
223 (" 2.1", 0),
224 ] {
225 let execution = memory(0, 1024);
226 let error = OwnedOid::parse(text, &execution).unwrap_err();
227 assert_eq!(error.code(), ErrorCode::MalformedOid);
228 assert_eq!(error.offset(), offset);
229 assert_eq!(execution.used(), 0);
230 }
231 }
232
233 #[test]
234 fn owned_oid_refusal_releases_partial_conversion_and_copy_storage() {
235 let execution = memory(4096, 350);
238 let error =
239 OwnedOid::parse("2.1844674407370955161618446744073709551616", &execution).unwrap_err();
240 assert_eq!(error.code(), ErrorCode::LimitExceeded(crate::Limit::Work));
241 assert_eq!(execution.used(), 0);
242 let storage = memory(128, 10_000);
243 let original = OwnedOid::parse("1.2.840.113549", &storage).unwrap();
244 let used = storage.used();
245 let refused = memory(0, 1000);
246 assert_eq!(
247 OwnedOid::from_oid(original.as_oid(), &refused).unwrap_err(),
248 MemoryError::LimitExceeded
249 );
250 assert_eq!(refused.used(), 0);
251 assert_eq!(storage.used(), used);
252 assert_eq!(
253 original.as_oid(),
254 Oid::from_bytes(&[0x2a, 0x86, 0x48, 0x86, 0xf7, 0x0d])
255 );
256 }
257}