async_snmp/
value.rs

1//! SNMP value types.
2//!
3//! The `Value` enum represents all SNMP data types including exceptions.
4
5use crate::ber::{Decoder, EncodeBuf, tag};
6use crate::error::internal::DecodeErrorKind;
7use crate::error::{Error, Result, UNKNOWN_TARGET};
8use crate::format::hex;
9use crate::oid::Oid;
10use bytes::Bytes;
11
12/// RFC 2579 RowStatus textual convention.
13///
14/// Used by SNMP tables to control row creation, modification, and deletion.
15/// The state machine for RowStatus is defined in RFC 2579 Section 7.1.
16///
17/// # State Transitions
18///
19/// | Current State | Set to | Result |
20/// |--------------|--------|--------|
21/// | (none) | createAndGo | row created in `active` state |
22/// | (none) | createAndWait | row created in `notInService` or `notReady` |
23/// | notInService | active | row becomes operational |
24/// | notReady | active | error (row must first be notInService) |
25/// | active | notInService | row becomes inactive |
26/// | any | destroy | row is deleted |
27///
28/// # Example
29///
30/// ```
31/// use async_snmp::{Value, RowStatus};
32///
33/// // Reading a RowStatus column
34/// let value = Value::Integer(1);
35/// assert_eq!(value.as_row_status(), Some(RowStatus::Active));
36///
37/// // Creating a value to write
38/// let create: Value = RowStatus::CreateAndGo.into();
39/// assert_eq!(create, Value::Integer(4));
40/// ```
41#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash)]
42pub enum RowStatus {
43    /// Row is operational and available for use.
44    Active = 1,
45    /// Row exists but is not operational (e.g., being modified).
46    NotInService = 2,
47    /// Row exists but required columns are missing or invalid.
48    NotReady = 3,
49    /// Request to create a new row that immediately becomes active.
50    CreateAndGo = 4,
51    /// Request to create a new row that starts in notInService/notReady.
52    CreateAndWait = 5,
53    /// Request to delete an existing row.
54    Destroy = 6,
55}
56
57impl RowStatus {
58    /// Convert an integer value to RowStatus.
59    ///
60    /// Returns `None` for values outside the valid range (1-6).
61    pub fn from_i32(value: i32) -> Option<Self> {
62        match value {
63            1 => Some(Self::Active),
64            2 => Some(Self::NotInService),
65            3 => Some(Self::NotReady),
66            4 => Some(Self::CreateAndGo),
67            5 => Some(Self::CreateAndWait),
68            6 => Some(Self::Destroy),
69            _ => None,
70        }
71    }
72}
73
74impl From<RowStatus> for Value {
75    fn from(status: RowStatus) -> Self {
76        Value::Integer(status as i32)
77    }
78}
79
80impl std::fmt::Display for RowStatus {
81    fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
82        match self {
83            Self::Active => write!(f, "active"),
84            Self::NotInService => write!(f, "notInService"),
85            Self::NotReady => write!(f, "notReady"),
86            Self::CreateAndGo => write!(f, "createAndGo"),
87            Self::CreateAndWait => write!(f, "createAndWait"),
88            Self::Destroy => write!(f, "destroy"),
89        }
90    }
91}
92
93/// RFC 2579 StorageType textual convention.
94///
95/// Describes how an SNMP row's data is stored and persisted.
96///
97/// # Persistence Levels
98///
99/// | Type | Survives Reboot | Writable |
100/// |------|-----------------|----------|
101/// | other | undefined | varies |
102/// | volatile | no | yes |
103/// | nonVolatile | yes | yes |
104/// | permanent | yes | limited |
105/// | readOnly | yes | no |
106///
107/// # Example
108///
109/// ```
110/// use async_snmp::{Value, StorageType};
111///
112/// // Reading a StorageType column
113/// let value = Value::Integer(3);
114/// assert_eq!(value.as_storage_type(), Some(StorageType::NonVolatile));
115///
116/// // Creating a value to write
117/// let storage: Value = StorageType::Volatile.into();
118/// assert_eq!(storage, Value::Integer(2));
119/// ```
120#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash)]
121pub enum StorageType {
122    /// Implementation-specific storage.
123    Other = 1,
124    /// Lost on reboot; can be modified.
125    Volatile = 2,
126    /// Survives reboot; can be modified.
127    NonVolatile = 3,
128    /// Survives reboot; limited modifications allowed.
129    Permanent = 4,
130    /// Survives reboot; cannot be modified.
131    ReadOnly = 5,
132}
133
134impl StorageType {
135    /// Convert an integer value to StorageType.
136    ///
137    /// Returns `None` for values outside the valid range (1-5).
138    pub fn from_i32(value: i32) -> Option<Self> {
139        match value {
140            1 => Some(Self::Other),
141            2 => Some(Self::Volatile),
142            3 => Some(Self::NonVolatile),
143            4 => Some(Self::Permanent),
144            5 => Some(Self::ReadOnly),
145            _ => None,
146        }
147    }
148}
149
150impl From<StorageType> for Value {
151    fn from(storage: StorageType) -> Self {
152        Value::Integer(storage as i32)
153    }
154}
155
156impl std::fmt::Display for StorageType {
157    fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
158        match self {
159            Self::Other => write!(f, "other"),
160            Self::Volatile => write!(f, "volatile"),
161            Self::NonVolatile => write!(f, "nonVolatile"),
162            Self::Permanent => write!(f, "permanent"),
163            Self::ReadOnly => write!(f, "readOnly"),
164        }
165    }
166}
167
168/// SNMP value.
169///
170/// Represents all SNMP data types including SMIv2 types and exception values.
171#[derive(Debug, Clone, PartialEq, Eq, Hash)]
172#[non_exhaustive]
173pub enum Value {
174    /// INTEGER (ASN.1 primitive, signed 32-bit)
175    Integer(i32),
176
177    /// OCTET STRING (arbitrary bytes).
178    ///
179    /// Per RFC 2578 (SMIv2), OCTET STRING values have a maximum size of 65535 octets.
180    /// This limit is **not enforced** during decoding to maintain permissive parsing
181    /// behavior. Applications that require strict compliance should validate size
182    /// after decoding.
183    OctetString(Bytes),
184
185    /// NULL
186    Null,
187
188    /// OBJECT IDENTIFIER
189    ObjectIdentifier(Oid),
190
191    /// IpAddress (4 bytes, big-endian)
192    IpAddress([u8; 4]),
193
194    /// Counter32 (unsigned 32-bit, wrapping)
195    Counter32(u32),
196
197    /// Gauge32 / Unsigned32 (unsigned 32-bit, non-wrapping)
198    Gauge32(u32),
199
200    /// TimeTicks (hundredths of seconds since epoch)
201    TimeTicks(u32),
202
203    /// Opaque (legacy, arbitrary bytes)
204    Opaque(Bytes),
205
206    /// Counter64 (unsigned 64-bit, wrapping).
207    ///
208    /// **SNMPv2c/v3 only.** Counter64 was introduced in SNMPv2 (RFC 2578) and is
209    /// not supported in SNMPv1. When sending Counter64 values to an SNMPv1 agent,
210    /// the value will be silently ignored or cause an error depending on the agent
211    /// implementation.
212    ///
213    /// If your application needs to support SNMPv1, avoid using Counter64 or
214    /// fall back to Counter32 (with potential overflow for high-bandwidth counters).
215    Counter64(u64),
216
217    /// noSuchObject exception - the requested OID exists in the MIB but has no value.
218    ///
219    /// This exception indicates that the agent recognizes the OID (it's a valid
220    /// MIB object), but there is no instance available. This commonly occurs when
221    /// requesting a table column OID without an index.
222    ///
223    /// # Example
224    ///
225    /// ```
226    /// use async_snmp::Value;
227    ///
228    /// let response = Value::NoSuchObject;
229    /// assert!(response.is_exception());
230    ///
231    /// // When handling responses, check for exceptions:
232    /// match response {
233    ///     Value::NoSuchObject => println!("OID exists but has no value"),
234    ///     _ => {}
235    /// }
236    /// ```
237    NoSuchObject,
238
239    /// noSuchInstance exception - the specific instance does not exist.
240    ///
241    /// This exception indicates that while the MIB object exists, the specific
242    /// instance (index) requested does not. This commonly occurs when querying
243    /// a table row that doesn't exist.
244    ///
245    /// # Example
246    ///
247    /// ```
248    /// use async_snmp::Value;
249    ///
250    /// let response = Value::NoSuchInstance;
251    /// assert!(response.is_exception());
252    /// ```
253    NoSuchInstance,
254
255    /// endOfMibView exception - end of the MIB has been reached.
256    ///
257    /// This exception is returned during GETNEXT/GETBULK operations when
258    /// there are no more OIDs lexicographically greater than the requested OID.
259    /// This is the normal termination condition for SNMP walks.
260    ///
261    /// # Example
262    ///
263    /// ```
264    /// use async_snmp::Value;
265    ///
266    /// let response = Value::EndOfMibView;
267    /// assert!(response.is_exception());
268    ///
269    /// // Commonly used to detect end of walk
270    /// if matches!(response, Value::EndOfMibView) {
271    ///     println!("Walk complete - reached end of MIB");
272    /// }
273    /// ```
274    EndOfMibView,
275
276    /// Unknown/unrecognized value type (for forward compatibility)
277    Unknown { tag: u8, data: Bytes },
278}
279
280impl Value {
281    /// Try to get as i32.
282    ///
283    /// Returns `Some(i32)` for [`Value::Integer`], `None` otherwise.
284    ///
285    /// # Examples
286    ///
287    /// ```
288    /// use async_snmp::Value;
289    ///
290    /// let v = Value::Integer(42);
291    /// assert_eq!(v.as_i32(), Some(42));
292    ///
293    /// let v = Value::Integer(-100);
294    /// assert_eq!(v.as_i32(), Some(-100));
295    ///
296    /// // Counter32 is not an Integer
297    /// let v = Value::Counter32(42);
298    /// assert_eq!(v.as_i32(), None);
299    /// ```
300    pub fn as_i32(&self) -> Option<i32> {
301        match self {
302            Value::Integer(v) => Some(*v),
303            _ => None,
304        }
305    }
306
307    /// Try to get as u32.
308    ///
309    /// Returns `Some(u32)` for [`Value::Counter32`], [`Value::Gauge32`],
310    /// [`Value::TimeTicks`], or non-negative [`Value::Integer`]. Returns `None` otherwise.
311    ///
312    /// # Examples
313    ///
314    /// ```
315    /// use async_snmp::Value;
316    ///
317    /// // Works for Counter32, Gauge32, TimeTicks
318    /// assert_eq!(Value::Counter32(100).as_u32(), Some(100));
319    /// assert_eq!(Value::Gauge32(200).as_u32(), Some(200));
320    /// assert_eq!(Value::TimeTicks(300).as_u32(), Some(300));
321    ///
322    /// // Works for non-negative integers
323    /// assert_eq!(Value::Integer(50).as_u32(), Some(50));
324    ///
325    /// // Returns None for negative integers
326    /// assert_eq!(Value::Integer(-1).as_u32(), None);
327    ///
328    /// // Counter64 returns None (use as_u64 instead)
329    /// assert_eq!(Value::Counter64(100).as_u32(), None);
330    /// ```
331    pub fn as_u32(&self) -> Option<u32> {
332        match self {
333            Value::Counter32(v) | Value::Gauge32(v) | Value::TimeTicks(v) => Some(*v),
334            Value::Integer(v) if *v >= 0 => Some(*v as u32),
335            _ => None,
336        }
337    }
338
339    /// Try to get as u64.
340    ///
341    /// Returns `Some(u64)` for [`Value::Counter64`], or any 32-bit unsigned type
342    /// ([`Value::Counter32`], [`Value::Gauge32`], [`Value::TimeTicks`]), or
343    /// non-negative [`Value::Integer`]. Returns `None` otherwise.
344    ///
345    /// # Examples
346    ///
347    /// ```
348    /// use async_snmp::Value;
349    ///
350    /// // Counter64 is the primary use case
351    /// assert_eq!(Value::Counter64(10_000_000_000).as_u64(), Some(10_000_000_000));
352    ///
353    /// // Also works for 32-bit unsigned types
354    /// assert_eq!(Value::Counter32(100).as_u64(), Some(100));
355    /// assert_eq!(Value::Gauge32(200).as_u64(), Some(200));
356    ///
357    /// // Non-negative integers work
358    /// assert_eq!(Value::Integer(50).as_u64(), Some(50));
359    ///
360    /// // Negative integers return None
361    /// assert_eq!(Value::Integer(-1).as_u64(), None);
362    /// ```
363    pub fn as_u64(&self) -> Option<u64> {
364        match self {
365            Value::Counter64(v) => Some(*v),
366            Value::Counter32(v) | Value::Gauge32(v) | Value::TimeTicks(v) => Some(*v as u64),
367            Value::Integer(v) if *v >= 0 => Some(*v as u64),
368            _ => None,
369        }
370    }
371
372    /// Try to get as bytes.
373    ///
374    /// Returns `Some(&[u8])` for [`Value::OctetString`] or [`Value::Opaque`].
375    /// Returns `None` otherwise.
376    ///
377    /// # Examples
378    ///
379    /// ```
380    /// use async_snmp::Value;
381    /// use bytes::Bytes;
382    ///
383    /// let v = Value::OctetString(Bytes::from_static(b"hello"));
384    /// assert_eq!(v.as_bytes(), Some(b"hello".as_slice()));
385    ///
386    /// // Works for Opaque too
387    /// let v = Value::Opaque(Bytes::from_static(&[0xDE, 0xAD, 0xBE, 0xEF]));
388    /// assert_eq!(v.as_bytes(), Some(&[0xDE, 0xAD, 0xBE, 0xEF][..]));
389    ///
390    /// // Other types return None
391    /// assert_eq!(Value::Integer(42).as_bytes(), None);
392    /// ```
393    pub fn as_bytes(&self) -> Option<&[u8]> {
394        match self {
395            Value::OctetString(v) | Value::Opaque(v) => Some(v),
396            _ => None,
397        }
398    }
399
400    /// Try to get as string (UTF-8).
401    ///
402    /// Returns `Some(&str)` if the value is an [`Value::OctetString`] or [`Value::Opaque`]
403    /// containing valid UTF-8. Returns `None` for other types or invalid UTF-8.
404    ///
405    /// # Examples
406    ///
407    /// ```
408    /// use async_snmp::Value;
409    /// use bytes::Bytes;
410    ///
411    /// let v = Value::OctetString(Bytes::from_static(b"Linux router1 5.4.0"));
412    /// assert_eq!(v.as_str(), Some("Linux router1 5.4.0"));
413    ///
414    /// // Invalid UTF-8 returns None
415    /// let v = Value::OctetString(Bytes::from_static(&[0xFF, 0xFE]));
416    /// assert_eq!(v.as_str(), None);
417    ///
418    /// // Binary data with valid UTF-8 bytes still works, but use as_bytes() for clarity
419    /// let binary = Value::OctetString(Bytes::from_static(&[0x80, 0x81, 0x82]));
420    /// assert_eq!(binary.as_str(), None); // Invalid UTF-8 sequence
421    /// assert!(binary.as_bytes().is_some());
422    /// ```
423    pub fn as_str(&self) -> Option<&str> {
424        self.as_bytes().and_then(|b| std::str::from_utf8(b).ok())
425    }
426
427    /// Try to get as OID.
428    ///
429    /// Returns `Some(&Oid)` for [`Value::ObjectIdentifier`], `None` otherwise.
430    ///
431    /// # Examples
432    ///
433    /// ```
434    /// use async_snmp::{Value, oid};
435    ///
436    /// let v = Value::ObjectIdentifier(oid!(1, 3, 6, 1, 2, 1, 1, 2, 0));
437    /// let oid = v.as_oid().unwrap();
438    /// assert_eq!(oid.to_string(), "1.3.6.1.2.1.1.2.0");
439    ///
440    /// // Other types return None
441    /// assert_eq!(Value::Integer(42).as_oid(), None);
442    /// ```
443    pub fn as_oid(&self) -> Option<&Oid> {
444        match self {
445            Value::ObjectIdentifier(oid) => Some(oid),
446            _ => None,
447        }
448    }
449
450    /// Try to get as IP address.
451    ///
452    /// Returns `Some(Ipv4Addr)` for [`Value::IpAddress`], `None` otherwise.
453    ///
454    /// # Examples
455    ///
456    /// ```
457    /// use async_snmp::Value;
458    /// use std::net::Ipv4Addr;
459    ///
460    /// let v = Value::IpAddress([192, 168, 1, 1]);
461    /// assert_eq!(v.as_ip(), Some(Ipv4Addr::new(192, 168, 1, 1)));
462    ///
463    /// // Other types return None
464    /// assert_eq!(Value::Integer(42).as_ip(), None);
465    /// ```
466    pub fn as_ip(&self) -> Option<std::net::Ipv4Addr> {
467        match self {
468            Value::IpAddress(bytes) => Some(std::net::Ipv4Addr::from(*bytes)),
469            _ => None,
470        }
471    }
472
473    /// Extract any numeric value as f64.
474    ///
475    /// Useful for metrics systems and graphing where all values become f64.
476    /// Counter64 values above 2^53 may lose precision.
477    ///
478    /// # Examples
479    ///
480    /// ```
481    /// use async_snmp::Value;
482    ///
483    /// assert_eq!(Value::Integer(42).as_f64(), Some(42.0));
484    /// assert_eq!(Value::Counter32(1000).as_f64(), Some(1000.0));
485    /// assert_eq!(Value::Counter64(10_000_000_000).as_f64(), Some(10_000_000_000.0));
486    /// assert_eq!(Value::Null.as_f64(), None);
487    /// ```
488    pub fn as_f64(&self) -> Option<f64> {
489        match self {
490            Value::Integer(v) => Some(*v as f64),
491            Value::Counter32(v) | Value::Gauge32(v) | Value::TimeTicks(v) => Some(*v as f64),
492            Value::Counter64(v) => Some(*v as f64),
493            _ => None,
494        }
495    }
496
497    /// Extract Counter64 as f64 with wrapping at 2^53.
498    ///
499    /// Prevents precision loss for large counters. IEEE 754 double-precision
500    /// floats have a 53-bit mantissa, so Counter64 values above 2^53 lose
501    /// precision when converted directly. This method wraps at the mantissa
502    /// limit, preserving precision for rate calculations.
503    ///
504    /// Use when computing rates where precision matters more than absolute
505    /// magnitude. For Counter32 and other types, behaves identically to `as_f64()`.
506    ///
507    /// # Examples
508    ///
509    /// ```
510    /// use async_snmp::Value;
511    ///
512    /// // Small values behave the same as as_f64()
513    /// assert_eq!(Value::Counter64(1000).as_f64_wrapped(), Some(1000.0));
514    ///
515    /// // Large Counter64 wraps at 2^53
516    /// let large = 1u64 << 54; // 2^54
517    /// let wrapped = Value::Counter64(large).as_f64_wrapped().unwrap();
518    /// assert!(wrapped < large as f64); // Wrapped to smaller value
519    /// ```
520    pub fn as_f64_wrapped(&self) -> Option<f64> {
521        const MANTISSA_LIMIT: u64 = 1 << 53;
522        match self {
523            Value::Counter64(v) => Some((*v % MANTISSA_LIMIT) as f64),
524            _ => self.as_f64(),
525        }
526    }
527
528    /// Extract integer with implied decimal places.
529    ///
530    /// Many SNMP sensors report fixed-point values as integers with an
531    /// implied decimal point. This method applies the scaling directly,
532    /// returning a usable f64 value.
533    ///
534    /// This complements `format_with_hint("d-2")` which returns a String
535    /// for display. Use `as_decimal()` when you need the numeric value
536    /// for computation or metrics.
537    ///
538    /// # Examples
539    ///
540    /// ```
541    /// use async_snmp::Value;
542    ///
543    /// // Temperature 2350 with places=2 → 23.50
544    /// assert_eq!(Value::Integer(2350).as_decimal(2), Some(23.50));
545    ///
546    /// // Percentage 9999 with places=2 → 99.99
547    /// assert_eq!(Value::Integer(9999).as_decimal(2), Some(99.99));
548    ///
549    /// // Voltage 12500 with places=3 → 12.500
550    /// assert_eq!(Value::Integer(12500).as_decimal(3), Some(12.5));
551    ///
552    /// // Non-numeric types return None
553    /// assert_eq!(Value::Null.as_decimal(2), None);
554    /// ```
555    pub fn as_decimal(&self, places: u8) -> Option<f64> {
556        let divisor = 10f64.powi(places as i32);
557        self.as_f64().map(|v| v / divisor)
558    }
559
560    /// TimeTicks as Duration (hundredths of seconds).
561    ///
562    /// TimeTicks represents time in hundredths of a second. This method
563    /// converts to `std::time::Duration` for idiomatic Rust time handling.
564    ///
565    /// Common use: sysUpTime, interface last-change timestamps.
566    ///
567    /// # Examples
568    ///
569    /// ```
570    /// use async_snmp::Value;
571    /// use std::time::Duration;
572    ///
573    /// // 360000 ticks = 3600 seconds = 1 hour
574    /// let uptime = Value::TimeTicks(360000);
575    /// assert_eq!(uptime.as_duration(), Some(Duration::from_secs(3600)));
576    ///
577    /// // Non-TimeTicks return None
578    /// assert_eq!(Value::Integer(100).as_duration(), None);
579    /// ```
580    pub fn as_duration(&self) -> Option<std::time::Duration> {
581        match self {
582            Value::TimeTicks(v) => Some(std::time::Duration::from_millis(*v as u64 * 10)),
583            _ => None,
584        }
585    }
586
587    /// Extract IEEE 754 float from Opaque value (net-snmp extension).
588    ///
589    /// Decodes the two-layer ASN.1 structure used by net-snmp to encode floats
590    /// inside Opaque values: extension tag (0x9f) + float type (0x78) + length (4)
591    /// + 4 bytes IEEE 754 big-endian float.
592    ///
593    /// This is a non-standard extension supported by net-snmp for agents that
594    /// need to report floating-point values. Standard SNMP uses implied decimal
595    /// points via DISPLAY-HINT instead.
596    ///
597    /// # Examples
598    ///
599    /// ```
600    /// use async_snmp::Value;
601    /// use bytes::Bytes;
602    ///
603    /// // Opaque-encoded float for pi
604    /// let data = Bytes::from_static(&[0x9f, 0x78, 0x04, 0x40, 0x49, 0x0f, 0xdb]);
605    /// let value = Value::Opaque(data);
606    /// let pi = value.as_opaque_float().unwrap();
607    /// assert!((pi - std::f32::consts::PI).abs() < 0.0001);
608    ///
609    /// // Non-Opaque or wrong format returns None
610    /// assert_eq!(Value::Integer(42).as_opaque_float(), None);
611    /// ```
612    pub fn as_opaque_float(&self) -> Option<f32> {
613        match self {
614            Value::Opaque(data)
615                if data.len() >= 7
616                && data[0] == 0x9f       // ASN_OPAQUE_TAG1 (extension)
617                && data[1] == 0x78       // ASN_OPAQUE_FLOAT
618                && data[2] == 0x04 =>
619            {
620                // length = 4
621                let bytes: [u8; 4] = data[3..7].try_into().ok()?;
622                Some(f32::from_be_bytes(bytes))
623            }
624            _ => None,
625        }
626    }
627
628    /// Extract IEEE 754 double from Opaque value (net-snmp extension).
629    ///
630    /// Decodes the two-layer ASN.1 structure used by net-snmp to encode doubles
631    /// inside Opaque values: extension tag (0x9f) + double type (0x79) + length (8)
632    /// + 8 bytes IEEE 754 big-endian double.
633    ///
634    /// # Examples
635    ///
636    /// ```
637    /// use async_snmp::Value;
638    /// use bytes::Bytes;
639    ///
640    /// // Opaque-encoded double for pi ≈ 3.141592653589793
641    /// let data = Bytes::from_static(&[
642    ///     0x9f, 0x79, 0x08,  // extension tag, double type, length
643    ///     0x40, 0x09, 0x21, 0xfb, 0x54, 0x44, 0x2d, 0x18  // IEEE 754 double
644    /// ]);
645    /// let value = Value::Opaque(data);
646    /// let pi = value.as_opaque_double().unwrap();
647    /// assert!((pi - std::f64::consts::PI).abs() < 1e-10);
648    /// ```
649    pub fn as_opaque_double(&self) -> Option<f64> {
650        match self {
651            Value::Opaque(data)
652                if data.len() >= 11
653                && data[0] == 0x9f       // ASN_OPAQUE_TAG1 (extension)
654                && data[1] == 0x79       // ASN_OPAQUE_DOUBLE
655                && data[2] == 0x08 =>
656            {
657                // length = 8
658                let bytes: [u8; 8] = data[3..11].try_into().ok()?;
659                Some(f64::from_be_bytes(bytes))
660            }
661            _ => None,
662        }
663    }
664
665    /// Extract Counter64 from Opaque value (net-snmp extension for SNMPv1).
666    ///
667    /// SNMPv1 doesn't support Counter64 natively. net-snmp encodes 64-bit
668    /// counters inside Opaque for SNMPv1 compatibility using extension tag
669    /// (0x9f) + counter64 type (0x76) + length + big-endian bytes.
670    ///
671    /// # Examples
672    ///
673    /// ```
674    /// use async_snmp::Value;
675    /// use bytes::Bytes;
676    ///
677    /// // Opaque-encoded Counter64 with value 0x0123456789ABCDEF
678    /// let data = Bytes::from_static(&[
679    ///     0x9f, 0x76, 0x08,  // extension tag, counter64 type, length
680    ///     0x01, 0x23, 0x45, 0x67, 0x89, 0xAB, 0xCD, 0xEF
681    /// ]);
682    /// let value = Value::Opaque(data);
683    /// assert_eq!(value.as_opaque_counter64(), Some(0x0123456789ABCDEF));
684    /// ```
685    pub fn as_opaque_counter64(&self) -> Option<u64> {
686        self.as_opaque_unsigned(0x76)
687    }
688
689    /// Extract signed 64-bit integer from Opaque value (net-snmp extension).
690    ///
691    /// Uses extension tag (0x9f) + i64 type (0x7a) + length + big-endian bytes.
692    ///
693    /// # Examples
694    ///
695    /// ```
696    /// use async_snmp::Value;
697    /// use bytes::Bytes;
698    ///
699    /// // Opaque-encoded I64 with value -1 (0xFFFFFFFFFFFFFFFF)
700    /// let data = Bytes::from_static(&[
701    ///     0x9f, 0x7a, 0x08,
702    ///     0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF
703    /// ]);
704    /// let value = Value::Opaque(data);
705    /// assert_eq!(value.as_opaque_i64(), Some(-1i64));
706    /// ```
707    pub fn as_opaque_i64(&self) -> Option<i64> {
708        match self {
709            Value::Opaque(data)
710                if data.len() >= 4
711                && data[0] == 0x9f       // ASN_OPAQUE_TAG1 (extension)
712                && data[1] == 0x7a =>
713            {
714                // ASN_OPAQUE_I64
715                let len = data[2] as usize;
716                if data.len() < 3 + len || len == 0 || len > 8 {
717                    return None;
718                }
719                let bytes = &data[3..3 + len];
720                // Sign-extend from the actual length
721                let is_negative = bytes[0] & 0x80 != 0;
722                let mut value: i64 = if is_negative { -1 } else { 0 };
723                for &byte in bytes {
724                    value = (value << 8) | (byte as i64);
725                }
726                Some(value)
727            }
728            _ => None,
729        }
730    }
731
732    /// Extract unsigned 64-bit integer from Opaque value (net-snmp extension).
733    ///
734    /// Uses extension tag (0x9f) + u64 type (0x7b) + length + big-endian bytes.
735    ///
736    /// # Examples
737    ///
738    /// ```
739    /// use async_snmp::Value;
740    /// use bytes::Bytes;
741    ///
742    /// // Opaque-encoded U64 with value 0x0123456789ABCDEF
743    /// let data = Bytes::from_static(&[
744    ///     0x9f, 0x7b, 0x08,
745    ///     0x01, 0x23, 0x45, 0x67, 0x89, 0xAB, 0xCD, 0xEF
746    /// ]);
747    /// let value = Value::Opaque(data);
748    /// assert_eq!(value.as_opaque_u64(), Some(0x0123456789ABCDEF));
749    /// ```
750    pub fn as_opaque_u64(&self) -> Option<u64> {
751        self.as_opaque_unsigned(0x7b)
752    }
753
754    /// Helper for extracting unsigned 64-bit values from Opaque (Counter64, U64).
755    fn as_opaque_unsigned(&self, expected_type: u8) -> Option<u64> {
756        match self {
757            Value::Opaque(data)
758                if data.len() >= 4
759                && data[0] == 0x9f           // ASN_OPAQUE_TAG1 (extension)
760                && data[1] == expected_type =>
761            {
762                let len = data[2] as usize;
763                if data.len() < 3 + len || len == 0 || len > 8 {
764                    return None;
765                }
766                let bytes = &data[3..3 + len];
767                let mut value: u64 = 0;
768                for &byte in bytes {
769                    value = (value << 8) | (byte as u64);
770                }
771                Some(value)
772            }
773            _ => None,
774        }
775    }
776
777    /// Extract RFC 2579 TruthValue as bool.
778    ///
779    /// TruthValue is an INTEGER with: true(1), false(2).
780    /// Returns `None` for non-Integer values or values outside {1, 2}.
781    ///
782    /// # Examples
783    ///
784    /// ```
785    /// use async_snmp::Value;
786    ///
787    /// assert_eq!(Value::Integer(1).as_truth_value(), Some(true));
788    /// assert_eq!(Value::Integer(2).as_truth_value(), Some(false));
789    ///
790    /// // Invalid values return None
791    /// assert_eq!(Value::Integer(0).as_truth_value(), None);
792    /// assert_eq!(Value::Integer(3).as_truth_value(), None);
793    /// assert_eq!(Value::Null.as_truth_value(), None);
794    /// ```
795    pub fn as_truth_value(&self) -> Option<bool> {
796        match self {
797            Value::Integer(1) => Some(true),
798            Value::Integer(2) => Some(false),
799            _ => None,
800        }
801    }
802
803    /// Extract RFC 2579 RowStatus.
804    ///
805    /// Returns `None` for non-Integer values or values outside {1-6}.
806    ///
807    /// # Examples
808    ///
809    /// ```
810    /// use async_snmp::{Value, RowStatus};
811    ///
812    /// assert_eq!(Value::Integer(1).as_row_status(), Some(RowStatus::Active));
813    /// assert_eq!(Value::Integer(6).as_row_status(), Some(RowStatus::Destroy));
814    ///
815    /// // Invalid values return None
816    /// assert_eq!(Value::Integer(0).as_row_status(), None);
817    /// assert_eq!(Value::Integer(7).as_row_status(), None);
818    /// assert_eq!(Value::Null.as_row_status(), None);
819    /// ```
820    pub fn as_row_status(&self) -> Option<RowStatus> {
821        match self {
822            Value::Integer(v) => RowStatus::from_i32(*v),
823            _ => None,
824        }
825    }
826
827    /// Extract RFC 2579 StorageType.
828    ///
829    /// Returns `None` for non-Integer values or values outside {1-5}.
830    ///
831    /// # Examples
832    ///
833    /// ```
834    /// use async_snmp::{Value, StorageType};
835    ///
836    /// assert_eq!(Value::Integer(2).as_storage_type(), Some(StorageType::Volatile));
837    /// assert_eq!(Value::Integer(3).as_storage_type(), Some(StorageType::NonVolatile));
838    ///
839    /// // Invalid values return None
840    /// assert_eq!(Value::Integer(0).as_storage_type(), None);
841    /// assert_eq!(Value::Integer(6).as_storage_type(), None);
842    /// assert_eq!(Value::Null.as_storage_type(), None);
843    /// ```
844    pub fn as_storage_type(&self) -> Option<StorageType> {
845        match self {
846            Value::Integer(v) => StorageType::from_i32(*v),
847            _ => None,
848        }
849    }
850
851    /// Check if this is an exception value.
852    pub fn is_exception(&self) -> bool {
853        matches!(
854            self,
855            Value::NoSuchObject | Value::NoSuchInstance | Value::EndOfMibView
856        )
857    }
858
859    /// Returns the total BER-encoded length (tag + length + content).
860    pub(crate) fn ber_encoded_len(&self) -> usize {
861        use crate::ber::{
862            integer_content_len, length_encoded_len, unsigned32_content_len, unsigned64_content_len,
863        };
864
865        match self {
866            Value::Integer(v) => {
867                let content_len = integer_content_len(*v);
868                1 + length_encoded_len(content_len) + content_len
869            }
870            Value::OctetString(data) => {
871                let content_len = data.len();
872                1 + length_encoded_len(content_len) + content_len
873            }
874            Value::Null => 2, // tag + length(0)
875            Value::ObjectIdentifier(oid) => oid.ber_encoded_len(),
876            Value::IpAddress(_) => 6, // tag + length(4) + 4 bytes
877            Value::Counter32(v) | Value::Gauge32(v) | Value::TimeTicks(v) => {
878                let content_len = unsigned32_content_len(*v);
879                1 + length_encoded_len(content_len) + content_len
880            }
881            Value::Opaque(data) => {
882                let content_len = data.len();
883                1 + length_encoded_len(content_len) + content_len
884            }
885            Value::Counter64(v) => {
886                let content_len = unsigned64_content_len(*v);
887                1 + length_encoded_len(content_len) + content_len
888            }
889            Value::NoSuchObject | Value::NoSuchInstance | Value::EndOfMibView => 2, // tag + length(0)
890            Value::Unknown { data, .. } => {
891                let content_len = data.len();
892                1 + length_encoded_len(content_len) + content_len
893            }
894        }
895    }
896
897    /// Format an OctetString, Opaque, or Integer value using RFC 2579 DISPLAY-HINT.
898    ///
899    /// For OctetString and Opaque, uses the OCTET STRING hint format (e.g., "1x:").
900    /// For Integer, uses the INTEGER hint format (e.g., "d-2" for decimal places).
901    ///
902    /// Returns `None` for other value types or invalid hint syntax.
903    /// On invalid OCTET STRING hint syntax, falls back to hex encoding.
904    ///
905    /// # Example
906    ///
907    /// ```
908    /// use async_snmp::Value;
909    /// use bytes::Bytes;
910    ///
911    /// // OctetString: MAC address
912    /// let mac = Value::OctetString(Bytes::from_static(&[0x00, 0x1a, 0x2b, 0x3c, 0x4d, 0x5e]));
913    /// assert_eq!(mac.format_with_hint("1x:"), Some("00:1a:2b:3c:4d:5e".into()));
914    ///
915    /// // Integer: Temperature with 2 decimal places
916    /// let temp = Value::Integer(2350);
917    /// assert_eq!(temp.format_with_hint("d-2"), Some("23.50".into()));
918    ///
919    /// // Integer: Hex format
920    /// let int = Value::Integer(255);
921    /// assert_eq!(int.format_with_hint("x"), Some("ff".into()));
922    /// ```
923    pub fn format_with_hint(&self, hint: &str) -> Option<String> {
924        match self {
925            Value::OctetString(bytes) => Some(crate::format::display_hint::apply(hint, bytes)),
926            Value::Opaque(bytes) => Some(crate::format::display_hint::apply(hint, bytes)),
927            Value::Integer(v) => crate::format::display_hint::apply_integer(hint, *v),
928            _ => None,
929        }
930    }
931
932    /// Encode to BER.
933    pub fn encode(&self, buf: &mut EncodeBuf) {
934        match self {
935            Value::Integer(v) => buf.push_integer(*v),
936            Value::OctetString(data) => buf.push_octet_string(data),
937            Value::Null => buf.push_null(),
938            Value::ObjectIdentifier(oid) => buf.push_oid(oid),
939            Value::IpAddress(addr) => buf.push_ip_address(*addr),
940            Value::Counter32(v) => buf.push_unsigned32(tag::application::COUNTER32, *v),
941            Value::Gauge32(v) => buf.push_unsigned32(tag::application::GAUGE32, *v),
942            Value::TimeTicks(v) => buf.push_unsigned32(tag::application::TIMETICKS, *v),
943            Value::Opaque(data) => {
944                buf.push_bytes(data);
945                buf.push_length(data.len());
946                buf.push_tag(tag::application::OPAQUE);
947            }
948            Value::Counter64(v) => buf.push_integer64(*v),
949            Value::NoSuchObject => {
950                buf.push_length(0);
951                buf.push_tag(tag::context::NO_SUCH_OBJECT);
952            }
953            Value::NoSuchInstance => {
954                buf.push_length(0);
955                buf.push_tag(tag::context::NO_SUCH_INSTANCE);
956            }
957            Value::EndOfMibView => {
958                buf.push_length(0);
959                buf.push_tag(tag::context::END_OF_MIB_VIEW);
960            }
961            Value::Unknown { tag: t, data } => {
962                buf.push_bytes(data);
963                buf.push_length(data.len());
964                buf.push_tag(*t);
965            }
966        }
967    }
968
969    /// Decode from BER.
970    pub fn decode(decoder: &mut Decoder) -> Result<Self> {
971        let tag = decoder.read_tag()?;
972        let len = decoder.read_length()?;
973
974        match tag {
975            tag::universal::INTEGER => {
976                let value = decoder.read_integer_value(len)?;
977                Ok(Value::Integer(value))
978            }
979            tag::universal::OCTET_STRING => {
980                let data = decoder.read_bytes(len)?;
981                Ok(Value::OctetString(data))
982            }
983            tag::universal::NULL => {
984                if len != 0 {
985                    tracing::debug!(target: "async_snmp::value", { offset = decoder.offset(), kind = %DecodeErrorKind::InvalidNull }, "decode error");
986                    return Err(Error::MalformedResponse {
987                        target: UNKNOWN_TARGET,
988                    }
989                    .boxed());
990                }
991                Ok(Value::Null)
992            }
993            tag::universal::OBJECT_IDENTIFIER => {
994                let oid = decoder.read_oid_value(len)?;
995                Ok(Value::ObjectIdentifier(oid))
996            }
997            tag::application::IP_ADDRESS => {
998                if len != 4 {
999                    tracing::debug!(target: "async_snmp::value", { offset = decoder.offset(), length = len, kind = %DecodeErrorKind::InvalidIpAddressLength { length: len } }, "decode error");
1000                    return Err(Error::MalformedResponse {
1001                        target: UNKNOWN_TARGET,
1002                    }
1003                    .boxed());
1004                }
1005                let data = decoder.read_bytes(4)?;
1006                Ok(Value::IpAddress([data[0], data[1], data[2], data[3]]))
1007            }
1008            tag::application::COUNTER32 => {
1009                let value = decoder.read_unsigned32_value(len)?;
1010                Ok(Value::Counter32(value))
1011            }
1012            tag::application::GAUGE32 => {
1013                let value = decoder.read_unsigned32_value(len)?;
1014                Ok(Value::Gauge32(value))
1015            }
1016            tag::application::TIMETICKS => {
1017                let value = decoder.read_unsigned32_value(len)?;
1018                Ok(Value::TimeTicks(value))
1019            }
1020            tag::application::OPAQUE => {
1021                let data = decoder.read_bytes(len)?;
1022                Ok(Value::Opaque(data))
1023            }
1024            tag::application::COUNTER64 => {
1025                let value = decoder.read_integer64_value(len)?;
1026                Ok(Value::Counter64(value))
1027            }
1028            tag::context::NO_SUCH_OBJECT => {
1029                if len != 0 {
1030                    let _ = decoder.read_bytes(len)?;
1031                }
1032                Ok(Value::NoSuchObject)
1033            }
1034            tag::context::NO_SUCH_INSTANCE => {
1035                if len != 0 {
1036                    let _ = decoder.read_bytes(len)?;
1037                }
1038                Ok(Value::NoSuchInstance)
1039            }
1040            tag::context::END_OF_MIB_VIEW => {
1041                if len != 0 {
1042                    let _ = decoder.read_bytes(len)?;
1043                }
1044                Ok(Value::EndOfMibView)
1045            }
1046            // Reject constructed OCTET STRING (0x24).
1047            // Net-snmp documents but does not parse constructed form; we follow suit.
1048            tag::universal::OCTET_STRING_CONSTRUCTED => {
1049                tracing::debug!(target: "async_snmp::value", { offset = decoder.offset(), kind = %DecodeErrorKind::ConstructedOctetString }, "decode error");
1050                Err(Error::MalformedResponse {
1051                    target: UNKNOWN_TARGET,
1052                }
1053                .boxed())
1054            }
1055            _ => {
1056                // Unknown tag - preserve for forward compatibility
1057                let data = decoder.read_bytes(len)?;
1058                Ok(Value::Unknown { tag, data })
1059            }
1060        }
1061    }
1062}
1063
1064impl std::fmt::Display for Value {
1065    fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
1066        match self {
1067            Value::Integer(v) => write!(f, "{}", v),
1068            Value::OctetString(data) => {
1069                // Try to display as string if it's valid UTF-8
1070                if let Ok(s) = std::str::from_utf8(data) {
1071                    write!(f, "{}", s)
1072                } else {
1073                    write!(f, "0x{}", hex::encode(data))
1074                }
1075            }
1076            Value::Null => write!(f, "NULL"),
1077            Value::ObjectIdentifier(oid) => write!(f, "{}", oid),
1078            Value::IpAddress(addr) => {
1079                write!(f, "{}.{}.{}.{}", addr[0], addr[1], addr[2], addr[3])
1080            }
1081            Value::Counter32(v) => write!(f, "{}", v),
1082            Value::Gauge32(v) => write!(f, "{}", v),
1083            Value::TimeTicks(v) => {
1084                // Display as time
1085                let secs = v / 100;
1086                let days = secs / 86400;
1087                let hours = (secs % 86400) / 3600;
1088                let mins = (secs % 3600) / 60;
1089                let s = secs % 60;
1090                write!(f, "{}d {}h {}m {}s", days, hours, mins, s)
1091            }
1092            Value::Opaque(data) => write!(f, "Opaque(0x{})", hex::encode(data)),
1093            Value::Counter64(v) => write!(f, "{}", v),
1094            Value::NoSuchObject => write!(f, "noSuchObject"),
1095            Value::NoSuchInstance => write!(f, "noSuchInstance"),
1096            Value::EndOfMibView => write!(f, "endOfMibView"),
1097            Value::Unknown { tag, data } => {
1098                write!(
1099                    f,
1100                    "Unknown(tag=0x{:02X}, data=0x{})",
1101                    tag,
1102                    hex::encode(data)
1103                )
1104            }
1105        }
1106    }
1107}
1108
1109/// Convenience conversions for creating [`Value`] from common Rust types.
1110///
1111/// # Examples
1112///
1113/// ```
1114/// use async_snmp::Value;
1115/// use bytes::Bytes;
1116///
1117/// // From integers
1118/// let v: Value = 42i32.into();
1119/// assert_eq!(v.as_i32(), Some(42));
1120///
1121/// // From strings (creates OctetString)
1122/// let v: Value = "hello".into();
1123/// assert_eq!(v.as_str(), Some("hello"));
1124///
1125/// // From String
1126/// let v: Value = String::from("world").into();
1127/// assert_eq!(v.as_str(), Some("world"));
1128///
1129/// // From byte slices
1130/// let v: Value = (&[1u8, 2, 3][..]).into();
1131/// assert_eq!(v.as_bytes(), Some(&[1, 2, 3][..]));
1132///
1133/// // From Bytes
1134/// let v: Value = Bytes::from_static(b"data").into();
1135/// assert_eq!(v.as_bytes(), Some(b"data".as_slice()));
1136///
1137/// // From u64 (creates Counter64)
1138/// let v: Value = 10_000_000_000u64.into();
1139/// assert_eq!(v.as_u64(), Some(10_000_000_000));
1140///
1141/// // From Ipv4Addr
1142/// use std::net::Ipv4Addr;
1143/// let v: Value = Ipv4Addr::new(10, 0, 0, 1).into();
1144/// assert_eq!(v.as_ip(), Some(Ipv4Addr::new(10, 0, 0, 1)));
1145///
1146/// // From [u8; 4] (creates IpAddress)
1147/// let v: Value = [192u8, 168, 1, 1].into();
1148/// assert!(matches!(v, Value::IpAddress([192, 168, 1, 1])));
1149/// ```
1150impl From<i32> for Value {
1151    fn from(v: i32) -> Self {
1152        Value::Integer(v)
1153    }
1154}
1155
1156impl From<&str> for Value {
1157    fn from(s: &str) -> Self {
1158        Value::OctetString(Bytes::copy_from_slice(s.as_bytes()))
1159    }
1160}
1161
1162impl From<String> for Value {
1163    fn from(s: String) -> Self {
1164        Value::OctetString(Bytes::from(s))
1165    }
1166}
1167
1168impl From<&[u8]> for Value {
1169    fn from(data: &[u8]) -> Self {
1170        Value::OctetString(Bytes::copy_from_slice(data))
1171    }
1172}
1173
1174impl From<Oid> for Value {
1175    fn from(oid: Oid) -> Self {
1176        Value::ObjectIdentifier(oid)
1177    }
1178}
1179
1180impl From<std::net::Ipv4Addr> for Value {
1181    fn from(addr: std::net::Ipv4Addr) -> Self {
1182        Value::IpAddress(addr.octets())
1183    }
1184}
1185
1186impl From<Bytes> for Value {
1187    fn from(data: Bytes) -> Self {
1188        Value::OctetString(data)
1189    }
1190}
1191
1192impl From<u64> for Value {
1193    fn from(v: u64) -> Self {
1194        Value::Counter64(v)
1195    }
1196}
1197
1198impl From<[u8; 4]> for Value {
1199    fn from(addr: [u8; 4]) -> Self {
1200        Value::IpAddress(addr)
1201    }
1202}
1203
1204#[cfg(test)]
1205mod tests {
1206    use super::*;
1207
1208    // AUDIT-003: Test that constructed OCTET STRING (0x24) is explicitly rejected.
1209    // Net-snmp documents but does not parse constructed form; we reject it.
1210    #[test]
1211    fn test_reject_constructed_octet_string() {
1212        // Constructed OCTET STRING has tag 0x24 (0x04 | 0x20)
1213        // Create a fake BER-encoded constructed OCTET STRING: 0x24 0x03 0x04 0x01 0x41
1214        // (constructed OCTET STRING containing primitive OCTET STRING "A")
1215        let data = bytes::Bytes::from_static(&[0x24, 0x03, 0x04, 0x01, 0x41]);
1216        let mut decoder = Decoder::new(data);
1217        let result = Value::decode(&mut decoder);
1218
1219        assert!(
1220            result.is_err(),
1221            "constructed OCTET STRING (0x24) should be rejected"
1222        );
1223        // Verify error is MalformedResponse (detailed error kind is logged via tracing)
1224        let err = result.unwrap_err();
1225        assert!(
1226            matches!(&*err, crate::Error::MalformedResponse { .. }),
1227            "expected MalformedResponse error, got: {:?}",
1228            err
1229        );
1230    }
1231
1232    #[test]
1233    fn test_primitive_octet_string_accepted() {
1234        // Primitive OCTET STRING (0x04) should be accepted
1235        let data = bytes::Bytes::from_static(&[0x04, 0x03, 0x41, 0x42, 0x43]); // "ABC"
1236        let mut decoder = Decoder::new(data);
1237        let result = Value::decode(&mut decoder);
1238
1239        assert!(result.is_ok(), "primitive OCTET STRING should be accepted");
1240        let value = result.unwrap();
1241        assert_eq!(value.as_bytes(), Some(&b"ABC"[..]));
1242    }
1243
1244    // ========================================================================
1245    // Value Type Encoding/Decoding Tests
1246    // ========================================================================
1247
1248    fn roundtrip(value: Value) -> Value {
1249        let mut buf = EncodeBuf::new();
1250        value.encode(&mut buf);
1251        let data = buf.finish();
1252        let mut decoder = Decoder::new(data);
1253        Value::decode(&mut decoder).unwrap()
1254    }
1255
1256    #[test]
1257    fn test_integer_positive() {
1258        let value = Value::Integer(42);
1259        assert_eq!(roundtrip(value.clone()), value);
1260    }
1261
1262    #[test]
1263    fn test_integer_negative() {
1264        let value = Value::Integer(-42);
1265        assert_eq!(roundtrip(value.clone()), value);
1266    }
1267
1268    #[test]
1269    fn test_integer_zero() {
1270        let value = Value::Integer(0);
1271        assert_eq!(roundtrip(value.clone()), value);
1272    }
1273
1274    #[test]
1275    fn test_integer_min() {
1276        let value = Value::Integer(i32::MIN);
1277        assert_eq!(roundtrip(value.clone()), value);
1278    }
1279
1280    #[test]
1281    fn test_integer_max() {
1282        let value = Value::Integer(i32::MAX);
1283        assert_eq!(roundtrip(value.clone()), value);
1284    }
1285
1286    #[test]
1287    fn test_octet_string_ascii() {
1288        let value = Value::OctetString(Bytes::from_static(b"hello world"));
1289        assert_eq!(roundtrip(value.clone()), value);
1290    }
1291
1292    #[test]
1293    fn test_octet_string_binary() {
1294        let value = Value::OctetString(Bytes::from_static(&[0x00, 0xFF, 0x80, 0x7F]));
1295        assert_eq!(roundtrip(value.clone()), value);
1296    }
1297
1298    #[test]
1299    fn test_octet_string_empty() {
1300        let value = Value::OctetString(Bytes::new());
1301        assert_eq!(roundtrip(value.clone()), value);
1302    }
1303
1304    #[test]
1305    fn test_null() {
1306        let value = Value::Null;
1307        assert_eq!(roundtrip(value.clone()), value);
1308    }
1309
1310    #[test]
1311    fn test_object_identifier() {
1312        let value = Value::ObjectIdentifier(crate::oid!(1, 3, 6, 1, 2, 1, 1, 1, 0));
1313        assert_eq!(roundtrip(value.clone()), value);
1314    }
1315
1316    #[test]
1317    fn test_ip_address() {
1318        let value = Value::IpAddress([192, 168, 1, 1]);
1319        assert_eq!(roundtrip(value.clone()), value);
1320    }
1321
1322    #[test]
1323    fn test_ip_address_zero() {
1324        let value = Value::IpAddress([0, 0, 0, 0]);
1325        assert_eq!(roundtrip(value.clone()), value);
1326    }
1327
1328    #[test]
1329    fn test_ip_address_broadcast() {
1330        let value = Value::IpAddress([255, 255, 255, 255]);
1331        assert_eq!(roundtrip(value.clone()), value);
1332    }
1333
1334    #[test]
1335    fn test_counter32() {
1336        let value = Value::Counter32(999999);
1337        assert_eq!(roundtrip(value.clone()), value);
1338    }
1339
1340    #[test]
1341    fn test_counter32_zero() {
1342        let value = Value::Counter32(0);
1343        assert_eq!(roundtrip(value.clone()), value);
1344    }
1345
1346    #[test]
1347    fn test_counter32_max() {
1348        let value = Value::Counter32(u32::MAX);
1349        assert_eq!(roundtrip(value.clone()), value);
1350    }
1351
1352    #[test]
1353    fn test_gauge32() {
1354        let value = Value::Gauge32(1000000000);
1355        assert_eq!(roundtrip(value.clone()), value);
1356    }
1357
1358    #[test]
1359    fn test_gauge32_max() {
1360        let value = Value::Gauge32(u32::MAX);
1361        assert_eq!(roundtrip(value.clone()), value);
1362    }
1363
1364    #[test]
1365    fn test_timeticks() {
1366        let value = Value::TimeTicks(123456);
1367        assert_eq!(roundtrip(value.clone()), value);
1368    }
1369
1370    #[test]
1371    fn test_timeticks_max() {
1372        let value = Value::TimeTicks(u32::MAX);
1373        assert_eq!(roundtrip(value.clone()), value);
1374    }
1375
1376    #[test]
1377    fn test_opaque() {
1378        let value = Value::Opaque(Bytes::from_static(&[0xDE, 0xAD, 0xBE, 0xEF]));
1379        assert_eq!(roundtrip(value.clone()), value);
1380    }
1381
1382    #[test]
1383    fn test_opaque_empty() {
1384        let value = Value::Opaque(Bytes::new());
1385        assert_eq!(roundtrip(value.clone()), value);
1386    }
1387
1388    #[test]
1389    fn test_counter64() {
1390        let value = Value::Counter64(123456789012345);
1391        assert_eq!(roundtrip(value.clone()), value);
1392    }
1393
1394    #[test]
1395    fn test_counter64_zero() {
1396        let value = Value::Counter64(0);
1397        assert_eq!(roundtrip(value.clone()), value);
1398    }
1399
1400    #[test]
1401    fn test_counter64_max() {
1402        let value = Value::Counter64(u64::MAX);
1403        assert_eq!(roundtrip(value.clone()), value);
1404    }
1405
1406    #[test]
1407    fn test_no_such_object() {
1408        let value = Value::NoSuchObject;
1409        assert_eq!(roundtrip(value.clone()), value);
1410    }
1411
1412    #[test]
1413    fn test_no_such_instance() {
1414        let value = Value::NoSuchInstance;
1415        assert_eq!(roundtrip(value.clone()), value);
1416    }
1417
1418    #[test]
1419    fn test_end_of_mib_view() {
1420        let value = Value::EndOfMibView;
1421        assert_eq!(roundtrip(value.clone()), value);
1422    }
1423
1424    #[test]
1425    fn test_unknown_tag_preserved() {
1426        // Tag 0x45 is application class but not a standard SNMP type
1427        let data = Bytes::from_static(&[0x45, 0x03, 0x01, 0x02, 0x03]);
1428        let mut decoder = Decoder::new(data);
1429        let value = Value::decode(&mut decoder).unwrap();
1430
1431        match value {
1432            Value::Unknown { tag, ref data } => {
1433                assert_eq!(tag, 0x45);
1434                assert_eq!(data.as_ref(), &[0x01, 0x02, 0x03]);
1435            }
1436            _ => panic!("expected Unknown variant"),
1437        }
1438
1439        // Roundtrip should preserve
1440        assert_eq!(roundtrip(value.clone()), value);
1441    }
1442
1443    // ========================================================================
1444    // Accessor Method Tests
1445    // ========================================================================
1446
1447    #[test]
1448    fn test_as_i32() {
1449        assert_eq!(Value::Integer(42).as_i32(), Some(42));
1450        assert_eq!(Value::Integer(-42).as_i32(), Some(-42));
1451        assert_eq!(Value::Counter32(100).as_i32(), None);
1452        assert_eq!(Value::Null.as_i32(), None);
1453    }
1454
1455    #[test]
1456    fn test_as_u32() {
1457        assert_eq!(Value::Counter32(100).as_u32(), Some(100));
1458        assert_eq!(Value::Gauge32(200).as_u32(), Some(200));
1459        assert_eq!(Value::TimeTicks(300).as_u32(), Some(300));
1460        assert_eq!(Value::Integer(50).as_u32(), Some(50));
1461        assert_eq!(Value::Integer(-1).as_u32(), None);
1462        assert_eq!(Value::Counter64(100).as_u32(), None);
1463    }
1464
1465    #[test]
1466    fn test_as_u64() {
1467        assert_eq!(Value::Counter64(100).as_u64(), Some(100));
1468        assert_eq!(Value::Counter32(100).as_u64(), Some(100));
1469        assert_eq!(Value::Gauge32(200).as_u64(), Some(200));
1470        assert_eq!(Value::TimeTicks(300).as_u64(), Some(300));
1471        assert_eq!(Value::Integer(50).as_u64(), Some(50));
1472        assert_eq!(Value::Integer(-1).as_u64(), None);
1473    }
1474
1475    #[test]
1476    fn test_as_bytes() {
1477        let s = Value::OctetString(Bytes::from_static(b"test"));
1478        assert_eq!(s.as_bytes(), Some(b"test".as_slice()));
1479
1480        let o = Value::Opaque(Bytes::from_static(b"data"));
1481        assert_eq!(o.as_bytes(), Some(b"data".as_slice()));
1482
1483        assert_eq!(Value::Integer(1).as_bytes(), None);
1484    }
1485
1486    #[test]
1487    fn test_as_str() {
1488        let s = Value::OctetString(Bytes::from_static(b"hello"));
1489        assert_eq!(s.as_str(), Some("hello"));
1490
1491        // Invalid UTF-8 returns None
1492        let invalid = Value::OctetString(Bytes::from_static(&[0xFF, 0xFE]));
1493        assert_eq!(invalid.as_str(), None);
1494
1495        assert_eq!(Value::Integer(1).as_str(), None);
1496    }
1497
1498    #[test]
1499    fn test_as_oid() {
1500        let oid = crate::oid!(1, 3, 6, 1);
1501        let v = Value::ObjectIdentifier(oid.clone());
1502        assert_eq!(v.as_oid(), Some(&oid));
1503
1504        assert_eq!(Value::Integer(1).as_oid(), None);
1505    }
1506
1507    #[test]
1508    fn test_as_ip() {
1509        let v = Value::IpAddress([192, 168, 1, 1]);
1510        assert_eq!(v.as_ip(), Some(std::net::Ipv4Addr::new(192, 168, 1, 1)));
1511
1512        assert_eq!(Value::Integer(1).as_ip(), None);
1513    }
1514
1515    // ========================================================================
1516    // is_exception() Tests
1517    // ========================================================================
1518
1519    #[test]
1520    fn test_is_exception() {
1521        assert!(Value::NoSuchObject.is_exception());
1522        assert!(Value::NoSuchInstance.is_exception());
1523        assert!(Value::EndOfMibView.is_exception());
1524
1525        assert!(!Value::Integer(1).is_exception());
1526        assert!(!Value::Null.is_exception());
1527        assert!(!Value::OctetString(Bytes::new()).is_exception());
1528    }
1529
1530    // ========================================================================
1531    // Display Trait Tests
1532    // ========================================================================
1533
1534    #[test]
1535    fn test_display_integer() {
1536        assert_eq!(format!("{}", Value::Integer(42)), "42");
1537        assert_eq!(format!("{}", Value::Integer(-42)), "-42");
1538    }
1539
1540    #[test]
1541    fn test_display_octet_string_utf8() {
1542        let v = Value::OctetString(Bytes::from_static(b"hello"));
1543        assert_eq!(format!("{}", v), "hello");
1544    }
1545
1546    #[test]
1547    fn test_display_octet_string_binary() {
1548        // Use bytes that are not valid UTF-8 (0xFF is never valid in UTF-8)
1549        let v = Value::OctetString(Bytes::from_static(&[0xFF, 0xFE]));
1550        assert_eq!(format!("{}", v), "0xfffe");
1551    }
1552
1553    #[test]
1554    fn test_display_null() {
1555        assert_eq!(format!("{}", Value::Null), "NULL");
1556    }
1557
1558    #[test]
1559    fn test_display_ip_address() {
1560        let v = Value::IpAddress([192, 168, 1, 1]);
1561        assert_eq!(format!("{}", v), "192.168.1.1");
1562    }
1563
1564    #[test]
1565    fn test_display_counter32() {
1566        assert_eq!(format!("{}", Value::Counter32(999)), "999");
1567    }
1568
1569    #[test]
1570    fn test_display_gauge32() {
1571        assert_eq!(format!("{}", Value::Gauge32(1000)), "1000");
1572    }
1573
1574    #[test]
1575    fn test_display_timeticks() {
1576        // 123456 hundredths = 1234.56 seconds
1577        // = 0d 0h 20m 34s
1578        let v = Value::TimeTicks(123456);
1579        assert_eq!(format!("{}", v), "0d 0h 20m 34s");
1580    }
1581
1582    #[test]
1583    fn test_display_opaque() {
1584        let v = Value::Opaque(Bytes::from_static(&[0xBE, 0xEF]));
1585        assert_eq!(format!("{}", v), "Opaque(0xbeef)");
1586    }
1587
1588    #[test]
1589    fn test_display_counter64() {
1590        assert_eq!(format!("{}", Value::Counter64(12345678)), "12345678");
1591    }
1592
1593    #[test]
1594    fn test_display_exceptions() {
1595        assert_eq!(format!("{}", Value::NoSuchObject), "noSuchObject");
1596        assert_eq!(format!("{}", Value::NoSuchInstance), "noSuchInstance");
1597        assert_eq!(format!("{}", Value::EndOfMibView), "endOfMibView");
1598    }
1599
1600    #[test]
1601    fn test_display_unknown() {
1602        let v = Value::Unknown {
1603            tag: 0x99,
1604            data: Bytes::from_static(&[0x01, 0x02]),
1605        };
1606        assert_eq!(format!("{}", v), "Unknown(tag=0x99, data=0x0102)");
1607    }
1608
1609    // ========================================================================
1610    // From Conversion Tests
1611    // ========================================================================
1612
1613    #[test]
1614    fn test_from_i32() {
1615        let v: Value = 42i32.into();
1616        assert_eq!(v, Value::Integer(42));
1617    }
1618
1619    #[test]
1620    fn test_from_str() {
1621        let v: Value = "hello".into();
1622        assert_eq!(v.as_str(), Some("hello"));
1623    }
1624
1625    #[test]
1626    fn test_from_string() {
1627        let v: Value = String::from("hello").into();
1628        assert_eq!(v.as_str(), Some("hello"));
1629    }
1630
1631    #[test]
1632    fn test_from_bytes_slice() {
1633        let v: Value = (&[1u8, 2, 3][..]).into();
1634        assert_eq!(v.as_bytes(), Some(&[1u8, 2, 3][..]));
1635    }
1636
1637    #[test]
1638    fn test_from_oid() {
1639        let oid = crate::oid!(1, 3, 6, 1);
1640        let v: Value = oid.clone().into();
1641        assert_eq!(v.as_oid(), Some(&oid));
1642    }
1643
1644    #[test]
1645    fn test_from_ipv4addr() {
1646        let addr = std::net::Ipv4Addr::new(10, 0, 0, 1);
1647        let v: Value = addr.into();
1648        assert_eq!(v, Value::IpAddress([10, 0, 0, 1]));
1649    }
1650
1651    #[test]
1652    fn test_from_bytes() {
1653        let data = Bytes::from_static(b"hello");
1654        let v: Value = data.into();
1655        assert_eq!(v.as_bytes(), Some(b"hello".as_slice()));
1656    }
1657
1658    #[test]
1659    fn test_from_u64() {
1660        let v: Value = 12345678901234u64.into();
1661        assert_eq!(v, Value::Counter64(12345678901234));
1662    }
1663
1664    #[test]
1665    fn test_from_ip_array() {
1666        let v: Value = [192u8, 168, 1, 1].into();
1667        assert_eq!(v, Value::IpAddress([192, 168, 1, 1]));
1668    }
1669
1670    // ========================================================================
1671    // Eq and Hash Tests
1672    // ========================================================================
1673
1674    #[test]
1675    fn test_value_eq_and_hash() {
1676        use std::collections::HashSet;
1677
1678        let mut set = HashSet::new();
1679        set.insert(Value::Integer(42));
1680        set.insert(Value::Integer(42)); // Duplicate
1681        set.insert(Value::Integer(100));
1682
1683        assert_eq!(set.len(), 2);
1684        assert!(set.contains(&Value::Integer(42)));
1685        assert!(set.contains(&Value::Integer(100)));
1686    }
1687
1688    // ========================================================================
1689    // Decode Error Tests
1690    // ========================================================================
1691
1692    #[test]
1693    fn test_decode_invalid_null_length() {
1694        // NULL must have length 0
1695        let data = Bytes::from_static(&[0x05, 0x01, 0x00]); // NULL with length 1
1696        let mut decoder = Decoder::new(data);
1697        let result = Value::decode(&mut decoder);
1698        assert!(result.is_err());
1699    }
1700
1701    #[test]
1702    fn test_decode_invalid_ip_address_length() {
1703        // IpAddress must have length 4
1704        let data = Bytes::from_static(&[0x40, 0x03, 0x01, 0x02, 0x03]); // Only 3 bytes
1705        let mut decoder = Decoder::new(data);
1706        let result = Value::decode(&mut decoder);
1707        assert!(result.is_err());
1708    }
1709
1710    #[test]
1711    fn test_decode_exception_with_content_accepted() {
1712        // Per implementation, exceptions with non-zero length have content skipped
1713        let data = Bytes::from_static(&[0x80, 0x01, 0xFF]); // NoSuchObject with 1 byte
1714        let mut decoder = Decoder::new(data);
1715        let result = Value::decode(&mut decoder);
1716        assert!(result.is_ok());
1717        assert_eq!(result.unwrap(), Value::NoSuchObject);
1718    }
1719
1720    // ========================================================================
1721    // Numeric Extraction Method Tests
1722    // ========================================================================
1723
1724    #[test]
1725    fn test_as_f64() {
1726        assert_eq!(Value::Integer(42).as_f64(), Some(42.0));
1727        assert_eq!(Value::Integer(-42).as_f64(), Some(-42.0));
1728        assert_eq!(Value::Counter32(1000).as_f64(), Some(1000.0));
1729        assert_eq!(Value::Gauge32(2000).as_f64(), Some(2000.0));
1730        assert_eq!(Value::TimeTicks(3000).as_f64(), Some(3000.0));
1731        assert_eq!(
1732            Value::Counter64(10_000_000_000).as_f64(),
1733            Some(10_000_000_000.0)
1734        );
1735        assert_eq!(Value::Null.as_f64(), None);
1736        assert_eq!(
1737            Value::OctetString(Bytes::from_static(b"test")).as_f64(),
1738            None
1739        );
1740    }
1741
1742    #[test]
1743    fn test_as_f64_wrapped() {
1744        // Small values behave same as as_f64()
1745        assert_eq!(Value::Counter64(1000).as_f64_wrapped(), Some(1000.0));
1746        assert_eq!(Value::Counter32(1000).as_f64_wrapped(), Some(1000.0));
1747        assert_eq!(Value::Integer(42).as_f64_wrapped(), Some(42.0));
1748
1749        // Large Counter64 wraps at 2^53
1750        let mantissa_limit = 1u64 << 53;
1751        assert_eq!(Value::Counter64(mantissa_limit).as_f64_wrapped(), Some(0.0));
1752        assert_eq!(
1753            Value::Counter64(mantissa_limit + 1).as_f64_wrapped(),
1754            Some(1.0)
1755        );
1756    }
1757
1758    #[test]
1759    fn test_as_decimal() {
1760        assert_eq!(Value::Integer(2350).as_decimal(2), Some(23.50));
1761        assert_eq!(Value::Integer(9999).as_decimal(2), Some(99.99));
1762        assert_eq!(Value::Integer(12500).as_decimal(3), Some(12.5));
1763        assert_eq!(Value::Integer(-500).as_decimal(2), Some(-5.0));
1764        assert_eq!(Value::Counter32(1000).as_decimal(1), Some(100.0));
1765        assert_eq!(Value::Null.as_decimal(2), None);
1766    }
1767
1768    #[test]
1769    fn test_as_duration() {
1770        use std::time::Duration;
1771
1772        // 100 ticks = 1 second
1773        assert_eq!(
1774            Value::TimeTicks(100).as_duration(),
1775            Some(Duration::from_secs(1))
1776        );
1777        // 360000 ticks = 3600 seconds = 1 hour
1778        assert_eq!(
1779            Value::TimeTicks(360000).as_duration(),
1780            Some(Duration::from_secs(3600))
1781        );
1782        // 1 tick = 10 milliseconds
1783        assert_eq!(
1784            Value::TimeTicks(1).as_duration(),
1785            Some(Duration::from_millis(10))
1786        );
1787
1788        // Non-TimeTicks return None
1789        assert_eq!(Value::Integer(100).as_duration(), None);
1790        assert_eq!(Value::Counter32(100).as_duration(), None);
1791    }
1792
1793    // ========================================================================
1794    // Opaque Sub-type Extraction Tests
1795    // ========================================================================
1796
1797    #[test]
1798    fn test_as_opaque_float() {
1799        // Opaque-encoded float for pi ≈ 3.14159
1800        // 0x40490fdb is IEEE 754 single-precision for ~3.14159
1801        let data = Bytes::from_static(&[0x9f, 0x78, 0x04, 0x40, 0x49, 0x0f, 0xdb]);
1802        let value = Value::Opaque(data);
1803        let pi = value.as_opaque_float().unwrap();
1804        assert!((pi - std::f32::consts::PI).abs() < 0.0001);
1805
1806        // Non-Opaque returns None
1807        assert_eq!(Value::Integer(42).as_opaque_float(), None);
1808
1809        // Wrong subtype returns None
1810        let wrong_type = Bytes::from_static(&[0x9f, 0x79, 0x04, 0x40, 0x49, 0x0f, 0xdb]);
1811        assert_eq!(Value::Opaque(wrong_type).as_opaque_float(), None);
1812
1813        // Too short returns None
1814        let short = Bytes::from_static(&[0x9f, 0x78, 0x04, 0x40, 0x49]);
1815        assert_eq!(Value::Opaque(short).as_opaque_float(), None);
1816    }
1817
1818    #[test]
1819    fn test_as_opaque_double() {
1820        // Opaque-encoded double for pi
1821        // 0x400921fb54442d18 is IEEE 754 double-precision for pi
1822        let data = Bytes::from_static(&[
1823            0x9f, 0x79, 0x08, 0x40, 0x09, 0x21, 0xfb, 0x54, 0x44, 0x2d, 0x18,
1824        ]);
1825        let value = Value::Opaque(data);
1826        let pi = value.as_opaque_double().unwrap();
1827        assert!((pi - std::f64::consts::PI).abs() < 1e-10);
1828
1829        // Non-Opaque returns None
1830        assert_eq!(Value::Integer(42).as_opaque_double(), None);
1831    }
1832
1833    #[test]
1834    fn test_as_opaque_counter64() {
1835        // 8-byte Counter64
1836        let data = Bytes::from_static(&[
1837            0x9f, 0x76, 0x08, 0x01, 0x23, 0x45, 0x67, 0x89, 0xAB, 0xCD, 0xEF,
1838        ]);
1839        let value = Value::Opaque(data);
1840        assert_eq!(value.as_opaque_counter64(), Some(0x0123456789ABCDEF));
1841
1842        // Shorter encoding (e.g., small value)
1843        let small = Bytes::from_static(&[0x9f, 0x76, 0x01, 0x42]);
1844        assert_eq!(Value::Opaque(small).as_opaque_counter64(), Some(0x42));
1845
1846        // Zero
1847        let zero = Bytes::from_static(&[0x9f, 0x76, 0x01, 0x00]);
1848        assert_eq!(Value::Opaque(zero).as_opaque_counter64(), Some(0));
1849    }
1850
1851    #[test]
1852    fn test_as_opaque_i64() {
1853        // Positive value
1854        let positive = Bytes::from_static(&[0x9f, 0x7a, 0x02, 0x01, 0x00]);
1855        assert_eq!(Value::Opaque(positive).as_opaque_i64(), Some(256));
1856
1857        // Negative value (-1)
1858        let minus_one = Bytes::from_static(&[0x9f, 0x7a, 0x01, 0xFF]);
1859        assert_eq!(Value::Opaque(minus_one).as_opaque_i64(), Some(-1));
1860
1861        // Full 8-byte negative
1862        let full_neg = Bytes::from_static(&[
1863            0x9f, 0x7a, 0x08, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF,
1864        ]);
1865        assert_eq!(Value::Opaque(full_neg).as_opaque_i64(), Some(-1));
1866
1867        // i64::MIN
1868        let min = Bytes::from_static(&[
1869            0x9f, 0x7a, 0x08, 0x80, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
1870        ]);
1871        assert_eq!(Value::Opaque(min).as_opaque_i64(), Some(i64::MIN));
1872    }
1873
1874    #[test]
1875    fn test_as_opaque_u64() {
1876        // 8-byte U64
1877        let data = Bytes::from_static(&[
1878            0x9f, 0x7b, 0x08, 0x01, 0x23, 0x45, 0x67, 0x89, 0xAB, 0xCD, 0xEF,
1879        ]);
1880        let value = Value::Opaque(data);
1881        assert_eq!(value.as_opaque_u64(), Some(0x0123456789ABCDEF));
1882
1883        // u64::MAX
1884        let max = Bytes::from_static(&[
1885            0x9f, 0x7b, 0x08, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF,
1886        ]);
1887        assert_eq!(Value::Opaque(max).as_opaque_u64(), Some(u64::MAX));
1888    }
1889
1890    #[test]
1891    fn test_format_with_hint_integer() {
1892        // Decimal places
1893        assert_eq!(
1894            Value::Integer(2350).format_with_hint("d-2"),
1895            Some("23.50".into())
1896        );
1897        assert_eq!(
1898            Value::Integer(-500).format_with_hint("d-2"),
1899            Some("-5.00".into())
1900        );
1901
1902        // Basic formats
1903        assert_eq!(Value::Integer(255).format_with_hint("x"), Some("ff".into()));
1904        assert_eq!(Value::Integer(8).format_with_hint("o"), Some("10".into()));
1905        assert_eq!(Value::Integer(5).format_with_hint("b"), Some("101".into()));
1906        assert_eq!(Value::Integer(42).format_with_hint("d"), Some("42".into()));
1907
1908        // Invalid hint
1909        assert_eq!(Value::Integer(42).format_with_hint("invalid"), None);
1910
1911        // Counter32 etc. still return None (only Integer supported for INTEGER hints)
1912        assert_eq!(Value::Counter32(42).format_with_hint("d-2"), None);
1913    }
1914
1915    #[test]
1916    fn test_as_truth_value() {
1917        // Valid TruthValue
1918        assert_eq!(Value::Integer(1).as_truth_value(), Some(true));
1919        assert_eq!(Value::Integer(2).as_truth_value(), Some(false));
1920
1921        // Invalid integers
1922        assert_eq!(Value::Integer(0).as_truth_value(), None);
1923        assert_eq!(Value::Integer(3).as_truth_value(), None);
1924        assert_eq!(Value::Integer(-1).as_truth_value(), None);
1925
1926        // Non-Integer types
1927        assert_eq!(Value::Null.as_truth_value(), None);
1928        assert_eq!(Value::Counter32(1).as_truth_value(), None);
1929        assert_eq!(Value::Gauge32(1).as_truth_value(), None);
1930    }
1931
1932    // ========================================================================
1933    // RowStatus Tests
1934    // ========================================================================
1935
1936    #[test]
1937    fn test_row_status_from_i32() {
1938        assert_eq!(RowStatus::from_i32(1), Some(RowStatus::Active));
1939        assert_eq!(RowStatus::from_i32(2), Some(RowStatus::NotInService));
1940        assert_eq!(RowStatus::from_i32(3), Some(RowStatus::NotReady));
1941        assert_eq!(RowStatus::from_i32(4), Some(RowStatus::CreateAndGo));
1942        assert_eq!(RowStatus::from_i32(5), Some(RowStatus::CreateAndWait));
1943        assert_eq!(RowStatus::from_i32(6), Some(RowStatus::Destroy));
1944
1945        // Invalid values
1946        assert_eq!(RowStatus::from_i32(0), None);
1947        assert_eq!(RowStatus::from_i32(7), None);
1948        assert_eq!(RowStatus::from_i32(-1), None);
1949    }
1950
1951    #[test]
1952    fn test_row_status_into_value() {
1953        let v: Value = RowStatus::Active.into();
1954        assert_eq!(v, Value::Integer(1));
1955
1956        let v: Value = RowStatus::Destroy.into();
1957        assert_eq!(v, Value::Integer(6));
1958    }
1959
1960    #[test]
1961    fn test_row_status_display() {
1962        assert_eq!(format!("{}", RowStatus::Active), "active");
1963        assert_eq!(format!("{}", RowStatus::NotInService), "notInService");
1964        assert_eq!(format!("{}", RowStatus::NotReady), "notReady");
1965        assert_eq!(format!("{}", RowStatus::CreateAndGo), "createAndGo");
1966        assert_eq!(format!("{}", RowStatus::CreateAndWait), "createAndWait");
1967        assert_eq!(format!("{}", RowStatus::Destroy), "destroy");
1968    }
1969
1970    #[test]
1971    fn test_as_row_status() {
1972        // Valid RowStatus values
1973        assert_eq!(Value::Integer(1).as_row_status(), Some(RowStatus::Active));
1974        assert_eq!(Value::Integer(6).as_row_status(), Some(RowStatus::Destroy));
1975
1976        // Invalid integers
1977        assert_eq!(Value::Integer(0).as_row_status(), None);
1978        assert_eq!(Value::Integer(7).as_row_status(), None);
1979
1980        // Non-Integer types
1981        assert_eq!(Value::Null.as_row_status(), None);
1982        assert_eq!(Value::Counter32(1).as_row_status(), None);
1983    }
1984
1985    // ========================================================================
1986    // StorageType Tests
1987    // ========================================================================
1988
1989    #[test]
1990    fn test_storage_type_from_i32() {
1991        assert_eq!(StorageType::from_i32(1), Some(StorageType::Other));
1992        assert_eq!(StorageType::from_i32(2), Some(StorageType::Volatile));
1993        assert_eq!(StorageType::from_i32(3), Some(StorageType::NonVolatile));
1994        assert_eq!(StorageType::from_i32(4), Some(StorageType::Permanent));
1995        assert_eq!(StorageType::from_i32(5), Some(StorageType::ReadOnly));
1996
1997        // Invalid values
1998        assert_eq!(StorageType::from_i32(0), None);
1999        assert_eq!(StorageType::from_i32(6), None);
2000        assert_eq!(StorageType::from_i32(-1), None);
2001    }
2002
2003    #[test]
2004    fn test_storage_type_into_value() {
2005        let v: Value = StorageType::Volatile.into();
2006        assert_eq!(v, Value::Integer(2));
2007
2008        let v: Value = StorageType::NonVolatile.into();
2009        assert_eq!(v, Value::Integer(3));
2010    }
2011
2012    #[test]
2013    fn test_storage_type_display() {
2014        assert_eq!(format!("{}", StorageType::Other), "other");
2015        assert_eq!(format!("{}", StorageType::Volatile), "volatile");
2016        assert_eq!(format!("{}", StorageType::NonVolatile), "nonVolatile");
2017        assert_eq!(format!("{}", StorageType::Permanent), "permanent");
2018        assert_eq!(format!("{}", StorageType::ReadOnly), "readOnly");
2019    }
2020
2021    #[test]
2022    fn test_as_storage_type() {
2023        // Valid StorageType values
2024        assert_eq!(
2025            Value::Integer(2).as_storage_type(),
2026            Some(StorageType::Volatile)
2027        );
2028        assert_eq!(
2029            Value::Integer(3).as_storage_type(),
2030            Some(StorageType::NonVolatile)
2031        );
2032
2033        // Invalid integers
2034        assert_eq!(Value::Integer(0).as_storage_type(), None);
2035        assert_eq!(Value::Integer(6).as_storage_type(), None);
2036
2037        // Non-Integer types
2038        assert_eq!(Value::Null.as_storage_type(), None);
2039        assert_eq!(Value::Counter32(1).as_storage_type(), None);
2040    }
2041}