use super::storage::{AssociationType, ProtectionStatus};
use crate::ptp::pack::{
pack_datetime, pack_string, pack_u16, pack_u32, unpack_datetime, unpack_string, unpack_u16,
unpack_u32, DateTime,
};
use crate::ptp::{ObjectFormatCode, ObjectHandle, StorageId};
#[derive(Debug, Clone, Default)]
pub struct ObjectInfo {
pub handle: ObjectHandle,
pub storage_id: StorageId,
pub format: ObjectFormatCode,
pub protection_status: ProtectionStatus,
pub size: u64,
pub thumb_format: ObjectFormatCode,
pub thumb_size: u32,
pub thumb_width: u32,
pub thumb_height: u32,
pub image_width: u32,
pub image_height: u32,
pub image_bit_depth: u32,
pub parent: ObjectHandle,
pub association_type: AssociationType,
pub association_desc: u32,
pub sequence_number: u32,
pub filename: String,
pub created: Option<DateTime>,
pub modified: Option<DateTime>,
pub keywords: String,
}
impl ObjectInfo {
pub fn from_bytes(buf: &[u8]) -> Result<Self, crate::Error> {
let mut offset = 0;
let storage_id = StorageId(unpack_u32(&buf[offset..])?);
offset += 4;
let format = ObjectFormatCode::from(unpack_u16(&buf[offset..])?);
offset += 2;
let protection_status = ProtectionStatus::from(unpack_u16(&buf[offset..])?);
offset += 2;
let size = unpack_u32(&buf[offset..])? as u64;
offset += 4;
let thumb_format = ObjectFormatCode::from(unpack_u16(&buf[offset..])?);
offset += 2;
let thumb_size = unpack_u32(&buf[offset..])?;
offset += 4;
let thumb_width = unpack_u32(&buf[offset..])?;
offset += 4;
let thumb_height = unpack_u32(&buf[offset..])?;
offset += 4;
let image_width = unpack_u32(&buf[offset..])?;
offset += 4;
let image_height = unpack_u32(&buf[offset..])?;
offset += 4;
let image_bit_depth = unpack_u32(&buf[offset..])?;
offset += 4;
let parent = ObjectHandle(unpack_u32(&buf[offset..])?);
offset += 4;
let association_type = AssociationType::from(unpack_u16(&buf[offset..])?);
offset += 2;
let association_desc = unpack_u32(&buf[offset..])?;
offset += 4;
let sequence_number = unpack_u32(&buf[offset..])?;
offset += 4;
let (filename, consumed) = unpack_string(&buf[offset..])?;
offset += consumed;
let (created, consumed) = unpack_datetime(&buf[offset..])?;
offset += consumed;
let (modified, consumed) = unpack_datetime(&buf[offset..])?;
offset += consumed;
let (keywords, _consumed) = unpack_string(&buf[offset..])?;
Ok(ObjectInfo {
handle: ObjectHandle::default(), storage_id,
format,
protection_status,
size,
thumb_format,
thumb_size,
thumb_width,
thumb_height,
image_width,
image_height,
image_bit_depth,
parent,
association_type,
association_desc,
sequence_number,
filename,
created,
modified,
keywords,
})
}
pub fn to_bytes(&self) -> Result<Vec<u8>, crate::Error> {
let mut buf = Vec::new();
buf.extend_from_slice(&pack_u32(self.storage_id.0));
buf.extend_from_slice(&pack_u16(self.format.into()));
buf.extend_from_slice(&pack_u16(self.protection_status.into()));
let size_u32 = if self.size > u32::MAX as u64 {
u32::MAX
} else {
self.size as u32
};
buf.extend_from_slice(&pack_u32(size_u32));
buf.extend_from_slice(&pack_u16(self.thumb_format.into()));
buf.extend_from_slice(&pack_u32(self.thumb_size));
buf.extend_from_slice(&pack_u32(self.thumb_width));
buf.extend_from_slice(&pack_u32(self.thumb_height));
buf.extend_from_slice(&pack_u32(self.image_width));
buf.extend_from_slice(&pack_u32(self.image_height));
buf.extend_from_slice(&pack_u32(self.image_bit_depth));
buf.extend_from_slice(&pack_u32(self.parent.0));
buf.extend_from_slice(&pack_u16(self.association_type.into()));
buf.extend_from_slice(&pack_u32(self.association_desc));
buf.extend_from_slice(&pack_u32(self.sequence_number));
buf.extend_from_slice(&pack_string(&self.filename));
if let Some(dt) = &self.created {
buf.extend_from_slice(&pack_datetime(dt)?);
} else {
buf.push(0x00); }
if let Some(dt) = &self.modified {
buf.extend_from_slice(&pack_datetime(dt)?);
} else {
buf.push(0x00); }
buf.extend_from_slice(&pack_string(&self.keywords));
Ok(buf)
}
#[must_use]
pub fn is_folder(&self) -> bool {
self.format == ObjectFormatCode::Association
|| self.association_type == AssociationType::GenericFolder
}
#[must_use]
pub fn is_file(&self) -> bool {
!self.is_folder()
}
}
#[cfg(test)]
mod tests {
use super::*;
use crate::ptp::pack::{pack_datetime, pack_string, pack_u16, pack_u32, DateTime};
fn build_file_object_info_bytes() -> Vec<u8> {
let mut buf = Vec::new();
buf.extend_from_slice(&pack_u32(0x00010001));
buf.extend_from_slice(&pack_u16(0x3801));
buf.extend_from_slice(&pack_u16(0));
buf.extend_from_slice(&pack_u32(1024));
buf.extend_from_slice(&pack_u16(0x3801));
buf.extend_from_slice(&pack_u32(512));
buf.extend_from_slice(&pack_u32(160));
buf.extend_from_slice(&pack_u32(120));
buf.extend_from_slice(&pack_u32(1920));
buf.extend_from_slice(&pack_u32(1080));
buf.extend_from_slice(&pack_u32(24));
buf.extend_from_slice(&pack_u32(5));
buf.extend_from_slice(&pack_u16(0));
buf.extend_from_slice(&pack_u32(0));
buf.extend_from_slice(&pack_u32(1));
buf.extend_from_slice(&pack_string("photo.jpg"));
buf.extend_from_slice(
&pack_datetime(&DateTime {
year: 2024,
month: 3,
day: 15,
hour: 14,
minute: 30,
second: 22,
})
.unwrap(),
);
buf.extend_from_slice(
&pack_datetime(&DateTime {
year: 2024,
month: 3,
day: 16,
hour: 9,
minute: 0,
second: 0,
})
.unwrap(),
);
buf.push(0x00);
buf
}
#[test]
fn object_info_parse_file() {
let buf = build_file_object_info_bytes();
let info = ObjectInfo::from_bytes(&buf).unwrap();
assert_eq!(info.storage_id, StorageId(0x00010001));
assert_eq!(info.format, ObjectFormatCode::Jpeg);
assert_eq!(info.protection_status, ProtectionStatus::None);
assert_eq!(info.size, 1024);
assert_eq!(info.thumb_format, ObjectFormatCode::Jpeg);
assert_eq!(info.thumb_size, 512);
assert_eq!(info.thumb_width, 160);
assert_eq!(info.thumb_height, 120);
assert_eq!(info.image_width, 1920);
assert_eq!(info.image_height, 1080);
assert_eq!(info.image_bit_depth, 24);
assert_eq!(info.parent, ObjectHandle(5));
assert_eq!(info.association_type, AssociationType::None);
assert_eq!(info.association_desc, 0);
assert_eq!(info.sequence_number, 1);
assert_eq!(info.filename, "photo.jpg");
assert!(info.created.is_some());
let created = info.created.unwrap();
assert_eq!(created.year, 2024);
assert_eq!(created.month, 3);
assert_eq!(created.day, 15);
assert!(info.modified.is_some());
assert_eq!(info.keywords, "");
assert!(info.is_file());
assert!(!info.is_folder());
}
fn build_folder_object_info_bytes() -> Vec<u8> {
let mut buf = Vec::new();
buf.extend_from_slice(&pack_u32(0x00010001));
buf.extend_from_slice(&pack_u16(0x3001));
buf.extend_from_slice(&pack_u16(0));
buf.extend_from_slice(&pack_u32(0));
buf.extend_from_slice(&pack_u16(0x3000));
buf.extend_from_slice(&pack_u32(0));
buf.extend_from_slice(&pack_u32(0));
buf.extend_from_slice(&pack_u32(0));
buf.extend_from_slice(&pack_u32(0));
buf.extend_from_slice(&pack_u32(0));
buf.extend_from_slice(&pack_u32(0));
buf.extend_from_slice(&pack_u32(0));
buf.extend_from_slice(&pack_u16(1));
buf.extend_from_slice(&pack_u32(0));
buf.extend_from_slice(&pack_u32(0));
buf.extend_from_slice(&pack_string("DCIM"));
buf.push(0x00);
buf.push(0x00);
buf.push(0x00);
buf
}
#[test]
fn object_info_parse_folder() {
let buf = build_folder_object_info_bytes();
let info = ObjectInfo::from_bytes(&buf).unwrap();
assert_eq!(info.format, ObjectFormatCode::Association);
assert_eq!(info.association_type, AssociationType::GenericFolder);
assert_eq!(info.filename, "DCIM");
assert_eq!(info.parent, ObjectHandle::ROOT);
assert!(info.created.is_none());
assert!(info.modified.is_none());
assert!(info.is_folder());
assert!(!info.is_file());
}
#[test]
fn object_info_to_bytes_roundtrip() {
let original = ObjectInfo {
handle: ObjectHandle(42),
storage_id: StorageId(0x00010001),
format: ObjectFormatCode::Jpeg,
protection_status: ProtectionStatus::None,
size: 2048,
thumb_format: ObjectFormatCode::Jpeg,
thumb_size: 256,
thumb_width: 80,
thumb_height: 60,
image_width: 800,
image_height: 600,
image_bit_depth: 24,
parent: ObjectHandle(10),
association_type: AssociationType::None,
association_desc: 0,
sequence_number: 5,
filename: "test.jpg".to_string(),
created: Some(DateTime {
year: 2024,
month: 6,
day: 15,
hour: 10,
minute: 30,
second: 0,
}),
modified: Some(DateTime {
year: 2024,
month: 6,
day: 16,
hour: 11,
minute: 45,
second: 30,
}),
keywords: "test,photo".to_string(),
};
let bytes = original.to_bytes().unwrap();
let parsed = ObjectInfo::from_bytes(&bytes).unwrap();
assert_eq!(parsed.storage_id, original.storage_id);
assert_eq!(parsed.format, original.format);
assert_eq!(parsed.protection_status, original.protection_status);
assert_eq!(parsed.size, original.size);
assert_eq!(parsed.thumb_format, original.thumb_format);
assert_eq!(parsed.thumb_size, original.thumb_size);
assert_eq!(parsed.thumb_width, original.thumb_width);
assert_eq!(parsed.thumb_height, original.thumb_height);
assert_eq!(parsed.image_width, original.image_width);
assert_eq!(parsed.image_height, original.image_height);
assert_eq!(parsed.image_bit_depth, original.image_bit_depth);
assert_eq!(parsed.parent, original.parent);
assert_eq!(parsed.association_type, original.association_type);
assert_eq!(parsed.association_desc, original.association_desc);
assert_eq!(parsed.sequence_number, original.sequence_number);
assert_eq!(parsed.filename, original.filename);
assert_eq!(parsed.created, original.created);
assert_eq!(parsed.modified, original.modified);
assert_eq!(parsed.keywords, original.keywords);
}
#[test]
fn object_info_to_bytes_large_size() {
let info = ObjectInfo {
size: 5_000_000_000, ..Default::default()
};
let bytes = info.to_bytes().unwrap();
let parsed = ObjectInfo::from_bytes(&bytes).unwrap();
assert_eq!(parsed.size, u32::MAX as u64);
}
#[test]
fn object_info_is_folder_by_format() {
let info = ObjectInfo {
format: ObjectFormatCode::Association,
association_type: AssociationType::None,
..Default::default()
};
assert!(info.is_folder());
}
#[test]
fn object_info_is_folder_by_association() {
let info = ObjectInfo {
format: ObjectFormatCode::Undefined,
association_type: AssociationType::GenericFolder,
..Default::default()
};
assert!(info.is_folder());
}
#[test]
fn object_info_is_file() {
let info = ObjectInfo {
format: ObjectFormatCode::Jpeg,
association_type: AssociationType::None,
..Default::default()
};
assert!(info.is_file());
assert!(!info.is_folder());
}
#[test]
fn object_info_parse_insufficient_bytes() {
let buf = vec![0x00; 10]; assert!(ObjectInfo::from_bytes(&buf).is_err());
}
#[test]
fn object_info_default() {
let info = ObjectInfo::default();
assert_eq!(info.storage_id, StorageId::default());
assert_eq!(info.format, ObjectFormatCode::Undefined);
assert_eq!(info.protection_status, ProtectionStatus::None);
assert_eq!(info.size, 0);
assert_eq!(info.filename, "");
assert!(info.created.is_none());
assert!(info.modified.is_none());
}
crate::fuzz_bytes!(fuzz_object_info, ObjectInfo, 200);
#[test]
fn object_info_minimum_valid() {
assert!(ObjectInfo::from_bytes(&[]).is_err());
assert!(ObjectInfo::from_bytes(&[0; 51]).is_err());
assert!(ObjectInfo::from_bytes(&[0; 52]).is_err()); }
#[test]
fn object_info_size_u32_max() {
let mut buf = build_file_object_info_bytes();
buf[8] = 0xFF;
buf[9] = 0xFF;
buf[10] = 0xFF;
buf[11] = 0xFF;
let info = ObjectInfo::from_bytes(&buf).unwrap();
assert_eq!(info.size, u32::MAX as u64);
}
}