async_snmp/notification/
types.rs

1//! USM user configuration types for notification receiver.
2//!
3//! These types store authentication and privacy settings for SNMPv3 notification handling.
4
5use bytes::Bytes;
6
7use crate::message::SecurityLevel;
8use crate::v3::{AuthProtocol, LocalizedKey, PrivKey, PrivProtocol};
9
10/// USM user credentials for V3 notification handling.
11///
12/// Stores the authentication and privacy settings for a single USM user.
13#[derive(Clone)]
14pub struct UsmUserConfig {
15    /// Username
16    pub username: Bytes,
17    /// Authentication protocol and password
18    pub auth: Option<(AuthProtocol, Vec<u8>)>,
19    /// Privacy protocol and password
20    pub privacy: Option<(PrivProtocol, Vec<u8>)>,
21}
22
23impl UsmUserConfig {
24    /// Create a new USM user config with no authentication (noAuthNoPriv).
25    pub fn new(username: impl Into<Bytes>) -> Self {
26        Self {
27            username: username.into(),
28            auth: None,
29            privacy: None,
30        }
31    }
32
33    /// Add authentication (authNoPriv or authPriv).
34    pub fn auth(mut self, protocol: AuthProtocol, password: &[u8]) -> Self {
35        self.auth = Some((protocol, password.to_vec()));
36        self
37    }
38
39    /// Add privacy/encryption (authPriv).
40    pub fn privacy(mut self, protocol: PrivProtocol, password: &[u8]) -> Self {
41        self.privacy = Some((protocol, password.to_vec()));
42        self
43    }
44
45    /// Get the security level based on configured auth/privacy.
46    pub fn security_level(&self) -> SecurityLevel {
47        match (&self.auth, &self.privacy) {
48            (None, _) => SecurityLevel::NoAuthNoPriv,
49            (Some(_), None) => SecurityLevel::AuthNoPriv,
50            (Some(_), Some(_)) => SecurityLevel::AuthPriv,
51        }
52    }
53
54    /// Derive keys localized to a specific engine ID.
55    pub(crate) fn derive_keys(&self, engine_id: &[u8]) -> DerivedKeys {
56        let auth_key = self.auth.as_ref().map(|(protocol, password)| {
57            LocalizedKey::from_password(*protocol, password, engine_id)
58        });
59
60        let priv_key = match (&self.auth, &self.privacy) {
61            (Some((auth_protocol, _)), Some((priv_protocol, priv_password))) => Some(
62                PrivKey::from_password(*auth_protocol, *priv_protocol, priv_password, engine_id),
63            ),
64            _ => None,
65        };
66
67        DerivedKeys { auth_key, priv_key }
68    }
69}
70
71impl std::fmt::Debug for UsmUserConfig {
72    fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
73        f.debug_struct("UsmUserConfig")
74            .field("username", &String::from_utf8_lossy(&self.username))
75            .field("auth", &self.auth.as_ref().map(|(p, _)| p))
76            .field("privacy", &self.privacy.as_ref().map(|(p, _)| p))
77            .finish()
78    }
79}
80
81/// Derived keys for a specific engine ID.
82///
83/// Used internally by notification receiver and agent for V3 authentication.
84pub(crate) struct DerivedKeys {
85    /// Localized authentication key
86    pub auth_key: Option<LocalizedKey>,
87    /// Privacy key
88    pub priv_key: Option<PrivKey>,
89}
90
91#[cfg(test)]
92mod tests {
93    use super::*;
94
95    #[test]
96    fn test_usm_user_config_no_auth() {
97        let config = UsmUserConfig::new(Bytes::from_static(b"testuser"));
98        assert_eq!(config.security_level(), SecurityLevel::NoAuthNoPriv);
99        assert!(config.auth.is_none());
100        assert!(config.privacy.is_none());
101    }
102
103    #[test]
104    fn test_usm_user_config_auth_only() {
105        let config = UsmUserConfig::new(Bytes::from_static(b"testuser"))
106            .auth(AuthProtocol::Sha1, b"password123");
107        assert_eq!(config.security_level(), SecurityLevel::AuthNoPriv);
108        assert!(config.auth.is_some());
109        assert!(config.privacy.is_none());
110    }
111
112    #[test]
113    fn test_usm_user_config_auth_priv() {
114        let config = UsmUserConfig::new(Bytes::from_static(b"testuser"))
115            .auth(AuthProtocol::Sha256, b"authpass")
116            .privacy(PrivProtocol::Aes128, b"privpass");
117        assert_eq!(config.security_level(), SecurityLevel::AuthPriv);
118        assert!(config.auth.is_some());
119        assert!(config.privacy.is_some());
120    }
121
122    #[test]
123    fn test_usm_user_config_derive_keys() {
124        let config = UsmUserConfig::new(Bytes::from_static(b"testuser"))
125            .auth(AuthProtocol::Sha1, b"password123");
126
127        let engine_id = b"test-engine-id";
128        let keys = config.derive_keys(engine_id);
129
130        assert!(keys.auth_key.is_some());
131        assert!(keys.priv_key.is_none());
132    }
133
134    #[test]
135    fn test_usm_user_config_derive_keys_with_privacy() {
136        let config = UsmUserConfig::new(Bytes::from_static(b"testuser"))
137            .auth(AuthProtocol::Sha256, b"authpass")
138            .privacy(PrivProtocol::Aes128, b"privpass");
139
140        let engine_id = b"test-engine-id";
141        let keys = config.derive_keys(engine_id);
142
143        assert!(keys.auth_key.is_some());
144        assert!(keys.priv_key.is_some());
145    }
146}