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}