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
@@ -21,10 +21,8 @@ from socket import create_connection
21
21
  from ssl import VerifyMode
22
22
  from typing import Optional, TextIO, cast
23
23
 
24
- import aiofiles
25
- from construct import Const, Container
24
+ from construct import Const, Container, GreedyBytes, GreedyRange, Int8ul, Int16ub, Int64ul, Prefixed, Struct
26
25
  from construct import Enum as ConstructEnum
27
- from construct import GreedyBytes, GreedyRange, Int8ul, Int16ub, Int64ul, Prefixed, Struct
28
26
  from cryptography.hazmat.primitives import hashes
29
27
  from cryptography.hazmat.primitives._serialization import Encoding, NoEncryption, PrivateFormat, PublicFormat
30
28
  from cryptography.hazmat.primitives.asymmetric import rsa
@@ -33,7 +31,7 @@ from cryptography.hazmat.primitives.asymmetric.rsa import RSAPrivateKey
33
31
  from cryptography.hazmat.primitives.asymmetric.x25519 import X25519PrivateKey, X25519PublicKey
34
32
  from cryptography.hazmat.primitives.ciphers.aead import ChaCha20Poly1305
35
33
  from cryptography.hazmat.primitives.kdf.hkdf import HKDF
36
- from opack import dumps
34
+ from opack2 import dumps
37
35
  from packaging.version import Version
38
36
  from pytun_pmd3 import TunTapDevice
39
37
  from qh3.asyncio import QuicConnectionProtocol
@@ -56,9 +54,20 @@ except ImportError:
56
54
 
57
55
  from pymobiledevice3.bonjour import DEFAULT_BONJOUR_TIMEOUT, browse_remotepairing
58
56
  from pymobiledevice3.ca import make_cert
59
- from pymobiledevice3.exceptions import PairingError, PyMobileDevice3Exception, UserDeniedPairingError
60
- from pymobiledevice3.pair_records import PAIRING_RECORD_EXT, create_pairing_records_cache_folder, generate_host_id, \
61
- get_remote_pairing_record_filename, iter_remote_paired_identifiers
57
+ from pymobiledevice3.exceptions import (
58
+ PairingError,
59
+ PyMobileDevice3Exception,
60
+ QuicProtocolNotSupportedError,
61
+ RemotePairingCompletedError,
62
+ UserDeniedPairingError,
63
+ )
64
+ from pymobiledevice3.pair_records import (
65
+ PAIRING_RECORD_EXT,
66
+ create_pairing_records_cache_folder,
67
+ generate_host_id,
68
+ get_remote_pairing_record_filename,
69
+ iter_remote_paired_identifiers,
70
+ )
62
71
  from pymobiledevice3.remote.common import TunnelProtocol
63
72
  from pymobiledevice3.remote.remote_service import RemoteService
64
73
  from pymobiledevice3.remote.remote_service_discovery import RemoteServiceDiscoveryService
@@ -67,6 +76,7 @@ from pymobiledevice3.remote.xpc_message import XpcInt64Type, XpcUInt64Type
67
76
  from pymobiledevice3.service_connection import ServiceConnection
68
77
  from pymobiledevice3.utils import asyncio_print_traceback
69
78
 
79
+ DEFAULT_INTERFACE_NAME = "pymobiledevice3-tunnel"
70
80
  TIMEOUT = 1
71
81
 
72
82
  OSUTIL = get_os_utils()
@@ -80,58 +90,60 @@ UDP_HEADER_SIZE = 8
80
90
  IOS_DEVICE_MTU_SIZE = 1500
81
91
  packet_builder.PACKET_MAX_SIZE = IOS_DEVICE_MTU_SIZE - IPV6_HEADER_SIZE - UDP_HEADER_SIZE
82
92
 
83
- PairingDataComponentType = ConstructEnum(Int8ul,
84
- METHOD=0x00,
85
- IDENTIFIER=0x01,
86
- SALT=0x02,
87
- PUBLIC_KEY=0x03,
88
- PROOF=0x04,
89
- ENCRYPTED_DATA=0x05,
90
- STATE=0x06,
91
- ERROR=0x07,
92
- RETRY_DELAY=0x08,
93
- CERTIFICATE=0x09,
94
- SIGNATURE=0x0a,
95
- PERMISSIONS=0x0b,
96
- FRAGMENT_DATA=0x0c,
97
- FRAGMENT_LAST=0x0d,
98
- SESSION_ID=0x0e,
99
- TTL=0x0f,
100
- EXTRA_DATA=0x10,
101
- INFO=0x11,
102
- ACL=0x12,
103
- FLAGS=0x13,
104
- VALIDATION_DATA=0x14,
105
- MFI_AUTH_TOKEN=0x15,
106
- MFI_PRODUCT_TYPE=0x16,
107
- SERIAL_NUMBER=0x17,
108
- MFI_AUTH_TOKEN_UUID=0x18,
109
- APP_FLAGS=0x19,
110
- OWNERSHIP_PROOF=0x1a,
111
- SETUP_CODE_TYPE=0x1b,
112
- PRODUCTION_DATA=0x1c,
113
- APP_INFO=0x1d,
114
- SEPARATOR=0xff)
93
+ PairingDataComponentType = ConstructEnum(
94
+ Int8ul,
95
+ METHOD=0x00,
96
+ IDENTIFIER=0x01,
97
+ SALT=0x02,
98
+ PUBLIC_KEY=0x03,
99
+ PROOF=0x04,
100
+ ENCRYPTED_DATA=0x05,
101
+ STATE=0x06,
102
+ ERROR=0x07,
103
+ RETRY_DELAY=0x08,
104
+ CERTIFICATE=0x09,
105
+ SIGNATURE=0x0A,
106
+ PERMISSIONS=0x0B,
107
+ FRAGMENT_DATA=0x0C,
108
+ FRAGMENT_LAST=0x0D,
109
+ SESSION_ID=0x0E,
110
+ TTL=0x0F,
111
+ EXTRA_DATA=0x10,
112
+ INFO=0x11,
113
+ ACL=0x12,
114
+ FLAGS=0x13,
115
+ VALIDATION_DATA=0x14,
116
+ MFI_AUTH_TOKEN=0x15,
117
+ MFI_PRODUCT_TYPE=0x16,
118
+ SERIAL_NUMBER=0x17,
119
+ MFI_AUTH_TOKEN_UUID=0x18,
120
+ APP_FLAGS=0x19,
121
+ OWNERSHIP_PROOF=0x1A,
122
+ SETUP_CODE_TYPE=0x1B,
123
+ PRODUCTION_DATA=0x1C,
124
+ APP_INFO=0x1D,
125
+ SEPARATOR=0xFF,
126
+ )
115
127
 
116
128
  PairingDataComponentTLV8 = Struct(
117
- 'type' / PairingDataComponentType,
118
- 'data' / Prefixed(Int8ul, GreedyBytes),
129
+ "type" / PairingDataComponentType,
130
+ "data" / Prefixed(Int8ul, GreedyBytes),
119
131
  )
120
132
 
121
133
  PairingDataComponentTLVBuf = GreedyRange(PairingDataComponentTLV8)
122
134
 
123
- PairConsentResult = namedtuple('PairConsentResult', 'public_key salt pin')
135
+ PairConsentResult = namedtuple("PairConsentResult", "public_key salt pin")
124
136
 
125
137
  CDTunnelPacket = Struct(
126
- 'magic' / Const(b'CDTunnel'),
127
- 'body' / Prefixed(Int16ub, GreedyBytes),
138
+ "magic" / Const(b"CDTunnel"),
139
+ "body" / Prefixed(Int16ub, GreedyBytes),
128
140
  )
129
141
 
130
- REPAIRING_PACKET_MAGIC = b'RPPairing'
142
+ REPAIRING_PACKET_MAGIC = b"RPPairing"
131
143
 
132
144
  RPPairingPacket = Struct(
133
- 'magic' / Const(REPAIRING_PACKET_MAGIC),
134
- 'body' / Prefixed(Int16ub, GreedyBytes),
145
+ "magic" / Const(REPAIRING_PACKET_MAGIC),
146
+ "body" / Prefixed(Int16ub, GreedyBytes),
135
147
  )
136
148
 
137
149
 
@@ -139,7 +151,7 @@ class RemotePairingTunnel(ABC):
139
151
  def __init__(self):
140
152
  self._queue = asyncio.Queue()
141
153
  self._tun_read_task = None
142
- self._logger = logging.getLogger(f'{__name__}.{self.__class__.__name__}')
154
+ self._logger = logging.getLogger(f"{__name__}.{self.__class__.__name__}")
143
155
  self.tun = None
144
156
 
145
157
  @abstractmethod
@@ -158,32 +170,38 @@ class RemotePairingTunnel(ABC):
158
170
  async def tun_read_task(self) -> None:
