pymobiledevice3 5.0.1__py3-none-any.whl → 5.0.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.
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 +26 -49
- 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 +394 -241
- 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 +62 -41
- 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 +136 -126
- 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 +55 -47
- 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 +40 -50
- 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 +180 -176
- 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 +2 -2
- pymobiledevice3/services/web_protocol/session_protocol.py +15 -10
- pymobiledevice3/services/web_protocol/switch_to.py +11 -12
- pymobiledevice3/services/webinspector.py +127 -116
- 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.2.dist-info}/METADATA +1 -1
- pymobiledevice3-5.0.2.dist-info/RECORD +173 -0
- pymobiledevice3-5.0.1.dist-info/RECORD +0 -173
- {pymobiledevice3-5.0.1.dist-info → pymobiledevice3-5.0.2.dist-info}/WHEEL +0 -0
- {pymobiledevice3-5.0.1.dist-info → pymobiledevice3-5.0.2.dist-info}/entry_points.txt +0 -0
- {pymobiledevice3-5.0.1.dist-info → pymobiledevice3-5.0.2.dist-info}/licenses/LICENSE +0 -0
- {pymobiledevice3-5.0.1.dist-info → pymobiledevice3-5.0.2.dist-info}/top_level.txt +0 -0
|
@@ -1,4 +1,5 @@
|
|
|
1
1
|
import asyncio
|
|
2
|
+
import contextlib
|
|
2
3
|
import json
|
|
3
4
|
import logging
|
|
4
5
|
import uuid
|
|
@@ -8,8 +9,11 @@ from typing import Any, Coroutine, Optional, Union
|
|
|
8
9
|
|
|
9
10
|
import nest_asyncio
|
|
10
11
|
|
|
11
|
-
from pymobiledevice3.exceptions import
|
|
12
|
-
|
|
12
|
+
from pymobiledevice3.exceptions import (
|
|
13
|
+
LaunchingApplicationError,
|
|
14
|
+
RemoteAutomationNotEnabledError,
|
|
15
|
+
WebInspectorNotEnabledError,
|
|
16
|
+
)
|
|
13
17
|
from pymobiledevice3.lockdown import LockdownClient
|
|
14
18
|
from pymobiledevice3.lockdown_service_provider import LockdownServiceProvider
|
|
15
19
|
from pymobiledevice3.service_connection import ServiceConnection
|
|
@@ -17,55 +21,55 @@ from pymobiledevice3.services.web_protocol.automation_session import AutomationS
|
|
|
17
21
|
from pymobiledevice3.services.web_protocol.inspector_session import InspectorSession
|
|
18
22
|
from pymobiledevice3.services.web_protocol.session_protocol import SessionProtocol
|
|
19
23
|
|
|
20
|
-
SAFARI =
|
|
24
|
+
SAFARI = "com.apple.mobilesafari"
|
|
21
25
|
|
|
22
26
|
|
|
23
27
|
def key_to_pid(key: str) -> int:
|
|
24
|
-
return int(key.split(
|
|
28
|
+
return int(key.split(":")[1])
|
|
25
29
|
|
|
26
30
|
|
|
27
31
|
class WirTypes(Enum):
|
|
28
|
-
AUTOMATION =
|
|
29
|
-
ITML =
|
|
30
|
-
JAVASCRIPT =
|
|
31
|
-
PAGE =
|
|
32
|
-
SERVICE_WORKER =
|
|
33
|
-
WEB =
|
|
34
|
-
WEB_PAGE =
|
|
35
|
-
AUTOMATICALLY_PAUSE =
|
|
32
|
+
AUTOMATION = "WIRTypeAutomation"
|
|
33
|
+
ITML = "WIRTypeITML"
|
|
34
|
+
JAVASCRIPT = "WIRTypeJavaScript"
|
|
35
|
+
PAGE = "WIRTypePage"
|
|
36
|
+
SERVICE_WORKER = "WIRTypeServiceWorker"
|
|
37
|
+
WEB = "WIRTypeWeb"
|
|
38
|
+
WEB_PAGE = "WIRTypeWebPage"
|
|
39
|
+
AUTOMATICALLY_PAUSE = "WIRAutomaticallyPause"
|
|
36
40
|
|
|
37
41
|
|
|
38
42
|
class AutomationAvailability(Enum):
|
|
39
|
-
NOT_AVAILABLE =
|
|
40
|
-
AVAILABLE =
|
|
41
|
-
UNKNOWN =
|
|
43
|
+
NOT_AVAILABLE = "WIRAutomationAvailabilityNotAvailable"
|
|
44
|
+
AVAILABLE = "WIRAutomationAvailabilityAvailable"
|
|
45
|
+
UNKNOWN = "WIRAutomationAvailabilityUnknown"
|
|
42
46
|
|
|
43
47
|
|
|
44
48
|
@dataclass
|
|
45
49
|
class Page:
|
|
46
50
|
id_: int
|
|
47
51
|
type_: WirTypes
|
|
48
|
-
web_url: str =
|
|
49
|
-
web_title: str =
|
|
52
|
+
web_url: str = ""
|
|
53
|
+
web_title: str = ""
|
|
50
54
|
automation_is_paired_key: bool = False
|
|
51
|
-
automation_name: str =
|
|
52
|
-
automation_version: str =
|
|
53
|
-
automation_session_id: str =
|
|
54
|
-
automation_connection_id: str =
|
|
55
|
+
automation_name: str = ""
|
|
56
|
+
automation_version: str = ""
|
|
57
|
+
automation_session_id: str = ""
|
|
58
|
+
automation_connection_id: str = ""
|
|
55
59
|
|
|
56
60
|
@classmethod
|
|
57
|
-
def from_page_dictionary(cls, page_dict: dict) ->
|
|
58
|
-
p = cls(page_dict[
|
|
61
|
+
def from_page_dictionary(cls, page_dict: dict) -> "Page":
|
|
62
|
+
p = cls(page_dict["WIRPageIdentifierKey"], WirTypes(page_dict["WIRTypeKey"]))
|
|
59
63
|
if p.type_ in (WirTypes.WEB, WirTypes.WEB_PAGE):
|
|
60
|
-
p.web_title = page_dict[
|
|
61
|
-
p.web_url = page_dict[
|
|
64
|
+
p.web_title = page_dict["WIRTitleKey"]
|
|
65
|
+
p.web_url = page_dict["WIRURLKey"]
|
|
62
66
|
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[
|
|
67
|
+
p.automation_is_paired_key = page_dict["WIRAutomationTargetIsPairedKey"]
|
|
68
|
+
p.automation_name = page_dict["WIRAutomationTargetNameKey"]
|
|
69
|
+
p.automation_version = page_dict["WIRAutomationTargetVersionKey"]
|
|
70
|
+
p.automation_session_id = page_dict["WIRSessionIdentifierKey"]
|
|
71
|
+
if "WIRConnectionIdentifierKey" in page_dict:
|
|
72
|
+
p.automation_connection_id = page_dict["WIRConnectionIdentifierKey"]
|
|
69
73
|
return p
|
|
70
74
|
|
|
71
75
|
def update(self, page_dict: dict):
|
|
@@ -74,7 +78,7 @@ class Page:
|
|
|
74
78
|
setattr(self, field.name, getattr(new_p, field.name))
|
|
75
79
|
|
|
76
80
|
def __str__(self):
|
|
77
|
-
return f
|
|
81
|
+
return f"id: {self.id_}, title: {self.web_title}, url: {self.web_url}"
|
|
78
82
|
|
|
79
83
|
|
|
80
84
|
@dataclass
|
|
@@ -87,20 +91,20 @@ class Application:
|
|
|
87
91
|
active: int
|
|
88
92
|
proxy: bool
|
|
89
93
|
ready: bool
|
|
90
|
-
host: str =
|
|
94
|
+
host: str = ""
|
|
91
95
|
|
|
92
96
|
@classmethod
|
|
93
|
-
def from_application_dictionary(cls, app_dict) ->
|
|
97
|
+
def from_application_dictionary(cls, app_dict) -> "Application":
|
|
94
98
|
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(
|
|
99
|
+
app_dict["WIRApplicationIdentifierKey"],
|
|
100
|
+
app_dict["WIRApplicationBundleIdentifierKey"],
|
|
101
|
+
key_to_pid(app_dict["WIRApplicationIdentifierKey"]),
|
|
102
|
+
app_dict["WIRApplicationNameKey"],
|
|
103
|
+
AutomationAvailability(app_dict["WIRAutomationAvailabilityKey"]),
|
|
104
|
+
app_dict["WIRIsApplicationActiveKey"],
|
|
105
|
+
app_dict["WIRIsApplicationProxyKey"],
|
|
106
|
+
app_dict["WIRIsApplicationReadyKey"],
|
|
107
|
+
app_dict.get("WIRHostApplicationIdentifierKey", ""),
|
|
104
108
|
)
|
|
105
109
|
|
|
106
110
|
|
|
@@ -110,12 +114,12 @@ class ApplicationPage:
|
|
|
110
114
|
page: Page
|
|
111
115
|
|
|
112
116
|
def __str__(self) -> str:
|
|
113
|
-
return f
|
|
117
|
+
return f"<{self.application.name}({self.application.pid}) TYPE:{self.page.type_.value} URL:{self.page.web_url}>"
|
|
114
118
|
|
|
115
119
|
|
|
116
120
|
class WebinspectorService:
|
|
117
|
-
SERVICE_NAME =
|
|
118
|
-
RSD_SERVICE_NAME =
|
|
121
|
+
SERVICE_NAME = "com.apple.webinspector"
|
|
122
|
+
RSD_SERVICE_NAME = "com.apple.webinspector.shim.remote"
|
|
119
123
|
|
|
120
124
|
def __init__(self, lockdown: LockdownServiceProvider, loop=None):
|
|
121
125
|
if loop is None:
|
|
@@ -142,18 +146,18 @@ class WebinspectorService:
|
|
|
142
146
|
self.wir_message_results = {}
|
|
143
147
|
self.wir_events = []
|
|
144
148
|
self.receive_handlers = {
|
|
145
|
-
|
|
146
|
-
|
|
147
|
-
|
|
148
|
-
|
|
149
|
-
|
|
150
|
-
|
|
151
|
-
|
|
152
|
-
|
|
149
|
+
"_rpc_reportCurrentState:": self._handle_report_current_state,
|
|
150
|
+
"_rpc_reportConnectedApplicationList:": self._handle_report_connected_application_list,
|
|
151
|
+
"_rpc_reportConnectedDriverList:": self._handle_report_connected_driver_list,
|
|
152
|
+
"_rpc_applicationSentListing:": self._handle_application_sent_listing,
|
|
153
|
+
"_rpc_applicationUpdated:": self._handle_application_updated,
|
|
154
|
+
"_rpc_applicationConnected:": self._handle_application_connected,
|
|
155
|
+
"_rpc_applicationSentData:": self._handle_application_sent_data,
|
|
156
|
+
"_rpc_applicationDisconnected:": self._handle_application_disconnected,
|
|
153
157
|
}
|
|
154
158
|
self._recv_task: Optional[asyncio.Task] = None
|
|
155
159
|
|
|
156
|
-
def connect(self, timeout: Union[float, int] = None):
|
|
160
|
+
def connect(self, timeout: Optional[Union[float, int]] = None):
|
|
157
161
|
self.service = self.await_(self.lockdown.aio_start_lockdown_service(self.service_name))
|
|
158
162
|
self.await_(self._report_identifier())
|
|
159
163
|
try:
|
|
@@ -164,10 +168,8 @@ class WebinspectorService:
|
|
|
164
168
|
|
|
165
169
|
def close(self):
|
|
166
170
|
self._recv_task.cancel()
|
|
167
|
-
|
|
171
|
+
with contextlib.suppress(asyncio.CancelledError):
|
|
168
172
|
self.await_(self._recv_task)
|
|
169
|
-
except asyncio.CancelledError:
|
|
170
|
-
pass
|
|
171
173
|
self.await_(self.service.aio_close())
|
|
172
174
|
|
|
173
175
|
async def _recv_message(self):
|
|
@@ -182,7 +184,7 @@ class WebinspectorService:
|
|
|
182
184
|
self._handle_recv(await self._recv_message())
|
|
183
185
|
|
|
184
186
|
def automation_session(self, app: Application) -> AutomationSession:
|
|
185
|
-
if self.state ==
|
|
187
|
+
if self.state == "WIRAutomationAvailabilityNotAvailable":
|
|
186
188
|
raise RemoteAutomationNotEnabledError()
|
|
187
189
|
session_id = str(uuid.uuid4()).upper()
|
|
188
190
|
self.await_(self._forward_automation_session_request(session_id, app.id_))
|
|
@@ -196,8 +198,10 @@ class WebinspectorService:
|
|
|
196
198
|
|
|
197
199
|
async def inspector_session(self, app: Application, page: Page) -> InspectorSession:
|
|
198
200
|
session_id = str(uuid.uuid4()).upper()
|
|
199
|
-
return await InspectorSession.create(
|
|
200
|
-
|
|
201
|
+
return await InspectorSession.create(
|
|
202
|
+
SessionProtocol(self, session_id, app, page, method_prefix=""),
|
|
203
|
+
wait_target=page.type_ != WirTypes.JAVASCRIPT,
|
|
204
|
+
)
|
|
201
205
|
|
|
202
206
|
def get_open_pages(self) -> dict:
|
|
203
207
|
apps = {}
|
|
@@ -226,8 +230,8 @@ class WebinspectorService:
|
|
|
226
230
|
self.get_open_pages()
|
|
227
231
|
try:
|
|
228
232
|
return self._await_with_timeout(self._wait_for_application(bundle), timeout)
|
|
229
|
-
except TimeoutError:
|
|
230
|
-
raise LaunchingApplicationError()
|
|
233
|
+
except TimeoutError as e:
|
|
234
|
+
raise LaunchingApplicationError() from e
|
|
231
235
|
|
|
232
236
|
async def send_socket_data(self, session_id: str, app_id: str, page_id: int, data: dict):
|
|
233
237
|
await self._forward_socket_data(session_id, app_id, page_id, data)
|
|
@@ -247,29 +251,27 @@ class WebinspectorService:
|
|
|
247
251
|
def await_(self, awaitable: Coroutine) -> Any:
|
|
248
252
|
return self.loop.run_until_complete(awaitable)
|
|
249
253
|
|
|
250
|
-
def _await_with_timeout(self, coro: Coroutine, timeout: float = None) -> Any:
|
|
254
|
+
def _await_with_timeout(self, coro: Coroutine, timeout: Optional[float] = None) -> Any:
|
|
251
255
|
# Create a task explicitly so we're definitely inside a Task
|
|
252
256
|
task = self.loop.create_task(coro)
|
|
253
|
-
done,
|
|
257
|
+
done, _pending = self.loop.run_until_complete(asyncio.wait({task}, timeout=timeout))
|
|
254
258
|
if not done:
|
|
255
259
|
task.cancel()
|
|
256
260
|
# Give the task a chance to cancel cleanly
|
|
257
|
-
|
|
261
|
+
with contextlib.suppress(asyncio.CancelledError):
|
|
258
262
|
self.loop.run_until_complete(task)
|
|
259
|
-
except asyncio.CancelledError:
|
|
260
|
-
pass
|
|
261
263
|
raise WebInspectorNotEnabledError()
|
|
262
264
|
return task.result()
|
|
263
265
|
|
|
264
266
|
def _handle_recv(self, plist):
|
|
265
|
-
self.receive_handlers[plist[
|
|
267
|
+
self.receive_handlers[plist["__selector"]](plist["__argument"])
|
|
266
268
|
|
|
267
269
|
def _handle_report_current_state(self, arg):
|
|
268
|
-
self.state = arg[
|
|
270
|
+
self.state = arg["WIRAutomationAvailabilityKey"]
|
|
269
271
|
|
|
270
272
|
def _handle_report_connected_application_list(self, arg):
|
|
271
273
|
self.connected_application = {}
|
|
272
|
-
for key, application in arg[
|
|
274
|
+
for key, application in arg["WIRApplicationDictionaryKey"].items():
|
|
273
275
|
self.connected_application[key] = Application.from_application_dictionary(application)
|
|
274
276
|
|
|
275
277
|
# Immediately also query the application pages
|
|
@@ -279,19 +281,19 @@ class WebinspectorService:
|
|
|
279
281
|
pass
|
|
280
282
|
|
|
281
283
|
def _handle_application_sent_listing(self, arg):
|
|
282
|
-
if arg[
|
|
284
|
+
if arg["WIRApplicationIdentifierKey"] in self.application_pages:
|
|
283
285
|
# Update existing application pages
|
|
284
|
-
for id_, page in arg[
|
|
285
|
-
if id_ in self.application_pages[arg[
|
|
286
|
-
self.application_pages[arg[
|
|
286
|
+
for id_, page in arg["WIRListingKey"].items():
|
|
287
|
+
if id_ in self.application_pages[arg["WIRApplicationIdentifierKey"]]:
|
|
288
|
+
self.application_pages[arg["WIRApplicationIdentifierKey"]][id_].update(page)
|
|
287
289
|
else:
|
|
288
|
-
self.application_pages[arg[
|
|
290
|
+
self.application_pages[arg["WIRApplicationIdentifierKey"]][id_] = Page.from_page_dictionary(page)
|
|
289
291
|
else:
|
|
290
292
|
# Add new application pages
|
|
291
293
|
pages = {}
|
|
292
|
-
for id_, page in arg[
|
|
294
|
+
for id_, page in arg["WIRListingKey"].items():
|
|
293
295
|
pages[id_] = Page.from_page_dictionary(page)
|
|
294
|
-
self.application_pages[arg[
|
|
296
|
+
self.application_pages[arg["WIRApplicationIdentifierKey"]] = pages
|
|
295
297
|
|
|
296
298
|
def _handle_application_updated(self, arg):
|
|
297
299
|
app = Application.from_application_dictionary(arg)
|
|
@@ -302,72 +304,81 @@ class WebinspectorService:
|
|
|
302
304
|
self.connected_application[app.id_] = app
|
|
303
305
|
|
|
304
306
|
def _handle_application_sent_data(self, arg):
|
|
305
|
-
response = json.loads(arg[
|
|
307
|
+
response = json.loads(arg["WIRMessageDataKey"])
|
|
306
308
|
|
|
307
|
-
if
|
|
308
|
-
self.wir_message_results[response[
|
|
309
|
+
if "id" in response:
|
|
310
|
+
self.wir_message_results[response["id"]] = response
|
|
309
311
|
else:
|
|
310
312
|
self.wir_events.append(response)
|
|
311
313
|
|
|
312
314
|
def _handle_application_disconnected(self, arg):
|
|
313
|
-
self.connected_application.pop(arg[
|
|
314
|
-
self.application_pages.pop(arg[
|
|
315
|
+
self.connected_application.pop(arg["WIRApplicationIdentifierKey"], None)
|
|
316
|
+
self.application_pages.pop(arg["WIRApplicationIdentifierKey"], None)
|
|
315
317
|
|
|
316
318
|
async def _report_identifier(self):
|
|
317
|
-
await self._send_message(
|
|
319
|
+
await self._send_message("_rpc_reportIdentifier:")
|
|
318
320
|
|
|
319
321
|
async def _forward_get_listing(self, app_id):
|
|
320
|
-
self.logger.debug(f
|
|
321
|
-
await self._send_message(
|
|
322
|
+
self.logger.debug(f"Listing app with id {app_id}")
|
|
323
|
+
await self._send_message("_rpc_forwardGetListing:", {"WIRApplicationIdentifierKey": app_id})
|
|
322
324
|
|
|
323
325
|
async def _request_application_launch(self, bundle: str):
|
|
324
|
-
await self._send_message(
|
|
326
|
+
await self._send_message("_rpc_requestApplicationLaunch:", {"WIRApplicationBundleIdentifierKey": bundle})
|
|
325
327
|
|
|
326
328
|
async def _get_connected_applications(self) -> None:
|
|
327
|
-
await self._send_message(
|
|
329
|
+
await self._send_message("_rpc_getConnectedApplications:", {})
|
|
328
330
|
|
|
329
331
|
async def _forward_automation_session_request(self, session_id: str, app_id: str):
|
|
330
|
-
await self._send_message(
|
|
331
|
-
|
|
332
|
-
|
|
333
|
-
|
|
334
|
-
|
|
332
|
+
await self._send_message(
|
|
333
|
+
"_rpc_forwardAutomationSessionRequest:",
|
|
334
|
+
{
|
|
335
|
+
"WIRApplicationIdentifierKey": app_id,
|
|
336
|
+
"WIRSessionCapabilitiesKey": {
|
|
337
|
+
"org.webkit.webdriver.webrtc.allow-insecure-media-capture": True,
|
|
338
|
+
"org.webkit.webdriver.webrtc.suppress-ice-candidate-filtering": False,
|
|
339
|
+
},
|
|
340
|
+
"WIRSessionIdentifierKey": session_id,
|
|
335
341
|
},
|
|
336
|
-
|
|
337
|
-
})
|
|
342
|
+
)
|
|
338
343
|
|
|
339
344
|
async def _forward_socket_setup(self, session_id: str, app_id: str, page_id: int, pause: bool = True):
|
|
340
345
|
message = {
|
|
341
|
-
|
|
342
|
-
|
|
343
|
-
|
|
344
|
-
|
|
346
|
+
"WIRApplicationIdentifierKey": app_id,
|
|
347
|
+
"WIRPageIdentifierKey": page_id,
|
|
348
|
+
"WIRSenderKey": session_id,
|
|
349
|
+
"WIRMessageDataTypeChunkSupportedKey": 0,
|
|
345
350
|
}
|
|
346
351
|
if not pause:
|
|
347
|
-
message[
|
|
348
|
-
await self._send_message(
|
|
352
|
+
message["WIRAutomaticallyPause"] = False
|
|
353
|
+
await self._send_message("_rpc_forwardSocketSetup:", message)
|
|
349
354
|
|
|
350
355
|
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
|
-
|
|
356
|
+
await self._send_message(
|
|
357
|
+
"_rpc_forwardSocketData:",
|
|
358
|
+
{
|
|
359
|
+
"WIRApplicationIdentifierKey": app_id,
|
|
360
|
+
"WIRPageIdentifierKey": page_id,
|
|
361
|
+
"WIRSessionIdentifierKey": session_id,
|
|
362
|
+
"WIRSenderKey": session_id,
|
|
363
|
+
"WIRSocketDataKey": json.dumps(data).encode(),
|
|
364
|
+
},
|
|
365
|
+
)
|
|
358
366
|
|
|
359
367
|
async def _forward_indicate_web_view(self, app_id: str, page_id: int, enable: bool):
|
|
360
|
-
|
|
361
|
-
|
|
362
|
-
|
|
363
|
-
|
|
364
|
-
|
|
368
|
+
(
|
|
369
|
+
await self._send_message("_rpc_forwardIndicateWebView"),
|
|
370
|
+
{
|
|
371
|
+
"WIRApplicationIdentifierKey": app_id,
|
|
372
|
+
"WIRPageIdentifierKey": page_id,
|
|
373
|
+
"WIRIndicateEnabledKey": enable,
|
|
374
|
+
},
|
|
375
|
+
)
|
|
365
376
|
|
|
366
377
|
async def _send_message(self, selector: str, args=None):
|
|
367
378
|
if args is None:
|
|
368
379
|
args = {}
|
|
369
|
-
args[
|
|
370
|
-
await self.service.aio_send_plist({
|
|
380
|
+
args["WIRConnectionIdentifierKey"] = self.connection_id
|
|
381
|
+
await self.service.aio_send_plist({"__selector": selector, "__argument": args})
|
|
371
382
|
|
|
372
383
|
def _page_by_automation_session(self, session_id: str) -> Page:
|
|
373
384
|
for app_id in self.application_pages:
|
|
@@ -383,7 +394,7 @@ class WebinspectorService:
|
|
|
383
394
|
return page
|
|
384
395
|
await asyncio.sleep(0)
|
|
385
396
|
|
|
386
|
-
async def _wait_for_application(self, bundle: str =
|
|
397
|
+
async def _wait_for_application(self, bundle: str = "", app_id: str = "") -> Application:
|
|
387
398
|
while True:
|
|
388
399
|
for app in self.connected_application.values():
|
|
389
400
|
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)
|