pymobiledevice3 5.0.1__py3-none-any.whl → 5.0.3__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.
Potentially problematic release.
This version of pymobiledevice3 might be problematic. Click here for more details.
- misc/plist_sniffer.py +15 -15
- misc/remotexpc_sniffer.py +29 -28
- pymobiledevice3/__main__.py +128 -102
- pymobiledevice3/_version.py +2 -2
- pymobiledevice3/bonjour.py +36 -59
- 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 +25 -18
- 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 +602 -520
- pymobiledevice3/cli/diagnostics.py +38 -33
- pymobiledevice3/cli/lockdown.py +79 -74
- pymobiledevice3/cli/mounter.py +85 -68
- 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 +186 -110
- 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 +157 -79
- pymobiledevice3/common.py +1 -1
- pymobiledevice3/exceptions.py +154 -60
- pymobiledevice3/irecv.py +49 -53
- pymobiledevice3/irecv_devices.py +1489 -492
- pymobiledevice3/lockdown.py +396 -242
- 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 +371 -293
- pymobiledevice3/remote/utils.py +12 -11
- 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 +35 -54
- pymobiledevice3/restore/recovery.py +125 -135
- pymobiledevice3/restore/restore.py +524 -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 +350 -291
- pymobiledevice3/services/amfi.py +21 -18
- pymobiledevice3/services/companion.py +23 -19
- pymobiledevice3/services/crash_reports.py +60 -46
- 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 +442 -421
- 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 +330 -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 +69 -51
- 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 +129 -117
- pymobiledevice3/tcp_forwarder.py +35 -22
- pymobiledevice3/tunneld/api.py +20 -15
- pymobiledevice3/tunneld/server.py +212 -133
- pymobiledevice3/usbmux.py +183 -138
- pymobiledevice3/utils.py +14 -11
- {pymobiledevice3-5.0.1.dist-info → pymobiledevice3-5.0.3.dist-info}/METADATA +1 -1
- pymobiledevice3-5.0.3.dist-info/RECORD +173 -0
- pymobiledevice3-5.0.1.dist-info/RECORD +0 -173
- {pymobiledevice3-5.0.1.dist-info → pymobiledevice3-5.0.3.dist-info}/WHEEL +0 -0
- {pymobiledevice3-5.0.1.dist-info → pymobiledevice3-5.0.3.dist-info}/entry_points.txt +0 -0
- {pymobiledevice3-5.0.1.dist-info → pymobiledevice3-5.0.3.dist-info}/licenses/LICENSE +0 -0
- {pymobiledevice3-5.0.1.dist-info → pymobiledevice3-5.0.3.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 Any,
|
|
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,18 +147,18 @@ 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:
|
|
@@ -164,10 +169,8 @@ class WebinspectorService:
|
|
|
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 = {}
|
|
@@ -226,8 +231,8 @@ class WebinspectorService:
|
|
|
226
231
|
self.get_open_pages()
|
|
227
232
|
try:
|
|
228
233
|
return self._await_with_timeout(self._wait_for_application(bundle), timeout)
|
|
229
|
-
except TimeoutError:
|
|
230
|
-
raise LaunchingApplicationError()
|
|
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)
|
|
@@ -247,29 +252,27 @@ class WebinspectorService:
|
|
|
247
252
|
def await_(self, awaitable: Coroutine) -> Any:
|
|
248
253
|
return self.loop.run_until_complete(awaitable)
|
|
249
254
|
|
|
250
|
-
def _await_with_timeout(self, coro: Coroutine, timeout: float = None) -> Any:
|
|
255
|
+
def _await_with_timeout(self, coro: Coroutine, timeout: Optional[float] = None) -> Any:
|
|
251
256
|
# Create a task explicitly so we're definitely inside a Task
|
|
252
257
|
task = self.loop.create_task(coro)
|
|
253
|
-
done,
|
|
258
|
+
done, _pending = self.loop.run_until_complete(asyncio.wait({task}, timeout=timeout))
|
|
254
259
|
if not done:
|
|
255
260
|
task.cancel()
|
|
256
261
|
# Give the task a chance to cancel cleanly
|
|
257
|
-
|
|
262
|
+
with contextlib.suppress(asyncio.CancelledError):
|
|
258
263
|
self.loop.run_until_complete(task)
|
|
259
|
-
except asyncio.CancelledError:
|
|
260
|
-
pass
|
|
261
264
|
raise WebInspectorNotEnabledError()
|
|
262
265
|
return task.result()
|
|
263
266
|
|
|
264
267
|
def _handle_recv(self, plist):
|
|
265
|
-
self.receive_handlers[plist[
|
|
268
|
+
self.receive_handlers[plist["__selector"]](plist["__argument"])
|
|
266
269
|
|
|
267
270
|
def _handle_report_current_state(self, arg):
|
|
268
|
-
self.state = arg[
|
|
271
|
+
self.state = arg["WIRAutomationAvailabilityKey"]
|
|
269
272
|
|
|
270
273
|
def _handle_report_connected_application_list(self, arg):
|
|
271
274
|
self.connected_application = {}
|
|
272
|
-
for key, application in arg[
|
|
275
|
+
for key, application in arg["WIRApplicationDictionaryKey"].items():
|
|
273
276
|
self.connected_application[key] = Application.from_application_dictionary(application)
|
|
274
277
|
|
|
275
278
|
# Immediately also query the application pages
|
|
@@ -279,19 +282,19 @@ class WebinspectorService:
|
|
|
279
282
|
pass
|
|
280
283
|
|
|
281
284
|
def _handle_application_sent_listing(self, arg):
|
|
282
|
-
if arg[
|
|
285
|
+
if arg["WIRApplicationIdentifierKey"] in self.application_pages:
|
|
283
286
|
# Update existing application pages
|
|
284
|
-
for id_, page in arg[
|
|
285
|
-
if id_ in self.application_pages[arg[
|
|
286
|
-
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)
|
|
287
290
|
else:
|
|
288
|
-
self.application_pages[arg[
|
|
291
|
+
self.application_pages[arg["WIRApplicationIdentifierKey"]][id_] = Page.from_page_dictionary(page)
|
|
289
292
|
else:
|
|
290
293
|
# Add new application pages
|
|
291
294
|
pages = {}
|
|
292
|
-
for id_, page in arg[
|
|
295
|
+
for id_, page in arg["WIRListingKey"].items():
|
|
293
296
|
pages[id_] = Page.from_page_dictionary(page)
|
|
294
|
-
self.application_pages[arg[
|
|
297
|
+
self.application_pages[arg["WIRApplicationIdentifierKey"]] = pages
|
|
295
298
|
|
|
296
299
|
def _handle_application_updated(self, arg):
|
|
297
300
|
app = Application.from_application_dictionary(arg)
|
|
@@ -302,72 +305,81 @@ class WebinspectorService:
|
|
|
302
305
|
self.connected_application[app.id_] = app
|
|
303
306
|
|
|
304
307
|
def _handle_application_sent_data(self, arg):
|
|
305
|
-
response = json.loads(arg[
|
|
308
|
+
response = json.loads(arg["WIRMessageDataKey"])
|
|
306
309
|
|
|
307
|
-
if
|
|
308
|
-
self.wir_message_results[response[
|
|
310
|
+
if "id" in response:
|
|
311
|
+
self.wir_message_results[response["id"]] = response
|
|
309
312
|
else:
|
|
310
313
|
self.wir_events.append(response)
|
|
311
314
|
|
|
312
315
|
def _handle_application_disconnected(self, arg):
|
|
313
|
-
self.connected_application.pop(arg[
|
|
314
|
-
self.application_pages.pop(arg[
|
|
316
|
+
self.connected_application.pop(arg["WIRApplicationIdentifierKey"], None)
|
|
317
|
+
self.application_pages.pop(arg["WIRApplicationIdentifierKey"], None)
|
|
315
318
|
|
|
316
319
|
async def _report_identifier(self):
|
|
317
|
-
await self._send_message(
|
|
320
|
+
await self._send_message("_rpc_reportIdentifier:")
|
|
318
321
|
|
|
319
322
|
async def _forward_get_listing(self, app_id):
|
|
320
|
-
self.logger.debug(f
|
|
321
|
-
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})
|
|
322
325
|
|
|
323
326
|
async def _request_application_launch(self, bundle: str):
|
|
324
|
-
await self._send_message(
|
|
327
|
+
await self._send_message("_rpc_requestApplicationLaunch:", {"WIRApplicationBundleIdentifierKey": bundle})
|
|
325
328
|
|
|
326
329
|
async def _get_connected_applications(self) -> None:
|
|
327
|
-
await self._send_message(
|
|
330
|
+
await self._send_message("_rpc_getConnectedApplications:", {})
|
|
328
331
|
|
|
329
332
|
async def _forward_automation_session_request(self, session_id: str, app_id: str):
|
|
330
|
-
await self._send_message(
|
|
331
|
-
|
|
332
|
-
|
|
333
|
-
|
|
334
|
-
|
|
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,
|
|
335
342
|
},
|
|
336
|
-
|
|
337
|
-
})
|
|
343
|
+
)
|
|
338
344
|
|
|
339
345
|
async def _forward_socket_setup(self, session_id: str, app_id: str, page_id: int, pause: bool = True):
|
|
340
346
|
message = {
|
|
341
|
-
|
|
342
|
-
|
|
343
|
-
|
|
344
|
-
|
|
347
|
+
"WIRApplicationIdentifierKey": app_id,
|
|
348
|
+
"WIRPageIdentifierKey": page_id,
|
|
349
|
+
"WIRSenderKey": session_id,
|
|
350
|
+
"WIRMessageDataTypeChunkSupportedKey": 0,
|
|
345
351
|
}
|
|
346
352
|
if not pause:
|
|
347
|
-
message[
|
|
348
|
-
await self._send_message(
|
|
353
|
+
message["WIRAutomaticallyPause"] = False
|
|
354
|
+
await self._send_message("_rpc_forwardSocketSetup:", message)
|
|
349
355
|
|
|
350
356
|
async def _forward_socket_data(self, session_id: str, app_id: str, page_id: int, data: dict):
|
|
351
|
-
await self._send_message(
|
|
352
|
-
|
|
353
|
-
|
|
354
|
-
|
|
355
|
-
|
|
356
|
-
|
|
357
|
-
|
|
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
|
+
)
|
|
358
367
|
|
|
359
368
|
async def _forward_indicate_web_view(self, app_id: str, page_id: int, enable: bool):
|
|
360
|
-
|
|
361
|
-
|
|
362
|
-
|
|
363
|
-
|
|
364
|
-
|
|
369
|
+
(
|
|
370
|
+
await self._send_message("_rpc_forwardIndicateWebView"),
|
|
371
|
+
{
|
|
372
|
+
"WIRApplicationIdentifierKey": app_id,
|
|
373
|
+
"WIRPageIdentifierKey": page_id,
|
|
374
|
+
"WIRIndicateEnabledKey": enable,
|
|
375
|
+
},
|
|
376
|
+
)
|
|
365
377
|
|
|
366
378
|
async def _send_message(self, selector: str, args=None):
|
|
367
379
|
if args is None:
|
|
368
380
|
args = {}
|
|
369
|
-
args[
|
|
370
|
-
await self.service.aio_send_plist({
|
|
381
|
+
args["WIRConnectionIdentifierKey"] = self.connection_id
|
|
382
|
+
await self.service.aio_send_plist({"__selector": selector, "__argument": args})
|
|
371
383
|
|
|
372
384
|
def _page_by_automation_session(self, session_id: str) -> Page:
|
|
373
385
|
for app_id in self.application_pages:
|
|
@@ -383,7 +395,7 @@ class WebinspectorService:
|
|
|
383
395
|
return page
|
|
384
396
|
await asyncio.sleep(0)
|
|
385
397
|
|
|
386
|
-
async def _wait_for_application(self, bundle: str =
|
|
398
|
+
async def _wait_for_application(self, bundle: str = "", app_id: str = "") -> Application:
|
|
387
399
|
while True:
|
|
388
400
|
for app in self.connected_application.values():
|
|
389
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)
|
|
@@ -55,7 +55,7 @@ class TcpForwarderBase:
|
|
|
55
55
|
while self.inputs:
|
|
56
56
|
# will only perform the socket select on the inputs. the outputs will handled
|
|
57
57
|
# as synchronous blocking
|
|
58
|
-
readable,
|
|
58
|
+
readable, _writable, exceptional = select.select(self.inputs, [], self.inputs, self.TIMEOUT)
|
|
59
59
|
if self.stopped.is_set():
|
|
60
60
|
self.logger.debug("Closing since stopped is set")
|
|
61
61
|
break
|
|
@@ -85,7 +85,7 @@ class TcpForwarderBase:
|
|
|
85
85
|
current_sock.close()
|
|
86
86
|
|
|
87
87
|
def _handle_close_or_error(self, from_sock):
|
|
88
|
-
"""
|
|
88
|
+
"""if an error occurred its time to close the two sockets"""
|
|
89
89
|
other_sock = self.connections[from_sock]
|
|
90
90
|
|
|
91
91
|
other_sock.close()
|
|
@@ -93,7 +93,7 @@ class TcpForwarderBase:
|
|
|
93
93
|
self.inputs.remove(other_sock)
|
|
94
94
|
self.inputs.remove(from_sock)
|
|
95
95
|
|
|
96
|
-
self.logger.info(f
|
|
96
|
+
self.logger.info(f"connection {other_sock} was closed")
|
|
97
97
|
|
|
98
98
|
def _handle_data(self, from_sock, closed_sockets):
|
|
99
99
|
self.logger.debug(f"Handling data from {from_sock}")
|
|
@@ -104,8 +104,8 @@ class TcpForwarderBase:
|
|
|
104
104
|
except BlockingIOError:
|
|
105
105
|
self.logger.warning(f"Non-blocking read failed on {from_sock}, retrying later.")
|
|
106
106
|
return
|
|
107
|
-
except OSError
|
|
108
|
-
self.logger.
|
|
107
|
+
except OSError:
|
|
108
|
+
self.logger.exception(f"Error reading from socket {from_sock}")
|
|
109
109
|
self._handle_close_or_error(from_sock)
|
|
110
110
|
closed_sockets.add(from_sock)
|
|
111
111
|
return
|
|
@@ -125,10 +125,10 @@ class TcpForwarderBase:
|
|
|
125
125
|
self.logger.warning(f"Socket buffer full for {other_sock}, retrying in 100ms.")
|
|
126
126
|
time.sleep(0.1) # Introduce a small delay
|
|
127
127
|
except BrokenPipeError:
|
|
128
|
-
self.logger.
|
|
128
|
+
self.logger.exception(f"Broken pipe error on {other_sock}.")
|
|
129
129
|
raise
|
|
130
|
-
except OSError
|
|
131
|
-
self.logger.
|
|
130
|
+
except OSError:
|
|
131
|
+
self.logger.exception("Unhandled error while forwarding data")
|
|
132
132
|
self._handle_close_or_error(from_sock)
|
|
133
133
|
closed_sockets.add(from_sock)
|
|
134
134
|
closed_sockets.add(other_sock)
|
|
@@ -138,14 +138,14 @@ class TcpForwarderBase:
|
|
|
138
138
|
pass
|
|
139
139
|
|
|
140
140
|
def _handle_server_connection(self):
|
|
141
|
-
"""
|
|
142
|
-
local_connection,
|
|
141
|
+
"""accept the connection from local machine and attempt to connect at remote"""
|
|
142
|
+
local_connection, _client_address = self.server_socket.accept()
|
|
143
143
|
local_connection.setblocking(False)
|
|
144
144
|
|
|
145
145
|
try:
|
|
146
146
|
remote_connection = self._establish_remote_connection()
|
|
147
147
|
except ConnectionFailedError:
|
|
148
|
-
self.logger.
|
|
148
|
+
self.logger.exception(f"failed to connect to port: {self.dst_port}")
|
|
149
149
|
local_connection.close()
|
|
150
150
|
return
|
|
151
151
|
|
|
@@ -159,10 +159,10 @@ class TcpForwarderBase:
|
|
|
159
159
|
self.connections[remote_connection] = local_connection
|
|
160
160
|
self.connections[local_connection] = remote_connection
|
|
161
161
|
|
|
162
|
-
self.logger.info(
|
|
162
|
+
self.logger.info("connection established from local to remote")
|
|
163
163
|
|
|
164
164
|
def stop(self):
|
|
165
|
-
"""
|
|
165
|
+
"""stop forwarding"""
|
|
166
166
|
self.stopped.set()
|
|
167
167
|
|
|
168
168
|
|
|
@@ -171,8 +171,15 @@ class UsbmuxTcpForwarder(TcpForwarderBase):
|
|
|
171
171
|
Allows forwarding local tcp connection into the device via a given lockdown connection
|
|
172
172
|
"""
|
|
173
173
|
|
|
174
|
-
def __init__(
|
|
175
|
-
|
|
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
|
+
):
|
|
176
183
|
"""
|
|
177
184
|
Initialize a new tcp forwarder
|
|
178
185
|
|
|
@@ -191,8 +198,9 @@ class UsbmuxTcpForwarder(TcpForwarderBase):
|
|
|
191
198
|
|
|
192
199
|
def _establish_remote_connection(self) -> socket.socket:
|
|
193
200
|
# connect directly using usbmuxd
|
|
194
|
-
mux_device = usbmux.select_device(
|
|
195
|
-
|
|
201
|
+
mux_device = usbmux.select_device(
|
|
202
|
+
self.serial, connection_type=self.usbmux_connection_type, usbmux_address=self.usbmux_address
|
|
203
|
+
)
|
|
196
204
|
self.logger.debug("Selected device: %r", mux_device)
|
|
197
205
|
if mux_device is None:
|
|
198
206
|
raise ConnectionFailedError()
|
|
@@ -204,8 +212,13 @@ class LockdownTcpForwarder(TcpForwarderBase):
|
|
|
204
212
|
Allows forwarding local tcp connection into the device via a given lockdown connection
|
|
205
213
|
"""
|
|
206
214
|
|
|
207
|
-
def __init__(
|
|
208
|
-
|
|
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
|
+
):
|
|
209
222
|
"""
|
|
210
223
|
Initialize a new tcp forwarder
|
|
211
224
|
|
pymobiledevice3/tunneld/api.py
CHANGED
|
@@ -6,22 +6,25 @@ from pymobiledevice3.exceptions import TunneldConnectionError
|
|
|
6
6
|
from pymobiledevice3.remote.remote_service_discovery import RemoteServiceDiscoveryService
|
|
7
7
|
from pymobiledevice3.utils import get_asyncio_loop
|
|
8
8
|
|
|
9
|
-
TUNNELD_DEFAULT_ADDRESS = (
|
|
9
|
+
TUNNELD_DEFAULT_ADDRESS = ("127.0.0.1", 49151)
|
|
10
10
|
|
|
11
11
|
|
|
12
|
-
async def async_get_tunneld_devices(
|
|
13
|
-
|
|
12
|
+
async def async_get_tunneld_devices(
|
|
13
|
+
tunneld_address: tuple[str, int] = TUNNELD_DEFAULT_ADDRESS,
|
|
14
|
+
) -> list[RemoteServiceDiscoveryService]:
|
|
14
15
|
tunnels = _list_tunnels(tunneld_address)
|
|
15
16
|
return await _create_rsds_from_tunnels(tunnels)
|
|
16
17
|
|
|
17
18
|
|
|
18
|
-
def get_tunneld_devices(
|
|
19
|
-
|
|
19
|
+
def get_tunneld_devices(
|
|
20
|
+
tunneld_address: tuple[str, int] = TUNNELD_DEFAULT_ADDRESS,
|
|
21
|
+
) -> list[RemoteServiceDiscoveryService]:
|
|
20
22
|
return get_asyncio_loop().run_until_complete(async_get_tunneld_devices(tunneld_address))
|
|
21
23
|
|
|
22
24
|
|
|
23
|
-
async def async_get_tunneld_device_by_udid(
|
|
24
|
-
|
|
25
|
+
async def async_get_tunneld_device_by_udid(
|
|
26
|
+
udid: str, tunneld_address: tuple[str, int] = TUNNELD_DEFAULT_ADDRESS
|
|
27
|
+
) -> Optional[RemoteServiceDiscoveryService]:
|
|
25
28
|
tunnels = _list_tunnels(tunneld_address)
|
|
26
29
|
if udid not in tunnels:
|
|
27
30
|
return None
|
|
@@ -29,27 +32,29 @@ async def async_get_tunneld_device_by_udid(udid: str, tunneld_address: tuple[str
|
|
|
29
32
|
return rsds[0]
|
|
30
33
|
|
|
31
34
|
|
|
32
|
-
def get_tunneld_device_by_udid(
|
|
33
|
-
|
|
35
|
+
def get_tunneld_device_by_udid(
|
|
36
|
+
udid: str, tunneld_address: tuple[str, int] = TUNNELD_DEFAULT_ADDRESS
|
|
37
|
+
) -> Optional[RemoteServiceDiscoveryService]:
|
|
34
38
|
return get_asyncio_loop().run_until_complete(async_get_tunneld_device_by_udid(udid, tunneld_address))
|
|
35
39
|
|
|
36
40
|
|
|
37
41
|
def _list_tunnels(tunneld_address: tuple[str, int] = TUNNELD_DEFAULT_ADDRESS) -> dict[str, list[dict]]:
|
|
38
42
|
try:
|
|
39
43
|
# Get the list of tunnels from the specified address
|
|
40
|
-
resp = requests.get(f
|
|
44
|
+
resp = requests.get(f"http://{tunneld_address[0]}:{tunneld_address[1]}")
|
|
41
45
|
tunnels = resp.json()
|
|
42
|
-
except requests.exceptions.ConnectionError:
|
|
43
|
-
raise TunneldConnectionError()
|
|
46
|
+
except requests.exceptions.ConnectionError as e:
|
|
47
|
+
raise TunneldConnectionError() from e
|
|
44
48
|
return tunnels
|
|
45
49
|
|
|
46
50
|
|
|
47
51
|
async def _create_rsds_from_tunnels(tunnels: dict[str, list[dict]]) -> list[RemoteServiceDiscoveryService]:
|
|
48
52
|
rsds = []
|
|
49
|
-
for
|
|
53
|
+
for _udid, details in tunnels.items():
|
|
50
54
|
for tunnel_details in details:
|
|
51
|
-
rsd = RemoteServiceDiscoveryService(
|
|
52
|
-
|
|
55
|
+
rsd = RemoteServiceDiscoveryService(
|
|
56
|
+
(tunnel_details["tunnel-address"], tunnel_details["tunnel-port"]), name=tunnel_details["interface"]
|
|
57
|
+
)
|
|
53
58
|
try:
|
|
54
59
|
await rsd.connect()
|
|
55
60
|
rsds.append(rsd)
|