159
171
  read_size = self.tun.mtu + len(LOOPBACK_HEADER)
160
172
  try:
161
- if sys.platform != 'win32':
162
- async with aiofiles.open(self.tun.fileno(), 'rb', opener=lambda path, flags: path, buffering=0) as f:
163
- while True:
164
- packet = await f.read(read_size)
165
- assert packet.startswith(LOOPBACK_HEADER)
166
- packet = packet[len(LOOPBACK_HEADER):]
167
- await self.send_packet_to_device(packet)
173
+ if sys.platform != "win32":
174
+ while True:
175
+ packet = await asyncio.to_thread(self.tun.read, read_size)
176
+ assert packet.startswith(LOOPBACK_HEADER)
177
+ packet = packet[len(LOOPBACK_HEADER) :]
178
+ await self.send_packet_to_device(packet)
168
179
  else:
169
180
  while True:
170
- packet = await asyncio.get_running_loop().run_in_executor(None, self.tun.read)
181
+ packet = await self.tun.async_read()
171
182
  if packet:
183
+ if (packet[0] >> 4) != 6:
184
+ # Make sure to output only IPv6 packets
185
+ continue
172
186
  await self.send_packet_to_device(packet)
173
187
  except ConnectionResetError:
174
- self._logger.warning(f'got connection reset in {asyncio.current_task().get_name()}')
188
+ self._logger.warning(f"got connection reset in {asyncio.current_task().get_name()}")
175
189
  except OSError:
176
- self._logger.warning(f'got oserror in {asyncio.current_task().get_name()}')
190
+ self._logger.warning(f"got oserror in {asyncio.current_task().get_name()}")
177
191
 
178
- def start_tunnel(self, address: str, mtu: int) -> None:
179
- self.tun = TunTapDevice()
192
+ def start_tunnel(self, address: str, mtu: int, interface_name=DEFAULT_INTERFACE_NAME) -> None:
193
+ if sys.platform == "win32":
194
+ # Only win32 tunnel implementation supports interface name
195
+ self.tun = TunTapDevice(interface_name)
196
+ else:
197
+ self.tun = TunTapDevice()
180
198
  self.tun.addr = address
181
199
  self.tun.mtu = mtu
182
200
  self.tun.up()
183
- self._tun_read_task = asyncio.create_task(self.tun_read_task(), name=f'tun-read-{address}')
201
+ self._tun_read_task = asyncio.create_task(self.tun_read_task(), name=f"tun-read-{address}")
184
202
 
185
203
  async def stop_tunnel(self) -> None:
186
- self._logger.debug('stopping tunnel')
204
+ self._logger.debug(f"[{asyncio.current_task().get_name()}] stopping tunnel")
187
205
  self._tun_read_task.cancel()
188
206
  with suppress(CancelledError):
189
207
  await self._tun_read_task
@@ -193,7 +211,7 @@ class RemotePairingTunnel(ABC):
193
211
 
194
212
  @staticmethod
195
213
  def _encode_cdtunnel_packet(data: dict) -> bytes:
196
- return CDTunnelPacket.build({'body': json.dumps(data).encode()})
214
+ return CDTunnelPacket.build({"body": json.dumps(data).encode()})
197
215
 
198
216
 
199
217
  class RemotePairingQuicTunnel(RemotePairingTunnel, QuicConnectionProtocol):
@@ -207,21 +225,23 @@ class RemotePairingQuicTunnel(RemotePairingTunnel, QuicConnectionProtocol):
207
225
  self._keep_alive_task = None
208
226
 
209
227
  async def wait_closed(self) -> None:
210
- try:
228
+ with suppress(asyncio.CancelledError):
211
229
  await QuicConnectionProtocol.wait_closed(self)
212
- except asyncio.CancelledError:
213
- pass
214
230
 
215
231
  async def send_packet_to_device(self, packet: bytes) -> None:
216
232
  self._quic.send_datagram_frame(packet)
217
233
  self.transmit()
218
234
 
235
+ # Allow other tasks to run
236
+ await asyncio.sleep(0)
237
+
219
238
  async def request_tunnel_establish(self) -> dict:
220
239
  stream_id = self._quic.get_next_available_stream_id()
221
240
  # pad the data with random data to force the MTU size correctly
222
- self._quic.send_datagram_frame(b'x' * 1024)
223
- self._quic.send_stream_data(stream_id, self._encode_cdtunnel_packet(
224
- {'type': 'clientHandshakeRequest', 'mtu': self.REQUESTED_MTU}))
241
+ self._quic.send_datagram_frame(b"x" * 1024)
242
+ self._quic.send_stream_data(
243
+ stream_id, self._encode_cdtunnel_packet({"type": "clientHandshakeRequest", "mtu": self.REQUESTED_MTU})
244
+ )
225
245
  self.transmit()
226
246
  return await self._queue.get()
227
247
 
@@ -231,8 +251,8 @@ class RemotePairingQuicTunnel(RemotePairingTunnel, QuicConnectionProtocol):
231
251
  await self.ping()
232
252
  await asyncio.sleep(self._quic.configuration.idle_timeout / 2)
233
253
 
234
- def start_tunnel(self, address: str, mtu: int) -> None:
235
- super().start_tunnel(address, mtu)
254
+ def start_tunnel(self, address: str, mtu: int, interface_name=DEFAULT_INTERFACE_NAME) -> None:
255
+ super().start_tunnel(address, mtu, interface_name=interface_name)
236
256
  self._keep_alive_task = asyncio.create_task(self.keep_alive_task())
237
257
 
238
258
  async def stop_tunnel(self) -> None:
@@ -251,7 +271,7 @@ class RemotePairingQuicTunnel(RemotePairingTunnel, QuicConnectionProtocol):
251
271
 
252
272
  @staticmethod
253
273
  def _encode_cdtunnel_packet(data: dict) -> bytes:
254
- return CDTunnelPacket.build({'body': json.dumps(data).encode()})
274
+ return CDTunnelPacket.build({"body": json.dumps(data).encode()})
255
275
 
256
276
 
257
277
  class RemotePairingTcpTunnel(RemotePairingTunnel):
@@ -273,42 +293,37 @@ class RemotePairingTcpTunnel(RemotePairingTunnel):
273
293
  while True:
274
294
  try:
275
295
  ipv6_header = await self._reader.readexactly(IPV6_HEADER_SIZE)
276
- ipv6_length = struct.unpack('>H', ipv6_header[4:6])[0]
296
+ ipv6_length = struct.unpack(">H", ipv6_header[4:6])[0]
277
297
  ipv6_body = await self._reader.readexactly(ipv6_length)
278
298
  self.tun.write(LOOPBACK_HEADER + ipv6_header + ipv6_body)
279
299
  except asyncio.exceptions.IncompleteReadError:
280
300
  await asyncio.sleep(1)
281
301
  except OSError as e:
282
- self._logger.warning(f'got {e.__class__.__name__} in {asyncio.current_task().get_name()}')
302
+ self._logger.warning(f"got {e.__class__.__name__} in {asyncio.current_task().get_name()}")
283
303
  await self.wait_closed()
284
304
 
285
305
  async def wait_closed(self) -> None:
286
- try:
306
+ with suppress(OSError):
287
307
  await self._writer.wait_closed()
288
- except OSError:
289
- pass
290
308
 
291
309
  async def request_tunnel_establish(self) -> dict:
292
- self._writer.write(self._encode_cdtunnel_packet(
293
- {'type': 'clientHandshakeRequest', 'mtu': self.REQUESTED_MTU}))
310
+ self._writer.write(self._encode_cdtunnel_packet({"type": "clientHandshakeRequest", "mtu": self.REQUESTED_MTU}))
294
311
  await self._writer.drain()
295
312
  return json.loads(CDTunnelPacket.parse(await self._reader.read(self.REQUESTED_MTU)).body)
296
313
 
297
- def start_tunnel(self, address: str, mtu: int) -> None:
298
- super().start_tunnel(address, mtu)
299
- self._sock_read_task = asyncio.create_task(self.sock_read_task(), name=f'sock-read-task-{address}')
314
+ def start_tunnel(self, address: str, mtu: int, interface_name=DEFAULT_INTERFACE_NAME) -> None:
315
+ super().start_tunnel(address, mtu, interface_name=interface_name)
316
+ self._sock_read_task = asyncio.create_task(self.sock_read_task(), name=f"sock-read-task-{address}")
300
317
 
301
318
  async def stop_tunnel(self) -> None:
302
319
  self._sock_read_task.cancel()
303
320
  with suppress(CancelledError):
304
321
  await self._sock_read_task
322
+ await super().stop_tunnel()
305
323
  if not self._writer.is_closing():
306
324
  self._writer.close()
307
- try:
325
+ with suppress(OSError):
308
326
  await self._writer.wait_closed()
309
- except OSError:
310
- pass
311
- await super().stop_tunnel()
312
327
 
313
328
 
