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
|
@@ -1,15 +1,20 @@
|
|
|
1
1
|
import asyncio
|
|
2
|
+
import contextlib
|
|
2
3
|
import json
|
|
3
4
|
import logging
|
|
4
5
|
import uuid
|
|
6
|
+
from collections.abc import Coroutine
|
|
5
7
|
from dataclasses import dataclass, fields
|
|
6
8
|
from enum import Enum
|
|
7
|
-
from typing import Optional, Union
|
|
9
|
+
from typing import Any, Optional, Union
|
|
8
10
|
|
|
9
11
|
import nest_asyncio
|
|
10
12
|
|
|
11
|
-
from pymobiledevice3.exceptions import
|
|
12
|
-
|
|
13
|
+
from pymobiledevice3.exceptions import (
|
|
14
|
+
LaunchingApplicationError,
|
|
15
|
+
RemoteAutomationNotEnabledError,
|
|
16
|
+
WebInspectorNotEnabledError,
|
|
17
|
+
)
|
|
13
18
|
from pymobiledevice3.lockdown import LockdownClient
|
|
14
19
|
from pymobiledevice3.lockdown_service_provider import LockdownServiceProvider
|
|
15
20
|
from pymobiledevice3.service_connection import ServiceConnection
|
|
@@ -17,55 +22,55 @@ from pymobiledevice3.services.web_protocol.automation_session import AutomationS
|
|
|
17
22
|
from pymobiledevice3.services.web_protocol.inspector_session import InspectorSession
|
|
18
23
|
from pymobiledevice3.services.web_protocol.session_protocol import SessionProtocol
|
|
19
24
|
|
|
20
|
-
SAFARI =
|
|
25
|
+
SAFARI = "com.apple.mobilesafari"
|
|
21
26
|
|
|
22
27
|
|
|
23
28
|
def key_to_pid(key: str) -> int:
|
|
24
|
-
return int(key.split(
|
|
29
|
+
return int(key.split(":")[1])
|
|
25
30
|
|
|
26
31
|
|
|
27
32
|
class WirTypes(Enum):
|
|
28
|
-
AUTOMATION =
|
|
29
|
-
ITML =
|
|
30
|
-
JAVASCRIPT =
|
|
31
|
-
PAGE =
|
|
32
|
-
SERVICE_WORKER =
|
|
33
|
-
WEB =
|
|
34
|
-
WEB_PAGE =
|
|
35
|
-
AUTOMATICALLY_PAUSE =
|
|
33
|
+
AUTOMATION = "WIRTypeAutomation"
|
|
34
|
+
ITML = "WIRTypeITML"
|
|
35
|
+
JAVASCRIPT = "WIRTypeJavaScript"
|
|
36
|
+
PAGE = "WIRTypePage"
|
|
37
|
+
SERVICE_WORKER = "WIRTypeServiceWorker"
|
|
38
|
+
WEB = "WIRTypeWeb"
|
|
39
|
+
WEB_PAGE = "WIRTypeWebPage"
|
|
40
|
+
AUTOMATICALLY_PAUSE = "WIRAutomaticallyPause"
|
|
36
41
|
|
|
37
42
|
|
|
38
43
|
class AutomationAvailability(Enum):
|
|
39
|
-
NOT_AVAILABLE =
|
|
40
|
-
AVAILABLE =
|
|
41
|
-
UNKNOWN =
|
|
44
|
+
NOT_AVAILABLE = "WIRAutomationAvailabilityNotAvailable"
|
|
45
|
+
AVAILABLE = "WIRAutomationAvailabilityAvailable"
|
|
46
|
+
UNKNOWN = "WIRAutomationAvailabilityUnknown"
|
|
42
47
|
|
|
43
48
|
|
|
44
49
|
@dataclass
|
|
45
50
|
class Page:
|
|
46
51
|
id_: int
|
|
47
52
|
type_: WirTypes
|
|
48
|
-
web_url: str =
|
|
49
|
-
web_title: str =
|
|
53
|
+
web_url: str = ""
|
|
54
|
+
web_title: str = ""
|
|
50
55
|
automation_is_paired_key: bool = False
|
|
51
|
-
automation_name: str =
|
|
52
|
-
automation_version: str =
|
|
53
|
-
automation_session_id: str =
|
|
54
|
-
automation_connection_id: str =
|
|
56
|
+
automation_name: str = ""
|
|
57
|
+
automation_version: str = ""
|
|
58
|
+
automation_session_id: str = ""
|
|
59
|
+
automation_connection_id: str = ""
|
|
55
60
|
|
|
56
61
|
@classmethod
|
|
57
|
-
def from_page_dictionary(cls, page_dict: dict) ->
|
|
58
|
-
p = cls(page_dict[
|
|
62
|
+
def from_page_dictionary(cls, page_dict: dict) -> "Page":
|
|
63
|
+
p = cls(page_dict["WIRPageIdentifierKey"], WirTypes(page_dict["WIRTypeKey"]))
|
|
59
64
|
if p.type_ in (WirTypes.WEB, WirTypes.WEB_PAGE):
|
|
60
|
-
p.web_title = page_dict[
|
|
61
|
-
p.web_url = page_dict[
|
|
65
|
+
p.web_title = page_dict["WIRTitleKey"]
|
|
66
|
+
p.web_url = page_dict["WIRURLKey"]
|
|
62
67
|
if p.type_ == WirTypes.AUTOMATION:
|
|
63
|
-
p.automation_is_paired_key = page_dict[
|
|
64
|
-
p.automation_name = page_dict[
|
|
65
|
-
p.automation_version = page_dict[
|
|
66
|
-
p.automation_session_id = page_dict[
|
|
67
|
-
if
|
|
68
|
-
p.automation_connection_id = page_dict[
|
|
68
|
+
p.automation_is_paired_key = page_dict["WIRAutomationTargetIsPairedKey"]
|
|
69
|
+
p.automation_name = page_dict["WIRAutomationTargetNameKey"]
|
|
70
|
+
p.automation_version = page_dict["WIRAutomationTargetVersionKey"]
|
|
71
|
+
p.automation_session_id = page_dict["WIRSessionIdentifierKey"]
|
|
72
|
+
if "WIRConnectionIdentifierKey" in page_dict:
|
|
73
|
+
p.automation_connection_id = page_dict["WIRConnectionIdentifierKey"]
|
|
69
74
|
return p
|
|
70
75
|
|
|
71
76
|
def update(self, page_dict: dict):
|
|
@@ -74,7 +79,7 @@ class Page:
|
|
|
74
79
|
setattr(self, field.name, getattr(new_p, field.name))
|
|
75
80
|
|
|
76
81
|
def __str__(self):
|
|
77
|
-
return f
|
|
82
|
+
return f"id: {self.id_}, title: {self.web_title}, url: {self.web_url}"
|
|
78
83
|
|
|
79
84
|
|
|
80
85
|
@dataclass
|
|
@@ -87,20 +92,20 @@ class Application:
|
|
|
87
92
|
active: int
|
|
88
93
|
proxy: bool
|
|
89
94
|
ready: bool
|
|
90
|
-
host: str =
|
|
95
|
+
host: str = ""
|
|
91
96
|
|
|
92
97
|
@classmethod
|
|
93
|
-
def from_application_dictionary(cls, app_dict) ->
|
|
98
|
+
def from_application_dictionary(cls, app_dict) -> "Application":
|
|
94
99
|
return cls(
|
|
95
|
-
app_dict[
|
|
96
|
-
app_dict[
|
|
97
|
-
key_to_pid(app_dict[
|
|
98
|
-
app_dict[
|
|
99
|
-
AutomationAvailability(app_dict[
|
|
100
|
-
app_dict[
|
|
101
|
-
app_dict[
|
|
102
|
-
app_dict[
|
|
103
|
-
app_dict.get(
|
|
100
|
+
app_dict["WIRApplicationIdentifierKey"],
|
|
101
|
+
app_dict["WIRApplicationBundleIdentifierKey"],
|
|
102
|
+
key_to_pid(app_dict["WIRApplicationIdentifierKey"]),
|
|
103
|
+
app_dict["WIRApplicationNameKey"],
|
|
104
|
+
AutomationAvailability(app_dict["WIRAutomationAvailabilityKey"]),
|
|
105
|
+
app_dict["WIRIsApplicationActiveKey"],
|
|
106
|
+
app_dict["WIRIsApplicationProxyKey"],
|
|
107
|
+
app_dict["WIRIsApplicationReadyKey"],
|
|
108
|
+
app_dict.get("WIRHostApplicationIdentifierKey", ""),
|
|
104
109
|
)
|
|
105
110
|
|
|
106
111
|
|
|
@@ -110,12 +115,12 @@ class ApplicationPage:
|
|
|
110
115
|
page: Page
|
|
111
116
|
|
|
112
117
|
def __str__(self) -> str:
|
|
113
|
-
return f
|
|
118
|
+
return f"<{self.application.name}({self.application.pid}) TYPE:{self.page.type_.value} URL:{self.page.web_url}>"
|
|
114
119
|
|
|
115
120
|
|
|
116
121
|
class WebinspectorService:
|
|
117
|
-
SERVICE_NAME =
|
|
118
|
-
RSD_SERVICE_NAME =
|
|
122
|
+
SERVICE_NAME = "com.apple.webinspector"
|
|
123
|
+
RSD_SERVICE_NAME = "com.apple.webinspector.shim.remote"
|
|
119
124
|
|
|
120
125
|
def __init__(self, lockdown: LockdownServiceProvider, loop=None):
|
|
121
126
|
if loop is None:
|
|
@@ -142,32 +147,30 @@ class WebinspectorService:
|
|
|
142
147
|
self.wir_message_results = {}
|
|
143
148
|
self.wir_events = []
|
|
144
149
|
self.receive_handlers = {
|
|
145
|
-
|
|
146
|
-
|
|
147
|
-
|
|
148
|
-
|
|
149
|
-
|
|
150
|
-
|
|
151
|
-
|
|
152
|
-
|
|
150
|
+
"_rpc_reportCurrentState:": self._handle_report_current_state,
|
|
151
|
+
"_rpc_reportConnectedApplicationList:": self._handle_report_connected_application_list,
|
|
152
|
+
"_rpc_reportConnectedDriverList:": self._handle_report_connected_driver_list,
|
|
153
|
+
"_rpc_applicationSentListing:": self._handle_application_sent_listing,
|
|
154
|
+
"_rpc_applicationUpdated:": self._handle_application_updated,
|
|
155
|
+
"_rpc_applicationConnected:": self._handle_application_connected,
|
|
156
|
+
"_rpc_applicationSentData:": self._handle_application_sent_data,
|
|
157
|
+
"_rpc_applicationDisconnected:": self._handle_application_disconnected,
|
|
153
158
|
}
|
|
154
159
|
self._recv_task: Optional[asyncio.Task] = None
|
|
155
160
|
|
|
156
|
-
def connect(self, timeout: Union[float, int] = None):
|
|
161
|
+
def connect(self, timeout: Optional[Union[float, int]] = None):
|
|
157
162
|
self.service = self.await_(self.lockdown.aio_start_lockdown_service(self.service_name))
|
|
158
163
|
self.await_(self._report_identifier())
|
|
159
164
|
try:
|
|
160
|
-
self._handle_recv(self.
|
|
165
|
+
self._handle_recv(self._await_with_timeout(self._recv_message(), timeout))
|
|
161
166
|
except asyncio.TimeoutError as e:
|
|
162
167
|
raise WebInspectorNotEnabledError from e
|
|
163
168
|
self._recv_task = self.loop.create_task(self._receiving_task())
|
|
164
169
|
|
|
165
170
|
def close(self):
|
|
166
171
|
self._recv_task.cancel()
|
|
167
|
-
|
|
172
|
+
with contextlib.suppress(asyncio.CancelledError):
|
|
168
173
|
self.await_(self._recv_task)
|
|
169
|
-
except asyncio.CancelledError:
|
|
170
|
-
pass
|
|
171
174
|
self.await_(self.service.aio_close())
|
|
172
175
|
|
|
173
176
|
async def _recv_message(self):
|
|
@@ -182,7 +185,7 @@ class WebinspectorService:
|
|
|
182
185
|
self._handle_recv(await self._recv_message())
|
|
183
186
|
|
|
184
187
|
def automation_session(self, app: Application) -> AutomationSession:
|
|
185
|
-
if self.state ==
|
|
188
|
+
if self.state == "WIRAutomationAvailabilityNotAvailable":
|
|
186
189
|
raise RemoteAutomationNotEnabledError()
|
|
187
190
|
session_id = str(uuid.uuid4()).upper()
|
|
188
191
|
self.await_(self._forward_automation_session_request(session_id, app.id_))
|
|
@@ -196,8 +199,10 @@ class WebinspectorService:
|
|
|
196
199
|
|
|
197
200
|
async def inspector_session(self, app: Application, page: Page) -> InspectorSession:
|
|
198
201
|
session_id = str(uuid.uuid4()).upper()
|
|
199
|
-
return await InspectorSession.create(
|
|
200
|
-
|
|
202
|
+
return await InspectorSession.create(
|
|
203
|
+
SessionProtocol(self, session_id, app, page, method_prefix=""),
|
|
204
|
+
wait_target=page.type_ != WirTypes.JAVASCRIPT,
|
|
205
|
+
)
|
|
201
206
|
|
|
202
207
|
def get_open_pages(self) -> dict:
|
|
203
208
|
apps = {}
|
|
@@ -225,9 +230,9 @@ class WebinspectorService:
|
|
|
225
230
|
self.await_(self._request_application_launch(bundle))
|
|
226
231
|
self.get_open_pages()
|
|
227
232
|
try:
|
|
228
|
-
return self.
|
|
229
|
-
except TimeoutError:
|
|
230
|
-
raise LaunchingApplicationError()
|
|
233
|
+
return self._await_with_timeout(self._wait_for_application(bundle), timeout)
|
|
234
|
+
except TimeoutError as e:
|
|
235
|
+
raise LaunchingApplicationError() from e
|
|
231
236
|
|
|
232
237
|
async def send_socket_data(self, session_id: str, app_id: str, page_id: int, data: dict):
|
|
233
238
|
await self._forward_socket_data(session_id, app_id, page_id, data)
|
|
@@ -244,18 +249,30 @@ class WebinspectorService:
|
|
|
244
249
|
def flush_input(self, duration: Union[float, int] = 0):
|
|
245
250
|
return self.await_(asyncio.sleep(duration))
|
|
246
251
|
|
|
247
|
-
def await_(self, awaitable):
|
|
248
|
-
return self.loop.run_until_complete(
|
|
252
|
+
def await_(self, awaitable: Coroutine) -> Any:
|
|
253
|
+
return self.loop.run_until_complete(awaitable)
|
|
254
|
+
|
|
255
|
+
def _await_with_timeout(self, coro: Coroutine, timeout: Optional[float] = None) -> Any:
|
|
256
|
+
# Create a task explicitly so we're definitely inside a Task
|
|
257
|
+
task = self.loop.create_task(coro)
|
|
258
|
+
done, _pending = self.loop.run_until_complete(asyncio.wait({task}, timeout=timeout))
|
|
259
|
+
if not done:
|
|
260
|
+
task.cancel()
|
|
261
|
+
# Give the task a chance to cancel cleanly
|
|
262
|
+
with contextlib.suppress(asyncio.CancelledError):
|
|
263
|
+
self.loop.run_until_complete(task)
|
|
264
|
+
raise WebInspectorNotEnabledError()
|
|
265
|
+
return task.result()
|
|
249
266
|
|
|
250
267
|
def _handle_recv(self, plist):
|
|
251
|
-
self.receive_handlers[plist[
|
|
268
|
+
self.receive_handlers[plist["__selector"]](plist["__argument"])
|
|
252
269
|
|
|
253
270
|
def _handle_report_current_state(self, arg):
|
|
254
|
-
self.state = arg[
|
|
271
|
+
self.state = arg["WIRAutomationAvailabilityKey"]
|
|
255
272
|
|
|
256
273
|
def _handle_report_connected_application_list(self, arg):
|
|
257
274
|
self.connected_application = {}
|
|
258
|
-
for key, application in arg[
|
|
275
|
+
for key, application in arg["WIRApplicationDictionaryKey"].items():
|
|
259
276
|
self.connected_application[key] = Application.from_application_dictionary(application)
|
|
260
277
|
|
|
261
278
|
# Immediately also query the application pages
|
|
@@ -265,19 +282,19 @@ class WebinspectorService:
|
|
|
265
282
|
pass
|
|
266
283
|
|
|
267
284
|
def _handle_application_sent_listing(self, arg):
|
|
268
|
-
if arg[
|
|
285
|
+
if arg["WIRApplicationIdentifierKey"] in self.application_pages:
|
|
269
286
|
# Update existing application pages
|
|
270
|
-
for id_, page in arg[
|
|
271
|
-
if id_ in self.application_pages[arg[
|
|
272
|
-
self.application_pages[arg[
|
|
287
|
+
for id_, page in arg["WIRListingKey"].items():
|
|
288
|
+
if id_ in self.application_pages[arg["WIRApplicationIdentifierKey"]]:
|
|
289
|
+
self.application_pages[arg["WIRApplicationIdentifierKey"]][id_].update(page)
|
|
273
290
|
else:
|
|
274
|
-
self.application_pages[arg[
|
|
291
|
+
self.application_pages[arg["WIRApplicationIdentifierKey"]][id_] = Page.from_page_dictionary(page)
|
|
275
292
|
else:
|
|
276
293
|
# Add new application pages
|
|
277
294
|
pages = {}
|
|
278
|
-
for id_, page in arg[
|
|
295
|
+
for id_, page in arg["WIRListingKey"].items():
|
|
279
296
|
pages[id_] = Page.from_page_dictionary(page)
|
|
280
|
-
self.application_pages[arg[
|
|
297
|
+
self.application_pages[arg["WIRApplicationIdentifierKey"]] = pages
|
|
281
298
|
|
|
282
299
|
def _handle_application_updated(self, arg):
|
|
283
300
|
app = Application.from_application_dictionary(arg)
|
|
@@ -288,72 +305,81 @@ class WebinspectorService:
|
|
|
288
305
|
self.connected_application[app.id_] = app
|
|
289
306
|
|
|
290
307
|
def _handle_application_sent_data(self, arg):
|
|
291
|
-
response = json.loads(arg[
|
|
308
|
+
response = json.loads(arg["WIRMessageDataKey"])
|
|
292
309
|
|
|
293
|
-
if
|
|
294
|
-
self.wir_message_results[response[
|
|
310
|
+
if "id" in response:
|
|
311
|
+
self.wir_message_results[response["id"]] = response
|
|
295
312
|
else:
|
|
296
313
|
self.wir_events.append(response)
|
|
297
314
|
|
|
298
315
|
def _handle_application_disconnected(self, arg):
|
|
299
|
-
self.connected_application.pop(arg[
|
|
300
|
-
self.application_pages.pop(arg[
|
|
316
|
+
self.connected_application.pop(arg["WIRApplicationIdentifierKey"], None)
|
|
317
|
+
self.application_pages.pop(arg["WIRApplicationIdentifierKey"], None)
|
|
301
318
|
|
|
302
319
|
async def _report_identifier(self):
|
|
303
|
-
await self._send_message(
|
|
320
|
+
await self._send_message("_rpc_reportIdentifier:")
|
|
304
321
|
|
|
305
322
|
async def _forward_get_listing(self, app_id):
|
|
306
|
-
self.logger.debug(f
|
|
307
|
-
await self._send_message(
|
|
323
|
+
self.logger.debug(f"Listing app with id {app_id}")
|
|
324
|
+
await self._send_message("_rpc_forwardGetListing:", {"WIRApplicationIdentifierKey": app_id})
|
|
308
325
|
|
|
309
326
|
async def _request_application_launch(self, bundle: str):
|
|
310
|
-
await self._send_message(
|
|
327
|
+
await self._send_message("_rpc_requestApplicationLaunch:", {"WIRApplicationBundleIdentifierKey": bundle})
|
|
311
328
|
|
|
312
329
|
async def _get_connected_applications(self) -> None:
|
|
313
|
-
await self._send_message(
|
|
330
|
+
await self._send_message("_rpc_getConnectedApplications:", {})
|
|
314
331
|
|
|
315
332
|
async def _forward_automation_session_request(self, session_id: str, app_id: str):
|
|
316
|
-
await self._send_message(
|
|
317
|
-
|
|
318
|
-
|
|
319
|
-
|
|
320
|
-
|
|
333
|
+
await self._send_message(
|
|
334
|
+
"_rpc_forwardAutomationSessionRequest:",
|
|
335
|
+
{
|
|
336
|
+
"WIRApplicationIdentifierKey": app_id,
|
|
337
|
+
"WIRSessionCapabilitiesKey": {
|
|
338
|
+
"org.webkit.webdriver.webrtc.allow-insecure-media-capture": True,
|
|
339
|
+
"org.webkit.webdriver.webrtc.suppress-ice-candidate-filtering": False,
|
|
340
|
+
},
|
|
341
|
+
"WIRSessionIdentifierKey": session_id,
|
|
321
342
|
},
|
|
322
|
-
|
|
323
|
-
})
|
|
343
|
+
)
|
|
324
344
|
|
|
325
345
|
async def _forward_socket_setup(self, session_id: str, app_id: str, page_id: int, pause: bool = True):
|
|
326
346
|
message = {
|
|
327
|
-
|
|
328
|
-
|
|
329
|
-
|
|
330
|
-
|
|
347
|
+
"WIRApplicationIdentifierKey": app_id,
|
|
348
|
+
"WIRPageIdentifierKey": page_id,
|
|
349
|
+
"WIRSenderKey": session_id,
|
|
350
|
+
"WIRMessageDataTypeChunkSupportedKey": 0,
|
|
331
351
|
}
|
|
332
352
|
if not pause:
|
|
333
|
-
message[
|
|
334
|
-
await self._send_message(
|
|
353
|
+
message["WIRAutomaticallyPause"] = False
|
|
354
|
+
await self._send_message("_rpc_forwardSocketSetup:", message)
|
|
335
355
|
|
|
336
356
|
async def _forward_socket_data(self, session_id: str, app_id: str, page_id: int, data: dict):
|
|
337
|
-
await self._send_message(
|
|
338
|
-
|
|
339
|
-
|
|
340
|
-
|
|
341
|
-
|
|
342
|
-
|
|
343
|
-
|
|
357
|
+
await self._send_message(
|
|
358
|
+
"_rpc_forwardSocketData:",
|
|
359
|
+
{
|
|
360
|
+
"WIRApplicationIdentifierKey": app_id,
|
|
361
|
+
"WIRPageIdentifierKey": page_id,
|
|
362
|
+
"WIRSessionIdentifierKey": session_id,
|
|
363
|
+
"WIRSenderKey": session_id,
|
|
364
|
+
"WIRSocketDataKey": json.dumps(data).encode(),
|
|
365
|
+
},
|
|
366
|
+
)
|
|
344
367
|
|
|
345
368
|
async def _forward_indicate_web_view(self, app_id: str, page_id: int, enable: bool):
|
|
346
|
-
|
|
347
|
-
|
|
348
|
-
|
|
349
|
-
|
|
350
|
-
|
|
369
|
+
(
|
|
370
|
+
await self._send_message("_rpc_forwardIndicateWebView"),
|
|
371
|
+
{
|
|
372
|
+
"WIRApplicationIdentifierKey": app_id,
|
|
373
|
+
"WIRPageIdentifierKey": page_id,
|
|
374
|
+
"WIRIndicateEnabledKey": enable,
|
|
375
|
+
},
|
|
376
|
+
)
|
|
351
377
|
|
|
352
378
|
async def _send_message(self, selector: str, args=None):
|
|
353
379
|
if args is None:
|
|
354
380
|
args = {}
|
|
355
|
-
args[
|
|
356
|
-
await self.service.aio_send_plist({
|
|
381
|
+
args["WIRConnectionIdentifierKey"] = self.connection_id
|
|
382
|
+
await self.service.aio_send_plist({"__selector": selector, "__argument": args})
|
|
357
383
|
|
|
358
384
|
def _page_by_automation_session(self, session_id: str) -> Page:
|
|
359
385
|
for app_id in self.application_pages:
|
|
@@ -369,7 +395,7 @@ class WebinspectorService:
|
|
|
369
395
|
return page
|
|
370
396
|
await asyncio.sleep(0)
|
|
371
397
|
|
|
372
|
-
async def _wait_for_application(self, bundle: str =
|
|
398
|
+
async def _wait_for_application(self, bundle: str = "", app_id: str = "") -> Application:
|
|
373
399
|
while True:
|
|
374
400
|
for app in self.connected_application.values():
|
|
375
401
|
if bundle and app.bundle == bundle:
|
pymobiledevice3/tcp_forwarder.py
CHANGED
|
@@ -19,7 +19,7 @@ class TcpForwarderBase:
|
|
|
19
19
|
MAX_FORWARDED_CONNECTIONS = 200
|
|
20
20
|
TIMEOUT = 1
|
|
21
21
|
|
|
22
|
-
def __init__(self, src_port: int, listening_event: threading.Event = None):
|
|
22
|
+
def __init__(self, src_port: int, listening_event: Optional[threading.Event] = None):
|
|
23
23
|
"""
|
|
24
24
|
Initialize a new tcp forwarder
|
|
25
25
|
|
|
@@ -38,8 +38,8 @@ class TcpForwarderBase:
|
|
|
38
38
|
# socket to its remote socket and vice versa
|
|
39
39
|
self.connections = {}
|
|
40
40
|
|
|
41
|
-
def start(self, address=
|
|
42
|
-
"""
|
|
41
|
+
def start(self, address="0.0.0.0"):
|
|
42
|
+
"""forward each connection from given local machine port to remote device port"""
|
|
43
43
|
# create local tcp server socket
|
|
44
44
|
self.server_socket = socket.socket()
|
|
45
45
|
self.server_socket.setsockopt(socket.SOL_SOCKET, socket.SO_REUSEADDR, 1)
|
|
@@ -51,40 +51,41 @@ class TcpForwarderBase:
|
|
|
51
51
|
if self.listening_event:
|
|
52
52
|
self.listening_event.set()
|
|
53
53
|
|
|
54
|
-
|
|
55
|
-
|
|
56
|
-
|
|
57
|
-
|
|
58
|
-
|
|
59
|
-
self.
|
|
60
|
-
|
|
61
|
-
|
|
62
|
-
|
|
63
|
-
|
|
64
|
-
|
|
65
|
-
|
|
66
|
-
self.
|
|
67
|
-
|
|
68
|
-
if current_sock not in closed_sockets:
|
|
69
|
-
try:
|
|
70
|
-
self._handle_data(current_sock, closed_sockets)
|
|
71
|
-
except ConnectionResetError:
|
|
72
|
-
self.logger.exception("Error when handling data")
|
|
73
|
-
self._handle_close_or_error(current_sock)
|
|
54
|
+
try:
|
|
55
|
+
while self.inputs:
|
|
56
|
+
# will only perform the socket select on the inputs. the outputs will handled
|
|
57
|
+
# as synchronous blocking
|
|
58
|
+
readable, _writable, exceptional = select.select(self.inputs, [], self.inputs, self.TIMEOUT)
|
|
59
|
+
if self.stopped.is_set():
|
|
60
|
+
self.logger.debug("Closing since stopped is set")
|
|
61
|
+
break
|
|
62
|
+
|
|
63
|
+
closed_sockets = set()
|
|
64
|
+
for current_sock in readable:
|
|
65
|
+
self.logger.debug("Processing %r", current_sock)
|
|
66
|
+
if current_sock is self.server_socket:
|
|
67
|
+
self._handle_server_connection()
|
|
74
68
|
else:
|
|
75
|
-
|
|
76
|
-
|
|
77
|
-
|
|
78
|
-
|
|
79
|
-
|
|
80
|
-
|
|
81
|
-
|
|
82
|
-
|
|
83
|
-
|
|
84
|
-
|
|
69
|
+
if current_sock not in closed_sockets:
|
|
70
|
+
try:
|
|
71
|
+
self._handle_data(current_sock, closed_sockets)
|
|
72
|
+
except ConnectionResetError:
|
|
73
|
+
self.logger.error("Error when handling data")
|
|
74
|
+
self._handle_close_or_error(current_sock)
|
|
75
|
+
else:
|
|
76
|
+
self.logger.debug("Is closed")
|
|
77
|
+
|
|
78
|
+
for current_sock in exceptional:
|
|
79
|
+
self.logger.error("Sock failed: %r", current_sock)
|
|
80
|
+
self._handle_close_or_error(current_sock)
|
|
81
|
+
finally:
|
|
82
|
+
self.logger.info("Closing everything")
|
|
83
|
+
# on stop, close all currently opened sockets
|
|
84
|
+
for current_sock in self.inputs:
|
|
85
|
+
current_sock.close()
|
|
85
86
|
|
|
86
87
|
def _handle_close_or_error(self, from_sock):
|
|
87
|
-
"""
|
|
88
|
+
"""if an error occurred its time to close the two sockets"""
|
|
88
89
|
other_sock = self.connections[from_sock]
|
|
89
90
|
|
|
90
91
|
other_sock.close()
|
|
@@ -92,7 +93,7 @@ class TcpForwarderBase:
|
|
|
92
93
|
self.inputs.remove(other_sock)
|
|
93
94
|
self.inputs.remove(from_sock)
|
|
94
95
|
|
|
95
|
-
self.logger.info(f
|
|
96
|
+
self.logger.info(f"connection {other_sock} was closed")
|
|
96
97
|
|
|
97
98
|
def _handle_data(self, from_sock, closed_sockets):
|
|
98
99
|
self.logger.debug(f"Handling data from {from_sock}")
|
|
@@ -103,8 +104,8 @@ class TcpForwarderBase:
|
|
|
103
104
|
except BlockingIOError:
|
|
104
105
|
self.logger.warning(f"Non-blocking read failed on {from_sock}, retrying later.")
|
|
105
106
|
return
|
|
106
|
-
except OSError
|
|
107
|
-
self.logger.error(f"Error reading from socket {from_sock}
|
|
107
|
+
except OSError:
|
|
108
|
+
self.logger.error(f"Error reading from socket {from_sock}")
|
|
108
109
|
self._handle_close_or_error(from_sock)
|
|
109
110
|
closed_sockets.add(from_sock)
|
|
110
111
|
return
|
|
@@ -126,8 +127,8 @@ class TcpForwarderBase:
|
|
|
126
127
|
except BrokenPipeError:
|
|
127
128
|
self.logger.error(f"Broken pipe error on {other_sock}.")
|
|
128
129
|
raise
|
|
129
|
-
except OSError
|
|
130
|
-
self.logger.error(
|
|
130
|
+
except OSError:
|
|
131
|
+
self.logger.error("Unhandled error while forwarding data")
|
|
131
132
|
self._handle_close_or_error(from_sock)
|
|
132
133
|
closed_sockets.add(from_sock)
|
|
133
134
|
closed_sockets.add(other_sock)
|
|
@@ -137,14 +138,14 @@ class TcpForwarderBase:
|
|
|
137
138
|
pass
|
|
138
139
|
|
|
139
140
|
def _handle_server_connection(self):
|
|
140
|
-
"""
|
|
141
|
-
local_connection,
|
|
141
|
+
"""accept the connection from local machine and attempt to connect at remote"""
|
|
142
|
+
local_connection, _client_address = self.server_socket.accept()
|
|
142
143
|
local_connection.setblocking(False)
|
|
143
144
|
|
|
144
145
|
try:
|
|
145
146
|
remote_connection = self._establish_remote_connection()
|
|
146
147
|
except ConnectionFailedError:
|
|
147
|
-
self.logger.error(f
|
|
148
|
+
self.logger.error(f"failed to connect to port: {self.dst_port}")
|
|
148
149
|
local_connection.close()
|
|
149
150
|
return
|
|
150
151
|
|
|
@@ -158,10 +159,10 @@ class TcpForwarderBase:
|
|
|
158
159
|
self.connections[remote_connection] = local_connection
|
|
159
160
|
self.connections[local_connection] = remote_connection
|
|
160
161
|
|
|
161
|
-
self.logger.info(
|
|
162
|
+
self.logger.info("connection established from local to remote")
|
|
162
163
|
|
|
163
164
|
def stop(self):
|
|
164
|
-
"""
|
|
165
|
+
"""stop forwarding"""
|
|
165
166
|
self.stopped.set()
|
|
166
167
|
|
|
167
168
|
|
|
@@ -170,8 +171,15 @@ class UsbmuxTcpForwarder(TcpForwarderBase):
|
|
|
170
171
|
Allows forwarding local tcp connection into the device via a given lockdown connection
|
|
171
172
|
"""
|
|
172
173
|
|
|
173
|
-
def __init__(
|
|
174
|
-
|
|
174
|
+
def __init__(
|
|
175
|
+
self,
|
|
176
|
+
serial: str,
|
|
177
|
+
dst_port: int,
|
|
178
|
+
src_port: int,
|
|
179
|
+
listening_event: Optional[threading.Event] = None,
|
|
180
|
+
usbmux_connection_type: Optional[str] = None,
|
|
181
|
+
usbmux_address: Optional[str] = None,
|
|
182
|
+
):
|
|
175
183
|
"""
|
|
176
184
|
Initialize a new tcp forwarder
|
|
177
185
|
|
|
@@ -190,8 +198,9 @@ class UsbmuxTcpForwarder(TcpForwarderBase):
|
|
|
190
198
|
|
|
191
199
|
def _establish_remote_connection(self) -> socket.socket:
|
|
192
200
|
# connect directly using usbmuxd
|
|
193
|
-
mux_device = usbmux.select_device(
|
|
194
|
-
|
|
201
|
+
mux_device = usbmux.select_device(
|
|
202
|
+
self.serial, connection_type=self.usbmux_connection_type, usbmux_address=self.usbmux_address
|
|
203
|
+
)
|
|
195
204
|
self.logger.debug("Selected device: %r", mux_device)
|
|
196
205
|
if mux_device is None:
|
|
197
206
|
raise ConnectionFailedError()
|
|
@@ -203,8 +212,13 @@ class LockdownTcpForwarder(TcpForwarderBase):
|
|
|
203
212
|
Allows forwarding local tcp connection into the device via a given lockdown connection
|
|
204
213
|
"""
|
|
205
214
|
|
|
206
|
-
def __init__(
|
|
207
|
-
|
|
215
|
+
def __init__(
|
|
216
|
+
self,
|
|
217
|
+
service_provider: LockdownServiceProvider,
|
|
218
|
+
src_port: int,
|
|
219
|
+
service_name: str,
|
|
220
|
+
listening_event: Optional[threading.Event] = None,
|
|
221
|
+
):
|
|
208
222
|
"""
|
|
209
223
|
Initialize a new tcp forwarder
|
|
210
224
|
|