Skip to main content

mtp_rs/ptp/types/
properties.rs

1//! Property-related types for MTP/PTP device properties.
2//!
3//! This module contains types for working with device properties:
4//! - [`PropertyValue`]: A property value with its associated type
5//! - [`PropertyFormType`]: Form type for property value constraints
6//! - [`PropertyRange`]: Range constraint for a property value
7//! - [`DevicePropDesc`]: Device property descriptor
8
9use crate::ptp::codes::{DevicePropertyCode, PropertyDataType};
10use crate::ptp::pack::{
11    pack_i16, pack_i32, pack_i64, pack_i8, pack_string, pack_u16, pack_u32, pack_u64, pack_u8,
12    unpack_i16, unpack_i32, unpack_i64, unpack_i8, unpack_string, unpack_u16, unpack_u64,
13    unpack_u8,
14};
15
16// --- PropertyValue Enum ---
17
18/// A property value with its associated type.
19///
20/// Used for device property values in `DevicePropDesc`, as well as
21/// for get/set device property operations.
22#[derive(Debug, Clone, PartialEq)]
23pub enum PropertyValue {
24    /// Signed 8-bit integer.
25    Int8(i8),
26    /// Unsigned 8-bit integer.
27    Uint8(u8),
28    /// Signed 16-bit integer.
29    Int16(i16),
30    /// Unsigned 16-bit integer.
31    Uint16(u16),
32    /// Signed 32-bit integer.
33    Int32(i32),
34    /// Unsigned 32-bit integer.
35    Uint32(u32),
36    /// Signed 64-bit integer.
37    Int64(i64),
38    /// Unsigned 64-bit integer.
39    Uint64(u64),
40    /// UTF-16LE encoded string.
41    String(String),
42}
43
44impl PropertyValue {
45    /// Serialize this property value to bytes.
46    pub fn to_bytes(&self) -> Vec<u8> {
47        match self {
48            PropertyValue::Int8(v) => pack_i8(*v).to_vec(),
49            PropertyValue::Uint8(v) => pack_u8(*v).to_vec(),
50            PropertyValue::Int16(v) => pack_i16(*v).to_vec(),
51            PropertyValue::Uint16(v) => pack_u16(*v).to_vec(),
52            PropertyValue::Int32(v) => pack_i32(*v).to_vec(),
53            PropertyValue::Uint32(v) => pack_u32(*v).to_vec(),
54            PropertyValue::Int64(v) => pack_i64(*v).to_vec(),
55            PropertyValue::Uint64(v) => pack_u64(*v).to_vec(),
56            PropertyValue::String(v) => pack_string(v),
57        }
58    }
59
60    /// Parse a property value from bytes given the expected data type.
61    ///
62    /// Returns the parsed value and the number of bytes consumed.
63    pub fn from_bytes(
64        buf: &[u8],
65        data_type: PropertyDataType,
66    ) -> Result<(Self, usize), crate::Error> {
67        match data_type {
68            PropertyDataType::Int8 => {
69                let val = unpack_i8(buf)?;
70                Ok((PropertyValue::Int8(val), 1))
71            }
72            PropertyDataType::Uint8 => {
73                let val = unpack_u8(buf)?;
74                Ok((PropertyValue::Uint8(val), 1))
75            }
76            PropertyDataType::Int16 => {
77                let val = unpack_i16(buf)?;
78                Ok((PropertyValue::Int16(val), 2))
79            }
80            PropertyDataType::Uint16 => {
81                let val = unpack_u16(buf)?;
82                Ok((PropertyValue::Uint16(val), 2))
83            }
84            PropertyDataType::Int32 => {
85                let val = unpack_i32(buf)?;
86                Ok((PropertyValue::Int32(val), 4))
87            }
88            PropertyDataType::Uint32 => {
89                let val = crate::ptp::pack::unpack_u32(buf)?;
90                Ok((PropertyValue::Uint32(val), 4))
91            }
92            PropertyDataType::Int64 => {
93                let val = unpack_i64(buf)?;
94                Ok((PropertyValue::Int64(val), 8))
95            }
96            PropertyDataType::Uint64 => {
97                let val = unpack_u64(buf)?;
98                Ok((PropertyValue::Uint64(val), 8))
99            }
100            PropertyDataType::String => {
101                let (val, consumed) = unpack_string(buf)?;
102                Ok((PropertyValue::String(val), consumed))
103            }
104            PropertyDataType::Undefined
105            | PropertyDataType::Int128
106            | PropertyDataType::Uint128
107            | PropertyDataType::Unknown(_) => Err(crate::Error::invalid_data(format!(
108                "unsupported property data type: {:?}",
109                data_type
110            ))),
111        }
112    }
113
114    /// Get the data type of this property value.
115    #[must_use]
116    pub fn data_type(&self) -> PropertyDataType {
117        match self {
118            PropertyValue::Int8(_) => PropertyDataType::Int8,
119            PropertyValue::Uint8(_) => PropertyDataType::Uint8,
120            PropertyValue::Int16(_) => PropertyDataType::Int16,
121            PropertyValue::Uint16(_) => PropertyDataType::Uint16,
122            PropertyValue::Int32(_) => PropertyDataType::Int32,
123            PropertyValue::Uint32(_) => PropertyDataType::Uint32,
124            PropertyValue::Int64(_) => PropertyDataType::Int64,
125            PropertyValue::Uint64(_) => PropertyDataType::Uint64,
126            PropertyValue::String(_) => PropertyDataType::String,
127        }
128    }
129}
130
131// --- PropertyFormType Enum ---
132
133/// Form type for property value constraints.
134///
135/// Describes how allowed values are specified for a property.
136#[derive(Debug, Clone, Copy, PartialEq, Eq, Default)]
137pub enum PropertyFormType {
138    /// No constraints (any value valid).
139    #[default]
140    None,
141    /// Value must be within a range (min, max, step).
142    Range,
143    /// Value must be one of an enumerated set.
144    Enumeration,
145    /// Unknown form type.
146    Unknown(u8),
147}
148
149impl PropertyFormType {
150    /// Convert a raw u8 code to a PropertyFormType.
151    #[must_use]
152    pub fn from_code(code: u8) -> Self {
153        match code {
154            0x00 => PropertyFormType::None,
155            0x01 => PropertyFormType::Range,
156            0x02 => PropertyFormType::Enumeration,
157            _ => PropertyFormType::Unknown(code),
158        }
159    }
160
161    /// Convert a PropertyFormType to its raw u8 value.
162    #[must_use]
163    pub fn to_code(self) -> u8 {
164        match self {
165            PropertyFormType::None => 0x00,
166            PropertyFormType::Range => 0x01,
167            PropertyFormType::Enumeration => 0x02,
168            PropertyFormType::Unknown(code) => code,
169        }
170    }
171}
172
173// --- PropertyRange Struct ---
174
175/// Range constraint for a property value.
176///
177/// Used when `PropertyFormType::Range` is specified.
178#[derive(Debug, Clone, PartialEq)]
179pub struct PropertyRange {
180    /// Minimum allowed value.
181    pub min: PropertyValue,
182    /// Maximum allowed value.
183    pub max: PropertyValue,
184    /// Step size between allowed values.
185    pub step: PropertyValue,
186}
187
188impl PropertyRange {
189    /// Parse a PropertyRange from bytes given the data type.
190    ///
191    /// Returns the parsed range and the number of bytes consumed.
192    pub fn from_bytes(
193        buf: &[u8],
194        data_type: PropertyDataType,
195    ) -> Result<(Self, usize), crate::Error> {
196        let mut offset = 0;
197
198        let (min, consumed) = PropertyValue::from_bytes(&buf[offset..], data_type)?;
199        offset += consumed;
200
201        let (max, consumed) = PropertyValue::from_bytes(&buf[offset..], data_type)?;
202        offset += consumed;
203
204        let (step, consumed) = PropertyValue::from_bytes(&buf[offset..], data_type)?;
205        offset += consumed;
206
207        Ok((PropertyRange { min, max, step }, offset))
208    }
209
210    /// Serialize this property range to bytes.
211    pub fn to_bytes(&self) -> Vec<u8> {
212        let mut buf = Vec::new();
213        buf.extend_from_slice(&self.min.to_bytes());
214        buf.extend_from_slice(&self.max.to_bytes());
215        buf.extend_from_slice(&self.step.to_bytes());
216        buf
217    }
218}
219
220// --- DevicePropDesc Structure ---
221
222/// Device property descriptor.
223///
224/// Describes a device property including its type, current value,
225/// default value, and allowed values/ranges.
226///
227/// Returned by the GetDevicePropDesc operation.
228#[derive(Debug, Clone)]
229pub struct DevicePropDesc {
230    /// Property code identifying this property.
231    pub property_code: DevicePropertyCode,
232    /// Data type of the property value.
233    pub data_type: PropertyDataType,
234    /// Whether the property is writable (true) or read-only (false).
235    pub writable: bool,
236    /// Default/factory value.
237    pub default_value: PropertyValue,
238    /// Current value.
239    pub current_value: PropertyValue,
240    /// Form type (None, Range, or Enumeration).
241    pub form_type: PropertyFormType,
242    /// Allowed values (if form_type is Enumeration).
243    pub enum_values: Option<Vec<PropertyValue>>,
244    /// Value range (if form_type is Range).
245    pub range: Option<PropertyRange>,
246}
247
248impl DevicePropDesc {
249    /// Parse a DevicePropDesc from bytes.
250    ///
251    /// The buffer should contain the DevicePropDesc dataset as returned
252    /// by GetDevicePropDesc.
253    pub fn from_bytes(buf: &[u8]) -> Result<Self, crate::Error> {
254        let mut offset = 0;
255
256        // 1. PropertyCode (u16)
257        let property_code = DevicePropertyCode::from(unpack_u16(&buf[offset..])?);
258        offset += 2;
259
260        // 2. DataType (u16)
261        let data_type = PropertyDataType::from(unpack_u16(&buf[offset..])?);
262        offset += 2;
263
264        // 3. GetSet (u8): 0x00 = read-only, 0x01 = read-write
265        if buf.len() <= offset {
266            return Err(crate::Error::invalid_data(
267                "DevicePropDesc: insufficient bytes for GetSet",
268            ));
269        }
270        let writable = buf[offset] != 0x00;
271        offset += 1;
272
273        // 4. DefaultValue (variable size based on data type)
274        let (default_value, consumed) = PropertyValue::from_bytes(&buf[offset..], data_type)?;
275        offset += consumed;
276
277        // 5. CurrentValue (variable size based on data type)
278        let (current_value, consumed) = PropertyValue::from_bytes(&buf[offset..], data_type)?;
279        offset += consumed;
280
281        // 6. FormFlag (u8)
282        if buf.len() <= offset {
283            return Err(crate::Error::invalid_data(
284                "DevicePropDesc: insufficient bytes for FormFlag",
285            ));
286        }
287        let form_type = PropertyFormType::from_code(buf[offset]);
288        offset += 1;
289
290        // 7. Form data (depends on form_type)
291        let (enum_values, range) = match form_type {
292            PropertyFormType::None | PropertyFormType::Unknown(_) => (None, None),
293            PropertyFormType::Range => {
294                let (range, _consumed) = PropertyRange::from_bytes(&buf[offset..], data_type)?;
295                (None, Some(range))
296            }
297            PropertyFormType::Enumeration => {
298                // Number of values (u16)
299                let count = unpack_u16(&buf[offset..])? as usize;
300                offset += 2;
301
302                let mut values = Vec::with_capacity(count);
303                for _ in 0..count {
304                    let (val, consumed) = PropertyValue::from_bytes(&buf[offset..], data_type)?;
305                    values.push(val);
306                    offset += consumed;
307                }
308                (Some(values), None)
309            }
310        };
311
312        Ok(DevicePropDesc {
313            property_code,
314            data_type,
315            writable,
316            default_value,
317            current_value,
318            form_type,
319            enum_values,
320            range,
321        })
322    }
323}
324
325#[cfg(test)]
326mod tests {
327    use super::*;
328    use crate::ptp::pack::{pack_i16, pack_u16};
329    use proptest::prelude::*;
330
331    // --- PropertyValue tests ---
332
333    #[test]
334    fn property_value_roundtrip() {
335        let values = [
336            PropertyValue::Int8(-42),
337            PropertyValue::Uint8(100),
338            PropertyValue::Int16(-1000),
339            PropertyValue::Uint16(5000),
340            PropertyValue::Int32(-100000),
341            PropertyValue::Uint32(100000),
342            PropertyValue::Int64(-1_000_000_000),
343            PropertyValue::Uint64(1_000_000_000),
344            PropertyValue::String("Test".to_string()),
345        ];
346        for val in &values {
347            let bytes = val.to_bytes();
348            let (parsed, _) = PropertyValue::from_bytes(&bytes, val.data_type()).unwrap();
349            assert_eq!(&parsed, val);
350        }
351    }
352
353    #[test]
354    fn property_value_data_type() {
355        let cases: &[(PropertyValue, PropertyDataType)] = &[
356            (PropertyValue::Int8(0), PropertyDataType::Int8),
357            (PropertyValue::Uint8(0), PropertyDataType::Uint8),
358            (PropertyValue::Int16(0), PropertyDataType::Int16),
359            (PropertyValue::Uint16(0), PropertyDataType::Uint16),
360            (PropertyValue::Int32(0), PropertyDataType::Int32),
361            (PropertyValue::Uint32(0), PropertyDataType::Uint32),
362            (PropertyValue::Int64(0), PropertyDataType::Int64),
363            (PropertyValue::Uint64(0), PropertyDataType::Uint64),
364            (PropertyValue::String("".into()), PropertyDataType::String),
365        ];
366        for (val, expected) in cases {
367            assert_eq!(val.data_type(), *expected);
368        }
369    }
370
371    #[test]
372    fn property_value_from_bytes_unsupported_type() {
373        for dt in [
374            PropertyDataType::Undefined,
375            PropertyDataType::Int128,
376            PropertyDataType::Uint128,
377            PropertyDataType::Unknown(0x99),
378        ] {
379            assert!(PropertyValue::from_bytes(&[0x00], dt).is_err());
380        }
381    }
382
383    #[test]
384    fn property_value_from_bytes_insufficient_bytes() {
385        assert!(PropertyValue::from_bytes(&[], PropertyDataType::Int8).is_err());
386        assert!(PropertyValue::from_bytes(&[0x00], PropertyDataType::Int16).is_err());
387        assert!(PropertyValue::from_bytes(&[0x00, 0x00], PropertyDataType::Int32).is_err());
388        assert!(PropertyValue::from_bytes(&[0x00; 7], PropertyDataType::Int64).is_err());
389    }
390
391    // --- PropertyFormType tests ---
392
393    #[test]
394    fn property_form_type_from_code() {
395        for (code, expected) in [
396            (0x00, PropertyFormType::None),
397            (0x01, PropertyFormType::Range),
398            (0x02, PropertyFormType::Enumeration),
399        ] {
400            assert_eq!(PropertyFormType::from_code(code), expected);
401            assert_eq!(expected.to_code(), code);
402        }
403        assert_eq!(
404            PropertyFormType::from_code(0x99),
405            PropertyFormType::Unknown(0x99)
406        );
407    }
408
409    #[test]
410    fn property_form_type_to_code() {
411        assert_eq!(PropertyFormType::None.to_code(), 0x00);
412        assert_eq!(PropertyFormType::Range.to_code(), 0x01);
413        assert_eq!(PropertyFormType::Enumeration.to_code(), 0x02);
414        assert_eq!(PropertyFormType::Unknown(0x99).to_code(), 0x99);
415    }
416
417    #[test]
418    fn property_form_type_roundtrip() {
419        for f in [
420            PropertyFormType::None,
421            PropertyFormType::Range,
422            PropertyFormType::Enumeration,
423        ] {
424            assert_eq!(PropertyFormType::from_code(f.to_code()), f);
425        }
426    }
427
428    // --- PropertyRange tests ---
429
430    #[test]
431    fn property_range_from_bytes_uint8() {
432        let buf = vec![0x00, 0x64, 0x01]; // min=0, max=100, step=1
433        let (range, consumed) = PropertyRange::from_bytes(&buf, PropertyDataType::Uint8).unwrap();
434        assert_eq!(range.min, PropertyValue::Uint8(0));
435        assert_eq!(range.max, PropertyValue::Uint8(100));
436        assert_eq!(range.step, PropertyValue::Uint8(1));
437        assert_eq!(consumed, 3);
438    }
439
440    #[test]
441    fn property_range_from_bytes_uint16() {
442        let buf = vec![0x64, 0x00, 0x00, 0x19, 0x64, 0x00]; // min=100, max=6400, step=100
443        let (range, consumed) = PropertyRange::from_bytes(&buf, PropertyDataType::Uint16).unwrap();
444        assert_eq!(range.min, PropertyValue::Uint16(100));
445        assert_eq!(range.max, PropertyValue::Uint16(6400));
446        assert_eq!(range.step, PropertyValue::Uint16(100));
447        assert_eq!(consumed, 6);
448    }
449
450    #[test]
451    fn property_range_to_bytes() {
452        let range = PropertyRange {
453            min: PropertyValue::Uint8(0),
454            max: PropertyValue::Uint8(100),
455            step: PropertyValue::Uint8(1),
456        };
457        assert_eq!(range.to_bytes(), vec![0x00, 0x64, 0x01]);
458    }
459
460    #[test]
461    fn property_range_roundtrip() {
462        let range = PropertyRange {
463            min: PropertyValue::Uint16(100),
464            max: PropertyValue::Uint16(6400),
465            step: PropertyValue::Uint16(100),
466        };
467        let bytes = range.to_bytes();
468        let (parsed, _) = PropertyRange::from_bytes(&bytes, PropertyDataType::Uint16).unwrap();
469        assert_eq!(parsed.min, range.min);
470        assert_eq!(parsed.max, range.max);
471        assert_eq!(parsed.step, range.step);
472    }
473
474    // --- DevicePropDesc tests ---
475
476    fn build_battery_level_prop_desc(current: u8) -> Vec<u8> {
477        let mut buf = Vec::new();
478        buf.extend_from_slice(&pack_u16(0x5001)); // BatteryLevel
479        buf.extend_from_slice(&pack_u16(0x0002)); // UINT8
480        buf.push(0x00); // read-only
481        buf.push(100); // default
482        buf.push(current); // current
483        buf.push(0x01); // Range
484        buf.extend([0, 100, 1]); // min, max, step
485        buf
486    }
487
488    #[test]
489    fn device_prop_desc_parse_battery_level() {
490        let desc = DevicePropDesc::from_bytes(&build_battery_level_prop_desc(75)).unwrap();
491        assert_eq!(desc.property_code, DevicePropertyCode::BatteryLevel);
492        assert_eq!(desc.data_type, PropertyDataType::Uint8);
493        assert!(!desc.writable);
494        assert_eq!(desc.current_value, PropertyValue::Uint8(75));
495        assert_eq!(desc.form_type, PropertyFormType::Range);
496        let range = desc.range.unwrap();
497        assert_eq!(range.min, PropertyValue::Uint8(0));
498        assert_eq!(range.max, PropertyValue::Uint8(100));
499    }
500
501    fn build_iso_prop_desc() -> Vec<u8> {
502        let mut buf = Vec::new();
503        buf.extend_from_slice(&pack_u16(0x500F)); // ExposureIndex
504        buf.extend_from_slice(&pack_u16(0x0004)); // UINT16
505        buf.push(0x01); // read-write
506        buf.extend_from_slice(&pack_u16(400)); // default
507        buf.extend_from_slice(&pack_u16(800)); // current
508        buf.push(0x02); // Enumeration
509        buf.extend_from_slice(&pack_u16(4)); // count
510        for iso in [100u16, 200, 400, 800] {
511            buf.extend_from_slice(&pack_u16(iso));
512        }
513        buf
514    }
515
516    #[test]
517    fn device_prop_desc_parse_iso_enumeration() {
518        let desc = DevicePropDesc::from_bytes(&build_iso_prop_desc()).unwrap();
519        assert_eq!(desc.property_code, DevicePropertyCode::ExposureIndex);
520        assert!(desc.writable);
521        assert_eq!(desc.current_value, PropertyValue::Uint16(800));
522        assert_eq!(desc.form_type, PropertyFormType::Enumeration);
523        let values = desc.enum_values.unwrap();
524        assert_eq!(
525            values,
526            vec![
527                PropertyValue::Uint16(100),
528                PropertyValue::Uint16(200),
529                PropertyValue::Uint16(400),
530                PropertyValue::Uint16(800),
531            ]
532        );
533    }
534
535    fn build_datetime_prop_desc() -> Vec<u8> {
536        let mut buf = Vec::new();
537        buf.extend_from_slice(&pack_u16(0x5011)); // DateTime
538        buf.extend_from_slice(&pack_u16(0xFFFF)); // String
539        buf.push(0x01); // read-write
540        buf.push(0x00); // default: empty
541        buf.extend_from_slice(&pack_string("20240315T120000"));
542        buf.push(0x00); // None
543        buf
544    }
545
546    #[test]
547    fn device_prop_desc_parse_datetime_no_form() {
548        let desc = DevicePropDesc::from_bytes(&build_datetime_prop_desc()).unwrap();
549        assert_eq!(desc.property_code, DevicePropertyCode::DateTime);
550        assert_eq!(desc.data_type, PropertyDataType::String);
551        assert_eq!(
552            desc.current_value,
553            PropertyValue::String("20240315T120000".into())
554        );
555        assert_eq!(desc.form_type, PropertyFormType::None);
556    }
557
558    fn build_exposure_bias_prop_desc() -> Vec<u8> {
559        let mut buf = Vec::new();
560        buf.extend_from_slice(&pack_u16(0x5010)); // ExposureBiasCompensation
561        buf.extend_from_slice(&pack_u16(0x0003)); // INT16
562        buf.push(0x01);
563        buf.extend_from_slice(&pack_i16(0)); // default
564        buf.extend_from_slice(&pack_i16(-1000)); // current
565        buf.push(0x01); // Range
566        buf.extend_from_slice(&pack_i16(-3000));
567        buf.extend_from_slice(&pack_i16(3000));
568        buf.extend_from_slice(&pack_i16(333));
569        buf
570    }
571
572    #[test]
573    fn device_prop_desc_parse_exposure_bias_signed() {
574        let desc = DevicePropDesc::from_bytes(&build_exposure_bias_prop_desc()).unwrap();
575        assert_eq!(
576            desc.property_code,
577            DevicePropertyCode::ExposureBiasCompensation
578        );
579        assert_eq!(desc.current_value, PropertyValue::Int16(-1000));
580        let range = desc.range.unwrap();
581        assert_eq!(range.min, PropertyValue::Int16(-3000));
582        assert_eq!(range.max, PropertyValue::Int16(3000));
583    }
584
585    #[test]
586    fn device_prop_desc_parse_insufficient_bytes() {
587        assert!(DevicePropDesc::from_bytes(&[0x01]).is_err());
588        assert!(DevicePropDesc::from_bytes(&[0x01, 0x50]).is_err());
589    }
590
591    #[test]
592    fn device_prop_desc_minimum_valid() {
593        assert!(DevicePropDesc::from_bytes(&[]).is_err());
594        assert!(DevicePropDesc::from_bytes(&[0; 4]).is_err());
595    }
596
597    // --- Property-based tests ---
598
599    proptest! {
600        #[test]
601        fn prop_property_form_type_known_roundtrip(code in 0u8..=2u8) {
602            let form = PropertyFormType::from_code(code);
603            prop_assert_eq!(form.to_code(), code);
604        }
605
606        #[test]
607        fn prop_property_form_type_unknown_preserves_code(code in 3u8..=u8::MAX) {
608            let form = PropertyFormType::from_code(code);
609            prop_assert_eq!(form, PropertyFormType::Unknown(code));
610            prop_assert_eq!(form.to_code(), code);
611        }
612
613        #[test]
614        fn prop_property_value_int8_roundtrip(val: i8) {
615            let pv = PropertyValue::Int8(val);
616            let (parsed, _) = PropertyValue::from_bytes(&pv.to_bytes(), PropertyDataType::Int8).unwrap();
617            prop_assert_eq!(parsed, pv);
618        }
619
620        #[test]
621        fn prop_property_value_int16_roundtrip(val: i16) {
622            let pv = PropertyValue::Int16(val);
623            let (parsed, _) = PropertyValue::from_bytes(&pv.to_bytes(), PropertyDataType::Int16).unwrap();
624            prop_assert_eq!(parsed, pv);
625        }
626
627        #[test]
628        fn prop_property_value_int32_roundtrip(val: i32) {
629            let pv = PropertyValue::Int32(val);
630            let (parsed, _) = PropertyValue::from_bytes(&pv.to_bytes(), PropertyDataType::Int32).unwrap();
631            prop_assert_eq!(parsed, pv);
632        }
633
634        #[test]
635        fn prop_property_value_int64_roundtrip(val: i64) {
636            let pv = PropertyValue::Int64(val);
637            let (parsed, _) = PropertyValue::from_bytes(&pv.to_bytes(), PropertyDataType::Int64).unwrap();
638            prop_assert_eq!(parsed, pv);
639        }
640
641        #[test]
642        fn prop_property_value_uint16_roundtrip(val: u16) {
643            let pv = PropertyValue::Uint16(val);
644            let (parsed, _) = PropertyValue::from_bytes(&pv.to_bytes(), PropertyDataType::Uint16).unwrap();
645            prop_assert_eq!(parsed, pv);
646        }
647
648        #[test]
649        fn prop_property_value_uint32_roundtrip(val: u32) {
650            let pv = PropertyValue::Uint32(val);
651            let (parsed, _) = PropertyValue::from_bytes(&pv.to_bytes(), PropertyDataType::Uint32).unwrap();
652            prop_assert_eq!(parsed, pv);
653        }
654
655        #[test]
656        fn prop_property_value_uint64_roundtrip(val: u64) {
657            let pv = PropertyValue::Uint64(val);
658            let (parsed, _) = PropertyValue::from_bytes(&pv.to_bytes(), PropertyDataType::Uint64).unwrap();
659            prop_assert_eq!(parsed, pv);
660        }
661
662        #[test]
663        fn prop_property_range_uint8_roundtrip(min: u8, max: u8, step: u8) {
664            let range = PropertyRange {
665                min: PropertyValue::Uint8(min),
666                max: PropertyValue::Uint8(max),
667                step: PropertyValue::Uint8(step),
668            };
669            let (parsed, _) = PropertyRange::from_bytes(&range.to_bytes(), PropertyDataType::Uint8).unwrap();
670            prop_assert_eq!(parsed.min, range.min);
671            prop_assert_eq!(parsed.max, range.max);
672            prop_assert_eq!(parsed.step, range.step);
673        }
674
675        #[test]
676        fn prop_property_range_uint16_roundtrip(min: u16, max: u16, step: u16) {
677            let range = PropertyRange {
678                min: PropertyValue::Uint16(min),
679                max: PropertyValue::Uint16(max),
680                step: PropertyValue::Uint16(step),
681            };
682            let (parsed, _) = PropertyRange::from_bytes(&range.to_bytes(), PropertyDataType::Uint16).unwrap();
683            prop_assert_eq!(parsed.min, range.min);
684            prop_assert_eq!(parsed.max, range.max);
685        }
686
687        #[test]
688        fn prop_property_range_int16_roundtrip(min: i16, max: i16, step: i16) {
689            let range = PropertyRange {
690                min: PropertyValue::Int16(min),
691                max: PropertyValue::Int16(max),
692                step: PropertyValue::Int16(step),
693            };
694            let (parsed, _) = PropertyRange::from_bytes(&range.to_bytes(), PropertyDataType::Int16).unwrap();
695            prop_assert_eq!(parsed.min, range.min);
696            prop_assert_eq!(parsed.max, range.max);
697        }
698
699        #[test]
700        fn prop_property_range_uint32_roundtrip(min: u32, max: u32, step: u32) {
701            let range = PropertyRange {
702                min: PropertyValue::Uint32(min),
703                max: PropertyValue::Uint32(max),
704                step: PropertyValue::Uint32(step),
705            };
706            let (parsed, _) = PropertyRange::from_bytes(&range.to_bytes(), PropertyDataType::Uint32).unwrap();
707            prop_assert_eq!(parsed.min, range.min);
708            prop_assert_eq!(parsed.max, range.max);
709        }
710    }
711
712    // --- Fuzz tests ---
713
714    fn valid_property_string() -> impl Strategy<Value = String> {
715        prop::collection::vec(
716            prop::char::range('\u{0000}', '\u{D7FF}')
717                .prop_union(prop::char::range('\u{E000}', '\u{FFFF}')),
718            0..50,
719        )
720        .prop_map(|chars| chars.into_iter().collect::<String>())
721    }
722
723    proptest! {
724        #[test]
725        fn prop_property_value_string_roundtrip(s in valid_property_string()) {
726            let s = if s.chars().count() > 254 { s.chars().take(254).collect() } else { s };
727            let pv = PropertyValue::String(s.clone());
728            let (parsed, _) = PropertyValue::from_bytes(&pv.to_bytes(), PropertyDataType::String).unwrap();
729            prop_assert_eq!(parsed, PropertyValue::String(s));
730        }
731
732        #[test]
733        fn fuzz_property_value_truncated(data_type_code in 1u16..=8u16, bytes in prop::collection::vec(any::<u8>(), 0..20)) {
734            let _ = PropertyValue::from_bytes(&bytes, PropertyDataType::from(data_type_code));
735        }
736
737        #[test]
738        fn fuzz_property_value_empty(data_type_code in 1u16..=8u16) {
739            prop_assert!(PropertyValue::from_bytes(&[], PropertyDataType::from(data_type_code)).is_err());
740        }
741
742        #[test]
743        fn fuzz_property_value_string_garbage(bytes in prop::collection::vec(any::<u8>(), 0..50)) {
744            let _ = PropertyValue::from_bytes(&bytes, PropertyDataType::String);
745        }
746
747        #[test]
748        fn fuzz_property_value_unsupported(bytes in prop::collection::vec(any::<u8>(), 0..20)) {
749            prop_assert!(PropertyValue::from_bytes(&bytes, PropertyDataType::Undefined).is_err());
750            prop_assert!(PropertyValue::from_bytes(&bytes, PropertyDataType::Int128).is_err());
751            prop_assert!(PropertyValue::from_bytes(&bytes, PropertyDataType::Uint128).is_err());
752        }
753
754        #[test]
755        fn fuzz_property_value_unknown_type(unknown_code in 11u16..=0xFFFEu16, bytes in prop::collection::vec(any::<u8>(), 0..20)) {
756            prop_assert!(PropertyValue::from_bytes(&bytes, PropertyDataType::Unknown(unknown_code)).is_err());
757        }
758
759        #[test]
760        fn fuzz_property_range_truncated(data_type_code in 1u16..=8u16, bytes in prop::collection::vec(any::<u8>(), 0..20)) {
761            let _ = PropertyRange::from_bytes(&bytes, PropertyDataType::from(data_type_code));
762        }
763
764        #[test]
765        fn fuzz_property_range_wrong_type(bytes in prop::collection::vec(any::<u8>(), 0..20)) {
766            prop_assert!(PropertyRange::from_bytes(&bytes, PropertyDataType::Undefined).is_err());
767            prop_assert!(PropertyRange::from_bytes(&bytes, PropertyDataType::Unknown(0x1234)).is_err());
768        }
769    }
770
771    crate::fuzz_bytes!(fuzz_device_prop_desc, DevicePropDesc, 200);
772}