pymobiledevice3 4.27.0__py3-none-any.whl → 5.1.2__py3-none-any.whl
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- misc/plist_sniffer.py +15 -15
- misc/remotexpc_sniffer.py +29 -28
- pymobiledevice3/__main__.py +123 -98
- pymobiledevice3/_version.py +2 -2
- pymobiledevice3/bonjour.py +351 -117
- pymobiledevice3/ca.py +32 -24
- pymobiledevice3/cli/activation.py +7 -7
- pymobiledevice3/cli/afc.py +19 -19
- pymobiledevice3/cli/amfi.py +4 -4
- pymobiledevice3/cli/apps.py +51 -39
- pymobiledevice3/cli/backup.py +58 -32
- pymobiledevice3/cli/bonjour.py +27 -20
- pymobiledevice3/cli/cli_common.py +112 -81
- pymobiledevice3/cli/companion_proxy.py +4 -4
- pymobiledevice3/cli/completions.py +10 -10
- pymobiledevice3/cli/crash.py +37 -31
- pymobiledevice3/cli/developer.py +601 -519
- pymobiledevice3/cli/diagnostics.py +38 -33
- pymobiledevice3/cli/lockdown.py +82 -72
- pymobiledevice3/cli/mounter.py +84 -67
- pymobiledevice3/cli/notification.py +10 -10
- pymobiledevice3/cli/pcap.py +19 -14
- pymobiledevice3/cli/power_assertion.py +12 -10
- pymobiledevice3/cli/processes.py +10 -10
- pymobiledevice3/cli/profile.py +88 -77
- pymobiledevice3/cli/provision.py +17 -17
- pymobiledevice3/cli/remote.py +188 -111
- pymobiledevice3/cli/restore.py +43 -40
- pymobiledevice3/cli/springboard.py +30 -28
- pymobiledevice3/cli/syslog.py +85 -58
- pymobiledevice3/cli/usbmux.py +21 -20
- pymobiledevice3/cli/version.py +3 -2
- pymobiledevice3/cli/webinspector.py +156 -78
- pymobiledevice3/common.py +1 -1
- pymobiledevice3/exceptions.py +154 -60
- pymobiledevice3/irecv.py +49 -53
- pymobiledevice3/irecv_devices.py +1489 -492
- pymobiledevice3/lockdown.py +400 -251
- pymobiledevice3/lockdown_service_provider.py +5 -7
- pymobiledevice3/osu/os_utils.py +18 -9
- pymobiledevice3/osu/posix_util.py +28 -15
- pymobiledevice3/osu/win_util.py +14 -8
- pymobiledevice3/pair_records.py +19 -19
- pymobiledevice3/remote/common.py +4 -4
- pymobiledevice3/remote/core_device/app_service.py +94 -67
- pymobiledevice3/remote/core_device/core_device_service.py +17 -14
- pymobiledevice3/remote/core_device/device_info.py +5 -5
- pymobiledevice3/remote/core_device/diagnostics_service.py +10 -8
- pymobiledevice3/remote/core_device/file_service.py +47 -33
- pymobiledevice3/remote/remote_service_discovery.py +53 -35
- pymobiledevice3/remote/remotexpc.py +64 -42
- pymobiledevice3/remote/tunnel_service.py +383 -297
- pymobiledevice3/remote/utils.py +14 -13
- pymobiledevice3/remote/xpc_message.py +145 -125
- pymobiledevice3/resources/dsc_uuid_map.py +19 -19
- pymobiledevice3/resources/firmware_notifications.py +16 -16
- pymobiledevice3/restore/asr.py +27 -27
- pymobiledevice3/restore/base_restore.py +90 -47
- pymobiledevice3/restore/consts.py +87 -66
- pymobiledevice3/restore/device.py +11 -11
- pymobiledevice3/restore/fdr.py +46 -46
- pymobiledevice3/restore/ftab.py +19 -19
- pymobiledevice3/restore/img4.py +130 -133
- pymobiledevice3/restore/mbn.py +587 -0
- pymobiledevice3/restore/recovery.py +125 -135
- pymobiledevice3/restore/restore.py +535 -523
- pymobiledevice3/restore/restore_options.py +122 -115
- pymobiledevice3/restore/restored_client.py +25 -22
- pymobiledevice3/restore/tss.py +378 -270
- pymobiledevice3/service_connection.py +50 -46
- pymobiledevice3/services/accessibilityaudit.py +137 -127
- pymobiledevice3/services/afc.py +363 -293
- pymobiledevice3/services/amfi.py +21 -18
- pymobiledevice3/services/companion.py +23 -19
- pymobiledevice3/services/crash_reports.py +61 -47
- pymobiledevice3/services/debugserver_applist.py +3 -3
- pymobiledevice3/services/device_arbitration.py +8 -8
- pymobiledevice3/services/device_link.py +56 -48
- pymobiledevice3/services/diagnostics.py +971 -968
- pymobiledevice3/services/dtfetchsymbols.py +8 -8
- pymobiledevice3/services/dvt/dvt_secure_socket_proxy.py +4 -4
- pymobiledevice3/services/dvt/dvt_testmanaged_proxy.py +4 -4
- pymobiledevice3/services/dvt/instruments/activity_trace_tap.py +85 -74
- pymobiledevice3/services/dvt/instruments/application_listing.py +2 -3
- pymobiledevice3/services/dvt/instruments/condition_inducer.py +7 -6
- pymobiledevice3/services/dvt/instruments/core_profile_session_tap.py +466 -384
- pymobiledevice3/services/dvt/instruments/device_info.py +11 -11
- pymobiledevice3/services/dvt/instruments/energy_monitor.py +1 -1
- pymobiledevice3/services/dvt/instruments/graphics.py +1 -1
- pymobiledevice3/services/dvt/instruments/location_simulation.py +1 -1
- pymobiledevice3/services/dvt/instruments/location_simulation_base.py +10 -10
- pymobiledevice3/services/dvt/instruments/network_monitor.py +17 -17
- pymobiledevice3/services/dvt/instruments/notifications.py +1 -1
- pymobiledevice3/services/dvt/instruments/process_control.py +25 -10
- pymobiledevice3/services/dvt/instruments/screenshot.py +2 -2
- pymobiledevice3/services/dvt/instruments/sysmontap.py +15 -15
- pymobiledevice3/services/dvt/testmanaged/xcuitest.py +42 -52
- pymobiledevice3/services/file_relay.py +10 -10
- pymobiledevice3/services/heartbeat.py +8 -7
- pymobiledevice3/services/house_arrest.py +12 -15
- pymobiledevice3/services/installation_proxy.py +119 -100
- pymobiledevice3/services/lockdown_service.py +12 -5
- pymobiledevice3/services/misagent.py +22 -19
- pymobiledevice3/services/mobile_activation.py +84 -72
- pymobiledevice3/services/mobile_config.py +331 -301
- pymobiledevice3/services/mobile_image_mounter.py +137 -116
- pymobiledevice3/services/mobilebackup2.py +188 -150
- pymobiledevice3/services/notification_proxy.py +11 -11
- pymobiledevice3/services/os_trace.py +128 -74
- pymobiledevice3/services/pcapd.py +306 -306
- pymobiledevice3/services/power_assertion.py +10 -9
- pymobiledevice3/services/preboard.py +4 -4
- pymobiledevice3/services/remote_fetch_symbols.py +16 -14
- pymobiledevice3/services/remote_server.py +176 -146
- pymobiledevice3/services/restore_service.py +16 -16
- pymobiledevice3/services/screenshot.py +13 -10
- pymobiledevice3/services/simulate_location.py +7 -7
- pymobiledevice3/services/springboard.py +15 -15
- pymobiledevice3/services/syslog.py +5 -5
- pymobiledevice3/services/web_protocol/alert.py +3 -3
- pymobiledevice3/services/web_protocol/automation_session.py +183 -179
- pymobiledevice3/services/web_protocol/cdp_screencast.py +44 -36
- pymobiledevice3/services/web_protocol/cdp_server.py +19 -19
- pymobiledevice3/services/web_protocol/cdp_target.py +411 -373
- pymobiledevice3/services/web_protocol/driver.py +47 -45
- pymobiledevice3/services/web_protocol/element.py +74 -63
- pymobiledevice3/services/web_protocol/inspector_session.py +106 -102
- pymobiledevice3/services/web_protocol/selenium_api.py +3 -3
- pymobiledevice3/services/web_protocol/session_protocol.py +15 -10
- pymobiledevice3/services/web_protocol/switch_to.py +11 -12
- pymobiledevice3/services/webinspector.py +142 -116
- pymobiledevice3/tcp_forwarder.py +64 -50
- pymobiledevice3/tunneld/api.py +20 -15
- pymobiledevice3/tunneld/server.py +315 -193
- pymobiledevice3/usbmux.py +197 -148
- pymobiledevice3/utils.py +14 -11
- {pymobiledevice3-4.27.0.dist-info → pymobiledevice3-5.1.2.dist-info}/METADATA +2 -6
- pymobiledevice3-5.1.2.dist-info/RECORD +173 -0
- pymobiledevice3-4.27.0.dist-info/RECORD +0 -172
- {pymobiledevice3-4.27.0.dist-info → pymobiledevice3-5.1.2.dist-info}/WHEEL +0 -0
- {pymobiledevice3-4.27.0.dist-info → pymobiledevice3-5.1.2.dist-info}/entry_points.txt +0 -0
- {pymobiledevice3-4.27.0.dist-info → pymobiledevice3-5.1.2.dist-info}/licenses/LICENSE +0 -0
- {pymobiledevice3-4.27.0.dist-info → pymobiledevice3-5.1.2.dist-info}/top_level.txt +0 -0
pymobiledevice3/usbmux.py
CHANGED
|
@@ -5,76 +5,115 @@ import time
|
|
|
5
5
|
from dataclasses import dataclass
|
|
6
6
|
from typing import Optional
|
|
7
7
|
|
|
8
|
-
from construct import
|
|
9
|
-
|
|
8
|
+
from construct import (
|
|
9
|
+
Const,
|
|
10
|
+
CString,
|
|
11
|
+
Enum,
|
|
12
|
+
FixedSized,
|
|
13
|
+
GreedyBytes,
|
|
14
|
+
Int16ul,
|
|
15
|
+
Int32ul,
|
|
16
|
+
Padding,
|
|
17
|
+
Prefixed,
|
|
18
|
+
StreamError,
|
|
19
|
+
Struct,
|
|
20
|
+
Switch,
|
|
21
|
+
this,
|
|
22
|
+
)
|
|
10
23
|
|
|
11
|
-
from pymobiledevice3.exceptions import
|
|
12
|
-
|
|
24
|
+
from pymobiledevice3.exceptions import (
|
|
25
|
+
BadCommandError,
|
|
26
|
+
BadDevError,
|
|
27
|
+
ConnectionFailedError,
|
|
28
|
+
ConnectionFailedToUsbmuxdError,
|
|
29
|
+
MuxException,
|
|
30
|
+
MuxVersionError,
|
|
31
|
+
NotPairedError,
|
|
32
|
+
)
|
|
13
33
|
from pymobiledevice3.osu.os_utils import get_os_utils
|
|
14
34
|
|
|
15
|
-
usbmuxd_version = Enum(
|
|
16
|
-
|
|
17
|
-
|
|
18
|
-
|
|
19
|
-
|
|
20
|
-
|
|
21
|
-
|
|
22
|
-
|
|
23
|
-
|
|
24
|
-
|
|
25
|
-
|
|
26
|
-
|
|
27
|
-
|
|
28
|
-
|
|
29
|
-
|
|
30
|
-
|
|
31
|
-
|
|
32
|
-
|
|
33
|
-
|
|
34
|
-
|
|
35
|
-
|
|
36
|
-
|
|
37
|
-
|
|
35
|
+
usbmuxd_version = Enum(
|
|
36
|
+
Int32ul,
|
|
37
|
+
BINARY=0,
|
|
38
|
+
PLIST=1,
|
|
39
|
+
)
|
|
40
|
+
|
|
41
|
+
usbmuxd_result = Enum(
|
|
42
|
+
Int32ul,
|
|
43
|
+
OK=0,
|
|
44
|
+
BADCOMMAND=1,
|
|
45
|
+
BADDEV=2,
|
|
46
|
+
CONNREFUSED=3,
|
|
47
|
+
NOSUCHSERVICE=4,
|
|
48
|
+
BADVERSION=6,
|
|
49
|
+
)
|
|
50
|
+
|
|
51
|
+
usbmuxd_msgtype = Enum(
|
|
52
|
+
Int32ul,
|
|
53
|
+
RESULT=1,
|
|
54
|
+
CONNECT=2,
|
|
55
|
+
LISTEN=3,
|
|
56
|
+
ADD=4,
|
|
57
|
+
REMOVE=5,
|
|
58
|
+
PAIRED=6,
|
|
59
|
+
PLIST=8,
|
|
60
|
+
)
|
|
38
61
|
|
|
39
62
|
usbmuxd_header = Struct(
|
|
40
|
-
|
|
41
|
-
|
|
42
|
-
|
|
63
|
+
"version" / usbmuxd_version, # protocol version
|
|
64
|
+
"message" / usbmuxd_msgtype, # message type
|
|
65
|
+
"tag" / Int32ul, # responses to this query will echo back this tag
|
|
43
66
|
)
|
|
44
67
|
|
|
45
|
-
usbmuxd_request = Prefixed(
|
|
46
|
-
|
|
47
|
-
|
|
48
|
-
|
|
49
|
-
|
|
50
|
-
|
|
51
|
-
|
|
68
|
+
usbmuxd_request = Prefixed(
|
|
69
|
+
Int32ul,
|
|
70
|
+
Struct(
|
|
71
|
+
"header" / usbmuxd_header,
|
|
72
|
+
"data"
|
|
73
|
+
/ Switch(
|
|
74
|
+
this.header.message,
|
|
75
|
+
{
|
|
76
|
+
usbmuxd_msgtype.CONNECT: Struct(
|
|
77
|
+
"device_id" / Int32ul,
|
|
78
|
+
"port" / Int16ul, # TCP port number
|
|
79
|
+
"reserved" / Const(0, Int16ul),
|
|
80
|
+
),
|
|
81
|
+
usbmuxd_msgtype.PLIST: GreedyBytes,
|
|
82
|
+
},
|
|
52
83
|
),
|
|
53
|
-
|
|
54
|
-
|
|
55
|
-
)
|
|
84
|
+
),
|
|
85
|
+
includelength=True,
|
|
86
|
+
)
|
|
56
87
|
|
|
57
88
|
usbmuxd_device_record = Struct(
|
|
58
|
-
|
|
59
|
-
|
|
60
|
-
|
|
89
|
+
"device_id" / Int32ul,
|
|
90
|
+
"product_id" / Int16ul,
|
|
91
|
+
"serial_number" / FixedSized(256, CString("ascii")),
|
|
61
92
|
Padding(2),
|
|
62
|
-
|
|
93
|
+
"location" / Int32ul,
|
|
63
94
|
)
|
|
64
95
|
|
|
65
|
-
usbmuxd_response = Prefixed(
|
|
66
|
-
|
|
67
|
-
|
|
68
|
-
|
|
69
|
-
|
|
96
|
+
usbmuxd_response = Prefixed(
|
|
97
|
+
Int32ul,
|
|
98
|
+
Struct(
|
|
99
|
+
"header" / usbmuxd_header,
|
|
100
|
+
"data"
|
|
101
|
+
/ Switch(
|
|
102
|
+
this.header.message,
|
|
103
|
+
{
|
|
104
|
+
usbmuxd_msgtype.RESULT: Struct(
|
|
105
|
+
"result" / usbmuxd_result,
|
|
106
|
+
),
|
|
107
|
+
usbmuxd_msgtype.ADD: usbmuxd_device_record,
|
|
108
|
+
usbmuxd_msgtype.REMOVE: Struct(
|
|
109
|
+
"device_id" / Int32ul,
|
|
110
|
+
),
|
|
111
|
+
usbmuxd_msgtype.PLIST: GreedyBytes,
|
|
112
|
+
},
|
|
70
113
|
),
|
|
71
|
-
|
|
72
|
-
|
|
73
|
-
|
|
74
|
-
),
|
|
75
|
-
usbmuxd_msgtype.PLIST: GreedyBytes,
|
|
76
|
-
}),
|
|
77
|
-
), includelength=True)
|
|
114
|
+
),
|
|
115
|
+
includelength=True,
|
|
116
|
+
)
|
|
78
117
|
|
|
79
118
|
|
|
80
119
|
@dataclass
|
|
@@ -87,24 +126,24 @@ class MuxDevice:
|
|
|
87
126
|
mux = create_mux(usbmux_address=usbmux_address)
|
|
88
127
|
try:
|
|
89
128
|
return mux.connect(self, port)
|
|
90
|
-
except:
|
|
129
|
+
except:
|
|
91
130
|
mux.close()
|
|
92
131
|
raise
|
|
93
132
|
|
|
94
133
|
@property
|
|
95
134
|
def is_usb(self) -> bool:
|
|
96
|
-
return self.connection_type ==
|
|
135
|
+
return self.connection_type == "USB"
|
|
97
136
|
|
|
98
137
|
@property
|
|
99
138
|
def is_network(self) -> bool:
|
|
100
|
-
return self.connection_type ==
|
|
139
|
+
return self.connection_type == "Network"
|
|
101
140
|
|
|
102
141
|
def matches_udid(self, udid: str) -> bool:
|
|
103
|
-
return self.serial.replace(
|
|
142
|
+
return self.serial.replace("-", "") == udid.replace("-", "")
|
|
104
143
|
|
|
105
144
|
|
|
106
145
|
class SafeStreamSocket:
|
|
107
|
-
"""
|
|
146
|
+
"""wrapper to native python socket object to be used with construct as a stream"""
|
|
108
147
|
|
|
109
148
|
def __init__(self, address, family):
|
|
110
149
|
self._offset = 0
|
|
@@ -117,12 +156,12 @@ class SafeStreamSocket:
|
|
|
117
156
|
return len(msg)
|
|
118
157
|
|
|
119
158
|
def recv(self, size: int) -> bytes:
|
|
120
|
-
msg = b
|
|
159
|
+
msg = b""
|
|
121
160
|
while len(msg) < size:
|
|
122
161
|
chunk = self.sock.recv(size - len(msg))
|
|
123
162
|
self._offset += len(chunk)
|
|
124
163
|
if not chunk:
|
|
125
|
-
raise MuxException(
|
|
164
|
+
raise MuxException("socket connection broken")
|
|
126
165
|
msg += chunk
|
|
127
166
|
return msg
|
|
128
167
|
|
|
@@ -144,18 +183,18 @@ class SafeStreamSocket:
|
|
|
144
183
|
|
|
145
184
|
class MuxConnection:
|
|
146
185
|
# used on Windows
|
|
147
|
-
ITUNES_HOST = (
|
|
186
|
+
ITUNES_HOST = ("127.0.0.1", 27015)
|
|
148
187
|
|
|
149
188
|
# used for macOS and Linux
|
|
150
|
-
USBMUXD_PIPE =
|
|
189
|
+
USBMUXD_PIPE = "/var/run/usbmuxd"
|
|
151
190
|
|
|
152
191
|
@staticmethod
|
|
153
192
|
def create_usbmux_socket(usbmux_address: Optional[str] = None) -> SafeStreamSocket:
|
|
154
193
|
try:
|
|
155
194
|
if usbmux_address is not None:
|
|
156
|
-
if
|
|
195
|
+
if ":" in usbmux_address:
|
|
157
196
|
# assume tcp address
|
|
158
|
-
hostname, port = usbmux_address.split(
|
|
197
|
+
hostname, port = usbmux_address.split(":")
|
|
159
198
|
port = int(port)
|
|
160
199
|
address = (hostname, port)
|
|
161
200
|
family = socket.AF_INET
|
|
@@ -166,23 +205,25 @@ class MuxConnection:
|
|
|
166
205
|
else:
|
|
167
206
|
address, family = get_os_utils().usbmux_address
|
|
168
207
|
return SafeStreamSocket(address, family)
|
|
169
|
-
except ConnectionRefusedError:
|
|
170
|
-
raise ConnectionFailedToUsbmuxdError()
|
|
208
|
+
except ConnectionRefusedError as e:
|
|
209
|
+
raise ConnectionFailedToUsbmuxdError() from e
|
|
171
210
|
|
|
172
211
|
@staticmethod
|
|
173
212
|
def create(usbmux_address: Optional[str] = None):
|
|
174
213
|
# first attempt to connect with possibly the wrong version header (plist protocol)
|
|
175
214
|
sock = MuxConnection.create_usbmux_socket(usbmux_address=usbmux_address)
|
|
176
215
|
|
|
177
|
-
|
|
178
|
-
|
|
179
|
-
|
|
180
|
-
|
|
181
|
-
|
|
182
|
-
|
|
183
|
-
|
|
184
|
-
|
|
185
|
-
|
|
216
|
+
try:
|
|
217
|
+
message = usbmuxd_request.build({
|
|
218
|
+
"header": {"version": usbmuxd_version.PLIST, "message": usbmuxd_msgtype.PLIST, "tag": 1},
|
|
219
|
+
"data": plistlib.dumps({"MessageType": "ReadBUID"}),
|
|
220
|
+
})
|
|
221
|
+
sock.send(message)
|
|
222
|
+
response = usbmuxd_response.parse_stream(sock)
|
|
223
|
+
|
|
224
|
+
finally:
|
|
225
|
+
# If we sent a bad request, we should re-create the socket in the correct version this time
|
|
226
|
+
sock.close()
|
|
186
227
|
sock = MuxConnection.create_usbmux_socket(usbmux_address=usbmux_address)
|
|
187
228
|
|
|
188
229
|
if response.header.version == usbmuxd_version.BINARY:
|
|
@@ -190,7 +231,7 @@ class MuxConnection:
|
|
|
190
231
|
elif response.header.version == usbmuxd_version.PLIST:
|
|
191
232
|
return PlistMuxConnection(sock)
|
|
192
233
|
|
|
193
|
-
raise MuxVersionError(f
|
|
234
|
+
raise MuxVersionError(f"usbmuxd returned unsupported version: {response.version}")
|
|
194
235
|
|
|
195
236
|
def __init__(self, sock: SafeStreamSocket):
|
|
196
237
|
self._sock = sock
|
|
@@ -207,30 +248,30 @@ class MuxConnection:
|
|
|
207
248
|
|
|
208
249
|
@abc.abstractmethod
|
|
209
250
|
def _connect(self, device_id: int, port: int):
|
|
210
|
-
"""
|
|
251
|
+
"""initiate a "Connect" request to target port"""
|
|
211
252
|
pass
|
|
212
253
|
|
|
213
254
|
@abc.abstractmethod
|
|
214
|
-
def get_device_list(self, timeout: float = None):
|
|
255
|
+
def get_device_list(self, timeout: Optional[float] = None):
|
|
215
256
|
"""
|
|
216
257
|
request an update to current device list
|
|
217
258
|
"""
|
|
218
259
|
pass
|
|
219
260
|
|
|
220
261
|
def connect(self, device: MuxDevice, port: int) -> socket.socket:
|
|
221
|
-
"""
|
|
262
|
+
"""connect to a relay port on target machine and get a raw python socket object for the connection"""
|
|
222
263
|
self._connect(device.devid, socket.htons(port))
|
|
223
264
|
self._connected = True
|
|
224
265
|
return self._sock.sock
|
|
225
266
|
|
|
226
267
|
def close(self):
|
|
227
|
-
"""
|
|
268
|
+
"""close current socket"""
|
|
228
269
|
self._sock.close()
|
|
229
270
|
|
|
230
271
|
def _assert_not_connected(self):
|
|
231
|
-
"""
|
|
272
|
+
"""verify active state is in state for control messages"""
|
|
232
273
|
if self._connected:
|
|
233
|
-
raise MuxException(
|
|
274
|
+
raise MuxException("Mux is connected, cannot issue control packets")
|
|
234
275
|
|
|
235
276
|
def _raise_mux_exception(self, result: int, message: Optional[str] = None) -> None:
|
|
236
277
|
exceptions = {
|
|
@@ -251,14 +292,14 @@ class MuxConnection:
|
|
|
251
292
|
|
|
252
293
|
|
|
253
294
|
class BinaryMuxConnection(MuxConnection):
|
|
254
|
-
"""
|
|
295
|
+
"""old binary protocol"""
|
|
255
296
|
|
|
256
297
|
def __init__(self, sock: SafeStreamSocket):
|
|
257
298
|
super().__init__(sock)
|
|
258
299
|
self._version = usbmuxd_version.BINARY
|
|
259
300
|
|
|
260
|
-
def get_device_list(self, timeout: float = None):
|
|
261
|
-
"""
|
|
301
|
+
def get_device_list(self, timeout: Optional[float] = None):
|
|
302
|
+
"""use timeout to wait for the device list to be fully populated"""
|
|
262
303
|
self._assert_not_connected()
|
|
263
304
|
end = time.time() + timeout
|
|
264
305
|
self.listen()
|
|
@@ -268,55 +309,54 @@ class BinaryMuxConnection(MuxConnection):
|
|
|
268
309
|
self._receive_device_state_update()
|
|
269
310
|
except (BlockingIOError, StreamError):
|
|
270
311
|
continue
|
|
271
|
-
except
|
|
312
|
+
except OSError as e:
|
|
272
313
|
try:
|
|
273
314
|
self._sock.setblocking(True)
|
|
274
315
|
self.close()
|
|
275
316
|
except OSError:
|
|
276
317
|
pass
|
|
277
|
-
raise MuxException(
|
|
318
|
+
raise MuxException("Exception in listener socket") from e
|
|
278
319
|
|
|
279
320
|
def listen(self):
|
|
280
|
-
"""
|
|
321
|
+
"""start listening for events of attached and detached devices"""
|
|
281
322
|
self._send_receive(usbmuxd_msgtype.LISTEN)
|
|
282
323
|
|
|
283
324
|
def _connect(self, device_id: int, port: int):
|
|
284
|
-
self._send({
|
|
285
|
-
|
|
286
|
-
|
|
287
|
-
|
|
288
|
-
})
|
|
325
|
+
self._send({
|
|
326
|
+
"header": {"version": self._version, "message": usbmuxd_msgtype.CONNECT, "tag": self._tag},
|
|
327
|
+
"data": {"device_id": device_id, "port": port},
|
|
328
|
+
})
|
|
289
329
|
response = self._receive()
|
|
290
330
|
if response.header.message != usbmuxd_msgtype.RESULT:
|
|
291
|
-
raise MuxException(f
|
|
331
|
+
raise MuxException(f"unexpected message type received: {response}")
|
|
292
332
|
|
|
293
333
|
if response.data.result != usbmuxd_result.OK:
|
|
294
|
-
raise self._raise_mux_exception(
|
|
295
|
-
|
|
296
|
-
|
|
334
|
+
raise self._raise_mux_exception(
|
|
335
|
+
int(response.data.result),
|
|
336
|
+
f"failed to connect to device: {device_id} at port: {port}. reason: {response.data.result}",
|
|
337
|
+
)
|
|
297
338
|
|
|
298
339
|
def _send(self, data: dict) -> None:
|
|
299
340
|
self._assert_not_connected()
|
|
300
341
|
self._sock.send(usbmuxd_request.build(data))
|
|
301
342
|
self._tag += 1
|
|
302
343
|
|
|
303
|
-
def _receive(self, expected_tag: int = None):
|
|
344
|
+
def _receive(self, expected_tag: Optional[int] = None):
|
|
304
345
|
self._assert_not_connected()
|
|
305
346
|
response = usbmuxd_response.parse_stream(self._sock)
|
|
306
347
|
if expected_tag and response.header.tag != expected_tag:
|
|
307
|
-
raise MuxException(f
|
|
348
|
+
raise MuxException(f"Reply tag mismatch: expected {expected_tag}, got {response.header.tag}")
|
|
308
349
|
return response
|
|
309
350
|
|
|
310
351
|
def _send_receive(self, message_type: int):
|
|
311
|
-
self._send({
|
|
312
|
-
'data': b''})
|
|
352
|
+
self._send({"header": {"version": self._version, "message": message_type, "tag": self._tag}, "data": b""})
|
|
313
353
|
response = self._receive(self._tag - 1)
|
|
314
354
|
if response.header.message != usbmuxd_msgtype.RESULT:
|
|
315
|
-
raise MuxException(f
|
|
355
|
+
raise MuxException(f"unexpected message type received: {response}")
|
|
316
356
|
|
|
317
357
|
result = response.data.result
|
|
318
358
|
if result != usbmuxd_result.OK:
|
|
319
|
-
raise self._raise_mux_exception(int(result), f
|
|
359
|
+
raise self._raise_mux_exception(int(result), f"{message_type} failed: error {result}")
|
|
320
360
|
|
|
321
361
|
def _add_device(self, device: MuxDevice):
|
|
322
362
|
self.devices.append(device)
|
|
@@ -328,11 +368,11 @@ class BinaryMuxConnection(MuxConnection):
|
|
|
328
368
|
response = self._receive()
|
|
329
369
|
if response.header.message == usbmuxd_msgtype.ADD:
|
|
330
370
|
# old protocol only supported USB devices
|
|
331
|
-
self._add_device(MuxDevice(response.data.device_id, response.data.serial_number,
|
|
371
|
+
self._add_device(MuxDevice(response.data.device_id, response.data.serial_number, "USB"))
|
|
332
372
|
elif response.header.message == usbmuxd_msgtype.REMOVE:
|
|
333
373
|
self._remove_device(response.data.device_id)
|
|
334
374
|
else:
|
|
335
|
-
raise MuxException(f
|
|
375
|
+
raise MuxException(f"Invalid packet type received: {response}")
|
|
336
376
|
|
|
337
377
|
|
|
338
378
|
class PlistMuxConnection(BinaryMuxConnection):
|
|
@@ -341,71 +381,77 @@ class PlistMuxConnection(BinaryMuxConnection):
|
|
|
341
381
|
self._version = usbmuxd_version.PLIST
|
|
342
382
|
|
|
343
383
|
def listen(self) -> None:
|
|
344
|
-
self._send_receive({
|
|
384
|
+
self._send_receive({"MessageType": "Listen"})
|
|
345
385
|
|
|
346
386
|
def get_pair_record(self, serial: str) -> dict:
|
|
347
387
|
# serials are saved inside usbmuxd without '-'
|
|
348
|
-
self._send({
|
|
388
|
+
self._send({"MessageType": "ReadPairRecord", "PairRecordID": serial})
|
|
349
389
|
response = self._receive(self._tag - 1)
|
|
350
|
-
pair_record = response.get(
|
|
390
|
+
pair_record = response.get("PairRecordData")
|
|
351
391
|
if pair_record is None:
|
|
352
|
-
raise NotPairedError(
|
|
392
|
+
raise NotPairedError("device should be paired first")
|
|
353
393
|
return plistlib.loads(pair_record)
|
|
354
394
|
|
|
355
|
-
def get_device_list(self, timeout: float = None) -> None:
|
|
356
|
-
"""
|
|
395
|
+
def get_device_list(self, timeout: Optional[float] = None) -> None:
|
|
396
|
+
"""get device list synchronously without waiting the timeout"""
|
|
357
397
|
self.devices = []
|
|
358
|
-
self._send({
|
|
398
|
+
self._send({"MessageType": "ListDevices"})
|
|
359
399
|
response = self._receive(self._tag - 1)
|
|
360
|
-
device_list = response.get(
|
|
400
|
+
device_list = response.get("DeviceList")
|
|
361
401
|
if device_list is None:
|
|
362
|
-
raise MuxException(f
|
|
402
|
+
raise MuxException(f"Got an invalid response from usbmux: {response}")
|
|
363
403
|
for response in device_list:
|
|
364
|
-
if response[
|
|
365
|
-
super()._add_device(
|
|
366
|
-
|
|
367
|
-
|
|
368
|
-
|
|
404
|
+
if response["MessageType"] == "Attached":
|
|
405
|
+
super()._add_device(
|
|
406
|
+
MuxDevice(
|
|
407
|
+
response["DeviceID"],
|
|
408
|
+
response["Properties"]["SerialNumber"],
|
|
409
|
+
response["Properties"]["ConnectionType"],
|
|
410
|
+
)
|
|
411
|
+
)
|
|
412
|
+
elif response["MessageType"] == "Detached":
|
|
413
|
+
super()._remove_device(response["DeviceID"])
|
|
369
414
|
else:
|
|
370
|
-
raise MuxException(f
|
|
415
|
+
raise MuxException(f"Invalid packet type received: {response}")
|
|
371
416
|
|
|
372
417
|
def get_buid(self) -> str:
|
|
373
|
-
"""
|
|
374
|
-
self._send({
|
|
375
|
-
return self._receive(self._tag - 1)[
|
|
418
|
+
"""get SystemBUID"""
|
|
419
|
+
self._send({"MessageType": "ReadBUID"})
|
|
420
|
+
return self._receive(self._tag - 1)["BUID"]
|
|
376
421
|
|
|
377
422
|
def save_pair_record(self, serial: str, device_id: int, record_data: bytes):
|
|
378
423
|
# serials are saved inside usbmuxd without '-'
|
|
379
|
-
self._send_receive({
|
|
380
|
-
|
|
381
|
-
|
|
382
|
-
|
|
424
|
+
self._send_receive({
|
|
425
|
+
"MessageType": "SavePairRecord",
|
|
426
|
+
"PairRecordID": serial,
|
|
427
|
+
"PairRecordData": record_data,
|
|
428
|
+
"DeviceID": device_id,
|
|
429
|
+
})
|
|
383
430
|
|
|
384
431
|
def _connect(self, device_id: int, port: int):
|
|
385
|
-
self._send_receive({
|
|
432
|
+
self._send_receive({"MessageType": "Connect", "DeviceID": device_id, "PortNumber": port})
|
|
386
433
|
|
|
387
434
|
def _send(self, data: dict):
|
|
388
|
-
request = {
|
|
435
|
+
request = {"ClientVersionString": "qt4i-usbmuxd", "ProgName": "pymobiledevice3", "kLibUSBMuxVersion": 3}
|
|
389
436
|
request.update(data)
|
|
390
|
-
super()._send({
|
|
391
|
-
|
|
392
|
-
|
|
393
|
-
|
|
394
|
-
})
|
|
437
|
+
super()._send({
|
|
438
|
+
"header": {"version": self._version, "message": usbmuxd_msgtype.PLIST, "tag": self._tag},
|
|
439
|
+
"data": plistlib.dumps(request),
|
|
440
|
+
})
|
|
395
441
|
|
|
396
|
-
def _receive(self, expected_tag: int = None) -> dict:
|
|
442
|
+
def _receive(self, expected_tag: Optional[int] = None) -> dict:
|
|
397
443
|
response = super()._receive(expected_tag=expected_tag)
|
|
398
444
|
if response.header.message != usbmuxd_msgtype.PLIST:
|
|
399
|
-
raise MuxException(f
|
|
445
|
+
raise MuxException(f"Received non-plist type {response}")
|
|
400
446
|
return plistlib.loads(response.data)
|
|
401
447
|
|
|
402
448
|
def _send_receive(self, data: dict):
|
|
403
449
|
self._send(data)
|
|
404
450
|
response = self._receive(self._tag - 1)
|
|
405
|
-
if response[
|
|
406
|
-
raise MuxException(f
|
|
407
|
-
if response[
|
|
408
|
-
raise self._raise_mux_exception(response[
|
|
451
|
+
if response["MessageType"] != "Result":
|
|
452
|
+
raise MuxException(f"got an invalid message: {response}")
|
|
453
|
+
if response["Number"] != 0:
|
|
454
|
+
raise self._raise_mux_exception(response["Number"], f"got an error message: {response}")
|
|
409
455
|
|
|
410
456
|
|
|
411
457
|
def create_mux(usbmux_address: Optional[str] = None) -> MuxConnection:
|
|
@@ -414,14 +460,17 @@ def create_mux(usbmux_address: Optional[str] = None) -> MuxConnection:
|
|
|
414
460
|
|
|
415
461
|
def list_devices(usbmux_address: Optional[str] = None) -> list[MuxDevice]:
|
|
416
462
|
mux = create_mux(usbmux_address=usbmux_address)
|
|
417
|
-
|
|
418
|
-
|
|
419
|
-
|
|
463
|
+
try:
|
|
464
|
+
mux.get_device_list(0.1)
|
|
465
|
+
devices = mux.devices
|
|
466
|
+
finally:
|
|
467
|
+
mux.close()
|
|
420
468
|
return devices
|
|
421
469
|
|
|
422
470
|
|
|
423
|
-
def select_device(
|
|
424
|
-
|
|
471
|
+
def select_device(
|
|
472
|
+
udid: Optional[str] = None, connection_type: Optional[str] = None, usbmux_address: Optional[str] = None
|
|
473
|
+
) -> Optional[MuxDevice]:
|
|
425
474
|
"""
|
|
426
475
|
select a UsbMux device according to given arguments.
|
|
427
476
|
if more than one device could be selected, always prefer the usb one.
|
pymobiledevice3/utils.py
CHANGED
|
@@ -15,16 +15,16 @@ def plist_access_path(d, path: tuple, type_=None, required=False):
|
|
|
15
15
|
if d is None:
|
|
16
16
|
break
|
|
17
17
|
|
|
18
|
-
if type_
|
|
19
|
-
if d.lower() not in (
|
|
18
|
+
if type_ is bool and isinstance(d, str):
|
|
19
|
+
if d.lower() not in ("true", "false"):
|
|
20
20
|
raise ValueError()
|
|
21
|
-
d =
|
|
21
|
+
d = d.lower() == "true"
|
|
22
22
|
elif type_ is not None and not isinstance(d, type_):
|
|
23
23
|
# wrong type
|
|
24
24
|
d = None
|
|
25
25
|
|
|
26
26
|
if d is None and required:
|
|
27
|
-
raise KeyError(f
|
|
27
|
+
raise KeyError(f"path: {path} doesn't exist in given plist object")
|
|
28
28
|
|
|
29
29
|
return d
|
|
30
30
|
|
|
@@ -35,7 +35,7 @@ def bytes_to_uint(b: bytes):
|
|
|
35
35
|
|
|
36
36
|
def try_decode(s: bytes):
|
|
37
37
|
try:
|
|
38
|
-
return s.decode(
|
|
38
|
+
return s.decode("utf8")
|
|
39
39
|
except UnicodeDecodeError:
|
|
40
40
|
return s
|
|
41
41
|
|
|
@@ -45,7 +45,7 @@ def asyncio_print_traceback(f: Callable):
|
|
|
45
45
|
async def wrapper(*args, **kwargs):
|
|
46
46
|
try:
|
|
47
47
|
return await f(*args, **kwargs)
|
|
48
|
-
except Exception as e:
|
|
48
|
+
except (Exception, RuntimeError) as e:
|
|
49
49
|
if not isinstance(e, asyncio.CancelledError):
|
|
50
50
|
traceback.print_exc()
|
|
51
51
|
raise
|
|
@@ -57,7 +57,7 @@ def get_asyncio_loop() -> asyncio.AbstractEventLoop:
|
|
|
57
57
|
try:
|
|
58
58
|
loop = asyncio.get_running_loop()
|
|
59
59
|
if loop.is_closed():
|
|
60
|
-
raise RuntimeError(
|
|
60
|
+
raise RuntimeError("The existing loop is closed.")
|
|
61
61
|
except RuntimeError:
|
|
62
62
|
# This happens when there is no current event loop
|
|
63
63
|
loop = asyncio.new_event_loop()
|
|
@@ -67,14 +67,17 @@ def get_asyncio_loop() -> asyncio.AbstractEventLoop:
|
|
|
67
67
|
|
|
68
68
|
def file_download(url: str, outfile: Path, chunk_size=1024) -> None:
|
|
69
69
|
resp = requests.get(url, stream=True)
|
|
70
|
-
total = int(resp.headers.get(
|
|
71
|
-
with
|
|
70
|
+
total = int(resp.headers.get("content-length", 0))
|
|
71
|
+
with (
|
|
72
|
+
outfile.open("wb") as file,
|
|
73
|
+
tqdm(
|
|
72
74
|
desc=outfile.name,
|
|
73
75
|
total=total,
|
|
74
|
-
unit=
|
|
76
|
+
unit="iB",
|
|
75
77
|
unit_scale=True,
|
|
76
78
|
unit_divisor=1024,
|
|
77
|
-
|
|
79
|
+
) as bar,
|
|
80
|
+
):
|
|
78
81
|
for data in resp.iter_content(chunk_size=chunk_size):
|
|
79
82
|
size = file.write(data)
|
|
80
83
|
bar.update(size)
|