use crate::ptp::codes::{DevicePropertyCode, PropertyDataType};
use crate::ptp::pack::{
pack_i16, pack_i32, pack_i64, pack_i8, pack_string, pack_u16, pack_u32, pack_u64, pack_u8,
unpack_i16, unpack_i32, unpack_i64, unpack_i8, unpack_string, unpack_u16, unpack_u64,
unpack_u8,
};
#[derive(Debug, Clone, PartialEq)]
pub enum PropertyValue {
Int8(i8),
Uint8(u8),
Int16(i16),
Uint16(u16),
Int32(i32),
Uint32(u32),
Int64(i64),
Uint64(u64),
String(String),
}
impl PropertyValue {
pub fn to_bytes(&self) -> Vec<u8> {
match self {
PropertyValue::Int8(v) => pack_i8(*v).to_vec(),
PropertyValue::Uint8(v) => pack_u8(*v).to_vec(),
PropertyValue::Int16(v) => pack_i16(*v).to_vec(),
PropertyValue::Uint16(v) => pack_u16(*v).to_vec(),
PropertyValue::Int32(v) => pack_i32(*v).to_vec(),
PropertyValue::Uint32(v) => pack_u32(*v).to_vec(),
PropertyValue::Int64(v) => pack_i64(*v).to_vec(),
PropertyValue::Uint64(v) => pack_u64(*v).to_vec(),
PropertyValue::String(v) => pack_string(v),
}
}
pub fn from_bytes(
buf: &[u8],
data_type: PropertyDataType,
) -> Result<(Self, usize), crate::Error> {
match data_type {
PropertyDataType::Int8 => {
let val = unpack_i8(buf)?;
Ok((PropertyValue::Int8(val), 1))
}
PropertyDataType::Uint8 => {
let val = unpack_u8(buf)?;
Ok((PropertyValue::Uint8(val), 1))
}
PropertyDataType::Int16 => {
let val = unpack_i16(buf)?;
Ok((PropertyValue::Int16(val), 2))
}
PropertyDataType::Uint16 => {
let val = unpack_u16(buf)?;
Ok((PropertyValue::Uint16(val), 2))
}
PropertyDataType::Int32 => {
let val = unpack_i32(buf)?;
Ok((PropertyValue::Int32(val), 4))
}
PropertyDataType::Uint32 => {
let val = crate::ptp::pack::unpack_u32(buf)?;
Ok((PropertyValue::Uint32(val), 4))
}
PropertyDataType::Int64 => {
let val = unpack_i64(buf)?;
Ok((PropertyValue::Int64(val), 8))
}
PropertyDataType::Uint64 => {
let val = unpack_u64(buf)?;
Ok((PropertyValue::Uint64(val), 8))
}
PropertyDataType::String => {
let (val, consumed) = unpack_string(buf)?;
Ok((PropertyValue::String(val), consumed))
}
PropertyDataType::Undefined
| PropertyDataType::Int128
| PropertyDataType::Uint128
| PropertyDataType::Unknown(_) => Err(crate::Error::invalid_data(format!(
"unsupported property data type: {:?}",
data_type
))),
}
}
#[must_use]
pub fn data_type(&self) -> PropertyDataType {
match self {
PropertyValue::Int8(_) => PropertyDataType::Int8,
PropertyValue::Uint8(_) => PropertyDataType::Uint8,
PropertyValue::Int16(_) => PropertyDataType::Int16,
PropertyValue::Uint16(_) => PropertyDataType::Uint16,
PropertyValue::Int32(_) => PropertyDataType::Int32,
PropertyValue::Uint32(_) => PropertyDataType::Uint32,
PropertyValue::Int64(_) => PropertyDataType::Int64,
PropertyValue::Uint64(_) => PropertyDataType::Uint64,
PropertyValue::String(_) => PropertyDataType::String,
}
}
}
#[derive(Debug, Clone, Copy, PartialEq, Eq, Default)]
pub enum PropertyFormType {
#[default]
None,
Range,
Enumeration,
Unknown(u8),
}
impl PropertyFormType {
#[must_use]
pub fn from_code(code: u8) -> Self {
match code {
0x00 => PropertyFormType::None,
0x01 => PropertyFormType::Range,
0x02 => PropertyFormType::Enumeration,
_ => PropertyFormType::Unknown(code),
}
}
#[must_use]
pub fn to_code(self) -> u8 {
match self {
PropertyFormType::None => 0x00,
PropertyFormType::Range => 0x01,
PropertyFormType::Enumeration => 0x02,
PropertyFormType::Unknown(code) => code,
}
}
}
#[derive(Debug, Clone, PartialEq)]
pub struct PropertyRange {
pub min: PropertyValue,
pub max: PropertyValue,
pub step: PropertyValue,
}
impl PropertyRange {
pub fn from_bytes(
buf: &[u8],
data_type: PropertyDataType,
) -> Result<(Self, usize), crate::Error> {
let mut offset = 0;
let (min, consumed) = PropertyValue::from_bytes(&buf[offset..], data_type)?;
offset += consumed;
let (max, consumed) = PropertyValue::from_bytes(&buf[offset..], data_type)?;
offset += consumed;
let (step, consumed) = PropertyValue::from_bytes(&buf[offset..], data_type)?;
offset += consumed;
Ok((PropertyRange { min, max, step }, offset))
}
pub fn to_bytes(&self) -> Vec<u8> {
let mut buf = Vec::new();
buf.extend_from_slice(&self.min.to_bytes());
buf.extend_from_slice(&self.max.to_bytes());
buf.extend_from_slice(&self.step.to_bytes());
buf
}
}
#[derive(Debug, Clone)]
pub struct DevicePropDesc {
pub property_code: DevicePropertyCode,
pub data_type: PropertyDataType,
pub writable: bool,
pub default_value: PropertyValue,
pub current_value: PropertyValue,
pub form_type: PropertyFormType,
pub enum_values: Option<Vec<PropertyValue>>,
pub range: Option<PropertyRange>,
}
impl DevicePropDesc {
pub fn from_bytes(buf: &[u8]) -> Result<Self, crate::Error> {
let mut offset = 0;
let property_code = DevicePropertyCode::from(unpack_u16(&buf[offset..])?);
offset += 2;
let data_type = PropertyDataType::from(unpack_u16(&buf[offset..])?);
offset += 2;
if buf.len() <= offset {
return Err(crate::Error::invalid_data(
"DevicePropDesc: insufficient bytes for GetSet",
));
}
let writable = buf[offset] != 0x00;
offset += 1;
let (default_value, consumed) = PropertyValue::from_bytes(&buf[offset..], data_type)?;
offset += consumed;
let (current_value, consumed) = PropertyValue::from_bytes(&buf[offset..], data_type)?;
offset += consumed;
if buf.len() <= offset {
return Err(crate::Error::invalid_data(
"DevicePropDesc: insufficient bytes for FormFlag",
));
}
let form_type = PropertyFormType::from_code(buf[offset]);
offset += 1;
let (enum_values, range) = match form_type {
PropertyFormType::None | PropertyFormType::Unknown(_) => (None, None),
PropertyFormType::Range => {
let (range, _consumed) = PropertyRange::from_bytes(&buf[offset..], data_type)?;
(None, Some(range))
}
PropertyFormType::Enumeration => {
let count = unpack_u16(&buf[offset..])? as usize;
offset += 2;
let mut values = Vec::with_capacity(count);
for _ in 0..count {
let (val, consumed) = PropertyValue::from_bytes(&buf[offset..], data_type)?;
values.push(val);
offset += consumed;
}
(Some(values), None)
}
};
Ok(DevicePropDesc {
property_code,
data_type,
writable,
default_value,
current_value,
form_type,
enum_values,
range,
})
}
}
#[cfg(test)]
mod tests {
use super::*;
use crate::ptp::pack::{pack_i16, pack_u16};
use proptest::prelude::*;
#[test]
fn property_value_roundtrip() {
let values = [
PropertyValue::Int8(-42),
PropertyValue::Uint8(100),
PropertyValue::Int16(-1000),
PropertyValue::Uint16(5000),
PropertyValue::Int32(-100000),
PropertyValue::Uint32(100000),
PropertyValue::Int64(-1_000_000_000),
PropertyValue::Uint64(1_000_000_000),
PropertyValue::String("Test".to_string()),
];
for val in &values {
let bytes = val.to_bytes();
let (parsed, _) = PropertyValue::from_bytes(&bytes, val.data_type()).unwrap();
assert_eq!(&parsed, val);
}
}
#[test]
fn property_value_data_type() {
let cases: &[(PropertyValue, PropertyDataType)] = &[
(PropertyValue::Int8(0), PropertyDataType::Int8),
(PropertyValue::Uint8(0), PropertyDataType::Uint8),
(PropertyValue::Int16(0), PropertyDataType::Int16),
(PropertyValue::Uint16(0), PropertyDataType::Uint16),
(PropertyValue::Int32(0), PropertyDataType::Int32),
(PropertyValue::Uint32(0), PropertyDataType::Uint32),
(PropertyValue::Int64(0), PropertyDataType::Int64),
(PropertyValue::Uint64(0), PropertyDataType::Uint64),
(PropertyValue::String("".into()), PropertyDataType::String),
];
for (val, expected) in cases {
assert_eq!(val.data_type(), *expected);
}
}
#[test]
fn property_value_from_bytes_unsupported_type() {
for dt in [
PropertyDataType::Undefined,
PropertyDataType::Int128,
PropertyDataType::Uint128,
PropertyDataType::Unknown(0x99),
] {
assert!(PropertyValue::from_bytes(&[0x00], dt).is_err());
}
}
#[test]
fn property_value_from_bytes_insufficient_bytes() {
assert!(PropertyValue::from_bytes(&[], PropertyDataType::Int8).is_err());
assert!(PropertyValue::from_bytes(&[0x00], PropertyDataType::Int16).is_err());
assert!(PropertyValue::from_bytes(&[0x00, 0x00], PropertyDataType::Int32).is_err());
assert!(PropertyValue::from_bytes(&[0x00; 7], PropertyDataType::Int64).is_err());
}
#[test]
fn property_form_type_from_code() {
for (code, expected) in [
(0x00, PropertyFormType::None),
(0x01, PropertyFormType::Range),
(0x02, PropertyFormType::Enumeration),
] {
assert_eq!(PropertyFormType::from_code(code), expected);
assert_eq!(expected.to_code(), code);
}
assert_eq!(
PropertyFormType::from_code(0x99),
PropertyFormType::Unknown(0x99)
);
}
#[test]
fn property_form_type_to_code() {
assert_eq!(PropertyFormType::None.to_code(), 0x00);
assert_eq!(PropertyFormType::Range.to_code(), 0x01);
assert_eq!(PropertyFormType::Enumeration.to_code(), 0x02);
assert_eq!(PropertyFormType::Unknown(0x99).to_code(), 0x99);
}
#[test]
fn property_form_type_roundtrip() {
for f in [
PropertyFormType::None,
PropertyFormType::Range,
PropertyFormType::Enumeration,
] {
assert_eq!(PropertyFormType::from_code(f.to_code()), f);
}
}
#[test]
fn property_range_from_bytes_uint8() {
let buf = vec![0x00, 0x64, 0x01]; let (range, consumed) = PropertyRange::from_bytes(&buf, PropertyDataType::Uint8).unwrap();
assert_eq!(range.min, PropertyValue::Uint8(0));
assert_eq!(range.max, PropertyValue::Uint8(100));
assert_eq!(range.step, PropertyValue::Uint8(1));
assert_eq!(consumed, 3);
}
#[test]
fn property_range_from_bytes_uint16() {
let buf = vec![0x64, 0x00, 0x00, 0x19, 0x64, 0x00]; let (range, consumed) = PropertyRange::from_bytes(&buf, PropertyDataType::Uint16).unwrap();
assert_eq!(range.min, PropertyValue::Uint16(100));
assert_eq!(range.max, PropertyValue::Uint16(6400));
assert_eq!(range.step, PropertyValue::Uint16(100));
assert_eq!(consumed, 6);
}
#[test]
fn property_range_to_bytes() {
let range = PropertyRange {
min: PropertyValue::Uint8(0),
max: PropertyValue::Uint8(100),
step: PropertyValue::Uint8(1),
};
assert_eq!(range.to_bytes(), vec![0x00, 0x64, 0x01]);
}
#[test]
fn property_range_roundtrip() {
let range = PropertyRange {
min: PropertyValue::Uint16(100),
max: PropertyValue::Uint16(6400),
step: PropertyValue::Uint16(100),
};
let bytes = range.to_bytes();
let (parsed, _) = PropertyRange::from_bytes(&bytes, PropertyDataType::Uint16).unwrap();
assert_eq!(parsed.min, range.min);
assert_eq!(parsed.max, range.max);
assert_eq!(parsed.step, range.step);
}
fn build_battery_level_prop_desc(current: u8) -> Vec<u8> {
let mut buf = Vec::new();
buf.extend_from_slice(&pack_u16(0x5001)); buf.extend_from_slice(&pack_u16(0x0002)); buf.push(0x00); buf.push(100); buf.push(current); buf.push(0x01); buf.extend([0, 100, 1]); buf
}
#[test]
fn device_prop_desc_parse_battery_level() {
let desc = DevicePropDesc::from_bytes(&build_battery_level_prop_desc(75)).unwrap();
assert_eq!(desc.property_code, DevicePropertyCode::BatteryLevel);
assert_eq!(desc.data_type, PropertyDataType::Uint8);
assert!(!desc.writable);
assert_eq!(desc.current_value, PropertyValue::Uint8(75));
assert_eq!(desc.form_type, PropertyFormType::Range);
let range = desc.range.unwrap();
assert_eq!(range.min, PropertyValue::Uint8(0));
assert_eq!(range.max, PropertyValue::Uint8(100));
}
fn build_iso_prop_desc() -> Vec<u8> {
let mut buf = Vec::new();
buf.extend_from_slice(&pack_u16(0x500F)); buf.extend_from_slice(&pack_u16(0x0004)); buf.push(0x01); buf.extend_from_slice(&pack_u16(400)); buf.extend_from_slice(&pack_u16(800)); buf.push(0x02); buf.extend_from_slice(&pack_u16(4)); for iso in [100u16, 200, 400, 800] {
buf.extend_from_slice(&pack_u16(iso));
}
buf
}
#[test]
fn device_prop_desc_parse_iso_enumeration() {
let desc = DevicePropDesc::from_bytes(&build_iso_prop_desc()).unwrap();
assert_eq!(desc.property_code, DevicePropertyCode::ExposureIndex);
assert!(desc.writable);
assert_eq!(desc.current_value, PropertyValue::Uint16(800));
assert_eq!(desc.form_type, PropertyFormType::Enumeration);
let values = desc.enum_values.unwrap();
assert_eq!(
values,
vec![
PropertyValue::Uint16(100),
PropertyValue::Uint16(200),
PropertyValue::Uint16(400),
PropertyValue::Uint16(800),
]
);
}
fn build_datetime_prop_desc() -> Vec<u8> {
let mut buf = Vec::new();
buf.extend_from_slice(&pack_u16(0x5011)); buf.extend_from_slice(&pack_u16(0xFFFF)); buf.push(0x01); buf.push(0x00); buf.extend_from_slice(&pack_string("20240315T120000"));
buf.push(0x00); buf
}
#[test]
fn device_prop_desc_parse_datetime_no_form() {
let desc = DevicePropDesc::from_bytes(&build_datetime_prop_desc()).unwrap();
assert_eq!(desc.property_code, DevicePropertyCode::DateTime);
assert_eq!(desc.data_type, PropertyDataType::String);
assert_eq!(
desc.current_value,
PropertyValue::String("20240315T120000".into())
);
assert_eq!(desc.form_type, PropertyFormType::None);
}
fn build_exposure_bias_prop_desc() -> Vec<u8> {
let mut buf = Vec::new();
buf.extend_from_slice(&pack_u16(0x5010)); buf.extend_from_slice(&pack_u16(0x0003)); buf.push(0x01);
buf.extend_from_slice(&pack_i16(0)); buf.extend_from_slice(&pack_i16(-1000)); buf.push(0x01); buf.extend_from_slice(&pack_i16(-3000));
buf.extend_from_slice(&pack_i16(3000));
buf.extend_from_slice(&pack_i16(333));
buf
}
#[test]
fn device_prop_desc_parse_exposure_bias_signed() {
let desc = DevicePropDesc::from_bytes(&build_exposure_bias_prop_desc()).unwrap();
assert_eq!(
desc.property_code,
DevicePropertyCode::ExposureBiasCompensation
);
assert_eq!(desc.current_value, PropertyValue::Int16(-1000));
let range = desc.range.unwrap();
assert_eq!(range.min, PropertyValue::Int16(-3000));
assert_eq!(range.max, PropertyValue::Int16(3000));
}
#[test]
fn device_prop_desc_parse_insufficient_bytes() {
assert!(DevicePropDesc::from_bytes(&[0x01]).is_err());
assert!(DevicePropDesc::from_bytes(&[0x01, 0x50]).is_err());
}
#[test]
fn device_prop_desc_minimum_valid() {
assert!(DevicePropDesc::from_bytes(&[]).is_err());
assert!(DevicePropDesc::from_bytes(&[0; 4]).is_err());
}
proptest! {
#[test]
fn prop_property_form_type_known_roundtrip(code in 0u8..=2u8) {
let form = PropertyFormType::from_code(code);
prop_assert_eq!(form.to_code(), code);
}
#[test]
fn prop_property_form_type_unknown_preserves_code(code in 3u8..=u8::MAX) {
let form = PropertyFormType::from_code(code);
prop_assert_eq!(form, PropertyFormType::Unknown(code));
prop_assert_eq!(form.to_code(), code);
}
#[test]
fn prop_property_value_int8_roundtrip(val: i8) {
let pv = PropertyValue::Int8(val);
let (parsed, _) = PropertyValue::from_bytes(&pv.to_bytes(), PropertyDataType::Int8).unwrap();
prop_assert_eq!(parsed, pv);
}
#[test]
fn prop_property_value_int16_roundtrip(val: i16) {
let pv = PropertyValue::Int16(val);
let (parsed, _) = PropertyValue::from_bytes(&pv.to_bytes(), PropertyDataType::Int16).unwrap();
prop_assert_eq!(parsed, pv);
}
#[test]
fn prop_property_value_int32_roundtrip(val: i32) {
let pv = PropertyValue::Int32(val);
let (parsed, _) = PropertyValue::from_bytes(&pv.to_bytes(), PropertyDataType::Int32).unwrap();
prop_assert_eq!(parsed, pv);
}
#[test]
fn prop_property_value_int64_roundtrip(val: i64) {
let pv = PropertyValue::Int64(val);
let (parsed, _) = PropertyValue::from_bytes(&pv.to_bytes(), PropertyDataType::Int64).unwrap();
prop_assert_eq!(parsed, pv);
}
#[test]
fn prop_property_value_uint16_roundtrip(val: u16) {
let pv = PropertyValue::Uint16(val);
let (parsed, _) = PropertyValue::from_bytes(&pv.to_bytes(), PropertyDataType::Uint16).unwrap();
prop_assert_eq!(parsed, pv);
}
#[test]
fn prop_property_value_uint32_roundtrip(val: u32) {
let pv = PropertyValue::Uint32(val);
let (parsed, _) = PropertyValue::from_bytes(&pv.to_bytes(), PropertyDataType::Uint32).unwrap();
prop_assert_eq!(parsed, pv);
}
#[test]
fn prop_property_value_uint64_roundtrip(val: u64) {
let pv = PropertyValue::Uint64(val);
let (parsed, _) = PropertyValue::from_bytes(&pv.to_bytes(), PropertyDataType::Uint64).unwrap();
prop_assert_eq!(parsed, pv);
}
#[test]
fn prop_property_range_uint8_roundtrip(min: u8, max: u8, step: u8) {
let range = PropertyRange {
min: PropertyValue::Uint8(min),
max: PropertyValue::Uint8(max),
step: PropertyValue::Uint8(step),
};
let (parsed, _) = PropertyRange::from_bytes(&range.to_bytes(), PropertyDataType::Uint8).unwrap();
prop_assert_eq!(parsed.min, range.min);
prop_assert_eq!(parsed.max, range.max);
prop_assert_eq!(parsed.step, range.step);
}
#[test]
fn prop_property_range_uint16_roundtrip(min: u16, max: u16, step: u16) {
let range = PropertyRange {
min: PropertyValue::Uint16(min),
max: PropertyValue::Uint16(max),
step: PropertyValue::Uint16(step),
};
let (parsed, _) = PropertyRange::from_bytes(&range.to_bytes(), PropertyDataType::Uint16).unwrap();
prop_assert_eq!(parsed.min, range.min);
prop_assert_eq!(parsed.max, range.max);
}
#[test]
fn prop_property_range_int16_roundtrip(min: i16, max: i16, step: i16) {
let range = PropertyRange {
min: PropertyValue::Int16(min),
max: PropertyValue::Int16(max),
step: PropertyValue::Int16(step),
};
let (parsed, _) = PropertyRange::from_bytes(&range.to_bytes(), PropertyDataType::Int16).unwrap();
prop_assert_eq!(parsed.min, range.min);
prop_assert_eq!(parsed.max, range.max);
}
#[test]
fn prop_property_range_uint32_roundtrip(min: u32, max: u32, step: u32) {
let range = PropertyRange {
min: PropertyValue::Uint32(min),
max: PropertyValue::Uint32(max),
step: PropertyValue::Uint32(step),
};
let (parsed, _) = PropertyRange::from_bytes(&range.to_bytes(), PropertyDataType::Uint32).unwrap();
prop_assert_eq!(parsed.min, range.min);
prop_assert_eq!(parsed.max, range.max);
}
}
fn valid_property_string() -> impl Strategy<Value = String> {
prop::collection::vec(
prop::char::range('\u{0000}', '\u{D7FF}')
.prop_union(prop::char::range('\u{E000}', '\u{FFFF}')),
0..50,
)
.prop_map(|chars| chars.into_iter().collect::<String>())
}
proptest! {
#[test]
fn prop_property_value_string_roundtrip(s in valid_property_string()) {
let s = if s.chars().count() > 254 { s.chars().take(254).collect() } else { s };
let pv = PropertyValue::String(s.clone());
let (parsed, _) = PropertyValue::from_bytes(&pv.to_bytes(), PropertyDataType::String).unwrap();
prop_assert_eq!(parsed, PropertyValue::String(s));
}
#[test]
fn fuzz_property_value_truncated(data_type_code in 1u16..=8u16, bytes in prop::collection::vec(any::<u8>(), 0..20)) {
let _ = PropertyValue::from_bytes(&bytes, PropertyDataType::from(data_type_code));
}
#[test]
fn fuzz_property_value_empty(data_type_code in 1u16..=8u16) {
prop_assert!(PropertyValue::from_bytes(&[], PropertyDataType::from(data_type_code)).is_err());
}
#[test]
fn fuzz_property_value_string_garbage(bytes in prop::collection::vec(any::<u8>(), 0..50)) {
let _ = PropertyValue::from_bytes(&bytes, PropertyDataType::String);
}
#[test]
fn fuzz_property_value_unsupported(bytes in prop::collection::vec(any::<u8>(), 0..20)) {
prop_assert!(PropertyValue::from_bytes(&bytes, PropertyDataType::Undefined).is_err());
prop_assert!(PropertyValue::from_bytes(&bytes, PropertyDataType::Int128).is_err());
prop_assert!(PropertyValue::from_bytes(&bytes, PropertyDataType::Uint128).is_err());
}
#[test]
fn fuzz_property_value_unknown_type(unknown_code in 11u16..=0xFFFEu16, bytes in prop::collection::vec(any::<u8>(), 0..20)) {
prop_assert!(PropertyValue::from_bytes(&bytes, PropertyDataType::Unknown(unknown_code)).is_err());
}
#[test]
fn fuzz_property_range_truncated(data_type_code in 1u16..=8u16, bytes in prop::collection::vec(any::<u8>(), 0..20)) {
let _ = PropertyRange::from_bytes(&bytes, PropertyDataType::from(data_type_code));
}
#[test]
fn fuzz_property_range_wrong_type(bytes in prop::collection::vec(any::<u8>(), 0..20)) {
prop_assert!(PropertyRange::from_bytes(&bytes, PropertyDataType::Undefined).is_err());
prop_assert!(PropertyRange::from_bytes(&bytes, PropertyDataType::Unknown(0x1234)).is_err());
}
}
crate::fuzz_bytes!(fuzz_device_prop_desc, DevicePropDesc, 200);
}