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