Skip to main content

async_snmp/cli/
args.rs

1//! Command-line argument structures for async-snmp CLI tools.
2//!
3//! This module provides reusable clap argument structures for the `asnmp-*` CLI tools.
4
5use clap::{Parser, ValueEnum};
6use std::time::Duration;
7
8use crate::Version;
9use crate::client::{Auth, Backoff, Retry};
10use crate::format::hex;
11use crate::v3::{AuthProtocol, PrivProtocol};
12
13/// SNMP version for CLI argument parsing.
14#[derive(Debug, Clone, Copy, Default, ValueEnum)]
15pub enum SnmpVersion {
16    /// SNMPv1
17    #[value(name = "1")]
18    V1,
19    /// SNMPv2c (default)
20    #[default]
21    #[value(name = "2c")]
22    V2c,
23    /// SNMPv3
24    #[value(name = "3")]
25    V3,
26}
27
28impl From<SnmpVersion> for Version {
29    fn from(v: SnmpVersion) -> Self {
30        match v {
31            SnmpVersion::V1 => Version::V1,
32            SnmpVersion::V2c => Version::V2c,
33            SnmpVersion::V3 => Version::V3,
34        }
35    }
36}
37
38/// Output format for CLI tools.
39#[derive(Debug, Clone, Copy, Default, ValueEnum)]
40pub enum OutputFormat {
41    /// Human-readable output with type information.
42    #[default]
43    Human,
44    /// JSON output for scripting.
45    Json,
46    /// Raw tab-separated output for scripting.
47    Raw,
48}
49
50/// Backoff strategy for CLI argument parsing.
51#[derive(Debug, Clone, Copy, Default, ValueEnum)]
52pub enum BackoffStrategy {
53    /// No delay between retries (immediate retry on timeout).
54    #[default]
55    None,
56    /// Fixed delay between each retry.
57    Fixed,
58    /// Exponential backoff: delay doubles after each attempt.
59    Exponential,
60}
61
62/// Common arguments shared across all CLI tools.
63#[derive(Debug, Parser)]
64pub struct CommonArgs {
65    /// Target host or host:port (default port 161).
66    #[arg(value_name = "TARGET")]
67    pub target: String,
68
69    /// SNMP version: 1, 2c, or 3.
70    #[arg(short = 'v', long = "snmp-version", default_value = "2c")]
71    pub snmp_version: SnmpVersion,
72
73    /// Community string (v1/v2c).
74    #[arg(short = 'c', long = "community", default_value = "public")]
75    pub community: String,
76
77    /// Request timeout in seconds.
78    #[arg(short = 't', long = "timeout", default_value = "5")]
79    pub timeout: f64,
80
81    /// Retry count.
82    #[arg(short = 'r', long = "retries", default_value = "3")]
83    pub retries: u32,
84
85    /// Backoff strategy between retries: none, fixed, or exponential.
86    #[arg(long = "backoff", default_value = "none")]
87    pub backoff: BackoffStrategy,
88
89    /// Backoff delay in milliseconds (initial delay for exponential, fixed delay otherwise).
90    #[arg(long = "backoff-delay", default_value = "1000")]
91    pub backoff_delay: u64,
92
93    /// Maximum backoff delay in milliseconds (exponential only).
94    #[arg(long = "backoff-max", default_value = "5000")]
95    pub backoff_max: u64,
96
97    /// Jitter factor for exponential backoff (0.0-1.0, e.g., 0.25 means +/-25%).
98    #[arg(long = "backoff-jitter", default_value = "0.25")]
99    pub backoff_jitter: f64,
100}
101
102impl CommonArgs {
103    /// Get the timeout as a Duration.
104    pub fn timeout_duration(&self) -> Duration {
105        Duration::from_secs_f64(self.timeout)
106    }
107
108    /// Resolve the effective SNMP version, upgrading to V3 when a username is set.
109    pub fn effective_version(&self, v3: &V3Args) -> SnmpVersion {
110        if v3.is_v3() {
111            SnmpVersion::V3
112        } else {
113            self.snmp_version
114        }
115    }
116
117    /// Build a Retry configuration from the CLI arguments.
118    pub fn retry_config(&self) -> Retry {
119        let backoff = match self.backoff {
120            BackoffStrategy::None => Backoff::None,
121            BackoffStrategy::Fixed => Backoff::Fixed {
122                delay: Duration::from_millis(self.backoff_delay),
123            },
124            BackoffStrategy::Exponential => Backoff::Exponential {
125                initial: Duration::from_millis(self.backoff_delay),
126                max: Duration::from_millis(self.backoff_max),
127                jitter: self.backoff_jitter.clamp(0.0, 1.0),
128            },
129        };
130        Retry {
131            max_attempts: self.retries,
132            backoff,
133        }
134    }
135}
136
137/// SNMPv3 security arguments.
138#[derive(Debug, Parser)]
139pub struct V3Args {
140    /// Security name/username (implies -v 3).
141    #[arg(short = 'u', long = "username")]
142    pub username: Option<String>,
143
144    /// Authentication protocol: MD5, SHA, SHA-224, SHA-256, SHA-384, SHA-512.
145    #[arg(short = 'a', long = "auth-protocol")]
146    pub auth_protocol: Option<AuthProtocol>,
147
148    /// Authentication passphrase.
149    #[arg(short = 'A', long = "auth-password")]
150    pub auth_password: Option<String>,
151
152    /// Privacy protocol: DES, AES, AES-128, AES-192, AES-256.
153    #[arg(short = 'x', long = "priv-protocol")]
154    pub priv_protocol: Option<PrivProtocol>,
155
156    /// Privacy passphrase.
157    #[arg(short = 'X', long = "priv-password")]
158    pub priv_password: Option<String>,
159}
160
161impl V3Args {
162    /// Check if V3 mode is enabled (username provided).
163    pub fn is_v3(&self) -> bool {
164        self.username.is_some()
165    }
166
167    /// Build an Auth configuration from the V3 args and common args.
168    ///
169    /// If a username is provided, builds a USM auth configuration.
170    /// Otherwise, builds a community auth based on the version and community from common args.
171    pub fn auth(&self, common: &CommonArgs) -> Result<Auth, String> {
172        if let Some(ref username) = self.username {
173            if self.auth_protocol.is_none() && self.auth_password.is_some() {
174                return Err("authentication protocol required when using auth password".into());
175            }
176            if self.priv_protocol.is_none() && self.priv_password.is_some() {
177                return Err("privacy protocol required when using priv password".into());
178            }
179
180            let config = match (
181                self.auth_protocol,
182                self.auth_password.as_deref(),
183                self.priv_protocol,
184                self.priv_password.as_deref(),
185            ) {
186                (None, _, None, _) => Auth::usm(username),
187                (Some(auth_protocol), Some(auth_password), None, _) => {
188                    Auth::usm(username).auth(auth_protocol, auth_password)
189                }
190                (
191                    Some(auth_protocol),
192                    Some(auth_password),
193                    Some(priv_protocol),
194                    Some(priv_password),
195                ) => Auth::usm(username).auth_priv(
196                    auth_protocol,
197                    auth_password,
198                    priv_protocol,
199                    priv_password,
200                ),
201                (None, _, Some(_), _) => {
202                    return Err("authentication protocol required when using privacy".into());
203                }
204                (Some(_), None, _, _) => return Err("auth password required".into()),
205                (Some(_), Some(_), Some(_), None) => {
206                    return Err("priv password required".into());
207                }
208            };
209            Ok(config.into())
210        } else {
211            let community = &common.community;
212            Ok(match common.snmp_version {
213                SnmpVersion::V1 => Auth::v1(community),
214                _ => Auth::v2c(community),
215            })
216        }
217    }
218
219    /// Validate V3 arguments and return an error message if invalid.
220    pub fn validate(&self) -> Result<(), String> {
221        if let Some(ref _username) = self.username {
222            if self.auth_protocol.is_none() && self.auth_password.is_some() {
223                return Err(
224                    "authentication protocol (-a) required when using auth password".into(),
225                );
226            }
227
228            if self.priv_protocol.is_none() && self.priv_password.is_some() {
229                return Err("privacy protocol (-x) required when using priv password".into());
230            }
231
232            // If auth-protocol is specified, auth-password is required
233            if self.auth_protocol.is_some() && self.auth_password.is_none() {
234                return Err(
235                    "authentication password (-A) required when using auth protocol".into(),
236                );
237            }
238
239            // If priv-protocol is specified, priv-password is required
240            if self.priv_protocol.is_some() && self.priv_password.is_none() {
241                return Err("privacy password (-X) required when using priv protocol".into());
242            }
243
244            // Privacy requires authentication
245            if self.priv_protocol.is_some() && self.auth_protocol.is_none() {
246                return Err("authentication protocol (-a) required when using privacy".into());
247            }
248        }
249        Ok(())
250    }
251}
252
253/// Output control arguments.
254#[derive(Debug, Parser)]
255pub struct OutputArgs {
256    /// Output format: human, json, or raw.
257    #[arg(short = 'O', long = "output", default_value = "human")]
258    pub format: OutputFormat,
259
260    /// Show PDU structure and wire details.
261    #[arg(long = "verbose")]
262    pub verbose: bool,
263
264    /// Always display OctetString as hex.
265    #[arg(long = "hex")]
266    pub hex: bool,
267
268    /// Show request timing.
269    #[arg(long = "timing")]
270    pub timing: bool,
271
272    /// Disable well-known OID name hints.
273    #[arg(long = "no-hints")]
274    pub no_hints: bool,
275
276    /// Enable debug logging (async_snmp=debug).
277    #[arg(short = 'd', long = "debug")]
278    pub debug: bool,
279
280    /// Enable trace logging (async_snmp=trace).
281    #[arg(short = 'D', long = "trace")]
282    pub trace: bool,
283}
284
285impl OutputArgs {
286    /// Return elapsed as Some if timing output is enabled, None otherwise.
287    pub fn elapsed(&self, elapsed: Duration) -> Option<Duration> {
288        if self.timing { Some(elapsed) } else { None }
289    }
290
291    /// Initialize tracing based on debug/trace flags.
292    ///
293    /// Note: --verbose is handled separately and shows structured request/response info.
294    /// Use -d/--debug for library-level tracing.
295    pub fn init_tracing(&self) {
296        use tracing_subscriber::EnvFilter;
297
298        let filter = if self.trace {
299            "async_snmp=trace"
300        } else if self.debug {
301            "async_snmp=debug"
302        } else {
303            "async_snmp=warn"
304        };
305
306        let _ = tracing_subscriber::fmt()
307            .with_env_filter(EnvFilter::new(filter))
308            .with_writer(std::io::stderr)
309            .try_init();
310    }
311}
312
313/// Walk-specific arguments.
314#[derive(Debug, Parser)]
315pub struct WalkArgs {
316    /// Use GETNEXT instead of GETBULK.
317    #[arg(long = "getnext")]
318    pub getnext: bool,
319
320    /// GETBULK max-repetitions.
321    #[arg(long = "max-rep", default_value = "10")]
322    pub max_repetitions: u32,
323}
324
325/// Set-specific type specifier for values.
326#[derive(Debug, Clone, Copy, PartialEq, Eq, ValueEnum)]
327pub enum ValueType {
328    /// INTEGER (i32)
329    #[value(name = "i")]
330    Integer,
331    /// Unsigned32/Gauge32 (u32)
332    #[value(name = "u")]
333    Unsigned,
334    /// STRING (OctetString from UTF-8)
335    #[value(name = "s")]
336    String,
337    /// Hex-STRING (OctetString from hex)
338    #[value(name = "x")]
339    HexString,
340    /// OBJECT IDENTIFIER
341    #[value(name = "o")]
342    Oid,
343    /// IpAddress
344    #[value(name = "a")]
345    IpAddress,
346    /// TimeTicks
347    #[value(name = "t")]
348    TimeTicks,
349    /// Counter32
350    #[value(name = "c")]
351    Counter32,
352    /// Counter64
353    #[value(name = "C")]
354    Counter64,
355}
356
357impl std::str::FromStr for ValueType {
358    type Err = String;
359
360    fn from_str(s: &str) -> Result<Self, Self::Err> {
361        match s {
362            "i" => Ok(ValueType::Integer),
363            "u" => Ok(ValueType::Unsigned),
364            "s" => Ok(ValueType::String),
365            "x" => Ok(ValueType::HexString),
366            "o" => Ok(ValueType::Oid),
367            "a" => Ok(ValueType::IpAddress),
368            "t" => Ok(ValueType::TimeTicks),
369            "c" => Ok(ValueType::Counter32),
370            "C" => Ok(ValueType::Counter64),
371            _ => Err(format!("invalid type specifier: {}", s)),
372        }
373    }
374}
375
376impl ValueType {
377    /// Parse a string value into an SNMP Value according to the type specifier.
378    pub fn parse_value(&self, s: &str) -> Result<crate::Value, String> {
379        use crate::{Oid, Value};
380
381        match self {
382            ValueType::Integer => {
383                let v: i32 = s
384                    .parse()
385                    .map_err(|_| format!("invalid integer value: {}", s))?;
386                Ok(Value::Integer(v))
387            }
388            ValueType::Unsigned => {
389                let v: u32 = s
390                    .parse()
391                    .map_err(|_| format!("invalid unsigned value: {}", s))?;
392                Ok(Value::Gauge32(v))
393            }
394            ValueType::String => Ok(Value::OctetString(s.as_bytes().to_vec().into())),
395            ValueType::HexString => {
396                let bytes = hex::decode_relaxed(s)
397                    .map_err(|_| "hex string must have even number of hex digits".to_string())?;
398                Ok(Value::OctetString(bytes.into()))
399            }
400            ValueType::Oid => {
401                let oid = Oid::parse(s).map_err(|e| format!("invalid OID value: {}", e))?;
402                Ok(Value::ObjectIdentifier(oid))
403            }
404            ValueType::IpAddress => {
405                let parts: Vec<&str> = s.split('.').collect();
406                if parts.len() != 4 {
407                    return Err(format!("invalid IP address: {}", s));
408                }
409                let mut bytes = [0u8; 4];
410                for (i, part) in parts.iter().enumerate() {
411                    bytes[i] = part
412                        .parse()
413                        .map_err(|_| format!("invalid IP address octet: {}", part))?;
414                }
415                Ok(Value::IpAddress(bytes))
416            }
417            ValueType::TimeTicks => {
418                let v: u32 = s
419                    .parse()
420                    .map_err(|_| format!("invalid timeticks value: {}", s))?;
421                Ok(Value::TimeTicks(v))
422            }
423            ValueType::Counter32 => {
424                let v: u32 = s
425                    .parse()
426                    .map_err(|_| format!("invalid counter32 value: {}", s))?;
427                Ok(Value::Counter32(v))
428            }
429            ValueType::Counter64 => {
430                let v: u64 = s
431                    .parse()
432                    .map_err(|_| format!("invalid counter64 value: {}", s))?;
433                Ok(Value::Counter64(v))
434            }
435        }
436    }
437}
438
439#[cfg(test)]
440mod tests {
441    use super::*;
442
443    fn common_args() -> CommonArgs {
444        CommonArgs {
445            target: "192.168.1.1".to_string(),
446            snmp_version: SnmpVersion::V3,
447            community: "public".to_string(),
448            timeout: 5.0,
449            retries: 3,
450            backoff: BackoffStrategy::None,
451            backoff_delay: 100,
452            backoff_max: 5000,
453            backoff_jitter: 0.25,
454        }
455    }
456
457    #[test]
458    fn test_retry_config_none() {
459        let args = CommonArgs {
460            target: "192.168.1.1".to_string(),
461            snmp_version: SnmpVersion::V2c,
462            community: "public".to_string(),
463            timeout: 5.0,
464            retries: 3,
465            backoff: BackoffStrategy::None,
466            backoff_delay: 100,
467            backoff_max: 5000,
468            backoff_jitter: 0.25,
469        };
470        let retry = args.retry_config();
471        assert_eq!(retry.max_attempts, 3);
472        assert!(matches!(retry.backoff, Backoff::None));
473    }
474
475    #[test]
476    fn test_retry_config_fixed() {
477        let args = CommonArgs {
478            target: "192.168.1.1".to_string(),
479            snmp_version: SnmpVersion::V2c,
480            community: "public".to_string(),
481            timeout: 5.0,
482            retries: 5,
483            backoff: BackoffStrategy::Fixed,
484            backoff_delay: 200,
485            backoff_max: 5000,
486            backoff_jitter: 0.25,
487        };
488        let retry = args.retry_config();
489        assert_eq!(retry.max_attempts, 5);
490        assert!(matches!(
491            retry.backoff,
492            Backoff::Fixed { delay } if delay == Duration::from_millis(200)
493        ));
494    }
495
496    #[test]
497    fn test_retry_config_exponential() {
498        let args = CommonArgs {
499            target: "192.168.1.1".to_string(),
500            snmp_version: SnmpVersion::V2c,
501            community: "public".to_string(),
502            timeout: 5.0,
503            retries: 4,
504            backoff: BackoffStrategy::Exponential,
505            backoff_delay: 50,
506            backoff_max: 2000,
507            backoff_jitter: 0.1,
508        };
509        let retry = args.retry_config();
510        assert_eq!(retry.max_attempts, 4);
511        match retry.backoff {
512            Backoff::Exponential {
513                initial,
514                max,
515                jitter,
516            } => {
517                assert_eq!(initial, Duration::from_millis(50));
518                assert_eq!(max, Duration::from_millis(2000));
519                assert!((jitter - 0.1).abs() < f64::EPSILON);
520            }
521            _ => panic!("expected Exponential"),
522        }
523    }
524
525    #[test]
526    fn test_v3_args_validation() {
527        // No username - valid (not v3)
528        let args = V3Args {
529            username: None,
530            auth_protocol: None,
531            auth_password: None,
532            priv_protocol: None,
533            priv_password: None,
534        };
535        assert!(args.validate().is_ok());
536
537        // Username only - valid (noAuthNoPriv)
538        let args = V3Args {
539            username: Some("admin".to_string()),
540            auth_protocol: None,
541            auth_password: None,
542            priv_protocol: None,
543            priv_password: None,
544        };
545        assert!(args.validate().is_ok());
546
547        // Auth password without protocol - invalid
548        let args = V3Args {
549            username: Some("admin".to_string()),
550            auth_protocol: None,
551            auth_password: Some("pass".to_string()),
552            priv_protocol: None,
553            priv_password: None,
554        };
555        assert!(args.validate().is_err());
556        assert!(args.auth(&common_args()).is_err());
557
558        // Auth protocol without password - invalid
559        let args = V3Args {
560            username: Some("admin".to_string()),
561            auth_protocol: Some(AuthProtocol::Sha256),
562            auth_password: None,
563            priv_protocol: None,
564            priv_password: None,
565        };
566        assert!(args.validate().is_err());
567        assert!(args.auth(&common_args()).is_err());
568
569        // Privacy password without protocol - invalid
570        let args = V3Args {
571            username: Some("admin".to_string()),
572            auth_protocol: Some(AuthProtocol::Sha256),
573            auth_password: Some("authpass".to_string()),
574            priv_protocol: None,
575            priv_password: Some("privpass".to_string()),
576        };
577        assert!(args.validate().is_err());
578        assert!(args.auth(&common_args()).is_err());
579
580        // Privacy protocol without password - invalid
581        let args = V3Args {
582            username: Some("admin".to_string()),
583            auth_protocol: Some(AuthProtocol::Sha256),
584            auth_password: Some("authpass".to_string()),
585            priv_protocol: Some(PrivProtocol::Aes128),
586            priv_password: None,
587        };
588        assert!(args.validate().is_err());
589        assert!(args.auth(&common_args()).is_err());
590
591        // Privacy without auth - invalid
592        let args = V3Args {
593            username: Some("admin".to_string()),
594            auth_protocol: None,
595            auth_password: None,
596            priv_protocol: Some(PrivProtocol::Aes128),
597            priv_password: Some("pass".to_string()),
598        };
599        assert!(args.validate().is_err());
600        assert!(args.auth(&common_args()).is_err());
601
602        // SHA-1 with AES-256 - valid (key extension auto-applied)
603        let args = V3Args {
604            username: Some("admin".to_string()),
605            auth_protocol: Some(AuthProtocol::Sha1),
606            auth_password: Some("pass".to_string()),
607            priv_protocol: Some(PrivProtocol::Aes256),
608            priv_password: Some("pass".to_string()),
609        };
610        assert!(args.validate().is_ok());
611    }
612
613    #[test]
614    fn test_v3_args_auth_constructs_valid_security_levels() {
615        let no_auth = V3Args {
616            username: Some("user".to_string()),
617            auth_protocol: None,
618            auth_password: None,
619            priv_protocol: None,
620            priv_password: None,
621        }
622        .auth(&common_args())
623        .unwrap();
624        let Auth::Usm(no_auth) = no_auth else {
625            panic!("expected USM config");
626        };
627        assert_eq!(no_auth.auth_protocol(), None);
628        assert_eq!(no_auth.priv_protocol(), None);
629
630        let auth = V3Args {
631            username: Some("user".to_string()),
632            auth_protocol: Some(AuthProtocol::Sha256),
633            auth_password: Some("authpass".to_string()),
634            priv_protocol: None,
635            priv_password: None,
636        }
637        .auth(&common_args())
638        .unwrap();
639        let Auth::Usm(auth) = auth else {
640            panic!("expected USM config");
641        };
642        assert_eq!(auth.auth_protocol(), Some(AuthProtocol::Sha256));
643        assert_eq!(auth.priv_protocol(), None);
644
645        let auth_priv = V3Args {
646            username: Some("user".to_string()),
647            auth_protocol: Some(AuthProtocol::Sha1),
648            auth_password: Some("authpass".to_string()),
649            priv_protocol: Some(PrivProtocol::Aes128),
650            priv_password: Some("privpass".to_string()),
651        }
652        .auth(&common_args())
653        .unwrap();
654        let Auth::Usm(auth_priv) = auth_priv else {
655            panic!("expected USM config");
656        };
657        assert_eq!(auth_priv.auth_protocol(), Some(AuthProtocol::Sha1));
658        assert_eq!(auth_priv.priv_protocol(), Some(PrivProtocol::Aes128));
659    }
660
661    #[test]
662    fn test_value_type_parse_integer() {
663        use crate::Value;
664        let v = ValueType::Integer.parse_value("42").unwrap();
665        assert!(matches!(v, Value::Integer(42)));
666
667        let v = ValueType::Integer.parse_value("-100").unwrap();
668        assert!(matches!(v, Value::Integer(-100)));
669
670        assert!(ValueType::Integer.parse_value("not_a_number").is_err());
671    }
672
673    #[test]
674    fn test_value_type_parse_unsigned() {
675        use crate::Value;
676        let v = ValueType::Unsigned.parse_value("42").unwrap();
677        assert!(matches!(v, Value::Gauge32(42)));
678
679        assert!(ValueType::Unsigned.parse_value("-1").is_err());
680    }
681
682    #[test]
683    fn test_value_type_parse_string() {
684        use crate::Value;
685        let v = ValueType::String.parse_value("hello world").unwrap();
686        if let Value::OctetString(bytes) = v {
687            assert_eq!(&*bytes, b"hello world");
688        } else {
689            panic!("expected OctetString");
690        }
691    }
692
693    #[test]
694    fn test_value_type_parse_hex_string() {
695        use crate::Value;
696
697        // Plain hex
698        let v = ValueType::HexString.parse_value("001a2b").unwrap();
699        if let Value::OctetString(bytes) = v {
700            assert_eq!(&*bytes, &[0x00, 0x1a, 0x2b]);
701        } else {
702            panic!("expected OctetString");
703        }
704
705        // With spaces
706        let v = ValueType::HexString.parse_value("00 1A 2B").unwrap();
707        if let Value::OctetString(bytes) = v {
708            assert_eq!(&*bytes, &[0x00, 0x1a, 0x2b]);
709        } else {
710            panic!("expected OctetString");
711        }
712
713        // Odd number of digits
714        assert!(ValueType::HexString.parse_value("001").is_err());
715    }
716
717    #[test]
718    fn test_value_type_parse_ip_address() {
719        use crate::Value;
720        let v = ValueType::IpAddress.parse_value("192.168.1.1").unwrap();
721        assert!(matches!(v, Value::IpAddress([192, 168, 1, 1])));
722
723        assert!(ValueType::IpAddress.parse_value("192.168.1").is_err());
724        assert!(ValueType::IpAddress.parse_value("256.1.1.1").is_err());
725    }
726
727    #[test]
728    fn test_value_type_parse_timeticks() {
729        use crate::Value;
730        let v = ValueType::TimeTicks.parse_value("12345678").unwrap();
731        assert!(matches!(v, Value::TimeTicks(12345678)));
732    }
733
734    #[test]
735    fn test_value_type_parse_counters() {
736        use crate::Value;
737
738        let v = ValueType::Counter32.parse_value("4294967295").unwrap();
739        assert!(matches!(v, Value::Counter32(4294967295)));
740
741        let v = ValueType::Counter64
742            .parse_value("18446744073709551615")
743            .unwrap();
744        assert!(matches!(v, Value::Counter64(18446744073709551615)));
745    }
746}