314
329
  @dataclasses.dataclass
@@ -369,104 +384,156 @@ class RemotePairingProtocol(StartTcpTunnel):
369
384
  async def connect(self, autopair: bool = True) -> None:
370
385
  await self._attempt_pair_verify()
371
386
 
372
- if not await self._validate_pairing():
373
- if autopair:
374
- await self._pair()
375
- self._init_client_server_main_encryption_keys()
387
+ if await self._validate_pairing():
388
+ # Pairing record validation succeeded, so we can just initiate the relevant session keys
389
+ self._init_client_server_main_encryption_keys()
390
+ return
391
+
392
+ if autopair:
393
+ await self._pair()
394
+ await self.close()
395
+
396
+ # Once pairing is completed, the remote endpoint closes the connection, so it must be re-established
397
+ raise RemotePairingCompletedError()
376
398
 
377
399
  async def create_quic_listener(self, private_key: RSAPrivateKey) -> dict:
378
- request = {'request': {'_0': {'createListener': {
379
- 'key': base64.b64encode(
380
- private_key.public_key().public_bytes(Encoding.DER, PublicFormat.SubjectPublicKeyInfo)
381
- ).decode(),
382
- 'peerConnectionsInfo': [{'owningPID': os.getpid(), 'owningProcessName': 'CoreDeviceService'}],
383
- 'transportProtocolType': 'quic'}}}}
400
+ request = {
401
+ "request": {
402
+ "_0": {
403
+ "createListener": {
404
+ "key": base64.b64encode(
405
+ private_key.public_key().public_bytes(Encoding.DER, PublicFormat.SubjectPublicKeyInfo)
406
+ ).decode(),
407
+ "peerConnectionsInfo": [{"owningPID": os.getpid(), "owningProcessName": "CoreDeviceService"}],
408
+ "transportProtocolType": "quic",
409
+ }
410
+ }
411
+ }
412
+ }
384
413
 
385
414
  response = await self._send_receive_encrypted_request(request)
386
- return response['createListener']
415
+ return response["createListener"]
387
416
 
388
417
  async def create_tcp_listener(self) -> dict:
389
- request = {'request': {'_0': {'createListener': {
390
- 'key': base64.b64encode(self.encryption_key).decode(),
391
- 'peerConnectionsInfo': [{'owningPID': os.getpid(), 'owningProcessName': 'CoreDeviceService'}],
392
- 'transportProtocolType': 'tcp'}}}}
418
+ request = {
419
+ "request": {
420
+ "_0": {
421
+ "createListener": {
422
+ "key": base64.b64encode(self.encryption_key).decode(),
423
+ "peerConnectionsInfo": [{"owningPID": os.getpid(), "owningProcessName": "CoreDeviceService"}],
424
+ "transportProtocolType": "tcp",
425
+ }
426
+ }
427
+ }
428
+ }
393
429
  response = await self._send_receive_encrypted_request(request)
394
- return response['createListener']
430
+ return response["createListener"]
395
431
 
396
432
  @asynccontextmanager
397
433
  async def start_quic_tunnel(
398
- self, secrets_log_file: Optional[TextIO] = None,
399
- max_idle_timeout: float = RemotePairingQuicTunnel.MAX_IDLE_TIMEOUT) -> AsyncGenerator[TunnelResult, None]:
434
+ self,
435
+ secrets_log_file: Optional[TextIO] = None,
436
+ max_idle_timeout: float = RemotePairingQuicTunnel.MAX_IDLE_TIMEOUT,
437
+ ) -> AsyncGenerator[TunnelResult, None]:
400
438
  private_key = rsa.generate_private_key(public_exponent=65537, key_size=2048)
401
439
  parameters = await self.create_quic_listener(private_key)
402
440
  cert = make_cert(private_key, private_key.public_key())
403
441
  configuration = QuicConfiguration(
404
- alpn_protocols=['RemotePairingTunnelProtocol'],
442
+ alpn_protocols=["RemotePairingTunnelProtocol"],
405
443
  is_client=True,
406
444
  verify_mode=VerifyMode.CERT_NONE,
407
445
  verify_hostname=False,
408
446
  max_datagram_frame_size=RemotePairingQuicTunnel.MAX_QUIC_DATAGRAM,
409
- idle_timeout=max_idle_timeout
447
+ idle_timeout=max_idle_timeout,
448
+ )
449
+ configuration.load_cert_chain(
450
+ cert.public_bytes(Encoding.PEM),
451
+ private_key.private_bytes(Encoding.PEM, PrivateFormat.TraditionalOpenSSL, NoEncryption()).decode(),
410
452
  )
411
- configuration.load_cert_chain(cert.public_bytes(Encoding.PEM),
412
- private_key.private_bytes(Encoding.PEM, PrivateFormat.TraditionalOpenSSL,
413
- NoEncryption()).decode())
414
453
  configuration.secrets_log_file = secrets_log_file
415
454
 
416
455
  host = self.hostname
417
- port = parameters['port']
456
+ port = parameters["port"]
418
457
 
419
- self.logger.debug(f'Connecting to {host}:{port}')
420
- async with aioquic_connect(
458
+ self.logger.debug(f"Connecting to {host}:{port}")
459
+ try:
460
+ async with aioquic_connect(
421
461
  host,
422
462
  port,
423
463
  configuration=configuration,
424
464
  create_protocol=RemotePairingQuicTunnel,
425
- ) as client:
426
- self.logger.debug('quic connected')
427
- client = cast(RemotePairingQuicTunnel, client)
428
- await client.wait_connected()
429
- handshake_response = await client.request_tunnel_establish()
430
- client.start_tunnel(handshake_response['clientParameters']['address'],
431
- handshake_response['clientParameters']['mtu'])
432
- try:
433
- yield TunnelResult(
434
- client.tun.name, handshake_response['serverAddress'], handshake_response['serverRSDPort'],
435
- TunnelProtocol.QUIC, client)
436
- finally:
437
- await client.stop_tunnel()
465
+ ) as client:
466
+ self.logger.debug("quic connected")
467
+ client = cast(RemotePairingQuicTunnel, client)
468
+ await client.wait_connected()
469
+ handshake_response = await client.request_tunnel_establish()
470
+ client.start_tunnel(
471
+ handshake_response["clientParameters"]["address"],
472
+ handshake_response["clientParameters"]["mtu"],
473
+ interface_name=f"{DEFAULT_INTERFACE_NAME}-{self.remote_identifier}",
474
+ )
475
+ try:
476
+ yield TunnelResult(
477
+ client.tun.name,
478
+ handshake_response["serverAddress"],
479
+ handshake_response["serverRSDPort"],
480
+ TunnelProtocol.QUIC,
481
+ client,
482
+ )
483
+ finally:
484
+ await client.stop_tunnel()
485
+ except ConnectionError as e:
486
+ raise QuicProtocolNotSupportedError(
487
+ "iOS 18.2+ removed QUIC protocol support. Use TCP instead (requires python3.13+)"
488
+ ) from e
438
489
 
439
490
  @asynccontextmanager
440
491
  async def start_tcp_tunnel(self) -> AsyncGenerator[TunnelResult, None]:
441
492
  parameters = await self.create_tcp_listener()
442
493
  host = self.hostname
443
- port = parameters['port']
494
+ port = parameters["port"]
444
495
  sock = create_connection((host, port))
445
496
  OSUTIL.set_keepalive(sock)
446
- ctx = SSLPSKContext(ssl.PROTOCOL_TLSv1_2)
447
- ctx.psk = self.encryption_key
448
- ctx.set_ciphers('PSK')
449
- reader, writer = await asyncio.open_connection(sock=sock, ssl=ctx, server_hostname='')
497
+ if sys.version_info >= (3, 13):
498
+ ctx = ssl.SSLContext(ssl.PROTOCOL_TLSv1_2)
499
+ ctx.check_hostname = False
500
+ ctx.verify_mode = ssl.CERT_NONE
501
+ ctx.set_ciphers("PSK")
502
+ ctx.set_psk_client_callback(lambda hint: (None, self.encryption_key))
503
+ else:
504
+ # TODO: remove this when python3.12 becomes deprecated
505
+ ctx = SSLPSKContext(ssl.PROTOCOL_TLSv1_2)
506
+ ctx.psk = self.encryption_key
507
+ ctx.set_ciphers("PSK")
508
+ reader, writer = await asyncio.open_connection(sock=sock, ssl=ctx, server_hostname="")
450
509
  tunnel = RemotePairingTcpTunnel(reader, writer)
451
510
  handshake_response = await tunnel.request_tunnel_establish()
452
511
 
453
- tunnel.start_tunnel(handshake_response['clientParameters']['address'],
454
- handshake_response['clientParameters']['mtu'])
512
+ tunnel.start_tunnel(
513
+ handshake_response["clientParameters"]["address"],
514
+ handshake_response["clientParameters"]["mtu"],
515
+ interface_name=f"{DEFAULT_INTERFACE_NAME}-{self.remote_identifier}",
516
+ )
455
517
 
