pymobiledevice3 5.0.0__py3-none-any.whl → 5.0.2__py3-none-any.whl

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.

Potentially problematic release.


This version of pymobiledevice3 might be problematic. Click here for more details.

Files changed (143) hide show
  1. misc/plist_sniffer.py +15 -15
  2. misc/remotexpc_sniffer.py +29 -28
  3. pymobiledevice3/__main__.py +128 -102
  4. pymobiledevice3/_version.py +2 -2
  5. pymobiledevice3/bonjour.py +26 -49
  6. pymobiledevice3/ca.py +32 -24
  7. pymobiledevice3/cli/activation.py +7 -7
  8. pymobiledevice3/cli/afc.py +19 -19
  9. pymobiledevice3/cli/amfi.py +4 -4
  10. pymobiledevice3/cli/apps.py +51 -39
  11. pymobiledevice3/cli/backup.py +58 -32
  12. pymobiledevice3/cli/bonjour.py +25 -18
  13. pymobiledevice3/cli/cli_common.py +112 -81
  14. pymobiledevice3/cli/companion_proxy.py +4 -4
  15. pymobiledevice3/cli/completions.py +10 -10
  16. pymobiledevice3/cli/crash.py +37 -31
  17. pymobiledevice3/cli/developer.py +602 -520
  18. pymobiledevice3/cli/diagnostics.py +38 -33
  19. pymobiledevice3/cli/lockdown.py +79 -74
  20. pymobiledevice3/cli/mounter.py +85 -68
  21. pymobiledevice3/cli/notification.py +10 -10
  22. pymobiledevice3/cli/pcap.py +19 -14
  23. pymobiledevice3/cli/power_assertion.py +12 -10
  24. pymobiledevice3/cli/processes.py +10 -10
  25. pymobiledevice3/cli/profile.py +88 -77
  26. pymobiledevice3/cli/provision.py +17 -17
  27. pymobiledevice3/cli/remote.py +186 -110
  28. pymobiledevice3/cli/restore.py +43 -40
  29. pymobiledevice3/cli/springboard.py +30 -28
  30. pymobiledevice3/cli/syslog.py +85 -58
  31. pymobiledevice3/cli/usbmux.py +21 -20
  32. pymobiledevice3/cli/version.py +3 -2
  33. pymobiledevice3/cli/webinspector.py +157 -79
  34. pymobiledevice3/common.py +1 -1
  35. pymobiledevice3/exceptions.py +154 -60
  36. pymobiledevice3/irecv.py +49 -53
  37. pymobiledevice3/irecv_devices.py +1489 -492
  38. pymobiledevice3/lockdown.py +394 -241
  39. pymobiledevice3/lockdown_service_provider.py +5 -7
  40. pymobiledevice3/osu/os_utils.py +18 -9
  41. pymobiledevice3/osu/posix_util.py +28 -15
  42. pymobiledevice3/osu/win_util.py +14 -8
  43. pymobiledevice3/pair_records.py +19 -19
  44. pymobiledevice3/remote/common.py +4 -4
  45. pymobiledevice3/remote/core_device/app_service.py +94 -67
  46. pymobiledevice3/remote/core_device/core_device_service.py +17 -14
  47. pymobiledevice3/remote/core_device/device_info.py +5 -5
  48. pymobiledevice3/remote/core_device/diagnostics_service.py +10 -8
  49. pymobiledevice3/remote/core_device/file_service.py +47 -33
  50. pymobiledevice3/remote/remote_service_discovery.py +53 -35
  51. pymobiledevice3/remote/remotexpc.py +62 -41
  52. pymobiledevice3/remote/tunnel_service.py +371 -293
  53. pymobiledevice3/remote/utils.py +12 -11
  54. pymobiledevice3/remote/xpc_message.py +145 -125
  55. pymobiledevice3/resources/dsc_uuid_map.py +19 -19
  56. pymobiledevice3/resources/firmware_notifications.py +16 -16
  57. pymobiledevice3/restore/asr.py +27 -27
  58. pymobiledevice3/restore/base_restore.py +90 -47
  59. pymobiledevice3/restore/consts.py +87 -66
  60. pymobiledevice3/restore/device.py +11 -11
  61. pymobiledevice3/restore/fdr.py +46 -46
  62. pymobiledevice3/restore/ftab.py +19 -19
  63. pymobiledevice3/restore/img4.py +130 -133
  64. pymobiledevice3/restore/mbn.py +35 -54
  65. pymobiledevice3/restore/recovery.py +125 -135
  66. pymobiledevice3/restore/restore.py +524 -523
  67. pymobiledevice3/restore/restore_options.py +122 -115
  68. pymobiledevice3/restore/restored_client.py +25 -22
  69. pymobiledevice3/restore/tss.py +378 -270
  70. pymobiledevice3/service_connection.py +50 -46
  71. pymobiledevice3/services/accessibilityaudit.py +136 -126
  72. pymobiledevice3/services/afc.py +350 -291
  73. pymobiledevice3/services/amfi.py +21 -18
  74. pymobiledevice3/services/companion.py +23 -19
  75. pymobiledevice3/services/crash_reports.py +60 -46
  76. pymobiledevice3/services/debugserver_applist.py +3 -3
  77. pymobiledevice3/services/device_arbitration.py +8 -8
  78. pymobiledevice3/services/device_link.py +55 -47
  79. pymobiledevice3/services/diagnostics.py +971 -968
  80. pymobiledevice3/services/dtfetchsymbols.py +8 -8
  81. pymobiledevice3/services/dvt/dvt_secure_socket_proxy.py +4 -4
  82. pymobiledevice3/services/dvt/dvt_testmanaged_proxy.py +4 -4
  83. pymobiledevice3/services/dvt/instruments/activity_trace_tap.py +85 -74
  84. pymobiledevice3/services/dvt/instruments/application_listing.py +2 -3
  85. pymobiledevice3/services/dvt/instruments/condition_inducer.py +7 -6
  86. pymobiledevice3/services/dvt/instruments/core_profile_session_tap.py +466 -384
  87. pymobiledevice3/services/dvt/instruments/device_info.py +11 -11
  88. pymobiledevice3/services/dvt/instruments/energy_monitor.py +1 -1
  89. pymobiledevice3/services/dvt/instruments/graphics.py +1 -1
  90. pymobiledevice3/services/dvt/instruments/location_simulation.py +1 -1
  91. pymobiledevice3/services/dvt/instruments/location_simulation_base.py +10 -10
  92. pymobiledevice3/services/dvt/instruments/network_monitor.py +17 -17
  93. pymobiledevice3/services/dvt/instruments/notifications.py +1 -1
  94. pymobiledevice3/services/dvt/instruments/process_control.py +25 -10
  95. pymobiledevice3/services/dvt/instruments/screenshot.py +2 -2
  96. pymobiledevice3/services/dvt/instruments/sysmontap.py +15 -15
  97. pymobiledevice3/services/dvt/testmanaged/xcuitest.py +40 -50
  98. pymobiledevice3/services/file_relay.py +10 -10
  99. pymobiledevice3/services/heartbeat.py +8 -7
  100. pymobiledevice3/services/house_arrest.py +12 -15
  101. pymobiledevice3/services/installation_proxy.py +119 -100
  102. pymobiledevice3/services/lockdown_service.py +12 -5
  103. pymobiledevice3/services/misagent.py +22 -19
  104. pymobiledevice3/services/mobile_activation.py +84 -72
  105. pymobiledevice3/services/mobile_config.py +330 -301
  106. pymobiledevice3/services/mobile_image_mounter.py +137 -116
  107. pymobiledevice3/services/mobilebackup2.py +188 -150
  108. pymobiledevice3/services/notification_proxy.py +11 -11
  109. pymobiledevice3/services/os_trace.py +69 -51
  110. pymobiledevice3/services/pcapd.py +306 -306
  111. pymobiledevice3/services/power_assertion.py +10 -9
  112. pymobiledevice3/services/preboard.py +4 -4
  113. pymobiledevice3/services/remote_fetch_symbols.py +16 -14
  114. pymobiledevice3/services/remote_server.py +176 -146
  115. pymobiledevice3/services/restore_service.py +16 -16
  116. pymobiledevice3/services/screenshot.py +13 -10
  117. pymobiledevice3/services/simulate_location.py +7 -7
  118. pymobiledevice3/services/springboard.py +15 -15
  119. pymobiledevice3/services/syslog.py +5 -5
  120. pymobiledevice3/services/web_protocol/alert.py +3 -3
  121. pymobiledevice3/services/web_protocol/automation_session.py +180 -176
  122. pymobiledevice3/services/web_protocol/cdp_screencast.py +44 -36
  123. pymobiledevice3/services/web_protocol/cdp_server.py +19 -19
  124. pymobiledevice3/services/web_protocol/cdp_target.py +411 -373
  125. pymobiledevice3/services/web_protocol/driver.py +47 -45
  126. pymobiledevice3/services/web_protocol/element.py +74 -63
  127. pymobiledevice3/services/web_protocol/inspector_session.py +106 -102
  128. pymobiledevice3/services/web_protocol/selenium_api.py +2 -2
  129. pymobiledevice3/services/web_protocol/session_protocol.py +15 -10
  130. pymobiledevice3/services/web_protocol/switch_to.py +11 -12
  131. pymobiledevice3/services/webinspector.py +127 -116
  132. pymobiledevice3/tcp_forwarder.py +35 -22
  133. pymobiledevice3/tunneld/api.py +20 -15
  134. pymobiledevice3/tunneld/server.py +212 -133
  135. pymobiledevice3/usbmux.py +183 -138
  136. pymobiledevice3/utils.py +14 -11
  137. {pymobiledevice3-5.0.0.dist-info → pymobiledevice3-5.0.2.dist-info}/METADATA +1 -1
  138. pymobiledevice3-5.0.2.dist-info/RECORD +173 -0
  139. pymobiledevice3-5.0.0.dist-info/RECORD +0 -173
  140. {pymobiledevice3-5.0.0.dist-info → pymobiledevice3-5.0.2.dist-info}/WHEEL +0 -0
  141. {pymobiledevice3-5.0.0.dist-info → pymobiledevice3-5.0.2.dist-info}/entry_points.txt +0 -0
  142. {pymobiledevice3-5.0.0.dist-info → pymobiledevice3-5.0.2.dist-info}/licenses/LICENSE +0 -0
  143. {pymobiledevice3-5.0.0.dist-info → pymobiledevice3-5.0.2.dist-info}/top_level.txt +0 -0
