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 {
58 Auth::v2c("public")
59 }
60}
61
62impl Auth {
63 pub fn v1(community: impl Into<String>) -> Self {
77 Auth::Community {
78 version: CommunityVersion::V1,
79 community: community.into(),
80 }
81 }
82
83 pub fn v2c(community: impl Into<String>) -> Self {
101 Auth::Community {
102 version: CommunityVersion::V2c,
103 community: community.into(),
104 }
105 }
106
107 pub fn usm(username: impl Into<String>) -> UsmBuilder {
135 UsmBuilder::new(username)
136 }
137}
138
139#[derive(Debug, Clone)]
141#[cfg_attr(feature = "serde", derive(serde::Serialize, serde::Deserialize))]
142pub struct UsmAuth {
143 pub username: String,
145 #[cfg_attr(
147 feature = "serde",
148 serde(default, skip_serializing_if = "Option::is_none")
149 )]
150 pub auth_protocol: Option<AuthProtocol>,
151 #[cfg_attr(
153 feature = "serde",
154 serde(default, skip_serializing_if = "Option::is_none")
155 )]
156 pub auth_password: Option<String>,
157 #[cfg_attr(
159 feature = "serde",
160 serde(default, skip_serializing_if = "Option::is_none")
161 )]
162 pub priv_protocol: Option<PrivProtocol>,
163 #[cfg_attr(
165 feature = "serde",
166 serde(default, skip_serializing_if = "Option::is_none")
167 )]
168 pub priv_password: Option<String>,
169 #[cfg_attr(
172 feature = "serde",
173 serde(default, skip_serializing_if = "Option::is_none")
174 )]
175 pub context_name: Option<String>,
176 #[cfg_attr(feature = "serde", serde(skip))]
179 pub master_keys: Option<MasterKeys>,
180}
181
182pub struct UsmBuilder {
184 username: String,
185 auth: Option<(AuthProtocol, String)>,
186 privacy: Option<(PrivProtocol, String)>,
187 context_name: Option<String>,
188 master_keys: Option<MasterKeys>,
189}
190
191impl UsmBuilder {
192 pub fn new(username: impl Into<String>) -> Self {
202 Self {
203 username: username.into(),
204 auth: None,
205 privacy: None,
206 context_name: None,
207 master_keys: None,
208 }
209 }
210
211 pub fn auth(mut self, protocol: AuthProtocol, password: impl Into<String>) -> Self {
236 self.auth = Some((protocol, password.into()));
237 self
238 }
239
240 pub fn privacy(mut self, protocol: PrivProtocol, password: impl Into<String>) -> Self {
262 self.privacy = Some((protocol, password.into()));
263 self
264 }
265
266 pub fn with_master_keys(mut self, master_keys: MasterKeys) -> Self {
291 self.master_keys = Some(master_keys);
292 self
293 }
294
295 pub fn context_name(mut self, name: impl Into<String>) -> Self {
311 self.context_name = Some(name.into());
312 self
313 }
314}
315
316impl From<UsmBuilder> for Auth {
317 fn from(b: UsmBuilder) -> Auth {
318 Auth::Usm(UsmAuth {
319 username: b.username,
320 auth_protocol: b
321 .master_keys
322 .as_ref()
323 .map(|m| m.auth_protocol())
324 .or(b.auth.as_ref().map(|(p, _)| *p)),
325 auth_password: b.auth.map(|(_, pw)| pw),
326 priv_protocol: b
327 .master_keys
328 .as_ref()
329 .and_then(|m| m.priv_protocol())
330 .or(b.privacy.as_ref().map(|(p, _)| *p)),
331 priv_password: b.privacy.map(|(_, pw)| pw),
332 context_name: b.context_name,
333 master_keys: b.master_keys,
334 })
335 }
336}
337
338#[cfg(test)]
339mod tests {
340 use super::*;
341
342 #[test]
343 fn test_default_auth() {
344 let auth = Auth::default();
345 match auth {
346 Auth::Community { version, community } => {
347 assert_eq!(version, CommunityVersion::V2c);
348 assert_eq!(community, "public");
349 }
350 _ => panic!("expected Community variant"),
351 }
352 }
353
354 #[test]
355 fn test_v1_auth() {
356 let auth = Auth::v1("private");
357 match auth {
358 Auth::Community { version, community } => {
359 assert_eq!(version, CommunityVersion::V1);
360 assert_eq!(community, "private");
361 }
362 _ => panic!("expected Community variant"),
363 }
364 }
365
366 #[test]
367 fn test_v2c_auth() {
368 let auth = Auth::v2c("secret");
369 match auth {
370 Auth::Community { version, community } => {
371 assert_eq!(version, CommunityVersion::V2c);
372 assert_eq!(community, "secret");
373 }
374 _ => panic!("expected Community variant"),
375 }
376 }
377
378 #[test]
379 fn test_community_version_default() {
380 let version = CommunityVersion::default();
381 assert_eq!(version, CommunityVersion::V2c);
382 }
383
384 #[test]
385 fn test_usm_no_auth_no_priv() {
386 let auth: Auth = Auth::usm("readonly").into();
387 match auth {
388 Auth::Usm(usm) => {
389 assert_eq!(usm.username, "readonly");
390 assert!(usm.auth_protocol.is_none());
391 assert!(usm.auth_password.is_none());
392 assert!(usm.priv_protocol.is_none());
393 assert!(usm.priv_password.is_none());
394 assert!(usm.context_name.is_none());
395 }
396 _ => panic!("expected Usm variant"),
397 }
398 }
399
400 #[test]
401 fn test_usm_auth_no_priv() {
402 let auth: Auth = Auth::usm("admin")
403 .auth(AuthProtocol::Sha256, "authpass123")
404 .into();
405 match auth {
406 Auth::Usm(usm) => {
407 assert_eq!(usm.username, "admin");
408 assert_eq!(usm.auth_protocol, Some(AuthProtocol::Sha256));
409 assert_eq!(usm.auth_password, Some("authpass123".to_string()));
410 assert!(usm.priv_protocol.is_none());
411 assert!(usm.priv_password.is_none());
412 }
413 _ => panic!("expected Usm variant"),
414 }
415 }
416
417 #[test]
418 fn test_usm_auth_priv() {
419 let auth: Auth = Auth::usm("admin")
420 .auth(AuthProtocol::Sha256, "authpass")
421 .privacy(PrivProtocol::Aes128, "privpass")
422 .into();
423 match auth {
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 }
431 _ => panic!("expected Usm variant"),
432 }
433 }
434
435 #[test]
436 fn test_usm_with_context_name() {
437 let auth: Auth = Auth::usm("admin")
438 .auth(AuthProtocol::Sha256, "authpass")
439 .context_name("vlan100")
440 .into();
441 match auth {
442 Auth::Usm(usm) => {
443 assert_eq!(usm.username, "admin");
444 assert_eq!(usm.context_name, Some("vlan100".to_string()));
445 }
446 _ => panic!("expected Usm variant"),
447 }
448 }
449
450 #[test]
451 fn test_usm_builder_chaining() {
452 let auth: Auth = Auth::usm("user")
454 .auth(AuthProtocol::Sha512, "auth")
455 .privacy(PrivProtocol::Aes256, "priv")
456 .context_name("ctx")
457 .into();
458
459 match auth {
460 Auth::Usm(usm) => {
461 assert_eq!(usm.username, "user");
462 assert_eq!(usm.auth_protocol, Some(AuthProtocol::Sha512));
463 assert_eq!(usm.auth_password, Some("auth".to_string()));
464 assert_eq!(usm.priv_protocol, Some(PrivProtocol::Aes256));
465 assert_eq!(usm.priv_password, Some("priv".to_string()));
466 assert_eq!(usm.context_name, Some("ctx".to_string()));
467 }
468 _ => panic!("expected Usm variant"),
469 }
470 }
471}
472
473#[cfg(all(test, feature = "serde"))]
474mod serde_tests {
475 use super::*;
476
477 #[test]
478 fn test_community_v2c_roundtrip() {
479 let auth = Auth::v2c("public");
480 let json = serde_json::to_string(&auth).unwrap();
481 let back: Auth = serde_json::from_str(&json).unwrap();
482
483 match back {
484 Auth::Community { version, community } => {
485 assert_eq!(version, CommunityVersion::V2c);
486 assert_eq!(community, "public");
487 }
488 _ => panic!("expected Community variant"),
489 }
490 }
491
492 #[test]
493 fn test_community_v1_roundtrip() {
494 let auth = Auth::v1("private");
495 let json = serde_json::to_string(&auth).unwrap();
496 let back: Auth = serde_json::from_str(&json).unwrap();
497
498 match back {
499 Auth::Community { version, community } => {
500 assert_eq!(version, CommunityVersion::V1);
501 assert_eq!(community, "private");
502 }
503 _ => panic!("expected Community variant"),
504 }
505 }
506
507 #[test]
508 fn test_usm_no_auth_roundtrip() {
509 let auth: Auth = Auth::usm("readonly").into();
510 let json = serde_json::to_string(&auth).unwrap();
511 let back: Auth = serde_json::from_str(&json).unwrap();
512
513 match back {
514 Auth::Usm(usm) => {
515 assert_eq!(usm.username, "readonly");
516 assert!(usm.auth_protocol.is_none());
517 assert!(usm.auth_password.is_none());
518 assert!(usm.priv_protocol.is_none());
519 assert!(usm.priv_password.is_none());
520 }
521 _ => panic!("expected Usm variant"),
522 }
523 }
524
525 #[test]
526 fn test_usm_auth_priv_roundtrip() {
527 let auth: Auth = Auth::usm("admin")
528 .auth(AuthProtocol::Sha256, "authpass")
529 .privacy(PrivProtocol::Aes128, "privpass")
530 .context_name("vlan100")
531 .into();
532
533 let json = serde_json::to_string(&auth).unwrap();
534 let back: Auth = serde_json::from_str(&json).unwrap();
535
536 match back {
537 Auth::Usm(usm) => {
538 assert_eq!(usm.username, "admin");
539 assert_eq!(usm.auth_protocol, Some(AuthProtocol::Sha256));
540 assert_eq!(usm.auth_password, Some("authpass".to_string()));
541 assert_eq!(usm.priv_protocol, Some(PrivProtocol::Aes128));
542 assert_eq!(usm.priv_password, Some("privpass".to_string()));
543 assert_eq!(usm.context_name, Some("vlan100".to_string()));
544 }
545 _ => panic!("expected Usm variant"),
546 }
547 }
548
549 #[test]
550 fn test_community_deserialize_without_version() {
551 let json = r#"{"community":"public"}"#;
553 let auth: Auth = serde_json::from_str(json).unwrap();
554
555 match auth {
556 Auth::Community { version, community } => {
557 assert_eq!(version, CommunityVersion::V2c);
558 assert_eq!(community, "public");
559 }
560 _ => panic!("expected Community variant"),
561 }
562 }
563
564 #[test]
565 fn test_usm_optional_fields_not_serialized_when_none() {
566 let auth: Auth = Auth::usm("readonly").into();
567 let json = serde_json::to_string(&auth).unwrap();
568
569 assert!(json.contains("username"));
571 assert!(!json.contains("auth_protocol"));
572 assert!(!json.contains("auth_password"));
573 assert!(!json.contains("priv_protocol"));
574 assert!(!json.contains("priv_password"));
575 assert!(!json.contains("context_name"));
576 }
577
578 #[test]
579 fn test_walk_mode_roundtrip() {
580 use crate::client::walk::WalkMode;
581
582 let modes = [WalkMode::Auto, WalkMode::GetNext, WalkMode::GetBulk];
583
584 for mode in modes {
585 let json = serde_json::to_string(&mode).unwrap();
586 let back: WalkMode = serde_json::from_str(&json).unwrap();
587 assert_eq!(back, mode);
588 }
589 }
590
591 #[test]
592 fn test_oid_ordering_roundtrip() {
593 use crate::client::walk::OidOrdering;
594
595 let orderings = [OidOrdering::Strict, OidOrdering::AllowNonIncreasing];
596
597 for ordering in orderings {
598 let json = serde_json::to_string(&ordering).unwrap();
599 let back: OidOrdering = serde_json::from_str(&json).unwrap();
600 assert_eq!(back, ordering);
601 }
602 }
603
604 #[test]
605 fn test_auth_protocol_roundtrip() {
606 let protocols = [
607 AuthProtocol::Md5,
608 AuthProtocol::Sha1,
609 AuthProtocol::Sha224,
610 AuthProtocol::Sha256,
611 AuthProtocol::Sha384,
612 AuthProtocol::Sha512,
613 ];
614
615 for proto in protocols {
616 let json = serde_json::to_string(&proto).unwrap();
617 let back: AuthProtocol = serde_json::from_str(&json).unwrap();
618 assert_eq!(back, proto);
619 }
620 }
621
622 #[test]
623 fn test_priv_protocol_roundtrip() {
624 let protocols = [
625 PrivProtocol::Des,
626 PrivProtocol::Aes128,
627 PrivProtocol::Aes192,
628 PrivProtocol::Aes256,
629 ];
630
631 for proto in protocols {
632 let json = serde_json::to_string(&proto).unwrap();
633 let back: PrivProtocol = serde_json::from_str(&json).unwrap();
634 assert_eq!(back, proto);
635 }
636 }
637}