use std::net::SocketAddr;
use bytes::Bytes;
use crate::ber::{Decoder, tag};
use crate::error::internal::DecodeErrorKind;
use crate::error::{Error, Result};
use crate::message::{CommunityMessage, V3Message};
use crate::pdu::{Pdu, PduType, TrapV1Pdu};
use crate::v3::UsmSecurityParams;
use crate::v3::compute_engine_boots_time;
use crate::v3::encode::encode_v3_response;
use crate::v3::process::{UsmFailure, V3Inbound, V3LocalContext, V3Role, process_v3_inbound};
use super::types::DerivedKeys;
use super::varbind::extract_notification_varbinds;
use super::{Notification, ReceiverInner};
impl super::NotificationReceiver {
pub(super) async fn handle_v1(
&self,
data: Bytes,
source: SocketAddr,
) -> Result<Option<Notification>> {
let mut decoder = Decoder::with_target(data, source);
let mut seq = decoder.read_sequence()?;
let _version = seq.read_integer()?;
let community = seq.read_octet_string()?;
if !super::community_allowed(&self.inner.communities, &community) {
tracing::debug!(target: "async_snmp::notification", { snmp.source = %source }, "dropped v1 notification with unaccepted community");
return Ok(None);
}
let pdu_tag = seq.peek_tag().ok_or_else(|| {
tracing::debug!(target: "async_snmp::notification", { source = %source, kind = %DecodeErrorKind::TruncatedData }, "truncated notification data");
Error::MalformedResponse { target: source }.boxed()
})?;
if pdu_tag == tag::pdu::TRAP_V1 {
let trap = TrapV1Pdu::decode(&mut seq)?;
Ok(Some(Notification::TrapV1 { community, trap }))
} else {
Ok(None)
}
}
pub(super) async fn handle_v2c(
&self,
data: Bytes,
source: SocketAddr,
) -> Result<Option<Notification>> {
let msg = CommunityMessage::decode(data)?;
if !super::community_allowed(&self.inner.communities, &msg.community) {
tracing::debug!(target: "async_snmp::notification", { snmp.source = %source }, "dropped v2c notification with unaccepted community");
return Ok(None);
}
let Some(pdu) = msg.pdu.standard() else {
return Ok(None);
};
match pdu.pdu_type {
PduType::TrapV2 => {
let (uptime, trap_oid, varbinds) = extract_notification_varbinds(pdu)?;
Ok(Some(Notification::TrapV2c {
community: msg.community,
uptime,
trap_oid,
varbinds,
request_id: pdu.request_id,
}))
}
PduType::InformRequest => {
let (uptime, trap_oid, varbinds) = extract_notification_varbinds(pdu)?;
let request_id = pdu.request_id;
let response = pdu.to_response();
let response_msg = CommunityMessage::v2c(msg.community.clone(), response);
let response_bytes = response_msg.encode();
self.inner
.socket
.send_to(&response_bytes, source)
.await
.map_err(|e| Error::Network {
target: source,
source: e,
})?;
tracing::debug!(target: "async_snmp::notification", { snmp.source = %source, snmp.request_id = request_id }, "sent Inform response");
Ok(Some(Notification::InformV2c {
community: msg.community,
uptime,
trap_oid,
varbinds,
request_id,
}))
}
_ => Ok(None), }
}
pub(super) async fn handle_v3(
&self,
data: Bytes,
source: SocketAddr,
) -> Result<Option<Notification>> {
let total_secs = self.inner.engine_start.elapsed().as_secs();
let (our_boots, our_time) =
compute_engine_boots_time(self.inner.engine_boots_base, total_secs);
let usm_ctx = V3LocalContext {
engine_id: &self.inner.engine_id,
engine_boots: our_boots,
engine_time: our_time,
usm_users: &self.inner.usm_users,
stats: &self.inner.usm_stats,
mpd: None,
source,
};
let role = V3Role::Receiver {
remote_engines: &self.inner.remote_engines,
max_remote_engines: super::MAX_REMOTE_ENGINES,
};
let inbound = match process_v3_inbound(data, &usm_ctx, &role)? {
V3Inbound::Failed { failure, report } => {
tracing::warn!(target: "async_snmp::notification", { snmp.source = %source, snmp.failure = ?failure }, "USM processing failed for inbound message");
if let Some(report) = report {
if let Err(e) = self.inner.socket.send_to(&report, source).await {
tracing::debug!(target: "async_snmp::notification", { snmp.source = %source, error = %e }, "failed to send USM report");
} else {
tracing::debug!(target: "async_snmp::notification", { snmp.source = %source }, "sent USM report");
}
}
return match failure {
UsmFailure::WrongDigests
| UsmFailure::NotInTimeWindows
| UsmFailure::DecryptionErrors => Err(Error::Auth { target: source }.boxed()),
UsmFailure::UnknownEngineIds
| UsmFailure::UnknownUserNames
| UsmFailure::UnsupportedSecLevels => Ok(None),
};
}
V3Inbound::RemoteNotInTimeWindow => {
return Err(Error::Auth { target: source }.boxed());
}
V3Inbound::Message(inbound) => inbound,
};
let msg = &inbound.msg;
let usm_params = &inbound.usm_params;
let scoped_pdu = &inbound.scoped_pdu;
let security_level = inbound.security_level;
let username = usm_params.username.clone();
let context_engine_id = scoped_pdu.context_engine_id.clone();
let context_name = scoped_pdu.context_name.clone();
let pdu = &scoped_pdu.pdu;
match pdu.pdu_type {
PduType::TrapV2 => {
let (uptime, trap_oid, varbinds) = extract_notification_varbinds(pdu)?;
Ok(Some(Notification::TrapV3 {
username,
context_engine_id,
context_name,
security_level,
uptime,
trap_oid,
varbinds,
request_id: pdu.request_id,
}))
}
PduType::InformRequest => {
let (uptime, trap_oid, varbinds) = extract_notification_varbinds(pdu)?;
let request_id = pdu.request_id;
let response_pdu = pdu.to_response();
let response_bytes = build_v3_response(
&self.inner,
msg,
usm_params,
response_pdu,
context_engine_id.clone(),
context_name.clone(),
Some(&inbound.derived_keys),
)?;
self.inner
.socket
.send_to(&response_bytes, source)
.await
.map_err(|e| Error::Network {
target: source,
source: e,
})?;
tracing::debug!(target: "async_snmp::notification", { snmp.source = %source, snmp.request_id = request_id, snmp.security_level = ?security_level }, "sent V3 Inform response");
Ok(Some(Notification::InformV3 {
username,
context_engine_id,
context_name,
security_level,
uptime,
trap_oid,
varbinds,
request_id,
}))
}
_ => Ok(None),
}
}
}
fn build_v3_response(
inner: &ReceiverInner,
incoming_msg: &V3Message,
incoming_usm: &UsmSecurityParams,
response_pdu: Pdu,
context_engine_id: Bytes,
context_name: Bytes,
derived_keys: Option<&DerivedKeys>,
) -> Result<Bytes> {
let response_usm = UsmSecurityParams::new(
incoming_usm.engine_id.clone(),
incoming_usm.engine_boots,
incoming_usm.engine_time,
incoming_usm.username.clone(),
);
encode_v3_response(
response_pdu,
incoming_msg.global_data.msg_id,
incoming_msg.global_data.msg_max_size,
incoming_msg.global_data.msg_flags.security_level,
response_usm,
context_engine_id,
context_name,
derived_keys,
&inner.salt_counter,
inner.local_addr,
)
}