@@ -16,7 +16,7 @@ from pymobiledevice3.bonjour import browse_remoted
16
16
 
17
17
  with warnings.catch_warnings():
18
18
  # Ignore: "Core Pydantic V1 functionality isn't compatible with Python 3.14 or greater."
19
- warnings.simplefilter('ignore', category=UserWarning)
19
+ warnings.simplefilter("ignore", category=UserWarning)
20
20
  import fastapi
21
21
 
22
22
  import uvicorn
@@ -25,16 +25,30 @@ from fastapi import FastAPI
25
25
  from packaging.version import Version
26
26
 
27
27
  from pymobiledevice3 import usbmux
28
- from pymobiledevice3.exceptions import ConnectionFailedError, ConnectionFailedToUsbmuxdError, DeviceNotFoundError, \
29
- GetProhibitedError, IncorrectModeError, InvalidServiceError, LockdownError, MuxException, PairingError, \
30
- StreamClosedError
28
+ from pymobiledevice3.exceptions import (
29
+ ConnectionFailedError,
30
+ ConnectionFailedToUsbmuxdError,
31
+ DeviceNotFoundError,
32
+ GetProhibitedError,
33
+ IncorrectModeError,
34
+ InvalidServiceError,
35
+ LockdownError,
36
+ MuxException,
37
+ PairingError,
38
+ StreamClosedError,
39
+ )
31
40
  from pymobiledevice3.lockdown import create_using_usbmux, get_mobdev2_lockdowns
