Skip to main content

mtp_rs/ptp/
codes.rs

1//! MTP/PTP protocol operation, response, event, and format codes.
2//!
3//! This module defines the standard codes used in MTP/PTP communication:
4//! - [`OperationCode`]: Commands sent to the device
5//! - [`ResponseCode`]: Status codes returned by the device
6//! - [`EventCode`]: Asynchronous events from the device
7//! - [`ObjectFormatCode`]: File format identifiers
8
9use num_enum::{FromPrimitive, IntoPrimitive};
10
11/// PTP operation codes (commands sent to device).
12///
13/// These codes identify the operation being requested in a PTP command container.
14#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash, FromPrimitive, IntoPrimitive)]
15#[repr(u16)]
16pub enum OperationCode {
17    /// Get device information (capabilities, manufacturer, etc.).
18    GetDeviceInfo = 0x1001,
19    /// Open a session with the device.
20    OpenSession = 0x1002,
21    /// Close the current session.
22    CloseSession = 0x1003,
23    /// Get list of storage IDs.
24    GetStorageIds = 0x1004,
25    /// Get information about a storage.
26    GetStorageInfo = 0x1005,
27    /// Get the number of objects in a storage/folder.
28    GetNumObjects = 0x1006,
29    /// Get list of object handles.
30    GetObjectHandles = 0x1007,
31    /// Get information about an object.
32    GetObjectInfo = 0x1008,
33    /// Download an object's data.
34    GetObject = 0x1009,
35    /// Get thumbnail for an object.
36    GetThumb = 0x100A,
37    /// Delete an object.
38    DeleteObject = 0x100B,
39    /// Send object metadata (before sending object data).
40    SendObjectInfo = 0x100C,
41    /// Send object data (after SendObjectInfo).
42    SendObject = 0x100D,
43    /// Initiate image capture on a camera.
44    InitiateCapture = 0x100E,
45    /// Get device property descriptor.
46    GetDevicePropDesc = 0x1014,
47    /// Get current device property value.
48    GetDevicePropValue = 0x1015,
49    /// Set device property value.
50    SetDevicePropValue = 0x1016,
51    /// Reset device property to default value.
52    ResetDevicePropValue = 0x1017,
53    /// Move an object to a different location.
54    MoveObject = 0x1019,
55    /// Copy an object.
56    CopyObject = 0x101A,
57    /// Get partial object data (range request).
58    GetPartialObject = 0x101B,
59    /// Get the value of an object property (MTP extension).
60    GetObjectPropValue = 0x9803,
61    /// Set the value of an object property (MTP extension).
62    SetObjectPropValue = 0x9804,
63    /// Unknown or vendor-specific operation code.
64    #[num_enum(catch_all)]
65    Unknown(u16),
66}
67
68/// PTP response codes (status returned by device).
69///
70/// These codes indicate the result of an operation.
71#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash, FromPrimitive, IntoPrimitive)]
72#[repr(u16)]
73pub enum ResponseCode {
74    /// Operation completed successfully.
75    Ok = 0x2001,
76    /// General unspecified error.
77    GeneralError = 0x2002,
78    /// Session is not open.
79    SessionNotOpen = 0x2003,
80    /// Invalid transaction ID.
81    InvalidTransactionId = 0x2004,
82    /// Operation is not supported.
83    OperationNotSupported = 0x2005,
84    /// Parameter is not supported.
85    ParameterNotSupported = 0x2006,
86    /// Transfer was incomplete.
87    IncompleteTransfer = 0x2007,
88    /// Invalid storage ID.
89    InvalidStorageId = 0x2008,
90    /// Invalid object handle.
91    InvalidObjectHandle = 0x2009,
92    /// Device property not supported.
93    DevicePropNotSupported = 0x200A,
94    /// Invalid object format code.
95    InvalidObjectFormatCode = 0x200B,
96    /// Storage is full.
97    StoreFull = 0x200C,
98    /// Object is write-protected.
99    ObjectWriteProtected = 0x200D,
100    /// Storage is read-only.
101    StoreReadOnly = 0x200E,
102    /// Access denied.
103    AccessDenied = 0x200F,
104    /// Object has no thumbnail.
105    NoThumbnailPresent = 0x2010,
106    /// Device is busy.
107    DeviceBusy = 0x2019,
108    /// Invalid parent object.
109    InvalidParentObject = 0x201A,
110    /// Invalid device property format.
111    InvalidDevicePropFormat = 0x201B,
112    /// Invalid device property value.
113    InvalidDevicePropValue = 0x201C,
114    /// Invalid parameter value.
115    InvalidParameter = 0x201D,
116    /// Session is already open.
117    SessionAlreadyOpen = 0x201E,
118    /// Transaction was cancelled.
119    TransactionCancelled = 0x201F,
120    /// Object is too large for the storage.
121    ObjectTooLarge = 0xA809,
122    /// Unknown or vendor-specific response code.
123    #[num_enum(catch_all)]
124    Unknown(u16),
125}
126
127/// PTP event codes (asynchronous notifications from device).
128///
129/// These codes identify events that the device sends asynchronously.
130#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash, FromPrimitive, IntoPrimitive)]
131#[repr(u16)]
132pub enum EventCode {
133    /// A new object was added.
134    ObjectAdded = 0x4002,
135    /// An object was removed.
136    ObjectRemoved = 0x4003,
137    /// A new storage was added.
138    StoreAdded = 0x4004,
139    /// A storage was removed.
140    StoreRemoved = 0x4005,
141    /// A device property changed.
142    DevicePropChanged = 0x4006,
143    /// Object information changed.
144    ObjectInfoChanged = 0x4007,
145    /// Device information changed.
146    DeviceInfoChanged = 0x4008,
147    /// Storage information changed.
148    StorageInfoChanged = 0x400C,
149    /// Capture operation completed.
150    CaptureComplete = 0x400D,
151    /// Unknown or vendor-specific event code.
152    #[num_enum(catch_all)]
153    Unknown(u16),
154}
155
156/// PTP/MTP object format codes.
157///
158/// These codes identify the format/type of objects stored on the device.
159#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash, FromPrimitive, IntoPrimitive)]
160#[repr(u16)]
161pub enum ObjectFormatCode {
162    /// Undefined/unknown format.
163    Undefined = 0x3000,
164    /// Association (folder/directory).
165    Association = 0x3001,
166    /// Script file.
167    Script = 0x3002,
168    /// Executable file.
169    Executable = 0x3003,
170    /// Plain text file.
171    Text = 0x3004,
172    /// HTML file.
173    Html = 0x3005,
174    /// DPOF (Digital Print Order Format).
175    Dpof = 0x3006,
176    /// AIFF audio.
177    Aiff = 0x3007,
178    /// WAV audio.
179    Wav = 0x3008,
180    /// MP3 audio.
181    Mp3 = 0x3009,
182    /// AVI video.
183    Avi = 0x300A,
184    /// MPEG video.
185    Mpeg = 0x300B,
186    /// ASF (Advanced Systems Format).
187    Asf = 0x300C,
188    /// JPEG image.
189    Jpeg = 0x3801,
190    /// TIFF image.
191    Tiff = 0x3804,
192    /// GIF image.
193    Gif = 0x3807,
194    /// BMP image.
195    Bmp = 0x3808,
196    /// PICT image.
197    Pict = 0x380A,
198    /// PNG image.
199    Png = 0x380B,
200    /// WMA audio.
201    WmaAudio = 0xB901,
202    /// OGG audio.
203    OggAudio = 0xB902,
204    /// AAC audio.
205    AacAudio = 0xB903,
206    /// FLAC audio.
207    FlacAudio = 0xB906,
208    /// WMV video.
209    WmvVideo = 0xB981,
210    /// MP4 container.
211    Mp4Container = 0xB982,
212    /// M4A audio.
213    M4aAudio = 0xB984,
214    /// Unknown or vendor-specific format code.
215    #[num_enum(catch_all)]
216    Unknown(u16),
217}
218
219impl ObjectFormatCode {
220    /// Detect object format from file extension (case insensitive).
221    ///
222    /// Returns `Undefined` for unrecognized extensions.
223    #[must_use]
224    pub fn from_extension(ext: &str) -> Self {
225        match ext.to_lowercase().as_str() {
226            // Text and documents
227            "txt" => ObjectFormatCode::Text,
228            "html" | "htm" => ObjectFormatCode::Html,
229            "dpof" => ObjectFormatCode::Dpof,
230
231            // Audio formats
232            "aiff" | "aif" => ObjectFormatCode::Aiff,
233            "wav" => ObjectFormatCode::Wav,
234            "mp3" => ObjectFormatCode::Mp3,
235            "wma" => ObjectFormatCode::WmaAudio,
236            "ogg" | "oga" => ObjectFormatCode::OggAudio,
237            "aac" => ObjectFormatCode::AacAudio,
238            "flac" => ObjectFormatCode::FlacAudio,
239            "m4a" => ObjectFormatCode::M4aAudio,
240
241            // Video formats
242            "avi" => ObjectFormatCode::Avi,
243            "mpg" | "mpeg" => ObjectFormatCode::Mpeg,
244            "asf" => ObjectFormatCode::Asf,
245            "wmv" => ObjectFormatCode::WmvVideo,
246            "mp4" | "m4v" => ObjectFormatCode::Mp4Container,
247
248            // Image formats
249            "jpg" | "jpeg" => ObjectFormatCode::Jpeg,
250            "tif" | "tiff" => ObjectFormatCode::Tiff,
251            "gif" => ObjectFormatCode::Gif,
252            "bmp" => ObjectFormatCode::Bmp,
253            "pict" | "pct" => ObjectFormatCode::Pict,
254            "png" => ObjectFormatCode::Png,
255
256            // Executables and scripts
257            "exe" | "dll" | "bin" => ObjectFormatCode::Executable,
258            "sh" | "bat" | "cmd" | "ps1" => ObjectFormatCode::Script,
259
260            _ => ObjectFormatCode::Undefined,
261        }
262    }
263
264    /// Check if this format is an audio format.
265    #[must_use]
266    pub fn is_audio(&self) -> bool {
267        matches!(
268            self,
269            ObjectFormatCode::Aiff
270                | ObjectFormatCode::Wav
271                | ObjectFormatCode::Mp3
272                | ObjectFormatCode::WmaAudio
273                | ObjectFormatCode::OggAudio
274                | ObjectFormatCode::AacAudio
275                | ObjectFormatCode::FlacAudio
276                | ObjectFormatCode::M4aAudio
277        )
278    }
279
280    /// Check if this format is a video format.
281    #[must_use]
282    pub fn is_video(&self) -> bool {
283        matches!(
284            self,
285            ObjectFormatCode::Avi
286                | ObjectFormatCode::Mpeg
287                | ObjectFormatCode::Asf
288                | ObjectFormatCode::WmvVideo
289                | ObjectFormatCode::Mp4Container
290        )
291    }
292
293    /// Check if this format is an image format.
294    #[must_use]
295    pub fn is_image(&self) -> bool {
296        matches!(
297            self,
298            ObjectFormatCode::Jpeg
299                | ObjectFormatCode::Tiff
300                | ObjectFormatCode::Gif
301                | ObjectFormatCode::Bmp
302                | ObjectFormatCode::Pict
303                | ObjectFormatCode::Png
304        )
305    }
306}
307
308// Manual impl required because #[default] attribute conflicts with num_enum's #[num_enum(catch_all)]
309#[allow(clippy::derivable_impls)]
310impl Default for ObjectFormatCode {
311    fn default() -> Self {
312        ObjectFormatCode::Undefined
313    }
314}
315
316/// MTP object property codes.
317///
318/// These codes identify object properties that can be get/set via MTP operations.
319#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash, FromPrimitive, IntoPrimitive)]
320#[repr(u16)]
321pub enum ObjectPropertyCode {
322    /// Storage ID containing the object.
323    StorageId = 0xDC01,
324    /// Object format code.
325    ObjectFormat = 0xDC02,
326    /// Protection status (read-only, etc.).
327    ProtectionStatus = 0xDC03,
328    /// Object size in bytes.
329    ObjectSize = 0xDC04,
330    /// Object filename (key property for renaming).
331    ObjectFileName = 0xDC07,
332    /// Date the object was created.
333    DateCreated = 0xDC08,
334    /// Date the object was last modified.
335    DateModified = 0xDC09,
336    /// Parent object handle.
337    ParentObject = 0xDC0B,
338    /// Display name of the object.
339    Name = 0xDC44,
340    /// Unknown or vendor-specific property code.
341    #[num_enum(catch_all)]
342    Unknown(u16),
343}
344
345/// PTP property data type codes.
346///
347/// These codes identify the data type of property values in property descriptors.
348#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash, FromPrimitive, IntoPrimitive)]
349#[repr(u16)]
350pub enum PropertyDataType {
351    /// Undefined type (0x0000).
352    Undefined = 0x0000,
353    /// Signed 8-bit integer (0x0001).
354    Int8 = 0x0001,
355    /// Unsigned 8-bit integer (0x0002).
356    Uint8 = 0x0002,
357    /// Signed 16-bit integer (0x0003).
358    Int16 = 0x0003,
359    /// Unsigned 16-bit integer (0x0004).
360    Uint16 = 0x0004,
361    /// Signed 32-bit integer (0x0005).
362    Int32 = 0x0005,
363    /// Unsigned 32-bit integer (0x0006).
364    Uint32 = 0x0006,
365    /// Signed 64-bit integer (0x0007).
366    Int64 = 0x0007,
367    /// Unsigned 64-bit integer (0x0008).
368    Uint64 = 0x0008,
369    /// Signed 128-bit integer (0x0009, rarely used).
370    Int128 = 0x0009,
371    /// Unsigned 128-bit integer (0x000A, rarely used).
372    Uint128 = 0x000A,
373    /// Unknown type code.
374    #[num_enum(catch_all)]
375    Unknown(u16),
376    /// UTF-16LE string (0xFFFF).
377    String = 0xFFFF,
378}
379
380impl PropertyDataType {
381    /// Returns the byte size of this data type.
382    ///
383    /// Returns `None` for variable-length types (String) and unsupported types
384    /// (Undefined, Int128, Uint128, Unknown).
385    #[must_use]
386    pub fn byte_size(&self) -> Option<usize> {
387        match self {
388            PropertyDataType::Int8 | PropertyDataType::Uint8 => Some(1),
389            PropertyDataType::Int16 | PropertyDataType::Uint16 => Some(2),
390            PropertyDataType::Int32 | PropertyDataType::Uint32 => Some(4),
391            PropertyDataType::Int64 | PropertyDataType::Uint64 => Some(8),
392            PropertyDataType::Int128 | PropertyDataType::Uint128 => Some(16),
393            PropertyDataType::String
394            | PropertyDataType::Undefined
395            | PropertyDataType::Unknown(_) => None,
396        }
397    }
398}
399
400/// Standard PTP device property codes (0x5000 range).
401///
402/// These codes identify device-level properties that can be read or modified
403/// using the GetDevicePropDesc, GetDevicePropValue, SetDevicePropValue, and
404/// ResetDevicePropValue operations.
405///
406/// Device properties are primarily used with digital cameras for settings
407/// like ISO, aperture, shutter speed, etc. Most Android MTP devices do not
408/// support device properties.
409#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash, FromPrimitive, IntoPrimitive)]
410#[repr(u16)]
411pub enum DevicePropertyCode {
412    /// Undefined property.
413    Undefined = 0x5000,
414    /// Battery level (UINT8, 0-100 percent).
415    BatteryLevel = 0x5001,
416    /// Device functional mode (UINT16).
417    FunctionalMode = 0x5002,
418    /// Image size setting (String, e.g., "1920x1080").
419    ImageSize = 0x5003,
420    /// Compression setting (UINT8).
421    CompressionSetting = 0x5004,
422    /// White balance (UINT16).
423    WhiteBalance = 0x5005,
424    /// RGB gain (String).
425    RgbGain = 0x5006,
426    /// F-Number/Aperture (UINT16, value/100 = f-stop).
427    FNumber = 0x5007,
428    /// Focal length (UINT32, units of 0.01mm).
429    FocalLength = 0x5008,
430    /// Focus distance (UINT16, mm).
431    FocusDistance = 0x5009,
432    /// Focus mode (UINT16).
433    FocusMode = 0x500A,
434    /// Exposure metering mode (UINT16).
435    ExposureMeteringMode = 0x500B,
436    /// Flash mode (UINT16).
437    FlashMode = 0x500C,
438    /// Exposure time/shutter speed (UINT32, units of 0.0001s).
439    ExposureTime = 0x500D,
440    /// Exposure program mode (UINT16).
441    ExposureProgramMode = 0x500E,
442    /// Exposure index/ISO (UINT16).
443    ExposureIndex = 0x500F,
444    /// Exposure bias compensation (INT16, units of 0.001 EV).
445    ExposureBiasCompensation = 0x5010,
446    /// Date and time (String, "YYYYMMDDThhmmss").
447    DateTime = 0x5011,
448    /// Capture delay (UINT32, ms).
449    CaptureDelay = 0x5012,
450    /// Still capture mode (UINT16).
451    StillCaptureMode = 0x5013,
452    /// Contrast (UINT8).
453    Contrast = 0x5014,
454    /// Sharpness (UINT8).
455    Sharpness = 0x5015,
456    /// Digital zoom (UINT8).
457    DigitalZoom = 0x5016,
458    /// Effect mode (UINT16).
459    EffectMode = 0x5017,
460    /// Burst number (UINT16).
461    BurstNumber = 0x5018,
462    /// Burst interval (UINT16, ms).
463    BurstInterval = 0x5019,
464    /// Timelapse number (UINT16).
465    TimelapseNumber = 0x501A,
466    /// Timelapse interval (UINT32, ms).
467    TimelapseInterval = 0x501B,
468    /// Focus metering mode (UINT16).
469    FocusMeteringMode = 0x501C,
470    /// Upload URL (String).
471    UploadUrl = 0x501D,
472    /// Artist name (String).
473    Artist = 0x501E,
474    /// Copyright info (String).
475    CopyrightInfo = 0x501F,
476    /// Unknown/vendor-specific property code.
477    #[num_enum(catch_all)]
478    Unknown(u16),
479}
480
481#[cfg(test)]
482mod tests {
483    use super::*;
484
485    #[test]
486    fn from_extension_detection() {
487        // Audio (representative samples)
488        assert_eq!(
489            ObjectFormatCode::from_extension("mp3"),
490            ObjectFormatCode::Mp3
491        );
492        assert_eq!(
493            ObjectFormatCode::from_extension("flac"),
494            ObjectFormatCode::FlacAudio
495        );
496        assert_eq!(
497            ObjectFormatCode::from_extension("aif"),
498            ObjectFormatCode::Aiff
499        ); // alternate ext
500
501        // Video
502        assert_eq!(
503            ObjectFormatCode::from_extension("mp4"),
504            ObjectFormatCode::Mp4Container
505        );
506        assert_eq!(
507            ObjectFormatCode::from_extension("avi"),
508            ObjectFormatCode::Avi
509        );
510        assert_eq!(
511            ObjectFormatCode::from_extension("mpg"),
512            ObjectFormatCode::Mpeg
513        ); // alternate ext
514
515        // Image
516        assert_eq!(
517            ObjectFormatCode::from_extension("jpg"),
518            ObjectFormatCode::Jpeg
519        );
520        assert_eq!(
521            ObjectFormatCode::from_extension("png"),
522            ObjectFormatCode::Png
523        );
524        assert_eq!(
525            ObjectFormatCode::from_extension("tif"),
526            ObjectFormatCode::Tiff
527        ); // alternate ext
528
529        // Text/Documents
530        assert_eq!(
531            ObjectFormatCode::from_extension("txt"),
532            ObjectFormatCode::Text
533        );
534        assert_eq!(
535            ObjectFormatCode::from_extension("htm"),
536            ObjectFormatCode::Html
537        ); // alternate ext
538
539        // Executables/Scripts
540        assert_eq!(
541            ObjectFormatCode::from_extension("exe"),
542            ObjectFormatCode::Executable
543        );
544        assert_eq!(
545            ObjectFormatCode::from_extension("sh"),
546            ObjectFormatCode::Script
547        );
548
549        // Case insensitivity (one example suffices since .to_lowercase() is used)
550        assert_eq!(
551            ObjectFormatCode::from_extension("MP3"),
552            ObjectFormatCode::Mp3
553        );
554
555        // Unknown extensions
556        assert_eq!(
557            ObjectFormatCode::from_extension("xyz"),
558            ObjectFormatCode::Undefined
559        );
560        assert_eq!(
561            ObjectFormatCode::from_extension(""),
562            ObjectFormatCode::Undefined
563        );
564    }
565
566    // ==================== Format Category Tests ====================
567
568    #[test]
569    fn is_audio() {
570        assert!(ObjectFormatCode::Mp3.is_audio());
571        assert!(ObjectFormatCode::FlacAudio.is_audio());
572        assert!(!ObjectFormatCode::Jpeg.is_audio());
573        assert!(!ObjectFormatCode::Mp4Container.is_audio());
574    }
575
576    #[test]
577    fn is_video() {
578        assert!(ObjectFormatCode::Mp4Container.is_video());
579        assert!(ObjectFormatCode::Avi.is_video());
580        assert!(!ObjectFormatCode::Mp3.is_video());
581        assert!(!ObjectFormatCode::Jpeg.is_video());
582    }
583
584    #[test]
585    fn is_image() {
586        assert!(ObjectFormatCode::Jpeg.is_image());
587        assert!(ObjectFormatCode::Png.is_image());
588        assert!(!ObjectFormatCode::Mp3.is_image());
589        assert!(!ObjectFormatCode::Mp4Container.is_image());
590    }
591
592    #[test]
593    fn format_categories_are_mutually_exclusive() {
594        let all_formats = [
595            ObjectFormatCode::Undefined,
596            ObjectFormatCode::Association,
597            ObjectFormatCode::Script,
598            ObjectFormatCode::Executable,
599            ObjectFormatCode::Text,
600            ObjectFormatCode::Html,
601            ObjectFormatCode::Dpof,
602            ObjectFormatCode::Aiff,
603            ObjectFormatCode::Wav,
604            ObjectFormatCode::Mp3,
605            ObjectFormatCode::Avi,
606            ObjectFormatCode::Mpeg,
607            ObjectFormatCode::Asf,
608            ObjectFormatCode::Jpeg,
609            ObjectFormatCode::Tiff,
610            ObjectFormatCode::Gif,
611            ObjectFormatCode::Bmp,
612            ObjectFormatCode::Pict,
613            ObjectFormatCode::Png,
614            ObjectFormatCode::WmaAudio,
615            ObjectFormatCode::OggAudio,
616            ObjectFormatCode::AacAudio,
617            ObjectFormatCode::FlacAudio,
618            ObjectFormatCode::WmvVideo,
619            ObjectFormatCode::Mp4Container,
620            ObjectFormatCode::M4aAudio,
621        ];
622
623        for format in all_formats {
624            let categories = [format.is_audio(), format.is_video(), format.is_image()];
625            let true_count = categories.iter().filter(|&&b| b).count();
626            assert!(
627                true_count <= 1,
628                "{:?} belongs to multiple categories",
629                format
630            );
631        }
632    }
633
634    // ==================== PropertyDataType Tests ====================
635
636    #[test]
637    fn property_data_type_byte_size() {
638        // Fixed-size types
639        assert_eq!(PropertyDataType::Int8.byte_size(), Some(1));
640        assert_eq!(PropertyDataType::Uint8.byte_size(), Some(1));
641        assert_eq!(PropertyDataType::Int16.byte_size(), Some(2));
642        assert_eq!(PropertyDataType::Uint16.byte_size(), Some(2));
643        assert_eq!(PropertyDataType::Int32.byte_size(), Some(4));
644        assert_eq!(PropertyDataType::Uint32.byte_size(), Some(4));
645        assert_eq!(PropertyDataType::Int64.byte_size(), Some(8));
646        assert_eq!(PropertyDataType::Uint64.byte_size(), Some(8));
647        assert_eq!(PropertyDataType::Int128.byte_size(), Some(16));
648        assert_eq!(PropertyDataType::Uint128.byte_size(), Some(16));
649
650        // Variable/undefined types
651        assert_eq!(PropertyDataType::String.byte_size(), None);
652        assert_eq!(PropertyDataType::Undefined.byte_size(), None);
653        assert_eq!(PropertyDataType::Unknown(0x1234).byte_size(), None);
654    }
655}