pymobiledevice3 4.14.6__py3-none-any.whl → 7.0.6__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
- misc/understanding_idevice_protocol_layers.md +15 -10
- pymobiledevice3/__main__.py +317 -127
- pymobiledevice3/_version.py +22 -4
- pymobiledevice3/bonjour.py +358 -113
- pymobiledevice3/ca.py +253 -16
- pymobiledevice3/cli/activation.py +31 -23
- pymobiledevice3/cli/afc.py +49 -40
- pymobiledevice3/cli/amfi.py +16 -21
- pymobiledevice3/cli/apps.py +87 -42
- pymobiledevice3/cli/backup.py +160 -90
- pymobiledevice3/cli/bonjour.py +44 -40
- pymobiledevice3/cli/cli_common.py +204 -198
- pymobiledevice3/cli/companion_proxy.py +14 -14
- pymobiledevice3/cli/crash.py +105 -56
- pymobiledevice3/cli/developer/__init__.py +62 -0
- pymobiledevice3/cli/developer/accessibility/__init__.py +65 -0
- pymobiledevice3/cli/developer/accessibility/settings.py +43 -0
- pymobiledevice3/cli/developer/arbitration.py +50 -0
- pymobiledevice3/cli/developer/condition.py +33 -0
- pymobiledevice3/cli/developer/core_device.py +294 -0
- pymobiledevice3/cli/developer/debugserver.py +244 -0
- pymobiledevice3/cli/developer/dvt/__init__.py +438 -0
- pymobiledevice3/cli/developer/dvt/core_profile_session.py +295 -0
- pymobiledevice3/cli/developer/dvt/simulate_location.py +56 -0
- pymobiledevice3/cli/developer/dvt/sysmon/__init__.py +69 -0
- pymobiledevice3/cli/developer/dvt/sysmon/process.py +188 -0
- pymobiledevice3/cli/developer/fetch_symbols.py +108 -0
- pymobiledevice3/cli/developer/simulate_location.py +51 -0
- pymobiledevice3/cli/diagnostics/__init__.py +75 -0
- pymobiledevice3/cli/diagnostics/battery.py +47 -0
- pymobiledevice3/cli/idam.py +42 -0
- pymobiledevice3/cli/lockdown.py +108 -103
- pymobiledevice3/cli/mounter.py +158 -99
- pymobiledevice3/cli/notification.py +38 -26
- pymobiledevice3/cli/pcap.py +45 -24
- pymobiledevice3/cli/power_assertion.py +18 -17
- pymobiledevice3/cli/processes.py +17 -23
- pymobiledevice3/cli/profile.py +165 -109
- pymobiledevice3/cli/provision.py +35 -34
- pymobiledevice3/cli/remote.py +217 -129
- pymobiledevice3/cli/restore.py +159 -143
- pymobiledevice3/cli/springboard.py +63 -53
- pymobiledevice3/cli/syslog.py +193 -86
- pymobiledevice3/cli/usbmux.py +73 -33
- pymobiledevice3/cli/version.py +5 -7
- pymobiledevice3/cli/webinspector.py +376 -214
- pymobiledevice3/common.py +3 -1
- pymobiledevice3/exceptions.py +182 -58
- pymobiledevice3/irecv.py +52 -53
- pymobiledevice3/irecv_devices.py +1489 -464
- pymobiledevice3/lockdown.py +473 -275
- pymobiledevice3/lockdown_service_provider.py +15 -8
- pymobiledevice3/osu/os_utils.py +27 -9
- pymobiledevice3/osu/posix_util.py +34 -15
- pymobiledevice3/osu/win_util.py +14 -8
- pymobiledevice3/pair_records.py +102 -21
- pymobiledevice3/remote/common.py +8 -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 +19 -4
- pymobiledevice3/remote/core_device/file_service.py +53 -23
- pymobiledevice3/remote/remote_service_discovery.py +79 -45
- pymobiledevice3/remote/remotexpc.py +73 -44
- pymobiledevice3/remote/tunnel_service.py +442 -317
- 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 +20 -16
- pymobiledevice3/resources/notifications.txt +144 -0
- pymobiledevice3/restore/asr.py +27 -27
- pymobiledevice3/restore/base_restore.py +110 -21
- pymobiledevice3/restore/consts.py +87 -66
- pymobiledevice3/restore/device.py +59 -12
- pymobiledevice3/restore/fdr.py +46 -48
- pymobiledevice3/restore/ftab.py +19 -19
- pymobiledevice3/restore/img4.py +163 -0
- pymobiledevice3/restore/mbn.py +587 -0
- pymobiledevice3/restore/recovery.py +151 -151
- pymobiledevice3/restore/restore.py +562 -544
- pymobiledevice3/restore/restore_options.py +131 -110
- pymobiledevice3/restore/restored_client.py +51 -31
- pymobiledevice3/restore/tss.py +385 -267
- pymobiledevice3/service_connection.py +252 -59
- pymobiledevice3/services/accessibilityaudit.py +202 -120
- pymobiledevice3/services/afc.py +962 -365
- pymobiledevice3/services/amfi.py +24 -30
- pymobiledevice3/services/companion.py +23 -19
- pymobiledevice3/services/crash_reports.py +71 -47
- pymobiledevice3/services/debugserver_applist.py +3 -3
- pymobiledevice3/services/device_arbitration.py +8 -8
- pymobiledevice3/services/device_link.py +101 -79
- pymobiledevice3/services/diagnostics.py +973 -967
- 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 +20 -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 +35 -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 +9 -8
- pymobiledevice3/services/house_arrest.py +16 -15
- pymobiledevice3/services/idam.py +20 -0
- pymobiledevice3/services/installation_proxy.py +173 -81
- pymobiledevice3/services/lockdown_service.py +20 -10
- pymobiledevice3/services/misagent.py +22 -19
- pymobiledevice3/services/mobile_activation.py +147 -64
- pymobiledevice3/services/mobile_config.py +331 -294
- pymobiledevice3/services/mobile_image_mounter.py +141 -113
- pymobiledevice3/services/mobilebackup2.py +203 -145
- pymobiledevice3/services/notification_proxy.py +11 -11
- pymobiledevice3/services/os_trace.py +134 -74
- pymobiledevice3/services/pcapd.py +314 -302
- pymobiledevice3/services/power_assertion.py +10 -9
- pymobiledevice3/services/preboard.py +4 -4
- pymobiledevice3/services/remote_fetch_symbols.py +21 -14
- pymobiledevice3/services/remote_server.py +176 -146
- pymobiledevice3/services/restore_service.py +16 -16
- pymobiledevice3/services/screenshot.py +15 -12
- 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 +11 -11
- pymobiledevice3/services/web_protocol/automation_session.py +251 -239
- pymobiledevice3/services/web_protocol/cdp_screencast.py +46 -37
- pymobiledevice3/services/web_protocol/cdp_server.py +19 -19
- pymobiledevice3/services/web_protocol/cdp_target.py +411 -373
- pymobiledevice3/services/web_protocol/driver.py +114 -111
- pymobiledevice3/services/web_protocol/element.py +124 -111
- pymobiledevice3/services/web_protocol/inspector_session.py +106 -102
- pymobiledevice3/services/web_protocol/selenium_api.py +49 -49
- pymobiledevice3/services/web_protocol/session_protocol.py +18 -12
- pymobiledevice3/services/web_protocol/switch_to.py +30 -27
- pymobiledevice3/services/webinspector.py +189 -155
- pymobiledevice3/tcp_forwarder.py +87 -69
- pymobiledevice3/tunneld/__init__.py +0 -0
- pymobiledevice3/tunneld/api.py +63 -0
- pymobiledevice3/tunneld/server.py +603 -0
- pymobiledevice3/usbmux.py +198 -147
- pymobiledevice3/utils.py +14 -11
- {pymobiledevice3-4.14.6.dist-info → pymobiledevice3-7.0.6.dist-info}/METADATA +55 -28
- pymobiledevice3-7.0.6.dist-info/RECORD +188 -0
- {pymobiledevice3-4.14.6.dist-info → pymobiledevice3-7.0.6.dist-info}/WHEEL +1 -1
- pymobiledevice3/cli/developer.py +0 -1215
- pymobiledevice3/cli/diagnostics.py +0 -99
- pymobiledevice3/tunneld.py +0 -524
- pymobiledevice3-4.14.6.dist-info/RECORD +0 -168
- {pymobiledevice3-4.14.6.dist-info → pymobiledevice3-7.0.6.dist-info}/entry_points.txt +0 -0
- {pymobiledevice3-4.14.6.dist-info → pymobiledevice3-7.0.6.dist-info/licenses}/LICENSE +0 -0
- {pymobiledevice3-4.14.6.dist-info → pymobiledevice3-7.0.6.dist-info}/top_level.txt +0 -0
pymobiledevice3/usbmux.py
CHANGED
|
@@ -5,75 +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
|
-
|
|
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
|
+
)
|
|
37
61
|
|
|
38
62
|
usbmuxd_header = Struct(
|
|
39
|
-
|
|
40
|
-
|
|
41
|
-
|
|
63
|
+
"version" / usbmuxd_version, # protocol version
|
|
64
|
+
"message" / usbmuxd_msgtype, # message type
|
|
65
|
+
"tag" / Int32ul, # responses to this query will echo back this tag
|
|
42
66
|
)
|
|
43
67
|
|
|
44
|
-
usbmuxd_request = Prefixed(
|
|
45
|
-
|
|
46
|
-
|
|
47
|
-
|
|
48
|
-
|
|
49
|
-
|
|
50
|
-
|
|
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
|
+
},
|
|
51
83
|
),
|
|
52
|
-
|
|
53
|
-
|
|
54
|
-
)
|
|
84
|
+
),
|
|
85
|
+
includelength=True,
|
|
86
|
+
)
|
|
55
87
|
|
|
56
88
|
usbmuxd_device_record = Struct(
|
|
57
|
-
|
|
58
|
-
|
|
59
|
-
|
|
89
|
+
"device_id" / Int32ul,
|
|
90
|
+
"product_id" / Int16ul,
|
|
91
|
+
"serial_number" / FixedSized(256, CString("ascii")),
|
|
60
92
|
Padding(2),
|
|
61
|
-
|
|
93
|
+
"location" / Int32ul,
|
|
62
94
|
)
|
|
63
95
|
|
|
64
|
-
usbmuxd_response = Prefixed(
|
|
65
|
-
|
|
66
|
-
|
|
67
|
-
|
|
68
|
-
|
|
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
|
+
},
|
|
69
113
|
),
|
|
70
|
-
|
|
71
|
-
|
|
72
|
-
|
|
73
|
-
),
|
|
74
|
-
usbmuxd_msgtype.PLIST: GreedyBytes,
|
|
75
|
-
}),
|
|
76
|
-
), includelength=True)
|
|
114
|
+
),
|
|
115
|
+
includelength=True,
|
|
116
|
+
)
|
|
77
117
|
|
|
78
118
|
|
|
79
119
|
@dataclass
|
|
@@ -86,24 +126,24 @@ class MuxDevice:
|
|
|
86
126
|
mux = create_mux(usbmux_address=usbmux_address)
|
|
87
127
|
try:
|
|
88
128
|
return mux.connect(self, port)
|
|
89
|
-
except:
|
|
129
|
+
except:
|
|
90
130
|
mux.close()
|
|
91
131
|
raise
|
|
92
132
|
|
|
93
133
|
@property
|
|
94
134
|
def is_usb(self) -> bool:
|
|
95
|
-
return self.connection_type ==
|
|
135
|
+
return self.connection_type == "USB"
|
|
96
136
|
|
|
97
137
|
@property
|
|
98
138
|
def is_network(self) -> bool:
|
|
99
|
-
return self.connection_type ==
|
|
139
|
+
return self.connection_type == "Network"
|
|
100
140
|
|
|
101
141
|
def matches_udid(self, udid: str) -> bool:
|
|
102
|
-
return self.serial.replace(
|
|
142
|
+
return self.serial.replace("-", "") == udid.replace("-", "")
|
|
103
143
|
|
|
104
144
|
|
|
105
145
|
class SafeStreamSocket:
|
|
106
|
-
"""
|
|
146
|
+
"""wrapper to native python socket object to be used with construct as a stream"""
|
|
107
147
|
|
|
108
148
|
def __init__(self, address, family):
|
|
109
149
|
self._offset = 0
|
|
@@ -116,12 +156,12 @@ class SafeStreamSocket:
|
|
|
116
156
|
return len(msg)
|
|
117
157
|
|
|
118
158
|
def recv(self, size: int) -> bytes:
|
|
119
|
-
msg = b
|
|
159
|
+
msg = b""
|
|
120
160
|
while len(msg) < size:
|
|
121
161
|
chunk = self.sock.recv(size - len(msg))
|
|
122
162
|
self._offset += len(chunk)
|
|
123
163
|
if not chunk:
|
|
124
|
-
raise MuxException(
|
|
164
|
+
raise MuxException("socket connection broken")
|
|
125
165
|
msg += chunk
|
|
126
166
|
return msg
|
|
127
167
|
|
|
@@ -143,18 +183,18 @@ class SafeStreamSocket:
|
|
|
143
183
|
|
|
144
184
|
class MuxConnection:
|
|
145
185
|
# used on Windows
|
|
146
|
-
ITUNES_HOST = (
|
|
186
|
+
ITUNES_HOST = ("127.0.0.1", 27015)
|
|
147
187
|
|
|
148
188
|
# used for macOS and Linux
|
|
149
|
-
USBMUXD_PIPE =
|
|
189
|
+
USBMUXD_PIPE = "/var/run/usbmuxd"
|
|
150
190
|
|
|
151
191
|
@staticmethod
|
|
152
192
|
def create_usbmux_socket(usbmux_address: Optional[str] = None) -> SafeStreamSocket:
|
|
153
193
|
try:
|
|
154
194
|
if usbmux_address is not None:
|
|
155
|
-
if
|
|
195
|
+
if ":" in usbmux_address:
|
|
156
196
|
# assume tcp address
|
|
157
|
-
hostname, port = usbmux_address.split(
|
|
197
|
+
hostname, port = usbmux_address.split(":")
|
|
158
198
|
port = int(port)
|
|
159
199
|
address = (hostname, port)
|
|
160
200
|
family = socket.AF_INET
|
|
@@ -165,23 +205,25 @@ class MuxConnection:
|
|
|
165
205
|
else:
|
|
166
206
|
address, family = get_os_utils().usbmux_address
|
|
167
207
|
return SafeStreamSocket(address, family)
|
|
168
|
-
except ConnectionRefusedError:
|
|
169
|
-
raise ConnectionFailedToUsbmuxdError()
|
|
208
|
+
except ConnectionRefusedError as e:
|
|
209
|
+
raise ConnectionFailedToUsbmuxdError() from e
|
|
170
210
|
|
|
171
211
|
@staticmethod
|
|
172
212
|
def create(usbmux_address: Optional[str] = None):
|
|
173
213
|
# first attempt to connect with possibly the wrong version header (plist protocol)
|
|
174
214
|
sock = MuxConnection.create_usbmux_socket(usbmux_address=usbmux_address)
|
|
175
215
|
|
|
176
|
-
|
|
177
|
-
|
|
178
|
-
|
|
179
|
-
|
|
180
|
-
|
|
181
|
-
|
|
182
|
-
|
|
183
|
-
|
|
184
|
-
|
|
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()
|
|
185
227
|
sock = MuxConnection.create_usbmux_socket(usbmux_address=usbmux_address)
|
|
186
228
|
|
|
187
229
|
if response.header.version == usbmuxd_version.BINARY:
|
|
@@ -189,7 +231,7 @@ class MuxConnection:
|
|
|
189
231
|
elif response.header.version == usbmuxd_version.PLIST:
|
|
190
232
|
return PlistMuxConnection(sock)
|
|
191
233
|
|
|
192
|
-
raise MuxVersionError(f
|
|
234
|
+
raise MuxVersionError(f"usbmuxd returned unsupported version: {response.version}")
|
|
193
235
|
|
|
194
236
|
def __init__(self, sock: SafeStreamSocket):
|
|
195
237
|
self._sock = sock
|
|
@@ -206,36 +248,37 @@ class MuxConnection:
|
|
|
206
248
|
|
|
207
249
|
@abc.abstractmethod
|
|
208
250
|
def _connect(self, device_id: int, port: int):
|
|
209
|
-
"""
|
|
251
|
+
"""initiate a "Connect" request to target port"""
|
|
210
252
|
pass
|
|
211
253
|
|
|
212
254
|
@abc.abstractmethod
|
|
213
|
-
def get_device_list(self, timeout: float = None):
|
|
255
|
+
def get_device_list(self, timeout: Optional[float] = None):
|
|
214
256
|
"""
|
|
215
257
|
request an update to current device list
|
|
216
258
|
"""
|
|
217
259
|
pass
|
|
218
260
|
|
|
219
261
|
def connect(self, device: MuxDevice, port: int) -> socket.socket:
|
|
220
|
-
"""
|
|
262
|
+
"""connect to a relay port on target machine and get a raw python socket object for the connection"""
|
|
221
263
|
self._connect(device.devid, socket.htons(port))
|
|
222
264
|
self._connected = True
|
|
223
265
|
return self._sock.sock
|
|
224
266
|
|
|
225
267
|
def close(self):
|
|
226
|
-
"""
|
|
268
|
+
"""close current socket"""
|
|
227
269
|
self._sock.close()
|
|
228
270
|
|
|
229
271
|
def _assert_not_connected(self):
|
|
230
|
-
"""
|
|
272
|
+
"""verify active state is in state for control messages"""
|
|
231
273
|
if self._connected:
|
|
232
|
-
raise MuxException(
|
|
274
|
+
raise MuxException("Mux is connected, cannot issue control packets")
|
|
233
275
|
|
|
234
276
|
def _raise_mux_exception(self, result: int, message: Optional[str] = None) -> None:
|
|
235
277
|
exceptions = {
|
|
236
278
|
int(usbmuxd_result.BADCOMMAND): BadCommandError,
|
|
237
279
|
int(usbmuxd_result.BADDEV): BadDevError,
|
|
238
280
|
int(usbmuxd_result.CONNREFUSED): ConnectionFailedError,
|
|
281
|
+
int(usbmuxd_result.NOSUCHSERVICE): ConnectionFailedError,
|
|
239
282
|
int(usbmuxd_result.BADVERSION): MuxVersionError,
|
|
240
283
|
}
|
|
241
284
|
exception = exceptions.get(result, MuxException)
|
|
@@ -249,14 +292,14 @@ class MuxConnection:
|
|
|
249
292
|
|
|
250
293
|
|
|
251
294
|
class BinaryMuxConnection(MuxConnection):
|
|
252
|
-
"""
|
|
295
|
+
"""old binary protocol"""
|
|
253
296
|
|
|
254
297
|
def __init__(self, sock: SafeStreamSocket):
|
|
255
298
|
super().__init__(sock)
|
|
256
299
|
self._version = usbmuxd_version.BINARY
|
|
257
300
|
|
|
258
|
-
def get_device_list(self, timeout: float = None):
|
|
259
|
-
"""
|
|
301
|
+
def get_device_list(self, timeout: Optional[float] = None):
|
|
302
|
+
"""use timeout to wait for the device list to be fully populated"""
|
|
260
303
|
self._assert_not_connected()
|
|
261
304
|
end = time.time() + timeout
|
|
262
305
|
self.listen()
|
|
@@ -266,55 +309,54 @@ class BinaryMuxConnection(MuxConnection):
|
|
|
266
309
|
self._receive_device_state_update()
|
|
267
310
|
except (BlockingIOError, StreamError):
|
|
268
311
|
continue
|
|
269
|
-
except
|
|
312
|
+
except OSError as e:
|
|
270
313
|
try:
|
|
271
314
|
self._sock.setblocking(True)
|
|
272
315
|
self.close()
|
|
273
316
|
except OSError:
|
|
274
317
|
pass
|
|
275
|
-
raise MuxException(
|
|
318
|
+
raise MuxException("Exception in listener socket") from e
|
|
276
319
|
|
|
277
320
|
def listen(self):
|
|
278
|
-
"""
|
|
321
|
+
"""start listening for events of attached and detached devices"""
|
|
279
322
|
self._send_receive(usbmuxd_msgtype.LISTEN)
|
|
280
323
|
|
|
281
324
|
def _connect(self, device_id: int, port: int):
|
|
282
|
-
self._send({
|
|
283
|
-
|
|
284
|
-
|
|
285
|
-
|
|
286
|
-
})
|
|
325
|
+
self._send({
|
|
326
|
+
"header": {"version": self._version, "message": usbmuxd_msgtype.CONNECT, "tag": self._tag},
|
|
327
|
+
"data": {"device_id": device_id, "port": port},
|
|
328
|
+
})
|
|
287
329
|
response = self._receive()
|
|
288
330
|
if response.header.message != usbmuxd_msgtype.RESULT:
|
|
289
|
-
raise MuxException(f
|
|
331
|
+
raise MuxException(f"unexpected message type received: {response}")
|
|
290
332
|
|
|
291
333
|
if response.data.result != usbmuxd_result.OK:
|
|
292
|
-
raise self._raise_mux_exception(
|
|
293
|
-
|
|
294
|
-
|
|
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
|
+
)
|
|
295
338
|
|
|
296
339
|
def _send(self, data: dict) -> None:
|
|
297
340
|
self._assert_not_connected()
|
|
298
341
|
self._sock.send(usbmuxd_request.build(data))
|
|
299
342
|
self._tag += 1
|
|
300
343
|
|
|
301
|
-
def _receive(self, expected_tag: int = None):
|
|
344
|
+
def _receive(self, expected_tag: Optional[int] = None):
|
|
302
345
|
self._assert_not_connected()
|
|
303
346
|
response = usbmuxd_response.parse_stream(self._sock)
|
|
304
347
|
if expected_tag and response.header.tag != expected_tag:
|
|
305
|
-
raise MuxException(f
|
|
348
|
+
raise MuxException(f"Reply tag mismatch: expected {expected_tag}, got {response.header.tag}")
|
|
306
349
|
return response
|
|
307
350
|
|
|
308
351
|
def _send_receive(self, message_type: int):
|
|
309
|
-
self._send({
|
|
310
|
-
'data': b''})
|
|
352
|
+
self._send({"header": {"version": self._version, "message": message_type, "tag": self._tag}, "data": b""})
|
|
311
353
|
response = self._receive(self._tag - 1)
|
|
312
354
|
if response.header.message != usbmuxd_msgtype.RESULT:
|
|
313
|
-
raise MuxException(f
|
|
355
|
+
raise MuxException(f"unexpected message type received: {response}")
|
|
314
356
|
|
|
315
357
|
result = response.data.result
|
|
316
358
|
if result != usbmuxd_result.OK:
|
|
317
|
-
raise self._raise_mux_exception(int(result), f
|
|
359
|
+
raise self._raise_mux_exception(int(result), f"{message_type} failed: error {result}")
|
|
318
360
|
|
|
319
361
|
def _add_device(self, device: MuxDevice):
|
|
320
362
|
self.devices.append(device)
|
|
@@ -326,11 +368,11 @@ class BinaryMuxConnection(MuxConnection):
|
|
|
326
368
|
response = self._receive()
|
|
327
369
|
if response.header.message == usbmuxd_msgtype.ADD:
|
|
328
370
|
# old protocol only supported USB devices
|
|
329
|
-
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"))
|
|
330
372
|
elif response.header.message == usbmuxd_msgtype.REMOVE:
|
|
331
373
|
self._remove_device(response.data.device_id)
|
|
332
374
|
else:
|
|
333
|
-
raise MuxException(f
|
|
375
|
+
raise MuxException(f"Invalid packet type received: {response}")
|
|
334
376
|
|
|
335
377
|
|
|
336
378
|
class PlistMuxConnection(BinaryMuxConnection):
|
|
@@ -339,71 +381,77 @@ class PlistMuxConnection(BinaryMuxConnection):
|
|
|
339
381
|
self._version = usbmuxd_version.PLIST
|
|
340
382
|
|
|
341
383
|
def listen(self) -> None:
|
|
342
|
-
self._send_receive({
|
|
384
|
+
self._send_receive({"MessageType": "Listen"})
|
|
343
385
|
|
|
344
386
|
def get_pair_record(self, serial: str) -> dict:
|
|
345
387
|
# serials are saved inside usbmuxd without '-'
|
|
346
|
-
self._send({
|
|
388
|
+
self._send({"MessageType": "ReadPairRecord", "PairRecordID": serial})
|
|
347
389
|
response = self._receive(self._tag - 1)
|
|
348
|
-
pair_record = response.get(
|
|
390
|
+
pair_record = response.get("PairRecordData")
|
|
349
391
|
if pair_record is None:
|
|
350
|
-
raise NotPairedError(
|
|
392
|
+
raise NotPairedError("device should be paired first")
|
|
351
393
|
return plistlib.loads(pair_record)
|
|
352
394
|
|
|
353
|
-
def get_device_list(self, timeout: float = None) -> None:
|
|
354
|
-
"""
|
|
395
|
+
def get_device_list(self, timeout: Optional[float] = None) -> None:
|
|
396
|
+
"""get device list synchronously without waiting the timeout"""
|
|
355
397
|
self.devices = []
|
|
356
|
-
self._send({
|
|
398
|
+
self._send({"MessageType": "ListDevices"})
|
|
357
399
|
response = self._receive(self._tag - 1)
|
|
358
|
-
device_list = response.get(
|
|
400
|
+
device_list = response.get("DeviceList")
|
|
359
401
|
if device_list is None:
|
|
360
|
-
raise MuxException(f
|
|
402
|
+
raise MuxException(f"Got an invalid response from usbmux: {response}")
|
|
361
403
|
for response in device_list:
|
|
362
|
-
if response[
|
|
363
|
-
super()._add_device(
|
|
364
|
-
|
|
365
|
-
|
|
366
|
-
|
|
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"])
|
|
367
414
|
else:
|
|
368
|
-
raise MuxException(f
|
|
415
|
+
raise MuxException(f"Invalid packet type received: {response}")
|
|
369
416
|
|
|
370
417
|
def get_buid(self) -> str:
|
|
371
|
-
"""
|
|
372
|
-
self._send({
|
|
373
|
-
return self._receive(self._tag - 1)[
|
|
418
|
+
"""get SystemBUID"""
|
|
419
|
+
self._send({"MessageType": "ReadBUID"})
|
|
420
|
+
return self._receive(self._tag - 1)["BUID"]
|
|
374
421
|
|
|
375
422
|
def save_pair_record(self, serial: str, device_id: int, record_data: bytes):
|
|
376
423
|
# serials are saved inside usbmuxd without '-'
|
|
377
|
-
self._send_receive({
|
|
378
|
-
|
|
379
|
-
|
|
380
|
-
|
|
424
|
+
self._send_receive({
|
|
425
|
+
"MessageType": "SavePairRecord",
|
|
426
|
+
"PairRecordID": serial,
|
|
427
|
+
"PairRecordData": record_data,
|
|
428
|
+
"DeviceID": device_id,
|
|
429
|
+
})
|
|
381
430
|
|
|
382
431
|
def _connect(self, device_id: int, port: int):
|
|
383
|
-
self._send_receive({
|
|
432
|
+
self._send_receive({"MessageType": "Connect", "DeviceID": device_id, "PortNumber": port})
|
|
384
433
|
|
|
385
434
|
def _send(self, data: dict):
|
|
386
|
-
request = {
|
|
435
|
+
request = {"ClientVersionString": "qt4i-usbmuxd", "ProgName": "pymobiledevice3", "kLibUSBMuxVersion": 3}
|
|
387
436
|
request.update(data)
|
|
388
|
-
super()._send({
|
|
389
|
-
|
|
390
|
-
|
|
391
|
-
|
|
392
|
-
})
|
|
437
|
+
super()._send({
|
|
438
|
+
"header": {"version": self._version, "message": usbmuxd_msgtype.PLIST, "tag": self._tag},
|
|
439
|
+
"data": plistlib.dumps(request),
|
|
440
|
+
})
|
|
393
441
|
|
|
394
|
-
def _receive(self, expected_tag: int = None) -> dict:
|
|
442
|
+
def _receive(self, expected_tag: Optional[int] = None) -> dict:
|
|
395
443
|
response = super()._receive(expected_tag=expected_tag)
|
|
396
444
|
if response.header.message != usbmuxd_msgtype.PLIST:
|
|
397
|
-
raise MuxException(f
|
|
445
|
+
raise MuxException(f"Received non-plist type {response}")
|
|
398
446
|
return plistlib.loads(response.data)
|
|
399
447
|
|
|
400
448
|
def _send_receive(self, data: dict):
|
|
401
449
|
self._send(data)
|
|
402
450
|
response = self._receive(self._tag - 1)
|
|
403
|
-
if response[
|
|
404
|
-
raise MuxException(f
|
|
405
|
-
if response[
|
|
406
|
-
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}")
|
|
407
455
|
|
|
408
456
|
|
|
409
457
|
def create_mux(usbmux_address: Optional[str] = None) -> MuxConnection:
|
|
@@ -412,14 +460,17 @@ def create_mux(usbmux_address: Optional[str] = None) -> MuxConnection:
|
|
|
412
460
|
|
|
413
461
|
def list_devices(usbmux_address: Optional[str] = None) -> list[MuxDevice]:
|
|
414
462
|
mux = create_mux(usbmux_address=usbmux_address)
|
|
415
|
-
|
|
416
|
-
|
|
417
|
-
|
|
463
|
+
try:
|
|
464
|
+
mux.get_device_list(0.1)
|
|
465
|
+
devices = mux.devices
|
|
466
|
+
finally:
|
|
467
|
+
mux.close()
|
|
418
468
|
return devices
|
|
419
469
|
|
|
420
470
|
|
|
421
|
-
def select_device(
|
|
422
|
-
|
|
471
|
+
def select_device(
|
|
472
|
+
udid: Optional[str] = None, connection_type: Optional[str] = None, usbmux_address: Optional[str] = None
|
|
473
|
+
) -> Optional[MuxDevice]:
|
|
423
474
|
"""
|
|
424
475
|
select a UsbMux device according to given arguments.
|
|
425
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)
|