32
41
  from pymobiledevice3.osu.os_utils import get_os_utils
33
42
  from pymobiledevice3.remote.common import TunnelProtocol
34
43
  from pymobiledevice3.remote.module_imports import start_tunnel
35
44
  from pymobiledevice3.remote.remote_service_discovery import RSD_PORT, RemoteServiceDiscoveryService
36
- from pymobiledevice3.remote.tunnel_service import CoreDeviceTunnelProxy, RemotePairingProtocol, TunnelResult, \
37
- create_core_device_tunnel_service_using_rsd, get_remote_pairing_tunnel_services
45
+ from pymobiledevice3.remote.tunnel_service import (
46
+ CoreDeviceTunnelProxy,
47
+ RemotePairingProtocol,
48
+ TunnelResult,
49
+ create_core_device_tunnel_service_using_rsd,
50
+ get_remote_pairing_tunnel_services,
51
+ )
38
52
  from pymobiledevice3.remote.utils import get_rsds, stop_remoted
39
53
  from pymobiledevice3.utils import asyncio_print_traceback
40
54
 
@@ -59,8 +73,14 @@ class TunnelTask:
59
73
 
60
74
 
61
75
  class TunneldCore:
62
- def __init__(self, protocol: TunnelProtocol = TunnelProtocol.DEFAULT, wifi_monitor: bool = True,
63
- usb_monitor: bool = True, usbmux_monitor: bool = True, mobdev2_monitor: bool = True) -> None:
76
+ def __init__(
77
+ self,
78
+ protocol: TunnelProtocol = TunnelProtocol.DEFAULT,
79
+ wifi_monitor: bool = True,
80
+ usb_monitor: bool = True,
81
+ usbmux_monitor: bool = True,
82
+ mobdev2_monitor: bool = True,
83
+ ) -> None:
64
84
  self.protocol = protocol
65
85
  self.tasks: list[asyncio.Task] = []
66
86
  self.tunnel_tasks: dict[str, TunnelTask] = {}
@@ -70,24 +90,24 @@ class TunneldCore:
70
90
  self.mobdev2_monitor = mobdev2_monitor
71
91
 
72
92
  def start(self) -> None:
73
- """ Register all tasks """
93
+ """Register all tasks"""
74
94
  self.tasks = []
75
95
  if self.usb_monitor:
76
- self.tasks.append(asyncio.create_task(self.monitor_usb_task(), name='monitor-usb-task'))
96
+ self.tasks.append(asyncio.create_task(self.monitor_usb_task(), name="monitor-usb-task"))
77
97
  if self.wifi_monitor:
78
- self.tasks.append(asyncio.create_task(self.monitor_wifi_task(), name='monitor-wifi-task'))
98
+ self.tasks.append(asyncio.create_task(self.monitor_wifi_task(), name="monitor-wifi-task"))
79
99
  if self.usbmux_monitor:
80
- self.tasks.append(asyncio.create_task(self.monitor_usbmux_task(), name='monitor-usbmux-task'))
100
+ self.tasks.append(asyncio.create_task(self.monitor_usbmux_task(), name="monitor-usbmux-task"))
81
101
  if self.mobdev2_monitor:
82
- self.tasks.append(asyncio.create_task(self.monitor_mobdev2_task(), name='monitor-mobdev2-task'))
102
+ self.tasks.append(asyncio.create_task(self.monitor_mobdev2_task(), name="monitor-mobdev2-task"))
83
103
 
84
104
  def tunnel_exists_for_udid(self, udid: str) -> bool:
85
105
  for task in self.tunnel_tasks.values():
86
106
  # Linux implementations of `usbmuxd` may report an incorrect value of UDID, dismissing the `-` character.
87
107
  # For such cases, we also check for a UDID without it.
88
108
  # See: <https://github.com/doronz88/pymobiledevice3/issues/1388#issuecomment-2782249770>
89
- task_udid = task.udid or ''
90
- if ((task_udid == udid) or (task_udid.replace('-', '') == udid)) and (task.tunnel is not None):
109
+ task_udid = task.udid or ""
110
+ if ((task_udid == udid) or (task_udid.replace("-", "") == udid)) and (task.tunnel is not None):
91
111
  return True
