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