use crate::ast::*;
use crate::snmp_crypto::*;
use alloc::string::{String, ToString};
use alloc::vec::Vec;
use core::net::IpAddr;
use rasn::types::{Integer, ObjectIdentifier, OctetString};
use rasn_smi::v1::{IpAddress, NetworkAddress, TimeTicks};
use rasn_smi::v2::{ApplicationSyntax, ObjectSyntax, SimpleSyntax};
use rasn_snmp::v2::{BulkPdu, Pdu, Pdus as PdusV2, VarBind, VarBindValue};
pub fn build_snmp1_payload(stmt: &FrameStatement, src_ip: IpAddr) -> Result<Vec<u8>, String> {
use rasn_snmp::v1::{Message, Pdus, Trap};
let community_str = stmt.community.unwrap_or("public");
let community = OctetString::from(community_str.as_bytes().to_vec());
let enterprise_str = stmt.enterprise.unwrap_or("1.3.6.1.4.1");
let trap_oid = parse_oid(enterprise_str)?;
let agent_ip = match stmt.agent_ip.unwrap_or(src_ip) {
IpAddr::V4(addr) => addr.octets(),
_ => return Err("SNMPv1 Trap requires IPv4 address".to_string()),
};
let trap = Trap {
enterprise: trap_oid,
agent_addr: NetworkAddress::Internet(IpAddress(agent_ip.into())),
generic_trap: Integer::from(stmt.gen_trap.unwrap_or(6)),
specific_trap: Integer::from(stmt.spec_trap.unwrap_or(1)),
time_stamp: TimeTicks(stmt.sys_up_time.unwrap_or(0)),
variable_bindings: convert_v1_varbinds(&stmt.varbinds)?,
};
let message = Message {
version: Integer::from(0),
community,
data: Pdus::Trap(trap),
};
rasn::ber::encode(&message).map_err(|e| alloc::format!("SNMP1 encode error: {:?}", e))
}
pub fn build_snmp2_payload(stmt: &FrameStatement) -> Result<Vec<u8>, String> {
use rasn_snmp::v2c::Message;
let community_str = stmt.community.unwrap_or("public");
let community = OctetString::from(community_str.as_bytes().to_vec());
let pdu_data = build_v2_pdus(stmt)?;
let message = Message {
version: Integer::from(1),
community,
data: pdu_data,
};
rasn::ber::encode(&message).map_err(|e| alloc::format!("SNMP2 encode error: {:?}", e))
}
pub fn build_snmp3_payload(stmt: &FrameStatement) -> Result<Vec<u8>, String> {
use rasn_snmp::v3::{HeaderData, Message, ScopedPdu, ScopedPduData, USMSecurityParameters};
let user_str = stmt.user.unwrap_or("admin");
let user = OctetString::from(user_str.as_bytes().to_vec());
let engine_id_str = stmt.engine_id.unwrap_or("8000000001");
let engine_id_bytes =
hex_to_bytes(engine_id_str).unwrap_or_else(|_| vec![0x80, 0x00, 0x00, 0x00, 0x01]);
let engine_id = OctetString::from(engine_id_bytes.clone());
let pdu_data = build_v2_pdus(stmt)?;
let scoped_pdu = ScopedPdu {
engine_id: engine_id.clone(),
name: OctetString::from(stmt.context_name.unwrap_or("").as_bytes().to_vec()),
data: pdu_data,
};
let mut flags = 0u8;
if stmt.auth.is_some() {
flags |= 1; }
if stmt.priv_param.is_some() {
flags |= 2; }
let auth_len = match stmt.auth.as_ref().map(|(t, _)| t) {
Some(SnmpAuthType::Md5) | Some(SnmpAuthType::Sha) => 12,
Some(SnmpAuthType::Sha256) => 24,
Some(SnmpAuthType::Sha384) => 32,
Some(SnmpAuthType::Sha512) => 48,
None => 0,
};
let mut usm_params = USMSecurityParameters {
authoritative_engine_id: engine_id.clone(),
authoritative_engine_boots: Integer::from(stmt.engine_boots.unwrap_or(0)),
authoritative_engine_time: Integer::from(stmt.engine_time.unwrap_or(0)),
user_name: user,
authentication_parameters: OctetString::from(vec![0; auth_len]),
privacy_parameters: OctetString::from(vec![0; 8]),
};
let mut auth_key = None;
if let Some((auth_type, pass)) = &stmt.auth {
auth_key = Some(match auth_type {
SnmpAuthType::Md5 => password_to_key_md5(pass.as_bytes(), &engine_id_bytes),
SnmpAuthType::Sha => password_to_key_sha1(pass.as_bytes(), &engine_id_bytes),
SnmpAuthType::Sha256 => password_to_key_sha256(pass.as_bytes(), &engine_id_bytes),
SnmpAuthType::Sha384 => password_to_key_sha384(pass.as_bytes(), &engine_id_bytes),
SnmpAuthType::Sha512 => password_to_key_sha512(pass.as_bytes(), &engine_id_bytes),
});
}
let mut priv_key = None;
let mut priv_iv = vec![0u8; 16];
if let Some((_priv_type, pass)) = &stmt.priv_param {
let hasher = stmt
.auth
.as_ref()
.map(|(t, _)| t)
.unwrap_or(&SnmpAuthType::Md5);
priv_key = Some(match hasher {
SnmpAuthType::Md5 => password_to_key_md5(pass.as_bytes(), &engine_id_bytes),
SnmpAuthType::Sha => password_to_key_sha1(pass.as_bytes(), &engine_id_bytes),
SnmpAuthType::Sha256 => password_to_key_sha256(pass.as_bytes(), &engine_id_bytes),
SnmpAuthType::Sha384 => password_to_key_sha384(pass.as_bytes(), &engine_id_bytes),
SnmpAuthType::Sha512 => password_to_key_sha512(pass.as_bytes(), &engine_id_bytes),
});
let iv = vec![0u8; 16];
priv_iv = iv;
usm_params.privacy_parameters = OctetString::from(priv_iv[0..8].to_vec());
} else {
usm_params.privacy_parameters = OctetString::from(vec![]);
}
if stmt.auth.is_none() {
usm_params.authentication_parameters = OctetString::from(vec![]);
}
let usm_bytes =
rasn::ber::encode(&usm_params).map_err(|e| alloc::format!("USM encode error: {:?}", e))?;
let scoped_data = if let Some((priv_type, _)) = &stmt.priv_param {
let plaintext_scoped = rasn::ber::encode(&scoped_pdu)
.map_err(|e| alloc::format!("ScopedPdu encode error: {:?}", e))?;
let pk = priv_key.unwrap();
let ciphertext = match priv_type {
SnmpPrivType::Des => encrypt_des(&pk[0..8], &priv_iv[0..8], &plaintext_scoped),
SnmpPrivType::Aes => encrypt_aes128(&pk[0..16], &priv_iv[0..16], &plaintext_scoped),
};
ScopedPduData::EncryptedPdu(OctetString::from(ciphertext))
} else {
ScopedPduData::CleartextPdu(scoped_pdu)
};
let mut message = Message {
version: Integer::from(3),
global_data: HeaderData {
message_id: Integer::from(stmt.req_id.unwrap_or(1)),
max_size: Integer::from(65507),
flags: OctetString::from(vec![flags]),
security_model: Integer::from(3),
},
security_parameters: OctetString::from(usm_bytes),
scoped_data,
};
if let Some((auth_type, _)) = &stmt.auth {
let whole_msg =
rasn::ber::encode(&message).map_err(|e| alloc::format!("Msg encode error: {:?}", e))?;
let ak = auth_key.unwrap();
let mac = match auth_type {
SnmpAuthType::Md5 => sign_md5(&ak, &whole_msg),
SnmpAuthType::Sha => sign_sha1(&ak, &whole_msg),
SnmpAuthType::Sha256 => sign_sha256(&ak, &whole_msg),
SnmpAuthType::Sha384 => sign_sha384(&ak, &whole_msg),
SnmpAuthType::Sha512 => sign_sha512(&ak, &whole_msg),
};
usm_params.authentication_parameters = OctetString::from(mac);
let usm_bytes = rasn::ber::encode(&usm_params)
.map_err(|e| alloc::format!("USM encode error: {:?}", e))?;
message.security_parameters = OctetString::from(usm_bytes);
}
rasn::ber::encode(&message).map_err(|e| alloc::format!("SNMP3 encode error: {:?}", e))
}
fn hex_to_bytes(hex: &str) -> Result<Vec<u8>, String> {
if hex.len() % 2 != 0 {
return Err("Hex string must have an even length".to_string());
}
let mut bytes = Vec::new();
for i in (0..hex.len()).step_by(2) {
bytes.push(u8::from_str_radix(&hex[i..i + 2], 16).map_err(|_| "Invalid hex")?);
}
Ok(bytes)
}
fn parse_oid(oid_str: &str) -> Result<ObjectIdentifier, String> {
let mut oid_vec = Vec::new();
for s in oid_str.trim_start_matches('.').split('.') {
match s.parse::<u32>() {
Ok(val) => oid_vec.push(val),
Err(_) => return Err(alloc::format!("Invalid OID segment: '{}'", s)),
}
}
ObjectIdentifier::new(oid_vec).ok_or_else(|| "Invalid OID structure".to_string())
}
fn convert_varbinds(vbs: &[VarBindAst]) -> Result<Vec<VarBind>, String> {
let mut out = Vec::new();
for vb in vbs {
let name = parse_oid(vb.oid)?;
let value = match &vb.val {
VarBindType::Null => VarBindValue::Unspecified,
VarBindType::String(s) => VarBindValue::Value(ObjectSyntax::Simple(
SimpleSyntax::String(s.as_bytes().to_vec().into()),
)),
VarBindType::Int(i) => VarBindValue::Value(ObjectSyntax::Simple(
SimpleSyntax::Integer(rasn::types::Integer::from(*i as i32)),
)),
VarBindType::TimeTicks(t) => VarBindValue::Value(ObjectSyntax::ApplicationWide(
ApplicationSyntax::Ticks(TimeTicks(*t)),
)),
VarBindType::Ip(ip) => {
let addr = match ip {
IpAddr::V4(v4) => v4.octets(),
_ => return Err("IPv6 not supported in VarBind".to_string()),
};
VarBindValue::Value(ObjectSyntax::ApplicationWide(ApplicationSyntax::Address(
IpAddress(addr.into()),
)))
}
};
out.push(VarBind { name, value });
}
Ok(out)
}
fn convert_v1_varbinds(vbs: &[VarBindAst]) -> Result<Vec<rasn_snmp::v1::VarBind>, String> {
use rasn_snmp::v1::VarBind;
let mut out = Vec::new();
for vb in vbs {
let name = parse_oid(vb.oid)?;
let value = match &vb.val {
VarBindType::Null => return Err("Null varbind not supported in v1".to_string()),
VarBindType::String(s) => rasn_smi::v1::ObjectSyntax::Simple(
rasn_smi::v1::SimpleSyntax::String(s.as_bytes().to_vec().into()),
),
VarBindType::Int(i) => rasn_smi::v1::ObjectSyntax::Simple(
rasn_smi::v1::SimpleSyntax::Number(rasn::types::Integer::from(*i as i32)),
),
VarBindType::TimeTicks(t) => rasn_smi::v1::ObjectSyntax::ApplicationWide(
rasn_smi::v1::ApplicationSyntax::Ticks(TimeTicks(*t)),
),
VarBindType::Ip(ip) => {
let addr = match ip {
IpAddr::V4(v4) => v4.octets(),
_ => return Err("IPv6 not supported in VarBind".to_string()),
};
rasn_smi::v1::ObjectSyntax::ApplicationWide(rasn_smi::v1::ApplicationSyntax::Address(
rasn_smi::v1::NetworkAddress::Internet(IpAddress(addr.into())),
))
}
};
out.push(VarBind { name, value });
}
Ok(out)
}
fn build_v2_pdus(stmt: &FrameStatement) -> Result<PdusV2, String> {
let mut varbinds = convert_varbinds(&stmt.varbinds)?;
if varbinds.is_empty() {
let sys_up_time_oid = ObjectIdentifier::new(vec![1, 3, 6, 1, 2, 1, 1, 3, 0]).unwrap();
let snmp_trap_oid_name =
ObjectIdentifier::new(vec![1, 3, 6, 1, 6, 3, 1, 1, 4, 1, 0]).unwrap();
let requested_trap_oid = parse_oid(stmt.oid.unwrap_or("1.3.6.1.6.3.1.1.5.3"))?;
varbinds = vec![
VarBind {
name: sys_up_time_oid,
value: VarBindValue::Value(ObjectSyntax::ApplicationWide(
ApplicationSyntax::Ticks(TimeTicks(stmt.sys_up_time.unwrap_or(0))),
)),
},
VarBind {
name: snmp_trap_oid_name,
value: VarBindValue::Value(ObjectSyntax::Simple(SimpleSyntax::ObjectId(
requested_trap_oid,
))),
},
];
}
let pdu = Pdu {
request_id: stmt.req_id.unwrap_or(1) as i32,
error_status: 0,
error_index: 0,
variable_bindings: varbinds.clone(),
};
let pdu_type = stmt.pdu_type.as_ref().unwrap_or(&SnmpPduType::Trap);
match pdu_type {
SnmpPduType::Trap => Ok(PdusV2::Trap(rasn_snmp::v2::Trap(pdu))),
SnmpPduType::Get => Ok(PdusV2::GetRequest(rasn_snmp::v2::GetRequest(pdu))),
SnmpPduType::Set => Ok(PdusV2::SetRequest(rasn_snmp::v2::SetRequest(pdu))),
SnmpPduType::Response => Ok(PdusV2::Response(rasn_snmp::v2::Response(pdu))),
SnmpPduType::Inform => Ok(PdusV2::InformRequest(rasn_snmp::v2::InformRequest(pdu))),
SnmpPduType::GetNext => Ok(PdusV2::GetNextRequest(rasn_snmp::v2::GetNextRequest(pdu))),
SnmpPduType::GetBulk => {
let bulk_pdu = BulkPdu {
request_id: stmt.req_id.unwrap_or(1) as i32,
non_repeaters: stmt.non_repeaters.unwrap_or(0),
max_repetitions: stmt.max_repetitions.unwrap_or(10),
variable_bindings: varbinds,
};
Ok(PdusV2::GetBulkRequest(rasn_snmp::v2::GetBulkRequest(
bulk_pdu,
)))
}
}
}