Skip to main content

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,ignore
29//! use mtp_rs::ptp::{PtpDevice, PtpSession};
30//! use mtp_rs::transport::NusbTransport;
31//!
32//! // Open device and start a session
33//! let transport = NusbTransport::open_first().await?;
34//! let device = PtpDevice::new(transport);
35//! let session = device.open_session().await?;
36//!
37//! // Get device info
38//! let info = session.get_device_info().await?;
39//! println!("Model: {}", info.model);
40//!
41//! // List storage IDs
42//! let storage_ids = session.get_storage_ids().await?;
43//! ```
44
45mod codes;
46mod container;
47mod device;
48mod pack;
49mod session;
50#[cfg(test)]
51mod test_utils;
52mod types;
53
54pub use codes::{
55    DevicePropertyCode, EventCode, ObjectFormatCode, ObjectPropertyCode, OperationCode,
56    PropertyDataType, ResponseCode,
57};
58pub use container::{
59    container_type, CommandContainer, ContainerType, DataContainer, EventContainer,
60    ResponseContainer,
61};
62pub use device::PtpDevice;
63pub use pack::{
64    pack_datetime, pack_i16, pack_i32, pack_i64, pack_i8, pack_string, pack_u16, pack_u16_array,
65    pack_u32, pack_u32_array, pack_u64, pack_u8, unpack_datetime, unpack_i16, unpack_i32,
66    unpack_i64, unpack_i8, unpack_string, unpack_u16, unpack_u16_array, unpack_u32,
67    unpack_u32_array, unpack_u64, unpack_u8, DateTime,
68};
69pub use session::{receive_stream_to_stream, PtpSession, ReceiveStream};
70pub use types::{
71    AccessCapability, AssociationType, DeviceInfo, DevicePropDesc, FilesystemType, ObjectInfo,
72    PropertyFormType, PropertyRange, PropertyValue, ProtectionStatus, StorageInfo, StorageType,
73};
74
75/// 32-bit object handle assigned by the device.
76#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash, Default)]
77pub struct ObjectHandle(pub u32);
78
79impl ObjectHandle {
80    /// Root folder (parent = root means object is in storage root).
81    pub const ROOT: Self = ObjectHandle(0x00000000);
82    /// All objects (used in GetObjectHandles to list recursively).
83    pub const ALL: Self = ObjectHandle(0xFFFFFFFF);
84}
85
86/// 32-bit storage identifier.
87#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash, Default)]
88pub struct StorageId(pub u32);
89
90impl StorageId {
91    /// All storages (used in GetObjectHandles to search all).
92    pub const ALL: Self = StorageId(0xFFFFFFFF);
93}
94
95/// 32-bit session identifier.
96#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash, Default)]
97pub struct SessionId(pub u32);
98
99/// 32-bit transaction identifier.
100#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash, Default)]
101pub struct TransactionId(pub u32);
102
103impl TransactionId {
104    /// The first valid transaction ID in a session.
105    pub const FIRST: Self = TransactionId(0x00000001);
106
107    /// Invalid transaction ID (must never be used).
108    pub const INVALID: Self = TransactionId(0xFFFFFFFF);
109
110    /// Transaction ID for session-less operations (e.g., GetDeviceInfo before OpenSession).
111    pub const SESSION_LESS: Self = TransactionId(0x00000000);
112
113    /// Get the next transaction ID, wrapping correctly.
114    ///
115    /// Wraps from 0xFFFFFFFE to 0x00000001 (skipping both 0x00000000 and 0xFFFFFFFF).
116    #[must_use]
117    pub fn next(self) -> Self {
118        let next = self.0.wrapping_add(1);
119        if next == 0 || next == 0xFFFFFFFF {
120            TransactionId(0x00000001)
121        } else {
122            TransactionId(next)
123        }
124    }
125}
126
127#[cfg(test)]
128mod tests {
129    use super::*;
130
131    #[test]
132    fn transaction_id_next() {
133        assert_eq!(TransactionId(1).next(), TransactionId(2));
134        assert_eq!(TransactionId(100).next(), TransactionId(101));
135    }
136
137    #[test]
138    fn transaction_id_wrapping() {
139        // Should wrap from 0xFFFFFFFE to 0x00000001, skipping 0xFFFFFFFF and 0x00000000
140        assert_eq!(TransactionId(0xFFFFFFFE).next(), TransactionId(1));
141        assert_eq!(TransactionId(0xFFFFFFFD).next(), TransactionId(0xFFFFFFFE));
142    }
143
144    #[test]
145    fn object_handle_constants() {
146        assert_eq!(ObjectHandle::ROOT.0, 0);
147        assert_eq!(ObjectHandle::ALL.0, 0xFFFFFFFF);
148    }
149
150    #[test]
151    fn storage_id_constants() {
152        assert_eq!(StorageId::ALL.0, 0xFFFFFFFF);
153    }
154}