456
518
  try:
457
519
  yield TunnelResult(
458
- tunnel.tun.name, handshake_response['serverAddress'], handshake_response['serverRSDPort'],
459
- TunnelProtocol.TCP, tunnel)
520
+ tunnel.tun.name,
521
+ handshake_response["serverAddress"],
522
+ handshake_response["serverRSDPort"],
523
+ TunnelProtocol.TCP,
524
+ tunnel,
525
+ )
460
526
  finally:
461
527
  await tunnel.stop_tunnel()
462
528
 
463
529
  def save_pair_record(self) -> None:
464
530
  self.pair_record_path.write_bytes(
465
531
  plistlib.dumps({
466
- 'public_key': self.ed25519_private_key.public_key().public_bytes_raw(),
467
- 'private_key': self.ed25519_private_key.private_bytes_raw(),
468
- 'remote_unlock_host_key': self.remote_unlock_host_key
469
- }))
532
+ "public_key": self.ed25519_private_key.public_key().public_bytes_raw(),
533
+ "private_key": self.ed25519_private_key.private_bytes_raw(),
534
+ "remote_unlock_host_key": self.remote_unlock_host_key,
535
+ })
536
+ )
470
537
  OSUTIL.chown_to_non_sudo_if_needed(self.pair_record_path)
471
538
 
472
539
  @property
@@ -477,17 +544,19 @@ class RemotePairingProtocol(StartTcpTunnel):
477
544
 
478
545
  @property
479
546
  def remote_identifier(self) -> str:
480
- return self.handshake_info['peerDeviceInfo']['identifier']
547
+ return self.handshake_info["peerDeviceInfo"]["identifier"]
481
548
 
482
549
  @property
483
550
  def remote_device_model(self) -> str:
484
- return self.handshake_info['peerDeviceInfo']['model']
551
+ return self.handshake_info["peerDeviceInfo"]["model"]
485
552
 
486
553
  @property
487
554
  def pair_record_path(self) -> Path:
488
555
  pair_records_cache_directory = create_pairing_records_cache_folder()
489
- return (pair_records_cache_directory /
490
- f'{get_remote_pairing_record_filename(self.remote_identifier)}.{PAIRING_RECORD_EXT}')
556
+ return (
557
+ pair_records_cache_directory
558
+ / f"{get_remote_pairing_record_filename(self.remote_identifier)}.{PAIRING_RECORD_EXT}"
559
+ )
491
560
 
492
561
  async def _pair(self) -> None:
493
562
  pairing_consent_result = await self._request_pair_consent()
@@ -499,47 +568,49 @@ class RemotePairingProtocol(StartTcpTunnel):
499
568
  self.save_pair_record()
500
569
 
501
570
  async def _request_pair_consent(self) -> PairConsentResult:
502
- """ Display a Trust / Don't Trust dialog """
571
+ """Display a Trust / Don't Trust dialog"""
503
572
 
504
573
  tlv = PairingDataComponentTLVBuf.build([
505
- {'type': PairingDataComponentType.METHOD, 'data': b'\x00'},
506
- {'type': PairingDataComponentType.STATE, 'data': b'\x01'},
574
+ {"type": PairingDataComponentType.METHOD, "data": b"\x00"},
575
+ {"type": PairingDataComponentType.STATE, "data": b"\x01"},
507
576
  ])
508
577
 
509
- await self._send_pairing_data({'data': tlv,
510
- 'kind': 'setupManualPairing',
511
- 'sendingHost': platform.node(),
512
- 'startNewSession': True})
513
- self.logger.info('Waiting user pairing consent')
578
+ await self._send_pairing_data({
579
+ "data": tlv,
580
+ "kind": "setupManualPairing",
581
+ "sendingHost": platform.node(),
582
+ "startNewSession": True,
583
+ })
584
+ self.logger.info("Waiting user pairing consent")
514
585
  response = await self._receive_plain_response()
515
- response = response['event']['_0']
586
+ response = response["event"]["_0"]
516
587
 
517
588
  pin = None
518
- if 'pairingRejectedWithError' in response:
589
+ if "pairingRejectedWithError" in response:
519
590
  raise PairingError(
520
- response['pairingRejectedWithError']['wrappedError']['userInfo']['NSLocalizedDescription'])
521
- elif 'awaitingUserConsent' in response:
591
+ response["pairingRejectedWithError"]["wrappedError"]["userInfo"]["NSLocalizedDescription"]
592
+ )
593
+ elif "awaitingUserConsent" in response:
522
594
  pairing_data = await self._receive_pairing_data()
523
595
  else:
524
596
  # On tvOS no consent is needed and pairing data is returned immediately.
525
- pairing_data = self._decode_bytes_if_needed(response['pairingData']['_0']['data'])
597
+ pairing_data = self._decode_bytes_if_needed(response["pairingData"]["_0"]["data"])
526
598
  # On tvOS we need pin to setup pairing.
527
- if 'AppleTV' in self.remote_device_model:
528
- pin = input('Enter PIN: ')
599
+ if "AppleTV" in self.remote_device_model:
600
+ pin = input("Enter PIN: ")
529
601
 
530
602
  data = self.decode_tlv(PairingDataComponentTLVBuf.parse(pairing_data))
531
- return PairConsentResult(public_key=data[PairingDataComponentType.PUBLIC_KEY],
532
- salt=data[PairingDataComponentType.SALT],
533
- pin=pin)
603
+ return PairConsentResult(
604
+ public_key=data[PairingDataComponentType.PUBLIC_KEY], salt=data[PairingDataComponentType.SALT], pin=pin
605
+ )
534
606
 
535
607
  def _init_srp_context(self, pairing_consent_result: PairConsentResult) -> None:
536
608
  # Receive server public and salt and process them.
537
- pin = pairing_consent_result.pin or '000000'
609
+ pin = pairing_consent_result.pin or "000000"
538
610
  client_session = SRPClientSession(
539
- SRPContext('Pair-Setup', password=pin, prime=PRIME_3072, generator=PRIME_3072_GEN,
540
- hash_func=hashlib.sha512))
541
- client_session.process(pairing_consent_result.public_key.hex(),
542
- pairing_consent_result.salt.hex())
611
+ SRPContext("Pair-Setup", password=pin, prime=PRIME_3072, generator=PRIME_3072_GEN, hash_func=hashlib.sha512)
612
+ )
613
+ client_session.process(pairing_consent_result.public_key.hex(), pairing_consent_result.salt.hex())
543
614
  self.srp_context = client_session
544
615
  self.encryption_key = binascii.unhexlify(self.srp_context.key)
545
616
 
@@ -548,17 +619,18 @@ class RemotePairingProtocol(StartTcpTunnel):
548
619
  client_session_key_proof = binascii.unhexlify(self.srp_context.key_proof)
549
620
 
550
621
  tlv = PairingDataComponentTLVBuf.build([
551
- {'type': PairingDataComponentType.STATE, 'data': b'\x03'},
552
- {'type': PairingDataComponentType.PUBLIC_KEY, 'data': client_public[:255]},
553
- {'type': PairingDataComponentType.PUBLIC_KEY, 'data': client_public[255:]},
554
- {'type': PairingDataComponentType.PROOF, 'data': client_session_key_proof},
622
+ {"type": PairingDataComponentType.STATE, "data": b"\x03"},
623
+ {"type": PairingDataComponentType.PUBLIC_KEY, "data": client_public[:255]},
624
+ {"type": PairingDataComponentType.PUBLIC_KEY, "data": client_public[255:]},
625
+ {"type": PairingDataComponentType.PROOF, "data": client_session_key_proof},
555
626
  ])
556
627
 
557
628
  response = await self._send_receive_pairing_data({
558
- 'data': tlv,
559
- 'kind': 'setupManualPairing',
560
- 'sendingHost': platform.node(),
561
- 'startNewSession': False})
629
+ "data": tlv,
630
+ "kind": "setupManualPairing",
631
+ "sendingHost": platform.node(),
632
+ "startNewSession": False,
633
+ })
562
634
  data = self.decode_tlv(PairingDataComponentTLVBuf.parse(response))
563
635
  assert self.srp_context.verify_proof(data[PairingDataComponentType.PROOF].hex().encode())
564
636
 
@@ -568,8 +640,8 @@ class RemotePairingProtocol(StartTcpTunnel):
568
640
  setup_encryption_key = HKDF(
569
641
  algorithm=hashes.SHA512(),
570
642
  length=32,
571
- salt=b'Pair-Setup-Encrypt-Salt',
572
- info=b'Pair-Setup-Encrypt-Info',
643
+ salt=b"Pair-Setup-Encrypt-Salt",
644
+ info=b"Pair-Setup-Encrypt-Info",
573
645
  ).derive(self.encryption_key)
