1use super::storage::{AssociationType, ProtectionStatus};
6use crate::ptp::pack::{
7 pack_datetime, pack_string, pack_u16, pack_u32, unpack_datetime, unpack_string, unpack_u16,
8 unpack_u32, DateTime,
9};
10use crate::ptp::{ObjectFormatCode, ObjectHandle, StorageId};
11
12#[derive(Debug, Clone, Default)]
18pub struct ObjectInfo {
19 pub handle: ObjectHandle,
21 pub storage_id: StorageId,
23 pub format: ObjectFormatCode,
25 pub protection_status: ProtectionStatus,
27 pub size: u64,
32 pub thumb_format: ObjectFormatCode,
34 pub thumb_size: u32,
36 pub thumb_width: u32,
38 pub thumb_height: u32,
40 pub image_width: u32,
42 pub image_height: u32,
44 pub image_bit_depth: u32,
46 pub parent: ObjectHandle,
48 pub association_type: AssociationType,
50 pub association_desc: u32,
52 pub sequence_number: u32,
54 pub filename: String,
56 pub created: Option<DateTime>,
58 pub modified: Option<DateTime>,
60 pub keywords: String,
62}
63
64impl ObjectInfo {
65 pub fn from_bytes(buf: &[u8]) -> Result<Self, crate::Error> {
69 let mut offset = 0;
70
71 let storage_id = StorageId(unpack_u32(&buf[offset..])?);
73 offset += 4;
74
75 let format = ObjectFormatCode::from(unpack_u16(&buf[offset..])?);
77 offset += 2;
78
79 let protection_status = ProtectionStatus::from(unpack_u16(&buf[offset..])?);
81 offset += 2;
82
83 let size = unpack_u32(&buf[offset..])? as u64;
85 offset += 4;
86
87 let thumb_format = ObjectFormatCode::from(unpack_u16(&buf[offset..])?);
89 offset += 2;
90
91 let thumb_size = unpack_u32(&buf[offset..])?;
93 offset += 4;
94
95 let thumb_width = unpack_u32(&buf[offset..])?;
97 offset += 4;
98
99 let thumb_height = unpack_u32(&buf[offset..])?;
101 offset += 4;
102
103 let image_width = unpack_u32(&buf[offset..])?;
105 offset += 4;
106
107 let image_height = unpack_u32(&buf[offset..])?;
109 offset += 4;
110
111 let image_bit_depth = unpack_u32(&buf[offset..])?;
113 offset += 4;
114
115 let parent = ObjectHandle(unpack_u32(&buf[offset..])?);
117 offset += 4;
118
119 let association_type = AssociationType::from(unpack_u16(&buf[offset..])?);
121 offset += 2;
122
123 let association_desc = unpack_u32(&buf[offset..])?;
125 offset += 4;
126
127 let sequence_number = unpack_u32(&buf[offset..])?;
129 offset += 4;
130
131 let (filename, consumed) = unpack_string(&buf[offset..])?;
133 offset += consumed;
134
135 let (created, consumed) = unpack_datetime(&buf[offset..])?;
137 offset += consumed;
138
139 let (modified, consumed) = unpack_datetime(&buf[offset..])?;
141 offset += consumed;
142
143 let (keywords, _consumed) = unpack_string(&buf[offset..])?;
145
146 Ok(ObjectInfo {
147 handle: ObjectHandle::default(), storage_id,
149 format,
150 protection_status,
151 size,
152 thumb_format,
153 thumb_size,
154 thumb_width,
155 thumb_height,
156 image_width,
157 image_height,
158 image_bit_depth,
159 parent,
160 association_type,
161 association_desc,
162 sequence_number,
163 filename,
164 created,
165 modified,
166 keywords,
167 })
168 }
169
170 pub fn to_bytes(&self) -> Result<Vec<u8>, crate::Error> {
176 let mut buf = Vec::new();
177
178 buf.extend_from_slice(&pack_u32(self.storage_id.0));
180
181 buf.extend_from_slice(&pack_u16(self.format.into()));
183
184 buf.extend_from_slice(&pack_u16(self.protection_status.into()));
186
187 let size_u32 = if self.size > u32::MAX as u64 {
189 u32::MAX
190 } else {
191 self.size as u32
192 };
193 buf.extend_from_slice(&pack_u32(size_u32));
194
195 buf.extend_from_slice(&pack_u16(self.thumb_format.into()));
197
198 buf.extend_from_slice(&pack_u32(self.thumb_size));
200
201 buf.extend_from_slice(&pack_u32(self.thumb_width));
203
204 buf.extend_from_slice(&pack_u32(self.thumb_height));
206
207 buf.extend_from_slice(&pack_u32(self.image_width));
209
210 buf.extend_from_slice(&pack_u32(self.image_height));
212
213 buf.extend_from_slice(&pack_u32(self.image_bit_depth));
215
216 buf.extend_from_slice(&pack_u32(self.parent.0));
218
219 buf.extend_from_slice(&pack_u16(self.association_type.into()));
221
222 buf.extend_from_slice(&pack_u32(self.association_desc));
224
225 buf.extend_from_slice(&pack_u32(self.sequence_number));
227
228 buf.extend_from_slice(&pack_string(&self.filename));
230
231 if let Some(dt) = &self.created {
233 buf.extend_from_slice(&pack_datetime(dt)?);
234 } else {
235 buf.push(0x00); }
237
238 if let Some(dt) = &self.modified {
240 buf.extend_from_slice(&pack_datetime(dt)?);
241 } else {
242 buf.push(0x00); }
244
245 buf.extend_from_slice(&pack_string(&self.keywords));
247
248 Ok(buf)
249 }
250
251 #[must_use]
255 pub fn is_folder(&self) -> bool {
256 self.format == ObjectFormatCode::Association
257 || self.association_type == AssociationType::GenericFolder
258 }
259
260 #[must_use]
264 pub fn is_file(&self) -> bool {
265 !self.is_folder()
266 }
267}
268
269#[cfg(test)]
270mod tests {
271 use super::*;
272 use crate::ptp::pack::{pack_datetime, pack_string, pack_u16, pack_u32, DateTime};
273
274 fn build_file_object_info_bytes() -> Vec<u8> {
277 let mut buf = Vec::new();
278
279 buf.extend_from_slice(&pack_u32(0x00010001));
281 buf.extend_from_slice(&pack_u16(0x3801));
283 buf.extend_from_slice(&pack_u16(0));
285 buf.extend_from_slice(&pack_u32(1024));
287 buf.extend_from_slice(&pack_u16(0x3801));
289 buf.extend_from_slice(&pack_u32(512));
291 buf.extend_from_slice(&pack_u32(160));
293 buf.extend_from_slice(&pack_u32(120));
295 buf.extend_from_slice(&pack_u32(1920));
297 buf.extend_from_slice(&pack_u32(1080));
299 buf.extend_from_slice(&pack_u32(24));
301 buf.extend_from_slice(&pack_u32(5));
303 buf.extend_from_slice(&pack_u16(0));
305 buf.extend_from_slice(&pack_u32(0));
307 buf.extend_from_slice(&pack_u32(1));
309 buf.extend_from_slice(&pack_string("photo.jpg"));
311 buf.extend_from_slice(
313 &pack_datetime(&DateTime {
314 year: 2024,
315 month: 3,
316 day: 15,
317 hour: 14,
318 minute: 30,
319 second: 22,
320 })
321 .unwrap(),
322 );
323 buf.extend_from_slice(
325 &pack_datetime(&DateTime {
326 year: 2024,
327 month: 3,
328 day: 16,
329 hour: 9,
330 minute: 0,
331 second: 0,
332 })
333 .unwrap(),
334 );
335 buf.push(0x00);
337
338 buf
339 }
340
341 #[test]
342 fn object_info_parse_file() {
343 let buf = build_file_object_info_bytes();
344 let info = ObjectInfo::from_bytes(&buf).unwrap();
345
346 assert_eq!(info.storage_id, StorageId(0x00010001));
347 assert_eq!(info.format, ObjectFormatCode::Jpeg);
348 assert_eq!(info.protection_status, ProtectionStatus::None);
349 assert_eq!(info.size, 1024);
350 assert_eq!(info.thumb_format, ObjectFormatCode::Jpeg);
351 assert_eq!(info.thumb_size, 512);
352 assert_eq!(info.thumb_width, 160);
353 assert_eq!(info.thumb_height, 120);
354 assert_eq!(info.image_width, 1920);
355 assert_eq!(info.image_height, 1080);
356 assert_eq!(info.image_bit_depth, 24);
357 assert_eq!(info.parent, ObjectHandle(5));
358 assert_eq!(info.association_type, AssociationType::None);
359 assert_eq!(info.association_desc, 0);
360 assert_eq!(info.sequence_number, 1);
361 assert_eq!(info.filename, "photo.jpg");
362 assert!(info.created.is_some());
363 let created = info.created.unwrap();
364 assert_eq!(created.year, 2024);
365 assert_eq!(created.month, 3);
366 assert_eq!(created.day, 15);
367 assert!(info.modified.is_some());
368 assert_eq!(info.keywords, "");
369
370 assert!(info.is_file());
371 assert!(!info.is_folder());
372 }
373
374 fn build_folder_object_info_bytes() -> Vec<u8> {
375 let mut buf = Vec::new();
376
377 buf.extend_from_slice(&pack_u32(0x00010001));
379 buf.extend_from_slice(&pack_u16(0x3001));
381 buf.extend_from_slice(&pack_u16(0));
383 buf.extend_from_slice(&pack_u32(0));
385 buf.extend_from_slice(&pack_u16(0x3000));
387 buf.extend_from_slice(&pack_u32(0));
389 buf.extend_from_slice(&pack_u32(0));
391 buf.extend_from_slice(&pack_u32(0));
393 buf.extend_from_slice(&pack_u32(0));
395 buf.extend_from_slice(&pack_u32(0));
397 buf.extend_from_slice(&pack_u32(0));
399 buf.extend_from_slice(&pack_u32(0));
401 buf.extend_from_slice(&pack_u16(1));
403 buf.extend_from_slice(&pack_u32(0));
405 buf.extend_from_slice(&pack_u32(0));
407 buf.extend_from_slice(&pack_string("DCIM"));
409 buf.push(0x00);
411 buf.push(0x00);
413 buf.push(0x00);
415
416 buf
417 }
418
419 #[test]
420 fn object_info_parse_folder() {
421 let buf = build_folder_object_info_bytes();
422 let info = ObjectInfo::from_bytes(&buf).unwrap();
423
424 assert_eq!(info.format, ObjectFormatCode::Association);
425 assert_eq!(info.association_type, AssociationType::GenericFolder);
426 assert_eq!(info.filename, "DCIM");
427 assert_eq!(info.parent, ObjectHandle::ROOT);
428 assert!(info.created.is_none());
429 assert!(info.modified.is_none());
430
431 assert!(info.is_folder());
432 assert!(!info.is_file());
433 }
434
435 #[test]
436 fn object_info_to_bytes_roundtrip() {
437 let original = ObjectInfo {
438 handle: ObjectHandle(42),
439 storage_id: StorageId(0x00010001),
440 format: ObjectFormatCode::Jpeg,
441 protection_status: ProtectionStatus::None,
442 size: 2048,
443 thumb_format: ObjectFormatCode::Jpeg,
444 thumb_size: 256,
445 thumb_width: 80,
446 thumb_height: 60,
447 image_width: 800,
448 image_height: 600,
449 image_bit_depth: 24,
450 parent: ObjectHandle(10),
451 association_type: AssociationType::None,
452 association_desc: 0,
453 sequence_number: 5,
454 filename: "test.jpg".to_string(),
455 created: Some(DateTime {
456 year: 2024,
457 month: 6,
458 day: 15,
459 hour: 10,
460 minute: 30,
461 second: 0,
462 }),
463 modified: Some(DateTime {
464 year: 2024,
465 month: 6,
466 day: 16,
467 hour: 11,
468 minute: 45,
469 second: 30,
470 }),
471 keywords: "test,photo".to_string(),
472 };
473
474 let bytes = original.to_bytes().unwrap();
475 let parsed = ObjectInfo::from_bytes(&bytes).unwrap();
476
477 assert_eq!(parsed.storage_id, original.storage_id);
478 assert_eq!(parsed.format, original.format);
479 assert_eq!(parsed.protection_status, original.protection_status);
480 assert_eq!(parsed.size, original.size);
481 assert_eq!(parsed.thumb_format, original.thumb_format);
482 assert_eq!(parsed.thumb_size, original.thumb_size);
483 assert_eq!(parsed.thumb_width, original.thumb_width);
484 assert_eq!(parsed.thumb_height, original.thumb_height);
485 assert_eq!(parsed.image_width, original.image_width);
486 assert_eq!(parsed.image_height, original.image_height);
487 assert_eq!(parsed.image_bit_depth, original.image_bit_depth);
488 assert_eq!(parsed.parent, original.parent);
489 assert_eq!(parsed.association_type, original.association_type);
490 assert_eq!(parsed.association_desc, original.association_desc);
491 assert_eq!(parsed.sequence_number, original.sequence_number);
492 assert_eq!(parsed.filename, original.filename);
493 assert_eq!(parsed.created, original.created);
494 assert_eq!(parsed.modified, original.modified);
495 assert_eq!(parsed.keywords, original.keywords);
496 }
497
498 #[test]
499 fn object_info_to_bytes_large_size() {
500 let info = ObjectInfo {
501 size: 5_000_000_000, ..Default::default()
503 };
504
505 let bytes = info.to_bytes().unwrap();
506 let parsed = ObjectInfo::from_bytes(&bytes).unwrap();
507
508 assert_eq!(parsed.size, u32::MAX as u64);
510 }
511
512 #[test]
513 fn object_info_is_folder_by_format() {
514 let info = ObjectInfo {
515 format: ObjectFormatCode::Association,
516 association_type: AssociationType::None,
517 ..Default::default()
518 };
519 assert!(info.is_folder());
520 }
521
522 #[test]
523 fn object_info_is_folder_by_association() {
524 let info = ObjectInfo {
525 format: ObjectFormatCode::Undefined,
526 association_type: AssociationType::GenericFolder,
527 ..Default::default()
528 };
529 assert!(info.is_folder());
530 }
531
532 #[test]
533 fn object_info_is_file() {
534 let info = ObjectInfo {
535 format: ObjectFormatCode::Jpeg,
536 association_type: AssociationType::None,
537 ..Default::default()
538 };
539 assert!(info.is_file());
540 assert!(!info.is_folder());
541 }
542
543 #[test]
544 fn object_info_parse_insufficient_bytes() {
545 let buf = vec![0x00; 10]; assert!(ObjectInfo::from_bytes(&buf).is_err());
547 }
548
549 #[test]
550 fn object_info_default() {
551 let info = ObjectInfo::default();
552 assert_eq!(info.storage_id, StorageId::default());
553 assert_eq!(info.format, ObjectFormatCode::Undefined);
554 assert_eq!(info.protection_status, ProtectionStatus::None);
555 assert_eq!(info.size, 0);
556 assert_eq!(info.filename, "");
557 assert!(info.created.is_none());
558 assert!(info.modified.is_none());
559 }
560
561 crate::fuzz_bytes!(fuzz_object_info, ObjectInfo, 200);
563
564 #[test]
565 fn object_info_minimum_valid() {
566 assert!(ObjectInfo::from_bytes(&[]).is_err());
571 assert!(ObjectInfo::from_bytes(&[0; 51]).is_err());
572 assert!(ObjectInfo::from_bytes(&[0; 52]).is_err()); }
574
575 #[test]
576 fn object_info_size_u32_max() {
577 let mut buf = build_file_object_info_bytes();
579 buf[8] = 0xFF;
581 buf[9] = 0xFF;
582 buf[10] = 0xFF;
583 buf[11] = 0xFF;
584
585 let info = ObjectInfo::from_bytes(&buf).unwrap();
586 assert_eq!(info.size, u32::MAX as u64);
587 }
588}