Skip to main content

mtp_rs/ptp/types/
device.rs

1//! Device and storage information types for MTP/PTP.
2//!
3//! This module contains:
4//! - [`DeviceInfo`]: Device capabilities and identification
5//! - [`StorageInfo`]: Storage characteristics and capacity
6
7use super::storage::{AccessCapability, FilesystemType, StorageType};
8use crate::ptp::pack::{unpack_string, unpack_u16, unpack_u16_array, unpack_u32, unpack_u64};
9use crate::ptp::{EventCode, ObjectFormatCode, OperationCode};
10
11// --- DeviceInfo Structure ---
12
13/// Device information returned by GetDeviceInfo.
14///
15/// Contains device capabilities, manufacturer info, and supported operations.
16#[derive(Debug, Clone, Default)]
17pub struct DeviceInfo {
18    /// PTP standard version (e.g., 100 = v1.00).
19    pub standard_version: u16,
20    /// Vendor extension ID (0 = no extension).
21    pub vendor_extension_id: u32,
22    /// Vendor extension version.
23    pub vendor_extension_version: u16,
24    /// Vendor extension description.
25    pub vendor_extension_desc: String,
26    /// Functional mode (0 = standard).
27    pub functional_mode: u16,
28    /// Operations supported by the device.
29    pub operations_supported: Vec<OperationCode>,
30    /// Events supported by the device.
31    pub events_supported: Vec<EventCode>,
32    /// Device properties supported.
33    pub device_properties_supported: Vec<u16>,
34    /// Object formats the device can capture/create.
35    pub capture_formats: Vec<ObjectFormatCode>,
36    /// Object formats the device can play/display.
37    pub playback_formats: Vec<ObjectFormatCode>,
38    /// Manufacturer name.
39    pub manufacturer: String,
40    /// Device model name.
41    pub model: String,
42    /// Device version string.
43    pub device_version: String,
44    /// Device serial number.
45    pub serial_number: String,
46}
47
48impl DeviceInfo {
49    /// Parse DeviceInfo from a byte buffer.
50    ///
51    /// The buffer should contain the DeviceInfo dataset as returned by GetDeviceInfo.
52    pub fn from_bytes(buf: &[u8]) -> Result<Self, crate::Error> {
53        let mut offset = 0;
54
55        // 1. StandardVersion (u16)
56        let standard_version = unpack_u16(&buf[offset..])?;
57        offset += 2;
58
59        // 2. VendorExtensionID (u32)
60        let vendor_extension_id = unpack_u32(&buf[offset..])?;
61        offset += 4;
62
63        // 3. VendorExtensionVersion (u16)
64        let vendor_extension_version = unpack_u16(&buf[offset..])?;
65        offset += 2;
66
67        // 4. VendorExtensionDesc (string)
68        let (vendor_extension_desc, consumed) = unpack_string(&buf[offset..])?;
69        offset += consumed;
70
71        // 5. FunctionalMode (u16)
72        let functional_mode = unpack_u16(&buf[offset..])?;
73        offset += 2;
74
75        // 6. OperationsSupported (u16 array)
76        let (ops_raw, consumed) = unpack_u16_array(&buf[offset..])?;
77        let operations_supported: Vec<OperationCode> =
78            ops_raw.into_iter().map(OperationCode::from).collect();
79        offset += consumed;
80
81        // 7. EventsSupported (u16 array)
82        let (events_raw, consumed) = unpack_u16_array(&buf[offset..])?;
83        let events_supported: Vec<EventCode> =
84            events_raw.into_iter().map(EventCode::from).collect();
85        offset += consumed;
86
87        // 8. DevicePropertiesSupported (u16 array)
88        let (device_properties_supported, consumed) = unpack_u16_array(&buf[offset..])?;
89        offset += consumed;
90
91        // 9. CaptureFormats (u16 array)
92        let (capture_raw, consumed) = unpack_u16_array(&buf[offset..])?;
93        let capture_formats: Vec<ObjectFormatCode> = capture_raw
94            .into_iter()
95            .map(ObjectFormatCode::from)
96            .collect();
97        offset += consumed;
98
99        // 10. PlaybackFormats (u16 array)
100        let (playback_raw, consumed) = unpack_u16_array(&buf[offset..])?;
101        let playback_formats: Vec<ObjectFormatCode> = playback_raw
102            .into_iter()
103            .map(ObjectFormatCode::from)
104            .collect();
105        offset += consumed;
106
107        // 11. Manufacturer (string)
108        let (manufacturer, consumed) = unpack_string(&buf[offset..])?;
109        offset += consumed;
110
111        // 12. Model (string)
112        let (model, consumed) = unpack_string(&buf[offset..])?;
113        offset += consumed;
114
115        // 13. DeviceVersion (string)
116        let (device_version, consumed) = unpack_string(&buf[offset..])?;
117        offset += consumed;
118
119        // 14. SerialNumber (string)
120        let (serial_number, _) = unpack_string(&buf[offset..])?;
121
122        Ok(DeviceInfo {
123            standard_version,
124            vendor_extension_id,
125            vendor_extension_version,
126            vendor_extension_desc,
127            functional_mode,
128            operations_supported,
129            events_supported,
130            device_properties_supported,
131            capture_formats,
132            playback_formats,
133            manufacturer,
134            model,
135            device_version,
136            serial_number,
137        })
138    }
139
140    /// Check if the device supports a specific operation.
141    ///
142    /// # Arguments
143    ///
144    /// * `operation` - The operation code to check
145    ///
146    /// # Returns
147    ///
148    /// Returns true if the operation is in the device's supported operations list.
149    #[must_use]
150    pub fn supports_operation(&self, operation: OperationCode) -> bool {
151        self.operations_supported.contains(&operation)
152    }
153
154    /// Check if the device supports renaming objects.
155    ///
156    /// This checks for support of the SetObjectPropValue operation (0x9804),
157    /// which is required to rename files and folders via the ObjectFileName property.
158    ///
159    /// # Returns
160    ///
161    /// Returns true if the device advertises SetObjectPropValue support.
162    #[must_use]
163    pub fn supports_rename(&self) -> bool {
164        self.supports_operation(OperationCode::SetObjectPropValue)
165    }
166}
167
168// --- StorageInfo Structure ---
169
170/// Storage information returned by GetStorageInfo.
171///
172/// Contains storage capacity, type, and access information.
173#[derive(Debug, Clone, Default)]
174pub struct StorageInfo {
175    /// Type of storage medium.
176    pub storage_type: StorageType,
177    /// Type of filesystem.
178    pub filesystem_type: FilesystemType,
179    /// Access capability.
180    pub access_capability: AccessCapability,
181    /// Maximum storage capacity in bytes.
182    pub max_capacity: u64,
183    /// Free space in bytes.
184    pub free_space_bytes: u64,
185    /// Free space in number of objects (0xFFFFFFFF if unknown).
186    pub free_space_objects: u32,
187    /// Storage description string.
188    pub description: String,
189    /// Volume identifier/label.
190    pub volume_identifier: String,
191}
192
193impl StorageInfo {
194    /// Parse StorageInfo from a byte buffer.
195    ///
196    /// The buffer should contain the StorageInfo dataset as returned by GetStorageInfo.
197    pub fn from_bytes(buf: &[u8]) -> Result<Self, crate::Error> {
198        let mut offset = 0;
199
200        // 1. StorageType (u16)
201        let storage_type = StorageType::from(unpack_u16(&buf[offset..])?);
202        offset += 2;
203
204        // 2. FilesystemType (u16)
205        let filesystem_type = FilesystemType::from(unpack_u16(&buf[offset..])?);
206        offset += 2;
207
208        // 3. AccessCapability (u16)
209        let access_capability = AccessCapability::from(unpack_u16(&buf[offset..])?);
210        offset += 2;
211
212        // 4. MaxCapacity (u64)
213        let max_capacity = unpack_u64(&buf[offset..])?;
214        offset += 8;
215
216        // 5. FreeSpaceInBytes (u64)
217        let free_space_bytes = unpack_u64(&buf[offset..])?;
218        offset += 8;
219
220        // 6. FreeSpaceInObjects (u32)
221        let free_space_objects = unpack_u32(&buf[offset..])?;
222        offset += 4;
223
224        // 7. StorageDescription (string)
225        let (description, consumed) = unpack_string(&buf[offset..])?;
226        offset += consumed;
227
228        // 8. VolumeIdentifier (string)
229        let (volume_identifier, _) = unpack_string(&buf[offset..])?;
230
231        Ok(StorageInfo {
232            storage_type,
233            filesystem_type,
234            access_capability,
235            max_capacity,
236            free_space_bytes,
237            free_space_objects,
238            description,
239            volume_identifier,
240        })
241    }
242}
243
244#[cfg(test)]
245mod tests {
246    use super::*;
247    use crate::ptp::pack::{pack_string, pack_u16, pack_u16_array, pack_u32};
248
249    // --- DeviceInfo Tests ---
250
251    fn build_minimal_device_info_bytes() -> Vec<u8> {
252        let mut buf = Vec::new();
253
254        // StandardVersion: 100 (v1.00)
255        buf.extend_from_slice(&pack_u16(100));
256        // VendorExtensionID: 0
257        buf.extend_from_slice(&pack_u32(0));
258        // VendorExtensionVersion: 0
259        buf.extend_from_slice(&pack_u16(0));
260        // VendorExtensionDesc: empty string
261        buf.push(0x00);
262        // FunctionalMode: 0
263        buf.extend_from_slice(&pack_u16(0));
264        // OperationsSupported: empty array
265        buf.extend_from_slice(&pack_u16_array(&[]));
266        // EventsSupported: empty array
267        buf.extend_from_slice(&pack_u16_array(&[]));
268        // DevicePropertiesSupported: empty array
269        buf.extend_from_slice(&pack_u16_array(&[]));
270        // CaptureFormats: empty array
271        buf.extend_from_slice(&pack_u16_array(&[]));
272        // PlaybackFormats: empty array
273        buf.extend_from_slice(&pack_u16_array(&[]));
274        // Manufacturer: empty string
275        buf.push(0x00);
276        // Model: empty string
277        buf.push(0x00);
278        // DeviceVersion: empty string
279        buf.push(0x00);
280        // SerialNumber: empty string
281        buf.push(0x00);
282
283        buf
284    }
285
286    #[test]
287    fn device_info_parse_minimal() {
288        let buf = build_minimal_device_info_bytes();
289        let info = DeviceInfo::from_bytes(&buf).unwrap();
290
291        assert_eq!(info.standard_version, 100);
292        assert_eq!(info.vendor_extension_id, 0);
293        assert_eq!(info.vendor_extension_version, 0);
294        assert_eq!(info.vendor_extension_desc, "");
295        assert_eq!(info.functional_mode, 0);
296        assert!(info.operations_supported.is_empty());
297        assert!(info.events_supported.is_empty());
298        assert!(info.device_properties_supported.is_empty());
299        assert!(info.capture_formats.is_empty());
300        assert!(info.playback_formats.is_empty());
301        assert_eq!(info.manufacturer, "");
302        assert_eq!(info.model, "");
303        assert_eq!(info.device_version, "");
304        assert_eq!(info.serial_number, "");
305    }
306
307    fn build_full_device_info_bytes() -> Vec<u8> {
308        let mut buf = Vec::new();
309
310        // StandardVersion: 100 (v1.00)
311        buf.extend_from_slice(&pack_u16(100));
312        // VendorExtensionID: 0x00000006 (Microsoft)
313        buf.extend_from_slice(&pack_u32(6));
314        // VendorExtensionVersion: 100
315        buf.extend_from_slice(&pack_u16(100));
316        // VendorExtensionDesc: "microsoft.com: 1.0"
317        buf.extend_from_slice(&pack_string("microsoft.com: 1.0"));
318        // FunctionalMode: 0
319        buf.extend_from_slice(&pack_u16(0));
320        // OperationsSupported: [GetDeviceInfo, OpenSession, CloseSession]
321        buf.extend_from_slice(&pack_u16_array(&[0x1001, 0x1002, 0x1003]));
322        // EventsSupported: [ObjectAdded, ObjectRemoved]
323        buf.extend_from_slice(&pack_u16_array(&[0x4002, 0x4003]));
324        // DevicePropertiesSupported: [0x5001, 0x5002]
325        buf.extend_from_slice(&pack_u16_array(&[0x5001, 0x5002]));
326        // CaptureFormats: [JPEG]
327        buf.extend_from_slice(&pack_u16_array(&[0x3801]));
328        // PlaybackFormats: [JPEG, MP3]
329        buf.extend_from_slice(&pack_u16_array(&[0x3801, 0x3009]));
330        // Manufacturer: "Test Manufacturer"
331        buf.extend_from_slice(&pack_string("Test Manufacturer"));
332        // Model: "Test Model"
333        buf.extend_from_slice(&pack_string("Test Model"));
334        // DeviceVersion: "1.0.0"
335        buf.extend_from_slice(&pack_string("1.0.0"));
336        // SerialNumber: "ABC123"
337        buf.extend_from_slice(&pack_string("ABC123"));
338
339        buf
340    }
341
342    #[test]
343    fn device_info_parse_full() {
344        let buf = build_full_device_info_bytes();
345        let info = DeviceInfo::from_bytes(&buf).unwrap();
346
347        assert_eq!(info.standard_version, 100);
348        assert_eq!(info.vendor_extension_id, 6);
349        assert_eq!(info.vendor_extension_version, 100);
350        assert_eq!(info.vendor_extension_desc, "microsoft.com: 1.0");
351        assert_eq!(info.functional_mode, 0);
352
353        assert_eq!(info.operations_supported.len(), 3);
354        assert_eq!(info.operations_supported[0], OperationCode::GetDeviceInfo);
355        assert_eq!(info.operations_supported[1], OperationCode::OpenSession);
356        assert_eq!(info.operations_supported[2], OperationCode::CloseSession);
357
358        assert_eq!(info.events_supported.len(), 2);
359        assert_eq!(info.events_supported[0], EventCode::ObjectAdded);
360        assert_eq!(info.events_supported[1], EventCode::ObjectRemoved);
361
362        assert_eq!(info.device_properties_supported, vec![0x5001, 0x5002]);
363
364        assert_eq!(info.capture_formats.len(), 1);
365        assert_eq!(info.capture_formats[0], ObjectFormatCode::Jpeg);
366
367        assert_eq!(info.playback_formats.len(), 2);
368        assert_eq!(info.playback_formats[0], ObjectFormatCode::Jpeg);
369        assert_eq!(info.playback_formats[1], ObjectFormatCode::Mp3);
370
371        assert_eq!(info.manufacturer, "Test Manufacturer");
372        assert_eq!(info.model, "Test Model");
373        assert_eq!(info.device_version, "1.0.0");
374        assert_eq!(info.serial_number, "ABC123");
375    }
376
377    #[test]
378    fn device_info_parse_insufficient_bytes() {
379        let buf = vec![0x00, 0x01]; // Only 2 bytes
380        assert!(DeviceInfo::from_bytes(&buf).is_err());
381    }
382
383    // --- StorageInfo Tests ---
384
385    fn build_storage_info_bytes() -> Vec<u8> {
386        let mut buf = Vec::new();
387
388        // StorageType: RemovableRam (4)
389        buf.extend_from_slice(&pack_u16(4));
390        // FilesystemType: GenericHierarchical (2)
391        buf.extend_from_slice(&pack_u16(2));
392        // AccessCapability: ReadWrite (0)
393        buf.extend_from_slice(&pack_u16(0));
394        // MaxCapacity: 32GB
395        buf.extend_from_slice(&32_000_000_000u64.to_le_bytes());
396        // FreeSpaceInBytes: 16GB
397        buf.extend_from_slice(&16_000_000_000u64.to_le_bytes());
398        // FreeSpaceInObjects: 0xFFFFFFFF (unknown)
399        buf.extend_from_slice(&pack_u32(0xFFFFFFFF));
400        // StorageDescription: "SD Card"
401        buf.extend_from_slice(&pack_string("SD Card"));
402        // VolumeIdentifier: "VOL001"
403        buf.extend_from_slice(&pack_string("VOL001"));
404
405        buf
406    }
407
408    #[test]
409    fn storage_info_parse() {
410        let buf = build_storage_info_bytes();
411        let info = StorageInfo::from_bytes(&buf).unwrap();
412
413        assert_eq!(info.storage_type, StorageType::RemovableRam);
414        assert_eq!(info.filesystem_type, FilesystemType::GenericHierarchical);
415        assert_eq!(info.access_capability, AccessCapability::ReadWrite);
416        assert_eq!(info.max_capacity, 32_000_000_000);
417        assert_eq!(info.free_space_bytes, 16_000_000_000);
418        assert_eq!(info.free_space_objects, 0xFFFFFFFF);
419        assert_eq!(info.description, "SD Card");
420        assert_eq!(info.volume_identifier, "VOL001");
421    }
422
423    #[test]
424    fn storage_info_parse_insufficient_bytes() {
425        let buf = vec![0x00; 10]; // Not enough bytes
426        assert!(StorageInfo::from_bytes(&buf).is_err());
427    }
428
429    // --- DeviceInfo capability tests ---
430
431    #[test]
432    fn device_info_supports_operation() {
433        let info = DeviceInfo {
434            operations_supported: vec![
435                OperationCode::GetDeviceInfo,
436                OperationCode::OpenSession,
437                OperationCode::SetObjectPropValue,
438            ],
439            ..Default::default()
440        };
441
442        assert!(info.supports_operation(OperationCode::GetDeviceInfo));
443        assert!(info.supports_operation(OperationCode::OpenSession));
444        assert!(info.supports_operation(OperationCode::SetObjectPropValue));
445        assert!(!info.supports_operation(OperationCode::DeleteObject));
446        assert!(!info.supports_operation(OperationCode::GetObjectPropValue));
447    }
448
449    #[test]
450    fn device_info_supports_rename_true() {
451        let info = DeviceInfo {
452            operations_supported: vec![
453                OperationCode::GetDeviceInfo,
454                OperationCode::SetObjectPropValue, // Required for rename
455            ],
456            ..Default::default()
457        };
458
459        assert!(info.supports_rename());
460    }
461
462    #[test]
463    fn device_info_supports_rename_false() {
464        let info = DeviceInfo {
465            operations_supported: vec![
466                OperationCode::GetDeviceInfo,
467                OperationCode::GetObjectPropValue, // Has Get but not Set
468            ],
469            ..Default::default()
470        };
471
472        assert!(!info.supports_rename());
473    }
474
475    #[test]
476    fn device_info_supports_rename_empty() {
477        let info = DeviceInfo::default();
478        assert!(!info.supports_rename());
479    }
480
481    // Fuzz tests using shared macros - verify parsers don't panic on arbitrary input
482    crate::fuzz_bytes!(fuzz_device_info, DeviceInfo, 200);
483    crate::fuzz_bytes!(fuzz_storage_info, StorageInfo, 100);
484
485    #[test]
486    fn device_info_minimum_valid() {
487        // DeviceInfo needs at minimum: u16 + u32 + u16 + string + u16 + 5 arrays + 4 strings
488        // This is a lot of bytes. Test that small buffers fail gracefully.
489        assert!(DeviceInfo::from_bytes(&[]).is_err());
490        assert!(DeviceInfo::from_bytes(&[0; 1]).is_err());
491        assert!(DeviceInfo::from_bytes(&[0; 7]).is_err());
492        assert!(DeviceInfo::from_bytes(&[0; 8]).is_err()); // Need at least string data after first fields
493    }
494
495    #[test]
496    fn storage_info_minimum_valid() {
497        // StorageInfo needs: 3 * u16 + 2 * u64 + u32 + 2 strings = 26 bytes minimum + string data
498        assert!(StorageInfo::from_bytes(&[]).is_err());
499        assert!(StorageInfo::from_bytes(&[0; 25]).is_err());
500        assert!(StorageInfo::from_bytes(&[0; 26]).is_err()); // Still need string data
501    }
502
503    #[test]
504    fn storage_info_max_capacity() {
505        let mut buf = build_storage_info_bytes();
506        // Replace MaxCapacity field (bytes 6-13, after 3 u16s = 6 bytes)
507        let max_bytes = u64::MAX.to_le_bytes();
508        buf[6..14].copy_from_slice(&max_bytes);
509
510        let info = StorageInfo::from_bytes(&buf).unwrap();
511        assert_eq!(info.max_capacity, u64::MAX);
512    }
513}