Skip to main content

mtp_rs/ptp/session/
operations.rs

1//! High-level PTP/MTP operations.
2//!
3//! This module contains methods for common operations like getting device info,
4//! listing storage and objects, downloading/uploading files, etc.
5
6use crate::ptp::{
7    pack_string, unpack_u32_array, DeviceInfo, EventContainer, ObjectFormatCode, ObjectHandle,
8    ObjectInfo, ObjectPropertyCode, OperationCode, StorageId, StorageInfo,
9};
10use crate::Error;
11
12use super::PtpSession;
13
14impl PtpSession {
15    /// Returns information about the device including its capabilities,
16    /// manufacturer, model, and supported operations.
17    pub async fn get_device_info(&self) -> Result<DeviceInfo, Error> {
18        let (response, data) = self
19            .execute_with_receive(OperationCode::GetDeviceInfo, &[])
20            .await?;
21        Self::check_response(&response, OperationCode::GetDeviceInfo)?;
22        DeviceInfo::from_bytes(&data)
23    }
24
25    /// Returns a list of storage IDs available on the device.
26    pub async fn get_storage_ids(&self) -> Result<Vec<StorageId>, Error> {
27        let (response, data) = self
28            .execute_with_receive(OperationCode::GetStorageIds, &[])
29            .await?;
30        Self::check_response(&response, OperationCode::GetStorageIds)?;
31        let (ids, _) = unpack_u32_array(&data)?;
32        Ok(ids.into_iter().map(StorageId).collect())
33    }
34
35    /// Returns information about a specific storage, including capacity,
36    /// free space, and filesystem type.
37    pub async fn get_storage_info(&self, storage_id: StorageId) -> Result<StorageInfo, Error> {
38        let (response, data) = self
39            .execute_with_receive(OperationCode::GetStorageInfo, &[storage_id.0])
40            .await?;
41        Self::check_response(&response, OperationCode::GetStorageInfo)?;
42        StorageInfo::from_bytes(&data)
43    }
44
45    /// Get object handles.
46    ///
47    /// Returns a list of object handles matching the specified criteria.
48    ///
49    /// # Arguments
50    ///
51    /// * `storage_id` - Storage to search, or `StorageId::ALL` for all storages
52    /// * `format` - Filter by format, or `None` for all formats
53    /// * `parent` - Parent folder handle, or `None` for root level only,
54    ///   or `Some(ObjectHandle::ALL)` for recursive listing
55    pub async fn get_object_handles(
56        &self,
57        storage_id: StorageId,
58        format: Option<ObjectFormatCode>,
59        parent: Option<ObjectHandle>,
60    ) -> Result<Vec<ObjectHandle>, Error> {
61        let format_code = format.map(|f| u16::from(f) as u32).unwrap_or(0);
62        let parent_handle = parent.map(|p| p.0).unwrap_or(0); // 0 = root only
63
64        let (response, data) = self
65            .execute_with_receive(
66                OperationCode::GetObjectHandles,
67                &[storage_id.0, format_code, parent_handle],
68            )
69            .await?;
70        Self::check_response(&response, OperationCode::GetObjectHandles)?;
71        let (handles, _) = unpack_u32_array(&data)?;
72        Ok(handles.into_iter().map(ObjectHandle).collect())
73    }
74
75    /// Returns metadata about an object, including filename, size, and timestamps.
76    pub async fn get_object_info(&self, handle: ObjectHandle) -> Result<ObjectInfo, Error> {
77        let (response, data) = self
78            .execute_with_receive(OperationCode::GetObjectInfo, &[handle.0])
79            .await?;
80        Self::check_response(&response, OperationCode::GetObjectInfo)?;
81        ObjectInfo::from_bytes(&data)
82    }
83
84    /// Downloads the complete data of an object.
85    pub async fn get_object(&self, handle: ObjectHandle) -> Result<Vec<u8>, Error> {
86        let (response, data) = self
87            .execute_with_receive(OperationCode::GetObject, &[handle.0])
88            .await?;
89        Self::check_response(&response, OperationCode::GetObject)?;
90        Ok(data)
91    }
92
93    /// Get partial object.
94    ///
95    /// Downloads a portion of an object's data.
96    ///
97    /// # Arguments
98    ///
99    /// * `handle` - The object handle
100    /// * `offset` - Byte offset to start from (truncated to u32 in standard MTP)
101    /// * `max_bytes` - Maximum number of bytes to retrieve
102    pub async fn get_partial_object(
103        &self,
104        handle: ObjectHandle,
105        offset: u64,
106        max_bytes: u32,
107    ) -> Result<Vec<u8>, Error> {
108        // GetPartialObject params: handle, offset (u32), max_bytes (u32)
109        // Note: offset is truncated to u32 in standard MTP
110        let (response, data) = self
111            .execute_with_receive(
112                OperationCode::GetPartialObject,
113                &[handle.0, offset as u32, max_bytes],
114            )
115            .await?;
116        Self::check_response(&response, OperationCode::GetPartialObject)?;
117        Ok(data)
118    }
119
120    /// Downloads the thumbnail image for an object.
121    pub async fn get_thumb(&self, handle: ObjectHandle) -> Result<Vec<u8>, Error> {
122        let (response, data) = self
123            .execute_with_receive(OperationCode::GetThumb, &[handle.0])
124            .await?;
125        Self::check_response(&response, OperationCode::GetThumb)?;
126        Ok(data)
127    }
128
129    /// Send object info (prepare for upload).
130    ///
131    /// This must be called before `send_object()` to prepare the device for
132    /// receiving a new object.
133    ///
134    /// # Returns
135    ///
136    /// Returns a tuple of (storage_id, parent_handle, new_object_handle) where:
137    /// - `storage_id` - The storage where the object will be created
138    /// - `parent_handle` - The parent folder handle
139    /// - `new_object_handle` - The handle assigned to the new object
140    pub async fn send_object_info(
141        &self,
142        storage_id: StorageId,
143        parent: ObjectHandle,
144        info: &ObjectInfo,
145    ) -> Result<(StorageId, ObjectHandle, ObjectHandle), Error> {
146        let data = info.to_bytes()?;
147        let response = self
148            .execute_with_send(
149                OperationCode::SendObjectInfo,
150                &[storage_id.0, parent.0],
151                &data,
152            )
153            .await?;
154        Self::check_response(&response, OperationCode::SendObjectInfo)?;
155
156        // Response params: storage_id, parent_handle, object_handle
157        if response.params.len() < 3 {
158            return Err(Error::invalid_data(
159                "SendObjectInfo response missing params",
160            ));
161        }
162        Ok((
163            StorageId(response.params[0]),
164            ObjectHandle(response.params[1]),
165            ObjectHandle(response.params[2]),
166        ))
167    }
168
169    /// Send object data (must follow send_object_info).
170    ///
171    /// Uploads the actual data for an object. This must be called immediately
172    /// after `send_object_info()`.
173    pub async fn send_object(&self, data: &[u8]) -> Result<(), Error> {
174        let response = self
175            .execute_with_send(OperationCode::SendObject, &[], data)
176            .await?;
177        Self::check_response(&response, OperationCode::SendObject)?;
178        Ok(())
179    }
180
181    /// Deletes an object from the device.
182    pub async fn delete_object(&self, handle: ObjectHandle) -> Result<(), Error> {
183        // Param2 is format code, 0 means any format
184        let response = self
185            .execute(OperationCode::DeleteObject, &[handle.0, 0])
186            .await?;
187        Self::check_response(&response, OperationCode::DeleteObject)?;
188        Ok(())
189    }
190
191    /// Moves an object to a different location.
192    pub async fn move_object(
193        &self,
194        handle: ObjectHandle,
195        storage_id: StorageId,
196        parent: ObjectHandle,
197    ) -> Result<(), Error> {
198        let response = self
199            .execute(
200                OperationCode::MoveObject,
201                &[handle.0, storage_id.0, parent.0],
202            )
203            .await?;
204        Self::check_response(&response, OperationCode::MoveObject)?;
205        Ok(())
206    }
207
208    /// Copies an object to a new location.
209    /// Returns the handle of the newly created copy.
210    pub async fn copy_object(
211        &self,
212        handle: ObjectHandle,
213        storage_id: StorageId,
214        parent: ObjectHandle,
215    ) -> Result<ObjectHandle, Error> {
216        let response = self
217            .execute(
218                OperationCode::CopyObject,
219                &[handle.0, storage_id.0, parent.0],
220            )
221            .await?;
222        Self::check_response(&response, OperationCode::CopyObject)?;
223
224        if response.params.is_empty() {
225            return Err(Error::invalid_data("CopyObject response missing handle"));
226        }
227        Ok(ObjectHandle(response.params[0]))
228    }
229
230    /// Get object property value.
231    ///
232    /// Retrieves the value of a specific property for an object.
233    /// This is an MTP extension operation (0x9803).
234    ///
235    /// # Arguments
236    ///
237    /// * `handle` - The object handle
238    /// * `property` - The property code to retrieve
239    ///
240    /// # Returns
241    ///
242    /// Returns the raw property value as bytes.
243    pub async fn get_object_prop_value(
244        &self,
245        handle: ObjectHandle,
246        property: ObjectPropertyCode,
247    ) -> Result<Vec<u8>, Error> {
248        let (response, data) = self
249            .execute_with_receive(
250                OperationCode::GetObjectPropValue,
251                &[handle.0, u16::from(property) as u32],
252            )
253            .await?;
254        Self::check_response(&response, OperationCode::GetObjectPropValue)?;
255        Ok(data)
256    }
257
258    /// Set object property value.
259    ///
260    /// Sets the value of a specific property for an object.
261    /// This is an MTP extension operation (0x9804).
262    ///
263    /// # Arguments
264    ///
265    /// * `handle` - The object handle
266    /// * `property` - The property code to set
267    /// * `value` - The raw property value as bytes
268    pub async fn set_object_prop_value(
269        &self,
270        handle: ObjectHandle,
271        property: ObjectPropertyCode,
272        value: &[u8],
273    ) -> Result<(), Error> {
274        let response = self
275            .execute_with_send(
276                OperationCode::SetObjectPropValue,
277                &[handle.0, u16::from(property) as u32],
278                value,
279            )
280            .await?;
281        Self::check_response(&response, OperationCode::SetObjectPropValue)?;
282        Ok(())
283    }
284
285    /// Rename an object (file or folder).
286    ///
287    /// This is a convenience method that uses SetObjectPropValue to change
288    /// the ObjectFileName property (0xDC07).
289    ///
290    /// # Arguments
291    ///
292    /// * `handle` - The object handle to rename
293    /// * `new_name` - The new filename
294    ///
295    /// # Note
296    ///
297    /// Not all devices support renaming. Check `supports_rename()` on DeviceInfo first.
298    pub async fn rename_object(&self, handle: ObjectHandle, new_name: &str) -> Result<(), Error> {
299        let name_bytes = pack_string(new_name);
300        self.set_object_prop_value(handle, ObjectPropertyCode::ObjectFileName, &name_bytes)
301            .await
302    }
303
304    // --- Capture operations ---
305
306    /// Initiate a capture operation.
307    ///
308    /// This triggers the camera to capture an image. The operation is asynchronous;
309    /// use `poll_event()` to wait for `CaptureComplete` and `ObjectAdded` events.
310    ///
311    /// # Arguments
312    ///
313    /// * `storage_id` - Target storage (use `StorageId(0)` for camera default)
314    /// * `format` - Object format for the capture (use `ObjectFormatCode::Undefined`
315    ///   for camera default)
316    ///
317    /// # Events
318    ///
319    /// After calling this method, monitor for these events:
320    /// - `EventCode::CaptureComplete` - Capture operation finished
321    /// - `EventCode::ObjectAdded` - New object (image) was created on device
322    ///
323    /// # Example
324    ///
325    /// ```rust,ignore
326    /// // Trigger capture
327    /// session.initiate_capture(StorageId(0), ObjectFormatCode::Undefined).await?;
328    ///
329    /// // Wait for events
330    /// loop {
331    ///     match session.poll_event().await? {
332    ///         Some(event) if event.code == EventCode::CaptureComplete => {
333    ///             println!("Capture complete!");
334    ///             break;
335    ///         }
336    ///         Some(event) if event.code == EventCode::ObjectAdded => {
337    ///             println!("New object: {}", event.params[0]);
338    ///         }
339    ///         _ => continue,
340    ///     }
341    /// }
342    /// ```
343    pub async fn initiate_capture(
344        &self,
345        storage_id: StorageId,
346        format: ObjectFormatCode,
347    ) -> Result<(), Error> {
348        // Per PTP spec, 0x00000000 means "any format" / "use device default".
349        // ObjectFormatCode::Undefined (0x3000) is different and may not be accepted.
350        let format_code = match format {
351            ObjectFormatCode::Undefined => 0,
352            other => u16::from(other) as u32,
353        };
354        let response = self
355            .execute(OperationCode::InitiateCapture, &[storage_id.0, format_code])
356            .await?;
357        Self::check_response(&response, OperationCode::InitiateCapture)?;
358        Ok(())
359    }
360
361    // --- Event handling ---
362
363    /// Poll for a single event from the interrupt endpoint.
364    ///
365    /// This method waits until an event is received from the USB interrupt endpoint.
366    /// Events are asynchronous notifications from the device about changes such as
367    /// objects being added/removed, storage changes, etc.
368    ///
369    /// Note: This method does not require the operation lock since events are
370    /// received on the interrupt endpoint, which is independent of bulk transfers.
371    ///
372    /// # Returns
373    ///
374    /// - `Ok(Some(container))` - An event was received
375    /// - `Ok(None)` - Timeout (only if caller wraps with their own timeout)
376    /// - `Err(_)` - Communication error
377    pub async fn poll_event(&self) -> Result<Option<EventContainer>, Error> {
378        match self.transport.receive_interrupt().await {
379            Ok(bytes) => {
380                let container = EventContainer::from_bytes(&bytes)?;
381                Ok(Some(container))
382            }
383            Err(Error::Timeout) => Ok(None),
384            Err(e) => Err(e),
385        }
386    }
387}
388
389#[cfg(test)]
390mod tests {
391    use super::*;
392    use crate::ptp::session::tests::{
393        data_container, mock_transport, ok_response, response_with_params,
394    };
395    use crate::ptp::{
396        pack_string, pack_u16, pack_u32, pack_u32_array, ContainerType, ResponseCode,
397    };
398
399    fn event_container(code: u16, params: [u32; 3]) -> Vec<u8> {
400        let mut buf = Vec::with_capacity(24);
401        buf.extend_from_slice(&pack_u32(24)); // length = 24
402        buf.extend_from_slice(&pack_u16(ContainerType::Event.to_code()));
403        buf.extend_from_slice(&pack_u16(code));
404        buf.extend_from_slice(&pack_u32(0)); // transaction_id
405        buf.extend_from_slice(&pack_u32(params[0]));
406        buf.extend_from_slice(&pack_u32(params[1]));
407        buf.extend_from_slice(&pack_u32(params[2]));
408        buf
409    }
410
411    #[tokio::test]
412    async fn test_get_storage_ids() {
413        let (transport, mock) = mock_transport();
414        mock.queue_response(ok_response(1)); // OpenSession
415
416        // GetStorageIds data response
417        let storage_ids_data = pack_u32_array(&[0x00010001, 0x00010002]);
418        mock.queue_response(data_container(
419            2,
420            OperationCode::GetStorageIds,
421            &storage_ids_data,
422        ));
423        mock.queue_response(ok_response(2));
424
425        let session = PtpSession::open(transport, 1).await.unwrap();
426        let ids = session.get_storage_ids().await.unwrap();
427
428        assert_eq!(ids, vec![StorageId(0x00010001), StorageId(0x00010002)]);
429    }
430
431    #[tokio::test]
432    async fn test_get_object_handles() {
433        let (transport, mock) = mock_transport();
434        mock.queue_response(ok_response(1)); // OpenSession
435
436        // GetObjectHandles data response
437        let handles_data = pack_u32_array(&[1, 2, 3]);
438        mock.queue_response(data_container(
439            2,
440            OperationCode::GetObjectHandles,
441            &handles_data,
442        ));
443        mock.queue_response(ok_response(2));
444
445        let session = PtpSession::open(transport, 1).await.unwrap();
446        let handles = session
447            .get_object_handles(StorageId::ALL, None, None)
448            .await
449            .unwrap();
450
451        assert_eq!(
452            handles,
453            vec![ObjectHandle(1), ObjectHandle(2), ObjectHandle(3)]
454        );
455    }
456
457    #[tokio::test]
458    async fn test_get_object() {
459        let (transport, mock) = mock_transport();
460        mock.queue_response(ok_response(1)); // OpenSession
461
462        // GetObject data response
463        let object_data = vec![0x01, 0x02, 0x03, 0x04, 0x05];
464        mock.queue_response(data_container(2, OperationCode::GetObject, &object_data));
465        mock.queue_response(ok_response(2));
466
467        let session = PtpSession::open(transport, 1).await.unwrap();
468        let data = session.get_object(ObjectHandle(1)).await.unwrap();
469
470        assert_eq!(data, object_data);
471    }
472
473    #[tokio::test]
474    async fn test_delete_object() {
475        let (transport, mock) = mock_transport();
476        mock.queue_response(ok_response(1)); // OpenSession
477        mock.queue_response(ok_response(2)); // DeleteObject
478
479        let session = PtpSession::open(transport, 1).await.unwrap();
480        session.delete_object(ObjectHandle(1)).await.unwrap();
481    }
482
483    #[tokio::test]
484    async fn test_copy_object() {
485        let (transport, mock) = mock_transport();
486        mock.queue_response(ok_response(1)); // OpenSession
487        mock.queue_response(response_with_params(2, ResponseCode::Ok, &[100])); // CopyObject with new handle
488
489        let session = PtpSession::open(transport, 1).await.unwrap();
490        let new_handle = session
491            .copy_object(ObjectHandle(1), StorageId(0x00010001), ObjectHandle::ROOT)
492            .await
493            .unwrap();
494
495        assert_eq!(new_handle, ObjectHandle(100));
496    }
497
498    // --- Event polling tests ---
499
500    #[tokio::test]
501    async fn test_poll_event_object_added() {
502        use crate::ptp::EventCode;
503
504        let (transport, mock) = mock_transport();
505        mock.queue_response(ok_response(1)); // OpenSession
506
507        // Queue an ObjectAdded event (code 0x4002)
508        mock.queue_interrupt(event_container(0x4002, [42, 0, 0]));
509
510        let session = PtpSession::open(transport, 1).await.unwrap();
511        let event = session.poll_event().await.unwrap().unwrap();
512
513        assert_eq!(event.code, EventCode::ObjectAdded);
514        assert_eq!(event.params[0], 42);
515    }
516
517    #[tokio::test]
518    async fn test_poll_event_store_removed() {
519        use crate::ptp::EventCode;
520
521        let (transport, mock) = mock_transport();
522        mock.queue_response(ok_response(1)); // OpenSession
523
524        // Queue a StoreRemoved event (code 0x4005)
525        mock.queue_interrupt(event_container(0x4005, [0x00010001, 0, 0]));
526
527        let session = PtpSession::open(transport, 1).await.unwrap();
528        let event = session.poll_event().await.unwrap().unwrap();
529
530        assert_eq!(event.code, EventCode::StoreRemoved);
531        assert_eq!(event.params[0], 0x00010001);
532    }
533
534    #[tokio::test]
535    async fn test_poll_event_multiple_events() {
536        use crate::ptp::EventCode;
537
538        let (transport, mock) = mock_transport();
539        mock.queue_response(ok_response(1)); // OpenSession
540
541        // Queue multiple events
542        mock.queue_interrupt(event_container(0x4002, [1, 0, 0])); // ObjectAdded
543        mock.queue_interrupt(event_container(0x4002, [2, 0, 0])); // ObjectAdded
544        mock.queue_interrupt(event_container(0x4003, [1, 0, 0])); // ObjectRemoved
545
546        let session = PtpSession::open(transport, 1).await.unwrap();
547
548        let event1 = session.poll_event().await.unwrap().unwrap();
549        assert_eq!(event1.code, EventCode::ObjectAdded);
550        assert_eq!(event1.params[0], 1);
551
552        let event2 = session.poll_event().await.unwrap().unwrap();
553        assert_eq!(event2.code, EventCode::ObjectAdded);
554        assert_eq!(event2.params[0], 2);
555
556        let event3 = session.poll_event().await.unwrap().unwrap();
557        assert_eq!(event3.code, EventCode::ObjectRemoved);
558        assert_eq!(event3.params[0], 1);
559    }
560
561    // --- Object property and rename tests ---
562
563    #[tokio::test]
564    async fn test_get_object_prop_value() {
565        let (transport, mock) = mock_transport();
566        mock.queue_response(ok_response(1)); // OpenSession
567
568        // GetObjectPropValue data response (property value is raw bytes)
569        let prop_value = vec![0x05, 0x48, 0x00, 0x69, 0x00, 0x00, 0x00]; // Packed string "Hi"
570        mock.queue_response(data_container(
571            2,
572            OperationCode::GetObjectPropValue,
573            &prop_value,
574        ));
575        mock.queue_response(ok_response(2));
576
577        let session = PtpSession::open(transport, 1).await.unwrap();
578        let data = session
579            .get_object_prop_value(ObjectHandle(1), ObjectPropertyCode::ObjectFileName)
580            .await
581            .unwrap();
582
583        assert_eq!(data, prop_value);
584    }
585
586    #[tokio::test]
587    async fn test_set_object_prop_value() {
588        let (transport, mock) = mock_transport();
589        mock.queue_response(ok_response(1)); // OpenSession
590        mock.queue_response(ok_response(2)); // SetObjectPropValue
591
592        let session = PtpSession::open(transport, 1).await.unwrap();
593        let prop_value = pack_string("newfile.txt");
594        session
595            .set_object_prop_value(
596                ObjectHandle(1),
597                ObjectPropertyCode::ObjectFileName,
598                &prop_value,
599            )
600            .await
601            .unwrap();
602    }
603
604    #[tokio::test]
605    async fn test_set_object_prop_value_not_supported() {
606        let (transport, mock) = mock_transport();
607        mock.queue_response(ok_response(1)); // OpenSession
608        mock.queue_response(response_with_params(
609            2,
610            ResponseCode::OperationNotSupported,
611            &[],
612        ));
613
614        let session = PtpSession::open(transport, 1).await.unwrap();
615        let prop_value = pack_string("newfile.txt");
616        let result = session
617            .set_object_prop_value(
618                ObjectHandle(1),
619                ObjectPropertyCode::ObjectFileName,
620                &prop_value,
621            )
622            .await;
623
624        assert!(matches!(
625            result,
626            Err(crate::Error::Protocol {
627                code: ResponseCode::OperationNotSupported,
628                ..
629            })
630        ));
631    }
632
633    #[tokio::test]
634    async fn test_rename_object() {
635        let (transport, mock) = mock_transport();
636        mock.queue_response(ok_response(1)); // OpenSession
637        mock.queue_response(ok_response(2)); // SetObjectPropValue (for rename)
638
639        let session = PtpSession::open(transport, 1).await.unwrap();
640        session
641            .rename_object(ObjectHandle(1), "renamed.txt")
642            .await
643            .unwrap();
644    }
645
646    #[tokio::test]
647    async fn test_rename_object_not_supported() {
648        let (transport, mock) = mock_transport();
649        mock.queue_response(ok_response(1)); // OpenSession
650        mock.queue_response(response_with_params(
651            2,
652            ResponseCode::OperationNotSupported,
653            &[],
654        ));
655
656        let session = PtpSession::open(transport, 1).await.unwrap();
657        let result = session.rename_object(ObjectHandle(1), "renamed.txt").await;
658
659        assert!(matches!(
660            result,
661            Err(crate::Error::Protocol {
662                code: ResponseCode::OperationNotSupported,
663                ..
664            })
665        ));
666    }
667
668    // --- Capture tests ---
669
670    #[tokio::test]
671    async fn test_initiate_capture() {
672        let (transport, mock) = mock_transport();
673        mock.queue_response(ok_response(1)); // OpenSession
674        mock.queue_response(ok_response(2)); // InitiateCapture
675
676        let session = PtpSession::open(transport, 1).await.unwrap();
677        session
678            .initiate_capture(StorageId(0), ObjectFormatCode::Undefined)
679            .await
680            .unwrap();
681    }
682
683    #[tokio::test]
684    async fn test_initiate_capture_with_format() {
685        let (transport, mock) = mock_transport();
686        mock.queue_response(ok_response(1)); // OpenSession
687        mock.queue_response(ok_response(2)); // InitiateCapture
688
689        let session = PtpSession::open(transport, 1).await.unwrap();
690        session
691            .initiate_capture(StorageId(0x00010001), ObjectFormatCode::Jpeg)
692            .await
693            .unwrap();
694    }
695
696    #[tokio::test]
697    async fn test_initiate_capture_not_supported() {
698        let (transport, mock) = mock_transport();
699        mock.queue_response(ok_response(1)); // OpenSession
700        mock.queue_response(response_with_params(
701            2,
702            ResponseCode::OperationNotSupported,
703            &[],
704        ));
705
706        let session = PtpSession::open(transport, 1).await.unwrap();
707        let result = session
708            .initiate_capture(StorageId(0), ObjectFormatCode::Undefined)
709            .await;
710
711        assert!(matches!(
712            result,
713            Err(crate::Error::Protocol {
714                code: ResponseCode::OperationNotSupported,
715                ..
716            })
717        ));
718    }
719}