Skip to content

Commit 5ca3913

Browse files
andrewleechclaude
andcommitted
usb-device-mtp: Refactor command handlers to use uctypes structs
- Add uctypes struct definitions for MTP protocol data structures - Convert GetDeviceInfo command to use uctypes structs - Convert GetStorageIds command to use uctypes structs - Convert GetStorageInfo command to use uctypes structs - Convert GetObjectHandles command to use uctypes structs - Update container header handling in _send_data and _send_response - Improve _process_rx to use uctypes for parsing container headers This refactoring improves code maintainability and type safety by using structured memory representation instead of manual byte packing. 🤖 Generated with [Claude Code](https://claude.ai/code) Co-Authored-By: Claude <noreply@anthropic.com>
1 parent 97223c7 commit 5ca3913

File tree

1 file changed

+127
-63
lines changed
  • micropython/usb/usb-device-mtp/usb/device

1 file changed

+127
-63
lines changed

micropython/usb/usb-device-mtp/usb/device/mtp.py

Lines changed: 127 additions & 63 deletions
Original file line numberDiff line numberDiff line change
@@ -6,6 +6,7 @@
66
import os
77
import io
88
import errno
9+
import uctypes
910

1011
from .core import Interface, Buffer, split_bmRequestType
1112

@@ -120,6 +121,46 @@
120121
_DEFAULT_RX_BUF_SIZE = const(4096)
121122
_CONTAINER_HEADER_SIZE = const(12)
122123

124+
# MTP struct definitions using uctypes
125+
# Container header struct
126+
_MTP_CONTAINER_HEADER_DESC = {
127+
"length": (0, uctypes.UINT32),
128+
"type": (4, uctypes.UINT16),
129+
"code": (6, uctypes.UINT16),
130+
"transaction_id": (8, uctypes.UINT32)
131+
}
132+
133+
# Device Info struct
134+
_MTP_DEVICE_INFO_DESC = {
135+
"standard_version": (0, uctypes.UINT16),
136+
"vendor_extension_id": (2, uctypes.UINT32),
137+
"mtp_version": (6, uctypes.UINT16),
138+
# Variable length data follows: extension string, operations, events, etc.
139+
}
140+
141+
# Storage IDs struct
142+
_MTP_STORAGE_IDS_DESC = {
143+
"count": (0, uctypes.UINT32),
144+
"storage_ids": (4, uctypes.ARRAY, 1, uctypes.UINT32) # Variable length array
145+
}
146+
147+
# Storage Info struct
148+
_MTP_STORAGE_INFO_DESC = {
149+
"storage_type": (0, uctypes.UINT16),
150+
"filesystem_type": (2, uctypes.UINT16),
151+
"access_capability": (4, uctypes.UINT16),
152+
"max_capacity": (6, uctypes.UINT64),
153+
"free_space": (14, uctypes.UINT64),
154+
"free_space_objects": (22, uctypes.UINT32)
155+
# Variable length data follows: storage_description, volume_identifier
156+
}
157+
158+
# Object Handles struct
159+
_MTP_OBJECT_HANDLES_DESC = {
160+
"count": (0, uctypes.UINT32),
161+
"handles": (4, uctypes.ARRAY, 1, uctypes.UINT32) # Variable length array
162+
}
163+
123164

124165
class MTPInterface(Interface):
125166
"""USB MTP device interface for MicroPython.
@@ -298,11 +339,15 @@ def _process_rx(self, _):
298339
# Peek at the container header without consuming it yet
299340
header = self._rx.pend_read()
300341

301-
# Parse container header
302-
length = struct.unpack_from("<I", header, 0)[0]
303-
container_type = struct.unpack_from("<H", header, 4)[0]
304-
code = struct.unpack_from("<H", header, 6)[0]
305-
transaction_id = struct.unpack_from("<I", header, 8)[0]
342+
# Parse container header using uctypes
343+
# Create a container header struct over the header buffer
344+
hdr = uctypes.struct(uctypes.addressof(header), _MTP_CONTAINER_HEADER_DESC, uctypes.LITTLE_ENDIAN)
345+
346+
# Extract values from the struct
347+
length = hdr.length
348+
container_type = hdr.type
349+
code = hdr.code
350+
transaction_id = hdr.transaction_id
306351

307352
container_types = {
308353
_MTP_CONTAINER_TYPE_COMMAND: "COMMAND",
@@ -476,38 +521,35 @@ def _cmd_get_device_info(self):
476521
"""Handle GetDeviceInfo command."""
477522
self._log("Generating device info response")
478523

479-
# Prepare the device info dataset
480-
data = bytearray(512) # Pre-allocate buffer
481-
offset = 0
524+
# Allocate a buffer for device info
525+
data = bytearray(512) # Pre-allocate buffer - device info has variable length
482526

483-
# Standard version
484-
struct.pack_into("<H", data, offset, 100) # Version 1.00
485-
offset += 2
527+
# Create a device info struct
528+
dev_info = uctypes.struct(uctypes.addressof(data), _MTP_DEVICE_INFO_DESC, uctypes.LITTLE_ENDIAN)
486529

487-
# MTP vendor extension ID
488-
# Use Microsoft's extension ID to better identify as a true MTP device
489-
struct.pack_into("<I", data, offset, 0x00000006) # Microsoft MTP Extension
490-
offset += 4
530+
# Fill in the fixed fields
531+
dev_info.standard_version = 100 # Version 1.00
532+
dev_info.vendor_extension_id = 0x00000006 # Microsoft MTP Extension
533+
dev_info.mtp_version = 100 # Version 1.00
491534

492-
# MTP version
493-
struct.pack_into("<H", data, offset, 100) # Version 1.00
494-
offset += 2
535+
# Handle variable-length data after the fixed struct
536+
offset = 8 # Start after the fixed part of the struct
495537

496538
# MTP extensions description string - Microsoft extension
497539
# MTP extension strings are ASCII strings in PIMA format (8-bit length + 8-bit chars with null terminator)
498540
ext_string = "microsoft.com: 1.0" # Standard Microsoft extension string
499541

500542
# String length (8-bit, including null terminator)
501-
struct.pack_into("<B", data, offset, len(ext_string) + 1)
543+
data[offset] = len(ext_string) + 1
502544
offset += 1
503545

504546
# String data as ASCII
505547
for c in ext_string:
506-
struct.pack_into("<B", data, offset, ord(c))
548+
data[offset] = ord(c)
507549
offset += 1
508550

509551
# ASCII null terminator
510-
struct.pack_into("<B", data, offset, 0)
552+
data[offset] = 0
511553
offset += 1
512554

513555
# Functional mode
@@ -529,8 +571,12 @@ def _cmd_get_device_info(self):
529571
_MTP_OPERATION_SEND_OBJECT_INFO,
530572
_MTP_OPERATION_SEND_OBJECT,
531573
]
574+
575+
# Number of operations
532576
struct.pack_into("<H", data, offset, len(operations))
533577
offset += 2
578+
579+
# List of operation codes
534580
for op in operations:
535581
struct.pack_into("<H", data, offset, op)
536582
offset += 2
@@ -553,8 +599,12 @@ def _cmd_get_device_info(self):
553599
_MTP_FORMAT_TEXT, # text files
554600
_MTP_FORMAT_UNDEFINED # all other files
555601
]
602+
603+
# Number of formats
556604
struct.pack_into("<H", data, offset, len(formats))
557605
offset += 2
606+
607+
# List of format codes
558608
for fmt in formats:
559609
struct.pack_into("<H", data, offset, fmt)
560610
offset += 2
@@ -618,11 +668,15 @@ def _cmd_get_storage_ids(self):
618668
# We only support a single storage
619669
self._log("GetStorageIDs: Reporting storage ID: 0x{:08x}", self._storage_id)
620670

621-
# Format: 4 bytes for count, 4 bytes per storage ID
622-
data = bytearray(8)
671+
# Create a buffer for storage IDs - 4 bytes for count, 4 bytes per storage ID
672+
data = bytearray(8) # 4 bytes for count + 4 bytes for one storage ID
623673

624-
# Pack count (1) followed by our storage ID
625-
struct.pack_into("<II", data, 0, 1, self._storage_id) # Count=1, ID=storage_id
674+
# Create a storage IDs struct
675+
storage_ids = uctypes.struct(uctypes.addressof(data), _MTP_STORAGE_IDS_DESC, uctypes.LITTLE_ENDIAN)
676+
677+
# Fill the struct
678+
storage_ids.count = 1 # We only support one storage
679+
storage_ids.storage_ids[0] = self._storage_id
626680

627681
# Send the storage IDs array
628682
self._send_data(data)
@@ -649,33 +703,22 @@ def _cmd_get_storage_info(self, params):
649703
free_bytes = 1024 * 1024 # 1MB
650704
total_bytes = 4 * 1024 * 1024 # 4MB
651705

652-
# Prepare storage info dataset
706+
# Create a buffer for storage info (fixed part is 26 bytes, plus variable-length strings)
653707
data = bytearray(128)
654-
offset = 0
655708

656-
# Storage type
657-
struct.pack_into("<H", data, offset, _MTP_STORAGE_FIXED_RAM)
658-
offset += 2
659-
660-
# Filesystem type
661-
struct.pack_into("<H", data, offset, 0x0002) # Generic hierarchical
662-
offset += 2
663-
664-
# Access capability
665-
struct.pack_into("<H", data, offset, _MTP_STORAGE_READ_WRITE) # Read-write access
666-
offset += 2
667-
668-
# Max capacity - use 64-bit value (8 bytes)
669-
struct.pack_into("<Q", data, offset, total_bytes)
670-
offset += 8
709+
# Create a storage info struct
710+
storage_info = uctypes.struct(uctypes.addressof(data), _MTP_STORAGE_INFO_DESC, uctypes.LITTLE_ENDIAN)
671711

672-
# Free space - use 64-bit value (8 bytes)
673-
struct.pack_into("<Q", data, offset, free_bytes)
674-
offset += 8
712+
# Fill in the fixed fields
713+
storage_info.storage_type = _MTP_STORAGE_FIXED_RAM
714+
storage_info.filesystem_type = 0x0002 # Generic hierarchical
715+
storage_info.access_capability = _MTP_STORAGE_READ_WRITE # Read-write access
716+
storage_info.max_capacity = total_bytes
717+
storage_info.free_space = free_bytes
718+
storage_info.free_space_objects = 0xFFFFFFFF # Maximum value - unknown
675719

676-
# Free space in objects (unknown - use 0xFFFFFFFF)
677-
struct.pack_into("<I", data, offset, 0xFFFFFFFF) # Maximum value
678-
offset += 4
720+
# Handle variable-length data after the fixed struct
721+
offset = 26 # Start after the fixed part
679722

680723
# Storage description
681724
offset += self._write_mtp_string(data, offset, "MicroPython Flash Storage")
@@ -733,11 +776,24 @@ def _cmd_get_object_handles(self, params):
733776
if format_code == 0 or self._get_format_by_path(self._object_handles[handle]) == format_code:
734777
handles.append(handle)
735778

736-
# Prepare and send the array of handles
737-
data = bytearray(4 + len(handles) * 4)
738-
struct.pack_into("<I", data, 0, len(handles)) # Count
779+
# Create a buffer for the handles array
780+
data_size = 4 + len(handles) * 4 # 4 bytes for count + 4 bytes per handle
781+
data = bytearray(data_size)
782+
783+
# For the _MTP_OBJECT_HANDLES_DESC, we need to dynamically adjust the array size
784+
# Create a custom descriptor with the actual array size
785+
obj_handles_desc = {
786+
"count": (0, uctypes.UINT32),
787+
"handles": (4, uctypes.ARRAY, len(handles), uctypes.UINT32)
788+
}
789+
790+
# Create the struct
791+
obj_handles = uctypes.struct(uctypes.addressof(data), obj_handles_desc, uctypes.LITTLE_ENDIAN)
792+
793+
# Fill in the data
794+
obj_handles.count = len(handles)
739795
for i, handle in enumerate(handles):
740-
struct.pack_into("<I", data, 4 + i*4, handle)
796+
obj_handles.handles[i] = handle
741797

742798
self._send_data(data)
743799
self._send_response(_MTP_RESPONSE_OK)
@@ -1119,11 +1175,14 @@ def _send_data(self, data, final=True):
11191175
container = bytearray(_CONTAINER_HEADER_SIZE)
11201176
total_len = _CONTAINER_HEADER_SIZE + len(data)
11211177

1122-
struct.pack_into("<IHHI", container, 0,
1123-
total_len, # Container length
1124-
_MTP_CONTAINER_TYPE_DATA, # Container type
1125-
self._current_operation, # Operation code
1126-
self._transaction_id) # Transaction ID
1178+
# Create a container header struct
1179+
header = uctypes.struct(uctypes.addressof(container), _MTP_CONTAINER_HEADER_DESC, uctypes.LITTLE_ENDIAN)
1180+
1181+
# Fill in the container header fields
1182+
header.length = total_len
1183+
header.type = _MTP_CONTAINER_TYPE_DATA
1184+
header.code = self._current_operation
1185+
header.transaction_id = self._transaction_id
11271186

11281187
self._log("Sending DATA container: length={}, operation=0x{:04x}, transaction_id={}{}",
11291188
total_len, self._current_operation, self._transaction_id,
@@ -1186,17 +1245,22 @@ def _send_response(self, response_code, params=None):
11861245
self._log("Sending RESPONSE: {} (0x{:04x}), transaction_id={}, params={}",
11871246
response_name, response_code, self._transaction_id, params if params else "None")
11881247

1189-
# Create and fill container header
1248+
# Create container buffer for header + params
11901249
container = bytearray(total_len)
1191-
struct.pack_into("<IHHI", container, 0,
1192-
total_len, # Container length
1193-
_MTP_CONTAINER_TYPE_RESPONSE, # Container type
1194-
response_code, # Response code
1195-
self._transaction_id) # Transaction ID
1250+
1251+
# Create a container header struct
1252+
header = uctypes.struct(uctypes.addressof(container), _MTP_CONTAINER_HEADER_DESC, uctypes.LITTLE_ENDIAN)
1253+
1254+
# Fill in the container header fields
1255+
header.length = total_len
1256+
header.type = _MTP_CONTAINER_TYPE_RESPONSE
1257+
header.code = response_code
1258+
header.transaction_id = self._transaction_id
11961259

11971260
# Add parameters if any
11981261
if params:
11991262
for i, param in enumerate(params):
1263+
# Pack parameters directly after header
12001264
struct.pack_into("<I", container, _CONTAINER_HEADER_SIZE + i * 4, param)
12011265

12021266
# Send the response

0 commit comments

Comments
 (0)