pymobiledevice3 4.14.6__py3-none-any.whl → 7.0.6__py3-none-any.whl
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- misc/plist_sniffer.py +15 -15
- misc/remotexpc_sniffer.py +29 -28
- misc/understanding_idevice_protocol_layers.md +15 -10
- pymobiledevice3/__main__.py +317 -127
- pymobiledevice3/_version.py +22 -4
- pymobiledevice3/bonjour.py +358 -113
- pymobiledevice3/ca.py +253 -16
- pymobiledevice3/cli/activation.py +31 -23
- pymobiledevice3/cli/afc.py +49 -40
- pymobiledevice3/cli/amfi.py +16 -21
- pymobiledevice3/cli/apps.py +87 -42
- pymobiledevice3/cli/backup.py +160 -90
- pymobiledevice3/cli/bonjour.py +44 -40
- pymobiledevice3/cli/cli_common.py +204 -198
- pymobiledevice3/cli/companion_proxy.py +14 -14
- pymobiledevice3/cli/crash.py +105 -56
- pymobiledevice3/cli/developer/__init__.py +62 -0
- pymobiledevice3/cli/developer/accessibility/__init__.py +65 -0
- pymobiledevice3/cli/developer/accessibility/settings.py +43 -0
- pymobiledevice3/cli/developer/arbitration.py +50 -0
- pymobiledevice3/cli/developer/condition.py +33 -0
- pymobiledevice3/cli/developer/core_device.py +294 -0
- pymobiledevice3/cli/developer/debugserver.py +244 -0
- pymobiledevice3/cli/developer/dvt/__init__.py +438 -0
- pymobiledevice3/cli/developer/dvt/core_profile_session.py +295 -0
- pymobiledevice3/cli/developer/dvt/simulate_location.py +56 -0
- pymobiledevice3/cli/developer/dvt/sysmon/__init__.py +69 -0
- pymobiledevice3/cli/developer/dvt/sysmon/process.py +188 -0
- pymobiledevice3/cli/developer/fetch_symbols.py +108 -0
- pymobiledevice3/cli/developer/simulate_location.py +51 -0
- pymobiledevice3/cli/diagnostics/__init__.py +75 -0
- pymobiledevice3/cli/diagnostics/battery.py +47 -0
- pymobiledevice3/cli/idam.py +42 -0
- pymobiledevice3/cli/lockdown.py +108 -103
- pymobiledevice3/cli/mounter.py +158 -99
- pymobiledevice3/cli/notification.py +38 -26
- pymobiledevice3/cli/pcap.py +45 -24
- pymobiledevice3/cli/power_assertion.py +18 -17
- pymobiledevice3/cli/processes.py +17 -23
- pymobiledevice3/cli/profile.py +165 -109
- pymobiledevice3/cli/provision.py +35 -34
- pymobiledevice3/cli/remote.py +217 -129
- pymobiledevice3/cli/restore.py +159 -143
- pymobiledevice3/cli/springboard.py +63 -53
- pymobiledevice3/cli/syslog.py +193 -86
- pymobiledevice3/cli/usbmux.py +73 -33
- pymobiledevice3/cli/version.py +5 -7
- pymobiledevice3/cli/webinspector.py +376 -214
- pymobiledevice3/common.py +3 -1
- pymobiledevice3/exceptions.py +182 -58
- pymobiledevice3/irecv.py +52 -53
- pymobiledevice3/irecv_devices.py +1489 -464
- pymobiledevice3/lockdown.py +473 -275
- pymobiledevice3/lockdown_service_provider.py +15 -8
- pymobiledevice3/osu/os_utils.py +27 -9
- pymobiledevice3/osu/posix_util.py +34 -15
- pymobiledevice3/osu/win_util.py +14 -8
- pymobiledevice3/pair_records.py +102 -21
- pymobiledevice3/remote/common.py +8 -4
- pymobiledevice3/remote/core_device/app_service.py +94 -67
- pymobiledevice3/remote/core_device/core_device_service.py +17 -14
- pymobiledevice3/remote/core_device/device_info.py +5 -5
- pymobiledevice3/remote/core_device/diagnostics_service.py +19 -4
- pymobiledevice3/remote/core_device/file_service.py +53 -23
- pymobiledevice3/remote/remote_service_discovery.py +79 -45
- pymobiledevice3/remote/remotexpc.py +73 -44
- pymobiledevice3/remote/tunnel_service.py +442 -317
- pymobiledevice3/remote/utils.py +14 -13
- pymobiledevice3/remote/xpc_message.py +145 -125
- pymobiledevice3/resources/dsc_uuid_map.py +19 -19
- pymobiledevice3/resources/firmware_notifications.py +20 -16
- pymobiledevice3/resources/notifications.txt +144 -0
- pymobiledevice3/restore/asr.py +27 -27
- pymobiledevice3/restore/base_restore.py +110 -21
- pymobiledevice3/restore/consts.py +87 -66
- pymobiledevice3/restore/device.py +59 -12
- pymobiledevice3/restore/fdr.py +46 -48
- pymobiledevice3/restore/ftab.py +19 -19
- pymobiledevice3/restore/img4.py +163 -0
- pymobiledevice3/restore/mbn.py +587 -0
- pymobiledevice3/restore/recovery.py +151 -151
- pymobiledevice3/restore/restore.py +562 -544
- pymobiledevice3/restore/restore_options.py +131 -110
- pymobiledevice3/restore/restored_client.py +51 -31
- pymobiledevice3/restore/tss.py +385 -267
- pymobiledevice3/service_connection.py +252 -59
- pymobiledevice3/services/accessibilityaudit.py +202 -120
- pymobiledevice3/services/afc.py +962 -365
- pymobiledevice3/services/amfi.py +24 -30
- pymobiledevice3/services/companion.py +23 -19
- pymobiledevice3/services/crash_reports.py +71 -47
- pymobiledevice3/services/debugserver_applist.py +3 -3
- pymobiledevice3/services/device_arbitration.py +8 -8
- pymobiledevice3/services/device_link.py +101 -79
- pymobiledevice3/services/diagnostics.py +973 -967
- pymobiledevice3/services/dtfetchsymbols.py +8 -8
- pymobiledevice3/services/dvt/dvt_secure_socket_proxy.py +4 -4
- pymobiledevice3/services/dvt/dvt_testmanaged_proxy.py +4 -4
- pymobiledevice3/services/dvt/instruments/activity_trace_tap.py +85 -74
- pymobiledevice3/services/dvt/instruments/application_listing.py +2 -3
- pymobiledevice3/services/dvt/instruments/condition_inducer.py +7 -6
- pymobiledevice3/services/dvt/instruments/core_profile_session_tap.py +466 -384
- pymobiledevice3/services/dvt/instruments/device_info.py +20 -11
- pymobiledevice3/services/dvt/instruments/energy_monitor.py +1 -1
- pymobiledevice3/services/dvt/instruments/graphics.py +1 -1
- pymobiledevice3/services/dvt/instruments/location_simulation.py +1 -1
- pymobiledevice3/services/dvt/instruments/location_simulation_base.py +10 -10
- pymobiledevice3/services/dvt/instruments/network_monitor.py +17 -17
- pymobiledevice3/services/dvt/instruments/notifications.py +1 -1
- pymobiledevice3/services/dvt/instruments/process_control.py +35 -10
- pymobiledevice3/services/dvt/instruments/screenshot.py +2 -2
- pymobiledevice3/services/dvt/instruments/sysmontap.py +15 -15
- pymobiledevice3/services/dvt/testmanaged/xcuitest.py +42 -52
- pymobiledevice3/services/file_relay.py +10 -10
- pymobiledevice3/services/heartbeat.py +9 -8
- pymobiledevice3/services/house_arrest.py +16 -15
- pymobiledevice3/services/idam.py +20 -0
- pymobiledevice3/services/installation_proxy.py +173 -81
- pymobiledevice3/services/lockdown_service.py +20 -10
- pymobiledevice3/services/misagent.py +22 -19
- pymobiledevice3/services/mobile_activation.py +147 -64
- pymobiledevice3/services/mobile_config.py +331 -294
- pymobiledevice3/services/mobile_image_mounter.py +141 -113
- pymobiledevice3/services/mobilebackup2.py +203 -145
- pymobiledevice3/services/notification_proxy.py +11 -11
- pymobiledevice3/services/os_trace.py +134 -74
- pymobiledevice3/services/pcapd.py +314 -302
- pymobiledevice3/services/power_assertion.py +10 -9
- pymobiledevice3/services/preboard.py +4 -4
- pymobiledevice3/services/remote_fetch_symbols.py +21 -14
- pymobiledevice3/services/remote_server.py +176 -146
- pymobiledevice3/services/restore_service.py +16 -16
- pymobiledevice3/services/screenshot.py +15 -12
- pymobiledevice3/services/simulate_location.py +7 -7
- pymobiledevice3/services/springboard.py +15 -15
- pymobiledevice3/services/syslog.py +5 -5
- pymobiledevice3/services/web_protocol/alert.py +11 -11
- pymobiledevice3/services/web_protocol/automation_session.py +251 -239
- pymobiledevice3/services/web_protocol/cdp_screencast.py +46 -37
- pymobiledevice3/services/web_protocol/cdp_server.py +19 -19
- pymobiledevice3/services/web_protocol/cdp_target.py +411 -373
- pymobiledevice3/services/web_protocol/driver.py +114 -111
- pymobiledevice3/services/web_protocol/element.py +124 -111
- pymobiledevice3/services/web_protocol/inspector_session.py +106 -102
- pymobiledevice3/services/web_protocol/selenium_api.py +49 -49
- pymobiledevice3/services/web_protocol/session_protocol.py +18 -12
- pymobiledevice3/services/web_protocol/switch_to.py +30 -27
- pymobiledevice3/services/webinspector.py +189 -155
- pymobiledevice3/tcp_forwarder.py +87 -69
- pymobiledevice3/tunneld/__init__.py +0 -0
- pymobiledevice3/tunneld/api.py +63 -0
- pymobiledevice3/tunneld/server.py +603 -0
- pymobiledevice3/usbmux.py +198 -147
- pymobiledevice3/utils.py +14 -11
- {pymobiledevice3-4.14.6.dist-info → pymobiledevice3-7.0.6.dist-info}/METADATA +55 -28
- pymobiledevice3-7.0.6.dist-info/RECORD +188 -0
- {pymobiledevice3-4.14.6.dist-info → pymobiledevice3-7.0.6.dist-info}/WHEEL +1 -1
- pymobiledevice3/cli/developer.py +0 -1215
- pymobiledevice3/cli/diagnostics.py +0 -99
- pymobiledevice3/tunneld.py +0 -524
- pymobiledevice3-4.14.6.dist-info/RECORD +0 -168
- {pymobiledevice3-4.14.6.dist-info → pymobiledevice3-7.0.6.dist-info}/entry_points.txt +0 -0
- {pymobiledevice3-4.14.6.dist-info → pymobiledevice3-7.0.6.dist-info/licenses}/LICENSE +0 -0
- {pymobiledevice3-4.14.6.dist-info → pymobiledevice3-7.0.6.dist-info}/top_level.txt +0 -0
|
@@ -1,18 +1,24 @@
|
|
|
1
1
|
import asyncio
|
|
2
|
+
import contextlib
|
|
2
3
|
import logging
|
|
3
4
|
import plistlib
|
|
4
5
|
import socket
|
|
5
6
|
import ssl
|
|
6
7
|
import struct
|
|
7
8
|
import time
|
|
9
|
+
import xml
|
|
8
10
|
from enum import Enum
|
|
9
|
-
from typing import Any, Optional
|
|
11
|
+
from typing import Any, Optional, Union
|
|
10
12
|
|
|
11
13
|
import IPython
|
|
12
14
|
from pygments import formatters, highlight, lexers
|
|
13
15
|
|
|
14
|
-
from pymobiledevice3.exceptions import
|
|
15
|
-
|
|
16
|
+
from pymobiledevice3.exceptions import (
|
|
17
|
+
ConnectionTerminatedError,
|
|
18
|
+
DeviceNotFoundError,
|
|
19
|
+
NoDeviceConnectedError,
|
|
20
|
+
PyMobileDevice3Exception,
|
|
21
|
+
)
|
|
16
22
|
from pymobiledevice3.osu.os_utils import get_os_utils
|
|
17
23
|
from pymobiledevice3.usbmux import MuxDevice, select_device
|
|
18
24
|
|
|
@@ -38,36 +44,51 @@ print(client.recvall(20))
|
|
|
38
44
|
"""
|
|
39
45
|
|
|
40
46
|
|
|
41
|
-
def build_plist(d: dict, endianity: str =
|
|
47
|
+
def build_plist(d: dict, endianity: str = ">", fmt: Enum = plistlib.FMT_XML) -> bytes:
|
|
48
|
+
"""
|
|
49
|
+
Convert a dictionary to a plist-formatted byte string prefixed with a length field.
|
|
50
|
+
|
|
51
|
+
:param d: The dictionary to convert.
|
|
52
|
+
:param endianity: The byte order ('>' for big-endian, '<' for little-endian).
|
|
53
|
+
:param fmt: The plist format (e.g., plistlib.FMT_XML).
|
|
54
|
+
:return: The plist-formatted byte string.
|
|
55
|
+
"""
|
|
42
56
|
payload = plistlib.dumps(d, fmt=fmt)
|
|
43
|
-
message = struct.pack(endianity +
|
|
57
|
+
message = struct.pack(endianity + "L", len(payload))
|
|
44
58
|
return message + payload
|
|
45
59
|
|
|
46
60
|
|
|
47
|
-
def parse_plist(payload):
|
|
61
|
+
def parse_plist(payload: bytes) -> dict:
|
|
62
|
+
"""
|
|
63
|
+
Parse a plist-formatted byte string into a dictionary.
|
|
64
|
+
|
|
65
|
+
:param payload: The plist-formatted byte string to parse.
|
|
66
|
+
:return: The parsed dictionary.
|
|
67
|
+
:raises PyMobileDevice3Exception: If the payload is invalid.
|
|
68
|
+
:retries with a filtered payload of only valid XML characters if plistlib compains about "not well-formed (invalid token)"
|
|
69
|
+
"""
|
|
48
70
|
try:
|
|
49
71
|
return plistlib.loads(payload)
|
|
50
|
-
except
|
|
51
|
-
|
|
52
|
-
|
|
53
|
-
|
|
54
|
-
|
|
55
|
-
|
|
56
|
-
|
|
57
|
-
|
|
58
|
-
else:
|
|
59
|
-
context.set_ciphers('ALL:!aNULL:!eNULL')
|
|
60
|
-
context.options |= 0x4 # OPENSSL OP_LEGACY_SERVER_CONNECT (required for legacy iOS devices)
|
|
61
|
-
context.check_hostname = False
|
|
62
|
-
context.verify_mode = ssl.CERT_NONE
|
|
63
|
-
context.load_cert_chain(certfile, keyfile)
|
|
64
|
-
return context
|
|
72
|
+
except xml.parsers.expat.ExpatError:
|
|
73
|
+
payload = bytes([b for b in payload if b >= 0x20 or b in (0x09, 0x0A, 0x0D)])
|
|
74
|
+
try:
|
|
75
|
+
return plistlib.loads(payload)
|
|
76
|
+
except plistlib.InvalidFileException as e:
|
|
77
|
+
raise PyMobileDevice3Exception(f"parse_plist invalid data: {payload[:100].hex()}") from e
|
|
78
|
+
except plistlib.InvalidFileException as e:
|
|
79
|
+
raise PyMobileDevice3Exception(f"parse_plist invalid data: {payload[:100].hex()}") from e
|
|
65
80
|
|
|
66
81
|
|
|
67
82
|
class ServiceConnection:
|
|
68
|
-
"""
|
|
83
|
+
"""wrapper for tcp-relay connections"""
|
|
69
84
|
|
|
70
85
|
def __init__(self, sock: socket.socket, mux_device: MuxDevice = None):
|
|
86
|
+
"""
|
|
87
|
+
Initialize a ServiceConnection object.
|
|
88
|
+
|
|
89
|
+
:param sock: The socket to use for the connection.
|
|
90
|
+
:param mux_device: The MuxDevice associated with the connection (optional).
|
|
91
|
+
"""
|
|
71
92
|
self.logger = logging.getLogger(__name__)
|
|
72
93
|
self.socket = sock
|
|
73
94
|
self._offset = 0
|
|
@@ -78,9 +99,24 @@ class ServiceConnection:
|
|
|
78
99
|
self.reader = None # type: Optional[asyncio.StreamReader]
|
|
79
100
|
self.writer = None # type: Optional[asyncio.StreamWriter]
|
|
80
101
|
|
|
102
|
+
# SSL/TLS version to be used for connecting to device
|
|
103
|
+
# TLS v1.2 is supported since iOS 5
|
|
104
|
+
self.min_ssl_proto = ssl.TLSVersion.TLSv1_2
|
|
105
|
+
self.max_ssl_proto = ssl.TLSVersion.TLSv1_3
|
|
106
|
+
|
|
81
107
|
@staticmethod
|
|
82
|
-
def create_using_tcp(
|
|
83
|
-
|
|
108
|
+
def create_using_tcp(
|
|
109
|
+
hostname: str, port: int, keep_alive: bool = True, create_connection_timeout: int = DEFAULT_TIMEOUT
|
|
110
|
+
) -> "ServiceConnection":
|
|
111
|
+
"""
|
|
112
|
+
Create a ServiceConnection using a TCP connection.
|
|
113
|
+
|
|
114
|
+
:param hostname: The hostname of the server to connect to.
|
|
115
|
+
:param port: The port to connect to.
|
|
116
|
+
:param keep_alive: Whether to enable TCP keep-alive.
|
|
117
|
+
:param create_connection_timeout: The timeout for creating the connection.
|
|
118
|
+
:return: A ServiceConnection object.
|
|
119
|
+
"""
|
|
84
120
|
sock = socket.create_connection((hostname, port), timeout=create_connection_timeout)
|
|
85
121
|
sock.settimeout(None)
|
|
86
122
|
if keep_alive:
|
|
@@ -88,8 +124,20 @@ class ServiceConnection:
|
|
|
88
124
|
return ServiceConnection(sock)
|
|
89
125
|
|
|
90
126
|
@staticmethod
|
|
91
|
-
def create_using_usbmux(
|
|
92
|
-
|
|
127
|
+
def create_using_usbmux(
|
|
128
|
+
udid: Optional[str], port: int, connection_type: Optional[str] = None, usbmux_address: Optional[str] = None
|
|
129
|
+
) -> "ServiceConnection":
|
|
130
|
+
"""
|
|
131
|
+
Create a ServiceConnection using a USBMux connection.
|
|
132
|
+
|
|
133
|
+
:param udid: The UDID of the target device.
|
|
134
|
+
:param port: The port to connect to.
|
|
135
|
+
:param connection_type: The type of connection to use.
|
|
136
|
+
:param usbmux_address: The address of the usbmuxd socket.
|
|
137
|
+
:return: A ServiceConnection object.
|
|
138
|
+
:raises DeviceNotFoundError: If the device with the specified UDID is not found.
|
|
139
|
+
:raises NoDeviceConnectedError: If no device is connected.
|
|
140
|
+
"""
|
|
93
141
|
target_device = select_device(udid, connection_type=connection_type, usbmux_address=usbmux_address)
|
|
94
142
|
if target_device is None:
|
|
95
143
|
if udid:
|
|
@@ -99,42 +147,84 @@ class ServiceConnection:
|
|
|
99
147
|
return ServiceConnection(sock, mux_device=target_device)
|
|
100
148
|
|
|
101
149
|
def setblocking(self, blocking: bool) -> None:
|
|
150
|
+
"""
|
|
151
|
+
Set the blocking mode of the socket.
|
|
152
|
+
|
|
153
|
+
:param blocking: If True, set the socket to blocking mode; otherwise, set it to non-blocking mode.
|
|
154
|
+
"""
|
|
102
155
|
self.socket.setblocking(blocking)
|
|
103
156
|
|
|
104
157
|
def close(self) -> None:
|
|
158
|
+
"""Close the connection."""
|
|
105
159
|
self.socket.close()
|
|
106
160
|
|
|
107
161
|
async def aio_close(self) -> None:
|
|
162
|
+
"""Asynchronously close the connection."""
|
|
108
163
|
if self.writer is None:
|
|
109
164
|
return
|
|
110
165
|
self.writer.close()
|
|
111
|
-
|
|
166
|
+
with contextlib.suppress(ssl.SSLError):
|
|
112
167
|
await self.writer.wait_closed()
|
|
113
|
-
except ssl.SSLError:
|
|
114
|
-
pass
|
|
115
168
|
self.writer = None
|
|
116
169
|
self.reader = None
|
|
117
170
|
|
|
118
|
-
def recv(self, length=4096) -> bytes:
|
|
119
|
-
"""
|
|
120
|
-
|
|
171
|
+
def recv(self, length: int = 4096) -> bytes:
|
|
172
|
+
"""
|
|
173
|
+
Receive data from the socket.
|
|
174
|
+
|
|
175
|
+
:param length: The maximum amount of data to receive.
|
|
176
|
+
:return: The received data.
|
|
177
|
+
"""
|
|
178
|
+
try:
|
|
179
|
+
return self.socket.recv(length)
|
|
180
|
+
except ssl.SSLError as e:
|
|
181
|
+
raise ConnectionAbortedError() from e
|
|
121
182
|
|
|
122
183
|
def sendall(self, data: bytes) -> None:
|
|
184
|
+
"""
|
|
185
|
+
Send data to the socket.
|
|
186
|
+
|
|
187
|
+
:param data: The data to send.
|
|
188
|
+
:raises ConnectionTerminatedError: If the connection is terminated abruptly.
|
|
189
|
+
"""
|
|
123
190
|
try:
|
|
124
191
|
self.socket.sendall(data)
|
|
125
192
|
except ssl.SSLEOFError as e:
|
|
126
193
|
raise ConnectionTerminatedError from e
|
|
127
194
|
|
|
128
|
-
def send_recv_plist(self, data: dict, endianity=
|
|
195
|
+
def send_recv_plist(self, data: Union[dict, list], endianity: str = ">", fmt: Enum = plistlib.FMT_XML) -> Any:
|
|
196
|
+
"""
|
|
197
|
+
Send a plist to the socket and receive a plist response.
|
|
198
|
+
|
|
199
|
+
:param data: The dictionary to send as a plist.
|
|
200
|
+
:param endianity: The byte order ('>' for big-endian, '<' for little-endian).
|
|
201
|
+
:param fmt: The plist format (e.g., plistlib.FMT_XML).
|
|
202
|
+
:return: The received plist as a dictionary.
|
|
203
|
+
"""
|
|
129
204
|
self.send_plist(data, endianity=endianity, fmt=fmt)
|
|
130
205
|
return self.recv_plist(endianity=endianity)
|
|
131
206
|
|
|
132
|
-
async def aio_send_recv_plist(self, data: dict, endianity=
|
|
207
|
+
async def aio_send_recv_plist(self, data: dict, endianity: str = ">", fmt: Enum = plistlib.FMT_XML) -> Any:
|
|
208
|
+
"""
|
|
209
|
+
Asynchronously send a plist to the socket and receive a plist response.
|
|
210
|
+
|
|
211
|
+
:param data: The dictionary to send as a plist.
|
|
212
|
+
:param endianity: The byte order ('>' for big-endian, '<' for little-endian).
|
|
213
|
+
:param fmt: The plist format (e.g., plistlib.FMT_XML).
|
|
214
|
+
:return: The received plist as a dictionary.
|
|
215
|
+
"""
|
|
133
216
|
await self.aio_send_plist(data, endianity=endianity, fmt=fmt)
|
|
134
217
|
return await self.aio_recv_plist(endianity=endianity)
|
|
135
218
|
|
|
136
219
|
def recvall(self, size: int) -> bytes:
|
|
137
|
-
|
|
220
|
+
"""
|
|
221
|
+
Receive all data of a specified size from the socket.
|
|
222
|
+
|
|
223
|
+
:param size: The amount of data to receive.
|
|
224
|
+
:return: The received data.
|
|
225
|
+
:raises ConnectionAbortedError: If the connection is aborted.
|
|
226
|
+
"""
|
|
227
|
+
data = b""
|
|
138
228
|
while len(data) < size:
|
|
139
229
|
chunk = self.recv(size - len(data))
|
|
140
230
|
if chunk is None or len(chunk) == 0:
|
|
@@ -142,12 +232,17 @@ class ServiceConnection:
|
|
|
142
232
|
data += chunk
|
|
143
233
|
return data
|
|
144
234
|
|
|
145
|
-
def recv_prefixed(self, endianity=
|
|
146
|
-
"""
|
|
235
|
+
def recv_prefixed(self, endianity: str = ">") -> bytes:
|
|
236
|
+
"""
|
|
237
|
+
Receive a data block prefixed with a length field.
|
|
238
|
+
|
|
239
|
+
:param endianity: The byte order ('>' for big-endian, '<' for little-endian).
|
|
240
|
+
:return: The received data block.
|
|
241
|
+
"""
|
|
147
242
|
size = self.recvall(4)
|
|
148
243
|
if not size or len(size) != 4:
|
|
149
|
-
return b
|
|
150
|
-
size = struct.unpack(endianity +
|
|
244
|
+
return b""
|
|
245
|
+
size = struct.unpack(endianity + "L", size)[0]
|
|
151
246
|
while True:
|
|
152
247
|
try:
|
|
153
248
|
return self.recvall(size)
|
|
@@ -156,67 +251,165 @@ class ServiceConnection:
|
|
|
156
251
|
time.sleep(0)
|
|
157
252
|
|
|
158
253
|
async def aio_recvall(self, size: int) -> bytes:
|
|
159
|
-
"""
|
|
254
|
+
"""
|
|
255
|
+
Asynchronously receive data of a specified size from the socket.
|
|
256
|
+
|
|
257
|
+
:param size: The amount of data to receive.
|
|
258
|
+
:return: The received data.
|
|
259
|
+
"""
|
|
160
260
|
return await self.reader.readexactly(size)
|
|
161
261
|
|
|
162
|
-
async def aio_recv_prefixed(self, endianity=
|
|
163
|
-
"""
|
|
262
|
+
async def aio_recv_prefixed(self, endianity: str = ">") -> bytes:
|
|
263
|
+
"""
|
|
264
|
+
Asynchronously receive a data block prefixed with a length field.
|
|
265
|
+
|
|
266
|
+
:param endianity: The byte order ('>' for big-endian, '<' for little-endian).
|
|
267
|
+
:return: The received data block.
|
|
268
|
+
"""
|
|
164
269
|
size = await self.aio_recvall(4)
|
|
165
|
-
size = struct.unpack(endianity +
|
|
270
|
+
size = struct.unpack(endianity + "L", size)[0]
|
|
166
271
|
return await self.aio_recvall(size)
|
|
167
272
|
|
|
168
273
|
def send_prefixed(self, data: bytes) -> None:
|
|
169
|
-
"""
|
|
274
|
+
"""
|
|
275
|
+
Send a data block prefixed with a length field.
|
|
276
|
+
|
|
277
|
+
:param data: The data to send.
|
|
278
|
+
"""
|
|
170
279
|
if isinstance(data, str):
|
|
171
280
|
data = data.encode()
|
|
172
|
-
hdr = struct.pack(
|
|
173
|
-
msg = b
|
|
281
|
+
hdr = struct.pack(">L", len(data))
|
|
282
|
+
msg = b"".join([hdr, data])
|
|
174
283
|
return self.sendall(msg)
|
|
175
284
|
|
|
176
|
-
def recv_plist(self, endianity=
|
|
285
|
+
def recv_plist(self, endianity: str = ">") -> Union[dict, list]:
|
|
286
|
+
"""
|
|
287
|
+
Receive a plist from the socket and parse it into a native type.
|
|
288
|
+
|
|
289
|
+
:param endianity: The byte order ('>' for big-endian, '<' for little-endian).
|
|
290
|
+
:return: The received plist as a native type.
|
|
291
|
+
"""
|
|
177
292
|
return parse_plist(self.recv_prefixed(endianity=endianity))
|
|
178
293
|
|
|
179
|
-
async def aio_recv_plist(self, endianity=
|
|
294
|
+
async def aio_recv_plist(self, endianity: str = ">") -> dict:
|
|
295
|
+
"""
|
|
296
|
+
Asynchronously receive a plist from the socket and parse it into a native type.
|
|
297
|
+
|
|
298
|
+
:param endianity: The byte order ('>' for big-endian, '<' for little-endian).
|
|
299
|
+
:return: The received plist as a native type.
|
|
300
|
+
"""
|
|
180
301
|
return parse_plist(await self.aio_recv_prefixed(endianity))
|
|
181
302
|
|
|
182
|
-
def send_plist(self, d, endianity=
|
|
303
|
+
def send_plist(self, d: Union[dict, list], endianity: str = ">", fmt: Enum = plistlib.FMT_XML) -> None:
|
|
304
|
+
"""
|
|
305
|
+
Send a native type as a plist to the socket.
|
|
306
|
+
|
|
307
|
+
:param d: The native type to send.
|
|
308
|
+
:param endianity: The byte order ('>' for big-endian, '<' for little-endian).
|
|
309
|
+
:param fmt: The plist format (e.g., plistlib.FMT_XML).
|
|
310
|
+
"""
|
|
183
311
|
return self.sendall(build_plist(d, endianity, fmt))
|
|
184
312
|
|
|
185
313
|
async def aio_sendall(self, payload: bytes) -> None:
|
|
314
|
+
"""
|
|
315
|
+
Asynchronously send data to the socket.
|
|
316
|
+
|
|
317
|
+
:param payload: The data to send.
|
|
318
|
+
"""
|
|
186
319
|
self.writer.write(payload)
|
|
187
320
|
await self.writer.drain()
|
|
188
321
|
|
|
189
|
-
async def aio_send_plist(self, d: dict, endianity: str =
|
|
322
|
+
async def aio_send_plist(self, d: Union[dict, list], endianity: str = ">", fmt: Enum = plistlib.FMT_XML) -> None:
|
|
323
|
+
"""
|
|
324
|
+
Asynchronously send a dictionary as a plist to the socket.
|
|
325
|
+
|
|
326
|
+
:param d: The dictionary to send.
|
|
327
|
+
:param endianity: The byte order ('>' for big-endian, '<' for little-endian).
|
|
328
|
+
:param fmt: The plist format (e.g., plistlib.FMT_XML).
|
|
329
|
+
"""
|
|
190
330
|
await self.aio_sendall(build_plist(d, endianity, fmt))
|
|
191
331
|
|
|
192
|
-
def
|
|
193
|
-
|
|
332
|
+
def create_ssl_context(self, certfile: str, keyfile: Optional[str] = None) -> ssl.SSLContext:
|
|
333
|
+
"""
|
|
334
|
+
Create an SSL context for a secure connection.
|
|
335
|
+
|
|
336
|
+
:param certfile: The path to the certificate file.
|
|
337
|
+
:param keyfile: The path to the key file (optional).
|
|
338
|
+
:return: An SSL context object.
|
|
339
|
+
"""
|
|
340
|
+
context = ssl.SSLContext(ssl.PROTOCOL_TLS_CLIENT)
|
|
341
|
+
context.minimum_version = self.min_ssl_proto
|
|
342
|
+
context.maximum_version = self.max_ssl_proto
|
|
343
|
+
if ssl.OPENSSL_VERSION.lower().startswith("openssl"):
|
|
344
|
+
context.set_ciphers("ALL:!aNULL:!eNULL:@SECLEVEL=0")
|
|
345
|
+
else:
|
|
346
|
+
context.set_ciphers("ALL:!aNULL:!eNULL")
|
|
347
|
+
context.options |= 0x4 # OPENSSL OP_LEGACY_SERVER_CONNECT (required for legacy iOS devices)
|
|
348
|
+
context.check_hostname = False
|
|
349
|
+
context.verify_mode = ssl.CERT_NONE
|
|
350
|
+
context.load_cert_chain(certfile, keyfile)
|
|
351
|
+
return context
|
|
352
|
+
|
|
353
|
+
def ssl_start(self, certfile: str, keyfile: Optional[str] = None) -> None:
|
|
354
|
+
"""
|
|
355
|
+
Start an SSL connection.
|
|
356
|
+
|
|
357
|
+
:param certfile: The path to the certificate file.
|
|
358
|
+
:param keyfile: The path to the key file (optional).
|
|
359
|
+
"""
|
|
360
|
+
try:
|
|
361
|
+
self.socket = self.create_ssl_context(certfile, keyfile=keyfile).wrap_socket(self.socket)
|
|
362
|
+
except OSError as e:
|
|
363
|
+
raise ConnectionAbortedError() from e
|
|
364
|
+
|
|
365
|
+
async def aio_ssl_start(self, certfile: str, keyfile: Optional[str] = None) -> None:
|
|
366
|
+
"""
|
|
367
|
+
Asynchronously start an SSL connection.
|
|
194
368
|
|
|
195
|
-
|
|
369
|
+
:param certfile: The path to the certificate file.
|
|
370
|
+
:param keyfile: The path to the key file (optional).
|
|
371
|
+
"""
|
|
196
372
|
self.reader, self.writer = await asyncio.open_connection(
|
|
197
|
-
sock=self.socket,
|
|
198
|
-
ssl=create_context(certfile, keyfile=keyfile),
|
|
199
|
-
server_hostname=''
|
|
373
|
+
sock=self.socket, ssl=self.create_ssl_context(certfile, keyfile=keyfile), server_hostname=""
|
|
200
374
|
)
|
|
201
375
|
|
|
202
376
|
async def aio_start(self) -> None:
|
|
377
|
+
"""Asynchronously start a connection."""
|
|
203
378
|
self.reader, self.writer = await asyncio.open_connection(sock=self.socket)
|
|
204
379
|
|
|
205
380
|
def shell(self) -> None:
|
|
381
|
+
"""Start an interactive shell."""
|
|
206
382
|
IPython.embed(
|
|
207
|
-
header=highlight(SHELL_USAGE, lexers.PythonLexer(), formatters.Terminal256Formatter(style=
|
|
383
|
+
header=highlight(SHELL_USAGE, lexers.PythonLexer(), formatters.Terminal256Formatter(style="native")),
|
|
208
384
|
user_ns={
|
|
209
|
-
|
|
210
|
-
}
|
|
385
|
+
"client": self,
|
|
386
|
+
},
|
|
387
|
+
)
|
|
211
388
|
|
|
212
389
|
def read(self, size: int) -> bytes:
|
|
390
|
+
"""
|
|
391
|
+
Read data from the socket.
|
|
392
|
+
|
|
393
|
+
:param size: The amount of data to read.
|
|
394
|
+
:return: The read data.
|
|
395
|
+
"""
|
|
213
396
|
result = self.recvall(size)
|
|
214
397
|
self._offset += size
|
|
215
398
|
return result
|
|
216
399
|
|
|
217
400
|
def write(self, data: bytes) -> None:
|
|
401
|
+
"""
|
|
402
|
+
Write data to the socket.
|
|
403
|
+
|
|
404
|
+
:param data: The data to write.
|
|
405
|
+
"""
|
|
218
406
|
self.sendall(data)
|
|
219
407
|
self._offset += len(data)
|
|
220
408
|
|
|
221
409
|
def tell(self) -> int:
|
|
410
|
+
"""
|
|
411
|
+
Get the current offset.
|
|
412
|
+
|
|
413
|
+
:return: The current offset.
|
|
414
|
+
"""
|
|
222
415
|
return self._offset
|