574
646
 
575
647
  self.ed25519_private_key = Ed25519PrivateKey.generate()
@@ -579,8 +651,8 @@ class RemotePairingProtocol(StartTcpTunnel):
579
651
  signbuf = HKDF(
580
652
  algorithm=hashes.SHA512(),
581
653
  length=32,
582
- salt=b'Pair-Setup-Controller-Sign-Salt',
583
- info=b'Pair-Setup-Controller-Sign-Info',
654
+ salt=b"Pair-Setup-Controller-Sign-Salt",
655
+ info=b"Pair-Setup-Controller-Sign-Info",
584
656
  ).derive(self.encryption_key)
585
657
 
586
658
  signbuf += self.identifier.encode()
@@ -589,41 +661,45 @@ class RemotePairingProtocol(StartTcpTunnel):
589
661
  self.signature = self.ed25519_private_key.sign(signbuf)
590
662
 
591
663
  device_info = dumps({
592
- 'altIRK': b'\xe9\xe8-\xc0jIykVoT\x00\x19\xb1\xc7{',
593
- 'btAddr': '11:22:33:44:55:66',
594
- 'mac': b'\x11\x22\x33\x44\x55\x66',
595
- 'remotepairing_serial_number': 'AAAAAAAAAAAA',
596
- 'accountID': self.identifier,
597
- 'model': 'computer-model',
598
- 'name': platform.node()
664
+ "altIRK": b"\xe9\xe8-\xc0jIykVoT\x00\x19\xb1\xc7{",
665
+ "btAddr": "11:22:33:44:55:66",
666
+ "mac": b"\x11\x22\x33\x44\x55\x66",
667
+ "remotepairing_serial_number": "AAAAAAAAAAAA",
668
+ "accountID": self.identifier,
669
+ "model": "computer-model",
670
+ "name": platform.node(),
599
671
  })
600
672
 
601
673
  tlv = PairingDataComponentTLVBuf.build([
602
- {'type': PairingDataComponentType.IDENTIFIER, 'data': self.identifier.encode()},
603
- {'type': PairingDataComponentType.PUBLIC_KEY,
604
- 'data': self.ed25519_private_key.public_key().public_bytes_raw()},
605
- {'type': PairingDataComponentType.SIGNATURE, 'data': self.signature},
606
- {'type': PairingDataComponentType.INFO, 'data': device_info},
674
+ {"type": PairingDataComponentType.IDENTIFIER, "data": self.identifier.encode()},
675
+ {
676
+ "type": PairingDataComponentType.PUBLIC_KEY,
677
+ "data": self.ed25519_private_key.public_key().public_bytes_raw(),
678
+ },
679
+ {"type": PairingDataComponentType.SIGNATURE, "data": self.signature},
680
+ {"type": PairingDataComponentType.INFO, "data": device_info},
607
681
  ])
608
682
 
609
683
  cip = ChaCha20Poly1305(setup_encryption_key)
610
- encrypted_data = cip.encrypt(b'\x00\x00\x00\x00PS-Msg05', tlv, b'')
684
+ encrypted_data = cip.encrypt(b"\x00\x00\x00\x00PS-Msg05", tlv, b"")
611
685
 
612
686
  tlv = PairingDataComponentTLVBuf.build([
613
- {'type': PairingDataComponentType.ENCRYPTED_DATA, 'data': encrypted_data[:255]},
614
- {'type': PairingDataComponentType.ENCRYPTED_DATA, 'data': encrypted_data[255:]},
615
- {'type': PairingDataComponentType.STATE, 'data': b'\x05'},
687
+ {"type": PairingDataComponentType.ENCRYPTED_DATA, "data": encrypted_data[:255]},
688
+ {"type": PairingDataComponentType.ENCRYPTED_DATA, "data": encrypted_data[255:]},
689
+ {"type": PairingDataComponentType.STATE, "data": b"\x05"},
616
690
  ])
617
691
 
618
692
  response = await self._send_receive_pairing_data({
619
- 'data': tlv,
620
- 'kind': 'setupManualPairing',
621
- 'sendingHost': platform.node(),
622
- 'startNewSession': False})
693
+ "data": tlv,
694
+ "kind": "setupManualPairing",
695
+ "sendingHost": platform.node(),
696
+ "startNewSession": False,
697
+ })
623
698
  data = self.decode_tlv(PairingDataComponentTLVBuf.parse(response))
624
699
 
625
- tlv = PairingDataComponentTLVBuf.parse(cip.decrypt(
626
- b'\x00\x00\x00\x00PS-Msg06', data[PairingDataComponentType.ENCRYPTED_DATA], b''))
700
+ tlv = PairingDataComponentTLVBuf.parse(
701
+ cip.decrypt(b"\x00\x00\x00\x00PS-Msg06", data[PairingDataComponentType.ENCRYPTED_DATA], b"")
702
+ )
627
703
 
628
704
  return tlv
629
705
 
@@ -632,7 +708,7 @@ class RemotePairingProtocol(StartTcpTunnel):
632
708
  algorithm=hashes.SHA512(),
633
709
  length=32,
634
710
  salt=None,
635
- info=b'ClientEncrypt-main',
711
+ info=b"ClientEncrypt-main",
636
712
  ).derive(self.encryption_key)
637
713
  self.client_cip = ChaCha20Poly1305(client_key)
638
714
 
@@ -640,22 +716,23 @@ class RemotePairingProtocol(StartTcpTunnel):
640
716
  algorithm=hashes.SHA512(),
641
717
  length=32,
642
718
  salt=None,
643
- info=b'ServerEncrypt-main',
719
+ info=b"ServerEncrypt-main",
644
720
  ).derive(self.encryption_key)
645
721
  self.server_cip = ChaCha20Poly1305(server_key)
646
722
 
647
723
  async def _create_remote_unlock(self) -> None:
648
724
  try:
649
- response = await self._send_receive_encrypted_request({'request': {'_0': {'createRemoteUnlockKey': {}}}})
650
- self.remote_unlock_host_key = response['createRemoteUnlockKey']['hostKey']
725
+ response = await self._send_receive_encrypted_request({"request": {"_0": {"createRemoteUnlockKey": {}}}})
726
+ self.remote_unlock_host_key = response["createRemoteUnlockKey"]["hostKey"]
651
727
  except PyMobileDevice3Exception:
652
728
  # tvOS does not support remote unlock.
653
- self.remote_unlock_host_key = ''
729
+ self.remote_unlock_host_key = ""
654
730
 
655
731
  async def _attempt_pair_verify(self) -> None:
656
732
  self.handshake_info = await self._send_receive_handshake({
657
- 'hostOptions': {'attemptPairVerify': True},
658
- 'wireProtocolVersion': XpcInt64Type(self.WIRE_PROTOCOL_VERSION)})
733
+ "hostOptions": {"attemptPairVerify": True},
734
+ "wireProtocolVersion": XpcInt64Type(self.WIRE_PROTOCOL_VERSION),
735
+ })
659
736
 
660
737
  @staticmethod
661
738
  def _decode_bytes_if_needed(data: bytes) -> bytes:
@@ -663,14 +740,17 @@ class RemotePairingProtocol(StartTcpTunnel):
663
740
 
664
741
  async def _validate_pairing(self) -> bool:
665
742
  pairing_data = PairingDataComponentTLVBuf.build([
666
- {'type': PairingDataComponentType.STATE, 'data': b'\x01'},
667
- {'type': PairingDataComponentType.PUBLIC_KEY,
668
- 'data': self.x25519_private_key.public_key().public_bytes_raw()},
743
+ {"type": PairingDataComponentType.STATE, "data": b"\x01"},
744
+ {
745
+ "type": PairingDataComponentType.PUBLIC_KEY,
746
+ "data": self.x25519_private_key.public_key().public_bytes_raw(),
747
+ },
669
748
  ])
670
- response = await self._send_receive_pairing_data({'data': pairing_data,
671
- 'kind': 'verifyManualPairing',
672
- 'startNewSession': True})
673
-
749
+ response = await self._send_receive_pairing_data({
750
+ "data": pairing_data,
751
+ "kind": "verifyManualPairing",
752
+ "startNewSession": True,
753
+ })
674
754
  data = self.decode_tlv(PairingDataComponentTLVBuf.parse(response))
675
755
 
676
756
  if PairingDataComponentType.ERROR in data:
@@ -683,8 +763,8 @@ class RemotePairingProtocol(StartTcpTunnel):
683
763
  derived_key = HKDF(
684
764
  algorithm=hashes.SHA512(),
685
765
  length=32,
686
- salt=b'Pair-Verify-Encrypt-Salt',
687
- info=b'Pair-Verify-Encrypt-Info',
766
+ salt=b"Pair-Verify-Encrypt-Salt",
767
+ info=b"Pair-Verify-Encrypt-Info",
688
768
  ).derive(self.encryption_key)
