use crate::error::internal::DecodeErrorKind;
use crate::error::{Error, Result, UNKNOWN_TARGET};
use smallvec::SmallVec;
use std::fmt;
pub const MAX_OID_LEN: usize = 128;
#[derive(Clone, PartialEq, Eq, Hash)]
pub struct Oid {
arcs: SmallVec<[u32; 16]>,
}
impl Oid {
pub fn empty() -> Self {
Self {
arcs: SmallVec::new(),
}
}
pub fn new(arcs: impl IntoIterator<Item = u32>) -> Self {
Self {
arcs: arcs.into_iter().collect(),
}
}
pub fn from_slice(arcs: &[u32]) -> Self {
Self {
arcs: SmallVec::from_slice(arcs),
}
}
pub fn parse(s: &str) -> Result<Self> {
let s = s.strip_prefix('.').unwrap_or(s);
if s.is_empty() {
return Ok(Self::empty());
}
let mut arcs = SmallVec::new();
for part in s.split('.') {
if part.is_empty() {
return Err(Error::InvalidOid(format!("'{}': empty arc", s).into()).boxed());
}
let arc: u32 = part
.parse()
.map_err(|_| Error::InvalidOid(format!("'{}': invalid arc", s).into()).boxed())?;
arcs.push(arc);
}
Ok(Self { arcs })
}
pub fn arcs(&self) -> &[u32] {
&self.arcs
}
pub fn len(&self) -> usize {
self.arcs.len()
}
pub fn is_empty(&self) -> bool {
self.arcs.is_empty()
}
pub fn starts_with(&self, other: &Oid) -> bool {
self.arcs.len() >= other.arcs.len() && self.arcs[..other.arcs.len()] == other.arcs[..]
}
pub fn parent(&self) -> Option<Oid> {
if self.arcs.is_empty() {
None
} else {
Some(Oid {
arcs: SmallVec::from_slice(&self.arcs[..self.arcs.len() - 1]),
})
}
}
pub fn child(&self, arc: u32) -> Oid {
let mut arcs = self.arcs.clone();
arcs.push(arc);
Oid { arcs }
}
pub fn strip_prefix(&self, prefix: &Oid) -> Option<Oid> {
if self.starts_with(prefix) {
Some(Oid::from_slice(&self.arcs[prefix.len()..]))
} else {
None
}
}
pub fn suffix(&self, n: usize) -> Option<&[u32]> {
if n <= self.arcs.len() {
Some(&self.arcs[self.arcs.len() - n..])
} else {
None
}
}
pub fn validate(&self) -> Result<()> {
if self.arcs.is_empty() {
return Ok(());
}
let arc1 = self.arcs[0];
if arc1 > 2 {
return Err(Error::InvalidOid(
format!("first arc must be 0, 1, or 2, got {}", arc1).into(),
)
.boxed());
}
if self.arcs.len() >= 2 {
let arc2 = self.arcs[1];
if arc1 < 2 && arc2 >= 40 {
return Err(Error::InvalidOid(
format!(
"second arc must be <= 39 when first arc is {}, got {}",
arc1, arc2
)
.into(),
)
.boxed());
}
let base = arc1 * 40;
if arc2 > u32::MAX - base {
return Err(
Error::InvalidOid("subidentifier overflow in first two arcs".into()).boxed(),
);
}
}
Ok(())
}
pub fn validate_length(&self) -> Result<()> {
if self.arcs.len() > MAX_OID_LEN {
return Err(Error::InvalidOid(
format!(
"OID has {} arcs, exceeds maximum {}",
self.arcs.len(),
MAX_OID_LEN
)
.into(),
)
.boxed());
}
Ok(())
}
pub fn validate_all(&self) -> Result<()> {
self.validate()?;
self.validate_length()
}
pub(crate) fn to_ber_smallvec(&self) -> SmallVec<[u8; 64]> {
let mut bytes = SmallVec::new();
if self.arcs.is_empty() {
return bytes;
}
encode_subidentifier_smallvec(&mut bytes, first_subidentifier(&self.arcs));
for &arc in self.arcs.iter().skip(2) {
encode_subidentifier_smallvec(&mut bytes, arc);
}
bytes
}
pub fn to_ber(&self) -> Vec<u8> {
self.to_ber_smallvec().to_vec()
}
pub fn to_ber_checked(&self) -> Result<Vec<u8>> {
self.validate()?;
Ok(self.to_ber())
}
pub(crate) fn ber_content_size(&self) -> usize {
use crate::ber::base128_len;
if self.arcs.is_empty() {
return 0;
}
let mut len = 0;
len += base128_len(first_subidentifier(&self.arcs));
for &arc in self.arcs.iter().skip(2) {
len += base128_len(arc);
}
len
}
pub(crate) fn ber_encoded_size(&self) -> usize {
use crate::ber::length_encoded_len;
let content_len = self.ber_content_size();
1 + length_encoded_len(content_len) + content_len
}
pub fn from_ber(data: &[u8]) -> Result<Self> {
if data.is_empty() {
return Ok(Self::empty());
}
let mut arcs = SmallVec::new();
let (first_subid, consumed) = decode_subidentifier(data)?;
if first_subid < 40 {
arcs.push(0);
arcs.push(first_subid);
} else if first_subid < 80 {
arcs.push(1);
arcs.push(first_subid - 40);
} else {
arcs.push(2);
arcs.push(first_subid - 80);
}
let mut i = consumed;
while i < data.len() {
let (arc, bytes_consumed) = decode_subidentifier(&data[i..])?;
arcs.push(arc);
i += bytes_consumed;
if arcs.len() > MAX_OID_LEN {
tracing::debug!(target: "async_snmp::oid", { snmp.offset = %i, kind = %DecodeErrorKind::OidTooLong { count: arcs.len(), max: MAX_OID_LEN } }, "OID exceeds maximum arc count");
return Err(Error::MalformedResponse {
target: UNKNOWN_TARGET,
}
.boxed());
}
}
Ok(Self { arcs })
}
}
#[inline]
fn first_subidentifier(arcs: &SmallVec<[u32; 16]>) -> u32 {
if arcs.len() >= 2 {
arcs[0] * 40 + arcs[1]
} else {
arcs[0] * 40
}
}
#[inline]
fn encode_subidentifier_smallvec(bytes: &mut SmallVec<[u8; 64]>, value: u32) {
if value == 0 {
bytes.push(0);
return;
}
let mut temp = value;
let mut count = 0;
while temp > 0 {
count += 1;
temp >>= 7;
}
for i in (0..count).rev() {
let mut byte = ((value >> (i * 7)) & 0x7F) as u8;
if i > 0 {
byte |= 0x80; }
bytes.push(byte);
}
}
fn decode_subidentifier(data: &[u8]) -> Result<(u32, usize)> {
let mut value: u32 = 0;
let mut i = 0;
loop {
if i >= data.len() {
tracing::debug!(target: "async_snmp::oid", { snmp.offset = %i, kind = %DecodeErrorKind::TruncatedData }, "unexpected end of data in OID subidentifier");
return Err(Error::MalformedResponse {
target: UNKNOWN_TARGET,
}
.boxed());
}
let byte = data[i];
i += 1;
if value > (u32::MAX >> 7) {
tracing::debug!(target: "async_snmp::oid", { snmp.offset = %i, kind = %DecodeErrorKind::IntegerOverflow }, "OID subidentifier overflow");
return Err(Error::MalformedResponse {
target: UNKNOWN_TARGET,
}
.boxed());
}
value = (value << 7) | ((byte & 0x7F) as u32);
if byte & 0x80 == 0 {
break;
}
}
Ok((value, i))
}
impl fmt::Debug for Oid {
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
write!(f, "Oid({})", self)
}
}
impl fmt::Display for Oid {
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
let mut first = true;
for arc in &self.arcs {
if !first {
write!(f, ".")?;
}
write!(f, "{}", arc)?;
first = false;
}
Ok(())
}
}
impl std::str::FromStr for Oid {
type Err = Box<crate::error::Error>;
fn from_str(s: &str) -> std::result::Result<Self, Self::Err> {
Self::parse(s)
}
}
impl From<&[u32]> for Oid {
fn from(arcs: &[u32]) -> Self {
Self::from_slice(arcs)
}
}
impl<const N: usize> From<[u32; N]> for Oid {
fn from(arcs: [u32; N]) -> Self {
Self::new(arcs)
}
}
impl From<Vec<u32>> for Oid {
fn from(arcs: Vec<u32>) -> Self {
Self {
arcs: SmallVec::from_vec(arcs),
}
}
}
impl AsRef<[u32]> for Oid {
fn as_ref(&self) -> &[u32] {
self.arcs()
}
}
impl<'a> IntoIterator for &'a Oid {
type Item = &'a u32;
type IntoIter = std::slice::Iter<'a, u32>;
fn into_iter(self) -> Self::IntoIter {
self.arcs().iter()
}
}
impl IntoIterator for Oid {
type Item = u32;
type IntoIter = smallvec::IntoIter<[u32; 16]>;
fn into_iter(self) -> Self::IntoIter {
self.arcs.into_iter()
}
}
impl PartialOrd for Oid {
fn partial_cmp(&self, other: &Self) -> Option<std::cmp::Ordering> {
Some(self.cmp(other))
}
}
impl Ord for Oid {
fn cmp(&self, other: &Self) -> std::cmp::Ordering {
self.arcs.cmp(&other.arcs)
}
}
#[macro_export]
macro_rules! oid {
($($arc:expr),* $(,)?) => {
$crate::oid::Oid::from_slice(&[$($arc),*])
};
}
#[cfg(feature = "mib")]
impl From<&mib_rs::Oid> for Oid {
fn from(oid: &mib_rs::Oid) -> Self {
Oid::from_slice(oid.as_ref())
}
}
#[cfg(feature = "mib")]
impl From<mib_rs::Oid> for Oid {
fn from(oid: mib_rs::Oid) -> Self {
Oid::from_slice(oid.as_ref())
}
}
#[cfg(feature = "mib")]
impl Oid {
pub fn to_mib_oid(&self) -> mib_rs::Oid {
mib_rs::Oid::from(self.arcs())
}
}
#[cfg(test)]
mod tests {
use super::*;
#[test]
fn test_parse() {
let oid = Oid::parse("1.3.6.1.2.1.1.1.0").unwrap();
assert_eq!(oid.arcs(), &[1, 3, 6, 1, 2, 1, 1, 1, 0]);
}
#[test]
fn test_parse_leading_dot() {
let oid = Oid::parse(".1.3.6").unwrap();
assert_eq!(oid.arcs(), &[1, 3, 6]);
let oid = Oid::parse(".1.3.6.1.2.1").unwrap();
assert_eq!(oid.arcs(), &[1, 3, 6, 1, 2, 1]);
let oid = Oid::parse(".0").unwrap();
assert_eq!(oid.arcs(), &[0]);
let oid = Oid::parse(".").unwrap();
assert!(oid.is_empty());
}
#[test]
fn test_parse_rejects_empty_components() {
assert!(Oid::parse("1.3.6.").is_err()); assert!(Oid::parse("1..3.6").is_err()); assert!(Oid::parse("..1.3").is_err()); assert!(Oid::parse("...").is_err()); }
#[test]
fn test_display() {
let oid = Oid::from_slice(&[1, 3, 6, 1, 2, 1, 1, 1, 0]);
assert_eq!(oid.to_string(), "1.3.6.1.2.1.1.1.0");
}
#[test]
fn test_starts_with() {
let oid = Oid::parse("1.3.6.1.2.1.1.1.0").unwrap();
let prefix = Oid::parse("1.3.6.1").unwrap();
assert!(oid.starts_with(&prefix));
assert!(!prefix.starts_with(&oid));
}
#[test]
fn test_ber_roundtrip() {
let oid = Oid::parse("1.3.6.1.2.1.1.1.0").unwrap();
let ber = oid.to_ber();
let decoded = Oid::from_ber(&ber).unwrap();
assert_eq!(oid, decoded);
}
#[test]
fn test_ber_encoding() {
let oid = Oid::parse("1.3.6.1").unwrap();
assert_eq!(oid.to_ber(), vec![0x2B, 0x06, 0x01]);
}
#[test]
fn test_macro() {
let oid = oid!(1, 3, 6, 1);
assert_eq!(oid.arcs(), &[1, 3, 6, 1]);
}
#[test]
fn from_vec_u32() {
let v = vec![1, 3, 6, 1, 2, 1];
let oid = Oid::from(v);
assert_eq!(oid.arcs(), &[1, 3, 6, 1, 2, 1]);
}
#[test]
fn oid_as_ref() {
let oid = Oid::new([1, 3, 6, 1]);
let slice: &[u32] = oid.as_ref();
assert_eq!(slice, &[1, 3, 6, 1]);
}
#[test]
fn test_validate_arc1_must_be_0_1_or_2() {
let oid = Oid::from_slice(&[3, 0]);
let result = oid.validate();
assert!(result.is_err(), "arc1=3 should be invalid");
}
#[test]
fn test_validate_arc2_limit_when_arc1_is_0() {
let oid = Oid::from_slice(&[0, 40]);
let result = oid.validate();
assert!(result.is_err(), "arc2=40 with arc1=0 should be invalid");
let oid = Oid::from_slice(&[0, 39]);
assert!(
oid.validate().is_ok(),
"arc2=39 with arc1=0 should be valid"
);
}
#[test]
fn test_validate_arc2_limit_when_arc1_is_1() {
let oid = Oid::from_slice(&[1, 40]);
let result = oid.validate();
assert!(result.is_err(), "arc2=40 with arc1=1 should be invalid");
let oid = Oid::from_slice(&[1, 39]);
assert!(
oid.validate().is_ok(),
"arc2=39 with arc1=1 should be valid"
);
}
#[test]
fn test_validate_arc2_no_limit_when_arc1_is_2() {
let oid = Oid::from_slice(&[2, 999]);
assert!(
oid.validate().is_ok(),
"arc2=999 with arc1=2 should be valid"
);
}
#[test]
fn test_to_ber_validates_arcs() {
let oid = Oid::from_slice(&[3, 0]); let result = oid.to_ber_checked();
assert!(
result.is_err(),
"to_ber_checked should fail for invalid arc1"
);
}
#[test]
fn test_ber_encoding_large_arc2() {
let oid = Oid::from_slice(&[2, 999, 3]);
let ber = oid.to_ber();
assert_eq!(
ber[0], 0x88,
"first byte should be 0x88 (8 with continuation)"
);
assert_eq!(
ber[1], 0x37,
"second byte should be 0x37 (55, no continuation)"
);
assert_eq!(ber[2], 0x03, "third byte should be 0x03 (arc 3)");
assert_eq!(ber.len(), 3, "OID 2.999.3 should encode to 3 bytes");
}
#[test]
fn test_ber_roundtrip_large_arc2() {
let oid = Oid::from_slice(&[2, 999, 3]);
let ber = oid.to_ber();
let decoded = Oid::from_ber(&ber).unwrap();
assert_eq!(oid, decoded, "roundtrip should preserve OID 2.999.3");
}
#[test]
fn test_ber_encoding_arc2_equals_80() {
let oid = Oid::from_slice(&[2, 0]);
let ber = oid.to_ber();
assert_eq!(ber, vec![80], "OID 2.0 should encode to [80]");
}
#[test]
fn test_ber_encoding_arc2_equals_127() {
let oid = Oid::from_slice(&[2, 47]);
let ber = oid.to_ber();
assert_eq!(ber, vec![127], "OID 2.47 should encode to [127]");
}
#[test]
fn test_ber_encoding_arc2_equals_128_needs_2_bytes() {
let oid = Oid::from_slice(&[2, 48]);
let ber = oid.to_ber();
assert_eq!(
ber,
vec![0x81, 0x00],
"OID 2.48 should encode to [0x81, 0x00]"
);
}
#[test]
fn test_from_ber_zero_length() {
let result = Oid::from_ber(&[]);
assert!(result.is_ok(), "zero-length OID content should be accepted");
assert!(result.unwrap().is_empty());
}
#[test]
fn test_oid_non_minimal_subidentifier() {
let result = Oid::from_ber(&[0x2B, 0x80, 0x01]);
assert!(
result.is_ok(),
"should accept non-minimal subidentifier 0x80 0x01"
);
let oid = result.unwrap();
assert_eq!(oid.arcs(), &[1, 3, 1]);
let result = Oid::from_ber(&[0x2B, 0x80, 0x80, 0x01]);
assert!(
result.is_ok(),
"should accept non-minimal subidentifier 0x80 0x80 0x01"
);
let oid = result.unwrap();
assert_eq!(oid.arcs(), &[1, 3, 1]);
let result = Oid::from_ber(&[0x2B, 0x80, 0x00]);
assert!(
result.is_ok(),
"should accept non-minimal subidentifier 0x80 0x00"
);
let oid = result.unwrap();
assert_eq!(oid.arcs(), &[1, 3, 0]);
}
#[test]
fn test_validate_length_within_limit() {
let arcs: Vec<u32> = (0..MAX_OID_LEN as u32).collect();
let oid = Oid::new(arcs);
assert!(
oid.validate_length().is_ok(),
"OID with exactly MAX_OID_LEN arcs should be valid"
);
}
#[test]
fn test_validate_length_exceeds_limit() {
let arcs: Vec<u32> = (0..(MAX_OID_LEN + 1) as u32).collect();
let oid = Oid::new(arcs);
let result = oid.validate_length();
assert!(
result.is_err(),
"OID exceeding MAX_OID_LEN should fail validation"
);
}
#[test]
fn test_validate_all_combines_checks() {
let oid = Oid::from_slice(&[1, 3, 6, 1]);
assert!(oid.validate_all().is_ok());
let oid = Oid::from_slice(&[3, 0]);
assert!(oid.validate_all().is_err());
let arcs: Vec<u32> = (0..(MAX_OID_LEN + 1) as u32).collect();
let oid = Oid::new(arcs);
assert!(oid.validate_all().is_err());
}
#[test]
fn test_oid_fromstr() {
let oid: Oid = "1.3.6.1.2.1.1.1.0".parse().unwrap();
assert_eq!(oid, oid!(1, 3, 6, 1, 2, 1, 1, 1, 0));
let empty: Oid = "".parse().unwrap();
assert!(empty.is_empty());
let single: Oid = "1".parse().unwrap();
assert_eq!(single.arcs(), &[1]);
let original = oid!(1, 3, 6, 1, 4, 1, 9, 9, 42);
let displayed = original.to_string();
let parsed: Oid = displayed.parse().unwrap();
assert_eq!(original, parsed);
}
#[test]
fn test_oid_fromstr_invalid() {
assert!("1.3.abc.1".parse::<Oid>().is_err());
assert!("1.3.-6.1".parse::<Oid>().is_err());
}
#[test]
fn test_validate_arc2_overflow_when_arc1_is_2() {
let max_valid_arc2 = u32::MAX - 80;
let oid = Oid::from_slice(&[2, max_valid_arc2]);
assert!(
oid.validate().is_ok(),
"arc2={} with arc1=2 should be valid (max that fits)",
max_valid_arc2
);
let overflow_arc2 = u32::MAX - 79; let oid = Oid::from_slice(&[2, overflow_arc2]);
assert!(
oid.validate().is_err(),
"arc2={} with arc1=2 should be invalid (would overflow first subidentifier)",
overflow_arc2
);
let oid = Oid::from_slice(&[2, u32::MAX]);
assert!(
oid.validate().is_err(),
"arc2=u32::MAX with arc1=2 should be invalid"
);
}
#[test]
fn test_to_ber_checked_rejects_overflow() {
let oid = Oid::from_slice(&[2, u32::MAX]);
let result = oid.to_ber_checked();
assert!(
result.is_err(),
"to_ber_checked should reject OID that would overflow"
);
}
#[test]
fn test_from_ber_enforces_max_oid_len() {
let mut ber_at_limit = vec![0x2B]; ber_at_limit.extend(std::iter::repeat_n(0x01, MAX_OID_LEN - 2));
let result = Oid::from_ber(&ber_at_limit);
assert!(
result.is_ok(),
"OID with exactly MAX_OID_LEN arcs should decode successfully"
);
assert_eq!(result.unwrap().len(), MAX_OID_LEN);
let mut ber_over_limit = vec![0x2B]; ber_over_limit.extend(std::iter::repeat_n(0x01, MAX_OID_LEN - 1));
let result = Oid::from_ber(&ber_over_limit);
assert!(
result.is_err(),
"OID exceeding MAX_OID_LEN should fail to decode"
);
}
#[test]
fn test_strip_prefix() {
let if_descr_5 = oid!(1, 3, 6, 1, 2, 1, 2, 2, 1, 2, 5);
let if_descr = oid!(1, 3, 6, 1, 2, 1, 2, 2, 1, 2);
let index = if_descr_5.strip_prefix(&if_descr).unwrap();
assert_eq!(index.arcs(), &[5]);
let sys_descr = oid!(1, 3, 6, 1, 2, 1, 1, 1);
assert!(if_descr_5.strip_prefix(&sys_descr).is_none());
let same = oid!(1, 3, 6);
assert!(same.strip_prefix(&same).unwrap().is_empty());
let any = oid!(1, 2, 3);
assert_eq!(any.strip_prefix(&Oid::empty()).unwrap(), any);
let composite = oid!(1, 3, 6, 1, 2, 1, 4, 22, 1, 2, 1, 192, 168, 1, 100);
let column = oid!(1, 3, 6, 1, 2, 1, 4, 22, 1, 2);
let idx = composite.strip_prefix(&column).unwrap();
assert_eq!(idx.arcs(), &[1, 192, 168, 1, 100]);
}
#[test]
fn test_suffix() {
let oid = oid!(1, 3, 6, 1, 2, 1, 4, 22, 1, 2, 1, 192, 168, 1, 100);
assert_eq!(oid.suffix(5), Some(&[1, 192, 168, 1, 100][..]));
assert_eq!(oid.suffix(1), Some(&[100][..]));
assert_eq!(oid.suffix(0), Some(&[][..]));
assert_eq!(oid.suffix(15), Some(oid.arcs()));
assert!(oid.suffix(16).is_none());
assert!(oid.suffix(100).is_none());
let empty = Oid::empty();
assert_eq!(empty.suffix(0), Some(&[][..]));
assert!(empty.suffix(1).is_none());
}
#[test]
fn oid_into_iter_ref() {
let oid = Oid::new([1, 3, 6]);
let arcs: Vec<&u32> = (&oid).into_iter().collect();
assert_eq!(arcs, vec![&1, &3, &6]);
}
#[test]
fn oid_into_iter_owned() {
let oid = Oid::new([1, 3, 6]);
let arcs: Vec<u32> = oid.into_iter().collect();
assert_eq!(arcs, vec![1, 3, 6]);
}
#[test]
fn oid_for_loop() {
let oid = Oid::new([1, 3, 6]);
let mut sum = 0u32;
for arc in &oid {
sum += arc;
}
assert_eq!(sum, 10);
}
}