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