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.
Files changed (164) hide show
  1. misc/plist_sniffer.py +15 -15
  2. misc/remotexpc_sniffer.py +29 -28
  3. misc/understanding_idevice_protocol_layers.md +15 -10
  4. pymobiledevice3/__main__.py +317 -127
  5. pymobiledevice3/_version.py +22 -4
  6. pymobiledevice3/bonjour.py +358 -113
  7. pymobiledevice3/ca.py +253 -16
  8. pymobiledevice3/cli/activation.py +31 -23
  9. pymobiledevice3/cli/afc.py +49 -40
  10. pymobiledevice3/cli/amfi.py +16 -21
  11. pymobiledevice3/cli/apps.py +87 -42
  12. pymobiledevice3/cli/backup.py +160 -90
  13. pymobiledevice3/cli/bonjour.py +44 -40
  14. pymobiledevice3/cli/cli_common.py +204 -198
  15. pymobiledevice3/cli/companion_proxy.py +14 -14
  16. pymobiledevice3/cli/crash.py +105 -56
  17. pymobiledevice3/cli/developer/__init__.py +62 -0
  18. pymobiledevice3/cli/developer/accessibility/__init__.py +65 -0
  19. pymobiledevice3/cli/developer/accessibility/settings.py +43 -0
  20. pymobiledevice3/cli/developer/arbitration.py +50 -0
  21. pymobiledevice3/cli/developer/condition.py +33 -0
  22. pymobiledevice3/cli/developer/core_device.py +294 -0
  23. pymobiledevice3/cli/developer/debugserver.py +244 -0
  24. pymobiledevice3/cli/developer/dvt/__init__.py +438 -0
  25. pymobiledevice3/cli/developer/dvt/core_profile_session.py +295 -0
  26. pymobiledevice3/cli/developer/dvt/simulate_location.py +56 -0
  27. pymobiledevice3/cli/developer/dvt/sysmon/__init__.py +69 -0
  28. pymobiledevice3/cli/developer/dvt/sysmon/process.py +188 -0
  29. pymobiledevice3/cli/developer/fetch_symbols.py +108 -0
  30. pymobiledevice3/cli/developer/simulate_location.py +51 -0
  31. pymobiledevice3/cli/diagnostics/__init__.py +75 -0
  32. pymobiledevice3/cli/diagnostics/battery.py +47 -0
  33. pymobiledevice3/cli/idam.py +42 -0
  34. pymobiledevice3/cli/lockdown.py +108 -103
  35. pymobiledevice3/cli/mounter.py +158 -99
  36. pymobiledevice3/cli/notification.py +38 -26
  37. pymobiledevice3/cli/pcap.py +45 -24
  38. pymobiledevice3/cli/power_assertion.py +18 -17
  39. pymobiledevice3/cli/processes.py +17 -23
  40. pymobiledevice3/cli/profile.py +165 -109
  41. pymobiledevice3/cli/provision.py +35 -34
  42. pymobiledevice3/cli/remote.py +217 -129
  43. pymobiledevice3/cli/restore.py +159 -143
  44. pymobiledevice3/cli/springboard.py +63 -53
  45. pymobiledevice3/cli/syslog.py +193 -86
  46. pymobiledevice3/cli/usbmux.py +73 -33
  47. pymobiledevice3/cli/version.py +5 -7
  48. pymobiledevice3/cli/webinspector.py +376 -214
  49. pymobiledevice3/common.py +3 -1
  50. pymobiledevice3/exceptions.py +182 -58
  51. pymobiledevice3/irecv.py +52 -53
  52. pymobiledevice3/irecv_devices.py +1489 -464
  53. pymobiledevice3/lockdown.py +473 -275
  54. pymobiledevice3/lockdown_service_provider.py +15 -8
  55. pymobiledevice3/osu/os_utils.py +27 -9
  56. pymobiledevice3/osu/posix_util.py +34 -15
  57. pymobiledevice3/osu/win_util.py +14 -8
  58. pymobiledevice3/pair_records.py +102 -21
  59. pymobiledevice3/remote/common.py +8 -4
  60. pymobiledevice3/remote/core_device/app_service.py +94 -67
  61. pymobiledevice3/remote/core_device/core_device_service.py +17 -14
  62. pymobiledevice3/remote/core_device/device_info.py +5 -5
  63. pymobiledevice3/remote/core_device/diagnostics_service.py +19 -4
  64. pymobiledevice3/remote/core_device/file_service.py +53 -23
  65. pymobiledevice3/remote/remote_service_discovery.py +79 -45
  66. pymobiledevice3/remote/remotexpc.py +73 -44
  67. pymobiledevice3/remote/tunnel_service.py +442 -317
  68. pymobiledevice3/remote/utils.py +14 -13
  69. pymobiledevice3/remote/xpc_message.py +145 -125
  70. pymobiledevice3/resources/dsc_uuid_map.py +19 -19
  71. pymobiledevice3/resources/firmware_notifications.py +20 -16
  72. pymobiledevice3/resources/notifications.txt +144 -0
  73. pymobiledevice3/restore/asr.py +27 -27
  74. pymobiledevice3/restore/base_restore.py +110 -21
  75. pymobiledevice3/restore/consts.py +87 -66
  76. pymobiledevice3/restore/device.py +59 -12
  77. pymobiledevice3/restore/fdr.py +46 -48
  78. pymobiledevice3/restore/ftab.py +19 -19
  79. pymobiledevice3/restore/img4.py +163 -0
  80. pymobiledevice3/restore/mbn.py +587 -0
  81. pymobiledevice3/restore/recovery.py +151 -151
  82. pymobiledevice3/restore/restore.py +562 -544
  83. pymobiledevice3/restore/restore_options.py +131 -110
  84. pymobiledevice3/restore/restored_client.py +51 -31
  85. pymobiledevice3/restore/tss.py +385 -267
  86. pymobiledevice3/service_connection.py +252 -59
  87. pymobiledevice3/services/accessibilityaudit.py +202 -120
  88. pymobiledevice3/services/afc.py +962 -365
  89. pymobiledevice3/services/amfi.py +24 -30
  90. pymobiledevice3/services/companion.py +23 -19
  91. pymobiledevice3/services/crash_reports.py +71 -47
  92. pymobiledevice3/services/debugserver_applist.py +3 -3
  93. pymobiledevice3/services/device_arbitration.py +8 -8
  94. pymobiledevice3/services/device_link.py +101 -79
  95. pymobiledevice3/services/diagnostics.py +973 -967
  96. pymobiledevice3/services/dtfetchsymbols.py +8 -8
  97. pymobiledevice3/services/dvt/dvt_secure_socket_proxy.py +4 -4
  98. pymobiledevice3/services/dvt/dvt_testmanaged_proxy.py +4 -4
  99. pymobiledevice3/services/dvt/instruments/activity_trace_tap.py +85 -74
  100. pymobiledevice3/services/dvt/instruments/application_listing.py +2 -3
  101. pymobiledevice3/services/dvt/instruments/condition_inducer.py +7 -6
  102. pymobiledevice3/services/dvt/instruments/core_profile_session_tap.py +466 -384
  103. pymobiledevice3/services/dvt/instruments/device_info.py +20 -11
  104. pymobiledevice3/services/dvt/instruments/energy_monitor.py +1 -1
  105. pymobiledevice3/services/dvt/instruments/graphics.py +1 -1
  106. pymobiledevice3/services/dvt/instruments/location_simulation.py +1 -1
  107. pymobiledevice3/services/dvt/instruments/location_simulation_base.py +10 -10
  108. pymobiledevice3/services/dvt/instruments/network_monitor.py +17 -17
  109. pymobiledevice3/services/dvt/instruments/notifications.py +1 -1
  110. pymobiledevice3/services/dvt/instruments/process_control.py +35 -10
  111. pymobiledevice3/services/dvt/instruments/screenshot.py +2 -2
  112. pymobiledevice3/services/dvt/instruments/sysmontap.py +15 -15
  113. pymobiledevice3/services/dvt/testmanaged/xcuitest.py +42 -52
  114. pymobiledevice3/services/file_relay.py +10 -10
  115. pymobiledevice3/services/heartbeat.py +9 -8
  116. pymobiledevice3/services/house_arrest.py +16 -15
  117. pymobiledevice3/services/idam.py +20 -0
  118. pymobiledevice3/services/installation_proxy.py +173 -81
  119. pymobiledevice3/services/lockdown_service.py +20 -10
  120. pymobiledevice3/services/misagent.py +22 -19
  121. pymobiledevice3/services/mobile_activation.py +147 -64
  122. pymobiledevice3/services/mobile_config.py +331 -294
  123. pymobiledevice3/services/mobile_image_mounter.py +141 -113
  124. pymobiledevice3/services/mobilebackup2.py +203 -145
  125. pymobiledevice3/services/notification_proxy.py +11 -11
  126. pymobiledevice3/services/os_trace.py +134 -74
  127. pymobiledevice3/services/pcapd.py +314 -302
  128. pymobiledevice3/services/power_assertion.py +10 -9
  129. pymobiledevice3/services/preboard.py +4 -4
  130. pymobiledevice3/services/remote_fetch_symbols.py +21 -14
  131. pymobiledevice3/services/remote_server.py +176 -146
  132. pymobiledevice3/services/restore_service.py +16 -16
  133. pymobiledevice3/services/screenshot.py +15 -12
  134. pymobiledevice3/services/simulate_location.py +7 -7
  135. pymobiledevice3/services/springboard.py +15 -15
  136. pymobiledevice3/services/syslog.py +5 -5
  137. pymobiledevice3/services/web_protocol/alert.py +11 -11
  138. pymobiledevice3/services/web_protocol/automation_session.py +251 -239
  139. pymobiledevice3/services/web_protocol/cdp_screencast.py +46 -37
  140. pymobiledevice3/services/web_protocol/cdp_server.py +19 -19
  141. pymobiledevice3/services/web_protocol/cdp_target.py +411 -373
  142. pymobiledevice3/services/web_protocol/driver.py +114 -111
  143. pymobiledevice3/services/web_protocol/element.py +124 -111
  144. pymobiledevice3/services/web_protocol/inspector_session.py +106 -102
  145. pymobiledevice3/services/web_protocol/selenium_api.py +49 -49
  146. pymobiledevice3/services/web_protocol/session_protocol.py +18 -12
  147. pymobiledevice3/services/web_protocol/switch_to.py +30 -27
  148. pymobiledevice3/services/webinspector.py +189 -155
  149. pymobiledevice3/tcp_forwarder.py +87 -69
  150. pymobiledevice3/tunneld/__init__.py +0 -0
  151. pymobiledevice3/tunneld/api.py +63 -0
  152. pymobiledevice3/tunneld/server.py +603 -0
  153. pymobiledevice3/usbmux.py +198 -147
  154. pymobiledevice3/utils.py +14 -11
  155. {pymobiledevice3-4.14.6.dist-info → pymobiledevice3-7.0.6.dist-info}/METADATA +55 -28
  156. pymobiledevice3-7.0.6.dist-info/RECORD +188 -0
  157. {pymobiledevice3-4.14.6.dist-info → pymobiledevice3-7.0.6.dist-info}/WHEEL +1 -1
  158. pymobiledevice3/cli/developer.py +0 -1215
  159. pymobiledevice3/cli/diagnostics.py +0 -99
  160. pymobiledevice3/tunneld.py +0 -524
  161. pymobiledevice3-4.14.6.dist-info/RECORD +0 -168
  162. {pymobiledevice3-4.14.6.dist-info → pymobiledevice3-7.0.6.dist-info}/entry_points.txt +0 -0
  163. {pymobiledevice3-4.14.6.dist-info → pymobiledevice3-7.0.6.dist-info/licenses}/LICENSE +0 -0
  164. {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 ConnectionTerminatedError, DeviceNotFoundError, NoDeviceConnectedError, \
15
- PyMobileDevice3Exception
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 = '>', fmt: Enum = plistlib.FMT_XML) -> bytes:
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 + 'L', len(payload))
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 plistlib.InvalidFileException:
51
- raise PyMobileDevice3Exception(f'parse_plist invalid data: {payload[:100].hex()}')
52
-
53
-
54
- def create_context(certfile, keyfile=None):
55
- context = ssl.SSLContext(ssl.PROTOCOL_TLS_CLIENT)
56
- if ssl.OPENSSL_VERSION.lower().startswith('openssl'):
57
- context.set_ciphers('ALL:!aNULL:!eNULL:@SECLEVEL=0')
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
- """ wrapper for tcp-relay connections """
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(hostname: str, port: int, keep_alive: bool = True,
83
- create_connection_timeout: int = DEFAULT_TIMEOUT) -> 'ServiceConnection':
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(udid: Optional[str], port: int, connection_type: str = None,
92
- usbmux_address: Optional[str] = None) -> 'ServiceConnection':
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
- try:
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
- """ socket.recv() normal behavior. attempt to receive a single chunk """
120
- return self.socket.recv(length)
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='>', fmt=plistlib.FMT_XML) -> Any:
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='>', fmt=plistlib.FMT_XML) -> Any:
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
- data = b''
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='>') -> bytes:
146
- """ receive a data block prefixed with a u32 length field """
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 + 'L', size)[0]
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
- """ receive a payload """
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='>') -> bytes:
163
- """ receive a data block prefixed with a u32 length field """
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 + 'L', size)[0]
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
- """ send a data block prefixed with a u32 length field """
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('>L', len(data))
173
- msg = b''.join([hdr, data])
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='>') -> dict:
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='>') -> dict:
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='>', fmt=plistlib.FMT_XML) -> None:
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 = '>', fmt: Enum = plistlib.FMT_XML) -> None:
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 ssl_start(self, certfile, keyfile=None) -> None:
193
- self.socket = create_context(certfile, keyfile=keyfile).wrap_socket(self.socket)
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
- async def aio_ssl_start(self, certfile, keyfile=None) -> None:
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='native')),
383
+ header=highlight(SHELL_USAGE, lexers.PythonLexer(), formatters.Terminal256Formatter(style="native")),
208
384
  user_ns={
209
- 'client': self,
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