92
112
 
93
113
  return False
@@ -116,16 +136,18 @@ class TunneldCore:
116
136
  # A new interface was attached
117
137
  for answer in await browse_remoted():
118
138
  for address in answer.addresses:
119
- if address.iface.startswith('utun'):
139
+ if address.iface.startswith("utun"):
120
140
  # Skip already established tunnels
121
141
  continue
122
- if address.full_ip in self.tunnel_tasks.keys():
142
+ if address.full_ip in self.tunnel_tasks:
123
143
  # Skip already established tunnels
124
144
  continue
125
145
  self.tunnel_tasks[address.full_ip] = TunnelTask(
126
146
  task=asyncio.create_task(
127
147
  self.handle_new_potential_usb_cdc_ncm_interface_task(address.full_ip),
128
- name=f'handle-new-potential-usb-cdc-ncm-interface-task-{address.full_ip}'))
148
+ name=f"handle-new-potential-usb-cdc-ncm-interface-task-{address.full_ip}",
149
+ )
150
+ )
129
151
 
130
152
  # wait before re-iterating
131
153
  await asyncio.sleep(1)
@@ -148,9 +170,11 @@ class TunneldCore:
148
170
  await service.close()
149
171
  continue
150
172
  self.tunnel_tasks[service.hostname] = TunnelTask(
151
- task=asyncio.create_task(self.start_tunnel_task(service.hostname, service),
152
- name=f'start-tunnel-task-wifi-{service.hostname}'),
153
- udid=service.remote_identifier
173
+ task=asyncio.create_task(
174
+ self.start_tunnel_task(service.hostname, service),
175
+ name=f"start-tunnel-task-wifi-{service.hostname}",
176
+ ),
177
+ udid=service.remote_identifier,
154
178
  )
155
179
  except asyncio.exceptions.IncompleteReadError:
156
180
  continue
@@ -158,7 +182,9 @@ class TunneldCore:
158
182
  # Raise and cancel gracefully
159
183
  raise
160
184
  except Exception:
161
- logger.error(f'Got exception from {asyncio.current_task().get_name()}: {traceback.format_exc()}')
185
+ logger.exception(
186
+ f"Got exception from {asyncio.current_task().get_name()}: {traceback.format_exc()}"
187
+ )
162
188
  continue
163
189
  await asyncio.sleep(REMOTEPAIRING_INTERVAL)
164
190
  except asyncio.CancelledError:
@@ -171,7 +197,7 @@ class TunneldCore:
171
197
  while True:
172
198
  try:
173
199
  for mux_device in usbmux.list_devices():
174
- task_identifier = f'usbmux-{mux_device.serial}-{mux_device.connection_type}'
200
+ task_identifier = f"usbmux-{mux_device.serial}-{mux_device.connection_type}"
175
201
  if self.tunnel_exists_for_udid(mux_device.serial):
176
202
  # Skip if already established a tunnel for this udid
177
203
  continue
@@ -182,23 +208,31 @@ class TunneldCore:
182
208
  try:
183
209
  with create_using_usbmux(mux_device.serial) as lockdown:
184
210
  service = await CoreDeviceTunnelProxy.create(lockdown)
185
- except (MuxException, InvalidServiceError, GetProhibitedError, construct.core.StreamError,
186
- ConnectionAbortedError, DeviceNotFoundError, LockdownError, IncorrectModeError,
187
- SSLEOFError):
211
+ except (
212
+ MuxException,
213
+ InvalidServiceError,
214
+ GetProhibitedError,
215
+ construct.core.StreamError,
216
+ ConnectionAbortedError,
217
+ DeviceNotFoundError,
218
+ LockdownError,
219
+ IncorrectModeError,
220
+ SSLEOFError,
221
+ ):
188
222
  if service is not None:
189
223
  await service.close()
190
224
  continue
191
225
  self.tunnel_tasks[task_identifier] = TunnelTask(
192
226
  udid=mux_device.serial,
193
227
  task=asyncio.create_task(
194
- self.start_tunnel_task(task_identifier,
195
- service,
196
- protocol=TunnelProtocol.TCP),
197
- name=f'start-tunnel-task-{task_identifier}'))
228
+ self.start_tunnel_task(task_identifier, service, protocol=TunnelProtocol.TCP),
229
+ name=f"start-tunnel-task-{task_identifier}",
230
+ ),
231
+ )
198
232
  except ConnectionFailedToUsbmuxdError:
199
233
  # This is exception is expected to occur repeatedly on linux running usbmuxd
200
234
  # as long as there isn't any physical iDevice connected
201
- logger.debug('failed to connect to usbmux. waiting for it to restart')
235
+ logger.debug("failed to connect to usbmux. waiting for it to restart")
202
236
  finally:
203
237
  await asyncio.sleep(USBMUX_INTERVAL)
204
238
  except asyncio.CancelledError:
@@ -211,7 +245,7 @@ class TunneldCore:
211
245
  async for ip, lockdown in get_mobdev2_lockdowns(only_paired=True):
212
246
  with lockdown:
213
247
  udid = lockdown.udid
214
- task_identifier = f'mobdev2-{udid}-{ip}'
248
+ task_identifier = f"mobdev2-{udid}-{ip}"
215
249
  if self.tunnel_exists_for_udid(udid):
216
250
  # Skip tunnel if already exists for this udid
217
251
  continue
@@ -221,12 +255,14 @@ class TunneldCore:
221
255
  try:
222
256
  tunnel_service = await CoreDeviceTunnelProxy.create(lockdown)
223
257
  except InvalidServiceError:
224
- logger.warning(f'[{task_identifier}] failed to start CoreDeviceTunnelProxy - skipping')
258
+ logger.warning(f"[{task_identifier}] failed to start CoreDeviceTunnelProxy - skipping")
225
259
  continue
226
260
  self.tunnel_tasks[task_identifier] = TunnelTask(
227
- task=asyncio.create_task(self.start_tunnel_task(task_identifier, tunnel_service),
228
- name=f'start-tunnel-task-{task_identifier}'),
229
- udid=udid
261
+ task=asyncio.create_task(
262
+ self.start_tunnel_task(task_identifier, tunnel_service),
263
+ name=f"start-tunnel-task-{task_identifier}",
264
+ ),
265
+ udid=udid,
230
266
  )
231
267
  await asyncio.sleep(MOBDEV2_INTERVAL)
232
268
  except asyncio.CancelledError:
@@ -234,8 +270,12 @@ class TunneldCore:
234
270
 
235
271
  @asyncio_print_traceback
236
272
  async def start_tunnel_task(
237
- self, task_identifier: str, protocol_handler: Union[RemotePairingProtocol, CoreDeviceTunnelProxy],
238
- queue: Optional[asyncio.Queue] = None, protocol: Optional[TunnelProtocol] = None) -> None:
273
+ self,
274
+ task_identifier: str,
275
+ protocol_handler: Union[RemotePairingProtocol, CoreDeviceTunnelProxy],
276
+ queue: Optional[asyncio.Queue] = None,
277
+ protocol: Optional[TunnelProtocol] = None,
278
+ ) -> None:
239
279
  if protocol is None:
240
280
  protocol = self.protocol
241
281
  if isinstance(protocol_handler, CoreDeviceTunnelProxy):
@@ -255,37 +295,42 @@ class TunneldCore:
255
295
  queue.put_nowait(tun)
256
296
  # avoid sending another message if succeeded
257
297
  queue = None
258
- logger.info(f'[{asyncio.current_task().get_name()}] Created tunnel --rsd {tun.address} {tun.port}')
298
+ logger.info(f"[{asyncio.current_task().get_name()}] Created tunnel --rsd {tun.address} {tun.port}")
259
299
  await tun.client.wait_closed()
260
300
  else:
261
301
  bailed_out = True
262
302
  logger.debug(
263
- f'[{asyncio.current_task().get_name()}] Not establishing tunnel since there is already an '
264
- f'active one for same udid')
303
+ f"[{asyncio.current_task().get_name()}] Not establishing tunnel since there is already an "
304
+ f"active one for same udid"
305
+ )
265
306
  except asyncio.CancelledError:
266
307
  pass
267
- except (asyncio.exceptions.IncompleteReadError, TimeoutError, OSError, ConnectionResetError, StreamError,
268
- InvalidServiceError) as e:
308
+ except (
309
+ asyncio.exceptions.IncompleteReadError,
310
+ TimeoutError,
311
+ OSError,
312
+ ConnectionResetError,
313
+ StreamError,
314
+ InvalidServiceError,
315
+ ) as e:
269
316
  if tun is None:
270
- logger.debug(f'got {e.__class__.__name__} from {asyncio.current_task().get_name()}')
317
+ logger.debug(f"got {e.__class__.__name__} from {asyncio.current_task().get_name()}")
271
318
  else:
272
- logger.debug(f'got {e.__class__.__name__} from tunnel --rsd {tun.address} {tun.port}')
319
+ logger.debug(f"got {e.__class__.__name__} from tunnel --rsd {tun.address} {tun.port}")
273
320
  except Exception:
274
- logger.error(f'got exception from {asyncio.current_task().get_name()}: {traceback.format_exc()}')
321
+ logger.exception(f"got exception from {asyncio.current_task().get_name()}: {traceback.format_exc()}")
275
322
  finally:
276
323
  if queue is not None:
277
324
  # notify something went wrong
278
325
  queue.put_nowait(None)
279
326
 
280
327
  if tun is not None and not bailed_out:
281
- logger.info(f'disconnected from tunnel --rsd {tun.address} {tun.port}')
328
+ logger.info(f"disconnected from tunnel --rsd {tun.address} {tun.port}")
282
329
  await tun.client.stop_tunnel()
283
330
 
284
331
  if protocol_handler is not None:
285
- try:
332
+ with suppress(OSError):
286
333
  await protocol_handler.close()
287
- except OSError:
288
- pass
289
334
 
290
335
  if task_identifier in self.tunnel_tasks:
291
336
  # in case the tunnel was removed just now
@@ -309,47 +354,46 @@ class TunneldCore:
309
354
  # Could be on first try because of remoted race
310
355
  if first_time:
311
356
  retry = True
312
- except (ConnectionRefusedError, TimeoutError, OSError):
313
- raise asyncio.CancelledError()
357
+ except (ConnectionRefusedError, TimeoutError, OSError) as e:
358
+ raise asyncio.CancelledError() from e
314
359
  finally:
315
360
  first_time = False
316
361
 
317
- if (self.protocol == TunnelProtocol.QUIC) and (Version(rsd.product_version) < Version('17.0.0')):
362
+ if (self.protocol == TunnelProtocol.QUIC) and (Version(rsd.product_version) < Version("17.0.0")):
318
363
  await rsd.close()
