async_snmp/oid.rs
1//! Object Identifier (OID) type.
2//!
3//! OIDs are stored as `SmallVec<[u32; 16]>` to avoid heap allocation for common OIDs.
4
5use crate::error::{DecodeErrorKind, Error, OidErrorKind, Result};
6use smallvec::SmallVec;
7use std::fmt;
8
9/// Maximum number of arcs (subidentifiers) allowed in an OID.
10///
11/// SNMP implementations commonly limit OIDs to 128 subidentifiers.
12/// This provides protection against DoS attacks from maliciously long OIDs.
13///
14/// This limit is enforced optionally via [`Oid::validate_length()`]. The standard
15/// [`Oid::from_ber()`] and [`Oid::parse()`] methods accept OIDs of any length for
16/// maximum flexibility.
17pub const MAX_OID_LEN: usize = 128;
18
19/// Object Identifier.
20///
21/// Stored as a sequence of arc values (u32). Uses SmallVec to avoid
22/// heap allocation for OIDs with 16 or fewer arcs.
23#[derive(Clone, PartialEq, Eq, Hash)]
24pub struct Oid {
25 arcs: SmallVec<[u32; 16]>,
26}
27
28impl Oid {
29 /// Create an empty OID.
30 pub fn empty() -> Self {
31 Self {
32 arcs: SmallVec::new(),
33 }
34 }
35
36 /// Create an OID from arc values.
37 ///
38 /// Accepts any iterator of `u32` values.
39 ///
40 /// # Examples
41 ///
42 /// ```
43 /// use async_snmp::oid::Oid;
44 ///
45 /// // From a Vec
46 /// let oid = Oid::new(vec![1, 3, 6, 1, 2, 1]);
47 /// assert_eq!(oid.arcs(), &[1, 3, 6, 1, 2, 1]);
48 ///
49 /// // From an array
50 /// let oid = Oid::new([1, 3, 6, 1]);
51 /// assert_eq!(oid.len(), 4);
52 ///
53 /// // From a range
54 /// let oid = Oid::new(0..5);
55 /// assert_eq!(oid.arcs(), &[0, 1, 2, 3, 4]);
56 /// ```
57 pub fn new(arcs: impl IntoIterator<Item = u32>) -> Self {
58 Self {
59 arcs: arcs.into_iter().collect(),
60 }
61 }
62
63 /// Create an OID from a slice of arcs.
64 ///
65 /// # Examples
66 ///
67 /// ```
68 /// use async_snmp::oid::Oid;
69 ///
70 /// let arcs = [1, 3, 6, 1, 2, 1, 1, 1, 0];
71 /// let oid = Oid::from_slice(&arcs);
72 /// assert_eq!(oid.to_string(), "1.3.6.1.2.1.1.1.0");
73 ///
74 /// // Empty slice creates an empty OID
75 /// let empty = Oid::from_slice(&[]);
76 /// assert!(empty.is_empty());
77 /// ```
78 pub fn from_slice(arcs: &[u32]) -> Self {
79 Self {
80 arcs: SmallVec::from_slice(arcs),
81 }
82 }
83
84 /// Parse an OID from dotted string notation (e.g., "1.3.6.1.2.1.1.1.0").
85 ///
86 /// # Validation
87 ///
88 /// This method parses the string format but does **not** validate arc constraints
89 /// per X.690 Section 8.19.4. Invalid OIDs like `"3.0"` (arc1 must be 0, 1, or 2)
90 /// or `"0.40"` (arc2 must be ≤39 when arc1 < 2) will parse successfully.
91 ///
92 /// To validate arc constraints, call [`validate()`](Self::validate) after parsing,
93 /// or use [`to_ber_checked()`](Self::to_ber_checked) which validates before encoding.
94 ///
95 /// # Examples
96 ///
97 /// ```
98 /// use async_snmp::oid::Oid;
99 ///
100 /// // Valid OID
101 /// let oid = Oid::parse("1.3.6.1.2.1.1.1.0").unwrap();
102 /// assert!(oid.validate().is_ok());
103 ///
104 /// // Invalid arc1 parses but fails validation
105 /// let invalid = Oid::parse("3.0").unwrap();
106 /// assert!(invalid.validate().is_err());
107 /// ```
108 pub fn parse(s: &str) -> Result<Self> {
109 if s.is_empty() {
110 return Ok(Self::empty());
111 }
112
113 let mut arcs = SmallVec::new();
114
115 for part in s.split('.') {
116 if part.is_empty() {
117 continue;
118 }
119
120 let arc: u32 = part.parse().map_err(|_| {
121 Error::invalid_oid_with_input(OidErrorKind::InvalidArc, s.to_string())
122 })?;
123
124 arcs.push(arc);
125 }
126
127 Ok(Self { arcs })
128 }
129
130 /// Get the arc values.
131 pub fn arcs(&self) -> &[u32] {
132 &self.arcs
133 }
134
135 /// Get the number of arcs.
136 pub fn len(&self) -> usize {
137 self.arcs.len()
138 }
139
140 /// Check if the OID is empty.
141 pub fn is_empty(&self) -> bool {
142 self.arcs.is_empty()
143 }
144
145 /// Check if this OID starts with another OID.
146 ///
147 /// Returns `true` if `self` begins with the same arcs as `other`.
148 /// An OID always starts with itself, and any OID starts with an empty OID.
149 ///
150 /// # Examples
151 ///
152 /// ```
153 /// use async_snmp::oid::Oid;
154 ///
155 /// let sys_descr = Oid::parse("1.3.6.1.2.1.1.1.0").unwrap();
156 /// let system = Oid::parse("1.3.6.1.2.1.1").unwrap();
157 /// let interfaces = Oid::parse("1.3.6.1.2.1.2").unwrap();
158 ///
159 /// // sysDescr is under the system subtree
160 /// assert!(sys_descr.starts_with(&system));
161 ///
162 /// // sysDescr is not under the interfaces subtree
163 /// assert!(!sys_descr.starts_with(&interfaces));
164 ///
165 /// // Every OID starts with itself
166 /// assert!(sys_descr.starts_with(&sys_descr));
167 ///
168 /// // Every OID starts with the empty OID
169 /// assert!(sys_descr.starts_with(&Oid::empty()));
170 /// ```
171 pub fn starts_with(&self, other: &Oid) -> bool {
172 self.arcs.len() >= other.arcs.len() && self.arcs[..other.arcs.len()] == other.arcs[..]
173 }
174
175 /// Get the parent OID (all arcs except the last).
176 ///
177 /// Returns `None` if the OID is empty.
178 ///
179 /// # Examples
180 ///
181 /// ```
182 /// use async_snmp::oid::Oid;
183 ///
184 /// let sys_descr = Oid::parse("1.3.6.1.2.1.1.1.0").unwrap();
185 /// let parent = sys_descr.parent().unwrap();
186 /// assert_eq!(parent.to_string(), "1.3.6.1.2.1.1.1");
187 ///
188 /// // Can chain parent() calls
189 /// let grandparent = parent.parent().unwrap();
190 /// assert_eq!(grandparent.to_string(), "1.3.6.1.2.1.1");
191 ///
192 /// // Empty OID has no parent
193 /// assert!(Oid::empty().parent().is_none());
194 /// ```
195 pub fn parent(&self) -> Option<Oid> {
196 if self.arcs.is_empty() {
197 None
198 } else {
199 Some(Oid {
200 arcs: SmallVec::from_slice(&self.arcs[..self.arcs.len() - 1]),
201 })
202 }
203 }
204
205 /// Create a child OID by appending an arc.
206 ///
207 /// # Examples
208 ///
209 /// ```
210 /// use async_snmp::oid::Oid;
211 ///
212 /// let system = Oid::parse("1.3.6.1.2.1.1").unwrap();
213 ///
214 /// // sysDescr is system.1
215 /// let sys_descr = system.child(1);
216 /// assert_eq!(sys_descr.to_string(), "1.3.6.1.2.1.1.1");
217 ///
218 /// // sysDescr.0 is the scalar instance
219 /// let sys_descr_instance = sys_descr.child(0);
220 /// assert_eq!(sys_descr_instance.to_string(), "1.3.6.1.2.1.1.1.0");
221 /// ```
222 pub fn child(&self, arc: u32) -> Oid {
223 let mut arcs = self.arcs.clone();
224 arcs.push(arc);
225 Oid { arcs }
226 }
227
228 /// Validate OID arcs per X.690 Section 8.19.4.
229 ///
230 /// - arc1 must be 0, 1, or 2
231 /// - arc2 must be <= 39 when arc1 is 0 or 1
232 /// - arc2 can be any value when arc1 is 2
233 ///
234 /// # Examples
235 ///
236 /// ```
237 /// use async_snmp::oid::Oid;
238 ///
239 /// // Standard SNMP OIDs are valid
240 /// let oid = Oid::parse("1.3.6.1.2.1.1.1.0").unwrap();
241 /// assert!(oid.validate().is_ok());
242 ///
243 /// // arc1 must be 0, 1, or 2
244 /// let invalid = Oid::from_slice(&[3, 0]);
245 /// assert!(invalid.validate().is_err());
246 ///
247 /// // arc2 must be <= 39 when arc1 is 0 or 1
248 /// let invalid = Oid::from_slice(&[0, 40]);
249 /// assert!(invalid.validate().is_err());
250 ///
251 /// // arc2 can be any value when arc1 is 2
252 /// let valid = Oid::from_slice(&[2, 999]);
253 /// assert!(valid.validate().is_ok());
254 /// ```
255 pub fn validate(&self) -> Result<()> {
256 if self.arcs.is_empty() {
257 return Ok(());
258 }
259
260 let arc1 = self.arcs[0];
261
262 // arc1 must be 0, 1, or 2
263 if arc1 > 2 {
264 return Err(Error::invalid_oid(OidErrorKind::InvalidFirstArc(arc1)));
265 }
266
267 // arc2 must be <= 39 when arc1 < 2
268 if self.arcs.len() >= 2 {
269 let arc2 = self.arcs[1];
270 if arc1 < 2 && arc2 >= 40 {
271 return Err(Error::invalid_oid(OidErrorKind::InvalidSecondArc {
272 first: arc1,
273 second: arc2,
274 }));
275 }
276 }
277
278 Ok(())
279 }
280
281 /// Validate that the OID doesn't exceed the maximum arc count.
282 ///
283 /// SNMP implementations commonly limit OIDs to 128 subidentifiers. This check
284 /// provides protection against DoS attacks from maliciously long OIDs.
285 ///
286 /// # Examples
287 ///
288 /// ```
289 /// use async_snmp::oid::{Oid, MAX_OID_LEN};
290 ///
291 /// let oid = Oid::parse("1.3.6.1.2.1.1.1.0").unwrap();
292 /// assert!(oid.validate_length().is_ok());
293 ///
294 /// // Create an OID with too many arcs
295 /// let too_long: Vec<u32> = (0..150).collect();
296 /// let long_oid = Oid::new(too_long);
297 /// assert!(long_oid.validate_length().is_err());
298 /// ```
299 pub fn validate_length(&self) -> Result<()> {
300 if self.arcs.len() > MAX_OID_LEN {
301 return Err(Error::invalid_oid(OidErrorKind::TooManyArcs {
302 count: self.arcs.len(),
303 max: MAX_OID_LEN,
304 }));
305 }
306 Ok(())
307 }
308
309 /// Validate both arc constraints and length.
310 ///
311 /// Combines [`validate()`](Self::validate) and [`validate_length()`](Self::validate_length).
312 pub fn validate_all(&self) -> Result<()> {
313 self.validate()?;
314 self.validate_length()
315 }
316
317 /// Encode to BER format, returning bytes in a stack-allocated buffer.
318 ///
319 /// Uses SmallVec to avoid heap allocation for OIDs with up to ~20 arcs.
320 /// This is the optimized version used internally by encoding routines.
321 ///
322 /// OID encoding (X.690 Section 8.19):
323 /// - First two arcs encoded as (arc1 * 40) + arc2 using base-128
324 /// - Remaining arcs encoded as base-128 variable length
325 pub fn to_ber_smallvec(&self) -> SmallVec<[u8; 64]> {
326 let mut bytes = SmallVec::new();
327
328 if self.arcs.is_empty() {
329 return bytes;
330 }
331
332 // First two arcs combined into first subidentifier
333 // Uses base-128 encoding because arc2 can be > 127 when arc1=2
334 if self.arcs.len() >= 2 {
335 let first_subid = self.arcs[0] * 40 + self.arcs[1];
336 encode_subidentifier_smallvec(&mut bytes, first_subid);
337 } else if self.arcs.len() == 1 {
338 let first_subid = self.arcs[0] * 40;
339 encode_subidentifier_smallvec(&mut bytes, first_subid);
340 }
341
342 // Remaining arcs (only if there are more than 2)
343 if self.arcs.len() > 2 {
344 for &arc in &self.arcs[2..] {
345 encode_subidentifier_smallvec(&mut bytes, arc);
346 }
347 }
348
349 bytes
350 }
351
352 /// Encode to BER format.
353 ///
354 /// OID encoding (X.690 Section 8.19):
355 /// - First two arcs encoded as (arc1 * 40) + arc2 using base-128
356 /// - Remaining arcs encoded as base-128 variable length
357 ///
358 /// # Empty OID Encoding
359 ///
360 /// Empty OIDs are encoded as zero bytes (empty content). Note that net-snmp
361 /// encodes empty OIDs as `[0x00]` (single zero byte). This difference is
362 /// unlikely to matter in practice since empty OIDs are rarely used in SNMP.
363 ///
364 /// # Validation
365 ///
366 /// This method does not validate arc constraints. Use [`to_ber_checked()`](Self::to_ber_checked)
367 /// for validation, or call [`validate()`](Self::validate) first.
368 pub fn to_ber(&self) -> Vec<u8> {
369 self.to_ber_smallvec().to_vec()
370 }
371
372 /// Encode to BER format with validation.
373 ///
374 /// Returns an error if the OID has invalid arcs per X.690 Section 8.19.4.
375 pub fn to_ber_checked(&self) -> Result<Vec<u8>> {
376 self.validate()?;
377 Ok(self.to_ber())
378 }
379
380 /// Decode from BER format.
381 pub fn from_ber(data: &[u8]) -> Result<Self> {
382 if data.is_empty() {
383 return Ok(Self::empty());
384 }
385
386 let mut arcs = SmallVec::new();
387
388 // Decode first subidentifier (which encodes arc1*40 + arc2)
389 // This may be multi-byte for large arc2 values (when arc1=2)
390 let (first_subid, consumed) = decode_subidentifier(data)?;
391
392 // Decode first two arcs from the first subidentifier
393 if first_subid < 40 {
394 arcs.push(0);
395 arcs.push(first_subid);
396 } else if first_subid < 80 {
397 arcs.push(1);
398 arcs.push(first_subid - 40);
399 } else {
400 arcs.push(2);
401 arcs.push(first_subid - 80);
402 }
403
404 // Decode remaining arcs
405 let mut i = consumed;
406 while i < data.len() {
407 let (arc, bytes_consumed) = decode_subidentifier(&data[i..])?;
408 arcs.push(arc);
409 i += bytes_consumed;
410 }
411
412 Ok(Self { arcs })
413 }
414}
415
416/// Encode a subidentifier in base-128 variable length into a SmallVec.
417#[inline]
418fn encode_subidentifier_smallvec(bytes: &mut SmallVec<[u8; 64]>, value: u32) {
419 if value == 0 {
420 bytes.push(0);
421 return;
422 }
423
424 // Count how many 7-bit groups we need
425 let mut temp = value;
426 let mut count = 0;
427 while temp > 0 {
428 count += 1;
429 temp >>= 7;
430 }
431
432 // Encode from MSB to LSB
433 for i in (0..count).rev() {
434 let mut byte = ((value >> (i * 7)) & 0x7F) as u8;
435 if i > 0 {
436 byte |= 0x80; // Continuation bit
437 }
438 bytes.push(byte);
439 }
440}
441
442/// Decode a subidentifier, returning (value, bytes_consumed).
443fn decode_subidentifier(data: &[u8]) -> Result<(u32, usize)> {
444 let mut value: u32 = 0;
445 let mut i = 0;
446
447 loop {
448 if i >= data.len() {
449 return Err(Error::decode(i, DecodeErrorKind::TruncatedData));
450 }
451
452 let byte = data[i];
453 i += 1;
454
455 // Check for overflow before shifting
456 if value > (u32::MAX >> 7) {
457 return Err(Error::decode(i, DecodeErrorKind::IntegerOverflow));
458 }
459
460 value = (value << 7) | ((byte & 0x7F) as u32);
461
462 if byte & 0x80 == 0 {
463 // Last byte
464 break;
465 }
466 }
467
468 Ok((value, i))
469}
470
471impl fmt::Debug for Oid {
472 fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
473 write!(f, "Oid({})", self)
474 }
475}
476
477impl fmt::Display for Oid {
478 fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
479 let mut first = true;
480 for arc in &self.arcs {
481 if !first {
482 write!(f, ".")?;
483 }
484 write!(f, "{}", arc)?;
485 first = false;
486 }
487 Ok(())
488 }
489}
490
491impl std::str::FromStr for Oid {
492 type Err = crate::error::Error;
493
494 fn from_str(s: &str) -> Result<Self> {
495 Self::parse(s)
496 }
497}
498
499impl From<&[u32]> for Oid {
500 fn from(arcs: &[u32]) -> Self {
501 Self::from_slice(arcs)
502 }
503}
504
505impl<const N: usize> From<[u32; N]> for Oid {
506 fn from(arcs: [u32; N]) -> Self {
507 Self::new(arcs)
508 }
509}
510
511impl PartialOrd for Oid {
512 fn partial_cmp(&self, other: &Self) -> Option<std::cmp::Ordering> {
513 Some(self.cmp(other))
514 }
515}
516
517impl Ord for Oid {
518 fn cmp(&self, other: &Self) -> std::cmp::Ordering {
519 self.arcs.cmp(&other.arcs)
520 }
521}
522
523/// Macro to create an OID at compile time.
524///
525/// This is the preferred way to create OID constants since it's concise
526/// and avoids parsing overhead.
527///
528/// # Examples
529///
530/// ```
531/// use async_snmp::oid;
532///
533/// // Create an OID for sysDescr.0
534/// let sys_descr = oid!(1, 3, 6, 1, 2, 1, 1, 1, 0);
535/// assert_eq!(sys_descr.to_string(), "1.3.6.1.2.1.1.1.0");
536///
537/// // Trailing commas are allowed
538/// let sys_name = oid!(1, 3, 6, 1, 2, 1, 1, 5, 0,);
539///
540/// // Can use in const contexts (via from_slice)
541/// let interfaces = oid!(1, 3, 6, 1, 2, 1, 2);
542/// assert!(sys_descr.starts_with(&oid!(1, 3, 6, 1, 2, 1, 1)));
543/// ```
544#[macro_export]
545macro_rules! oid {
546 ($($arc:expr),* $(,)?) => {
547 $crate::oid::Oid::from_slice(&[$($arc),*])
548 };
549}
550
551#[cfg(test)]
552mod tests {
553 use super::*;
554
555 #[test]
556 fn test_parse() {
557 let oid = Oid::parse("1.3.6.1.2.1.1.1.0").unwrap();
558 assert_eq!(oid.arcs(), &[1, 3, 6, 1, 2, 1, 1, 1, 0]);
559 }
560
561 #[test]
562 fn test_display() {
563 let oid = Oid::from_slice(&[1, 3, 6, 1, 2, 1, 1, 1, 0]);
564 assert_eq!(oid.to_string(), "1.3.6.1.2.1.1.1.0");
565 }
566
567 #[test]
568 fn test_starts_with() {
569 let oid = Oid::parse("1.3.6.1.2.1.1.1.0").unwrap();
570 let prefix = Oid::parse("1.3.6.1").unwrap();
571 assert!(oid.starts_with(&prefix));
572 assert!(!prefix.starts_with(&oid));
573 }
574
575 #[test]
576 fn test_ber_roundtrip() {
577 let oid = Oid::parse("1.3.6.1.2.1.1.1.0").unwrap();
578 let ber = oid.to_ber();
579 let decoded = Oid::from_ber(&ber).unwrap();
580 assert_eq!(oid, decoded);
581 }
582
583 #[test]
584 fn test_ber_encoding() {
585 // 1.3.6.1 encodes as: (1*40+3)=43, 6, 1 = [0x2B, 0x06, 0x01]
586 let oid = Oid::parse("1.3.6.1").unwrap();
587 assert_eq!(oid.to_ber(), vec![0x2B, 0x06, 0x01]);
588 }
589
590 #[test]
591 fn test_macro() {
592 let oid = oid!(1, 3, 6, 1);
593 assert_eq!(oid.arcs(), &[1, 3, 6, 1]);
594 }
595
596 // AUDIT-001: Test arc validation
597 // X.690 Section 8.19.4: arc1 must be 0, 1, or 2; arc2 must be <= 39 when arc1 < 2
598 #[test]
599 fn test_validate_arc1_must_be_0_1_or_2() {
600 // arc1 = 3 is invalid
601 let oid = Oid::from_slice(&[3, 0]);
602 let result = oid.validate();
603 assert!(result.is_err(), "arc1=3 should be invalid");
604 }
605
606 #[test]
607 fn test_validate_arc2_limit_when_arc1_is_0() {
608 // arc1 = 0, arc2 = 40 is invalid (max is 39)
609 let oid = Oid::from_slice(&[0, 40]);
610 let result = oid.validate();
611 assert!(result.is_err(), "arc2=40 with arc1=0 should be invalid");
612
613 // arc1 = 0, arc2 = 39 is valid
614 let oid = Oid::from_slice(&[0, 39]);
615 assert!(
616 oid.validate().is_ok(),
617 "arc2=39 with arc1=0 should be valid"
618 );
619 }
620
621 #[test]
622 fn test_validate_arc2_limit_when_arc1_is_1() {
623 // arc1 = 1, arc2 = 40 is invalid
624 let oid = Oid::from_slice(&[1, 40]);
625 let result = oid.validate();
626 assert!(result.is_err(), "arc2=40 with arc1=1 should be invalid");
627
628 // arc1 = 1, arc2 = 39 is valid
629 let oid = Oid::from_slice(&[1, 39]);
630 assert!(
631 oid.validate().is_ok(),
632 "arc2=39 with arc1=1 should be valid"
633 );
634 }
635
636 #[test]
637 fn test_validate_arc2_no_limit_when_arc1_is_2() {
638 // arc1 = 2, arc2 can be anything (e.g., 999)
639 let oid = Oid::from_slice(&[2, 999]);
640 assert!(
641 oid.validate().is_ok(),
642 "arc2=999 with arc1=2 should be valid"
643 );
644 }
645
646 #[test]
647 fn test_to_ber_validates_arcs() {
648 // Invalid OID should return error from to_ber_checked
649 let oid = Oid::from_slice(&[3, 0]); // arc1=3 is invalid
650 let result = oid.to_ber_checked();
651 assert!(
652 result.is_err(),
653 "to_ber_checked should fail for invalid arc1"
654 );
655 }
656
657 // AUDIT-002: Test first subidentifier encoding for large arc2 values
658 // X.690 Section 8.19 example: OID {2 999 3} has first subidentifier = 1079
659 #[test]
660 fn test_ber_encoding_large_arc2() {
661 // OID 2.999.3: first subid = 2*40 + 999 = 1079 = 0x437
662 // 1079 in base-128: 0x88 0x37 (continuation bit set on first byte)
663 let oid = Oid::from_slice(&[2, 999, 3]);
664 let ber = oid.to_ber();
665 // First subidentifier 1079 = 0b10000110111 = 7 bits: 0b0110111 (0x37), 7 bits: 0b0001000 (0x08)
666 // In base-128: (1079 >> 7) = 8, (1079 & 0x7F) = 55
667 // So: 0x88 (8 | 0x80), 0x37 (55)
668 assert_eq!(
669 ber[0], 0x88,
670 "first byte should be 0x88 (8 with continuation)"
671 );
672 assert_eq!(
673 ber[1], 0x37,
674 "second byte should be 0x37 (55, no continuation)"
675 );
676 assert_eq!(ber[2], 0x03, "third byte should be 0x03 (arc 3)");
677 assert_eq!(ber.len(), 3, "OID 2.999.3 should encode to 3 bytes");
678 }
679
680 #[test]
681 fn test_ber_roundtrip_large_arc2() {
682 // Ensure roundtrip works for OID with large arc2
683 let oid = Oid::from_slice(&[2, 999, 3]);
684 let ber = oid.to_ber();
685 let decoded = Oid::from_ber(&ber).unwrap();
686 assert_eq!(oid, decoded, "roundtrip should preserve OID 2.999.3");
687 }
688
689 #[test]
690 fn test_ber_encoding_arc2_equals_80() {
691 // Edge case: arc1=2, arc2=0 gives first subid = 80, which is exactly 1 byte
692 let oid = Oid::from_slice(&[2, 0]);
693 let ber = oid.to_ber();
694 assert_eq!(ber, vec![80], "OID 2.0 should encode to [80]");
695 }
696
697 #[test]
698 fn test_ber_encoding_arc2_equals_127() {
699 // arc1=2, arc2=47 gives first subid = 127, still fits in 1 byte
700 let oid = Oid::from_slice(&[2, 47]);
701 let ber = oid.to_ber();
702 assert_eq!(ber, vec![127], "OID 2.47 should encode to [127]");
703 }
704
705 #[test]
706 fn test_ber_encoding_arc2_equals_128_needs_2_bytes() {
707 // arc1=2, arc2=48 gives first subid = 128, needs 2 bytes in base-128
708 let oid = Oid::from_slice(&[2, 48]);
709 let ber = oid.to_ber();
710 // 128 = 0x80 = base-128: 0x81 0x00
711 assert_eq!(
712 ber,
713 vec![0x81, 0x00],
714 "OID 2.48 should encode to [0x81, 0x00]"
715 );
716 }
717
718 #[test]
719 fn test_oid_non_minimal_subidentifier() {
720 // Non-minimal subidentifier encoding with leading 0x80 bytes should be accepted
721 // 0x80 0x01 should decode as 1 (non-minimal: minimal would be just 0x01)
722 // OID: 1.3 followed by arc 1 encoded as 0x80 0x01
723 let result = Oid::from_ber(&[0x2B, 0x80, 0x01]);
724 assert!(
725 result.is_ok(),
726 "should accept non-minimal subidentifier 0x80 0x01"
727 );
728 let oid = result.unwrap();
729 assert_eq!(oid.arcs(), &[1, 3, 1]);
730
731 // 0x80 0x80 0x01 should decode as 1 (two leading 0x80 bytes)
732 let result = Oid::from_ber(&[0x2B, 0x80, 0x80, 0x01]);
733 assert!(
734 result.is_ok(),
735 "should accept non-minimal subidentifier 0x80 0x80 0x01"
736 );
737 let oid = result.unwrap();
738 assert_eq!(oid.arcs(), &[1, 3, 1]);
739
740 // 0x80 0x00 should decode as 0 (non-minimal zero)
741 let result = Oid::from_ber(&[0x2B, 0x80, 0x00]);
742 assert!(
743 result.is_ok(),
744 "should accept non-minimal subidentifier 0x80 0x00"
745 );
746 let oid = result.unwrap();
747 assert_eq!(oid.arcs(), &[1, 3, 0]);
748 }
749
750 // Tests for MAX_OID_LEN validation
751 #[test]
752 fn test_validate_length_within_limit() {
753 // OID with MAX_OID_LEN arcs should be valid
754 let arcs: Vec<u32> = (0..MAX_OID_LEN as u32).collect();
755 let oid = Oid::new(arcs);
756 assert!(
757 oid.validate_length().is_ok(),
758 "OID with exactly MAX_OID_LEN arcs should be valid"
759 );
760 }
761
762 #[test]
763 fn test_validate_length_exceeds_limit() {
764 // OID with more than MAX_OID_LEN arcs should fail
765 let arcs: Vec<u32> = (0..(MAX_OID_LEN + 1) as u32).collect();
766 let oid = Oid::new(arcs);
767 let result = oid.validate_length();
768 assert!(
769 result.is_err(),
770 "OID exceeding MAX_OID_LEN should fail validation"
771 );
772 }
773
774 #[test]
775 fn test_validate_all_combines_checks() {
776 // Valid OID
777 let oid = Oid::from_slice(&[1, 3, 6, 1]);
778 assert!(oid.validate_all().is_ok());
779
780 // Invalid arc1 (fails validate)
781 let oid = Oid::from_slice(&[3, 0]);
782 assert!(oid.validate_all().is_err());
783
784 // Too many arcs (fails validate_length)
785 let arcs: Vec<u32> = (0..(MAX_OID_LEN + 1) as u32).collect();
786 let oid = Oid::new(arcs);
787 assert!(oid.validate_all().is_err());
788 }
789
790 #[test]
791 fn test_oid_fromstr() {
792 // Test basic parsing via FromStr trait
793 let oid: Oid = "1.3.6.1.2.1.1.1.0".parse().unwrap();
794 assert_eq!(oid, oid!(1, 3, 6, 1, 2, 1, 1, 1, 0));
795
796 // Test empty OID
797 let empty: Oid = "".parse().unwrap();
798 assert!(empty.is_empty());
799
800 // Test single arc
801 let single: Oid = "1".parse().unwrap();
802 assert_eq!(single.arcs(), &[1]);
803
804 // Test roundtrip Display -> FromStr
805 let original = oid!(1, 3, 6, 1, 4, 1, 9, 9, 42);
806 let displayed = original.to_string();
807 let parsed: Oid = displayed.parse().unwrap();
808 assert_eq!(original, parsed);
809 }
810
811 #[test]
812 fn test_oid_fromstr_invalid() {
813 // Invalid arc value
814 assert!("1.3.abc.1".parse::<Oid>().is_err());
815
816 // Negative number (parsed as invalid)
817 assert!("1.3.-6.1".parse::<Oid>().is_err());
818 }
819}