async_snmp/notification/
types.rs1use bytes::Bytes;
7
8use crate::message::SecurityLevel;
9use crate::v3::{AuthProtocol, LocalizedKey, PrivKey, PrivProtocol};
10
11#[derive(Clone)]
23pub struct UsmConfig {
24 pub username: Bytes,
26 pub auth: Option<(AuthProtocol, Vec<u8>)>,
28 pub privacy: Option<(PrivProtocol, Vec<u8>)>,
30 pub master_keys: Option<crate::v3::MasterKeys>,
32}
33
34impl UsmConfig {
35 pub fn new(username: impl Into<Bytes>) -> Self {
37 Self {
38 username: username.into(),
39 auth: None,
40 privacy: None,
41 master_keys: None,
42 }
43 }
44
45 pub fn auth(mut self, protocol: AuthProtocol, password: impl AsRef<[u8]>) -> Self {
47 self.auth = Some((protocol, password.as_ref().to_vec()));
48 self
49 }
50
51 pub fn privacy(mut self, protocol: PrivProtocol, password: impl AsRef<[u8]>) -> Self {
53 self.privacy = Some((protocol, password.as_ref().to_vec()));
54 self
55 }
56
57 pub fn with_master_keys(mut self, master_keys: crate::v3::MasterKeys) -> Self {
63 self.master_keys = Some(master_keys);
64 self
65 }
66
67 pub fn security_level(&self) -> SecurityLevel {
69 if let Some(ref master_keys) = self.master_keys {
71 if master_keys.priv_protocol().is_some() {
72 return SecurityLevel::AuthPriv;
73 }
74 return SecurityLevel::AuthNoPriv;
75 }
76
77 match (&self.auth, &self.privacy) {
78 (None, _) => SecurityLevel::NoAuthNoPriv,
79 (Some(_), None) => SecurityLevel::AuthNoPriv,
80 (Some(_), Some(_)) => SecurityLevel::AuthPriv,
81 }
82 }
83
84 pub fn derive_keys(&self, engine_id: &[u8]) -> DerivedKeys {
90 if let Some(ref master_keys) = self.master_keys {
92 tracing::trace!(target: "async_snmp::client", { engine_id_len = engine_id.len(), auth_protocol = ?master_keys.auth_protocol(), priv_protocol = ?master_keys.priv_protocol() }, "localizing from cached master keys");
93 let (auth_key, priv_key) = master_keys.localize(engine_id);
94 tracing::trace!(target: "async_snmp::client", "key localization complete");
95 return DerivedKeys {
96 auth_key: Some(auth_key),
97 priv_key,
98 };
99 }
100
101 tracing::trace!(target: "async_snmp::client", { engine_id_len = engine_id.len(), has_auth = self.auth.is_some(), has_priv = self.privacy.is_some() }, "deriving localized keys from passwords");
103
104 let auth_key = self.auth.as_ref().map(|(protocol, password)| {
105 tracing::trace!(target: "async_snmp::client", { auth_protocol = ?protocol }, "deriving auth key");
106 LocalizedKey::from_password(*protocol, password, engine_id)
107 });
108
109 let priv_key = match (&self.auth, &self.privacy) {
110 (Some((auth_protocol, _)), Some((priv_protocol, priv_password))) => {
111 tracing::trace!(target: "async_snmp::client", { priv_protocol = ?priv_protocol }, "deriving privacy key");
112 Some(PrivKey::from_password(
113 *auth_protocol,
114 *priv_protocol,
115 priv_password,
116 engine_id,
117 ))
118 }
119 _ => None,
120 };
121
122 tracing::trace!(target: "async_snmp::client", "key derivation complete");
123 DerivedKeys { auth_key, priv_key }
124 }
125}
126
127impl std::fmt::Debug for UsmConfig {
128 fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
129 f.debug_struct("UsmConfig")
130 .field("username", &String::from_utf8_lossy(&self.username))
131 .field("auth", &self.auth.as_ref().map(|(p, _)| p))
132 .field("privacy", &self.privacy.as_ref().map(|(p, _)| p))
133 .field(
134 "master_keys",
135 &self.master_keys.as_ref().map(|mk| {
136 format!(
137 "MasterKeys({:?}, {:?})",
138 mk.auth_protocol(),
139 mk.priv_protocol()
140 )
141 }),
142 )
143 .finish()
144 }
145}
146
147pub type UsmUserConfig = UsmConfig;
149
150pub struct DerivedKeys {
154 pub auth_key: Option<LocalizedKey>,
156 pub priv_key: Option<PrivKey>,
158}
159
160#[cfg(test)]
161mod tests {
162 use super::*;
163
164 #[test]
165 fn test_usm_user_config_no_auth() {
166 let config = UsmUserConfig::new(Bytes::from_static(b"testuser"));
167 assert_eq!(config.security_level(), SecurityLevel::NoAuthNoPriv);
168 assert!(config.auth.is_none());
169 assert!(config.privacy.is_none());
170 }
171
172 #[test]
173 fn test_usm_user_config_auth_only() {
174 let config = UsmUserConfig::new(Bytes::from_static(b"testuser"))
175 .auth(AuthProtocol::Sha1, b"password123");
176 assert_eq!(config.security_level(), SecurityLevel::AuthNoPriv);
177 assert!(config.auth.is_some());
178 assert!(config.privacy.is_none());
179 }
180
181 #[test]
182 fn test_usm_user_config_auth_priv() {
183 let config = UsmUserConfig::new(Bytes::from_static(b"testuser"))
184 .auth(AuthProtocol::Sha256, b"authpass")
185 .privacy(PrivProtocol::Aes128, b"privpass");
186 assert_eq!(config.security_level(), SecurityLevel::AuthPriv);
187 assert!(config.auth.is_some());
188 assert!(config.privacy.is_some());
189 }
190
191 #[test]
192 fn test_usm_user_config_derive_keys() {
193 let config = UsmUserConfig::new(Bytes::from_static(b"testuser"))
194 .auth(AuthProtocol::Sha1, b"password123");
195
196 let engine_id = b"test-engine-id";
197 let keys = config.derive_keys(engine_id);
198
199 assert!(keys.auth_key.is_some());
200 assert!(keys.priv_key.is_none());
201 }
202
203 #[test]
204 fn test_usm_user_config_derive_keys_with_privacy() {
205 let config = UsmUserConfig::new(Bytes::from_static(b"testuser"))
206 .auth(AuthProtocol::Sha256, b"authpass")
207 .privacy(PrivProtocol::Aes128, b"privpass");
208
209 let engine_id = b"test-engine-id";
210 let keys = config.derive_keys(engine_id);
211
212 assert!(keys.auth_key.is_some());
213 assert!(keys.priv_key.is_some());
214 }
215}