689
769
  cip = ChaCha20Poly1305(derived_key)
690
770
 
@@ -693,31 +773,36 @@ class RemotePairingProtocol(StartTcpTunnel):
693
773
  # do so. instead, we verify using the next stage
694
774
 
695
775
  if self.pair_record is None:
696
- private_key = Ed25519PrivateKey.from_private_bytes(b'\x00' * 0x20)
776
+ private_key = Ed25519PrivateKey.from_private_bytes(b"\x00" * 0x20)
697
777
  else:
698
- private_key = Ed25519PrivateKey.from_private_bytes(self.pair_record['private_key'])
778
+ private_key = Ed25519PrivateKey.from_private_bytes(self.pair_record["private_key"])
699
779
 
700
- signbuf = b''
780
+ signbuf = b""
701
781
  signbuf += self.x25519_private_key.public_key().public_bytes_raw()
702
782
  signbuf += self.identifier.encode()
703
783
  signbuf += peer_public_key.public_bytes_raw()
704
784
 
705
785
  signature = private_key.sign(signbuf)
706
786
 
707
- encrypted_data = cip.encrypt(b'\x00\x00\x00\x00PV-Msg03', PairingDataComponentTLVBuf.build([
708
- {'type': PairingDataComponentType.IDENTIFIER, 'data': self.identifier.encode()},
709
- {'type': PairingDataComponentType.SIGNATURE, 'data': signature},
710
- ]), b'')
787
+ encrypted_data = cip.encrypt(
788
+ b"\x00\x00\x00\x00PV-Msg03",
789
+ PairingDataComponentTLVBuf.build([
790
+ {"type": PairingDataComponentType.IDENTIFIER, "data": self.identifier.encode()},
791
+ {"type": PairingDataComponentType.SIGNATURE, "data": signature},
792
+ ]),
793
+ b"",
794
+ )
711
795
 
712
796
  pairing_data = PairingDataComponentTLVBuf.build([
713
- {'type': PairingDataComponentType.STATE, 'data': b'\x03'},
714
- {'type': PairingDataComponentType.ENCRYPTED_DATA, 'data': encrypted_data},
797
+ {"type": PairingDataComponentType.STATE, "data": b"\x03"},
798
+ {"type": PairingDataComponentType.ENCRYPTED_DATA, "data": encrypted_data},
715
799
  ])
716
800
 
717
801
  response = await self._send_receive_pairing_data({
718
- 'data': pairing_data,
719
- 'kind': 'verifyManualPairing',
720
- 'startNewSession': False})
802
+ "data": pairing_data,
803
+ "kind": "verifyManualPairing",
804
+ "startNewSession": False,
805
+ })
721
806
  data = self.decode_tlv(PairingDataComponentTLVBuf.parse(response))
722
807
 
723
808
  if PairingDataComponentType.ERROR in data:
@@ -727,66 +812,68 @@ class RemotePairingProtocol(StartTcpTunnel):
727
812
  return True
728
813
 
729
814
  async def _send_pair_verify_failed(self) -> None:
730
- await self._send_plain_request({'event': {'_0': {'pairVerifyFailed': {}}}})
815
+ await self._send_plain_request({"event": {"_0": {"pairVerifyFailed": {}}}})
731
816
 
732
817
  async def _send_receive_encrypted_request(self, request: dict) -> dict:
733
- nonce = Int64ul.build(self._encrypted_sequence_number) + b'\x00' * 4
734
- encrypted_data = self.client_cip.encrypt(
735
- nonce,
736
- json.dumps(request).encode(),
737
- b'')
738
-
739
- response = await self.send_receive_request({'message': {
740
- 'streamEncrypted': {'_0': encrypted_data}},
741
- 'originatedBy': 'host',
742
- 'sequenceNumber': XpcUInt64Type(self._sequence_number)})
818
+ nonce = Int64ul.build(self._encrypted_sequence_number) + b"\x00" * 4
819
+ encrypted_data = self.client_cip.encrypt(nonce, json.dumps(request).encode(), b"")
820
+
821
+ response = await self.send_receive_request({
822
+ "message": {"streamEncrypted": {"_0": encrypted_data}},
823
+ "originatedBy": "host",
824
+ "sequenceNumber": XpcUInt64Type(self._sequence_number),
825
+ })
743
826
  self._encrypted_sequence_number += 1
744
827
 
745
- encrypted_data = self._decode_bytes_if_needed(response['message']['streamEncrypted']['_0'])
828
+ encrypted_data = self._decode_bytes_if_needed(response["message"]["streamEncrypted"]["_0"])
746
829
  plaintext = self.server_cip.decrypt(nonce, encrypted_data, None)
747
- response = json.loads(plaintext)['response']['_1']
830
+ response = json.loads(plaintext)["response"]["_1"]
748
831
 
749
- if 'errorExtended' in response:
750
- raise PyMobileDevice3Exception(response['errorExtended']['_0']['userInfo']['NSLocalizedDescription'])
832
+ if "errorExtended" in response:
833
+ raise PyMobileDevice3Exception(response["errorExtended"]["_0"]["userInfo"]["NSLocalizedDescription"])
751
834
 
752
835
  return response
753
836
 
754
837
  async def _send_receive_handshake(self, handshake_data: dict) -> dict:
755
- response = await self._send_receive_plain_request({'request': {'_0': {'handshake': {'_0': handshake_data}}}})
756
- return response['response']['_1']['handshake']['_0']
838
+ response = await self._send_receive_plain_request({"request": {"_0": {"handshake": {"_0": handshake_data}}}})
839
+ return response["response"]["_1"]["handshake"]["_0"]
757
840
 
758
841
  async def _send_receive_pairing_data(self, pairing_data: dict) -> bytes:
759
842
  await self._send_pairing_data(pairing_data)
760
843
  return await self._receive_pairing_data()
761
844
 
762
845
  async def _send_pairing_data(self, pairing_data: dict) -> None:
763
- await self._send_plain_request({'event': {'_0': {'pairingData': {'_0': pairing_data}}}})
846
+ await self._send_plain_request({"event": {"_0": {"pairingData": {"_0": pairing_data}}}})
764
847
 
765
848
  async def _receive_pairing_data(self) -> bytes:
766
849
  response = await self._receive_plain_response()
767
- response = response['event']['_0']
768
- if 'pairingData' in response:
769
- return self._decode_bytes_if_needed(response['pairingData']['_0']['data'])
770
- if 'pairingRejectedWithError' in response:
771
- raise UserDeniedPairingError(response['pairingRejectedWithError']
772
- .get('wrappedError', {})
773
- .get('userInfo', {})
774
- .get('NSLocalizedDescription'))
775
- raise PyMobileDevice3Exception(f'Got an unknown state message: {response}')
850
+ response = response["event"]["_0"]
851
+ if "pairingData" in response:
852
+ return self._decode_bytes_if_needed(response["pairingData"]["_0"]["data"])
853
+ if "pairingRejectedWithError" in response:
854
+ raise UserDeniedPairingError(
855
+ response["pairingRejectedWithError"]
856
+ .get("wrappedError", {})
857
+ .get("userInfo", {})
858
+ .get("NSLocalizedDescription")
859
+ )
860
+ raise PyMobileDevice3Exception(f"Got an unknown state message: {response}")
776
861
 
777
862
  async def _send_receive_plain_request(self, plain_request: dict):
778
863
  await self._send_plain_request(plain_request)
779
864
  return await self._receive_plain_response()
780
865
 
781
866
  async def _send_plain_request(self, plain_request: dict) -> None:
782
- await self.send_request({'message': {'plain': {'_0': plain_request}},
783
- 'originatedBy': 'host',
784
- 'sequenceNumber': XpcUInt64Type(self._sequence_number)})
867
+ await self.send_request({
868
+ "message": {"plain": {"_0": plain_request}},
869
+ "originatedBy": "host",
870
+ "sequenceNumber": XpcUInt64Type(self._sequence_number),
871
+ })
785
872
  self._sequence_number += 1
786
873
 
787
874
  async def _receive_plain_response(self) -> dict:
788
875
  response = await self.receive_response()
789
- return response['message']['plain']['_0']
876
+ return response["message"]["plain"]["_0"]
790
877
 
791
878
  @staticmethod
792
879
  def decode_tlv(tlv_list: list[Container]) -> dict:
@@ -798,7 +885,7 @@ class RemotePairingProtocol(StartTcpTunnel):
798
885
  result[tlv.type] = tlv.data
799
886
  return result
800
887
 
801
- async def __aenter__(self) -> 'CoreDeviceTunnelService':
888
+ async def __aenter__(self) -> "CoreDeviceTunnelService":
802
889
  return self
803
890
 
804
891
  async def __aexit__(self, exc_type, exc_val, exc_tb) -> None:
@@ -806,7 +893,7 @@ class RemotePairingProtocol(StartTcpTunnel):
806
893
 
