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