319
364
  rsd = None
320
365
  raise asyncio.CancelledError()
321
366
 
322
367
  await asyncio.create_task(
323
368
  self.start_tunnel_task(ip, await create_core_device_tunnel_service_using_rsd(rsd)),
324
- name=f'start-tunnel-task-usb-{ip}')
369
+ name=f"start-tunnel-task-usb-{ip}",
370
+ )
325
371
  except asyncio.CancelledError:
326
372
  pass
327
- except PairingError as e:
328
- logger.error(f'Failed to pair with {ip} with error: {e}')
373
+ except PairingError:
374
+ logger.exception(f"Failed to pair with {ip}")
329
375
  except RuntimeError:
330
- logger.debug(f'Got RuntimeError from: {asyncio.current_task().get_name()}')
376
+ logger.debug(f"Got RuntimeError from: {asyncio.current_task().get_name()}")
331
377
  except Exception:
332
- logger.error(f'Error raised from: {asyncio.current_task().get_name()}: {traceback.format_exc()}')
378
+ logger.exception(f"Error raised from: {asyncio.current_task().get_name()}: {traceback.format_exc()}")
333
379
  finally:
334
380
  if rsd is not None:
335
- try:
381
+ with suppress(OSError):
336
382
  await rsd.close()
337
- except OSError:
338
- pass
339
383
 
340
384
  if ip in self.tunnel_tasks:
341
385
  # In case the tunnel was removed just now
342
386
  self.tunnel_tasks.pop(ip)
343
387
 
344
388
  async def close(self) -> None:
345
- """ close all tasks """
389
+ """close all tasks"""
346
390
  for task in self.tasks + [tunnel_task.task for tunnel_task in self.tunnel_tasks.values()]:
347
391
  task.cancel()
348
392
  with suppress(asyncio.CancelledError):
349
393
  await task
350
394
 
351
395
  def get_tunnels_ips(self) -> dict:
352
- """ Retrieve the available tunnel tasks and format them as {UDID: [IP]} """
396
+ """Retrieve the available tunnel tasks and format them as {UDID: [IP]}"""
353
397
  tunnels_ips = {}
354
398
  for ip, active_tunnel in self.tunnel_tasks.items():
355
399
  if (active_tunnel.udid is None) or (active_tunnel.tunnel is None):
@@ -361,47 +405,75 @@ class TunneldCore:
361
405
  return tunnels_ips
362
406
 
363
407
  def cancel(self, udid: str) -> None:
364
- """ Cancel active tunnels """
408
+ """Cancel active tunnels"""
365
409
  for tunnel_ip in self.get_tunnels_ips().get(udid, []):
366
410
  self.tunnel_tasks.pop(tunnel_ip).task.cancel()
367
- logger.info(f'Canceling tunnel {tunnel_ip}')
411
+ logger.info(f"Canceling tunnel {tunnel_ip}")
368
412
 
369
413
  def clear(self) -> None:
370
- """ Clear active tunnels """
371
- for udid, tunnel in self.tunnel_tasks.items():
372
- logger.info(f'Removing tunnel {tunnel}')
414
+ """Clear active tunnels"""
415
+ for _udid, tunnel in self.tunnel_tasks.items():
416
+ logger.info(f"Removing tunnel {tunnel}")
373
417
  tunnel.task.cancel()
374
418
  self.tunnel_tasks = {}
375
419
 
376
420
 
377
421
  class TunneldRunner:
378
- """ TunneldRunner orchestrate between the webserver and TunneldCore """
422
+ """TunneldRunner orchestrate between the webserver and TunneldCore"""
379
423
 
380
424
  @classmethod
381
- def create(cls, host: str, port: int, protocol: TunnelProtocol = TunnelProtocol.QUIC, usb_monitor: bool = True,
382
- wifi_monitor: bool = True, usbmux_monitor: bool = True, mobdev2_monitor: bool = True) -> None:
383
- cls(host, port, protocol=protocol, usb_monitor=usb_monitor, wifi_monitor=wifi_monitor,
384
- usbmux_monitor=usbmux_monitor, mobdev2_monitor=mobdev2_monitor)._run_app()
385
-
386
- def __init__(self, host: str, port: int, protocol: TunnelProtocol = TunnelProtocol.QUIC, usb_monitor: bool = True,
387
- wifi_monitor: bool = True, usbmux_monitor: bool = True, mobdev2_monitor: bool = True):
425
+ def create(
426
+ cls,
427
+ host: str,
428
+ port: int,
429
+ protocol: TunnelProtocol = TunnelProtocol.QUIC,
430
+ usb_monitor: bool = True,
431
+ wifi_monitor: bool = True,
432
+ usbmux_monitor: bool = True,
433
+ mobdev2_monitor: bool = True,
434
+ ) -> None:
435
+ cls(
436
+ host,
437
+ port,
438
+ protocol=protocol,
439
+ usb_monitor=usb_monitor,
440
+ wifi_monitor=wifi_monitor,
441
+ usbmux_monitor=usbmux_monitor,
442
+ mobdev2_monitor=mobdev2_monitor,
443
+ )._run_app()
444
+
445
+ def __init__(
446
+ self,
447
+ host: str,
448
+ port: int,
449
+ protocol: TunnelProtocol = TunnelProtocol.QUIC,
450
+ usb_monitor: bool = True,
451
+ wifi_monitor: bool = True,
452
+ usbmux_monitor: bool = True,
453
+ mobdev2_monitor: bool = True,
454
+ ):
388
455
  @asynccontextmanager