807
894
 
808
895
  class CoreDeviceTunnelService(RemotePairingProtocol, RemoteService):
809
- SERVICE_NAME = 'com.apple.internal.dt.coredevice.untrusted.tunnelservice'
896
+ SERVICE_NAME = "com.apple.internal.dt.coredevice.untrusted.tunnelservice"
810
897
 
811
898
  def __init__(self, rsd: RemoteServiceDiscoveryService):
812
899
  RemoteService.__init__(self, rsd, self.SERVICE_NAME)
@@ -814,16 +901,18 @@ class CoreDeviceTunnelService(RemotePairingProtocol, RemoteService):
814
901
  self.version: Optional[int] = None
815
902
 
816
903
  async def connect(self, autopair: bool = True) -> None:
904
+ # Establish RemoteXPC connection to `SERVICE_NAME`
817
905
  await RemoteService.connect(self)
818
906
  try:
819
907
  response = await self.service.receive_response()
820
- self.version = response['ServiceVersion']
908
+ self.version = response["ServiceVersion"]
909
+
910
+ # Perform pairing if necessary and start a trusted RemoteXPC connection
821
911
  await RemotePairingProtocol.connect(self, autopair=autopair)
822
912
  self.hostname = self.service.address[0]
823
- except Exception as e: # noqa: E722
913
+ except Exception:
824
914
  await self.service.close()
825
- if isinstance(e, UserDeniedPairingError):
826
- raise
915
+ raise
827
916
 
828
917
  async def close(self) -> None:
829
918
  await self.rsd.close()
@@ -831,11 +920,13 @@ class CoreDeviceTunnelService(RemotePairingProtocol, RemoteService):
831
920
 
832
921
  async def receive_response(self) -> dict:
833
922
  response = await self.service.receive_response()
834
- return response['value']
923
+ return response["value"]
835
924
 
836
925
  async def send_request(self, data: dict) -> None:
837
926
  return await self.service.send_request({
838
- 'mangledTypeName': 'RemotePairing.ControlChannelMessageEnvelope', 'value': data})
927
+ "mangledTypeName": "RemotePairing.ControlChannelMessageEnvelope",
928
+ "value": data,
929
+ })
839
930
 
840
931
 
841
932
  class RemotePairingTunnelService(RemotePairingProtocol):
@@ -860,7 +951,7 @@ class RemotePairingTunnelService(RemotePairingProtocol):
860
951
  if not await self._validate_pairing():
861
952
  raise ConnectionAbortedError()
862
953
  self._init_client_server_main_encryption_keys()
863
- except: # noqa: E722
954
+ except Exception:
864
955
  await self.close()
865
956
  raise
866
957
 
@@ -868,21 +959,20 @@ class RemotePairingTunnelService(RemotePairingProtocol):
868
959
  if self._writer is None:
869
960
  return
870
961
  self._writer.close()
871
- try:
962
+ with suppress(ssl.SSLError):
872
963
  await self._writer.wait_closed()
873
- except ssl.SSLError:
874
- pass
875
964
  self._writer = None
876
965
  self._reader = None
877
966
 
878
967
  async def receive_response(self) -> dict:
879
968
  await self._reader.readexactly(len(REPAIRING_PACKET_MAGIC))
880
- size = struct.unpack('>H', await self._reader.readexactly(2))[0]
969
+ size = struct.unpack(">H", await self._reader.readexactly(2))[0]
881
970
  return json.loads(await self._reader.readexactly(size))
882
971
 
883
972
  async def send_request(self, data: dict) -> None:
884
973
  self._writer.write(
885
- RPPairingPacket.build({'body': json.dumps(data, default=self._default_json_encoder).encode()}))
974
+ RPPairingPacket.build({"body": json.dumps(data, default=self._default_json_encoder).encode()})
975
+ )
886
976
  await self._writer.drain()
887
977
 
888
978
  @staticmethod
@@ -896,8 +986,9 @@ class RemotePairingTunnelService(RemotePairingProtocol):
896
986
  return base64.b64decode(data)
897
987
 
898
988
  def __repr__(self) -> str:
899
- return (f'<{self.__class__.__name__} IDENTIFIER:{self.remote_identifier} HOSTNAME:{self.hostname} '
900
- f'PORT:{self.port}>')
989
+ return (
990
+ f"<{self.__class__.__name__} IDENTIFIER:{self.remote_identifier} HOSTNAME:{self.hostname} PORT:{self.port}>"
991
+ )
901
992
 
902
993
 
903
994
  class RemotePairingManualPairingService(RemotePairingTunnelService):
@@ -908,27 +999,38 @@ class RemotePairingManualPairingService(RemotePairingTunnelService):
908
999
 
909
1000
 
910
1001
  class CoreDeviceTunnelProxy(StartTcpTunnel):
911
- SERVICE_NAME = 'com.apple.internal.devicecompute.CoreDeviceProxy'
1002
+ SERVICE_NAME = "com.apple.internal.devicecompute.CoreDeviceProxy"
1003
+
1004
+ @classmethod
1005
+ async def create(cls, lockdown: LockdownServiceProvider) -> "CoreDeviceTunnelProxy":
1006
+ return cls(await lockdown.aio_start_lockdown_service(cls.SERVICE_NAME), lockdown.udid)
912
1007
 
913
- def __init__(self, lockdown: LockdownServiceProvider) -> None:
914
- self._lockdown = lockdown
915
- self._service: Optional[ServiceConnection] = None
1008
+ def __init__(self, service: ServiceConnection, remote_identifier: str) -> None:
1009
+ self._service: ServiceConnection = service
1010
+ self._remote_identifier: str = remote_identifier
916
1011
 
917
1012
  @property
918
1013
  def remote_identifier(self) -> str:
919
- return self._lockdown.udid
1014
+ return self._remote_identifier
920
1015
 
921
1016
  @asynccontextmanager
922
- async def start_tcp_tunnel(self) -> AsyncGenerator['TunnelResult', None]:
923
- self._service = await self._lockdown.aio_start_lockdown_service(self.SERVICE_NAME)
1017
+ async def start_tcp_tunnel(self) -> AsyncGenerator["TunnelResult", None]:
1018
+ assert self._service is not None, "service must be connected first"
924
1019
  tunnel = RemotePairingTcpTunnel(self._service.reader, self._service.writer)
925
1020
  handshake_response = await tunnel.request_tunnel_establish()
926
- tunnel.start_tunnel(handshake_response['clientParameters']['address'],
927
- handshake_response['clientParameters']['mtu'])
1021
+ tunnel.start_tunnel(
1022
+ handshake_response["clientParameters"]["address"],
1023
+ handshake_response["clientParameters"]["mtu"],
1024
+ interface_name=f"{DEFAULT_INTERFACE_NAME}-{self.remote_identifier}",
1025
+ )
928
1026
  try:
929
1027
  yield TunnelResult(
930
- tunnel.tun.name, handshake_response['serverAddress'], handshake_response['serverRSDPort'],
931
- TunnelProtocol.TCP, tunnel)
1028
+ tunnel.tun.name,
1029
+ handshake_response["serverAddress"],
1030
+ handshake_response["serverRSDPort"],
1031
+ TunnelProtocol.TCP,
1032
+ tunnel,
1033
+ )
932
1034
  finally:
933
1035
  await tunnel.stop_tunnel()
934
1036
 
@@ -938,21 +1040,33 @@ class CoreDeviceTunnelProxy(StartTcpTunnel):
938
1040
 
939
1041
 
940
1042
  async def create_core_device_tunnel_service_using_rsd(
941
- rsd: RemoteServiceDiscoveryService, autopair: bool = True) -> CoreDeviceTunnelService:
1043
+ rsd: RemoteServiceDiscoveryService, autopair: bool = True
1044
+ ) -> CoreDeviceTunnelService:
942
1045
  service = CoreDeviceTunnelService(rsd)
943
- await service.connect(autopair=autopair)
1046
+ try:
1047
+ await service.connect(autopair=autopair)
1048
+ except RemotePairingCompletedError:
1049
+ # The connection must be reestablished upon pairing is completed
1050
+ await service.close()
1051
+ service = CoreDeviceTunnelService(rsd)
1052
+ await service.connect(autopair=autopair)
1053
+ except Exception:
1054
+ await service.close()
1055
+ raise
944
1056
  return service
945
1057
 
946
1058
 
947
1059
  async def create_core_device_tunnel_service_using_remotepairing(
948
- remote_identifier: str, hostname: str, port: int, autopair: bool = True) -> RemotePairingTunnelService:
1060
+ remote_identifier: str, hostname: str, port: int, autopair: bool = True
1061
+ ) -> RemotePairingTunnelService:
949
1062
  service = RemotePairingTunnelService(remote_identifier, hostname, port)
950
1063
  await service.connect(autopair=autopair)
