mtp_rs/ptp/mod.rs
1//! Low-level PTP (Picture Transfer Protocol) implementation.
2//!
3//! This module provides direct access to the PTP/MTP protocol layer. Use this module when:
4//!
5//! - Working with digital cameras that use PTP
6//! - You need fine-grained control over protocol operations
7//! - Implementing custom MTP extensions or vendor operations
8//! - You need access to raw response codes for error handling
9//! - Building your own high-level abstractions
10//!
11//! ## When to use `mtp` instead
12//!
13//! Most users working with Android devices should prefer the high-level [`crate::mtp`] module,
14//! which provides a simpler API for common operations like listing files, uploading, and
15//! downloading.
16//!
17//! ## Module structure
18//!
19//! - `codes`: Operation, response, event, and format code enums
20//! - `pack`: Binary serialization/deserialization primitives
21//! - `container`: USB container format for PTP messages
22//! - `types`: DeviceInfo, StorageInfo, ObjectInfo structures
23//! - `session`: PTP session management
24//! - `device`: PtpDevice public API
25//!
26//! ## Example
27//!
28//! ```rust,no_run
29//! use mtp_rs::ptp::PtpDevice;
30//!
31//! # async fn example() -> Result<(), mtp_rs::Error> {
32//! // Open device and start a session
33//! let device = PtpDevice::open_first().await?;
34//! let session = device.open_session().await?;
35//!
36//! // Get device info
37//! let info = session.get_device_info().await?;
38//! println!("Model: {}", info.model);
39//!
40//! // List storage IDs
41//! let storage_ids = session.get_storage_ids().await?;
42//! # Ok(())
43//! # }
44//! ```
45
46mod codes;
47mod container;
48mod device;
49mod pack;
50mod session;
51#[cfg(test)]
52mod test_utils;
53mod types;
54
55pub use codes::{
56 DevicePropertyCode, EventCode, ObjectFormatCode, ObjectPropertyCode, OperationCode,
57 PropertyDataType, ResponseCode,
58};
59pub use container::{
60 container_type, CommandContainer, ContainerType, DataContainer, EventContainer,
61 ResponseContainer,
62};
63pub use device::PtpDevice;
64pub use pack::{
65 pack_datetime, pack_i16, pack_i32, pack_i64, pack_i8, pack_string, pack_u16, pack_u16_array,
66 pack_u32, pack_u32_array, pack_u64, pack_u8, unpack_datetime, unpack_i16, unpack_i32,
67 unpack_i64, unpack_i8, unpack_string, unpack_u16, unpack_u16_array, unpack_u32,
68 unpack_u32_array, unpack_u64, unpack_u8, DateTime,
69};
70pub use session::{receive_stream_to_stream, PtpSession, ReceiveStream};
71pub use types::{
72 AccessCapability, AssociationType, DeviceInfo, DevicePropDesc, FilesystemType, ObjectInfo,
73 PropertyFormType, PropertyRange, PropertyValue, ProtectionStatus, StorageInfo, StorageType,
74};
75
76/// 32-bit object handle assigned by the device.
77#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash, Default)]
78pub struct ObjectHandle(pub u32);
79
80impl ObjectHandle {
81 /// Root folder (parent = root means object is in storage root).
82 pub const ROOT: Self = ObjectHandle(0x00000000);
83 /// All objects (used in GetObjectHandles to list recursively).
84 pub const ALL: Self = ObjectHandle(0xFFFFFFFF);
85 /// Storage root as the *destination* of a new object: `SendObjectInfo`
86 /// parameter 2.
87 ///
88 /// MTP 1.1 D.2.12 is explicit: "If the initiator wishes to place an object
89 /// in the root of a given storage, it shall indicate the desired storage in
90 /// the first parameter and include a value of 0xFFFFFFFF in the second
91 /// parameter." A `0` there means "no parent specified, responder may
92 /// choose", so responders look it up as a handle and reject it: Android's
93 /// `MtpServer` only maps `MTP_PARENT_ROOT` (0xFFFFFFFF) to the storage path,
94 /// and libhaze (Nintendo Switch homebrew, #21) only maps 0xFFFFFFFF to its
95 /// storage object. Both answer `0` with `InvalidObjectHandle`.
96 ///
97 /// The spec is asymmetric, so don't unify this with [`ObjectHandle::ROOT`]:
98 /// `MoveObject`/`CopyObject` (D.2.25/D.2.26) spell the same destination
99 /// `0x00000000`.
100 pub const SEND_ROOT: Self = ObjectHandle(0xFFFFFFFF);
101}
102
103/// 32-bit storage identifier.
104#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash, Default)]
105pub struct StorageId(pub u32);
106
107impl StorageId {
108 /// All storages (used in GetObjectHandles to search all).
109 pub const ALL: Self = StorageId(0xFFFFFFFF);
110}
111
112/// 32-bit session identifier.
113#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash, Default)]
114pub struct SessionId(pub u32);
115
116/// 32-bit transaction identifier.
117#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash, Default)]
118pub struct TransactionId(pub u32);
119
120impl TransactionId {
121 /// The first valid transaction ID in a session.
122 pub const FIRST: Self = TransactionId(0x00000001);
123
124 /// Invalid transaction ID (must never be used).
125 pub const INVALID: Self = TransactionId(0xFFFFFFFF);
126
127 /// Transaction ID for session-less operations (e.g., GetDeviceInfo before OpenSession).
128 pub const SESSION_LESS: Self = TransactionId(0x00000000);
129
130 /// Get the next transaction ID, wrapping correctly.
131 ///
132 /// Wraps from 0xFFFFFFFE to 0x00000001 (skipping both 0x00000000 and 0xFFFFFFFF).
133 #[must_use]
134 pub fn next(self) -> Self {
135 let next = self.0.wrapping_add(1);
136 if next == 0 || next == 0xFFFFFFFF {
137 TransactionId(0x00000001)
138 } else {
139 TransactionId(next)
140 }
141 }
142}
143
144#[cfg(test)]
145mod tests {
146 use super::*;
147
148 #[test]
149 fn transaction_id_next() {
150 assert_eq!(TransactionId(1).next(), TransactionId(2));
151 assert_eq!(TransactionId(100).next(), TransactionId(101));
152 }
153
154 #[test]
155 fn transaction_id_wrapping() {
156 // Should wrap from 0xFFFFFFFE to 0x00000001, skipping 0xFFFFFFFF and 0x00000000
157 assert_eq!(TransactionId(0xFFFFFFFE).next(), TransactionId(1));
158 assert_eq!(TransactionId(0xFFFFFFFD).next(), TransactionId(0xFFFFFFFE));
159 }
160
161 #[test]
162 fn object_handle_constants() {
163 assert_eq!(ObjectHandle::ROOT.0, 0);
164 assert_eq!(ObjectHandle::ALL.0, 0xFFFFFFFF);
165 }
166
167 #[test]
168 fn storage_id_constants() {
169 assert_eq!(StorageId::ALL.0, 0xFFFFFFFF);
170 }
171}