#![cfg_attr(not(test), allow(dead_code))]
use core::any::Any;
use core::fmt;
use core::ops::Div;
use super::{ber, pdu::SnmpPdu, registry};
use crate::error::Result;
use crate::packet::{IntoPacket, Layer, LayerContext, Packet};
pub(super) const SNMP_VERSION_VALUE_V1: i64 = 0;
pub(super) const SNMP_VERSION_VALUE_V2C: i64 = 1;
pub(super) const SNMP_VERSION_VALUE_V3: i64 = 3;
const SNMP_MESSAGE_COMMUNITY_CONTEXT: &str = "snmp.message.community";
const SNMP_V3_HEADER_DATA_CONTEXT: &str = "snmp.v3.header_data";
const SNMP_V3_FLAGS_CONTEXT: &str = "snmp.v3.flags";
const SNMP_V3_SECURITY_PARAMETERS_CONTEXT: &str = "snmp.v3.security_parameters";
const SNMP_V3_SCOPED_DATA_CONTEXT: &str = "snmp.v3.scoped_data";
const SNMP_V3_SCOPED_PDU_CONTEXT: &str = "snmp.v3.scoped_pdu";
const SNMP_V3_CONTEXT_ENGINE_ID_CONTEXT: &str = "snmp.v3.context_engine_id";
const SNMP_V3_CONTEXT_NAME_CONTEXT: &str = "snmp.v3.context_name";
const SNMP_USM_SECURITY_PARAMETERS_CONTEXT: &str = "snmp.usm.security_parameters";
const SNMP_USM_ENGINE_ID_CONTEXT: &str = "snmp.usm.engine_id";
const SNMP_USM_USER_NAME_CONTEXT: &str = "snmp.usm.user_name";
const SNMP_USM_AUTHENTICATION_PARAMETERS_CONTEXT: &str = "snmp.usm.authentication_parameters";
const SNMP_USM_PRIVACY_PARAMETERS_CONTEXT: &str = "snmp.usm.privacy_parameters";
#[derive(Debug, Clone, Copy, PartialEq, Eq)]
pub enum SnmpVersion {
V1,
V2c,
V3,
Unknown(i64),
}
impl SnmpVersion {
pub(super) const fn from_integer(value: i64) -> Self {
match value {
SNMP_VERSION_VALUE_V1 => Self::V1,
SNMP_VERSION_VALUE_V2C => Self::V2c,
SNMP_VERSION_VALUE_V3 => Self::V3,
value => Self::Unknown(value),
}
}
pub const fn as_integer(self) -> i64 {
match self {
Self::V1 => SNMP_VERSION_VALUE_V1,
Self::V2c => SNMP_VERSION_VALUE_V2C,
Self::V3 => SNMP_VERSION_VALUE_V3,
Self::Unknown(value) => value,
}
}
pub const fn label(self) -> &'static str {
match self {
Self::V1 => "v1",
Self::V2c => "v2c",
Self::V3 => "v3",
Self::Unknown(_) => "unknown",
}
}
pub(super) fn decode(bytes: &[u8]) -> Result<(Self, &[u8])> {
let (value, rest) = ber::decode_integer(bytes)?;
Ok((Self::from_integer(value), rest))
}
pub(super) fn encode(self, out: &mut Vec<u8>) -> Result<()> {
ber::encode_integer(self.as_integer(), out)
}
}
impl fmt::Display for SnmpVersion {
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
match self {
Self::Unknown(value) => write!(f, "unknown({value})"),
_ => f.write_str(self.label()),
}
}
}
#[derive(Debug, Clone, PartialEq, Eq)]
pub(super) struct SnmpCommunity {
bytes: Vec<u8>,
}
impl SnmpCommunity {
pub(super) fn new(bytes: impl Into<Vec<u8>>) -> Self {
Self {
bytes: bytes.into(),
}
}
pub(super) fn as_bytes(&self) -> &[u8] {
&self.bytes
}
pub(super) fn decode(bytes: &[u8]) -> Result<(Self, &[u8])> {
let (content, rest) = decode_community_octet_string(bytes)?;
Ok((Self::new(content.to_vec()), rest))
}
pub(super) fn encode(&self, out: &mut Vec<u8>) -> Result<()> {
encode_community_octet_string(self.as_bytes(), out)
}
}
#[derive(Debug, Clone, PartialEq, Eq)]
struct SnmpMessageHeader {
version: SnmpVersion,
community: SnmpCommunity,
}
impl SnmpMessageHeader {
fn new(version: SnmpVersion, community: impl Into<Vec<u8>>) -> Self {
Self {
version,
community: SnmpCommunity::new(community),
}
}
const fn version(&self) -> SnmpVersion {
self.version
}
fn community(&self) -> &[u8] {
self.community.as_bytes()
}
pub(super) fn decode(bytes: &[u8]) -> Result<(Self, &[u8])> {
let (version, rest) = SnmpVersion::decode(bytes)?;
let (community, rest) = SnmpCommunity::decode(rest)?;
Ok((Self { version, community }, rest))
}
pub(super) fn encode(&self, out: &mut Vec<u8>) -> Result<()> {
self.version.encode(out)?;
self.community.encode(out)
}
pub(super) fn to_bytes(&self) -> Result<Vec<u8>> {
let mut out = Vec::new();
self.encode(&mut out)?;
Ok(out)
}
}
#[derive(Debug, Clone, PartialEq, Eq)]
enum SnmpMessageData {
Community {
header: SnmpMessageHeader,
pdu: SnmpPdu,
},
V3(SnmpV3Message),
}
#[derive(Debug, Clone, PartialEq, Eq)]
pub struct SnmpV3GlobalData {
msg_id: i64,
max_size: i64,
flags: Vec<u8>,
security_model: i64,
}
impl SnmpV3GlobalData {
pub fn new(msg_id: i64, max_size: i64, flags: impl Into<Vec<u8>>, security_model: i64) -> Self {
Self {
msg_id,
max_size,
flags: flags.into(),
security_model,
}
}
pub fn with_msg_id(mut self, msg_id: i64) -> Self {
self.msg_id = msg_id;
self
}
pub fn with_max_size(mut self, max_size: i64) -> Self {
self.max_size = max_size;
self
}
pub fn with_flags(mut self, flags: impl Into<Vec<u8>>) -> Self {
self.flags = flags.into();
self
}
pub fn with_security_model(mut self, security_model: i64) -> Self {
self.security_model = security_model;
self
}
pub const fn msg_id(&self) -> i64 {
self.msg_id
}
pub const fn max_size(&self) -> i64 {
self.max_size
}
pub fn flags(&self) -> &[u8] {
&self.flags
}
pub fn flags_value(&self) -> registry::SnmpV3Flags {
registry::SnmpV3Flags::from_octets(&self.flags)
}
pub const fn security_model(&self) -> i64 {
self.security_model
}
pub const fn security_model_value(&self) -> registry::SnmpSecurityModel {
registry::SnmpSecurityModel::new(self.security_model)
}
pub const fn security_model_name(&self) -> Option<&'static str> {
registry::snmp_security_model_name(self.security_model)
}
pub const fn security_model_status(&self) -> registry::SnmpSecurityModelStatus {
registry::snmp_security_model_status(self.security_model)
}
pub fn security_model_label(&self) -> String {
registry::snmp_security_model_label(self.security_model)
}
pub fn decode(bytes: &[u8]) -> Result<(Self, &[u8])> {
let (header_content, rest) = ber::decode_sequence(bytes)?;
let (msg_id, header_rest) = ber::decode_integer(header_content)?;
let (max_size, header_rest) = ber::decode_integer(header_rest)?;
let (flags, header_rest) = decode_octet_string_tlv(
header_rest,
SNMP_V3_FLAGS_CONTEXT,
"expected universal primitive OCTET STRING for msgFlags",
"msgFlags length exceeds supported size",
)?;
let (security_model, header_rest) = ber::decode_integer(header_rest)?;
if !header_rest.is_empty() {
return Err(ber::invalid_ber_field(
SNMP_V3_HEADER_DATA_CONTEXT,
"trailing bytes after HeaderData fields",
));
}
Ok((
Self::new(msg_id, max_size, flags.to_vec(), security_model),
rest,
))
}
pub fn encode(&self, out: &mut Vec<u8>) -> Result<()> {
let mut content = Vec::with_capacity(self.encoded_content_len());
ber::encode_integer(self.msg_id, &mut content)?;
ber::encode_integer(self.max_size, &mut content)?;
encode_octet_string_tlv(&self.flags, &mut content)?;
ber::encode_integer(self.security_model, &mut content)?;
ber::encode_sequence(&content, out)
}
pub fn to_bytes(&self) -> Result<Vec<u8>> {
let mut out = Vec::with_capacity(self.encoded_len());
self.encode(&mut out)?;
Ok(out)
}
pub fn compile(&self) -> Result<Vec<u8>> {
self.to_bytes()
}
pub fn encoded_len(&self) -> usize {
encoded_tlv_len(self.encoded_content_len())
}
fn encoded_content_len(&self) -> usize {
encoded_integer_tlv_len(self.msg_id)
+ encoded_integer_tlv_len(self.max_size)
+ encoded_tlv_len(self.flags.len())
+ encoded_integer_tlv_len(self.security_model)
}
pub fn summary(&self) -> String {
format!("SnmpV3GlobalData({})", self.summary_fields())
}
fn summary_fields(&self) -> String {
let flags = self.flags_value();
format!(
"msg_id={} msg_max_size={} msg_flags={} msg_flags_len={} msg_security_model={} msg_security_model_label={}",
self.msg_id,
self.max_size,
flags,
self.flags.len(),
self.security_model,
self.security_model_label()
)
}
pub fn inspection_fields(&self) -> Vec<(&'static str, String)> {
let flags = self.flags_value();
vec![
("msg_id", self.msg_id.to_string()),
("msg_max_size", self.max_size.to_string()),
("msg_flags_len", self.flags.len().to_string()),
("msg_flags", ber::hex_bytes(&self.flags)),
("msg_flags_label", flags.label()),
("msg_flags_auth", flags.auth().to_string()),
("msg_flags_privacy", flags.privacy().to_string()),
("msg_flags_reportable", flags.reportable().to_string()),
(
"msg_flags_reserved_bits",
format!("0x{:02x}", flags.reserved_bits()),
),
(
"msg_flags_reserved_auth_priv",
flags.has_reserved_auth_priv_combination().to_string(),
),
("msg_security_model", self.security_model.to_string()),
("msg_security_model_label", self.security_model_label()),
(
"msg_security_model_status",
self.security_model_status().to_string(),
),
]
}
}
#[derive(Debug, Clone, PartialEq, Eq)]
pub struct SnmpScopedPdu {
context_engine_id: Vec<u8>,
context_name: Vec<u8>,
pdu: SnmpPdu,
}
impl SnmpScopedPdu {
pub fn new(
context_engine_id: impl Into<Vec<u8>>,
context_name: impl Into<Vec<u8>>,
pdu: SnmpPdu,
) -> Self {
Self {
context_engine_id: context_engine_id.into(),
context_name: context_name.into(),
pdu,
}
}
pub fn context_engine_id(&self) -> &[u8] {
&self.context_engine_id
}
pub fn context_name(&self) -> &[u8] {
&self.context_name
}
pub const fn pdu(&self) -> &SnmpPdu {
&self.pdu
}
pub fn pdu_mut(&mut self) -> &mut SnmpPdu {
&mut self.pdu
}
pub fn into_pdu(self) -> SnmpPdu {
self.pdu
}
pub fn decode(bytes: &[u8]) -> Result<(Self, &[u8])> {
let (content, rest) = ber::decode_sequence(bytes)?;
let (context_engine_id, content_rest) = decode_octet_string_tlv(
content,
SNMP_V3_CONTEXT_ENGINE_ID_CONTEXT,
"expected universal primitive OCTET STRING for contextEngineID",
"contextEngineID length exceeds supported size",
)?;
let (context_name, content_rest) = decode_octet_string_tlv(
content_rest,
SNMP_V3_CONTEXT_NAME_CONTEXT,
"expected universal primitive OCTET STRING for contextName",
"contextName length exceeds supported size",
)?;
let (pdu, content_rest) = SnmpPdu::decode(content_rest)?;
if !content_rest.is_empty() {
return Err(ber::invalid_ber_field(
SNMP_V3_SCOPED_PDU_CONTEXT,
"trailing bytes after ScopedPDU fields",
));
}
Ok((
Self::new(context_engine_id.to_vec(), context_name.to_vec(), pdu),
rest,
))
}
pub fn encode(&self, out: &mut Vec<u8>) -> Result<()> {
let mut content = Vec::with_capacity(self.encoded_content_len());
encode_octet_string_tlv(&self.context_engine_id, &mut content)?;
encode_octet_string_tlv(&self.context_name, &mut content)?;
self.pdu.encode(&mut content)?;
ber::encode_sequence(&content, out)
}
pub fn to_bytes(&self) -> Result<Vec<u8>> {
let mut out = Vec::with_capacity(self.encoded_len());
self.encode(&mut out)?;
Ok(out)
}
pub fn compile(&self) -> Result<Vec<u8>> {
self.to_bytes()
}
pub fn encoded_len(&self) -> usize {
encoded_tlv_len(self.encoded_content_len())
}
fn encoded_content_len(&self) -> usize {
encoded_tlv_len(self.context_engine_id.len())
+ encoded_tlv_len(self.context_name.len())
+ encoded_pdu_len(&self.pdu)
}
pub fn summary(&self) -> String {
format!(
"SnmpScopedPdu(context_engine_id_len={} context_name_len={} pdu={})",
self.context_engine_id.len(),
self.context_name.len(),
self.pdu.summary()
)
}
pub fn inspection_fields(&self) -> Vec<(&'static str, String)> {
let mut fields = vec![
(
"context_engine_id_len",
self.context_engine_id.len().to_string(),
),
(
"context_engine_id_bytes",
ber::hex_bytes(&self.context_engine_id),
),
("context_name_len", self.context_name.len().to_string()),
("context_name_bytes", ber::hex_bytes(&self.context_name)),
];
fields.extend(self.pdu.inspection_fields());
fields
}
pub fn show(&self) -> String {
let mut output = "SnmpScopedPdu".to_string();
for (name, value) in self.inspection_fields() {
output.push_str(&format!("\n {name}: {value}"));
}
output
}
}
#[derive(Debug, Clone, Copy, PartialEq, Eq)]
pub struct SnmpUsmEngineTime {
engine_boots: i64,
engine_time: i64,
}
impl SnmpUsmEngineTime {
pub const fn new(engine_boots: i64, engine_time: i64) -> Self {
Self {
engine_boots,
engine_time,
}
}
pub const fn engine_boots(self) -> i64 {
self.engine_boots
}
pub const fn engine_time(self) -> i64 {
self.engine_time
}
pub fn summary(self) -> String {
format!(
"SnmpUsmEngineTime(engine_boots={} engine_time={})",
self.engine_boots, self.engine_time
)
}
pub fn inspection_fields(self) -> Vec<(&'static str, String)> {
vec![
("usm_engine_boots", self.engine_boots.to_string()),
("usm_engine_time", self.engine_time.to_string()),
]
}
pub fn show(self) -> String {
let mut output = "SnmpUsmEngineTime".to_string();
for (name, value) in self.inspection_fields() {
output.push_str(&format!("\n {name}: {value}"));
}
output
}
}
#[derive(Debug, Clone, PartialEq, Eq)]
pub struct SnmpUsmSecurityParameters {
engine_id: Vec<u8>,
engine_boots: i64,
engine_time: i64,
user_name: Vec<u8>,
authentication_parameters: Vec<u8>,
privacy_parameters: Vec<u8>,
}
impl SnmpUsmSecurityParameters {
pub fn new(
engine_id: impl Into<Vec<u8>>,
engine_boots: i64,
engine_time: i64,
user_name: impl Into<Vec<u8>>,
authentication_parameters: impl Into<Vec<u8>>,
privacy_parameters: impl Into<Vec<u8>>,
) -> Self {
Self {
engine_id: engine_id.into(),
engine_boots,
engine_time,
user_name: user_name.into(),
authentication_parameters: authentication_parameters.into(),
privacy_parameters: privacy_parameters.into(),
}
}
pub fn from_engine_time(
engine_id: impl Into<Vec<u8>>,
engine_time: SnmpUsmEngineTime,
user_name: impl Into<Vec<u8>>,
authentication_parameters: impl Into<Vec<u8>>,
privacy_parameters: impl Into<Vec<u8>>,
) -> Self {
Self::new(
engine_id,
engine_time.engine_boots(),
engine_time.engine_time(),
user_name,
authentication_parameters,
privacy_parameters,
)
}
pub fn with_engine_boots(mut self, engine_boots: i64) -> Self {
self.engine_boots = engine_boots;
self
}
pub fn with_engine_time(mut self, engine_time: i64) -> Self {
self.engine_time = engine_time;
self
}
pub fn with_engine_time_fields(mut self, engine_time: SnmpUsmEngineTime) -> Self {
self.engine_boots = engine_time.engine_boots();
self.engine_time = engine_time.engine_time();
self
}
pub fn with_authentication_parameters(
mut self,
authentication_parameters: impl Into<Vec<u8>>,
) -> Self {
self.authentication_parameters = authentication_parameters.into();
self
}
pub fn with_privacy_parameters(mut self, privacy_parameters: impl Into<Vec<u8>>) -> Self {
self.privacy_parameters = privacy_parameters.into();
self
}
pub fn engine_id(&self) -> &[u8] {
&self.engine_id
}
pub const fn engine_boots(&self) -> i64 {
self.engine_boots
}
pub const fn engine_time(&self) -> i64 {
self.engine_time
}
pub const fn engine_time_fields(&self) -> SnmpUsmEngineTime {
SnmpUsmEngineTime::new(self.engine_boots, self.engine_time)
}
pub fn user_name(&self) -> &[u8] {
&self.user_name
}
pub fn authentication_parameters(&self) -> &[u8] {
&self.authentication_parameters
}
pub fn authentication_parameters_len(&self) -> usize {
self.authentication_parameters.len()
}
pub fn privacy_parameters(&self) -> &[u8] {
&self.privacy_parameters
}
pub fn privacy_parameters_len(&self) -> usize {
self.privacy_parameters.len()
}
pub fn decode(bytes: &[u8]) -> Result<(Self, &[u8])> {
let (content, rest) = ber::decode_sequence(bytes)?;
let (engine_id, content_rest) = decode_octet_string_tlv(
content,
SNMP_USM_ENGINE_ID_CONTEXT,
"expected universal primitive OCTET STRING for msgAuthoritativeEngineID",
"msgAuthoritativeEngineID length exceeds supported size",
)?;
let (engine_boots, content_rest) = ber::decode_integer(content_rest)?;
let (engine_time, content_rest) = ber::decode_integer(content_rest)?;
let (user_name, content_rest) = decode_octet_string_tlv(
content_rest,
SNMP_USM_USER_NAME_CONTEXT,
"expected universal primitive OCTET STRING for msgUserName",
"msgUserName length exceeds supported size",
)?;
let (authentication_parameters, content_rest) = decode_octet_string_tlv(
content_rest,
SNMP_USM_AUTHENTICATION_PARAMETERS_CONTEXT,
"expected universal primitive OCTET STRING for msgAuthenticationParameters",
"msgAuthenticationParameters length exceeds supported size",
)?;
let (privacy_parameters, content_rest) = decode_octet_string_tlv(
content_rest,
SNMP_USM_PRIVACY_PARAMETERS_CONTEXT,
"expected universal primitive OCTET STRING for msgPrivacyParameters",
"msgPrivacyParameters length exceeds supported size",
)?;
if !content_rest.is_empty() {
return Err(ber::invalid_ber_field(
SNMP_USM_SECURITY_PARAMETERS_CONTEXT,
"trailing bytes after USM security parameter fields",
));
}
Ok((
Self::new(
engine_id.to_vec(),
engine_boots,
engine_time,
user_name.to_vec(),
authentication_parameters.to_vec(),
privacy_parameters.to_vec(),
),
rest,
))
}
pub fn encode(&self, out: &mut Vec<u8>) -> Result<()> {
let mut content = Vec::with_capacity(self.encoded_content_len());
encode_octet_string_tlv(&self.engine_id, &mut content)?;
ber::encode_integer(self.engine_boots, &mut content)?;
ber::encode_integer(self.engine_time, &mut content)?;
encode_octet_string_tlv(&self.user_name, &mut content)?;
encode_octet_string_tlv(&self.authentication_parameters, &mut content)?;
encode_octet_string_tlv(&self.privacy_parameters, &mut content)?;
ber::encode_sequence(&content, out)
}
pub fn to_bytes(&self) -> Result<Vec<u8>> {
let mut out = Vec::with_capacity(self.encoded_len());
self.encode(&mut out)?;
Ok(out)
}
pub fn compile(&self) -> Result<Vec<u8>> {
self.to_bytes()
}
pub fn encoded_len(&self) -> usize {
encoded_tlv_len(self.encoded_content_len())
}
fn encoded_content_len(&self) -> usize {
encoded_tlv_len(self.engine_id.len())
+ encoded_integer_tlv_len(self.engine_boots)
+ encoded_integer_tlv_len(self.engine_time)
+ encoded_tlv_len(self.user_name.len())
+ encoded_tlv_len(self.authentication_parameters.len())
+ encoded_tlv_len(self.privacy_parameters.len())
}
pub fn summary(&self) -> String {
format!(
"SnmpUsmSecurityParameters(engine_id_len={} engine_boots={} engine_time={} user_name_len={} authentication_parameters_len={} privacy_parameters_len={})",
self.engine_id.len(),
self.engine_boots,
self.engine_time,
self.user_name.len(),
self.authentication_parameters.len(),
self.privacy_parameters.len()
)
}
pub fn inspection_fields(&self) -> Vec<(&'static str, String)> {
vec![
("usm_engine_id_len", self.engine_id.len().to_string()),
("usm_engine_boots", self.engine_boots.to_string()),
("usm_engine_time", self.engine_time.to_string()),
("usm_user_name_len", self.user_name.len().to_string()),
(
"usm_authentication_parameters_len",
self.authentication_parameters.len().to_string(),
),
(
"usm_privacy_parameters_len",
self.privacy_parameters.len().to_string(),
),
]
}
pub fn show(&self) -> String {
let mut output = "SnmpUsmSecurityParameters".to_string();
for (name, value) in self.inspection_fields() {
output.push_str(&format!("\n {name}: {value}"));
}
output
}
}
#[derive(Debug, Clone, PartialEq, Eq)]
pub struct SnmpRawSecurityParameters {
security_model: i64,
bytes: Vec<u8>,
}
impl SnmpRawSecurityParameters {
pub fn new(security_model: i64, bytes: impl Into<Vec<u8>>) -> Self {
Self {
security_model,
bytes: bytes.into(),
}
}
pub fn from_usm(usm: &SnmpUsmSecurityParameters) -> Result<Self> {
Ok(Self::new(registry::SNMP_SECURITY_MODEL_USM, usm.compile()?))
}
pub const fn security_model(&self) -> i64 {
self.security_model
}
pub const fn security_model_value(&self) -> registry::SnmpSecurityModel {
registry::SnmpSecurityModel::new(self.security_model)
}
pub fn security_model_label(&self) -> String {
registry::snmp_security_model_label(self.security_model)
}
pub const fn security_model_status(&self) -> registry::SnmpSecurityModelStatus {
registry::snmp_security_model_status(self.security_model)
}
pub fn bytes(&self) -> &[u8] {
&self.bytes
}
pub fn len(&self) -> usize {
self.bytes.len()
}
pub fn is_empty(&self) -> bool {
self.bytes.is_empty()
}
pub fn decode(security_model: i64, bytes: &[u8]) -> Result<(Self, &[u8])> {
let (parameters, rest) = decode_octet_string_tlv(
bytes,
SNMP_V3_SECURITY_PARAMETERS_CONTEXT,
"expected universal primitive OCTET STRING for msgSecurityParameters",
"msgSecurityParameters length exceeds supported size",
)?;
Ok((Self::new(security_model, parameters.to_vec()), rest))
}
pub fn as_usm(&self) -> Result<Option<SnmpUsmSecurityParameters>> {
if self.security_model != registry::SNMP_SECURITY_MODEL_USM {
return Ok(None);
}
let (usm, rest) = SnmpUsmSecurityParameters::decode(&self.bytes)?;
ber::require_sequence_exact(rest)?;
Ok(Some(usm))
}
pub fn encode(&self, out: &mut Vec<u8>) -> Result<()> {
encode_octet_string_tlv(&self.bytes, out)
}
pub fn to_bytes(&self) -> Result<Vec<u8>> {
let mut out = Vec::with_capacity(self.encoded_len());
self.encode(&mut out)?;
Ok(out)
}
pub fn compile(&self) -> Result<Vec<u8>> {
self.to_bytes()
}
pub fn encoded_len(&self) -> usize {
encoded_tlv_len(self.bytes.len())
}
pub fn summary(&self) -> String {
format!(
"SnmpRawSecurityParameters(security_model={} security_model_label={} len={})",
self.security_model,
self.security_model_label(),
self.bytes.len()
)
}
pub fn inspection_fields(&self) -> Vec<(&'static str, String)> {
vec![
("msg_security_model", self.security_model.to_string()),
("msg_security_model_label", self.security_model_label()),
(
"msg_security_model_status",
self.security_model_status().to_string(),
),
("msg_security_parameters_len", self.bytes.len().to_string()),
]
}
}
#[derive(Debug, Clone, PartialEq, Eq)]
pub struct SnmpEncryptedScopedData {
privacy_parameters: Vec<u8>,
encrypted_pdu: Vec<u8>,
}
impl SnmpEncryptedScopedData {
pub fn new(privacy_parameters: impl Into<Vec<u8>>, encrypted_pdu: impl Into<Vec<u8>>) -> Self {
Self {
privacy_parameters: privacy_parameters.into(),
encrypted_pdu: encrypted_pdu.into(),
}
}
pub fn privacy_parameters(&self) -> &[u8] {
&self.privacy_parameters
}
pub fn privacy_parameters_len(&self) -> usize {
self.privacy_parameters.len()
}
pub fn encrypted_pdu(&self) -> &[u8] {
&self.encrypted_pdu
}
pub fn encrypted_pdu_len(&self) -> usize {
self.encrypted_pdu.len()
}
pub fn encode(&self, out: &mut Vec<u8>) -> Result<()> {
encode_octet_string_tlv(&self.encrypted_pdu, out)
}
pub fn to_bytes(&self) -> Result<Vec<u8>> {
let mut out = Vec::with_capacity(self.encoded_len());
self.encode(&mut out)?;
Ok(out)
}
pub fn compile(&self) -> Result<Vec<u8>> {
self.to_bytes()
}
pub fn encoded_len(&self) -> usize {
encoded_tlv_len(self.encrypted_pdu.len())
}
pub fn summary(&self) -> String {
format!(
"SnmpEncryptedScopedData(encrypted_pdu_len={} privacy_parameters_len={})",
self.encrypted_pdu.len(),
self.privacy_parameters.len()
)
}
pub fn inspection_fields(&self) -> Vec<(&'static str, String)> {
vec![
(
"encrypted_scoped_pdu_len",
self.encrypted_pdu.len().to_string(),
),
(
"usm_privacy_parameters_len",
self.privacy_parameters.len().to_string(),
),
]
}
pub fn show(&self) -> String {
let mut output = "SnmpEncryptedScopedData".to_string();
for (name, value) in self.inspection_fields() {
output.push_str(&format!("\n {name}: {value}"));
}
output
}
}
#[derive(Debug, Clone, PartialEq, Eq)]
pub struct SnmpV3Message {
version: SnmpVersion,
global_data: SnmpV3GlobalData,
security_parameters: SnmpRawSecurityParameters,
scoped_data: Vec<u8>,
}
impl SnmpV3Message {
pub fn new(
msg_id: i64,
max_size: i64,
flags: impl Into<Vec<u8>>,
security_model: i64,
security_parameters: impl Into<Vec<u8>>,
scoped_data: impl Into<Vec<u8>>,
) -> Self {
Self {
version: SnmpVersion::V3,
global_data: SnmpV3GlobalData::new(msg_id, max_size, flags, security_model),
security_parameters: SnmpRawSecurityParameters::new(
security_model,
security_parameters,
),
scoped_data: scoped_data.into(),
}
}
pub fn new_plaintext(
msg_id: i64,
max_size: i64,
flags: impl Into<Vec<u8>>,
security_model: i64,
security_parameters: impl Into<Vec<u8>>,
scoped_pdu: SnmpScopedPdu,
) -> Result<Self> {
Ok(Self::new(
msg_id,
max_size,
flags,
security_model,
security_parameters,
scoped_pdu.compile()?,
))
}
pub fn new_usm(
msg_id: i64,
max_size: i64,
flags: impl Into<Vec<u8>>,
usm: SnmpUsmSecurityParameters,
scoped_data: impl Into<Vec<u8>>,
) -> Result<Self> {
Ok(Self::new(
msg_id,
max_size,
flags,
registry::SNMP_SECURITY_MODEL_USM,
usm.compile()?,
scoped_data,
))
}
pub fn new_encrypted_usm(
msg_id: i64,
max_size: i64,
flags: impl Into<Vec<u8>>,
usm: SnmpUsmSecurityParameters,
encrypted_pdu: impl Into<Vec<u8>>,
) -> Result<Self> {
let encrypted_scoped_data =
SnmpEncryptedScopedData::new(usm.privacy_parameters().to_vec(), encrypted_pdu);
Self::new_usm(
msg_id,
max_size,
flags,
usm,
encrypted_scoped_data.compile()?,
)
}
pub fn new_plaintext_report(
msg_id: i64,
max_size: i64,
flags: impl Into<Vec<u8>>,
security_model: i64,
security_parameters: impl Into<Vec<u8>>,
context_engine_id: impl Into<Vec<u8>>,
context_name: impl Into<Vec<u8>>,
request_id: i64,
varbinds: super::SnmpVarBindList,
) -> Result<Self> {
Self::new_plaintext(
msg_id,
max_size,
flags,
security_model,
security_parameters,
SnmpScopedPdu::new(
context_engine_id,
context_name,
SnmpPdu::report(request_id, varbinds)?,
),
)
}
pub fn new_usm_report(
msg_id: i64,
max_size: i64,
flags: impl Into<Vec<u8>>,
usm: SnmpUsmSecurityParameters,
context_engine_id: impl Into<Vec<u8>>,
context_name: impl Into<Vec<u8>>,
request_id: i64,
varbinds: super::SnmpVarBindList,
) -> Result<Self> {
let scoped_pdu = SnmpScopedPdu::new(
context_engine_id,
context_name,
SnmpPdu::report(request_id, varbinds)?,
);
Self::new_usm(msg_id, max_size, flags, usm, scoped_pdu.compile()?)
}
const fn with_version(mut self, version: SnmpVersion) -> Self {
self.version = version;
self
}
pub const fn version(&self) -> SnmpVersion {
self.version
}
pub const fn version_value(&self) -> i64 {
self.version.as_integer()
}
pub const fn global_data(&self) -> &SnmpV3GlobalData {
&self.global_data
}
pub const fn msg_id(&self) -> i64 {
self.global_data.msg_id()
}
pub const fn max_size(&self) -> i64 {
self.global_data.max_size()
}
pub fn flags(&self) -> &[u8] {
self.global_data.flags()
}
pub fn flags_value(&self) -> registry::SnmpV3Flags {
self.global_data.flags_value()
}
pub const fn security_model(&self) -> i64 {
self.global_data.security_model()
}
pub fn security_parameters(&self) -> &[u8] {
self.security_parameters.bytes()
}
pub const fn raw_security_parameters(&self) -> &SnmpRawSecurityParameters {
&self.security_parameters
}
pub fn usm_security_parameters(&self) -> Result<Option<SnmpUsmSecurityParameters>> {
self.security_parameters.as_usm()
}
pub fn scoped_data(&self) -> &[u8] {
&self.scoped_data
}
pub fn scoped_pdu(&self) -> Result<Option<SnmpScopedPdu>> {
let (tag, _) = ber::decode_identifier(&self.scoped_data)?;
if tag != ber::BerTag::new(ber::BerClass::Universal, true, ber::BER_TAG_SEQUENCE) {
return Ok(None);
}
let (scoped_pdu, rest) = SnmpScopedPdu::decode(&self.scoped_data)?;
ber::require_sequence_exact(rest)?;
Ok(Some(scoped_pdu))
}
pub fn encrypted_scoped_data(&self) -> Result<Option<SnmpEncryptedScopedData>> {
let Some(encrypted_pdu) = decode_encrypted_scoped_pdu_content(&self.scoped_data)? else {
return Ok(None);
};
let privacy_parameters = self
.usm_security_parameters()?
.map(|usm| usm.privacy_parameters().to_vec())
.unwrap_or_default();
Ok(Some(SnmpEncryptedScopedData::new(
privacy_parameters,
encrypted_pdu.to_vec(),
)))
}
pub fn scoped_data_kind(&self) -> &'static str {
scoped_data_kind_label(&self.scoped_data)
}
pub fn encrypted_scoped_pdu_len(&self) -> Result<Option<usize>> {
Ok(decode_encrypted_scoped_pdu_content(&self.scoped_data)?.map(<[u8]>::len))
}
fn decode_after_version(version: SnmpVersion, bytes: &[u8]) -> Result<(Self, &[u8])> {
let (global_data, rest) = SnmpV3GlobalData::decode(bytes)?;
let (security_parameters, rest) =
SnmpRawSecurityParameters::decode(global_data.security_model(), rest)?;
let (scoped_data, rest) = decode_scoped_data_tlv(rest)?;
Ok((
Self {
version,
global_data,
security_parameters,
scoped_data: scoped_data.to_vec(),
},
rest,
))
}
fn encode_content_after_version(&self, out: &mut Vec<u8>) -> Result<()> {
self.global_data.encode(out)?;
self.security_parameters.encode(out)?;
out.extend_from_slice(&self.scoped_data);
Ok(())
}
fn encoded_content_after_version_len(&self) -> usize {
self.global_data.encoded_len()
+ self.security_parameters.encoded_len()
+ self.scoped_data.len()
}
fn summary_fields(&self) -> String {
let encrypted_len = self.encrypted_scoped_pdu_len().ok().flatten().unwrap_or(0);
let usm_summary = self
.usm_security_parameters()
.ok()
.flatten()
.map(|usm| format!(" usm={}", usm.summary()))
.unwrap_or_default();
let scoped_summary = self
.scoped_pdu()
.ok()
.flatten()
.map(|scoped_pdu| format!(" scoped_pdu={}", scoped_pdu.summary()))
.unwrap_or_default();
let encrypted_summary = self
.encrypted_scoped_data()
.ok()
.flatten()
.map(|encrypted| format!(" encrypted_scoped_data={}", encrypted.summary()))
.unwrap_or_default();
format!(
"{} msg_security_parameters_len={} scoped_data_kind={} scoped_data_len={} encrypted_scoped_pdu_len={}{}{}{}",
self.global_data.summary_fields(),
self.security_parameters.len(),
self.scoped_data_kind(),
self.scoped_data.len(),
encrypted_len,
usm_summary,
scoped_summary,
encrypted_summary
)
}
fn inspection_fields(&self) -> Vec<(&'static str, String)> {
let mut fields = self.global_data.inspection_fields();
fields.push((
"msg_security_parameters_len",
self.security_parameters.len().to_string(),
));
if let Ok(Some(usm)) = self.usm_security_parameters() {
fields.extend(usm.inspection_fields());
}
fields.push(("scoped_data_kind", self.scoped_data_kind().to_string()));
fields.push(("scoped_data_len", self.scoped_data.len().to_string()));
if let Ok(Some(scoped_pdu)) = self.scoped_pdu() {
fields.extend(scoped_pdu.inspection_fields());
}
match self.encrypted_scoped_data() {
Ok(Some(encrypted)) => fields.extend(encrypted.inspection_fields()),
_ => {
if let Ok(Some(encrypted_len)) = self.encrypted_scoped_pdu_len() {
fields.push(("encrypted_scoped_pdu_len", encrypted_len.to_string()));
}
}
}
fields
}
}
#[derive(Debug, Clone, PartialEq, Eq)]
pub struct Snmp {
data: SnmpMessageData,
length: Option<usize>,
}
impl Snmp {
pub fn v1(community: impl Into<Vec<u8>>, pdu: SnmpPdu) -> Self {
Self::community_message(SnmpVersion::V1, community, pdu)
}
pub fn v1_get_request(
community: impl Into<Vec<u8>>,
request_id: i64,
varbinds: super::SnmpVarBindList,
) -> Result<Self> {
Ok(Self::v1(
community,
SnmpPdu::get_request(request_id, varbinds)?,
))
}
pub fn v1_get_next_request(
community: impl Into<Vec<u8>>,
request_id: i64,
varbinds: super::SnmpVarBindList,
) -> Result<Self> {
Ok(Self::v1(
community,
SnmpPdu::get_next_request(request_id, varbinds)?,
))
}
pub fn v1_set_request(
community: impl Into<Vec<u8>>,
request_id: i64,
varbinds: super::SnmpVarBindList,
) -> Result<Self> {
Ok(Self::v1(
community,
SnmpPdu::set_request(request_id, varbinds)?,
))
}
pub fn v1_response(
community: impl Into<Vec<u8>>,
request_id: i64,
varbinds: super::SnmpVarBindList,
) -> Result<Self> {
Ok(Self::v1(
community,
SnmpPdu::response(request_id, varbinds)?,
))
}
pub fn v1_response_error(
community: impl Into<Vec<u8>>,
request_id: i64,
error_status: i64,
error_index: i64,
varbinds: super::SnmpVarBindList,
) -> Result<Self> {
Ok(Self::v1(
community,
SnmpPdu::response_error(request_id, error_status, error_index, varbinds)?,
))
}
pub fn v1_trap(
community: impl Into<Vec<u8>>,
enterprise: super::SnmpOid,
agent_address: [u8; 4],
generic_trap: i64,
specific_trap: i64,
timestamp: u32,
varbinds: super::SnmpVarBindList,
) -> Result<Self> {
Ok(Self::v1(
community,
SnmpPdu::v1_trap(
enterprise,
agent_address,
generic_trap,
specific_trap,
timestamp,
varbinds,
)?,
))
}
pub fn v2c(community: impl Into<Vec<u8>>, pdu: SnmpPdu) -> Self {
Self::community_message(SnmpVersion::V2c, community, pdu)
}
pub fn v2c_get_request(
community: impl Into<Vec<u8>>,
request_id: i64,
varbinds: super::SnmpVarBindList,
) -> Result<Self> {
Ok(Self::v2c(
community,
SnmpPdu::get_request(request_id, varbinds)?,
))
}
pub fn v2c_get_next_request(
community: impl Into<Vec<u8>>,
request_id: i64,
varbinds: super::SnmpVarBindList,
) -> Result<Self> {
Ok(Self::v2c(
community,
SnmpPdu::get_next_request(request_id, varbinds)?,
))
}
pub fn v2c_set_request(
community: impl Into<Vec<u8>>,
request_id: i64,
varbinds: super::SnmpVarBindList,
) -> Result<Self> {
Ok(Self::v2c(
community,
SnmpPdu::set_request(request_id, varbinds)?,
))
}
pub fn v2c_response(
community: impl Into<Vec<u8>>,
request_id: i64,
varbinds: super::SnmpVarBindList,
) -> Result<Self> {
Ok(Self::v2c(
community,
SnmpPdu::response(request_id, varbinds)?,
))
}
pub fn v2c_response_error(
community: impl Into<Vec<u8>>,
request_id: i64,
error_status: i64,
error_index: i64,
varbinds: super::SnmpVarBindList,
) -> Result<Self> {
Ok(Self::v2c(
community,
SnmpPdu::response_error(request_id, error_status, error_index, varbinds)?,
))
}
pub fn v2c_get_bulk_request(
community: impl Into<Vec<u8>>,
request_id: i64,
non_repeaters: i64,
max_repetitions: i64,
varbinds: super::SnmpVarBindList,
) -> Result<Self> {
Ok(Self::v2c(
community,
SnmpPdu::get_bulk_request(request_id, non_repeaters, max_repetitions, varbinds)?,
))
}
pub fn v2c_inform_request(
community: impl Into<Vec<u8>>,
request_id: i64,
varbinds: super::SnmpVarBindList,
) -> Result<Self> {
Ok(Self::v2c(
community,
SnmpPdu::inform_request(request_id, varbinds)?,
))
}
pub fn v2c_snmpv2_trap(
community: impl Into<Vec<u8>>,
request_id: i64,
varbinds: super::SnmpVarBindList,
) -> Result<Self> {
Ok(Self::v2c(
community,
SnmpPdu::snmpv2_trap(request_id, varbinds)?,
))
}
pub fn v2c_report(
community: impl Into<Vec<u8>>,
request_id: i64,
varbinds: super::SnmpVarBindList,
) -> Result<Self> {
Ok(Self::v2c(community, SnmpPdu::report(request_id, varbinds)?))
}
pub fn v3(
msg_id: i64,
max_size: i64,
flags: impl Into<Vec<u8>>,
security_model: i64,
security_parameters: impl Into<Vec<u8>>,
scoped_data: impl Into<Vec<u8>>,
) -> Self {
Self::from_v3_message(SnmpV3Message::new(
msg_id,
max_size,
flags,
security_model,
security_parameters,
scoped_data,
))
}
pub fn v3_plaintext(
msg_id: i64,
max_size: i64,
flags: impl Into<Vec<u8>>,
security_model: i64,
security_parameters: impl Into<Vec<u8>>,
scoped_pdu: SnmpScopedPdu,
) -> Result<Self> {
Ok(Self::from_v3_message(SnmpV3Message::new_plaintext(
msg_id,
max_size,
flags,
security_model,
security_parameters,
scoped_pdu,
)?))
}
pub fn v3_encrypted_usm(
msg_id: i64,
max_size: i64,
flags: impl Into<Vec<u8>>,
usm: SnmpUsmSecurityParameters,
encrypted_pdu: impl Into<Vec<u8>>,
) -> Result<Self> {
Ok(Self::from_v3_message(SnmpV3Message::new_encrypted_usm(
msg_id,
max_size,
flags,
usm,
encrypted_pdu,
)?))
}
pub fn v3_report(
msg_id: i64,
max_size: i64,
flags: impl Into<Vec<u8>>,
security_model: i64,
security_parameters: impl Into<Vec<u8>>,
context_engine_id: impl Into<Vec<u8>>,
context_name: impl Into<Vec<u8>>,
request_id: i64,
varbinds: super::SnmpVarBindList,
) -> Result<Self> {
Ok(Self::from_v3_message(SnmpV3Message::new_plaintext_report(
msg_id,
max_size,
flags,
security_model,
security_parameters,
context_engine_id,
context_name,
request_id,
varbinds,
)?))
}
pub fn v3_usm_report(
msg_id: i64,
max_size: i64,
flags: impl Into<Vec<u8>>,
usm: SnmpUsmSecurityParameters,
context_engine_id: impl Into<Vec<u8>>,
context_name: impl Into<Vec<u8>>,
request_id: i64,
varbinds: super::SnmpVarBindList,
) -> Result<Self> {
Ok(Self::from_v3_message(SnmpV3Message::new_usm_report(
msg_id,
max_size,
flags,
usm,
context_engine_id,
context_name,
request_id,
varbinds,
)?))
}
pub fn from_v3_message(message: SnmpV3Message) -> Self {
Self {
data: SnmpMessageData::V3(message),
length: None,
}
}
fn community_message(
version: SnmpVersion,
community: impl Into<Vec<u8>>,
pdu: SnmpPdu,
) -> Self {
Self {
data: SnmpMessageData::Community {
header: SnmpMessageHeader::new(version, community),
pdu,
},
length: None,
}
}
pub const fn version(&self) -> SnmpVersion {
match &self.data {
SnmpMessageData::Community { header, .. } => header.version(),
SnmpMessageData::V3(message) => message.version(),
}
}
pub const fn version_value(&self) -> i64 {
self.version().as_integer()
}
pub const fn version_label(&self) -> &'static str {
self.version().label()
}
pub fn community(&self) -> &[u8] {
match &self.data {
SnmpMessageData::Community { header, .. } => header.community(),
SnmpMessageData::V3(_) => &[],
}
}
pub const fn pdu(&self) -> &SnmpPdu {
match &self.data {
SnmpMessageData::Community { pdu, .. } => pdu,
SnmpMessageData::V3(_) => panic!("SNMPv3 scoped PDU parsing is not available"),
}
}
pub const fn pdu_opt(&self) -> Option<&SnmpPdu> {
match &self.data {
SnmpMessageData::Community { pdu, .. } => Some(pdu),
SnmpMessageData::V3(_) => None,
}
}
pub const fn as_v3(&self) -> Option<&SnmpV3Message> {
match &self.data {
SnmpMessageData::Community { .. } => None,
SnmpMessageData::V3(message) => Some(message),
}
}
pub fn with_version(mut self, version: SnmpVersion) -> Self {
match &mut self.data {
SnmpMessageData::Community { header, .. } => header.version = version,
SnmpMessageData::V3(message) => {
*message = message.clone().with_version(version);
}
}
self
}
pub fn with_community(mut self, community: impl Into<Vec<u8>>) -> Self {
if let SnmpMessageData::Community { header, .. } = &mut self.data {
header.community = SnmpCommunity::new(community);
}
self
}
pub fn with_pdu(mut self, pdu: SnmpPdu) -> Self {
if let SnmpMessageData::Community {
pdu: current_pdu, ..
} = &mut self.data
{
*current_pdu = pdu;
}
self
}
pub fn length(mut self, length: usize) -> Self {
self.length = Some(length);
self
}
pub fn clear_length(mut self) -> Self {
self.length = None;
self
}
pub const fn explicit_length(&self) -> Option<usize> {
self.length
}
pub fn effective_length(&self) -> usize {
self.length.unwrap_or_else(|| self.encoded_content_len())
}
pub fn pdu_mut(&mut self) -> &mut SnmpPdu {
match &mut self.data {
SnmpMessageData::Community { pdu, .. } => pdu,
SnmpMessageData::V3(_) => panic!("SNMPv3 scoped PDU parsing is not available"),
}
}
pub fn into_pdu(self) -> SnmpPdu {
match self.data {
SnmpMessageData::Community { pdu, .. } => pdu,
SnmpMessageData::V3(_) => panic!("SNMPv3 scoped PDU parsing is not available"),
}
}
pub fn encoded_len(&self) -> usize {
ber::BER_IDENTIFIER_LEN
+ encoded_length_len(self.effective_length())
+ self.encoded_content_len()
}
pub fn encode(&self, out: &mut Vec<u8>) -> Result<()> {
let mut content = Vec::with_capacity(self.encoded_content_len());
match &self.data {
SnmpMessageData::Community { header, pdu } => {
header.encode(&mut content)?;
pdu.encode(&mut content)?;
}
SnmpMessageData::V3(message) => {
message.version().encode(&mut content)?;
message.encode_content_after_version(&mut content)?;
}
}
encode_message_sequence(&content, self.explicit_length(), out)
}
pub fn decode(bytes: &[u8]) -> Result<Self> {
let content = ber::decode_sequence_exact(bytes)?;
let (version, rest) = SnmpVersion::decode(content)?;
let data = if version == SnmpVersion::V3 {
let (message, rest) = SnmpV3Message::decode_after_version(version, rest)?;
ber::require_sequence_exact(rest)?;
SnmpMessageData::V3(message)
} else {
let (community, rest) = SnmpCommunity::decode(rest)?;
let (pdu, rest) = SnmpPdu::decode(rest)?;
ber::require_sequence_exact(rest)?;
SnmpMessageData::Community {
header: SnmpMessageHeader { version, community },
pdu,
}
};
Ok(Self { data, length: None })
}
pub fn to_bytes(&self) -> Result<Vec<u8>> {
let mut out = Vec::with_capacity(self.encoded_len());
self.encode(&mut out)?;
Ok(out)
}
pub fn compile(&self) -> Result<Vec<u8>> {
self.to_bytes()
}
pub fn summary(&self) -> String {
match &self.data {
SnmpMessageData::Community { pdu, .. } => format!(
"Snmp(version={}, community_len={}, pdu={})",
self.version(),
self.community().len(),
pdu.summary()
),
SnmpMessageData::V3(message) => {
format!(
"Snmp(version={}, {})",
self.version(),
message.summary_fields()
)
}
}
}
pub fn inspection_fields(&self) -> Vec<(&'static str, String)> {
let mut fields = vec![
("version", self.version().to_string()),
("version_value", self.version_value().to_string()),
("message_len", self.encoded_len().to_string()),
];
match &self.data {
SnmpMessageData::Community { pdu, .. } => {
fields.push(("community_len", self.community().len().to_string()));
fields.extend(pdu.inspection_fields());
}
SnmpMessageData::V3(message) => fields.extend(message.inspection_fields()),
}
fields
}
pub fn show(&self) -> String {
let mut output = "Snmp".to_string();
for (name, value) in self.inspection_fields() {
output.push_str(&format!("\n {name}: {value}"));
}
output
}
fn encoded_content_len(&self) -> usize {
encoded_integer_tlv_len(self.version_value())
+ match &self.data {
SnmpMessageData::Community { pdu, .. } => {
encoded_tlv_len(self.community().len()) + encoded_pdu_len(pdu)
}
SnmpMessageData::V3(message) => message.encoded_content_after_version_len(),
}
}
}
impl Layer for Snmp {
fn name(&self) -> &'static str {
"Snmp"
}
fn summary(&self) -> String {
Snmp::summary(self)
}
fn inspection_fields(&self) -> Vec<(&'static str, String)> {
Snmp::inspection_fields(self)
}
fn encoded_len(&self) -> usize {
Snmp::encoded_len(self)
}
fn compile(&self, _ctx: &LayerContext<'_>, out: &mut Vec<u8>) -> Result<()> {
self.encode(out)
}
fn clone_layer(&self) -> Box<dyn Layer> {
Box::new(self.clone())
}
fn as_any(&self) -> &dyn Any {
self
}
fn as_any_mut(&mut self) -> &mut dyn Any {
self
}
fn into_any(self: Box<Self>) -> Box<dyn Any> {
self
}
}
impl<R> Div<R> for Snmp
where
R: IntoPacket,
{
type Output = Packet;
fn div(self, rhs: R) -> Self::Output {
Packet::from_layer(self).concat(rhs)
}
}
fn decode_community_octet_string(bytes: &[u8]) -> Result<(&[u8], &[u8])> {
decode_octet_string_tlv(
bytes,
SNMP_MESSAGE_COMMUNITY_CONTEXT,
"expected universal primitive OCTET STRING",
"community length exceeds supported size",
)
}
fn encode_community_octet_string(bytes: &[u8], out: &mut Vec<u8>) -> Result<()> {
encode_octet_string_tlv(bytes, out)
}
fn decode_octet_string_tlv<'a>(
bytes: &'a [u8],
context: &'static str,
tag_reason: &'static str,
overflow_reason: &'static str,
) -> Result<(&'a [u8], &'a [u8])> {
let (tag, rest) = ber::decode_identifier(bytes)?;
if tag != ber::BerTag::new(ber::BerClass::Universal, false, ber::BER_TAG_OCTET_STRING) {
return Err(ber::invalid_ber_field(context, tag_reason));
}
let (length, rest) = ber::decode_length(rest)?;
if rest.len() < length {
let prefix_len = bytes.len() - rest.len();
let required = prefix_len
.checked_add(length)
.ok_or_else(|| ber::invalid_ber_field(context, overflow_reason))?;
return Err(ber::truncated_ber(context, required, bytes.len()));
}
Ok(rest.split_at(length))
}
fn encode_octet_string_tlv(bytes: &[u8], out: &mut Vec<u8>) -> Result<()> {
ber::encode_identifier(
ber::BerTag::new(ber::BerClass::Universal, false, ber::BER_TAG_OCTET_STRING),
out,
)?;
ber::encode_length(bytes.len(), out)?;
out.extend_from_slice(bytes);
Ok(())
}
fn decode_scoped_data_tlv(bytes: &[u8]) -> Result<(&[u8], &[u8])> {
let (tag, rest) = ber::decode_identifier(bytes)?;
let is_plaintext =
tag == ber::BerTag::new(ber::BerClass::Universal, true, ber::BER_TAG_SEQUENCE);
let is_encrypted =
tag == ber::BerTag::new(ber::BerClass::Universal, false, ber::BER_TAG_OCTET_STRING);
if !is_plaintext && !is_encrypted {
return Err(ber::invalid_ber_field(
SNMP_V3_SCOPED_DATA_CONTEXT,
"expected plaintext ScopedPDU SEQUENCE or encrypted OCTET STRING",
));
}
let (length, rest) = ber::decode_length(rest)?;
if rest.len() < length {
let prefix_len = bytes.len() - rest.len();
let required = prefix_len.checked_add(length).ok_or_else(|| {
ber::invalid_ber_field(
SNMP_V3_SCOPED_DATA_CONTEXT,
"ScopedPduData length exceeds supported size",
)
})?;
return Err(ber::truncated_ber(
SNMP_V3_SCOPED_DATA_CONTEXT,
required,
bytes.len(),
));
}
let tlv_len = bytes.len() - rest.len() + length;
Ok(bytes.split_at(tlv_len))
}
fn scoped_data_kind_label(bytes: &[u8]) -> &'static str {
match ber::decode_identifier(bytes) {
Ok((tag, _))
if tag == ber::BerTag::new(ber::BerClass::Universal, true, ber::BER_TAG_SEQUENCE) =>
{
"plaintext"
}
Ok((tag, _))
if tag
== ber::BerTag::new(ber::BerClass::Universal, false, ber::BER_TAG_OCTET_STRING) =>
{
"encrypted"
}
_ => "unknown",
}
}
fn decode_encrypted_scoped_pdu_content(bytes: &[u8]) -> Result<Option<&[u8]>> {
let (tag, _) = ber::decode_identifier(bytes)?;
if tag != ber::BerTag::new(ber::BerClass::Universal, false, ber::BER_TAG_OCTET_STRING) {
return Ok(None);
}
let (encrypted_pdu, rest) = decode_octet_string_tlv(
bytes,
SNMP_V3_SCOPED_DATA_CONTEXT,
"expected universal primitive OCTET STRING for encryptedPDU",
"encryptedPDU length exceeds supported size",
)?;
if !rest.is_empty() {
return Err(ber::invalid_ber_field(
SNMP_V3_SCOPED_DATA_CONTEXT,
"trailing bytes after encryptedPDU",
));
}
Ok(Some(encrypted_pdu))
}
fn encode_message_sequence(
content: &[u8],
explicit_length: Option<usize>,
out: &mut Vec<u8>,
) -> Result<()> {
ber::encode_identifier(
ber::BerTag::new(ber::BerClass::Universal, true, ber::BER_TAG_SEQUENCE),
out,
)?;
ber::encode_length(explicit_length.unwrap_or(content.len()), out)?;
out.extend_from_slice(content);
Ok(())
}
fn encoded_pdu_len(pdu: &SnmpPdu) -> usize {
pdu.raw_tlv_bytes().map(<[u8]>::len).unwrap_or_else(|| {
ber::BER_IDENTIFIER_LEN + encoded_length_len(pdu.effective_length()) + pdu.body().len()
})
}
fn encoded_integer_tlv_len(value: i64) -> usize {
encoded_tlv_len(encoded_integer_content_len(value))
}
fn encoded_integer_content_len(value: i64) -> usize {
let bytes = value.to_be_bytes();
let mut start = 0;
if value >= 0 {
while start < bytes.len() - 1 && bytes[start] == 0x00 && bytes[start + 1] & 0x80 == 0 {
start += 1;
}
} else {
while start < bytes.len() - 1 && bytes[start] == 0xff && bytes[start + 1] & 0x80 != 0 {
start += 1;
}
}
bytes.len() - start
}
fn encoded_tlv_len(content_len: usize) -> usize {
ber::BER_IDENTIFIER_LEN + encoded_length_len(content_len) + content_len
}
fn encoded_length_len(length: usize) -> usize {
if length <= ber::BER_LENGTH_SHORT_FORM_MAX {
return ber::BER_LENGTH_FIELD_MIN_LEN;
}
let mut value = length;
let mut octets = 0usize;
while value != 0 {
octets += 1;
value >>= 8;
}
ber::BER_LENGTH_FIELD_MIN_LEN + octets
}
#[cfg(test)]
mod tests {
use super::*;
use crate::protocols::snmp::{SnmpOid, SnmpVarBind, SnmpVarBindList};
use crate::protocols::transport::Udp;
use crate::Layer;
#[test]
fn snmp_version_labels_source_backed_values() {
let cases = [
(SNMP_VERSION_VALUE_V1, SnmpVersion::V1, "v1"),
(SNMP_VERSION_VALUE_V2C, SnmpVersion::V2c, "v2c"),
(SNMP_VERSION_VALUE_V3, SnmpVersion::V3, "v3"),
];
for (integer, version, label) in cases {
assert_eq!(SnmpVersion::from_integer(integer), version);
assert_eq!(version.as_integer(), integer);
assert_eq!(version.label(), label);
assert_eq!(version.to_string(), label);
}
let unknown = SnmpVersion::from_integer(4);
assert_eq!(unknown.label(), "unknown");
assert_eq!(unknown.to_string(), "unknown(4)");
}
#[test]
fn snmp_version_unknown_version_preservation() -> Result<()> {
let mut bytes =
SnmpMessageHeader::new(SnmpVersion::from_integer(4), b"example".to_vec()).to_bytes()?;
bytes.extend_from_slice(&[0xa0, 0x00]);
let (decoded, rest) = SnmpMessageHeader::decode(&bytes)?;
assert_eq!(decoded.version(), SnmpVersion::Unknown(4));
assert_eq!(decoded.version().as_integer(), 4);
assert_eq!(decoded.version().label(), "unknown");
assert_eq!(decoded.community(), b"example");
assert_eq!(rest, &[0xa0, 0x00]);
let mut reencoded = Vec::new();
decoded.encode(&mut reencoded)?;
assert_eq!(reencoded, &bytes[..bytes.len() - rest.len()]);
let negative = SnmpVersion::from_integer(-1);
assert_eq!(negative, SnmpVersion::Unknown(-1));
assert_eq!(negative.as_integer(), -1);
Ok(())
}
#[test]
fn snmp_version_empty_community_strings() -> Result<()> {
let header = SnmpMessageHeader::new(SnmpVersion::V1, Vec::<u8>::new());
let mut bytes = header.to_bytes()?;
bytes.extend_from_slice(&[0xa0, 0x00]);
assert_eq!(header.community(), b"");
assert_eq!(&bytes[..bytes.len() - 2], &[0x02, 0x01, 0x00, 0x04, 0x00]);
let (decoded, rest) = SnmpMessageHeader::decode(&bytes)?;
assert_eq!(decoded.version(), SnmpVersion::V1);
assert_eq!(decoded.community(), b"");
assert_eq!(rest, &[0xa0, 0x00]);
Ok(())
}
#[test]
fn snmp_version_non_utf8_community_bytes() -> Result<()> {
let community = [0x00, 0xff, 0x80, b'a'];
let header = SnmpMessageHeader::new(SnmpVersion::V2c, community.to_vec());
let bytes = header.to_bytes()?;
assert_eq!(
bytes,
[0x02, 0x01, 0x01, 0x04, 0x04, 0x00, 0xff, 0x80, b'a']
);
let (decoded, rest) = SnmpMessageHeader::decode(&bytes)?;
assert_eq!(decoded.version(), SnmpVersion::V2c);
assert_eq!(decoded.community(), &community);
assert!(rest.is_empty());
Ok(())
}
#[test]
fn snmp_layer_v1_v2c_messages_compile_as_packet_layers() -> Result<()> {
let v1 = Snmp::v1(
b"public".to_vec(),
SnmpPdu::get_request(1, crate::protocols::snmp::SnmpVarBindList::empty())?,
);
assert_eq!(v1.version(), SnmpVersion::V1);
assert_eq!(v1.version_value(), 0);
assert_eq!(v1.community(), b"public");
let v2c = Snmp::v2c(
b"public".to_vec(),
SnmpPdu::get_request(1, crate::protocols::snmp::SnmpVarBindList::empty())?,
);
let expected = [
0x30, 0x18, 0x02, 0x01, 0x01, 0x04, 0x06, b'p', b'u', b'b', b'l', b'i', b'c', 0xa0,
0x0b, 0x02, 0x01, 0x01, 0x02, 0x01, 0x00, 0x02, 0x01, 0x00, 0x30, 0x00,
];
assert_eq!(v2c.encoded_len(), expected.len());
assert_eq!(v2c.compile()?, expected);
let packet = Udp::new().sport(53000).dport(161) / v2c.clone();
assert_eq!(packet.encoded_len(), 8 + v2c.encoded_len());
assert!(packet.layer::<Snmp>().is_some());
assert!(packet.compile()?.as_bytes().ends_with(&expected));
Ok(())
}
#[test]
fn snmp_v1_message_builders_emit_source_backed_request_response_and_trap_bytes() -> Result<()> {
let request = Snmp::v1_get_request(b"public".to_vec(), 1, SnmpVarBindList::empty())?;
assert_eq!(
request.compile()?,
[
0x30, 0x18, 0x02, 0x01, 0x00, 0x04, 0x06, b'p', b'u', b'b', b'l', b'i', b'c', 0xa0,
0x0b, 0x02, 0x01, 0x01, 0x02, 0x01, 0x00, 0x02, 0x01, 0x00, 0x30, 0x00,
]
);
let response = Snmp::v1_response([0x00, 0xff], 128, SnmpVarBindList::empty())?;
assert_eq!(
response.compile()?,
[
0x30, 0x15, 0x02, 0x01, 0x00, 0x04, 0x02, 0x00, 0xff, 0xa2, 0x0c, 0x02, 0x02, 0x00,
0x80, 0x02, 0x01, 0x00, 0x02, 0x01, 0x00, 0x30, 0x00,
]
);
let trap_varbind = SnmpVarBind::null(SnmpOid::from_dotted("1.3.6.1.2.1.1.3.0")?);
let trap = Snmp::v1_trap(
b"public".to_vec(),
SnmpOid::from_dotted("1.3.6.1.4.1")?,
[192, 0, 2, 44],
6,
4_321,
12_345,
SnmpVarBindList::new(vec![trap_varbind]),
)?;
assert_eq!(
trap.compile()?,
[
0x30, 0x35, 0x02, 0x01, 0x00, 0x04, 0x06, b'p', b'u', b'b', b'l', b'i', b'c', 0xa4,
0x28, 0x06, 0x05, 0x2b, 0x06, 0x01, 0x04, 0x01, 0x40, 0x04, 192, 0, 2, 44, 0x02,
0x01, 0x06, 0x02, 0x02, 0x10, 0xe1, 0x43, 0x02, 0x30, 0x39, 0x30, 0x0e, 0x30, 0x0c,
0x06, 0x08, 0x2b, 0x06, 0x01, 0x02, 0x01, 0x01, 0x03, 0x00, 0x05, 0x00,
]
);
Ok(())
}
#[test]
fn snmp_v1_message_get_next_set_and_error_response_builders_select_v1_pdu_tags() -> Result<()> {
let get_next = Snmp::v1_get_next_request(b"public".to_vec(), 2, SnmpVarBindList::empty())?;
let set = Snmp::v1_set_request(b"public".to_vec(), 3, SnmpVarBindList::empty())?;
let error_response = Snmp::v1_response_error(
b"public".to_vec(),
4,
super::super::registry::SNMP_ERROR_STATUS_NO_SUCH_NAME,
1,
SnmpVarBindList::empty(),
)?;
assert_eq!(get_next.version(), SnmpVersion::V1);
assert_eq!(get_next.pdu().tag_number(), SnmpPdu::TAG_GET_NEXT_REQUEST);
assert_eq!(set.pdu().tag_number(), SnmpPdu::TAG_SET_REQUEST);
assert_eq!(error_response.pdu().tag_number(), SnmpPdu::TAG_RESPONSE);
assert_eq!(
error_response
.pdu()
.as_response()?
.expect("response fields")
.error_status(),
super::super::registry::SNMP_ERROR_STATUS_NO_SUCH_NAME,
);
Ok(())
}
#[test]
fn snmp_v1_message_decode_round_trips_header_and_pdu_without_public_decode() -> Result<()> {
let snmp = Snmp::v1_get_request([0x00, 0xff, b'a'], 7, SnmpVarBindList::empty())?;
let bytes = snmp.compile()?;
let content = ber::decode_sequence_exact(&bytes)?;
let (header, rest) = SnmpMessageHeader::decode(content)?;
let (pdu, rest) = SnmpPdu::decode(rest)?;
ber::require_sequence_exact(rest)?;
assert_eq!(header.version(), SnmpVersion::V1);
assert_eq!(header.community(), &[0x00, 0xff, b'a']);
assert_eq!(pdu.tag_number(), SnmpPdu::TAG_GET_REQUEST);
assert_eq!(
pdu.as_get_request()?
.expect("GetRequest fields")
.request_id(),
7
);
assert_eq!(pdu.compile()?, snmp.pdu().compile()?);
Ok(())
}
#[test]
fn snmp_message_decode_v1_raw_bytes_to_typed_layer() -> Result<()> {
let bytes = [
0x30, 0x18, 0x02, 0x01, 0x00, 0x04, 0x06, b'p', b'u', b'b', b'l', b'i', b'c', 0xa0,
0x0b, 0x02, 0x01, 0x07, 0x02, 0x01, 0x00, 0x02, 0x01, 0x00, 0x30, 0x00,
];
let decoded = Snmp::decode(&bytes)?;
let request = decoded.pdu().as_get_request()?.expect("GetRequest fields");
assert_eq!(decoded.version(), SnmpVersion::V1);
assert_eq!(decoded.community(), b"public");
assert_eq!(decoded.pdu().tag_number(), SnmpPdu::TAG_GET_REQUEST);
assert_eq!(request.request_id(), 7);
assert_eq!(request.error_status(), 0);
assert_eq!(request.error_index(), 0);
assert!(request.varbinds().is_empty());
assert_eq!(decoded.compile()?, bytes);
Ok(())
}
#[test]
fn snmp_message_decode_v2c_raw_bytes_to_typed_layer() -> Result<()> {
let bytes = [
0x30, 0x18, 0x02, 0x01, 0x01, 0x04, 0x06, b'p', b'u', b'b', b'l', b'i', b'c', 0xa5,
0x0b, 0x02, 0x01, 0x04, 0x02, 0x01, 0x01, 0x02, 0x01, 0x0a, 0x30, 0x00,
];
let decoded = Snmp::decode(&bytes)?;
let bulk = decoded
.pdu()
.as_get_bulk_request()?
.expect("GetBulk fields");
assert_eq!(decoded.version(), SnmpVersion::V2c);
assert_eq!(decoded.community(), b"public");
assert_eq!(decoded.pdu().tag_number(), SnmpPdu::TAG_GET_BULK_REQUEST);
assert_eq!(bulk.request_id(), 4);
assert_eq!(bulk.non_repeaters(), 1);
assert_eq!(bulk.max_repetitions(), 10);
assert!(bulk.varbinds().is_empty());
assert_eq!(decoded.compile()?, bytes);
Ok(())
}
fn minimal_plaintext_v3_scoped_data() -> Vec<u8> {
vec![
0x30, 0x11, 0x04, 0x00, 0x04, 0x00, 0xa0, 0x0b, 0x02, 0x01, 0x01, 0x02, 0x01, 0x00,
0x02, 0x01, 0x00, 0x30, 0x00,
]
}
#[test]
fn snmp_v3_global_data_boundary_values_compile_decode_and_summarize() -> Result<()> {
let global = SnmpV3GlobalData::new(2_147_483_647, 0, [0x07], 999);
let expected = [
0x30, 0x10, 0x02, 0x04, 0x7f, 0xff, 0xff, 0xff, 0x02, 0x01, 0x00, 0x04, 0x01, 0x07,
0x02, 0x02, 0x03, 0xe7,
];
assert_eq!(global.encoded_len(), expected.len());
assert_eq!(global.compile()?, expected);
let mut with_rest = expected.to_vec();
with_rest.push(0xaa);
let (decoded, rest) = SnmpV3GlobalData::decode(&with_rest)?;
assert_eq!(rest, &[0xaa]);
assert_eq!(decoded.msg_id(), 2_147_483_647);
assert_eq!(decoded.max_size(), 0);
assert_eq!(decoded.flags(), &[0x07]);
assert_eq!(decoded.security_model(), 999);
assert_eq!(decoded.compile()?, expected);
assert_eq!(
decoded.summary(),
"SnmpV3GlobalData(msg_id=2147483647 msg_max_size=0 msg_flags=auth|privacy|reportable msg_flags_len=1 msg_security_model=999 msg_security_model_label=security-model-999)"
);
let fields = decoded.inspection_fields();
assert_eq!(inspection_value(&fields, "msg_id"), Some("2147483647"));
assert_eq!(inspection_value(&fields, "msg_max_size"), Some("0"));
assert_eq!(inspection_value(&fields, "msg_flags"), Some("07"));
assert_eq!(
inspection_value(&fields, "msg_flags_label"),
Some("auth|privacy|reportable")
);
assert_eq!(inspection_value(&fields, "msg_security_model"), Some("999"));
assert_eq!(
inspection_value(&fields, "msg_security_model_status"),
Some("unknown")
);
Ok(())
}
#[test]
fn snmp_v3_global_data_builders_preserve_explicit_wire_values() -> Result<()> {
let global = SnmpV3GlobalData::new(0, 484, Vec::<u8>::new(), 3)
.with_msg_id(-1)
.with_max_size(65_535)
.with_flags([0xaa, 0xbb])
.with_security_model(65_535);
let bytes = global.compile()?;
let (decoded, rest) = SnmpV3GlobalData::decode(&bytes)?;
assert!(rest.is_empty());
assert_eq!(decoded.msg_id(), -1);
assert_eq!(decoded.max_size(), 65_535);
assert_eq!(decoded.flags(), &[0xaa, 0xbb]);
assert_eq!(decoded.flags_value().reserved_bits(), 0xa8);
assert_eq!(decoded.flags_value().label(), "privacy|reserved-0xa8");
assert_eq!(decoded.security_model(), 65_535);
assert_eq!(decoded.compile()?, bytes);
Ok(())
}
#[test]
fn snmp_v3_global_data_malformed_sequence_length_is_structured_error() {
let bytes = [
0x30, 0x0e, 0x02, 0x01, 0x01, 0x02, 0x01, 0x00, 0x04, 0x01, 0x00, 0x02, 0x01, 0x03,
];
let error = SnmpV3GlobalData::decode(&bytes).expect_err("overreported HeaderData length");
assert_eq!(
error,
crate::error::CrafterError::buffer_too_short("snmp.ber.sequence", 16, bytes.len())
);
}
#[test]
fn snmp_v3_flags_message_decode_inspects_raw_flags_and_unknown_security_model() -> Result<()> {
let scoped_data = minimal_plaintext_v3_scoped_data();
let snmp = Snmp::v3(9, 1500, [0xff, 0x55], 99, [0xaa], scoped_data);
let decoded = Snmp::decode(&snmp.compile()?)?;
let global = decoded.as_v3().expect("v3 wrapper").global_data();
let flags = global.flags_value();
assert_eq!(global.flags(), &[0xff, 0x55]);
assert_eq!(flags.bits(), 0xff);
assert!(flags.auth());
assert!(flags.privacy());
assert!(flags.reportable());
assert_eq!(flags.reserved_bits(), 0xf8);
assert_eq!(flags.label(), "auth|privacy|reportable|reserved-0xf8");
assert_eq!(global.security_model(), 99);
assert_eq!(global.security_model_label(), "security-model-99");
assert_eq!(
global.security_model_status(),
registry::SnmpSecurityModelStatus::Unassigned
);
let fields = decoded.inspection_fields();
assert_eq!(inspection_value(&fields, "msg_flags"), Some("ff 55"));
assert_eq!(
inspection_value(&fields, "msg_flags_label"),
Some("auth|privacy|reportable|reserved-0xf8")
);
assert_eq!(inspection_value(&fields, "msg_flags_auth"), Some("true"));
assert_eq!(inspection_value(&fields, "msg_flags_privacy"), Some("true"));
assert_eq!(
inspection_value(&fields, "msg_flags_reportable"),
Some("true")
);
assert_eq!(
inspection_value(&fields, "msg_flags_reserved_bits"),
Some("0xf8")
);
assert_eq!(
inspection_value(&fields, "msg_security_model_label"),
Some("security-model-99")
);
assert_eq!(
inspection_value(&fields, "msg_security_model_status"),
Some("unassigned")
);
Ok(())
}
#[test]
fn snmp_v3_scoped_pdu_wraps_request_pdu_and_v3_message() -> Result<()> {
let scoped = SnmpScopedPdu::new(
Vec::<u8>::new(),
Vec::<u8>::new(),
SnmpPdu::get_request(1, SnmpVarBindList::empty())?,
);
let expected = minimal_plaintext_v3_scoped_data();
assert_eq!(scoped.encoded_len(), expected.len());
assert_eq!(scoped.compile()?, expected);
assert_eq!(scoped.context_engine_id(), b"");
assert_eq!(scoped.context_name(), b"");
assert_eq!(scoped.pdu().tag_number(), SnmpPdu::TAG_GET_REQUEST);
let (decoded, rest) = SnmpScopedPdu::decode(&expected)?;
assert!(rest.is_empty());
assert_eq!(decoded.context_engine_id(), b"");
assert_eq!(decoded.context_name(), b"");
assert_eq!(decoded.pdu().tag_number(), SnmpPdu::TAG_GET_REQUEST);
assert_eq!(
decoded
.pdu()
.as_get_request()?
.expect("get request fields")
.request_id(),
1
);
assert_eq!(decoded.compile()?, expected);
assert!(decoded.summary().contains("context_engine_id_len=0"));
assert!(decoded.summary().contains("pdu_type=get-request"));
let snmp = Snmp::v3_plaintext(
1,
1500,
[0x00],
registry::SNMP_SECURITY_MODEL_USM,
Vec::<u8>::new(),
scoped,
)?;
let decoded_snmp = Snmp::decode(&snmp.compile()?)?;
let decoded_scoped = decoded_snmp
.as_v3()
.expect("v3 wrapper")
.scoped_pdu()?
.expect("plaintext scoped PDU");
assert_eq!(decoded_scoped.compile()?, expected);
Ok(())
}
#[test]
fn snmp_v3_scoped_pdu_preserves_non_utf8_context_name_and_response_pdu() -> Result<()> {
let scoped = SnmpScopedPdu::new(
[0x80, 0x00, 0x1f],
[0xff, 0x00, b'a'],
SnmpPdu::response_error(128, 2, 3, SnmpVarBindList::empty())?,
);
let bytes = scoped.compile()?;
let (decoded, rest) = SnmpScopedPdu::decode(&bytes)?;
assert!(rest.is_empty());
assert_eq!(decoded.context_engine_id(), &[0x80, 0x00, 0x1f]);
assert_eq!(decoded.context_name(), &[0xff, 0x00, b'a']);
assert_eq!(decoded.pdu().tag_number(), SnmpPdu::TAG_RESPONSE);
let response = decoded.pdu().as_response()?.expect("response fields");
assert_eq!(response.request_id(), 128);
assert_eq!(response.error_status(), 2);
assert_eq!(response.error_index(), 3);
assert_eq!(decoded.compile()?, bytes);
let fields = decoded.inspection_fields();
assert_eq!(
inspection_value(&fields, "context_engine_id_len"),
Some("3")
);
assert_eq!(
inspection_value(&fields, "context_engine_id_bytes"),
Some("80 00 1f")
);
assert_eq!(inspection_value(&fields, "context_name_len"), Some("3"));
assert_eq!(
inspection_value(&fields, "context_name_bytes"),
Some("ff 00 61")
);
assert_eq!(inspection_value(&fields, "pdu_type"), Some("response"));
Ok(())
}
#[test]
fn snmp_v3_scoped_pdu_wraps_report_pdu() -> Result<()> {
let scoped = SnmpScopedPdu::new(
b"engine".to_vec(),
b"context".to_vec(),
SnmpPdu::report(7, SnmpVarBindList::empty())?,
);
let snmp = Snmp::v3_plaintext(
44,
1500,
[registry::SNMP_V3_FLAG_REPORTABLE],
registry::SNMP_SECURITY_MODEL_USM,
Vec::<u8>::new(),
scoped.clone(),
)?;
let decoded = Snmp::decode(&snmp.compile()?)?;
let decoded_scoped = decoded
.as_v3()
.expect("v3 wrapper")
.scoped_pdu()?
.expect("plaintext scoped PDU");
assert_eq!(decoded_scoped.context_engine_id(), b"engine");
assert_eq!(decoded_scoped.context_name(), b"context");
assert_eq!(decoded_scoped.pdu().tag_number(), SnmpPdu::TAG_REPORT);
assert_eq!(
decoded_scoped
.pdu()
.as_report()?
.expect("report fields")
.request_id(),
7
);
assert_eq!(decoded_scoped.compile()?, scoped.compile()?);
assert!(decoded_scoped.summary().contains("pdu_type=report"));
Ok(())
}
#[test]
fn snmp_v3_raw_security_parameters_compile_decode_and_hide_bytes() -> Result<()> {
let raw = SnmpRawSecurityParameters::new(999, [0xde, 0xad, 0xbe, 0xef]);
let expected = [0x04, 0x04, 0xde, 0xad, 0xbe, 0xef];
assert_eq!(raw.security_model(), 999);
assert_eq!(
raw.security_model_status(),
registry::SnmpSecurityModelStatus::Unknown
);
assert_eq!(raw.security_model_label(), "security-model-999");
assert_eq!(raw.bytes(), &[0xde, 0xad, 0xbe, 0xef]);
assert_eq!(raw.len(), 4);
assert!(!raw.is_empty());
assert_eq!(raw.compile()?, expected);
let mut with_rest = expected.to_vec();
with_rest.push(0xaa);
let (decoded, rest) = SnmpRawSecurityParameters::decode(999, &with_rest)?;
assert_eq!(rest, &[0xaa]);
assert_eq!(decoded.bytes(), raw.bytes());
assert_eq!(decoded.compile()?, expected);
assert!(decoded.summary().contains("len=4"));
assert!(!decoded.summary().contains("de ad"));
assert!(!decoded
.inspection_fields()
.iter()
.any(|(_, value)| value == "de ad be ef"));
Ok(())
}
#[test]
fn snmp_v3_raw_security_parameters_unknown_model_message_roundtrips() -> Result<()> {
let scoped_data = minimal_plaintext_v3_scoped_data();
let snmp = Snmp::v3(7, 1500, [0x00], 999, [0xaa, 0xbb], scoped_data);
let bytes = snmp.compile()?;
let decoded = Snmp::decode(&bytes)?;
let v3 = decoded.as_v3().expect("v3 wrapper");
let raw = v3.raw_security_parameters();
assert_eq!(raw.security_model(), 999);
assert_eq!(
raw.security_model_status(),
registry::SnmpSecurityModelStatus::Unknown
);
assert_eq!(raw.security_model_label(), "security-model-999");
assert_eq!(raw.bytes(), &[0xaa, 0xbb]);
assert_eq!(v3.security_parameters(), &[0xaa, 0xbb]);
assert_eq!(raw.compile()?, [0x04, 0x02, 0xaa, 0xbb]);
assert_eq!(decoded.compile()?, bytes);
assert!(decoded.summary().contains("msg_security_parameters_len=2"));
assert!(!decoded.summary().contains("aa bb"));
assert!(!decoded.show().contains("aa bb"));
Ok(())
}
fn sample_usm_parameters() -> SnmpUsmSecurityParameters {
SnmpUsmSecurityParameters::new(
[0x80, 0x00, 0x1f],
7,
9,
[0xff, 0x00, b'u'],
[0xaa, 0xbb, 0xcc],
[0xde, 0xad],
)
}
#[test]
fn snmp_usm_parameters_compile_decode_and_hide_sensitive_bytes() -> Result<()> {
let usm = sample_usm_parameters();
let expected = [
0x30, 0x19, 0x04, 0x03, 0x80, 0x00, 0x1f, 0x02, 0x01, 0x07, 0x02, 0x01, 0x09, 0x04,
0x03, 0xff, 0x00, b'u', 0x04, 0x03, 0xaa, 0xbb, 0xcc, 0x04, 0x02, 0xde, 0xad,
];
assert_eq!(usm.encoded_len(), expected.len());
assert_eq!(usm.compile()?, expected);
let (decoded, rest) = SnmpUsmSecurityParameters::decode(&expected)?;
assert!(rest.is_empty());
assert_eq!(decoded.engine_id(), &[0x80, 0x00, 0x1f]);
assert_eq!(decoded.engine_boots(), 7);
assert_eq!(decoded.engine_time(), 9);
assert_eq!(decoded.user_name(), &[0xff, 0x00, b'u']);
assert_eq!(decoded.authentication_parameters(), &[0xaa, 0xbb, 0xcc]);
assert_eq!(decoded.privacy_parameters(), &[0xde, 0xad]);
assert_eq!(decoded.compile()?, expected);
let summary = decoded.summary();
assert!(summary.contains("engine_id_len=3"));
assert!(summary.contains("user_name_len=3"));
assert!(summary.contains("authentication_parameters_len=3"));
assert!(!summary.contains("aa bb"));
assert!(!decoded.show().contains("aa bb"));
assert!(!decoded.show().contains("ff 00 75"));
let raw = SnmpRawSecurityParameters::from_usm(&decoded)?;
assert_eq!(raw.security_model(), registry::SNMP_SECURITY_MODEL_USM);
assert_eq!(raw.bytes(), expected);
assert_eq!(raw.as_usm()?.expect("USM parameters"), decoded);
Ok(())
}
#[test]
fn snmp_usm_parameters_v3_message_decode_preserves_raw_and_typed_forms() -> Result<()> {
let usm = sample_usm_parameters();
let message = SnmpV3Message::new_usm(
11,
1500,
[0x00],
usm.clone(),
minimal_plaintext_v3_scoped_data(),
)?;
let snmp = Snmp::from_v3_message(message);
let decoded = Snmp::decode(&snmp.compile()?)?;
let v3 = decoded.as_v3().expect("v3 wrapper");
let decoded_usm = v3
.usm_security_parameters()?
.expect("USM security parameters");
assert_eq!(v3.security_model(), registry::SNMP_SECURITY_MODEL_USM);
assert_eq!(v3.raw_security_parameters().bytes(), usm.compile()?);
assert_eq!(decoded_usm, usm);
assert_eq!(decoded.compile()?, snmp.compile()?);
Ok(())
}
#[test]
fn snmp_usm_parameters_malformed_uses_raw_fallback() -> Result<()> {
let malformed = vec![0x30, 0x03, 0x04, 0x01, 0xaa];
let snmp = Snmp::v3(
12,
1500,
[0x00],
registry::SNMP_SECURITY_MODEL_USM,
malformed.clone(),
minimal_plaintext_v3_scoped_data(),
);
let decoded = Snmp::decode(&snmp.compile()?)?;
let v3 = decoded.as_v3().expect("v3 wrapper");
let raw = v3.raw_security_parameters();
assert_eq!(raw.security_model(), registry::SNMP_SECURITY_MODEL_USM);
assert_eq!(raw.bytes(), malformed);
assert_eq!(
v3.usm_security_parameters().expect_err("malformed USM"),
crate::error::CrafterError::buffer_too_short("snmp.ber.identifier", 1, 0)
);
assert_eq!(decoded.compile()?, snmp.compile()?);
Ok(())
}
#[test]
fn snmp_usm_engine_time_helpers_preserve_boundary_values() -> Result<()> {
let engine_time = SnmpUsmEngineTime::new(0, i64::from(i32::MAX));
assert_eq!(engine_time.engine_boots(), 0);
assert_eq!(engine_time.engine_time(), i64::from(i32::MAX));
assert!(engine_time.summary().contains("engine_boots=0"));
assert!(engine_time.summary().contains("engine_time=2147483647"));
let usm = SnmpUsmSecurityParameters::from_engine_time(
[0x80, 0x00, 0x1f],
engine_time,
[b'u'],
Vec::<u8>::new(),
Vec::<u8>::new(),
)
.with_engine_boots(i64::from(i32::MAX))
.with_engine_time(0);
assert_eq!(
usm.engine_time_fields(),
SnmpUsmEngineTime::new(i64::from(i32::MAX), 0)
);
let encoded = usm.compile()?;
assert!(encoded
.windows(6)
.any(|window| window == [0x02, 0x04, 0x7f, 0xff, 0xff, 0xff]));
assert!(encoded
.windows(3)
.any(|window| window == [0x02, 0x01, 0x00]));
let (decoded, rest) = SnmpUsmSecurityParameters::decode(&encoded)?;
assert!(rest.is_empty());
assert_eq!(
decoded.engine_time_fields(),
SnmpUsmEngineTime::new(i64::from(i32::MAX), 0)
);
assert_eq!(decoded.compile()?, encoded);
Ok(())
}
#[test]
fn snmp_usm_engine_time_decode_summary_and_inspection() -> Result<()> {
let engine_time = SnmpUsmEngineTime::new(123, 456);
let usm = sample_usm_parameters().with_engine_time_fields(engine_time);
let encoded = usm.compile()?;
let (decoded, rest) = SnmpUsmSecurityParameters::decode(&encoded)?;
assert!(rest.is_empty());
assert_eq!(decoded.engine_boots(), 123);
assert_eq!(decoded.engine_time(), 456);
assert_eq!(decoded.engine_time_fields(), engine_time);
let summary = decoded.summary();
assert!(summary.contains("engine_boots=123"));
assert!(summary.contains("engine_time=456"));
assert!(!summary.contains("aa bb"));
assert!(engine_time.show().contains("usm_engine_boots: 123"));
let fields = decoded.inspection_fields();
assert!(fields
.iter()
.any(|(name, value)| *name == "usm_engine_boots" && value == "123"));
assert!(fields
.iter()
.any(|(name, value)| *name == "usm_engine_time" && value == "456"));
Ok(())
}
#[test]
fn snmp_usm_engine_time_unknown_model_preserves_raw_bytes() -> Result<()> {
let usm = sample_usm_parameters().with_engine_time_fields(SnmpUsmEngineTime::new(1, 2));
let raw_bytes = usm.compile()?;
let raw = SnmpRawSecurityParameters::new(999, raw_bytes.clone());
assert_eq!(raw.as_usm()?, None);
assert_eq!(raw.bytes(), raw_bytes);
let snmp = Snmp::v3(
13,
1500,
[0x00],
raw.security_model(),
raw.bytes().to_vec(),
minimal_plaintext_v3_scoped_data(),
);
let decoded = Snmp::decode(&snmp.compile()?)?;
let v3 = decoded.as_v3().expect("v3 wrapper");
assert_eq!(v3.security_model(), 999);
assert_eq!(v3.usm_security_parameters()?, None);
assert_eq!(v3.raw_security_parameters().bytes(), raw_bytes);
assert_eq!(decoded.compile()?, snmp.compile()?);
Ok(())
}
#[test]
fn snmp_usm_auth_params_setter_preserves_exact_bytes_and_lengths() -> Result<()> {
let auth_parameters = [
0x00, 0x11, 0x22, 0x33, 0x44, 0x55, 0x66, 0x77, 0x88, 0x99, 0xaa, 0xbb, 0xcc,
];
let usm = sample_usm_parameters().with_authentication_parameters(auth_parameters);
assert_eq!(usm.authentication_parameters(), auth_parameters);
assert_eq!(usm.authentication_parameters_len(), auth_parameters.len());
let encoded = usm.compile()?;
let mut auth_tlv = vec![0x04, auth_parameters.len() as u8];
auth_tlv.extend_from_slice(&auth_parameters);
assert!(encoded
.windows(auth_tlv.len())
.any(|window| window == auth_tlv.as_slice()));
let (decoded, rest) = SnmpUsmSecurityParameters::decode(&encoded)?;
assert!(rest.is_empty());
assert_eq!(decoded.authentication_parameters(), auth_parameters);
assert_eq!(
decoded.authentication_parameters_len(),
auth_parameters.len()
);
assert_eq!(decoded.compile()?, encoded);
Ok(())
}
#[test]
fn snmp_usm_auth_params_arbitrary_lengths_remain_packet_bytes() -> Result<()> {
let cases = [Vec::<u8>::new(), vec![0xaa], vec![0xbb; 12], vec![0xcc; 13]];
for auth_parameters in cases {
let usm =
sample_usm_parameters().with_authentication_parameters(auth_parameters.clone());
let decoded = SnmpUsmSecurityParameters::decode(&usm.compile()?)?.0;
assert_eq!(decoded.authentication_parameters(), auth_parameters);
assert_eq!(
decoded.authentication_parameters_len(),
auth_parameters.len()
);
}
Ok(())
}
#[test]
fn snmp_usm_auth_params_v3_message_roundtrips_without_secrets() -> Result<()> {
let auth_parameters = [
0xa0, 0xa1, 0xa2, 0xa3, 0xa4, 0xa5, 0xa6, 0xa7, 0xa8, 0xa9, 0xaa, 0xab,
];
let usm = sample_usm_parameters().with_authentication_parameters(auth_parameters);
let message = SnmpV3Message::new_usm(
14,
1500,
[registry::SNMP_V3_FLAG_AUTH],
usm.clone(),
minimal_plaintext_v3_scoped_data(),
)?;
let snmp = Snmp::from_v3_message(message);
let bytes = snmp.compile()?;
let decoded = Snmp::decode(&bytes)?;
let v3 = decoded.as_v3().expect("v3 wrapper");
let decoded_usm = v3
.usm_security_parameters()?
.expect("USM security parameters");
assert_eq!(decoded_usm.authentication_parameters(), auth_parameters);
assert_eq!(v3.raw_security_parameters().bytes(), usm.compile()?);
assert_eq!(decoded.compile()?, bytes);
assert!(!decoded_usm.summary().contains("a0 a1"));
assert!(!decoded_usm.show().contains("a0 a1"));
assert!(!decoded.summary().contains("a0 a1"));
Ok(())
}
#[test]
fn snmp_v3_encrypted_pdu_builder_preserves_payload_and_privacy_params() -> Result<()> {
let privacy_parameters = [0x10, 0x11, 0x12, 0x13, 0x14, 0x15, 0x16, 0x17];
let encrypted_pdu = [0xde, 0xad, 0xbe, 0xef, 0x00, 0xff];
let usm = sample_usm_parameters().with_privacy_parameters(privacy_parameters);
let flags = [registry::SNMP_V3_FLAG_AUTH | registry::SNMP_V3_FLAG_PRIVACY];
let snmp = Snmp::v3_encrypted_usm(15, 1500, flags, usm.clone(), encrypted_pdu)?;
let decoded = Snmp::decode(&snmp.compile()?)?;
let v3 = decoded.as_v3().expect("v3 wrapper");
let encrypted = v3.encrypted_scoped_data()?.expect("encrypted scoped data");
assert_eq!(v3.flags_value().privacy(), true);
assert_eq!(v3.scoped_data_kind(), "encrypted");
assert_eq!(v3.encrypted_scoped_pdu_len()?, Some(encrypted_pdu.len()));
assert!(v3.scoped_pdu()?.is_none());
assert_eq!(encrypted.encrypted_pdu(), encrypted_pdu);
assert_eq!(encrypted.encrypted_pdu_len(), encrypted_pdu.len());
assert_eq!(encrypted.privacy_parameters(), privacy_parameters);
assert_eq!(encrypted.privacy_parameters_len(), privacy_parameters.len());
assert_eq!(encrypted.compile()?, v3.scoped_data());
assert_eq!(v3.raw_security_parameters().bytes(), usm.compile()?);
assert_eq!(decoded.compile()?, snmp.compile()?);
Ok(())
}
#[test]
fn snmp_v3_encrypted_pdu_decode_opaque_variant_from_raw_wrapper() -> Result<()> {
let privacy_parameters = [0x21, 0x22, 0x23, 0x24];
let encrypted_pdu = [0x8a, 0x8b, 0x8c, 0x8d, 0x8e];
let usm = sample_usm_parameters().with_privacy_parameters(privacy_parameters);
let encrypted = SnmpEncryptedScopedData::new(privacy_parameters, encrypted_pdu);
let snmp = Snmp::v3(
16,
1500,
[registry::SNMP_V3_FLAG_PRIVACY],
registry::SNMP_SECURITY_MODEL_USM,
usm.compile()?,
encrypted.compile()?,
);
let decoded = Snmp::decode(&snmp.compile()?)?;
let v3 = decoded.as_v3().expect("v3 wrapper");
let decoded_encrypted = v3.encrypted_scoped_data()?.expect("encrypted scoped data");
assert_eq!(v3.scoped_data_kind(), "encrypted");
assert_eq!(v3.scoped_data(), encrypted.compile()?);
assert_eq!(decoded_encrypted.encrypted_pdu(), encrypted_pdu);
assert_eq!(decoded_encrypted.privacy_parameters(), privacy_parameters);
assert_eq!(decoded.compile()?, snmp.compile()?);
Ok(())
}
#[test]
fn snmp_v3_encrypted_pdu_safe_summaries_hide_secret_bytes() -> Result<()> {
let privacy_parameters = [0x30, 0x31, 0x32, 0x33];
let encrypted_pdu = [0xfa, 0xfb, 0xfc, 0xfd, 0xfe];
let usm = sample_usm_parameters().with_privacy_parameters(privacy_parameters);
let snmp = Snmp::v3_encrypted_usm(
17,
1500,
[registry::SNMP_V3_FLAG_PRIVACY],
usm,
encrypted_pdu,
)?;
let decoded = Snmp::decode(&snmp.compile()?)?;
let encrypted = decoded
.as_v3()
.expect("v3 wrapper")
.encrypted_scoped_data()?
.expect("encrypted scoped data");
assert!(encrypted.summary().contains("encrypted_pdu_len=5"));
assert!(encrypted.summary().contains("privacy_parameters_len=4"));
assert!(!encrypted.summary().contains("fa fb"));
assert!(!encrypted.show().contains("fa fb"));
assert!(!encrypted.show().contains("30 31"));
assert!(decoded.summary().contains("scoped_data_kind=encrypted"));
assert!(decoded.summary().contains("encrypted_scoped_pdu_len=5"));
assert!(!decoded.summary().contains("fa fb"));
assert!(!decoded.show().contains("fa fb"));
Ok(())
}
#[test]
fn snmp_v3_summary_plaintext_report_exposes_safe_nested_fields() -> Result<()> {
let usm = sample_usm_parameters();
let snmp = Snmp::v3_usm_report(
63,
4096,
[registry::SNMP_V3_FLAG_REPORTABLE],
usm,
b"ctx-engine".to_vec(),
b"ctx-name".to_vec(),
6300,
SnmpVarBindList::empty(),
)?;
let decoded = Snmp::decode(&snmp.compile()?)?;
let summary = decoded.summary();
assert!(summary.contains("version=v3"));
assert!(summary.contains("msg_id=63"));
assert!(summary.contains("msg_flags=reportable"));
assert!(summary.contains("msg_security_model_label=usm"));
assert!(summary.contains("msg_security_parameters_len=27"));
assert!(summary.contains("scoped_data_kind=plaintext"));
assert!(summary.contains("usm=SnmpUsmSecurityParameters("));
assert!(summary.contains("authentication_parameters_len=3"));
assert!(summary.contains("privacy_parameters_len=2"));
assert!(summary.contains("scoped_pdu=SnmpScopedPdu("));
assert!(summary.contains("context_engine_id_len=10"));
assert!(summary.contains("context_name_len=8"));
assert!(summary.contains("pdu_type=report"));
assert!(!summary.contains("80 00 1f"));
assert!(!summary.contains("ff 00 75"));
assert!(!summary.contains("aa bb"));
assert!(!summary.contains("de ad"));
let fields = decoded.inspection_fields();
assert_eq!(inspection_value(&fields, "version"), Some("v3"));
assert_eq!(inspection_value(&fields, "msg_id"), Some("63"));
assert_eq!(
inspection_value(&fields, "msg_flags_label"),
Some("reportable")
);
assert_eq!(
inspection_value(&fields, "msg_security_model_label"),
Some("usm")
);
assert_eq!(
fields
.iter()
.filter(|(name, _)| *name == "msg_security_model")
.count(),
1
);
assert_eq!(
fields
.iter()
.filter(|(name, _)| *name == "msg_security_model_label")
.count(),
1
);
assert_eq!(
fields
.iter()
.filter(|(name, _)| *name == "msg_security_model_status")
.count(),
1
);
assert_eq!(
inspection_value(&fields, "usm_authentication_parameters_len"),
Some("3")
);
assert_eq!(
inspection_value(&fields, "usm_privacy_parameters_len"),
Some("2")
);
assert_eq!(
inspection_value(&fields, "context_engine_id_len"),
Some("10")
);
assert_eq!(inspection_value(&fields, "context_name_len"), Some("8"));
assert_eq!(inspection_value(&fields, "pdu_type"), Some("report"));
assert!(!fields.iter().any(|(_, value)| value == "80 00 1f"));
assert!(!fields.iter().any(|(_, value)| value == "ff 00 75"));
assert!(!fields.iter().any(|(_, value)| value == "aa bb cc"));
assert!(!fields.iter().any(|(_, value)| value == "de ad"));
let show = decoded.show();
assert!(show.contains(" msg_id: 63"));
assert!(show.contains(" msg_flags_reportable: true"));
assert!(show.contains(" usm_authentication_parameters_len: 3"));
assert!(show.contains(" usm_privacy_parameters_len: 2"));
assert!(show.contains(" context_engine_id_len: 10"));
assert!(show.contains(" context_name_len: 8"));
assert!(show.contains(" pdu_type: report"));
assert!(!show.contains("80 00 1f"));
assert!(!show.contains("ff 00 75"));
assert!(!show.contains("aa bb"));
assert!(!show.contains("de ad"));
Ok(())
}
#[test]
fn snmp_v3_summary_encrypted_reports_lengths_without_payload_bytes() -> Result<()> {
let auth_parameters = [0xa0, 0xa1, 0xa2, 0xa3];
let privacy_parameters = [0x01, 0x02, 0x03, 0x04];
let encrypted_pdu = [0xde, 0xad, 0xbe, 0xef, 0xfa, 0xfb];
let usm = sample_usm_parameters()
.with_authentication_parameters(auth_parameters)
.with_privacy_parameters(privacy_parameters);
let snmp = Snmp::v3_encrypted_usm(
64,
4096,
[registry::SNMP_V3_FLAG_AUTH | registry::SNMP_V3_FLAG_PRIVACY],
usm,
encrypted_pdu,
)?;
let decoded = Snmp::decode(&snmp.compile()?)?;
let summary = decoded.summary();
assert!(summary.contains("version=v3"));
assert!(summary.contains("msg_id=64"));
assert!(summary.contains("msg_flags=auth|privacy"));
assert!(summary.contains("scoped_data_kind=encrypted"));
assert!(summary.contains("encrypted_scoped_pdu_len=6"));
assert!(summary.contains("encrypted_scoped_data=SnmpEncryptedScopedData("));
assert!(summary.contains("encrypted_pdu_len=6"));
assert!(summary.contains("authentication_parameters_len=4"));
assert!(summary.contains("privacy_parameters_len=4"));
assert!(!summary.contains("a0 a1"));
assert!(!summary.contains("01 02"));
assert!(!summary.contains("de ad"));
assert!(!summary.contains("fa fb"));
let fields = decoded.inspection_fields();
assert_eq!(inspection_value(&fields, "msg_id"), Some("64"));
assert_eq!(inspection_value(&fields, "msg_flags_auth"), Some("true"));
assert_eq!(inspection_value(&fields, "msg_flags_privacy"), Some("true"));
assert_eq!(
inspection_value(&fields, "scoped_data_kind"),
Some("encrypted")
);
assert_eq!(
inspection_value(&fields, "encrypted_scoped_pdu_len"),
Some("6")
);
assert_eq!(
fields
.iter()
.filter(|(name, _)| *name == "encrypted_scoped_pdu_len")
.count(),
1
);
assert_eq!(
inspection_value(&fields, "usm_authentication_parameters_len"),
Some("4")
);
assert_eq!(
inspection_value(&fields, "usm_privacy_parameters_len"),
Some("4")
);
assert!(!fields.iter().any(|(_, value)| value == "a0 a1 a2 a3"));
assert!(!fields.iter().any(|(_, value)| value == "01 02 03 04"));
assert!(!fields.iter().any(|(_, value)| value == "de ad be ef fa fb"));
let show = decoded.show();
assert!(show.contains(" msg_flags_auth: true"));
assert!(show.contains(" msg_flags_privacy: true"));
assert!(show.contains(" encrypted_scoped_pdu_len: 6"));
assert!(show.contains(" usm_authentication_parameters_len: 4"));
assert!(show.contains(" usm_privacy_parameters_len: 4"));
assert_eq!(show.matches("encrypted_scoped_pdu_len").count(), 1);
assert!(!show.contains("a0 a1"));
assert!(!show.contains("01 02"));
assert!(!show.contains("de ad"));
assert!(!show.contains("fa fb"));
Ok(())
}
#[test]
fn snmp_v3_report_plaintext_builder_compiles_decodes_and_summarizes() -> Result<()> {
let oid = SnmpOid::from_dotted("1.3.6.1.2.1.1.3.0")?;
let varbinds = SnmpVarBindList::new(vec![SnmpVarBind::time_ticks(oid, 42)]);
let snmp = Snmp::v3_report(
18,
1500,
[registry::SNMP_V3_FLAG_REPORTABLE],
registry::SNMP_SECURITY_MODEL_USM,
Vec::<u8>::new(),
b"engine".to_vec(),
b"context".to_vec(),
99,
varbinds,
)?;
let bytes = snmp.compile()?;
let decoded = Snmp::decode(&bytes)?;
let v3 = decoded.as_v3().expect("v3 wrapper");
let scoped = v3.scoped_pdu()?.expect("plaintext scoped PDU");
let report = scoped.pdu().as_report()?.expect("Report fields");
assert_eq!(v3.scoped_data_kind(), "plaintext");
assert_eq!(scoped.context_engine_id(), b"engine");
assert_eq!(scoped.context_name(), b"context");
assert_eq!(scoped.pdu().tag_number(), SnmpPdu::TAG_REPORT);
assert_eq!(report.request_id(), 99);
assert_eq!(report.error_status(), 0);
assert_eq!(report.error_index(), 0);
assert_eq!(report.varbinds().len(), 1);
assert_eq!(decoded.compile()?, bytes);
assert!(decoded.summary().contains("scoped_data_kind=plaintext"));
assert!(scoped.summary().contains("pdu_type=report"));
assert!(decoded.show().contains("msg_flags_reportable: true"));
Ok(())
}
#[test]
fn snmp_v3_report_usm_builder_preserves_security_and_context() -> Result<()> {
let usm = sample_usm_parameters();
let message = SnmpV3Message::new_usm_report(
19,
1500,
[registry::SNMP_V3_FLAG_REPORTABLE],
usm.clone(),
[0x80, 0x00, 0x1f],
Vec::<u8>::new(),
100,
SnmpVarBindList::empty(),
)?;
let snmp = Snmp::from_v3_message(message);
let decoded = Snmp::decode(&snmp.compile()?)?;
let v3 = decoded.as_v3().expect("v3 wrapper");
let decoded_usm = v3
.usm_security_parameters()?
.expect("USM security parameters");
let scoped = v3.scoped_pdu()?.expect("plaintext scoped PDU");
let report = scoped.pdu().as_report()?.expect("Report fields");
assert_eq!(decoded_usm, usm);
assert_eq!(v3.raw_security_parameters().bytes(), usm.compile()?);
assert_eq!(scoped.context_engine_id(), &[0x80, 0x00, 0x1f]);
assert_eq!(scoped.context_name(), b"");
assert_eq!(report.request_id(), 100);
assert!(report.varbinds().is_empty());
assert!(decoded.summary().contains("pdu_type=report"));
assert_eq!(decoded.compile()?, snmp.compile()?);
Ok(())
}
#[test]
fn snmp_v3_report_layer_usm_builder_returns_packet_surface() -> Result<()> {
let usm = sample_usm_parameters();
let snmp = Snmp::v3_usm_report(
20,
1500,
[registry::SNMP_V3_FLAG_REPORTABLE],
usm,
b"engine".to_vec(),
Vec::<u8>::new(),
101,
SnmpVarBindList::empty(),
)?;
let packet = Packet::from_layer(snmp.clone());
let decoded = Snmp::decode(&packet.compile()?)?;
let report = decoded
.as_v3()
.expect("v3 wrapper")
.scoped_pdu()?
.expect("plaintext scoped PDU")
.pdu()
.as_report()?
.expect("Report fields");
assert_eq!(snmp.as_v3().expect("v3 wrapper").version(), SnmpVersion::V3);
assert_eq!(report.request_id(), 101);
assert!(packet.summary().contains("Snmp(version=v3"));
assert!(packet.summary().contains("pdu_type=report"));
Ok(())
}
#[test]
fn snmp_v3_message_minimal_plaintext_compile_decode_roundtrips() -> Result<()> {
let scoped_data = minimal_plaintext_v3_scoped_data();
let snmp = Snmp::v3(1, 1500, [0x00], 3, Vec::<u8>::new(), scoped_data.clone());
let expected = [
0x30, 0x27, 0x02, 0x01, 0x03, 0x30, 0x0d, 0x02, 0x01, 0x01, 0x02, 0x02, 0x05, 0xdc,
0x04, 0x01, 0x00, 0x02, 0x01, 0x03, 0x04, 0x00, 0x30, 0x11, 0x04, 0x00, 0x04, 0x00,
0xa0, 0x0b, 0x02, 0x01, 0x01, 0x02, 0x01, 0x00, 0x02, 0x01, 0x00, 0x30, 0x00,
];
assert_eq!(snmp.version(), SnmpVersion::V3);
assert_eq!(snmp.version_value(), 3);
assert_eq!(snmp.community(), b"");
assert!(snmp.pdu_opt().is_none());
let v3 = snmp.as_v3().expect("v3 wrapper");
assert_eq!(v3.msg_id(), 1);
assert_eq!(v3.max_size(), 1500);
assert_eq!(v3.flags(), &[0x00]);
assert_eq!(v3.security_model(), 3);
assert_eq!(v3.security_parameters(), b"");
assert_eq!(v3.scoped_data(), scoped_data);
assert_eq!(snmp.encoded_len(), expected.len());
assert_eq!(snmp.compile()?, expected);
let decoded = Snmp::decode(&expected)?;
let decoded_v3 = decoded.as_v3().expect("decoded v3 wrapper");
assert_eq!(decoded.version(), SnmpVersion::V3);
assert_eq!(decoded_v3.msg_id(), 1);
assert_eq!(decoded_v3.max_size(), 1500);
assert_eq!(decoded_v3.flags(), &[0x00]);
assert_eq!(decoded_v3.security_model(), 3);
assert_eq!(decoded_v3.security_parameters(), b"");
assert_eq!(decoded_v3.scoped_data(), scoped_data);
assert_eq!(decoded.compile()?, expected);
let summary = decoded.summary();
assert!(summary.contains("version=v3"));
assert!(summary.contains("msg_id=1"));
assert!(summary.contains("msg_max_size=1500"));
assert!(summary.contains("msg_security_model=3"));
assert!(summary.contains("msg_security_parameters_len=0"));
assert!(summary.contains("scoped_data_len=19"));
let fields = decoded.inspection_fields();
assert_eq!(inspection_value(&fields, "version"), Some("v3"));
assert_eq!(inspection_value(&fields, "version_value"), Some("3"));
assert_eq!(inspection_value(&fields, "msg_id"), Some("1"));
assert_eq!(inspection_value(&fields, "msg_max_size"), Some("1500"));
assert_eq!(inspection_value(&fields, "msg_flags"), Some("00"));
assert_eq!(inspection_value(&fields, "msg_security_model"), Some("3"));
assert_eq!(
inspection_value(&fields, "msg_security_parameters_len"),
Some("0")
);
assert_eq!(inspection_value(&fields, "scoped_data_len"), Some("19"));
assert!(decoded.show().contains(" msg_id: 1"));
Ok(())
}
#[test]
fn snmp_v3_message_preserves_unknown_security_model_and_security_bytes() -> Result<()> {
let scoped_data = minimal_plaintext_v3_scoped_data();
let snmp = Snmp::v3(7, 65_535, [0x01], 999, [0xaa, 0xbb], scoped_data.clone());
let bytes = snmp.compile()?;
let decoded = Snmp::decode(&bytes)?;
let v3 = decoded.as_v3().expect("decoded v3 wrapper");
assert_eq!(v3.msg_id(), 7);
assert_eq!(v3.max_size(), 65_535);
assert_eq!(v3.flags(), &[0x01]);
assert_eq!(v3.security_model(), 999);
assert_eq!(v3.security_parameters(), &[0xaa, 0xbb]);
assert_eq!(v3.scoped_data(), scoped_data);
assert_eq!(decoded.compile()?, bytes);
assert!(decoded.summary().contains("msg_security_parameters_len=2"));
assert!(!decoded.summary().contains("aa bb"));
assert!(!decoded.show().contains("aa bb"));
Ok(())
}
#[test]
fn snmp_message_decode_preserves_unknown_version_and_unknown_pdu() -> Result<()> {
let bytes = [
0x30, 0x0b, 0x02, 0x01, 0x04, 0x04, 0x01, b'x', 0xa9, 0x03, 0x02, 0x01, 0x05,
];
let decoded = Snmp::decode(&bytes)?;
let unknown = decoded.pdu().as_unknown().expect("unknown PDU");
assert_eq!(decoded.version(), SnmpVersion::Unknown(4));
assert_eq!(decoded.version_value(), 4);
assert_eq!(decoded.community(), b"x");
assert_eq!(unknown.tag_number(), 9);
assert!(unknown.is_constructed());
assert_eq!(unknown.body(), &[0x02, 0x01, 0x05]);
assert_eq!(unknown.raw_tlv_bytes(), Some(&bytes[8..]));
assert_eq!(decoded.compile()?, bytes);
Ok(())
}
#[test]
fn snmp_message_decode_rejects_non_pdu_tlv() {
let bytes = [0x30, 0x07, 0x02, 0x01, 0x01, 0x04, 0x00, 0x30, 0x00];
let error = Snmp::decode(&bytes).expect_err("non-PDU TLV");
assert_eq!(
error,
crate::error::CrafterError::invalid_field_value(
"snmp.pdu",
"expected context-specific PDU tag"
)
);
}
#[test]
fn snmp_v1_message_override_setters_preserve_explicit_wire_choices() -> Result<()> {
let malformed_pdu = SnmpPdu::get_request(1, SnmpVarBindList::empty())?.length(0);
let snmp = Snmp::v1_get_request(b"public".to_vec(), 99, SnmpVarBindList::empty())?
.with_version(SnmpVersion::Unknown(4))
.with_community([0xff])
.with_pdu(malformed_pdu);
assert_eq!(snmp.version(), SnmpVersion::Unknown(4));
assert_eq!(snmp.community(), &[0xff]);
assert_eq!(snmp.pdu().explicit_length(), Some(0));
assert_eq!(
snmp.compile()?,
[
0x30, 0x13, 0x02, 0x01, 0x04, 0x04, 0x01, 0xff, 0xa0, 0x00, 0x02, 0x01, 0x01, 0x02,
0x01, 0x00, 0x02, 0x01, 0x00, 0x30, 0x00,
]
);
Ok(())
}
#[test]
fn snmp_v2c_message_request_response_builders_emit_source_backed_wrappers() -> Result<()> {
let request = Snmp::v2c_get_request(b"public".to_vec(), 1, SnmpVarBindList::empty())?;
assert_eq!(
request.compile()?,
[
0x30, 0x18, 0x02, 0x01, 0x01, 0x04, 0x06, b'p', b'u', b'b', b'l', b'i', b'c', 0xa0,
0x0b, 0x02, 0x01, 0x01, 0x02, 0x01, 0x00, 0x02, 0x01, 0x00, 0x30, 0x00,
]
);
let response = Snmp::v2c_response(b"public".to_vec(), 128, SnmpVarBindList::empty())?;
assert_eq!(
response.compile()?,
[
0x30, 0x19, 0x02, 0x01, 0x01, 0x04, 0x06, b'p', b'u', b'b', b'l', b'i', b'c', 0xa2,
0x0c, 0x02, 0x02, 0x00, 0x80, 0x02, 0x01, 0x00, 0x02, 0x01, 0x00, 0x30, 0x00,
]
);
let error_response = Snmp::v2c_response_error(
b"public".to_vec(),
129,
super::super::registry::SNMP_ERROR_STATUS_GEN_ERR,
2,
SnmpVarBindList::empty(),
)?;
assert_eq!(request.version(), SnmpVersion::V2c);
assert_eq!(request.pdu().tag_number(), SnmpPdu::TAG_GET_REQUEST);
assert_eq!(response.pdu().tag_number(), SnmpPdu::TAG_RESPONSE);
assert_eq!(
error_response
.pdu()
.as_response()?
.expect("response fields")
.error_status(),
super::super::registry::SNMP_ERROR_STATUS_GEN_ERR,
);
Ok(())
}
#[test]
fn snmp_v2c_message_request_set_bulk_inform_trap_report_builders_select_tags() -> Result<()> {
let get_next = Snmp::v2c_get_next_request(b"public".to_vec(), 2, SnmpVarBindList::empty())?;
let set = Snmp::v2c_set_request(b"public".to_vec(), 3, SnmpVarBindList::empty())?;
let bulk =
Snmp::v2c_get_bulk_request(b"public".to_vec(), 4, 1, 10, SnmpVarBindList::empty())?;
let inform = Snmp::v2c_inform_request(b"public".to_vec(), 5, SnmpVarBindList::empty())?;
let trap = Snmp::v2c_snmpv2_trap(b"public".to_vec(), 6, SnmpVarBindList::empty())?;
let report = Snmp::v2c_report(b"public".to_vec(), 7, SnmpVarBindList::empty())?;
assert_eq!(get_next.version(), SnmpVersion::V2c);
assert_eq!(get_next.pdu().tag_number(), SnmpPdu::TAG_GET_NEXT_REQUEST);
assert_eq!(set.pdu().tag_number(), SnmpPdu::TAG_SET_REQUEST);
assert_eq!(bulk.pdu().tag_number(), SnmpPdu::TAG_GET_BULK_REQUEST);
assert_eq!(inform.pdu().tag_number(), SnmpPdu::TAG_INFORM_REQUEST);
assert_eq!(trap.pdu().tag_number(), SnmpPdu::TAG_TRAP_V2);
assert_eq!(report.pdu().tag_number(), SnmpPdu::TAG_REPORT);
assert_eq!(
bulk.compile()?,
[
0x30, 0x18, 0x02, 0x01, 0x01, 0x04, 0x06, b'p', b'u', b'b', b'l', b'i', b'c', 0xa5,
0x0b, 0x02, 0x01, 0x04, 0x02, 0x01, 0x01, 0x02, 0x01, 0x0a, 0x30, 0x00,
]
);
assert!(inform.summary().contains("pdu_type=inform-request"));
assert!(trap.summary().contains("pdu_type=snmpv2-trap"));
assert!(report.summary().contains("pdu_type=report"));
Ok(())
}
#[test]
fn snmp_v2c_message_preserves_raw_community_bytes_and_unknown_version_override() -> Result<()> {
let community = [0x00, 0xff, 0x80, b'a'];
let snmp = Snmp::v2c_get_bulk_request(community, 7, 1, 10, SnmpVarBindList::empty())?
.with_version(SnmpVersion::Unknown(4));
assert_eq!(snmp.version(), SnmpVersion::Unknown(4));
assert_eq!(snmp.community(), &community);
assert_eq!(
snmp.compile()?,
[
0x30, 0x16, 0x02, 0x01, 0x04, 0x04, 0x04, 0x00, 0xff, 0x80, b'a', 0xa5, 0x0b, 0x02,
0x01, 0x07, 0x02, 0x01, 0x01, 0x02, 0x01, 0x0a, 0x30, 0x00,
]
);
let bytes = snmp.compile()?;
let content = ber::decode_sequence_exact(&bytes)?;
let (header, rest) = SnmpMessageHeader::decode(content)?;
let (pdu, rest) = SnmpPdu::decode(rest)?;
ber::require_sequence_exact(rest)?;
assert_eq!(header.version(), SnmpVersion::Unknown(4));
assert_eq!(header.community(), &community);
assert_eq!(pdu.tag_number(), SnmpPdu::TAG_GET_BULK_REQUEST);
Ok(())
}
#[test]
fn snmp_layer_summary_and_inspection_keep_pdu_fields_visible() -> Result<()> {
let snmp = Snmp::v2c(
b"private".to_vec(),
SnmpPdu::response_error(128, 2, 3, crate::protocols::snmp::SnmpVarBindList::empty())?,
);
let summary = snmp.summary();
assert!(summary.contains("version=v2c"));
assert!(summary.contains("community_len=7"));
assert!(summary.contains("pdu_type=response"));
assert!(!summary.contains("private"));
let fields = snmp.inspection_fields();
assert!(fields.contains(&("version", "v2c".to_string())));
assert!(fields.contains(&("version_value", "1".to_string())));
assert!(fields.contains(&("community_len", "7".to_string())));
assert!(fields.contains(&("pdu_type", "response".to_string())));
assert!(fields.contains(&("request_id", "128".to_string())));
assert!(!fields.iter().any(|(_, value)| value == "private"));
Ok(())
}
fn inspection_value<'a>(
fields: &'a [(&'static str, String)],
name: &'static str,
) -> Option<&'a str> {
fields
.iter()
.find_map(|(field, value)| (*field == name).then_some(value.as_str()))
}
fn assert_message_hides_community(snmp: &Snmp, community: &[u8]) {
let secret = String::from_utf8_lossy(community);
assert!(!snmp.summary().contains(secret.as_ref()));
assert!(!snmp.show().contains(secret.as_ref()));
assert!(!snmp
.inspection_fields()
.iter()
.any(|(_, value)| value == secret.as_ref()));
}
#[test]
fn snmp_message_summary_v1_request_exposes_safe_fields() -> Result<()> {
let community = b"public";
let varbind =
crate::protocols::snmp::SnmpVarBind::null(SnmpOid::from_dotted("1.3.6.1.2.1.1.3.0")?);
let snmp = Snmp::v1_get_request(
community.to_vec(),
7,
crate::protocols::snmp::SnmpVarBindList::new(vec![varbind]),
)?;
let summary = snmp.summary();
assert!(summary.contains("version=v1"));
assert!(summary.contains("community_len=6"));
assert!(summary.contains("pdu_type=get-request"));
assert!(summary.contains("request_id=7"));
assert!(summary.contains("varbind_count=1"));
let fields = snmp.inspection_fields();
assert_eq!(inspection_value(&fields, "version"), Some("v1"));
assert_eq!(inspection_value(&fields, "version_value"), Some("0"));
assert_eq!(inspection_value(&fields, "community_len"), Some("6"));
assert_eq!(inspection_value(&fields, "pdu_type"), Some("get-request"));
assert_eq!(inspection_value(&fields, "request_id"), Some("7"));
assert_eq!(inspection_value(&fields, "varbind_count"), Some("1"));
assert_message_hides_community(&snmp, community);
let show = snmp.show();
assert!(show.starts_with("Snmp\n"));
assert!(show.contains(" version: v1"));
assert!(show.contains(" community_len: 6"));
assert!(show.contains(" pdu_type: get-request"));
Ok(())
}
#[test]
fn snmp_message_summary_v2c_response_exposes_request_and_error_fields() -> Result<()> {
let community = b"private";
let snmp = Snmp::v2c_response_error(
community.to_vec(),
128,
2,
3,
crate::protocols::snmp::SnmpVarBindList::empty(),
)?;
let summary = snmp.summary();
assert!(summary.contains("version=v2c"));
assert!(summary.contains("community_len=7"));
assert!(summary.contains("pdu_type=response"));
assert!(summary.contains("request_id=128"));
assert!(summary.contains("error_status=no-such-name(2)"));
assert!(summary.contains("varbind_count=0"));
let fields = snmp.inspection_fields();
assert_eq!(inspection_value(&fields, "version"), Some("v2c"));
assert_eq!(inspection_value(&fields, "version_value"), Some("1"));
assert_eq!(inspection_value(&fields, "pdu_type"), Some("response"));
assert_eq!(inspection_value(&fields, "request_id"), Some("128"));
assert_eq!(inspection_value(&fields, "error_status"), Some("2"));
assert_eq!(
inspection_value(&fields, "error_status_label"),
Some("no-such-name")
);
assert_message_hides_community(&snmp, community);
Ok(())
}
#[test]
fn snmp_message_summary_unknown_pdu_keeps_raw_metadata_visible() {
let community = b"public";
let snmp = Snmp::v2c(
community.to_vec(),
SnmpPdu::unknown(9, true, [0x02, 0x01, 0x05]),
);
assert_eq!(
snmp.summary(),
"Snmp(version=v2c, community_len=6, pdu=SnmpPdu(pdu_type=pdu-9 pdu_tag=9 constructed=true body_length=3))"
);
let fields = snmp.inspection_fields();
assert_eq!(inspection_value(&fields, "pdu_type"), Some("pdu-9"));
assert_eq!(inspection_value(&fields, "pdu_tag"), Some("9"));
assert_eq!(inspection_value(&fields, "pdu_tag_status"), Some("unknown"));
assert_eq!(inspection_value(&fields, "pdu_ber_length"), Some("3"));
assert_eq!(inspection_value(&fields, "body_bytes"), Some("02 01 05"));
assert_message_hides_community(&snmp, community);
}
#[test]
fn snmp_message_summary_raw_tlv_varbind_keeps_value_metadata_visible() -> Result<()> {
let community = b"agent-secret";
let raw = crate::protocols::snmp::SnmpVarBind::raw_value_tlv(
SnmpOid::from_dotted("1.3.6.1.2.1.1.5.0")?,
[0xc3, 0x81, 0x02, 0xde, 0xad],
);
let snmp = Snmp::v2c_response(
community.to_vec(),
9,
crate::protocols::snmp::SnmpVarBindList::new(vec![raw]),
)?;
let summary = snmp.summary();
assert!(summary.contains("pdu_type=response"));
assert!(summary.contains("request_id=9"));
assert!(summary.contains("varbind_count=1"));
let fields = snmp.inspection_fields();
assert_eq!(
inspection_value(&fields, "varbind[0]"),
Some("1.3.6.1.2.1.1.5.0=private-3")
);
assert!(snmp.show().contains("private-3"));
assert_message_hides_community(&snmp, community);
Ok(())
}
#[test]
fn snmp_layer_clone_downcast_and_mutable_accessors_work() -> Result<()> {
let snmp = Snmp::v2c(
b"public".to_vec(),
SnmpPdu::get_request(7, crate::protocols::snmp::SnmpVarBindList::empty())?,
);
let mut boxed: Box<dyn Layer> = Box::new(snmp.clone());
assert!(boxed.as_any().downcast_ref::<Snmp>().is_some());
let pdu = boxed
.as_any_mut()
.downcast_mut::<Snmp>()
.expect("boxed SNMP")
.pdu_mut();
*pdu = pdu.clone().length(0);
assert_eq!(pdu.explicit_length(), Some(0));
let cloned = boxed.clone_layer();
assert!(cloned.as_any().downcast_ref::<Snmp>().is_some());
let owned = cloned
.into_any()
.downcast::<Snmp>()
.expect("owned SNMP downcast");
assert_eq!(owned.version(), SnmpVersion::V2c);
Ok(())
}
#[test]
fn snmp_layer_prelude_exports_layer_surface() -> crate::Result<()> {
use crate::prelude::*;
let pdu = SnmpPdu::get_request(9, SnmpVarBindList::empty())?;
let packet = Packet::from_layer(Snmp::v2c(b"public".to_vec(), pdu));
assert!(packet.layer::<Snmp>().is_some());
assert!(packet.summary().contains("Snmp(version=v2c"));
Ok(())
}
#[test]
fn snmp_defaults_overrides_auto_fill_and_preserve_malformed_message_fields() -> Result<()> {
let name = SnmpOid::from_dotted("1.3.6.1.2.1.1.5.0")?;
let raw_varbind = SnmpVarBind::raw_value_tlv_with_length(name, 0x04, 5, [0xaa])?;
let varbinds = SnmpVarBindList::new(vec![raw_varbind]).length(0);
let pdu = SnmpPdu::get_request_with_fields(127, 99, 300, varbinds)?.length(0);
let snmp = Snmp::v2c_get_request(b"public".to_vec(), 1, SnmpVarBindList::empty())?
.with_version(SnmpVersion::Unknown(4))
.with_community([0x00, 0xff])
.with_pdu(pdu)
.length(3);
let bytes = snmp.compile()?;
assert_eq!(snmp.explicit_length(), Some(3));
assert_eq!(snmp.effective_length(), 3);
assert_eq!(snmp.encoded_len(), bytes.len());
assert_eq!(&bytes[..2], &[0x30, 0x03]);
assert_eq!(&bytes[2..9], &[0x02, 0x01, 0x04, 0x04, 0x02, 0x00, 0xff]);
assert_eq!(&bytes[9..11], &[0xa0, 0x00]);
assert_eq!(
&bytes[11..],
&[
0x02, 0x01, 0x7f, 0x02, 0x01, 0x63, 0x02, 0x02, 0x01, 0x2c, 0x30, 0x00, 0x30, 0x0d,
0x06, 0x08, 0x2b, 0x06, 0x01, 0x02, 0x01, 0x01, 0x05, 0x00, 0x04, 0x05, 0xaa,
]
);
let auto = snmp.clone().clear_length();
let auto_bytes = auto.compile()?;
assert_eq!(auto.explicit_length(), None);
assert_eq!(auto.effective_length(), auto_bytes.len() - 2);
assert_eq!(&auto_bytes[..2], &[0x30, 0x24]);
assert_eq!(&auto_bytes[2..], &bytes[2..]);
let raw_pdu_message = [
0x30, 0x0c, 0x02, 0x01, 0x04, 0x04, 0x01, b'x', 0xa9, 0x81, 0x03, 0x02, 0x01, 0x05,
];
let decoded = Snmp::decode(&raw_pdu_message)?;
assert_eq!(decoded.version(), SnmpVersion::Unknown(4));
assert_eq!(decoded.community(), b"x");
assert_eq!(decoded.pdu().raw_tlv_bytes(), Some(&raw_pdu_message[8..]));
assert_eq!(decoded.compile()?, raw_pdu_message);
Ok(())
}
}