951
1064
  return service
952
1065
 
953
1066
 
954
1067
  async def create_core_device_service_using_remotepairing_manual_pairing(
955
- remote_identifier: str, hostname: str, port: int, autopair: bool = True) -> RemotePairingTunnelService:
1068
+ remote_identifier: str, hostname: str, port: int, autopair: bool = True
1069
+ ) -> RemotePairingTunnelService:
956
1070
  service = RemotePairingManualPairingService(remote_identifier, hostname, port)
957
1071
  await service.connect(autopair=autopair)
958
1072
  return service
@@ -960,14 +1074,16 @@ async def create_core_device_service_using_remotepairing_manual_pairing(
960
1074
 
961
1075
  @asynccontextmanager
962
1076
  async def start_tunnel_over_remotepairing(
963
- remote_pairing: RemotePairingTunnelService, secrets: Optional[TextIO] = None,
964
- max_idle_timeout: float = RemotePairingQuicTunnel.MAX_IDLE_TIMEOUT,
965
- protocol: TunnelProtocol = TunnelProtocol.QUIC) \
966
- -> AsyncGenerator[TunnelResult, None]:
1077
+ remote_pairing: RemotePairingTunnelService,
1078
+ secrets: Optional[TextIO] = None,
1079
+ max_idle_timeout: float = RemotePairingQuicTunnel.MAX_IDLE_TIMEOUT,
1080
+ protocol: TunnelProtocol = TunnelProtocol.QUIC,
1081
+ ) -> AsyncGenerator[TunnelResult, None]:
967
1082
  async with remote_pairing:
968
1083
  if protocol == TunnelProtocol.QUIC:
969
1084
  async with remote_pairing.start_quic_tunnel(
970
- secrets_log_file=secrets, max_idle_timeout=max_idle_timeout) as tunnel_result:
1085
+ secrets_log_file=secrets, max_idle_timeout=max_idle_timeout
1086
+ ) as tunnel_result:
971
1087
  yield tunnel_result
972
1088
  elif protocol == TunnelProtocol.TCP:
973
1089
  async with remote_pairing.start_tcp_tunnel() as tunnel_result:
@@ -976,15 +1092,17 @@ async def start_tunnel_over_remotepairing(
976
1092
 
977
1093
  @asynccontextmanager
978
1094
  async def start_tunnel_over_core_device(
979
- service_provider: CoreDeviceTunnelService, secrets: Optional[TextIO] = None,
980
- max_idle_timeout: float = RemotePairingQuicTunnel.MAX_IDLE_TIMEOUT,
981
- protocol: TunnelProtocol = TunnelProtocol.QUIC) \
982
- -> AsyncGenerator[TunnelResult, None]:
1095
+ service_provider: CoreDeviceTunnelService,
1096
+ secrets: Optional[TextIO] = None,
1097
+ max_idle_timeout: float = RemotePairingQuicTunnel.MAX_IDLE_TIMEOUT,
1098
+ protocol: TunnelProtocol = TunnelProtocol.QUIC,
1099
+ ) -> AsyncGenerator[TunnelResult, None]:
983
1100
  stop_remoted_if_required()
984
1101
  async with service_provider:
985
1102
  if protocol == TunnelProtocol.QUIC:
986
1103
  async with service_provider.start_quic_tunnel(
987
- secrets_log_file=secrets, max_idle_timeout=max_idle_timeout) as tunnel_result:
1104
+ secrets_log_file=secrets, max_idle_timeout=max_idle_timeout
1105
+ ) as tunnel_result:
988
1106
  resume_remoted_if_required()
989
1107
  yield tunnel_result
990
1108
  elif protocol == TunnelProtocol.TCP:
@@ -995,57 +1113,64 @@ async def start_tunnel_over_core_device(
995
1113
 
996
1114
  @asynccontextmanager
997
1115
  async def start_tunnel(
998
- protocol_handler: RemotePairingProtocol, secrets: Optional[TextIO] = None,
999
- max_idle_timeout: float = RemotePairingQuicTunnel.MAX_IDLE_TIMEOUT,
1000
- protocol: TunnelProtocol = TunnelProtocol.QUIC) -> AsyncGenerator[TunnelResult, None]:
1116
+ protocol_handler: RemotePairingProtocol,
1117
+ secrets: Optional[TextIO] = None,
1118
+ max_idle_timeout: float = RemotePairingQuicTunnel.MAX_IDLE_TIMEOUT,
1119
+ protocol: TunnelProtocol = TunnelProtocol.QUIC,
1120
+ ) -> AsyncGenerator[TunnelResult, None]:
1001
1121
  if isinstance(protocol_handler, CoreDeviceTunnelService):
1002
1122
  async with start_tunnel_over_core_device(
1003
- protocol_handler, secrets=secrets, max_idle_timeout=max_idle_timeout, protocol=protocol) as service:
1123
+ protocol_handler, secrets=secrets, max_idle_timeout=max_idle_timeout, protocol=protocol
1124
+ ) as service:
1004
1125
  yield service
1005
1126
  elif isinstance(protocol_handler, RemotePairingTunnelService):
1006
1127
  async with start_tunnel_over_remotepairing(
1007
- protocol_handler, secrets=secrets, max_idle_timeout=max_idle_timeout, protocol=protocol) as service:
1128
+ protocol_handler, secrets=secrets, max_idle_timeout=max_idle_timeout, protocol=protocol
1129
+ ) as service:
1008
1130
  yield service
1009
1131
  elif isinstance(protocol_handler, CoreDeviceTunnelProxy):
1010
1132
  if protocol != TunnelProtocol.TCP:
1011
- raise ValueError('CoreDeviceTunnelProxy protocol can only be TCP')
1133
+ raise ValueError("CoreDeviceTunnelProxy protocol can only be TCP")
1012
1134
  async with protocol_handler.start_tcp_tunnel() as service:
1013
1135
  yield service
1014
1136
  else:
1015
- raise Exception(f'Bad value for protocol_handler: {protocol_handler}')
1137
+ raise TypeError(f"Bad value for protocol_handler: {protocol_handler}")
1016
1138
 
1017
1139
 
1018
1140
  async def get_core_device_tunnel_services(
1019
- bonjour_timeout: float = DEFAULT_BONJOUR_TIMEOUT,
1020
- udid: Optional[str] = None) -> list[CoreDeviceTunnelService]:
1141
+ bonjour_timeout: float = DEFAULT_BONJOUR_TIMEOUT, udid: Optional[str] = None
1142
+ ) -> list[CoreDeviceTunnelService]:
1021
1143
  result = []
1022
1144
  for rsd in await get_rsds(bonjour_timeout=bonjour_timeout, udid=udid):
1023
- if udid is None and ((Version(rsd.product_version) < Version('17.0'))
1024
- and not rsd.product_type.startswith('RealityDevice')):
1025
- logger.debug(f'Skipping {rsd.udid}:, iOS {rsd.product_version} < 17.0')
1145
+ if udid is None and (
1146
+ (Version(rsd.product_version) < Version("17.0")) and not rsd.product_type.startswith("RealityDevice")
1147
+ ):
1148
+ logger.debug(f"Skipping {rsd.udid}:, iOS {rsd.product_version} < 17.0")
1026
1149
  await rsd.close()
1027
1150
  continue
1028
1151
  try:
1029
1152
  result.append(await create_core_device_tunnel_service_using_rsd(rsd))
1030
- except Exception as e:
1031
- logger.error(f'Failed to start service: {rsd}: {e}')
1153
+ except Exception:
1154
+ logger.exception(f"Failed to start service: {rsd}")
1032
1155
  await rsd.close()
1033
1156
  raise
1034
1157
  return result
1035
1158
 
1036
1159
 
1037
1160
  async def get_remote_pairing_tunnel_services(
1038
- bonjour_timeout: float = DEFAULT_BONJOUR_TIMEOUT,
1039
- udid: Optional[str] = None) -> list[RemotePairingTunnelService]:
1161
+ bonjour_timeout: float = DEFAULT_BONJOUR_TIMEOUT, udid: Optional[str] = None
1162
+ ) -> list[RemotePairingTunnelService]:
1040
1163
  result = []
1041
1164
  for answer in await browse_remotepairing(timeout=bonjour_timeout):
1042
- for ip in answer.ips:
1165
+ for address in answer.addresses:
1043
1166
  for identifier in iter_remote_paired_identifiers():
1044
1167
  if udid is not None and identifier != udid:
1045
1168
  continue
1046
1169
  conn = None
1047
1170
  try:
1048
- conn = await create_core_device_tunnel_service_using_remotepairing(identifier, ip, answer.port)
1171
+ conn = await create_core_device_tunnel_service_using_remotepairing(
1172
+ identifier, address.full_ip, answer.port
1173
+ )
1049
1174
  result.append(conn)
1050
1175
  break
1051
1176
  except ConnectionAbortedError: