Skip to main content

netget/server/snmp/
usm.rs

1//! SNMPv3 with the User-based Security Model (RFC 3412 message processing, RFC 3414 USM,
2//! RFC 3826 AES, RFC 7860 HMAC-SHA-2), shared by the agent and the manager.
3//!
4//! What it implements:
5//!
6//! - **Keys** (RFC 3414 A.2): a password is stretched to a key by hashing a megabyte of it
7//!   repeated, then *localized* to one engine as `H(Ku ‖ engineID ‖ Ku)`. The megabyte hash is
8//!   done once per user and protocol; localization once per engine.
9//! - **Authentication**: HMAC-MD5-96 and HMAC-SHA-96 (RFC 3414), HMAC-SHA-224/256/384/512
10//!   (RFC 7860, 16/24/32/48-byte MACs), computed over the whole message with the MAC field
11//!   zeroed. An incoming MAC is checked over the bytes *as received*, with the field located by
12//!   walking the BER — never over a re-encoding, which a sender's non-minimal lengths would make
13//!   differ.
14//! - **Privacy**: DES-CBC (RFC 3414 §8: key and pre-IV from the localized key, salt = boots ‖
15//!   counter) and AES-128-CFB (RFC 3826: IV = boots ‖ time ‖ salt). AES-192/256 exist only as
16//!   expired drafts with two incompatible key extensions, and are not offered.
17//! - **The agent's side of the procedure** (RFC 3414 §3.2): discovery answered with a
18//!   `usmStatsUnknownEngineIDs` report, then unknown users, unsupported levels, wrong digests,
19//!   stale times and undecryptable PDUs each answered with their own report, the time report
20//!   authenticated so the manager can trust the clock in it.
21//! - **The manager's side**: discovery, a clock kept from the agent's reports, and replies
22//!   checked for message id, engine, level and MAC before anything in them is believed.
23//! - **Engine boots without persistence**: an agent given no `engine_boots` counts them as
24//!   seconds of wall-clock time since [`ENGINE_BOOTS_EPOCH_UNIX_SECS`], so every restart reports
25//!   a larger value than the run before it and a message captured in one run carries a boots the
26//!   next run refuses.
27//!
28//! What it does not: VACM (every authenticated user sees what the handler answers — the user
29//! and level are in the event for the handler to decide on) and SNMPv3 notifications.
30//!
31//! Every BER decode here runs on bytes screened by [`crate::server::snmp::check_ber_structure`]
32//! first: the whole message, the msgSecurityParameters OCTET STRING's contents (which the outer
33//! screen treats as one primitive and never descends into), and a decrypted scoped PDU. The
34//! screen is applied inside [`decode`] itself, so no caller can reach `rasn` without it.
35
36use anyhow::{bail, ensure, Context, Result};
37use des::cipher::{Block, BlockDecrypt, BlockEncrypt, KeyInit};
38use hmac::{Hmac, Mac};
39use rasn::ber;
40use rasn::types::{Integer, ObjectIdentifier, OctetString};
41use rasn_snmp::{v2, v3};
42use serde::Serialize;
43use std::collections::HashMap;
44use std::sync::atomic::{AtomicI64, AtomicU32, AtomicU64, Ordering};
45use std::sync::Mutex;
46
47/// The USM's security-model number (RFC 3411).
48pub const USM_SECURITY_MODEL: i64 = 3;
49/// `msgFlags` bits.
50pub const FLAG_AUTH: u8 = 0x01;
51pub const FLAG_PRIV: u8 = 0x02;
52pub const FLAG_REPORTABLE: u8 = 0x04;
53/// RFC 3414 §2.2.3: a message more than 150 seconds from the authoritative clock is stale.
54pub const TIME_WINDOW_SECS: i64 = 150;
55/// RFC 3414 §2.2.2: boots at this value means the engine must be reconfigured.
56pub const MAX_ENGINE_BOOTS: i64 = 2_147_483_647;
57/// RFC 3414 §2.2.1: snmpEngineBoots and snmpEngineTime are both `INTEGER (0..2147483647)`.
58/// A received value outside `0..=MAX_ENGINE_TIME` is refused before any arithmetic is done on it.
59pub const MAX_ENGINE_TIME: i64 = 2_147_483_647;
60/// The size of message NetGet's agent will accept and say it can receive.
61pub const MAX_MESSAGE_SIZE: i64 = 65_507;
62/// RFC 3414 A.2: the password is repeated to this many bytes before hashing.
63const PASSWORD_EXPANSION: usize = 1_048_576;
64/// Bounds on what a configuration may hold.
65pub const MAX_USERS: usize = 64;
66pub const MAX_NAME_BYTES: usize = 32;
67pub const MIN_PASSWORD_BYTES: usize = 8;
68pub const MAX_PASSWORD_BYTES: usize = 256;
69pub const MAX_ENGINE_ID_BYTES: usize = 32;
70pub const MIN_ENGINE_ID_BYTES: usize = 5;
71/// The instant default engine boots are counted from: 2024-01-01T00:00:00Z. Counting seconds
72/// from here stays below [`MAX_ENGINE_BOOTS`] until 2092.
73pub const ENGINE_BOOTS_EPOCH_UNIX_SECS: u64 = 1_704_067_200;
74
75/// The largest boots value this process has handed out, so two agents started in the same
76/// second of one process still get strictly increasing values.
77static LAST_DEFAULT_BOOTS: AtomicI64 = AtomicI64::new(0);
78
79/// snmpEngineBoots for an agent whose operator configured none.
80///
81/// NetGet persists nothing between runs, so boots cannot be a counter read from disk. It is the
82/// number of wall-clock seconds since [`ENGINE_BOOTS_EPOCH_UNIX_SECS`] instead, which grows
83/// across restarts as a stored counter would: with a fixed `engine_id` the localized keys are
84/// the same in every run, and a captured authenticated message carries the boots of the run it
85/// was sent in, which every later run refuses as `usmStatsNotInTimeWindows`. Within one process
86/// each call returns more than the last. The guarantee rests on the wall clock: a clock set back
87/// across a restart can repeat an earlier run's value, and an operator who cannot trust it sets
88/// `engine_boots` explicitly and raises it on every restart.
89pub fn default_engine_boots() -> i64 {
90    let wall = crate::utils::clock::SystemTime::now()
91        .duration_since(crate::utils::clock::UNIX_EPOCH)
92        .map(|d| d.as_secs())
93        .unwrap_or(0);
94    let since_epoch = wall.saturating_sub(ENGINE_BOOTS_EPOCH_UNIX_SECS);
95    let since_epoch = i64::try_from(since_epoch).unwrap_or(MAX_ENGINE_BOOTS - 1);
96    let mut previous = LAST_DEFAULT_BOOTS.load(Ordering::Relaxed);
97    loop {
98        let next = since_epoch
99            .max(previous.saturating_add(1))
100            .clamp(1, MAX_ENGINE_BOOTS - 1);
101        match LAST_DEFAULT_BOOTS.compare_exchange_weak(
102            previous,
103            next,
104            Ordering::Relaxed,
105            Ordering::Relaxed,
106        ) {
107            Ok(_) => return next,
108            Err(current) => previous = current,
109        }
110    }
111}
112
113/// Whether a received snmpEngineBoots or snmpEngineTime is inside RFC 3414's `0..2147483647`.
114pub fn in_engine_range(value: i64) -> bool {
115    (0..=MAX_ENGINE_TIME).contains(&value)
116}
117
118/// The agent's USM from its startup parameters, or `None` when no v3 user is configured —
119/// then a v3 message is dropped and only v1/v2c are served.
120pub fn agent_from_params(
121    params: Option<&crate::protocol::StartupParams>,
122) -> Result<Option<std::sync::Arc<Agent>>> {
123    let Some(params) = params else {
124        return Ok(None);
125    };
126    let Some(users) = params.get_optional_array("v3_users")? else {
127        return Ok(None);
128    };
129    let users = users
130        .iter()
131        .map(User::from_json)
132        .collect::<Result<Vec<_>>>()?;
133    let engine_id = match params.get_optional_string("engine_id")? {
134        Some(hex_id) => hex::decode(hex_id.trim_start_matches("0x"))
135            .context("engine_id is hexadecimal, as Net-SNMP writes it (e.g. 80001f8880...)")?,
136        None => Agent::random_engine_id(),
137    };
138    let boots = match params.get_optional_i64("engine_boots")? {
139        Some(boots) => boots,
140        None => default_engine_boots(),
141    };
142    Ok(Some(std::sync::Arc::new(Agent::new(
143        engine_id, boots, users,
144    )?)))
145}
146
147/// Whether a datagram is an SNMPv3 message: a SEQUENCE whose first element is INTEGER 3.
148pub fn is_v3(data: &[u8]) -> bool {
149    let Ok((0x30, start, _)) = tlv(data, 0) else {
150        return false;
151    };
152    matches!(tlv(data, start), Ok((0x02, s, 1)) if data[s] == 3)
153}
154
155/// The usmStats counters (RFC 3414 §5), whose varbind is the body of each report.
156#[derive(Clone, Copy, Debug, PartialEq, Eq, Serialize)]
157#[serde(rename_all = "camelCase")]
158pub enum ReportKind {
159    UnsupportedSecLevels,
160    NotInTimeWindows,
161    UnknownUserNames,
162    UnknownEngineIds,
163    WrongDigests,
164    DecryptionErrors,
165}
166
167impl ReportKind {
168    const ALL: [ReportKind; 6] = [
169        ReportKind::UnsupportedSecLevels,
170        ReportKind::NotInTimeWindows,
171        ReportKind::UnknownUserNames,
172        ReportKind::UnknownEngineIds,
173        ReportKind::WrongDigests,
174        ReportKind::DecryptionErrors,
175    ];
176
177    /// `1.3.6.1.6.3.15.1.1.<n>.0`.
178    pub fn oid(self) -> Vec<u32> {
179        let n = match self {
180            ReportKind::UnsupportedSecLevels => 1,
181            ReportKind::NotInTimeWindows => 2,
182            ReportKind::UnknownUserNames => 3,
183            ReportKind::UnknownEngineIds => 4,
184            ReportKind::WrongDigests => 5,
185            ReportKind::DecryptionErrors => 6,
186        };
187        vec![1, 3, 6, 1, 6, 3, 15, 1, 1, n, 0]
188    }
189
190    pub fn from_oid(oid: &[u32]) -> Option<ReportKind> {
191        ReportKind::ALL.into_iter().find(|k| k.oid() == oid)
192    }
193
194    pub fn name(self) -> &'static str {
195        match self {
196            ReportKind::UnsupportedSecLevels => "usmStatsUnsupportedSecLevels",
197            ReportKind::NotInTimeWindows => "usmStatsNotInTimeWindows",
198            ReportKind::UnknownUserNames => "usmStatsUnknownUserNames",
199            ReportKind::UnknownEngineIds => "usmStatsUnknownEngineIDs",
200            ReportKind::WrongDigests => "usmStatsWrongDigests",
201            ReportKind::DecryptionErrors => "usmStatsDecryptionErrors",
202        }
203    }
204
205    fn index(self) -> usize {
206        ReportKind::ALL.iter().position(|k| *k == self).unwrap_or(0)
207    }
208}
209
210#[derive(Clone, Copy, Debug, PartialEq, Eq)]
211pub enum AuthProtocol {
212    None,
213    Md5,
214    Sha1,
215    Sha224,
216    Sha256,
217    Sha384,
218    Sha512,
219}
220
221impl AuthProtocol {
222    pub fn parse(s: &str) -> Result<AuthProtocol> {
223        Ok(
224            match s.to_ascii_lowercase().replace(['-', '_'], "").as_str() {
225                "" | "none" => AuthProtocol::None,
226                "md5" => AuthProtocol::Md5,
227                "sha" | "sha1" => AuthProtocol::Sha1,
228                "sha224" => AuthProtocol::Sha224,
229                "sha256" => AuthProtocol::Sha256,
230                "sha384" => AuthProtocol::Sha384,
231                "sha512" => AuthProtocol::Sha512,
232                _ => bail!(
233                "auth_protocol {s:?} is not one of none, md5, sha, sha224, sha256, sha384, sha512"
234            ),
235            },
236        )
237    }
238
239    pub fn name(self) -> &'static str {
240        match self {
241            AuthProtocol::None => "none",
242            AuthProtocol::Md5 => "md5",
243            AuthProtocol::Sha1 => "sha",
244            AuthProtocol::Sha224 => "sha224",
245            AuthProtocol::Sha256 => "sha256",
246            AuthProtocol::Sha384 => "sha384",
247            AuthProtocol::Sha512 => "sha512",
248        }
249    }
250
251    /// The truncated MAC carried in msgAuthenticationParameters.
252    pub fn mac_len(self) -> usize {
253        match self {
254            AuthProtocol::None => 0,
255            AuthProtocol::Md5 | AuthProtocol::Sha1 => 12,
256            AuthProtocol::Sha224 => 16,
257            AuthProtocol::Sha256 => 24,
258            AuthProtocol::Sha384 => 32,
259            AuthProtocol::Sha512 => 48,
260        }
261    }
262
263    fn hash(self, parts: &[&[u8]]) -> Vec<u8> {
264        fn run<D: sha2::Digest>(parts: &[&[u8]]) -> Vec<u8> {
265            let mut d = D::new();
266            for p in parts {
267                d.update(p);
268            }
269            d.finalize().to_vec()
270        }
271        match self {
272            AuthProtocol::None => Vec::new(),
273            AuthProtocol::Md5 => run::<md5::Md5>(parts),
274            AuthProtocol::Sha1 => run::<sha1::Sha1>(parts),
275            AuthProtocol::Sha224 => run::<sha2::Sha224>(parts),
276            AuthProtocol::Sha256 => run::<sha2::Sha256>(parts),
277            AuthProtocol::Sha384 => run::<sha2::Sha384>(parts),
278            AuthProtocol::Sha512 => run::<sha2::Sha512>(parts),
279        }
280    }
281
282    fn mac(self, key: &[u8], message: &[u8]) -> Vec<u8> {
283        fn run<M: Mac + hmac::digest::KeyInit>(key: &[u8], message: &[u8]) -> Vec<u8> {
284            let mut m = <M as hmac::digest::KeyInit>::new_from_slice(key)
285                .expect("HMAC accepts any key length");
286            m.update(message);
287            m.finalize().into_bytes().to_vec()
288        }
289        let full = match self {
290            AuthProtocol::None => return Vec::new(),
291            AuthProtocol::Md5 => run::<Hmac<md5::Md5>>(key, message),
292            AuthProtocol::Sha1 => run::<Hmac<sha1::Sha1>>(key, message),
293            AuthProtocol::Sha224 => run::<Hmac<sha2::Sha224>>(key, message),
294            AuthProtocol::Sha256 => run::<Hmac<sha2::Sha256>>(key, message),
295            AuthProtocol::Sha384 => run::<Hmac<sha2::Sha384>>(key, message),
296            AuthProtocol::Sha512 => run::<Hmac<sha2::Sha512>>(key, message),
297        };
298        full[..self.mac_len()].to_vec()
299    }
300
301    /// RFC 3414 A.2.1: hash the password repeated to a megabyte.
302    pub fn password_to_key(self, password: &[u8]) -> Vec<u8> {
303        if self == AuthProtocol::None || password.is_empty() {
304            return Vec::new();
305        }
306        let mut block = Vec::with_capacity(PASSWORD_EXPANSION);
307        while block.len() < PASSWORD_EXPANSION {
308            let take = (PASSWORD_EXPANSION - block.len()).min(password.len());
309            block.extend_from_slice(&password[..take]);
310        }
311        self.hash(&[&block])
312    }
313
314    /// RFC 3414 A.2.2: localize a key to one engine.
315    pub fn localize(self, key: &[u8], engine_id: &[u8]) -> Vec<u8> {
316        self.hash(&[key, engine_id, key])
317    }
318}
319
320#[derive(Clone, Copy, Debug, PartialEq, Eq)]
321pub enum PrivProtocol {
322    None,
323    Des,
324    Aes128,
325}
326
327impl PrivProtocol {
328    pub fn parse(s: &str) -> Result<PrivProtocol> {
329        Ok(
330            match s.to_ascii_lowercase().replace(['-', '_'], "").as_str() {
331                "" | "none" => PrivProtocol::None,
332                "des" => PrivProtocol::Des,
333                "aes" | "aes128" => PrivProtocol::Aes128,
334                _ => bail!("priv_protocol {s:?} is not one of none, des, aes"),
335            },
336        )
337    }
338
339    pub fn name(self) -> &'static str {
340        match self {
341            PrivProtocol::None => "none",
342            PrivProtocol::Des => "des",
343            PrivProtocol::Aes128 => "aes",
344        }
345    }
346}
347
348/// msgFlags' security level.
349#[derive(Clone, Copy, Debug, PartialEq, Eq, Serialize)]
350pub enum SecurityLevel {
351    #[serde(rename = "noAuthNoPriv")]
352    NoAuthNoPriv,
353    #[serde(rename = "authNoPriv")]
354    AuthNoPriv,
355    #[serde(rename = "authPriv")]
356    AuthPriv,
357}
358
359impl SecurityLevel {
360    pub fn from_flags(flags: u8) -> Result<SecurityLevel> {
361        Ok(match (flags & FLAG_AUTH != 0, flags & FLAG_PRIV != 0) {
362            (false, false) => SecurityLevel::NoAuthNoPriv,
363            (true, false) => SecurityLevel::AuthNoPriv,
364            (true, true) => SecurityLevel::AuthPriv,
365            (false, true) => bail!("msgFlags asks for privacy without authentication"),
366        })
367    }
368
369    pub fn flags(self) -> u8 {
370        match self {
371            SecurityLevel::NoAuthNoPriv => 0,
372            SecurityLevel::AuthNoPriv => FLAG_AUTH,
373            SecurityLevel::AuthPriv => FLAG_AUTH | FLAG_PRIV,
374        }
375    }
376
377    pub fn name(self) -> &'static str {
378        match self {
379            SecurityLevel::NoAuthNoPriv => "noAuthNoPriv",
380            SecurityLevel::AuthNoPriv => "authNoPriv",
381            SecurityLevel::AuthPriv => "authPriv",
382        }
383    }
384}
385
386/// A user as configured: a name, and a password per protocol.
387#[derive(Clone)]
388pub struct User {
389    pub name: String,
390    pub auth: AuthProtocol,
391    pub priv_proto: PrivProtocol,
392    /// Ku for authentication and for privacy (both hashed with the auth protocol).
393    auth_ku: Vec<u8>,
394    priv_ku: Vec<u8>,
395}
396
397impl std::fmt::Debug for User {
398    fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
399        f.debug_struct("User")
400            .field("name", &self.name)
401            .field("auth", &self.auth)
402            .field("priv", &self.priv_proto)
403            .finish_non_exhaustive()
404    }
405}
406
407impl User {
408    /// `{name, auth_protocol, auth_password, priv_protocol, priv_password}`.
409    pub fn from_json(v: &serde_json::Value) -> Result<User> {
410        let name = v
411            .get("name")
412            .and_then(|n| n.as_str())
413            .context("a v3 user needs 'name'")?;
414        ensure!(
415            !name.is_empty() && name.len() <= MAX_NAME_BYTES,
416            "a v3 user name is 1 to {MAX_NAME_BYTES} bytes"
417        );
418        let text = |k: &str| v.get(k).and_then(|x| x.as_str()).unwrap_or("");
419        let auth = AuthProtocol::parse(text("auth_protocol"))?;
420        let priv_proto = PrivProtocol::parse(text("priv_protocol"))?;
421        let password = |k: &str, needed: bool| -> Result<Vec<u8>> {
422            let p = text(k);
423            if !needed {
424                ensure!(
425                    p.is_empty(),
426                    "user {name}: {k} is set for a protocol of none"
427                );
428                return Ok(Vec::new());
429            }
430            ensure!(
431                (MIN_PASSWORD_BYTES..=MAX_PASSWORD_BYTES).contains(&p.len()),
432                "user {name}: {k} must be {MIN_PASSWORD_BYTES} to {MAX_PASSWORD_BYTES} bytes \
433                 (RFC 3414 §11.2 sets 8 as the minimum)"
434            );
435            Ok(p.as_bytes().to_vec())
436        };
437        ensure!(
438            priv_proto == PrivProtocol::None || auth != AuthProtocol::None,
439            "user {name}: privacy needs an auth_protocol (there is no noAuthPriv)"
440        );
441        let auth_password = password("auth_password", auth != AuthProtocol::None)?;
442        let priv_password = password("priv_password", priv_proto != PrivProtocol::None)?;
443        Ok(User {
444            name: name.to_string(),
445            auth,
446            priv_proto,
447            auth_ku: auth.password_to_key(&auth_password),
448            priv_ku: auth.password_to_key(&priv_password),
449        })
450    }
451
452    pub fn level(&self) -> SecurityLevel {
453        match (self.auth, self.priv_proto) {
454            (AuthProtocol::None, _) => SecurityLevel::NoAuthNoPriv,
455            (_, PrivProtocol::None) => SecurityLevel::AuthNoPriv,
456            _ => SecurityLevel::AuthPriv,
457        }
458    }
459
460    pub fn localize(&self, engine_id: &[u8]) -> Keys {
461        Keys {
462            auth: self.auth,
463            priv_proto: self.priv_proto,
464            auth_key: self.auth.localize(&self.auth_ku, engine_id),
465            priv_key: if self.priv_proto == PrivProtocol::None {
466                Vec::new()
467            } else {
468                self.auth.localize(&self.priv_ku, engine_id)
469            },
470        }
471    }
472}
473
474/// A user's keys localized to one engine.
475#[derive(Clone)]
476pub struct Keys {
477    pub auth: AuthProtocol,
478    pub priv_proto: PrivProtocol,
479    pub auth_key: Vec<u8>,
480    pub priv_key: Vec<u8>,
481}
482
483impl Keys {
484    /// Encrypt a scoped PDU. Returns the ciphertext and msgPrivacyParameters.
485    pub fn encrypt(
486        &self,
487        plain: &[u8],
488        boots: i64,
489        time: i64,
490        salt: u64,
491    ) -> Result<(Vec<u8>, Vec<u8>)> {
492        match self.priv_proto {
493            PrivProtocol::None => bail!("no privacy protocol"),
494            PrivProtocol::Des => {
495                ensure!(
496                    self.priv_key.len() >= 16,
497                    "DES needs a 16-byte localized key"
498                );
499                let salt = [(boots as u32).to_be_bytes(), (salt as u32).to_be_bytes()].concat();
500                let iv: Vec<u8> = self.priv_key[8..16]
501                    .iter()
502                    .zip(&salt)
503                    .map(|(a, b)| a ^ b)
504                    .collect();
505                let cipher = des::Des::new_from_slice(&self.priv_key[..8]).context("DES key")?;
506                let mut data = plain.to_vec();
507                data.resize(plain.len().div_ceil(8) * 8, 0);
508                let mut prev: [u8; 8] = iv.try_into().expect("8 bytes");
509                for block in data.chunks_mut(8) {
510                    for (b, p) in block.iter_mut().zip(prev) {
511                        *b ^= p;
512                    }
513                    let mut ga = Block::<des::Des>::default();
514                    ga.copy_from_slice(block);
515                    cipher.encrypt_block(&mut ga);
516                    block.copy_from_slice(&ga);
517                    prev.copy_from_slice(block);
518                }
519                Ok((data, salt))
520            }
521            PrivProtocol::Aes128 => {
522                ensure!(
523                    self.priv_key.len() >= 16,
524                    "AES-128 needs a 16-byte localized key"
525                );
526                let salt = salt.to_be_bytes().to_vec();
527                let iv = aes_iv(boots, time, &salt);
528                let mut data = plain.to_vec();
529                aes_cfb(&self.priv_key[..16], iv, &mut data, true)?;
530                Ok((data, salt))
531            }
532        }
533    }
534
535    /// Decrypt with the message's boots, time and msgPrivacyParameters.
536    pub fn decrypt(
537        &self,
538        cipher_text: &[u8],
539        boots: i64,
540        time: i64,
541        params: &[u8],
542    ) -> Result<Vec<u8>> {
543        ensure!(params.len() == 8, "msgPrivacyParameters must be 8 bytes");
544        match self.priv_proto {
545            PrivProtocol::None => bail!("no privacy protocol"),
546            PrivProtocol::Des => {
547                ensure!(
548                    !cipher_text.is_empty() && cipher_text.len() % 8 == 0,
549                    "a DES ciphertext is a whole number of 8-byte blocks"
550                );
551                let iv: Vec<u8> = self.priv_key[8..16]
552                    .iter()
553                    .zip(params)
554                    .map(|(a, b)| a ^ b)
555                    .collect();
556                let cipher = des::Des::new_from_slice(&self.priv_key[..8]).context("DES key")?;
557                let mut prev: [u8; 8] = iv.try_into().expect("8 bytes");
558                let mut out = Vec::with_capacity(cipher_text.len());
559                for block in cipher_text.chunks(8) {
560                    let mut ga = Block::<des::Des>::default();
561                    ga.copy_from_slice(block);
562                    cipher.decrypt_block(&mut ga);
563                    out.extend(ga.iter().zip(prev).map(|(a, b)| a ^ b));
564                    prev.copy_from_slice(block);
565                }
566                Ok(out)
567            }
568            PrivProtocol::Aes128 => {
569                let iv = aes_iv(boots, time, params);
570                let mut data = cipher_text.to_vec();
571                aes_cfb(&self.priv_key[..16], iv, &mut data, false)?;
572                Ok(data)
573            }
574        }
575    }
576}
577
578fn aes_iv(boots: i64, time: i64, salt: &[u8]) -> [u8; 16] {
579    let mut iv = [0u8; 16];
580    iv[..4].copy_from_slice(&(boots as u32).to_be_bytes());
581    iv[4..8].copy_from_slice(&(time as u32).to_be_bytes());
582    iv[8..].copy_from_slice(salt);
583    iv
584}
585
586/// CFB-128: each keystream block is the encryption of the previous ciphertext block.
587fn aes_cfb(key: &[u8], iv: [u8; 16], data: &mut [u8], encrypt: bool) -> Result<()> {
588    let cipher = aes::Aes128::new_from_slice(key).context("AES key")?;
589    let mut feedback = iv;
590    for chunk in data.chunks_mut(16) {
591        let mut ga = Block::<aes::Aes128>::default();
592        ga.copy_from_slice(&feedback);
593        cipher.encrypt_block(&mut ga);
594        let mut next = [0u8; 16];
595        for (i, b) in chunk.iter_mut().enumerate() {
596            let received = *b;
597            *b ^= ga[i];
598            // The feedback is always the ciphertext: what was written when encrypting, what
599            // was read when decrypting.
600            next[i] = if encrypt { *b } else { received };
601        }
602        feedback = next;
603    }
604    Ok(())
605}
606
607/// The content span (offset, length) of the TLV starting at `at`, and where the TLV ends.
608fn tlv(data: &[u8], at: usize) -> Result<(u8, usize, usize)> {
609    let tag = *data.get(at).context("truncated BER")?;
610    let first = *data.get(at + 1).context("truncated BER length")?;
611    let (len, header) = if first < 0x80 {
612        (first as usize, 2)
613    } else {
614        let n = (first & 0x7f) as usize;
615        ensure!((1..=4).contains(&n), "unsupported BER length form");
616        let bytes = data
617            .get(at + 2..at + 2 + n)
618            .context("truncated BER length")?;
619        (
620            bytes.iter().fold(0usize, |a, b| (a << 8) | *b as usize),
621            2 + n,
622        )
623    };
624    let start = at + header;
625    ensure!(
626        start + len <= data.len(),
627        "BER length runs past the message"
628    );
629    Ok((tag, start, len))
630}
631
632/// Where msgAuthenticationParameters' content sits in a received message: outer SEQUENCE →
633/// third element (msgSecurityParameters OCTET STRING) → its SEQUENCE → fifth element.
634pub fn auth_params_span(data: &[u8]) -> Result<(usize, usize)> {
635    let (tag, outer, _) = tlv(data, 0)?;
636    ensure!(tag == 0x30, "not a SEQUENCE");
637    let mut at = outer;
638    for _ in 0..2 {
639        let (_, s, l) = tlv(data, at)?;
640        at = s + l;
641    }
642    let (tag, sec, _) = tlv(data, at)?;
643    ensure!(tag == 0x04, "msgSecurityParameters is not an OCTET STRING");
644    let (tag, seq, _) = tlv(data, sec)?;
645    ensure!(tag == 0x30, "UsmSecurityParameters is not a SEQUENCE");
646    let mut at = seq;
647    for _ in 0..4 {
648        let (_, s, l) = tlv(data, at)?;
649        at = s + l;
650    }
651    let (tag, s, l) = tlv(data, at)?;
652    ensure!(
653        tag == 0x04,
654        "msgAuthenticationParameters is not an OCTET STRING"
655    );
656    Ok((s, l))
657}
658
659/// The length of the first TLV in `data` — a decrypted scoped PDU ends there, before padding.
660pub fn first_tlv_len(data: &[u8]) -> Result<usize> {
661    let (_, s, l) = tlv(data, 0)?;
662    Ok(s + l)
663}
664
665fn int(v: &Integer) -> Result<i64> {
666    i64::try_from(v.clone()).map_err(|_| anyhow::anyhow!("integer out of range"))
667}
668
669/// A decoded v3 message, before anything in it is believed.
670pub struct Received {
671    pub msg_id: i64,
672    pub max_size: i64,
673    pub flags: u8,
674    pub usm: v3::USMSecurityParameters,
675    pub data: v3::ScopedPduData,
676}
677
678/// Decode a v3 message's envelope.
679///
680/// The message is depth-screened before `rasn` sees it, and so are msgSecurityParameters'
681/// contents before they are decoded in turn. The outer screen cannot cover the second: it reads
682/// msgSecurityParameters as one primitive OCTET STRING and skips its contents, while the
683/// UsmSecurityParameters inside are BER of their own whose OCTET STRINGs may be constructed —
684/// `24 80` repeated buys one level of `rasn`'s unbounded recursion per two bytes, enough in one
685/// datagram to overflow the stack and abort the process before any key is checked.
686pub fn decode(data: &[u8]) -> Result<Received> {
687    crate::server::snmp::check_ber_structure(data, crate::server::snmp::MAX_BER_DEPTH)
688        .map_err(|e| anyhow::anyhow!("not an SNMPv3 message: {e}"))?;
689    let msg: v3::Message =
690        ber::decode(data).map_err(|e| anyhow::anyhow!("not an SNMPv3 message: {e}"))?;
691    ensure!(int(&msg.version)? == 3, "not version 3");
692    ensure!(
693        int(&msg.global_data.security_model)? == USM_SECURITY_MODEL,
694        "security model {} is not the USM",
695        msg.global_data.security_model
696    );
697    ensure!(
698        msg.global_data.flags.len() == 1,
699        "msgFlags must be one byte"
700    );
701    crate::server::snmp::check_ber_structure(
702        &msg.security_parameters,
703        crate::server::snmp::MAX_BER_DEPTH,
704    )
705    .map_err(|e| anyhow::anyhow!("bad UsmSecurityParameters: {e}"))?;
706    let usm: v3::USMSecurityParameters = ber::decode(&msg.security_parameters)
707        .map_err(|e| anyhow::anyhow!("bad UsmSecurityParameters: {e}"))?;
708    Ok(Received {
709        msg_id: int(&msg.global_data.message_id)?,
710        max_size: int(&msg.global_data.max_size)?,
711        flags: msg.global_data.flags[0],
712        usm,
713        data: msg.scoped_data,
714    })
715}
716
717/// Everything needed to put one outgoing message on the wire.
718pub struct Outgoing<'a> {
719    pub msg_id: i64,
720    pub max_size: i64,
721    pub flags: u8,
722    pub engine_id: &'a [u8],
723    pub boots: i64,
724    pub time: i64,
725    pub user: &'a [u8],
726    /// Keys at the message's level; `None` for noAuthNoPriv.
727    pub keys: Option<&'a Keys>,
728    pub salt: u64,
729    pub context_engine_id: &'a [u8],
730    pub context_name: &'a [u8],
731    pub pdu: v2::Pdus,
732}
733
734/// Build, encrypt and authenticate one message.
735pub fn encode(out: Outgoing<'_>) -> Result<Vec<u8>> {
736    let level = SecurityLevel::from_flags(out.flags)?;
737    let scoped = v3::ScopedPdu {
738        engine_id: out.context_engine_id.to_vec().into(),
739        name: out.context_name.to_vec().into(),
740        data: out.pdu,
741    };
742    let (scoped_data, priv_params) = if level == SecurityLevel::AuthPriv {
743        let keys = out.keys.context("privacy needs keys")?;
744        let plain = ber::encode(&scoped).map_err(|e| anyhow::anyhow!("scoped PDU: {e}"))?;
745        let (cipher_text, params) = keys.encrypt(&plain, out.boots, out.time, out.salt)?;
746        (v3::ScopedPduData::EncryptedPdu(cipher_text.into()), params)
747    } else {
748        (v3::ScopedPduData::CleartextPdu(scoped), Vec::new())
749    };
750    let mac_len = match level {
751        SecurityLevel::NoAuthNoPriv => 0,
752        _ => out
753            .keys
754            .context("authentication needs keys")?
755            .auth
756            .mac_len(),
757    };
758    let build = |mac: Vec<u8>| -> Result<Vec<u8>> {
759        let usm = v3::USMSecurityParameters {
760            authoritative_engine_id: out.engine_id.to_vec().into(),
761            authoritative_engine_boots: out.boots.into(),
762            authoritative_engine_time: out.time.into(),
763            user_name: out.user.to_vec().into(),
764            authentication_parameters: mac.into(),
765            privacy_parameters: priv_params.clone().into(),
766        };
767        let message = v3::Message {
768            version: 3.into(),
769            global_data: v3::HeaderData {
770                message_id: out.msg_id.into(),
771                max_size: out.max_size.into(),
772                flags: OctetString::from(vec![out.flags]),
773                security_model: USM_SECURITY_MODEL.into(),
774            },
775            security_parameters: ber::encode(&usm)
776                .map_err(|e| anyhow::anyhow!("security parameters: {e}"))?
777                .into(),
778            scoped_data: scoped_data.clone(),
779        };
780        ber::encode(&message).map_err(|e| anyhow::anyhow!("v3 message: {e}"))
781    };
782    let zeroed = build(vec![0; mac_len])?;
783    if mac_len == 0 {
784        return Ok(zeroed);
785    }
786    let keys = out.keys.context("authentication needs keys")?;
787    let mac = keys.auth.mac(&keys.auth_key, &zeroed);
788    let (at, len) = auth_params_span(&zeroed)?;
789    ensure!(
790        len == mac_len,
791        "MAC field is {len} bytes, expected {mac_len}"
792    );
793    let mut message = zeroed;
794    message[at..at + len].copy_from_slice(&mac);
795    Ok(message)
796}
797
798/// Check a received message's MAC over the bytes as received.
799pub fn verify(data: &[u8], keys: &Keys) -> bool {
800    let Ok((at, len)) = auth_params_span(data) else {
801        return false;
802    };
803    if len != keys.auth.mac_len() || len == 0 {
804        return false;
805    }
806    let mut zeroed = data.to_vec();
807    let offered = data[at..at + len].to_vec();
808    zeroed[at..at + len].fill(0);
809    let expected = keys.auth.mac(&keys.auth_key, &zeroed);
810    // Constant-time comparison: the position of the first wrong byte is not to be learnt.
811    expected
812        .iter()
813        .zip(&offered)
814        .fold(0u8, |acc, (a, b)| acc | (a ^ b))
815        == 0
816}
817
818/// Recover the scoped PDU, decrypting it under `keys` when the message is encrypted.
819pub fn scoped_pdu(rx: &Received, keys: Option<&Keys>) -> Result<v3::ScopedPdu> {
820    match &rx.data {
821        v3::ScopedPduData::CleartextPdu(p) => {
822            ensure!(
823                rx.flags & FLAG_PRIV == 0,
824                "msgFlags says encrypted, the PDU is not"
825            );
826            Ok(p.clone())
827        }
828        v3::ScopedPduData::EncryptedPdu(c) => {
829            ensure!(
830                rx.flags & FLAG_PRIV != 0,
831                "an encrypted PDU without the privacy flag"
832            );
833            let keys = keys.context("an encrypted PDU for a user without privacy")?;
834            let plain = keys.decrypt(
835                c,
836                int(&rx.usm.authoritative_engine_boots)?,
837                int(&rx.usm.authoritative_engine_time)?,
838                &rx.usm.privacy_parameters,
839            )?;
840            crate::server::snmp::check_ber_structure(
841                &plain[..first_tlv_len(&plain)?],
842                crate::server::snmp::MAX_BER_DEPTH,
843            )
844            .map_err(|e| anyhow::anyhow!("decrypted PDU: {e}"))?;
845            ber::decode(&plain[..first_tlv_len(&plain)?])
846                .map_err(|e| anyhow::anyhow!("the decrypted scoped PDU does not decode: {e}"))
847        }
848    }
849}
850
851fn report_pdu(kind: ReportKind, count: u32, request_id: i32) -> v2::Pdus {
852    v2::Pdus::Report(v2::Report(v2::Pdu {
853        request_id,
854        error_status: 0,
855        error_index: 0,
856        variable_bindings: vec![v2::VarBind {
857            name: ObjectIdentifier::new_unchecked(kind.oid().into()),
858            value: v2::VarBindValue::Value(rasn_smi::v2::ObjectSyntax::ApplicationWide(
859                rasn_smi::v2::ApplicationSyntax::Counter(rasn_smi::v1::Counter(count)),
860            )),
861        }],
862    }))
863}
864
865/// The authoritative side: one engine, its users, and its counters.
866pub struct Agent {
867    pub engine_id: Vec<u8>,
868    pub boots: i64,
869    started: crate::utils::clock::Instant,
870    users: HashMap<Vec<u8>, (User, Keys)>,
871    counters: [AtomicU32; 6],
872    salt: AtomicU64,
873}
874
875/// What the agent made of a request.
876pub enum Inbound {
877    /// A request at the level its user is configured for, ready for the handler.
878    Request(Box<Request>),
879    /// A report to send back (already encoded), and why.
880    Report(Vec<u8>, ReportKind),
881    /// Nothing to answer: not reportable, or not answerable at all.
882    Drop(String),
883}
884
885/// An authenticated (or deliberately unauthenticated) request and how to answer it.
886pub struct Request {
887    pub msg_id: i64,
888    pub max_size: i64,
889    pub user: String,
890    pub level: SecurityLevel,
891    pub context_engine_id: Vec<u8>,
892    pub context_name: Vec<u8>,
893    pub pdu: v2::Pdus,
894}
895
896impl Agent {
897    pub fn new(engine_id: Vec<u8>, boots: i64, users: Vec<User>) -> Result<Agent> {
898        ensure!(
899            (MIN_ENGINE_ID_BYTES..=MAX_ENGINE_ID_BYTES).contains(&engine_id.len()),
900            "engine_id must be {MIN_ENGINE_ID_BYTES} to {MAX_ENGINE_ID_BYTES} bytes"
901        );
902        ensure!(
903            (1..MAX_ENGINE_BOOTS).contains(&boots),
904            "engine_boots must be 1 to {}",
905            MAX_ENGINE_BOOTS - 1
906        );
907        ensure!(users.len() <= MAX_USERS, "at most {MAX_USERS} v3 users");
908        let mut table = HashMap::new();
909        for u in users {
910            let keys = u.localize(&engine_id);
911            ensure!(
912                table
913                    .insert(u.name.as_bytes().to_vec(), (u.clone(), keys))
914                    .is_none(),
915                "v3 user {} is configured twice",
916                u.name
917            );
918        }
919        Ok(Agent {
920            engine_id,
921            boots,
922            started: crate::utils::clock::Instant::now(),
923            users: table,
924            counters: Default::default(),
925            salt: AtomicU64::new(rand::random()),
926        })
927    }
928
929    /// A fresh engine ID in RFC 3411 form: enterprise 0 with the high bit, format 5
930    /// (administratively assigned octets), eight random octets.
931    pub fn random_engine_id() -> Vec<u8> {
932        let mut id = vec![0x80, 0x00, 0x00, 0x00, 0x05];
933        id.extend_from_slice(&rand::random::<[u8; 8]>());
934        id
935    }
936
937    pub fn time(&self) -> i64 {
938        self.started.elapsed().as_secs() as i64
939    }
940
941    pub fn counter(&self, kind: ReportKind) -> u32 {
942        self.counters[kind.index()].load(Ordering::Relaxed)
943    }
944
945    fn report(
946        &self,
947        rx: &Received,
948        kind: ReportKind,
949        keys: Option<&Keys>,
950        request_id: i32,
951    ) -> Result<Vec<u8>> {
952        let count = self.counters[kind.index()]
953            .fetch_add(1, Ordering::Relaxed)
954            .wrapping_add(1);
955        // The time report is authenticated so the manager can believe the clock it carries;
956        // every other report goes out at noAuthNoPriv, as the user's keys are not usable.
957        let flags = if keys.is_some() { FLAG_AUTH } else { 0 };
958        encode(Outgoing {
959            msg_id: rx.msg_id,
960            max_size: MAX_MESSAGE_SIZE,
961            flags,
962            engine_id: &self.engine_id,
963            boots: self.boots,
964            time: self.time(),
965            user: &rx.usm.user_name,
966            keys,
967            salt: 0,
968            context_engine_id: &self.engine_id,
969            context_name: b"",
970            pdu: report_pdu(kind, count, request_id),
971        })
972    }
973
974    /// RFC 3414 §3.2, in order.
975    pub fn receive(&self, data: &[u8]) -> Inbound {
976        let rx = match decode(data) {
977            Ok(rx) => rx,
978            Err(e) => return Inbound::Drop(e.to_string()),
979        };
980        let reportable = rx.flags & FLAG_REPORTABLE != 0;
981        let level = match SecurityLevel::from_flags(rx.flags) {
982            Ok(l) => l,
983            Err(e) => return Inbound::Drop(e.to_string()),
984        };
985        let request_id = match &rx.data {
986            v3::ScopedPduData::CleartextPdu(p) => pdu_request_id(&p.data),
987            _ => 0,
988        };
989        let reply = |kind: ReportKind, keys: Option<&Keys>| -> Inbound {
990            if !reportable {
991                return Inbound::Drop(format!("{} (not reportable)", kind.name()));
992            }
993            match self.report(&rx, kind, keys, request_id) {
994                Ok(bytes) => Inbound::Report(bytes, kind),
995                Err(e) => Inbound::Drop(format!("report could not be built: {e}")),
996            }
997        };
998        if rx.usm.authoritative_engine_id.as_ref() != self.engine_id.as_slice() {
999            return reply(ReportKind::UnknownEngineIds, None);
1000        }
1001        let Some((user, keys)) = self.users.get(rx.usm.user_name.as_ref()) else {
1002            return reply(ReportKind::UnknownUserNames, None);
1003        };
1004        // A user is served only at the level it is configured for: one with keys is never
1005        // answered without them, which is what makes configuring them mean anything.
1006        if level != user.level() {
1007            return reply(ReportKind::UnsupportedSecLevels, None);
1008        }
1009        if level != SecurityLevel::NoAuthNoPriv {
1010            if !verify(data, keys) {
1011                return reply(ReportKind::WrongDigests, None);
1012            }
1013            let (Ok(boots), Ok(time)) = (
1014                int(&rx.usm.authoritative_engine_boots),
1015                int(&rx.usm.authoritative_engine_time),
1016            ) else {
1017                return reply(ReportKind::NotInTimeWindows, Some(keys));
1018            };
1019            // Both are the peer's numbers: range-checked before any arithmetic, and the
1020            // distance taken without a subtraction that could overflow.
1021            if !in_engine_range(boots)
1022                || !in_engine_range(time)
1023                || boots == MAX_ENGINE_BOOTS
1024                || boots != self.boots
1025                || time.abs_diff(self.time()) > TIME_WINDOW_SECS.unsigned_abs()
1026            {
1027                return reply(ReportKind::NotInTimeWindows, Some(keys));
1028            }
1029        }
1030        let scoped = match scoped_pdu(&rx, (level == SecurityLevel::AuthPriv).then_some(keys)) {
1031            Ok(s) => s,
1032            Err(_) if level == SecurityLevel::AuthPriv => {
1033                return reply(ReportKind::DecryptionErrors, None)
1034            }
1035            Err(e) => return Inbound::Drop(e.to_string()),
1036        };
1037        Inbound::Request(Box::new(Request {
1038            msg_id: rx.msg_id,
1039            max_size: rx.max_size,
1040            user: user.name.clone(),
1041            level,
1042            context_engine_id: scoped.engine_id.to_vec(),
1043            context_name: scoped.name.to_vec(),
1044            pdu: scoped.data,
1045        }))
1046    }
1047
1048    /// Encode the agent's answer to `request`.
1049    pub fn respond(&self, request: &Request, pdu: v2::Pdus) -> Result<Vec<u8>> {
1050        let keys = match request.level {
1051            SecurityLevel::NoAuthNoPriv => None,
1052            _ => Some(
1053                &self
1054                    .users
1055                    .get(request.user.as_bytes())
1056                    .context("the user is no longer configured")?
1057                    .1,
1058            ),
1059        };
1060        encode(Outgoing {
1061            msg_id: request.msg_id,
1062            max_size: MAX_MESSAGE_SIZE,
1063            flags: request.level.flags(),
1064            engine_id: &self.engine_id,
1065            boots: self.boots,
1066            time: self.time(),
1067            user: request.user.as_bytes(),
1068            keys,
1069            salt: self.salt.fetch_add(1, Ordering::Relaxed),
1070            context_engine_id: &self.engine_id,
1071            context_name: &request.context_name,
1072            pdu,
1073        })
1074    }
1075}
1076
1077/// A received message's snmpEngineBoots and snmpEngineTime, refused outside RFC 3414's range.
1078fn engine_clock(rx: &Received) -> Result<(i64, i64)> {
1079    let boots = int(&rx.usm.authoritative_engine_boots)?;
1080    let time = int(&rx.usm.authoritative_engine_time)?;
1081    ensure!(
1082        in_engine_range(boots),
1083        "snmpEngineBoots {boots} is outside 0..={MAX_ENGINE_BOOTS}"
1084    );
1085    ensure!(
1086        in_engine_range(time),
1087        "snmpEngineTime {time} is outside 0..={MAX_ENGINE_TIME}"
1088    );
1089    Ok((boots, time))
1090}
1091
1092pub fn pdu_request_id(pdu: &v2::Pdus) -> i32 {
1093    match pdu {
1094        v2::Pdus::GetRequest(p) => p.0.request_id,
1095        v2::Pdus::GetNextRequest(p) => p.0.request_id,
1096        v2::Pdus::GetBulkRequest(p) => p.0.request_id,
1097        v2::Pdus::SetRequest(p) => p.0.request_id,
1098        v2::Pdus::Response(p) => p.0.request_id,
1099        v2::Pdus::InformRequest(p) => p.0.request_id,
1100        v2::Pdus::Trap(p) => p.0.request_id,
1101        v2::Pdus::Report(p) => p.0.request_id,
1102    }
1103}
1104
1105/// The non-authoritative side: one user talking to one agent, whose engine it discovers.
1106pub struct Manager {
1107    pub user: User,
1108    engine: Mutex<Option<Engine>>,
1109    msg_id: AtomicU32,
1110    salt: AtomicU64,
1111    /// The message ids of the last few requests, any of which a reply may answer.
1112    outstanding: Mutex<std::collections::VecDeque<i64>>,
1113}
1114
1115/// How many sent message ids a reply may still match.
1116pub const OUTSTANDING_MESSAGES: usize = 16;
1117
1118#[derive(Clone)]
1119struct Engine {
1120    id: Vec<u8>,
1121    boots: i64,
1122    time: i64,
1123    at: crate::utils::clock::Instant,
1124    keys: Keys,
1125}
1126
1127/// What the manager made of a reply.
1128pub enum Reply {
1129    Pdu(v2::Pdus),
1130    /// A report: which counter, and its value.
1131    Report(ReportKind, u64),
1132}
1133
1134impl Manager {
1135    pub fn new(user: User) -> Manager {
1136        Manager {
1137            user,
1138            engine: Mutex::new(None),
1139            msg_id: AtomicU32::new(rand::random::<u32>() >> 1),
1140            salt: AtomicU64::new(rand::random()),
1141            outstanding: Mutex::new(std::collections::VecDeque::new()),
1142        }
1143    }
1144
1145    /// Wrap a request and remember its message id for [`Manager::unwrap_reply`].
1146    pub fn wrap_request(&self, pdu: v2::Pdus, context_name: &[u8]) -> Result<Vec<u8>> {
1147        let (msg_id, bytes) = self.wrap(pdu, context_name)?;
1148        let mut ids = self
1149            .outstanding
1150            .lock()
1151            .map_err(|_| anyhow::anyhow!("lock"))?;
1152        if ids.len() == OUTSTANDING_MESSAGES {
1153            ids.pop_front();
1154        }
1155        ids.push_back(msg_id);
1156        Ok(bytes)
1157    }
1158
1159    /// Open a reply to any recently sent request; the request is then no longer outstanding.
1160    pub fn unwrap_reply(&self, data: &[u8]) -> Result<Reply> {
1161        let msg_id = decode(data)?.msg_id;
1162        let known = self
1163            .outstanding
1164            .lock()
1165            .map_err(|_| anyhow::anyhow!("lock"))?
1166            .contains(&msg_id);
1167        ensure!(
1168            known,
1169            "a reply to message {msg_id}, which this client did not send"
1170        );
1171        let reply = self.unwrap(data, msg_id)?;
1172        if let Ok(mut ids) = self.outstanding.lock() {
1173            ids.retain(|id| *id != msg_id);
1174        }
1175        Ok(reply)
1176    }
1177
1178    pub fn engine_id(&self) -> Option<Vec<u8>> {
1179        self.engine.lock().ok()?.as_ref().map(|e| e.id.clone())
1180    }
1181
1182    fn next_msg_id(&self) -> i64 {
1183        (self.msg_id.fetch_add(1, Ordering::Relaxed) & 0x7fff_ffff) as i64
1184    }
1185
1186    /// The discovery probe (RFC 3414 §4): noAuthNoPriv, empty engine and user, reportable.
1187    pub fn discovery(&self) -> Result<(i64, Vec<u8>)> {
1188        let msg_id = self.next_msg_id();
1189        let bytes = encode(Outgoing {
1190            msg_id,
1191            max_size: MAX_MESSAGE_SIZE,
1192            flags: FLAG_REPORTABLE,
1193            engine_id: b"",
1194            boots: 0,
1195            time: 0,
1196            user: b"",
1197            keys: None,
1198            salt: 0,
1199            context_engine_id: b"",
1200            context_name: b"",
1201            pdu: v2::Pdus::GetRequest(v2::GetRequest(v2::Pdu {
1202                request_id: rand::random::<i32>() & 0x7fff_ffff,
1203                error_status: 0,
1204                error_index: 0,
1205                variable_bindings: vec![],
1206            })),
1207        })?;
1208        Ok((msg_id, bytes))
1209    }
1210
1211    /// Learn the engine from the discovery report.
1212    pub fn learn(&self, data: &[u8], msg_id: i64) -> Result<()> {
1213        let rx = decode(data)?;
1214        ensure!(
1215            rx.msg_id == msg_id,
1216            "the discovery reply answers another message"
1217        );
1218        let id = rx.usm.authoritative_engine_id.to_vec();
1219        ensure!(
1220            (MIN_ENGINE_ID_BYTES..=MAX_ENGINE_ID_BYTES).contains(&id.len()),
1221            "the agent reported a {}-byte engine ID",
1222            id.len()
1223        );
1224        // The discovery report is unauthenticated: its clock is the peer's word, and is held
1225        // to the range RFC 3414 gives it before anything adds to it.
1226        let (boots, time) = engine_clock(&rx)?;
1227        let keys = self.user.localize(&id);
1228        *self
1229            .engine
1230            .lock()
1231            .map_err(|_| anyhow::anyhow!("engine lock"))? = Some(Engine {
1232            id,
1233            boots,
1234            time,
1235            at: crate::utils::clock::Instant::now(),
1236            keys,
1237        });
1238        Ok(())
1239    }
1240
1241    /// Wrap a request PDU at the user's level, against the discovered engine.
1242    pub fn wrap(&self, pdu: v2::Pdus, context_name: &[u8]) -> Result<(i64, Vec<u8>)> {
1243        let engine = self
1244            .engine
1245            .lock()
1246            .map_err(|_| anyhow::anyhow!("engine lock"))?
1247            .clone()
1248            .context("the agent's engine has not been discovered")?;
1249        let level = self.user.level();
1250        let msg_id = self.next_msg_id();
1251        let bytes = encode(Outgoing {
1252            msg_id,
1253            max_size: MAX_MESSAGE_SIZE,
1254            flags: level.flags() | FLAG_REPORTABLE,
1255            engine_id: &engine.id,
1256            boots: engine.boots,
1257            time: engine
1258                .time
1259                .saturating_add(i64::try_from(engine.at.elapsed().as_secs()).unwrap_or(i64::MAX))
1260                .min(MAX_ENGINE_TIME),
1261            user: self.user.name.as_bytes(),
1262            keys: (level != SecurityLevel::NoAuthNoPriv).then_some(&engine.keys),
1263            salt: self.salt.fetch_add(1, Ordering::Relaxed),
1264            context_engine_id: &engine.id,
1265            context_name,
1266            pdu,
1267        })?;
1268        Ok((msg_id, bytes))
1269    }
1270
1271    /// Check and open a reply to `msg_id`. A report updates the engine's clock when it is
1272    /// authenticated (the only report whose clock may be believed) or names the engine.
1273    pub fn unwrap(&self, data: &[u8], msg_id: i64) -> Result<Reply> {
1274        let rx = decode(data)?;
1275        ensure!(
1276            rx.msg_id == msg_id,
1277            "a reply to message {} while {} is outstanding",
1278            rx.msg_id,
1279            msg_id
1280        );
1281        let mut guard = self
1282            .engine
1283            .lock()
1284            .map_err(|_| anyhow::anyhow!("engine lock"))?;
1285        let engine = guard.as_mut().context("no engine discovered")?;
1286        ensure!(
1287            rx.usm.authoritative_engine_id.as_ref() == engine.id.as_slice(),
1288            "a reply from engine {} while talking to {}",
1289            hex::encode(&rx.usm.authoritative_engine_id),
1290            hex::encode(&engine.id)
1291        );
1292        let level = SecurityLevel::from_flags(rx.flags)?;
1293        let authenticated = level != SecurityLevel::NoAuthNoPriv;
1294        if authenticated {
1295            ensure!(
1296                verify(data, &engine.keys),
1297                "the reply's MAC does not verify"
1298            );
1299        }
1300        let scoped = scoped_pdu(
1301            &rx,
1302            (level == SecurityLevel::AuthPriv).then_some(&engine.keys),
1303        )?;
1304        if let v2::Pdus::Report(r) = &scoped.data {
1305            if authenticated {
1306                let (boots, time) = engine_clock(&rx)?;
1307                engine.boots = boots;
1308                engine.time = time;
1309                engine.at = crate::utils::clock::Instant::now();
1310            }
1311            let vb =
1312                r.0.variable_bindings
1313                    .first()
1314                    .context("a report with no varbind")?;
1315            let kind = ReportKind::from_oid(&vb.name.to_vec()).with_context(|| {
1316                format!(
1317                    "a report naming {}",
1318                    vb.name
1319                        .iter()
1320                        .map(u32::to_string)
1321                        .collect::<Vec<_>>()
1322                        .join(".")
1323                )
1324            })?;
1325            let count = match &vb.value {
1326                v2::VarBindValue::Value(rasn_smi::v2::ObjectSyntax::ApplicationWide(
1327                    rasn_smi::v2::ApplicationSyntax::Counter(c),
1328                )) => c.0 as u64,
1329                _ => 0,
1330            };
1331            return Ok(Reply::Report(kind, count));
1332        }
1333        // A response must come back at the level it was asked at (RFC 3412 §7.2.11).
1334        ensure!(
1335            level == self.user.level(),
1336            "the reply is {}, the request was {}",
1337            level.name(),
1338            self.user.level().name()
1339        );
1340        Ok(Reply::Pdu(scoped.data))
1341    }
1342}