1use crate::ber::{Decoder, EncodeBuf, tag};
11use crate::error::internal::DecodeErrorKind;
12use crate::error::{Error, Result, UNKNOWN_TARGET};
13use crate::pdu::{GetBulkPdu, Pdu, PduType, TrapV1Pdu};
14use crate::version::Version;
15use bytes::Bytes;
16
17#[derive(Debug, Clone, PartialEq, Eq)]
22pub enum CommunityPdu {
23 Standard(Pdu),
25 TrapV1(TrapV1Pdu),
27}
28
29impl CommunityPdu {
30 #[must_use]
32 pub fn standard(&self) -> Option<&Pdu> {
33 match self {
34 Self::Standard(p) => Some(p),
35 Self::TrapV1(_) => None,
36 }
37 }
38
39 #[must_use]
41 pub fn trap_v1(&self) -> Option<&TrapV1Pdu> {
42 match self {
43 Self::TrapV1(t) => Some(t),
44 Self::Standard(_) => None,
45 }
46 }
47
48 #[must_use]
50 pub fn pdu_type(&self) -> PduType {
51 match self {
52 Self::Standard(p) => p.pdu_type,
53 Self::TrapV1(_) => PduType::TrapV1,
54 }
55 }
56
57 pub(crate) fn encode(&self, buf: &mut EncodeBuf) {
59 match self {
60 Self::Standard(p) => p.encode(buf),
61 Self::TrapV1(t) => t.encode(buf),
62 }
63 }
64}
65
66impl From<Pdu> for CommunityPdu {
67 fn from(p: Pdu) -> Self {
68 Self::Standard(p)
69 }
70}
71
72impl From<TrapV1Pdu> for CommunityPdu {
73 fn from(t: TrapV1Pdu) -> Self {
74 Self::TrapV1(t)
75 }
76}
77
78#[derive(Debug, Clone)]
85pub struct CommunityMessage {
86 pub version: Version,
88 pub community: Bytes,
90 pub pdu: CommunityPdu,
92}
93
94impl CommunityMessage {
95 pub fn new(version: Version, community: impl Into<Bytes>, pdu: Pdu) -> Self {
100 assert!(
101 matches!(version, Version::V1 | Version::V2c),
102 "CommunityMessage only supports V1/V2c, not {version:?}"
103 );
104 Self {
105 version,
106 community: community.into(),
107 pdu: CommunityPdu::Standard(pdu),
108 }
109 }
110
111 pub fn v2c(community: impl Into<Bytes>, pdu: Pdu) -> Self {
113 Self::new(Version::V2c, community, pdu)
114 }
115
116 pub fn v1(community: impl Into<Bytes>, pdu: Pdu) -> Self {
118 Self::new(Version::V1, community, pdu)
119 }
120
121 pub fn v1_trap(community: impl Into<Bytes>, trap: TrapV1Pdu) -> Self {
123 Self {
124 version: Version::V1,
125 community: community.into(),
126 pdu: CommunityPdu::TrapV1(trap),
127 }
128 }
129
130 pub fn encode(&self) -> Bytes {
132 let mut buf = EncodeBuf::new();
133
134 buf.push_sequence(|buf| {
135 self.pdu.encode(buf);
136 buf.push_octet_string(&self.community);
137 buf.push_integer(self.version.as_i32());
138 });
139
140 buf.finish()
141 }
142
143 pub fn decode(data: Bytes) -> Result<Self> {
147 let mut decoder = Decoder::new(data);
148 Self::decode_from(&mut decoder)
149 }
150
151 pub(crate) fn decode_from(decoder: &mut Decoder) -> Result<Self> {
153 let mut seq = decoder.read_sequence()?;
154
155 let version_num = seq.read_integer()?;
156 let version = Version::from_i32(version_num).ok_or_else(|| {
157 tracing::debug!(target: "async_snmp::ber", { offset = seq.offset(), kind = %DecodeErrorKind::UnknownVersion(version_num) }, "decode error");
158 Error::MalformedResponse {
159 target: UNKNOWN_TARGET,
160 }
161 .boxed()
162 })?;
163
164 Self::decode_from_sequence(&mut seq, version)
165 }
166
167 pub(crate) fn decode_from_sequence(seq: &mut Decoder, version: Version) -> Result<Self> {
169 if version == Version::V3 {
170 tracing::debug!(target: "async_snmp::ber", { offset = seq.offset(), kind = %DecodeErrorKind::UnknownVersion(3) }, "decode error");
171 return Err(Error::MalformedResponse {
172 target: UNKNOWN_TARGET,
173 }
174 .boxed());
175 }
176
177 let community = seq.read_octet_string()?;
178
179 let pdu_tag = seq.peek_tag().ok_or_else(|| {
181 tracing::debug!(target: "async_snmp::ber", { offset = seq.offset(), kind = %DecodeErrorKind::TruncatedData }, "truncated community message");
182 Error::MalformedResponse {
183 target: UNKNOWN_TARGET,
184 }
185 .boxed()
186 })?;
187
188 let pdu = if pdu_tag == tag::pdu::TRAP_V1 {
189 CommunityPdu::TrapV1(TrapV1Pdu::decode(seq)?)
190 } else {
191 CommunityPdu::Standard(Pdu::decode(seq)?)
192 };
193
194 Ok(CommunityMessage {
195 version,
196 community,
197 pdu,
198 })
199 }
200
201 pub fn into_pdu(self) -> Option<Pdu> {
205 match self.pdu {
206 CommunityPdu::Standard(p) => Some(p),
207 CommunityPdu::TrapV1(_) => None,
208 }
209 }
210
211 pub fn into_community_pdu(self) -> CommunityPdu {
213 self.pdu
214 }
215
216 pub fn encode_bulk(version: Version, community: impl Into<Bytes>, pdu: &GetBulkPdu) -> Bytes {
220 debug_assert!(version != Version::V1, "GETBULK not supported in SNMPv1");
221
222 let community = community.into();
223 let mut buf = EncodeBuf::new();
224
225 buf.push_sequence(|buf| {
226 pdu.encode(buf);
227 buf.push_octet_string(&community);
228 buf.push_integer(version.as_i32());
229 });
230
231 buf.finish()
232 }
233}
234
235#[cfg(test)]
236mod tests {
237 use super::*;
238 use crate::oid;
239 use crate::pdu::{GenericTrap, TrapV1Pdu};
240
241 #[test]
242 fn test_v1_trap_roundtrip() {
243 let trap = TrapV1Pdu::new(
244 oid!(1, 3, 6, 1, 4, 1, 9999),
245 [192, 168, 1, 1],
246 GenericTrap::LinkDown,
247 0,
248 12345,
249 vec![],
250 );
251 let msg = CommunityMessage::v1_trap(b"public".as_slice(), trap);
252
253 let encoded = msg.encode();
254 let decoded = CommunityMessage::decode(encoded).unwrap();
255
256 assert_eq!(decoded.version, Version::V1);
257 assert_eq!(decoded.community.as_ref(), b"public");
258 match decoded.pdu {
259 CommunityPdu::TrapV1(ref t) => {
260 assert_eq!(t.enterprise, oid!(1, 3, 6, 1, 4, 1, 9999));
261 assert_eq!(t.agent_addr, [192, 168, 1, 1]);
262 assert_eq!(t.generic_trap, GenericTrap::LinkDown);
263 assert_eq!(t.time_stamp, 12345);
264 }
265 CommunityPdu::Standard(_) => panic!("expected TrapV1 pdu"),
266 }
267 }
268
269 #[test]
270 fn test_v1_roundtrip() {
271 let pdu = Pdu::get_request(42, &[oid!(1, 3, 6, 1, 2, 1, 1, 1, 0)]);
272 let msg = CommunityMessage::v1(b"public".as_slice(), pdu);
273
274 let encoded = msg.encode();
275 let decoded = CommunityMessage::decode(encoded).unwrap();
276
277 assert_eq!(decoded.version, Version::V1);
278 assert_eq!(decoded.community.as_ref(), b"public");
279 assert_eq!(decoded.pdu.standard().unwrap().request_id, 42);
280 }
281
282 #[test]
283 fn test_v2c_roundtrip() {
284 let pdu = Pdu::get_request(123, &[oid!(1, 3, 6, 1, 2, 1, 1, 1, 0)]);
285 let msg = CommunityMessage::v2c(b"private".as_slice(), pdu);
286
287 let encoded = msg.encode();
288 let decoded = CommunityMessage::decode(encoded).unwrap();
289
290 assert_eq!(decoded.version, Version::V2c);
291 assert_eq!(decoded.community.as_ref(), b"private");
292 assert_eq!(decoded.pdu.standard().unwrap().request_id, 123);
293 }
294
295 #[test]
296 fn test_version_preserved() {
297 for version in [Version::V1, Version::V2c] {
298 let pdu = Pdu::get_request(1, &[oid!(1, 3, 6, 1)]);
299 let msg = CommunityMessage::new(version, b"test".as_slice(), pdu);
300
301 let encoded = msg.encode();
302 let decoded = CommunityMessage::decode(encoded).unwrap();
303
304 assert_eq!(decoded.version, version);
305 }
306 }
307}