389
456
  async def lifespan(app: FastAPI):
390
457
  self._tunneld_core.start()
391
458
  yield
392
- logger.info('Closing tunneld tasks...')
459
+ logger.info("Closing tunneld tasks...")
393
460
  await self._tunneld_core.close()
394
461
 
395
462
  self.host = host
396
463
  self.port = port
397
464
  self.protocol = protocol
398
465
  self._app = FastAPI(lifespan=lifespan)
399
- self._tunneld_core = TunneldCore(protocol=protocol, wifi_monitor=wifi_monitor, usb_monitor=usb_monitor,
400
- usbmux_monitor=usbmux_monitor, mobdev2_monitor=mobdev2_monitor)
401
-
402
- @self._app.get('/')
466
+ self._tunneld_core = TunneldCore(
467
+ protocol=protocol,
468
+ wifi_monitor=wifi_monitor,
469
+ usb_monitor=usb_monitor,
470
+ usbmux_monitor=usbmux_monitor,
471
+ mobdev2_monitor=mobdev2_monitor,
472
+ )
473
+
474
+ @self._app.get("/")
403
475
  async def list_tunnels() -> dict[str, list[dict]]:
404
- """ Retrieve the available tunnels and format them as {UUID: TUNNEL_ADDRESS} """
476
+ """Retrieve the available tunnels and format them as {UUID: TUNNEL_ADDRESS}"""
405
477
  tunnels = {}
406
478
  for ip, active_tunnel in self._tunneld_core.tunnel_tasks.items():
407
479
  if (active_tunnel.udid is None) or (active_tunnel.tunnel is None):
@@ -409,52 +481,53 @@ class TunneldRunner:
409
481
  if active_tunnel.udid not in tunnels:
410
482
  tunnels[active_tunnel.udid] = []
411
483
  tunnels[active_tunnel.udid].append({
412
- 'tunnel-address': active_tunnel.tunnel.address,
413
- 'tunnel-port': active_tunnel.tunnel.port,
414
- 'interface': ip})
484
+ "tunnel-address": active_tunnel.tunnel.address,
485
+ "tunnel-port": active_tunnel.tunnel.port,
486
+ "interface": ip,
487
+ })
415
488
  return tunnels
416
489
 
417
- @self._app.get('/shutdown')
490
+ @self._app.get("/shutdown")
418
491
  async def shutdown() -> fastapi.Response:
419
- """ Shutdown Tunneld """
492
+ """Shutdown Tunneld"""
420
493
  os.kill(os.getpid(), signal.SIGINT)
421
- data = {'operation': 'shutdown', 'data': True, 'message': 'Server shutting down...'}
494
+ data = {"operation": "shutdown", "data": True, "message": "Server shutting down..."}
422
495
  return generate_http_response(data)
423
496
 
424
- @self._app.get('/clear_tunnels')
497
+ @self._app.get("/clear_tunnels")
425
498
  async def clear_tunnels() -> fastapi.Response:
426
499
  self._tunneld_core.clear()
427
- data = {'operation': 'clear_tunnels', 'data': True, 'message': 'Cleared tunnels...'}
500
+ data = {"operation": "clear_tunnels", "data": True, "message": "Cleared tunnels..."}
428
501
  return generate_http_response(data)
429
502
 
430
- @self._app.get('/cancel')
503
+ @self._app.get("/cancel")
431
504
  async def cancel_tunnel(udid: str) -> fastapi.Response:
432
505
  self._tunneld_core.cancel(udid=udid)
433
- data = {'operation': 'cancel', 'udid': udid, 'data': True, 'message': f'tunnel {udid} Canceled ...'}
506
+ data = {"operation": "cancel", "udid": udid, "data": True, "message": f"tunnel {udid} Canceled ..."}
434
507
  return generate_http_response(data)
435
508
 
436
- @self._app.get('/hello')
509
+ @self._app.get("/hello")
437
510
  async def hello() -> fastapi.Response:
438
- data = {'message': 'Hello, I\'m alive'}
511
+ data = {"message": "Hello, I'm alive"}
439
512
  return generate_http_response(data)
440
513
 
441
514
  def generate_http_response(
442
- data: dict, status_code: int = 200, media_type: str = "application/json") -> fastapi.Response:
443
- return fastapi.Response(
444
- status_code=status_code,
445
- media_type=media_type,
446
- content=json.dumps(data))
515
+ data: dict, status_code: int = 200, media_type: str = "application/json"
516
+ ) -> fastapi.Response:
517
+ return fastapi.Response(status_code=status_code, media_type=media_type, content=json.dumps(data))
447
518
 
448
- @self._app.get('/start-tunnel')
519
+ @self._app.get("/start-tunnel")
449
520
  async def start_tunnel(
450
- udid: str, ip: Optional[str] = None, connection_type: Optional[str] = None) -> fastapi.Response:
451
- udid_tunnels = [t.tunnel for t in self._tunneld_core.tunnel_tasks.values() if
452
- t.udid == udid and t.tunnel is not None]
521
+ udid: str, ip: Optional[str] = None, connection_type: Optional[str] = None
522
+ ) -> fastapi.Response:
523
+ udid_tunnels = [
524
+ t.tunnel for t in self._tunneld_core.tunnel_tasks.values() if t.udid == udid and t.tunnel is not None
525
+ ]
453
526
  if len(udid_tunnels) > 0:
454
527
  data = {
455
- 'interface': udid_tunnels[0].interface,
456
- 'port': udid_tunnels[0].port,
457
- 'address': udid_tunnels[0].address
528
+ "interface": udid_tunnels[0].interface,
529
+ "port": udid_tunnels[0].port,
530
+ "address": udid_tunnels[0].address,
458
531
  }
459
532
  return generate_http_response(data)
460
533
 
@@ -462,33 +535,36 @@ class TunneldRunner:
462
535
  created_task = False
463
536
 
464
537
  try:
465
- if not created_task and connection_type in ('usbmux', None):
466
- task_identifier = f'usbmux-{udid}'
538
+ if not created_task and connection_type in ("usbmux", None):
539
+ task_identifier = f"usbmux-{udid}"
467
540
  try:
468
541
  with create_using_usbmux(udid) as lockdown:
469
542
  service = await CoreDeviceTunnelProxy.create(lockdown)
470
543
  task = asyncio.create_task(
471
- self._tunneld_core.start_tunnel_task(task_identifier, service, protocol=TunnelProtocol.TCP,
472
- queue=queue),
473
- name=f'start-tunnel-task-{task_identifier}')
544
+ self._tunneld_core.start_tunnel_task(
545
+ task_identifier, service, protocol=TunnelProtocol.TCP, queue=queue
546
+ ),
547
+ name=f"start-tunnel-task-{task_identifier}",
548
+ )
474
549
  self._tunneld_core.tunnel_tasks[task_identifier] = TunnelTask(task=task, udid=udid)
475
550
  created_task = True
476
551
  except (ConnectionFailedError, InvalidServiceError, MuxException):
477
552
  pass
478
- if connection_type in ('usb', None):
553
+ if connection_type in ("usb", None):
479
554
  for rsd in await get_rsds(udid=udid):
480
555
  rsd_ip = rsd.service.address[0]
481
556
  if ip is not None and rsd_ip != ip:
482
557
  await rsd.close()
483
558
  continue
484
559
  task = asyncio.create_task(
485
- self._tunneld_core.start_tunnel_task(rsd_ip,
486
- await create_core_device_tunnel_service_using_rsd(rsd),
487
- queue=queue),
488
- name=f'start-tunnel-usb-{rsd_ip}')
560
+ self._tunneld_core.start_tunnel_task(
561
+ rsd_ip, await create_core_device_tunnel_service_using_rsd(rsd), queue=queue
562
+ ),
563
+ name=f"start-tunnel-usb-{rsd_ip}",
564
+ )
489
565
  self._tunneld_core.tunnel_tasks[rsd_ip] = TunnelTask(task=task, udid=rsd.udid)
490
566
  created_task = True
491
- if not created_task and connection_type in ('wifi', None):
567
+ if not created_task and connection_type in ("wifi", None):
492
568
  for remotepairing in await get_remote_pairing_tunnel_services(udid=udid):
493
569
  remotepairing_ip = remotepairing.hostname
494
570
  if ip is not None and remotepairing_ip != ip:
@@ -496,31 +572,34 @@ class TunneldRunner:
496
572
  continue
497
573
  task = asyncio.create_task(
498
574
  self._tunneld_core.start_tunnel_task(remotepairing_ip, remotepairing, queue=queue),
499
- name=f'start-tunnel-wifi-{remotepairing_ip}')
575
+ name=f"start-tunnel-wifi-{remotepairing_ip}",
576
+ )
500
577
  self._tunneld_core.tunnel_tasks[remotepairing_ip] = TunnelTask(
501
- task=task, udid=remotepairing.remote_identifier)
578
+ task=task, udid=remotepairing.remote_identifier
579
+ )
502
580
  created_task = True
503
581
  except Exception as e:
504
- return fastapi.Response(status_code=501,
505
- content=json.dumps({'error': {
506
- 'exception': e.__class__.__name__,
507
- 'traceback': traceback.format_exc(),
508
- }}))
582
+ return fastapi.Response(
583
+ status_code=501,
584
+ content=json.dumps({
585
+ "error": {
586
+ "exception": e.__class__.__name__,
587
+ "traceback": traceback.format_exc(),
588
+ }
589
+ }),
590
+ )
509
591
 
510
592
  if not created_task:
511
- return fastapi.Response(status_code=501, content=json.dumps({'error': 'task not created'}))
593
+ return fastapi.Response(status_code=501, content=json.dumps({"error": "task not created"}))
512
594
 
513
595
  tunnel: Optional[TunnelResult] = await queue.get()
514
596
  if tunnel is not None:
515
- data = {
516
- 'interface': tunnel.interface,
517
- 'port': tunnel.port,
518
- 'address': tunnel.address
519
- }
597
+ data = {"interface": tunnel.interface, "port": tunnel.port, "address": tunnel.address}
520
598
  return generate_http_response(data)
521
599
  else:
522
- return fastapi.Response(status_code=404,
523
- content=json.dumps({'error': 'something went wrong during tunnel creation'}))
600
+ return fastapi.Response(
601
+ status_code=404, content=json.dumps({"error": "something went wrong during tunnel creation"})
602
+ )
524
603
 
525
604
  def _run_app(self) -> None:
526
- uvicorn.run(self._app, host=self.host, port=self.port, loop='asyncio')
605
+ uvicorn.run(self._app, host=self.host, port=self.port, loop="asyncio")