async_snmp/message/
v3.rs

1//! SNMPv3 message format (RFC 3412).
2//!
3//! V3 messages have a more complex structure than v1/v2c:
4//! ```text
5//! SEQUENCE {
6//!     INTEGER version (3)
7//!     SEQUENCE msgGlobalData {
8//!         INTEGER msgID
9//!         INTEGER msgMaxSize
10//!         OCTET STRING msgFlags (1 byte)
11//!         INTEGER msgSecurityModel
12//!     }
13//!     OCTET STRING msgSecurityParameters (opaque, USM-encoded)
14//!     msgData (ScopedPDU or encrypted OCTET STRING)
15//! }
16//! ```
17//!
18//! The msgData field is either:
19//! - A plaintext ScopedPDU (SEQUENCE) for noAuthNoPriv/authNoPriv
20//! - An encrypted OCTET STRING for authPriv (decrypts to ScopedPDU)
21
22use bytes::Bytes;
23
24use crate::ber::{Decoder, EncodeBuf};
25use crate::error::internal::DecodeErrorKind;
26use crate::error::{Error, Result, UNKNOWN_TARGET};
27use crate::pdu::Pdu;
28
29/// SNMPv3 security model identifiers.
30#[derive(Debug, Clone, Copy, PartialEq, Eq)]
31#[repr(i32)]
32pub enum SecurityModel {
33    /// User-based Security Model (RFC 3414)
34    Usm = 3,
35}
36
37impl SecurityModel {
38    /// Create from raw value.
39    pub fn from_i32(value: i32) -> Option<Self> {
40        match value {
41            3 => Some(Self::Usm),
42            _ => None,
43        }
44    }
45
46    /// Get the raw value.
47    pub fn as_i32(self) -> i32 {
48        self as i32
49    }
50}
51
52/// SNMPv3 security level.
53///
54/// The variants are ordered from least secure to most secure,
55/// supporting VACM-style level comparisons (e.g., `actual >= required`).
56#[derive(Debug, Clone, Copy, PartialEq, Eq, PartialOrd, Ord)]
57pub enum SecurityLevel {
58    /// No authentication, no privacy
59    NoAuthNoPriv,
60    /// Authentication only
61    AuthNoPriv,
62    /// Authentication and privacy (encryption)
63    AuthPriv,
64}
65
66impl SecurityLevel {
67    /// Decode from msgFlags byte.
68    pub fn from_flags(flags: u8) -> Option<Self> {
69        let auth = flags & 0x01 != 0;
70        let priv_ = flags & 0x02 != 0;
71
72        match (auth, priv_) {
73            (false, false) => Some(Self::NoAuthNoPriv),
74            (true, false) => Some(Self::AuthNoPriv),
75            (true, true) => Some(Self::AuthPriv),
76            (false, true) => None, // Invalid: priv without auth
77        }
78    }
79
80    /// Encode to msgFlags byte (without reportable flag).
81    pub fn to_flags(self) -> u8 {
82        match self {
83            Self::NoAuthNoPriv => 0x00,
84            Self::AuthNoPriv => 0x01,
85            Self::AuthPriv => 0x03,
86        }
87    }
88
89    /// Check if authentication is required.
90    pub fn requires_auth(self) -> bool {
91        matches!(self, Self::AuthNoPriv | Self::AuthPriv)
92    }
93
94    /// Check if privacy (encryption) is required.
95    pub fn requires_priv(self) -> bool {
96        matches!(self, Self::AuthPriv)
97    }
98}
99
100/// Message flags (RFC 3412 Section 6.4).
101#[derive(Debug, Clone, Copy, PartialEq, Eq)]
102pub struct MsgFlags {
103    /// Security level
104    pub security_level: SecurityLevel,
105    /// Whether a report PDU may be sent on error
106    pub reportable: bool,
107}
108
109impl MsgFlags {
110    /// Create new message flags.
111    pub fn new(security_level: SecurityLevel, reportable: bool) -> Self {
112        Self {
113            security_level,
114            reportable,
115        }
116    }
117
118    /// Decode from byte.
119    pub fn from_byte(byte: u8) -> Result<Self> {
120        let security_level = SecurityLevel::from_flags(byte).ok_or_else(|| {
121            tracing::debug!(target: "async_snmp::v3", { byte, kind = %DecodeErrorKind::InvalidMsgFlags }, "decode error");
122            Error::MalformedResponse {
123                target: UNKNOWN_TARGET,
124            }
125            .boxed()
126        })?;
127        let reportable = byte & 0x04 != 0;
128        Ok(Self {
129            security_level,
130            reportable,
131        })
132    }
133
134    /// Encode to byte.
135    pub fn to_byte(self) -> u8 {
136        let mut flags = self.security_level.to_flags();
137        if self.reportable {
138            flags |= 0x04;
139        }
140        flags
141    }
142}
143
144/// Message global data header (msgGlobalData).
145#[derive(Debug, Clone)]
146pub struct MsgGlobalData {
147    /// Message identifier for request/response correlation
148    pub msg_id: i32,
149    /// Maximum message size the sender can accept
150    pub msg_max_size: i32,
151    /// Message flags (security level + reportable)
152    pub msg_flags: MsgFlags,
153    /// Security model (always USM=3 for our implementation)
154    pub msg_security_model: SecurityModel,
155}
156
157impl MsgGlobalData {
158    /// Create new global data.
159    pub fn new(msg_id: i32, msg_max_size: i32, msg_flags: MsgFlags) -> Self {
160        Self {
161            msg_id,
162            msg_max_size,
163            msg_flags,
164            msg_security_model: SecurityModel::Usm,
165        }
166    }
167
168    /// Encode to buffer.
169    pub fn encode(&self, buf: &mut EncodeBuf) {
170        buf.push_sequence(|buf| {
171            buf.push_integer(self.msg_security_model.as_i32());
172            // msgFlags is a 1-byte OCTET STRING
173            buf.push_octet_string(&[self.msg_flags.to_byte()]);
174            buf.push_integer(self.msg_max_size);
175            buf.push_integer(self.msg_id);
176        });
177    }
178
179    /// Decode from decoder.
180    ///
181    /// Validates that:
182    /// - `msgID` is in range 0..2147483647 (RFC 3412 HeaderData)
183    /// - `msgMaxSize` is in range 484..2147483647 (RFC 3412 HeaderData)
184    /// - `msgSecurityModel` is a known value (currently only USM=3)
185    pub fn decode(decoder: &mut Decoder) -> Result<Self> {
186        const MSG_MAX_SIZE_MINIMUM: i32 = 484;
187
188        let mut seq = decoder.read_sequence()?;
189
190        let msg_id = seq.read_integer()?;
191        let msg_max_size = seq.read_integer()?;
192
193        // RFC 3412 HeaderData: msgID INTEGER (0..2147483647)
194        if msg_id < 0 {
195            tracing::debug!(target: "async_snmp::v3", { offset = seq.offset(), value = msg_id, kind = %DecodeErrorKind::InvalidMsgId { value: msg_id } }, "decode error");
196            return Err(Error::MalformedResponse {
197                target: UNKNOWN_TARGET,
198            }
199            .boxed());
200        }
201
202        // RFC 3412 HeaderData: msgMaxSize INTEGER (484..2147483647)
203        // Negative values indicate the sender encoded a value > 2^31-1
204        if msg_max_size < 0 {
205            tracing::debug!(target: "async_snmp::v3", { offset = seq.offset(), value = msg_max_size, kind = %DecodeErrorKind::MsgMaxSizeTooLarge { value: msg_max_size } }, "decode error");
206            return Err(Error::MalformedResponse {
207                target: UNKNOWN_TARGET,
208            }
209            .boxed());
210        }
211
212        if msg_max_size < MSG_MAX_SIZE_MINIMUM {
213            tracing::debug!(target: "async_snmp::v3", { offset = seq.offset(), value = msg_max_size, minimum = MSG_MAX_SIZE_MINIMUM, kind = %DecodeErrorKind::MsgMaxSizeTooSmall { value: msg_max_size, minimum: MSG_MAX_SIZE_MINIMUM } }, "decode error");
214            return Err(Error::MalformedResponse {
215                target: UNKNOWN_TARGET,
216            }
217            .boxed());
218        }
219
220        let flags_bytes = seq.read_octet_string()?;
221        if flags_bytes.len() != 1 {
222            tracing::debug!(target: "async_snmp::v3", { offset = seq.offset(), expected = 1, actual = flags_bytes.len() }, "invalid msgFlags length");
223            return Err(Error::MalformedResponse {
224                target: UNKNOWN_TARGET,
225            }
226            .boxed());
227        }
228        let msg_flags = MsgFlags::from_byte(flags_bytes[0])?;
229
230        let msg_security_model_raw = seq.read_integer()?;
231        // Reject unknown security models per RFC 3412 Section 7.2
232        let msg_security_model =
233            SecurityModel::from_i32(msg_security_model_raw).ok_or_else(|| {
234                tracing::debug!(target: "async_snmp::v3", { offset = seq.offset(), model = msg_security_model_raw, kind = %DecodeErrorKind::UnknownSecurityModel(msg_security_model_raw) }, "decode error");
235                Error::MalformedResponse {
236                    target: UNKNOWN_TARGET,
237                }
238                .boxed()
239            })?;
240
241        Ok(Self {
242            msg_id,
243            msg_max_size,
244            msg_flags,
245            msg_security_model,
246        })
247    }
248}
249
250/// Scoped PDU (contextEngineID + contextName + PDU).
251#[derive(Debug, Clone)]
252pub struct ScopedPdu {
253    /// Context engine ID (typically same as authoritative engine ID)
254    pub context_engine_id: Bytes,
255    /// Context name (typically empty string)
256    pub context_name: Bytes,
257    /// The actual PDU
258    pub pdu: Pdu,
259}
260
261impl ScopedPdu {
262    /// Create a new scoped PDU.
263    pub fn new(
264        context_engine_id: impl Into<Bytes>,
265        context_name: impl Into<Bytes>,
266        pdu: Pdu,
267    ) -> Self {
268        Self {
269            context_engine_id: context_engine_id.into(),
270            context_name: context_name.into(),
271            pdu,
272        }
273    }
274
275    /// Create with empty context (most common case).
276    pub fn with_empty_context(pdu: Pdu) -> Self {
277        Self {
278            context_engine_id: Bytes::new(),
279            context_name: Bytes::new(),
280            pdu,
281        }
282    }
283
284    /// Encode to buffer.
285    pub fn encode(&self, buf: &mut EncodeBuf) {
286        buf.push_sequence(|buf| {
287            self.pdu.encode(buf);
288            buf.push_octet_string(&self.context_name);
289            buf.push_octet_string(&self.context_engine_id);
290        });
291    }
292
293    /// Encode to bytes.
294    pub fn encode_to_bytes(&self) -> Bytes {
295        let mut buf = EncodeBuf::new();
296        self.encode(&mut buf);
297        buf.finish()
298    }
299
300    /// Decode from decoder.
301    pub fn decode(decoder: &mut Decoder) -> Result<Self> {
302        let mut seq = decoder.read_sequence()?;
303
304        let context_engine_id = seq.read_octet_string()?;
305        let context_name = seq.read_octet_string()?;
306        let pdu = Pdu::decode(&mut seq)?;
307
308        Ok(Self {
309            context_engine_id,
310            context_name,
311            pdu,
312        })
313    }
314}
315
316/// SNMPv3 message.
317#[derive(Debug, Clone)]
318pub struct V3Message {
319    /// Global data (header)
320    pub global_data: MsgGlobalData,
321    /// Security parameters (opaque, USM-encoded)
322    pub security_params: Bytes,
323    /// Message data - either plaintext ScopedPdu or encrypted bytes
324    pub data: V3MessageData,
325}
326
327/// Message data payload.
328#[derive(Debug, Clone)]
329pub enum V3MessageData {
330    /// Plaintext scoped PDU (noAuthNoPriv or authNoPriv)
331    Plaintext(ScopedPdu),
332    /// Encrypted scoped PDU (authPriv) - raw ciphertext
333    Encrypted(Bytes),
334}
335
336impl V3Message {
337    /// Create a new V3 message with plaintext data.
338    pub fn new(global_data: MsgGlobalData, security_params: Bytes, scoped_pdu: ScopedPdu) -> Self {
339        Self {
340            global_data,
341            security_params,
342            data: V3MessageData::Plaintext(scoped_pdu),
343        }
344    }
345
346    /// Create a new V3 message with encrypted data.
347    pub fn new_encrypted(
348        global_data: MsgGlobalData,
349        security_params: Bytes,
350        encrypted: Bytes,
351    ) -> Self {
352        Self {
353            global_data,
354            security_params,
355            data: V3MessageData::Encrypted(encrypted),
356        }
357    }
358
359    /// Get the scoped PDU if available (plaintext only).
360    pub fn scoped_pdu(&self) -> Option<&ScopedPdu> {
361        match &self.data {
362            V3MessageData::Plaintext(pdu) => Some(pdu),
363            V3MessageData::Encrypted(_) => None,
364        }
365    }
366
367    /// Consume and return the scoped PDU if available.
368    pub fn into_scoped_pdu(self) -> Option<ScopedPdu> {
369        match self.data {
370            V3MessageData::Plaintext(pdu) => Some(pdu),
371            V3MessageData::Encrypted(_) => None,
372        }
373    }
374
375    /// Get the PDU if available (convenience method).
376    pub fn pdu(&self) -> Option<&Pdu> {
377        self.scoped_pdu().map(|s| &s.pdu)
378    }
379
380    /// Consume and return the PDU.
381    pub fn into_pdu(self) -> Option<Pdu> {
382        self.into_scoped_pdu().map(|s| s.pdu)
383    }
384
385    /// Get the message ID.
386    pub fn msg_id(&self) -> i32 {
387        self.global_data.msg_id
388    }
389
390    /// Get the security level.
391    pub fn security_level(&self) -> SecurityLevel {
392        self.global_data.msg_flags.security_level
393    }
394
395    /// Encode to BER.
396    ///
397    /// Note: For authenticated messages, the caller must:
398    /// 1. Encode with placeholder auth params (12 zero bytes for HMAC-96)
399    /// 2. Compute HMAC over the entire encoded message
400    /// 3. Replace the placeholder with the actual HMAC
401    pub fn encode(&self) -> Bytes {
402        let mut buf = EncodeBuf::new();
403
404        buf.push_sequence(|buf| {
405            // msgData
406            match &self.data {
407                V3MessageData::Plaintext(scoped_pdu) => {
408                    scoped_pdu.encode(buf);
409                }
410                V3MessageData::Encrypted(ciphertext) => {
411                    buf.push_octet_string(ciphertext);
412                }
413            }
414
415            // msgSecurityParameters (as OCTET STRING)
416            buf.push_octet_string(&self.security_params);
417
418            // msgGlobalData
419            self.global_data.encode(buf);
420
421            // version
422            buf.push_integer(3);
423        });
424
425        buf.finish()
426    }
427
428    /// Decode from BER.
429    ///
430    /// For encrypted messages, returns `V3MessageData::Encrypted` with the raw ciphertext.
431    /// The caller must decrypt using USM before accessing the scoped PDU.
432    pub fn decode(data: Bytes) -> Result<Self> {
433        let mut decoder = Decoder::new(data);
434        let mut seq = decoder.read_sequence()?;
435
436        // Version
437        let version = seq.read_integer()?;
438        if version != 3 {
439            tracing::debug!(target: "async_snmp::v3", { offset = seq.offset(), version, kind = %DecodeErrorKind::UnknownVersion(version) }, "decode error");
440            return Err(Error::MalformedResponse {
441                target: UNKNOWN_TARGET,
442            }
443            .boxed());
444        }
445
446        Self::decode_from_sequence(&mut seq)
447    }
448
449    /// Decode from a sequence decoder where version has already been read.
450    pub(crate) fn decode_from_sequence(seq: &mut Decoder) -> Result<Self> {
451        // msgGlobalData
452        let global_data = MsgGlobalData::decode(seq)?;
453
454        // msgSecurityParameters (OCTET STRING containing USM params)
455        let security_params = seq.read_octet_string()?;
456
457        // msgData - either plaintext SEQUENCE or encrypted OCTET STRING
458        let data = if global_data.msg_flags.security_level.requires_priv() {
459            // Encrypted: expect OCTET STRING
460            let encrypted = seq.read_octet_string()?;
461            V3MessageData::Encrypted(encrypted)
462        } else {
463            // Plaintext: expect SEQUENCE (ScopedPDU)
464            let scoped_pdu = ScopedPdu::decode(seq)?;
465            V3MessageData::Plaintext(scoped_pdu)
466        };
467
468        Ok(Self {
469            global_data,
470            security_params,
471            data,
472        })
473    }
474
475    /// Create a discovery request message.
476    ///
477    /// This is sent to discover the engine ID and time of a remote SNMP engine.
478    /// Uses empty security parameters and no authentication.
479    pub fn discovery_request(msg_id: i32) -> Self {
480        let global_data = MsgGlobalData::new(
481            msg_id,
482            65507, // max UDP size
483            MsgFlags::new(SecurityLevel::NoAuthNoPriv, true),
484        );
485
486        // Empty USM security parameters for discovery
487        let security_params = crate::v3::UsmSecurityParams::empty().encode();
488
489        // Empty scoped PDU with Report request
490        let pdu = Pdu::get_request(0, &[]);
491        let scoped_pdu = ScopedPdu::with_empty_context(pdu);
492
493        Self::new(global_data, security_params, scoped_pdu)
494    }
495}
496
497#[cfg(test)]
498mod tests {
499    use super::*;
500    use crate::oid;
501
502    #[test]
503    fn test_security_level_flags() {
504        assert_eq!(SecurityLevel::NoAuthNoPriv.to_flags(), 0x00);
505        assert_eq!(SecurityLevel::AuthNoPriv.to_flags(), 0x01);
506        assert_eq!(SecurityLevel::AuthPriv.to_flags(), 0x03);
507
508        assert_eq!(
509            SecurityLevel::from_flags(0x00),
510            Some(SecurityLevel::NoAuthNoPriv)
511        );
512        assert_eq!(
513            SecurityLevel::from_flags(0x01),
514            Some(SecurityLevel::AuthNoPriv)
515        );
516        assert_eq!(
517            SecurityLevel::from_flags(0x03),
518            Some(SecurityLevel::AuthPriv)
519        );
520        assert_eq!(SecurityLevel::from_flags(0x02), None); // Invalid
521    }
522
523    #[test]
524    fn test_msg_flags_roundtrip() {
525        let flags = MsgFlags::new(SecurityLevel::AuthPriv, true);
526        let byte = flags.to_byte();
527        assert_eq!(byte, 0x07); // auth=1, priv=1, reportable=1
528
529        let decoded = MsgFlags::from_byte(byte).unwrap();
530        assert_eq!(decoded.security_level, SecurityLevel::AuthPriv);
531        assert!(decoded.reportable);
532    }
533
534    #[test]
535    fn test_msg_global_data_roundtrip() {
536        let global =
537            MsgGlobalData::new(12345, 1472, MsgFlags::new(SecurityLevel::AuthNoPriv, true));
538
539        let mut buf = EncodeBuf::new();
540        global.encode(&mut buf);
541        let encoded = buf.finish();
542
543        let mut decoder = Decoder::new(encoded);
544        let decoded = MsgGlobalData::decode(&mut decoder).unwrap();
545
546        assert_eq!(decoded.msg_id, 12345);
547        assert_eq!(decoded.msg_max_size, 1472);
548        assert_eq!(decoded.msg_flags.security_level, SecurityLevel::AuthNoPriv);
549        assert!(decoded.msg_flags.reportable);
550        assert_eq!(decoded.msg_security_model, SecurityModel::Usm);
551    }
552
553    #[test]
554    fn test_scoped_pdu_roundtrip() {
555        let pdu = Pdu::get_request(42, &[oid!(1, 3, 6, 1, 2, 1, 1, 1, 0)]);
556        let scoped = ScopedPdu::new(b"engine".as_slice(), b"ctx".as_slice(), pdu);
557
558        let mut buf = EncodeBuf::new();
559        scoped.encode(&mut buf);
560        let encoded = buf.finish();
561
562        let mut decoder = Decoder::new(encoded);
563        let decoded = ScopedPdu::decode(&mut decoder).unwrap();
564
565        assert_eq!(decoded.context_engine_id.as_ref(), b"engine");
566        assert_eq!(decoded.context_name.as_ref(), b"ctx");
567        assert_eq!(decoded.pdu.request_id, 42);
568    }
569
570    #[test]
571    fn test_v3_message_plaintext_roundtrip() {
572        let global =
573            MsgGlobalData::new(100, 1472, MsgFlags::new(SecurityLevel::NoAuthNoPriv, true));
574        let pdu = Pdu::get_request(42, &[oid!(1, 3, 6, 1, 2, 1, 1, 1, 0)]);
575        let scoped = ScopedPdu::with_empty_context(pdu);
576        let msg = V3Message::new(global, Bytes::from_static(b"usm-params"), scoped);
577
578        let encoded = msg.encode();
579        let decoded = V3Message::decode(encoded).unwrap();
580
581        assert_eq!(decoded.global_data.msg_id, 100);
582        assert_eq!(decoded.security_level(), SecurityLevel::NoAuthNoPriv);
583        assert_eq!(decoded.security_params.as_ref(), b"usm-params");
584
585        let scoped_pdu = decoded.scoped_pdu().unwrap();
586        assert_eq!(scoped_pdu.pdu.request_id, 42);
587    }
588
589    #[test]
590    fn test_v3_message_encrypted_roundtrip() {
591        let global = MsgGlobalData::new(200, 1472, MsgFlags::new(SecurityLevel::AuthPriv, false));
592        let msg = V3Message::new_encrypted(
593            global,
594            Bytes::from_static(b"usm-params"),
595            Bytes::from_static(b"encrypted-data"),
596        );
597
598        let encoded = msg.encode();
599        let decoded = V3Message::decode(encoded).unwrap();
600
601        assert_eq!(decoded.global_data.msg_id, 200);
602        assert_eq!(decoded.security_level(), SecurityLevel::AuthPriv);
603
604        match &decoded.data {
605            V3MessageData::Encrypted(data) => {
606                assert_eq!(data.as_ref(), b"encrypted-data");
607            }
608            V3MessageData::Plaintext(_) => panic!("expected encrypted data"),
609        }
610    }
611
612    #[test]
613    fn test_msg_global_data_rejects_msg_max_size_below_minimum() {
614        // Encode with invalid msgMaxSize (below 484)
615        let global = MsgGlobalData {
616            msg_id: 100,
617            msg_max_size: 400, // Below RFC 3412 minimum of 484
618            msg_flags: MsgFlags::new(SecurityLevel::NoAuthNoPriv, true),
619            msg_security_model: SecurityModel::Usm,
620        };
621
622        let mut buf = EncodeBuf::new();
623        global.encode(&mut buf);
624        let encoded = buf.finish();
625
626        let mut decoder = Decoder::new(encoded);
627        let result = MsgGlobalData::decode(&mut decoder);
628
629        assert!(result.is_err());
630        assert!(matches!(
631            *result.unwrap_err(),
632            Error::MalformedResponse { .. }
633        ));
634    }
635
636    #[test]
637    fn test_msg_global_data_accepts_msg_max_size_at_minimum() {
638        // 484 is exactly the RFC 3412 minimum
639        let global = MsgGlobalData::new(100, 484, MsgFlags::new(SecurityLevel::NoAuthNoPriv, true));
640
641        let mut buf = EncodeBuf::new();
642        global.encode(&mut buf);
643        let encoded = buf.finish();
644
645        let mut decoder = Decoder::new(encoded);
646        let decoded = MsgGlobalData::decode(&mut decoder).unwrap();
647
648        assert_eq!(decoded.msg_max_size, 484);
649    }
650
651    #[test]
652    fn test_msg_global_data_rejects_unknown_security_model() {
653        // Manually build encoded data with unknown security model
654        // SEQUENCE { msg_id, msg_max_size, msgFlags, msgSecurityModel=99 }
655        let mut buf = EncodeBuf::new();
656        buf.push_sequence(|buf| {
657            buf.push_integer(99); // unknown security model
658            buf.push_octet_string(&[0x04]); // reportable, noAuthNoPriv
659            buf.push_integer(1472); // msg_max_size
660            buf.push_integer(100); // msg_id
661        });
662        let encoded = buf.finish();
663
664        let mut decoder = Decoder::new(encoded);
665        let result = MsgGlobalData::decode(&mut decoder);
666
667        assert!(result.is_err());
668        assert!(matches!(
669            *result.unwrap_err(),
670            Error::MalformedResponse { .. }
671        ));
672    }
673
674    #[test]
675    fn test_msg_global_data_accepts_usm_security_model() {
676        // USM (3) should be accepted
677        let global =
678            MsgGlobalData::new(100, 1472, MsgFlags::new(SecurityLevel::NoAuthNoPriv, true));
679
680        let mut buf = EncodeBuf::new();
681        global.encode(&mut buf);
682        let encoded = buf.finish();
683
684        let mut decoder = Decoder::new(encoded);
685        let decoded = MsgGlobalData::decode(&mut decoder).unwrap();
686
687        assert_eq!(decoded.msg_security_model, SecurityModel::Usm);
688    }
689
690    // RFC 3412 bounds tests for msgID and msgMaxSize
691    //
692    // RFC 3412 HeaderData definition specifies:
693    //   msgID INTEGER (0..2147483647)
694    //   msgMaxSize INTEGER (484..2147483647)
695    //
696    // Values outside these ranges should be rejected.
697
698    #[test]
699    fn test_msg_global_data_rejects_negative_msg_id() {
700        // RFC 3412: msgID must be in range [0..2147483647]
701        // Negative values should be rejected
702        let mut buf = EncodeBuf::new();
703        buf.push_sequence(|buf| {
704            buf.push_integer(3); // USM security model
705            buf.push_octet_string(&[0x04]); // reportable, noAuthNoPriv
706            buf.push_integer(1472); // valid msg_max_size
707            buf.push_integer(-1); // negative msg_id
708        });
709        let encoded = buf.finish();
710
711        let mut decoder = Decoder::new(encoded);
712        let result = MsgGlobalData::decode(&mut decoder);
713
714        assert!(result.is_err());
715        assert!(matches!(
716            *result.unwrap_err(),
717            Error::MalformedResponse { .. }
718        ));
719    }
720
721    #[test]
722    fn test_msg_global_data_rejects_negative_msg_max_size() {
723        // RFC 3412: msgMaxSize must be in range [484..2147483647]
724        // Negative values (from signed integer interpretation) should be rejected
725        let mut buf = EncodeBuf::new();
726        buf.push_sequence(|buf| {
727            buf.push_integer(3); // USM security model
728            buf.push_octet_string(&[0x04]); // reportable, noAuthNoPriv
729            buf.push_integer(-1); // negative msg_max_size (would be > 2^31-1 unsigned)
730            buf.push_integer(100); // valid msg_id
731        });
732        let encoded = buf.finish();
733
734        let mut decoder = Decoder::new(encoded);
735        let result = MsgGlobalData::decode(&mut decoder);
736
737        assert!(result.is_err());
738        assert!(matches!(
739            *result.unwrap_err(),
740            Error::MalformedResponse { .. }
741        ));
742    }
743
744    #[test]
745    fn test_msg_global_data_accepts_msg_id_at_zero() {
746        // RFC 3412: msgID 0 is at the lower bound, should be accepted
747        let mut buf = EncodeBuf::new();
748        buf.push_sequence(|buf| {
749            buf.push_integer(3); // USM
750            buf.push_octet_string(&[0x04]); // reportable, noAuthNoPriv
751            buf.push_integer(1472); // valid msg_max_size
752            buf.push_integer(0); // msg_id at lower bound
753        });
754        let encoded = buf.finish();
755
756        let mut decoder = Decoder::new(encoded);
757        let decoded = MsgGlobalData::decode(&mut decoder).unwrap();
758
759        assert_eq!(decoded.msg_id, 0);
760    }
761
762    #[test]
763    fn test_msg_global_data_accepts_msg_id_at_maximum() {
764        // RFC 3412: msgID 2147483647 is at the upper bound, should be accepted
765        let mut buf = EncodeBuf::new();
766        buf.push_sequence(|buf| {
767            buf.push_integer(3); // USM
768            buf.push_octet_string(&[0x04]); // reportable, noAuthNoPriv
769            buf.push_integer(1472); // valid msg_max_size
770            buf.push_integer(i32::MAX); // msg_id at upper bound (2147483647)
771        });
772        let encoded = buf.finish();
773
774        let mut decoder = Decoder::new(encoded);
775        let decoded = MsgGlobalData::decode(&mut decoder).unwrap();
776
777        assert_eq!(decoded.msg_id, i32::MAX);
778    }
779
780    #[test]
781    fn test_msg_global_data_accepts_msg_max_size_at_maximum() {
782        // RFC 3412: msgMaxSize 2147483647 is at the upper bound, should be accepted
783        let mut buf = EncodeBuf::new();
784        buf.push_sequence(|buf| {
785            buf.push_integer(3); // USM
786            buf.push_octet_string(&[0x04]); // reportable, noAuthNoPriv
787            buf.push_integer(i32::MAX); // msg_max_size at upper bound (2147483647)
788            buf.push_integer(100); // valid msg_id
789        });
790        let encoded = buf.finish();
791
792        let mut decoder = Decoder::new(encoded);
793        let decoded = MsgGlobalData::decode(&mut decoder).unwrap();
794
795        assert_eq!(decoded.msg_max_size, i32::MAX);
796    }
797}