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