1use crate::v3::{AuthProtocol, MasterKeys, PrivProtocol};
26
27#[derive(Debug, Clone, Copy, Default, PartialEq, Eq)]
29#[cfg_attr(feature = "serde", derive(serde::Serialize, serde::Deserialize))]
30pub enum CommunityVersion {
31 V1,
33 #[default]
35 V2c,
36}
37
38#[derive(Debug, Clone)]
40#[cfg_attr(feature = "serde", derive(serde::Serialize, serde::Deserialize))]
41#[cfg_attr(feature = "serde", serde(untagged))]
42pub enum Auth {
43 Community {
45 #[cfg_attr(feature = "serde", serde(default))]
47 version: CommunityVersion,
48 community: String,
50 },
51 Usm(UsmAuth),
53}
54
55impl Default for Auth {
56 fn default() -> Self {
57 Auth::v2c("public")
58 }
59}
60
61impl Auth {
62 pub fn v1(community: impl Into<String>) -> Self {
64 Auth::Community {
65 version: CommunityVersion::V1,
66 community: community.into(),
67 }
68 }
69
70 pub fn v2c(community: impl Into<String>) -> Self {
72 Auth::Community {
73 version: CommunityVersion::V2c,
74 community: community.into(),
75 }
76 }
77
78 pub fn usm(username: impl Into<String>) -> UsmBuilder {
80 UsmBuilder::new(username)
81 }
82}
83
84#[derive(Debug, Clone)]
86#[cfg_attr(feature = "serde", derive(serde::Serialize, serde::Deserialize))]
87pub struct UsmAuth {
88 pub username: String,
90 #[cfg_attr(
92 feature = "serde",
93 serde(default, skip_serializing_if = "Option::is_none")
94 )]
95 pub auth_protocol: Option<AuthProtocol>,
96 #[cfg_attr(
98 feature = "serde",
99 serde(default, skip_serializing_if = "Option::is_none")
100 )]
101 pub auth_password: Option<String>,
102 #[cfg_attr(
104 feature = "serde",
105 serde(default, skip_serializing_if = "Option::is_none")
106 )]
107 pub priv_protocol: Option<PrivProtocol>,
108 #[cfg_attr(
110 feature = "serde",
111 serde(default, skip_serializing_if = "Option::is_none")
112 )]
113 pub priv_password: Option<String>,
114 #[cfg_attr(
117 feature = "serde",
118 serde(default, skip_serializing_if = "Option::is_none")
119 )]
120 pub context_name: Option<String>,
121 #[cfg_attr(feature = "serde", serde(skip))]
124 pub master_keys: Option<MasterKeys>,
125}
126
127pub struct UsmBuilder {
129 username: String,
130 auth: Option<(AuthProtocol, String)>,
131 privacy: Option<(PrivProtocol, String)>,
132 context_name: Option<String>,
133 master_keys: Option<MasterKeys>,
134}
135
136impl UsmBuilder {
137 pub fn new(username: impl Into<String>) -> Self {
139 Self {
140 username: username.into(),
141 auth: None,
142 privacy: None,
143 context_name: None,
144 master_keys: None,
145 }
146 }
147
148 pub fn auth(mut self, protocol: AuthProtocol, password: impl Into<String>) -> Self {
154 self.auth = Some((protocol, password.into()));
155 self
156 }
157
158 pub fn privacy(mut self, protocol: PrivProtocol, password: impl Into<String>) -> Self {
162 self.privacy = Some((protocol, password.into()));
163 self
164 }
165
166 pub fn with_master_keys(mut self, master_keys: MasterKeys) -> Self {
191 self.master_keys = Some(master_keys);
192 self
193 }
194
195 pub fn context_name(mut self, name: impl Into<String>) -> Self {
198 self.context_name = Some(name.into());
199 self
200 }
201}
202
203impl From<UsmBuilder> for Auth {
204 fn from(b: UsmBuilder) -> Auth {
205 Auth::Usm(UsmAuth {
206 username: b.username,
207 auth_protocol: b
208 .master_keys
209 .as_ref()
210 .map(|m| m.auth_protocol())
211 .or(b.auth.as_ref().map(|(p, _)| *p)),
212 auth_password: b.auth.map(|(_, pw)| pw),
213 priv_protocol: b
214 .master_keys
215 .as_ref()
216 .and_then(|m| m.priv_protocol())
217 .or(b.privacy.as_ref().map(|(p, _)| *p)),
218 priv_password: b.privacy.map(|(_, pw)| pw),
219 context_name: b.context_name,
220 master_keys: b.master_keys,
221 })
222 }
223}
224
225#[cfg(test)]
226mod tests {
227 use super::*;
228
229 #[test]
230 fn test_default_auth() {
231 let auth = Auth::default();
232 match auth {
233 Auth::Community { version, community } => {
234 assert_eq!(version, CommunityVersion::V2c);
235 assert_eq!(community, "public");
236 }
237 _ => panic!("expected Community variant"),
238 }
239 }
240
241 #[test]
242 fn test_v1_auth() {
243 let auth = Auth::v1("private");
244 match auth {
245 Auth::Community { version, community } => {
246 assert_eq!(version, CommunityVersion::V1);
247 assert_eq!(community, "private");
248 }
249 _ => panic!("expected Community variant"),
250 }
251 }
252
253 #[test]
254 fn test_v2c_auth() {
255 let auth = Auth::v2c("secret");
256 match auth {
257 Auth::Community { version, community } => {
258 assert_eq!(version, CommunityVersion::V2c);
259 assert_eq!(community, "secret");
260 }
261 _ => panic!("expected Community variant"),
262 }
263 }
264
265 #[test]
266 fn test_community_version_default() {
267 let version = CommunityVersion::default();
268 assert_eq!(version, CommunityVersion::V2c);
269 }
270
271 #[test]
272 fn test_usm_no_auth_no_priv() {
273 let auth: Auth = Auth::usm("readonly").into();
274 match auth {
275 Auth::Usm(usm) => {
276 assert_eq!(usm.username, "readonly");
277 assert!(usm.auth_protocol.is_none());
278 assert!(usm.auth_password.is_none());
279 assert!(usm.priv_protocol.is_none());
280 assert!(usm.priv_password.is_none());
281 assert!(usm.context_name.is_none());
282 }
283 _ => panic!("expected Usm variant"),
284 }
285 }
286
287 #[test]
288 fn test_usm_auth_no_priv() {
289 let auth: Auth = Auth::usm("admin")
290 .auth(AuthProtocol::Sha256, "authpass123")
291 .into();
292 match auth {
293 Auth::Usm(usm) => {
294 assert_eq!(usm.username, "admin");
295 assert_eq!(usm.auth_protocol, Some(AuthProtocol::Sha256));
296 assert_eq!(usm.auth_password, Some("authpass123".to_string()));
297 assert!(usm.priv_protocol.is_none());
298 assert!(usm.priv_password.is_none());
299 }
300 _ => panic!("expected Usm variant"),
301 }
302 }
303
304 #[test]
305 fn test_usm_auth_priv() {
306 let auth: Auth = Auth::usm("admin")
307 .auth(AuthProtocol::Sha256, "authpass")
308 .privacy(PrivProtocol::Aes128, "privpass")
309 .into();
310 match auth {
311 Auth::Usm(usm) => {
312 assert_eq!(usm.username, "admin");
313 assert_eq!(usm.auth_protocol, Some(AuthProtocol::Sha256));
314 assert_eq!(usm.auth_password, Some("authpass".to_string()));
315 assert_eq!(usm.priv_protocol, Some(PrivProtocol::Aes128));
316 assert_eq!(usm.priv_password, Some("privpass".to_string()));
317 }
318 _ => panic!("expected Usm variant"),
319 }
320 }
321
322 #[test]
323 fn test_usm_with_context_name() {
324 let auth: Auth = Auth::usm("admin")
325 .auth(AuthProtocol::Sha256, "authpass")
326 .context_name("vlan100")
327 .into();
328 match auth {
329 Auth::Usm(usm) => {
330 assert_eq!(usm.username, "admin");
331 assert_eq!(usm.context_name, Some("vlan100".to_string()));
332 }
333 _ => panic!("expected Usm variant"),
334 }
335 }
336
337 #[test]
338 fn test_usm_builder_chaining() {
339 let auth: Auth = Auth::usm("user")
341 .auth(AuthProtocol::Sha512, "auth")
342 .privacy(PrivProtocol::Aes256, "priv")
343 .context_name("ctx")
344 .into();
345
346 match auth {
347 Auth::Usm(usm) => {
348 assert_eq!(usm.username, "user");
349 assert_eq!(usm.auth_protocol, Some(AuthProtocol::Sha512));
350 assert_eq!(usm.auth_password, Some("auth".to_string()));
351 assert_eq!(usm.priv_protocol, Some(PrivProtocol::Aes256));
352 assert_eq!(usm.priv_password, Some("priv".to_string()));
353 assert_eq!(usm.context_name, Some("ctx".to_string()));
354 }
355 _ => panic!("expected Usm variant"),
356 }
357 }
358}
359
360#[cfg(all(test, feature = "serde"))]
361mod serde_tests {
362 use super::*;
363
364 #[test]
365 fn test_community_v2c_roundtrip() {
366 let auth = Auth::v2c("public");
367 let json = serde_json::to_string(&auth).unwrap();
368 let back: Auth = serde_json::from_str(&json).unwrap();
369
370 match back {
371 Auth::Community { version, community } => {
372 assert_eq!(version, CommunityVersion::V2c);
373 assert_eq!(community, "public");
374 }
375 _ => panic!("expected Community variant"),
376 }
377 }
378
379 #[test]
380 fn test_community_v1_roundtrip() {
381 let auth = Auth::v1("private");
382 let json = serde_json::to_string(&auth).unwrap();
383 let back: Auth = serde_json::from_str(&json).unwrap();
384
385 match back {
386 Auth::Community { version, community } => {
387 assert_eq!(version, CommunityVersion::V1);
388 assert_eq!(community, "private");
389 }
390 _ => panic!("expected Community variant"),
391 }
392 }
393
394 #[test]
395 fn test_usm_no_auth_roundtrip() {
396 let auth: Auth = Auth::usm("readonly").into();
397 let json = serde_json::to_string(&auth).unwrap();
398 let back: Auth = serde_json::from_str(&json).unwrap();
399
400 match back {
401 Auth::Usm(usm) => {
402 assert_eq!(usm.username, "readonly");
403 assert!(usm.auth_protocol.is_none());
404 assert!(usm.auth_password.is_none());
405 assert!(usm.priv_protocol.is_none());
406 assert!(usm.priv_password.is_none());
407 }
408 _ => panic!("expected Usm variant"),
409 }
410 }
411
412 #[test]
413 fn test_usm_auth_priv_roundtrip() {
414 let auth: Auth = Auth::usm("admin")
415 .auth(AuthProtocol::Sha256, "authpass")
416 .privacy(PrivProtocol::Aes128, "privpass")
417 .context_name("vlan100")
418 .into();
419
420 let json = serde_json::to_string(&auth).unwrap();
421 let back: Auth = serde_json::from_str(&json).unwrap();
422
423 match back {
424 Auth::Usm(usm) => {
425 assert_eq!(usm.username, "admin");
426 assert_eq!(usm.auth_protocol, Some(AuthProtocol::Sha256));
427 assert_eq!(usm.auth_password, Some("authpass".to_string()));
428 assert_eq!(usm.priv_protocol, Some(PrivProtocol::Aes128));
429 assert_eq!(usm.priv_password, Some("privpass".to_string()));
430 assert_eq!(usm.context_name, Some("vlan100".to_string()));
431 }
432 _ => panic!("expected Usm variant"),
433 }
434 }
435
436 #[test]
437 fn test_community_deserialize_without_version() {
438 let json = r#"{"community":"public"}"#;
440 let auth: Auth = serde_json::from_str(json).unwrap();
441
442 match auth {
443 Auth::Community { version, community } => {
444 assert_eq!(version, CommunityVersion::V2c);
445 assert_eq!(community, "public");
446 }
447 _ => panic!("expected Community variant"),
448 }
449 }
450
451 #[test]
452 fn test_usm_optional_fields_not_serialized_when_none() {
453 let auth: Auth = Auth::usm("readonly").into();
454 let json = serde_json::to_string(&auth).unwrap();
455
456 assert!(json.contains("username"));
458 assert!(!json.contains("auth_protocol"));
459 assert!(!json.contains("auth_password"));
460 assert!(!json.contains("priv_protocol"));
461 assert!(!json.contains("priv_password"));
462 assert!(!json.contains("context_name"));
463 }
464
465 #[test]
466 fn test_walk_mode_roundtrip() {
467 use crate::client::walk::WalkMode;
468
469 let modes = [WalkMode::Auto, WalkMode::GetNext, WalkMode::GetBulk];
470
471 for mode in modes {
472 let json = serde_json::to_string(&mode).unwrap();
473 let back: WalkMode = serde_json::from_str(&json).unwrap();
474 assert_eq!(back, mode);
475 }
476 }
477
478 #[test]
479 fn test_oid_ordering_roundtrip() {
480 use crate::client::walk::OidOrdering;
481
482 let orderings = [OidOrdering::Strict, OidOrdering::AllowNonIncreasing];
483
484 for ordering in orderings {
485 let json = serde_json::to_string(&ordering).unwrap();
486 let back: OidOrdering = serde_json::from_str(&json).unwrap();
487 assert_eq!(back, ordering);
488 }
489 }
490
491 #[test]
492 fn test_auth_protocol_roundtrip() {
493 let protocols = [
494 AuthProtocol::Md5,
495 AuthProtocol::Sha1,
496 AuthProtocol::Sha224,
497 AuthProtocol::Sha256,
498 AuthProtocol::Sha384,
499 AuthProtocol::Sha512,
500 ];
501
502 for proto in protocols {
503 let json = serde_json::to_string(&proto).unwrap();
504 let back: AuthProtocol = serde_json::from_str(&json).unwrap();
505 assert_eq!(back, proto);
506 }
507 }
508
509 #[test]
510 fn test_priv_protocol_roundtrip() {
511 let protocols = [
512 PrivProtocol::Des,
513 PrivProtocol::Aes128,
514 PrivProtocol::Aes192,
515 PrivProtocol::Aes256,
516 ];
517
518 for proto in protocols {
519 let json = serde_json::to_string(&proto).unwrap();
520 let back: PrivProtocol = serde_json::from_str(&json).unwrap();
521 assert_eq!(back, proto);
522 }
523 }
524}