pymobiledevice3 4.14.6__py3-none-any.whl → 7.0.6__py3-none-any.whl

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (164) hide show
  1. misc/plist_sniffer.py +15 -15
  2. misc/remotexpc_sniffer.py +29 -28
  3. misc/understanding_idevice_protocol_layers.md +15 -10
  4. pymobiledevice3/__main__.py +317 -127
  5. pymobiledevice3/_version.py +22 -4
  6. pymobiledevice3/bonjour.py +358 -113
  7. pymobiledevice3/ca.py +253 -16
  8. pymobiledevice3/cli/activation.py +31 -23
  9. pymobiledevice3/cli/afc.py +49 -40
  10. pymobiledevice3/cli/amfi.py +16 -21
  11. pymobiledevice3/cli/apps.py +87 -42
  12. pymobiledevice3/cli/backup.py +160 -90
  13. pymobiledevice3/cli/bonjour.py +44 -40
  14. pymobiledevice3/cli/cli_common.py +204 -198
  15. pymobiledevice3/cli/companion_proxy.py +14 -14
  16. pymobiledevice3/cli/crash.py +105 -56
  17. pymobiledevice3/cli/developer/__init__.py +62 -0
  18. pymobiledevice3/cli/developer/accessibility/__init__.py +65 -0
  19. pymobiledevice3/cli/developer/accessibility/settings.py +43 -0
  20. pymobiledevice3/cli/developer/arbitration.py +50 -0
  21. pymobiledevice3/cli/developer/condition.py +33 -0
  22. pymobiledevice3/cli/developer/core_device.py +294 -0
  23. pymobiledevice3/cli/developer/debugserver.py +244 -0
  24. pymobiledevice3/cli/developer/dvt/__init__.py +438 -0
  25. pymobiledevice3/cli/developer/dvt/core_profile_session.py +295 -0
  26. pymobiledevice3/cli/developer/dvt/simulate_location.py +56 -0
  27. pymobiledevice3/cli/developer/dvt/sysmon/__init__.py +69 -0
  28. pymobiledevice3/cli/developer/dvt/sysmon/process.py +188 -0
  29. pymobiledevice3/cli/developer/fetch_symbols.py +108 -0
  30. pymobiledevice3/cli/developer/simulate_location.py +51 -0
  31. pymobiledevice3/cli/diagnostics/__init__.py +75 -0
  32. pymobiledevice3/cli/diagnostics/battery.py +47 -0
  33. pymobiledevice3/cli/idam.py +42 -0
  34. pymobiledevice3/cli/lockdown.py +108 -103
  35. pymobiledevice3/cli/mounter.py +158 -99
  36. pymobiledevice3/cli/notification.py +38 -26
  37. pymobiledevice3/cli/pcap.py +45 -24
  38. pymobiledevice3/cli/power_assertion.py +18 -17
  39. pymobiledevice3/cli/processes.py +17 -23
  40. pymobiledevice3/cli/profile.py +165 -109
  41. pymobiledevice3/cli/provision.py +35 -34
  42. pymobiledevice3/cli/remote.py +217 -129
  43. pymobiledevice3/cli/restore.py +159 -143
  44. pymobiledevice3/cli/springboard.py +63 -53
  45. pymobiledevice3/cli/syslog.py +193 -86
  46. pymobiledevice3/cli/usbmux.py +73 -33
  47. pymobiledevice3/cli/version.py +5 -7
  48. pymobiledevice3/cli/webinspector.py +376 -214
  49. pymobiledevice3/common.py +3 -1
  50. pymobiledevice3/exceptions.py +182 -58
  51. pymobiledevice3/irecv.py +52 -53
  52. pymobiledevice3/irecv_devices.py +1489 -464
  53. pymobiledevice3/lockdown.py +473 -275
  54. pymobiledevice3/lockdown_service_provider.py +15 -8
  55. pymobiledevice3/osu/os_utils.py +27 -9
  56. pymobiledevice3/osu/posix_util.py +34 -15
  57. pymobiledevice3/osu/win_util.py +14 -8
  58. pymobiledevice3/pair_records.py +102 -21
  59. pymobiledevice3/remote/common.py +8 -4
  60. pymobiledevice3/remote/core_device/app_service.py +94 -67
  61. pymobiledevice3/remote/core_device/core_device_service.py +17 -14
  62. pymobiledevice3/remote/core_device/device_info.py +5 -5
  63. pymobiledevice3/remote/core_device/diagnostics_service.py +19 -4
  64. pymobiledevice3/remote/core_device/file_service.py +53 -23
  65. pymobiledevice3/remote/remote_service_discovery.py +79 -45
  66. pymobiledevice3/remote/remotexpc.py +73 -44
  67. pymobiledevice3/remote/tunnel_service.py +442 -317
  68. pymobiledevice3/remote/utils.py +14 -13
  69. pymobiledevice3/remote/xpc_message.py +145 -125
  70. pymobiledevice3/resources/dsc_uuid_map.py +19 -19
  71. pymobiledevice3/resources/firmware_notifications.py +20 -16
  72. pymobiledevice3/resources/notifications.txt +144 -0
  73. pymobiledevice3/restore/asr.py +27 -27
  74. pymobiledevice3/restore/base_restore.py +110 -21
  75. pymobiledevice3/restore/consts.py +87 -66
  76. pymobiledevice3/restore/device.py +59 -12
  77. pymobiledevice3/restore/fdr.py +46 -48
  78. pymobiledevice3/restore/ftab.py +19 -19
  79. pymobiledevice3/restore/img4.py +163 -0
  80. pymobiledevice3/restore/mbn.py +587 -0
  81. pymobiledevice3/restore/recovery.py +151 -151
  82. pymobiledevice3/restore/restore.py +562 -544
  83. pymobiledevice3/restore/restore_options.py +131 -110
  84. pymobiledevice3/restore/restored_client.py +51 -31
  85. pymobiledevice3/restore/tss.py +385 -267
  86. pymobiledevice3/service_connection.py +252 -59
  87. pymobiledevice3/services/accessibilityaudit.py +202 -120
  88. pymobiledevice3/services/afc.py +962 -365
  89. pymobiledevice3/services/amfi.py +24 -30
  90. pymobiledevice3/services/companion.py +23 -19
  91. pymobiledevice3/services/crash_reports.py +71 -47
  92. pymobiledevice3/services/debugserver_applist.py +3 -3
  93. pymobiledevice3/services/device_arbitration.py +8 -8
  94. pymobiledevice3/services/device_link.py +101 -79
  95. pymobiledevice3/services/diagnostics.py +973 -967
  96. pymobiledevice3/services/dtfetchsymbols.py +8 -8
  97. pymobiledevice3/services/dvt/dvt_secure_socket_proxy.py +4 -4
  98. pymobiledevice3/services/dvt/dvt_testmanaged_proxy.py +4 -4
  99. pymobiledevice3/services/dvt/instruments/activity_trace_tap.py +85 -74
  100. pymobiledevice3/services/dvt/instruments/application_listing.py +2 -3
  101. pymobiledevice3/services/dvt/instruments/condition_inducer.py +7 -6
  102. pymobiledevice3/services/dvt/instruments/core_profile_session_tap.py +466 -384
  103. pymobiledevice3/services/dvt/instruments/device_info.py +20 -11
  104. pymobiledevice3/services/dvt/instruments/energy_monitor.py +1 -1
  105. pymobiledevice3/services/dvt/instruments/graphics.py +1 -1
  106. pymobiledevice3/services/dvt/instruments/location_simulation.py +1 -1
  107. pymobiledevice3/services/dvt/instruments/location_simulation_base.py +10 -10
  108. pymobiledevice3/services/dvt/instruments/network_monitor.py +17 -17
  109. pymobiledevice3/services/dvt/instruments/notifications.py +1 -1
  110. pymobiledevice3/services/dvt/instruments/process_control.py +35 -10
  111. pymobiledevice3/services/dvt/instruments/screenshot.py +2 -2
  112. pymobiledevice3/services/dvt/instruments/sysmontap.py +15 -15
  113. pymobiledevice3/services/dvt/testmanaged/xcuitest.py +42 -52
  114. pymobiledevice3/services/file_relay.py +10 -10
  115. pymobiledevice3/services/heartbeat.py +9 -8
  116. pymobiledevice3/services/house_arrest.py +16 -15
  117. pymobiledevice3/services/idam.py +20 -0
  118. pymobiledevice3/services/installation_proxy.py +173 -81
  119. pymobiledevice3/services/lockdown_service.py +20 -10
  120. pymobiledevice3/services/misagent.py +22 -19
  121. pymobiledevice3/services/mobile_activation.py +147 -64
  122. pymobiledevice3/services/mobile_config.py +331 -294
  123. pymobiledevice3/services/mobile_image_mounter.py +141 -113
  124. pymobiledevice3/services/mobilebackup2.py +203 -145
  125. pymobiledevice3/services/notification_proxy.py +11 -11
  126. pymobiledevice3/services/os_trace.py +134 -74
  127. pymobiledevice3/services/pcapd.py +314 -302
  128. pymobiledevice3/services/power_assertion.py +10 -9
  129. pymobiledevice3/services/preboard.py +4 -4
  130. pymobiledevice3/services/remote_fetch_symbols.py +21 -14
  131. pymobiledevice3/services/remote_server.py +176 -146
  132. pymobiledevice3/services/restore_service.py +16 -16
  133. pymobiledevice3/services/screenshot.py +15 -12
  134. pymobiledevice3/services/simulate_location.py +7 -7
  135. pymobiledevice3/services/springboard.py +15 -15
  136. pymobiledevice3/services/syslog.py +5 -5
  137. pymobiledevice3/services/web_protocol/alert.py +11 -11
  138. pymobiledevice3/services/web_protocol/automation_session.py +251 -239
  139. pymobiledevice3/services/web_protocol/cdp_screencast.py +46 -37
  140. pymobiledevice3/services/web_protocol/cdp_server.py +19 -19
  141. pymobiledevice3/services/web_protocol/cdp_target.py +411 -373
  142. pymobiledevice3/services/web_protocol/driver.py +114 -111
  143. pymobiledevice3/services/web_protocol/element.py +124 -111
  144. pymobiledevice3/services/web_protocol/inspector_session.py +106 -102
  145. pymobiledevice3/services/web_protocol/selenium_api.py +49 -49
  146. pymobiledevice3/services/web_protocol/session_protocol.py +18 -12
  147. pymobiledevice3/services/web_protocol/switch_to.py +30 -27
  148. pymobiledevice3/services/webinspector.py +189 -155
  149. pymobiledevice3/tcp_forwarder.py +87 -69
  150. pymobiledevice3/tunneld/__init__.py +0 -0
  151. pymobiledevice3/tunneld/api.py +63 -0
  152. pymobiledevice3/tunneld/server.py +603 -0
  153. pymobiledevice3/usbmux.py +198 -147
  154. pymobiledevice3/utils.py +14 -11
  155. {pymobiledevice3-4.14.6.dist-info → pymobiledevice3-7.0.6.dist-info}/METADATA +55 -28
  156. pymobiledevice3-7.0.6.dist-info/RECORD +188 -0
  157. {pymobiledevice3-4.14.6.dist-info → pymobiledevice3-7.0.6.dist-info}/WHEEL +1 -1
  158. pymobiledevice3/cli/developer.py +0 -1215
  159. pymobiledevice3/cli/diagnostics.py +0 -99
  160. pymobiledevice3/tunneld.py +0 -524
  161. pymobiledevice3-4.14.6.dist-info/RECORD +0 -168
  162. {pymobiledevice3-4.14.6.dist-info → pymobiledevice3-7.0.6.dist-info}/entry_points.txt +0 -0
  163. {pymobiledevice3-4.14.6.dist-info → pymobiledevice3-7.0.6.dist-info/licenses}/LICENSE +0 -0
  164. {pymobiledevice3-4.14.6.dist-info → pymobiledevice3-7.0.6.dist-info}/top_level.txt +0 -0
@@ -1,99 +0,0 @@
1
- import logging
2
- import time
3
-
4
- import click
5
-
6
- from pymobiledevice3.cli.cli_common import Command, print_json
7
- from pymobiledevice3.lockdown import LockdownClient
8
- from pymobiledevice3.lockdown_service_provider import LockdownServiceProvider
9
- from pymobiledevice3.services.diagnostics import DiagnosticsService
10
-
11
- logger = logging.getLogger(__name__)
12
-
13
-
14
- @click.group()
15
- def cli() -> None:
16
- pass
17
-
18
-
19
- @cli.group()
20
- def diagnostics() -> None:
21
- """ Reboot/Shutdown device or access other diagnostics services """
22
- pass
23
-
24
-
25
- @diagnostics.command('restart', cls=Command)
26
- def diagnostics_restart(service_provider: LockdownClient):
27
- """ Restart device """
28
- DiagnosticsService(lockdown=service_provider).restart()
29
-
30
-
31
- @diagnostics.command('shutdown', cls=Command)
32
- def diagnostics_shutdown(service_provider: LockdownClient):
33
- """ Shutdown device """
34
- DiagnosticsService(lockdown=service_provider).shutdown()
35
-
36
-
37
- @diagnostics.command('sleep', cls=Command)
38
- def diagnostics_sleep(service_provider: LockdownClient):
39
- """ Put device into sleep """
40
- DiagnosticsService(lockdown=service_provider).sleep()
41
-
42
-
43
- @diagnostics.command('info', cls=Command)
44
- def diagnostics_info(service_provider: LockdownClient):
45
- """ Get diagnostics info """
46
- print_json(DiagnosticsService(lockdown=service_provider).info())
47
-
48
-
49
- @diagnostics.command('ioregistry', cls=Command)
50
- @click.option('--plane')
51
- @click.option('--name')
52
- @click.option('--ioclass')
53
- def diagnostics_ioregistry(service_provider: LockdownClient, plane, name, ioclass):
54
- """ Get ioregistry info """
55
- print_json(DiagnosticsService(lockdown=service_provider).ioregistry(plane=plane, name=name, ioclass=ioclass))
56
-
57
-
58
- @diagnostics.command('mg', cls=Command)
59
- @click.argument('keys', nargs=-1, default=None)
60
- def diagnostics_mg(service_provider: LockdownClient, keys):
61
- """ Get MobileGestalt key values from given list. If empty, return all known. """
62
- print_json(DiagnosticsService(lockdown=service_provider).mobilegestalt(keys=keys))
63
-
64
-
65
- @diagnostics.group('battery')
66
- def diagnostics_battery():
67
- """ Battery options """
68
- pass
69
-
70
-
71
- @diagnostics_battery.command('single', cls=Command)
72
- def diagnostics_battery_single(service_provider: LockdownClient):
73
- """ get single snapshot of battery data """
74
- raw_info = DiagnosticsService(lockdown=service_provider).get_battery()
75
- print_json(raw_info)
76
-
77
-
78
- @diagnostics_battery.command('monitor', cls=Command)
79
- def diagnostics_battery_monitor(service_provider: LockdownClient):
80
- """ monitor battery usage """
81
- diagnostics = DiagnosticsService(lockdown=service_provider)
82
- while True:
83
- raw_info = diagnostics.get_battery()
84
- info = {
85
- 'InstantAmperage': raw_info.get('InstantAmperage'),
86
- 'Temperature': raw_info.get('Temperature'),
87
- 'Voltage': raw_info.get('Voltage'),
88
- 'IsCharging': raw_info.get('IsCharging'),
89
- 'CurrentCapacity': raw_info.get('CurrentCapacity'),
90
- }
91
- logger.info(info)
92
- time.sleep(1)
93
-
94
-
95
- @diagnostics.command('wifi', cls=Command)
96
- def diagnostics_wifi(service_provider: LockdownServiceProvider) -> None:
97
- """ Query WiFi info from IORegistry """
98
- raw_info = DiagnosticsService(lockdown=service_provider).get_wifi()
99
- print_json(raw_info)
@@ -1,524 +0,0 @@
1
- import asyncio
2
- import dataclasses
3
- import json
4
- import logging
5
- import os
6
- import signal
7
- import traceback
8
- from contextlib import asynccontextmanager, suppress
9
- from typing import Optional, Union
10
-
11
- import construct
12
- import fastapi
13
- import requests
14
- import uvicorn
15
- from construct import StreamError
16
- from fastapi import FastAPI
17
- from packaging.version import Version
18
-
19
- from pymobiledevice3 import usbmux
20
- from pymobiledevice3.bonjour import REMOTED_SERVICE_NAMES, browse
21
- from pymobiledevice3.exceptions import ConnectionFailedError, ConnectionFailedToUsbmuxdError, DeviceNotFoundError, \
22
- GetProhibitedError, InvalidServiceError, MuxException, PairingError, TunneldConnectionError
23
- from pymobiledevice3.lockdown import create_using_usbmux, get_mobdev2_lockdowns
24
- from pymobiledevice3.osu.os_utils import get_os_utils
25
- from pymobiledevice3.remote.common import TunnelProtocol
26
- from pymobiledevice3.remote.module_imports import start_tunnel
27
- from pymobiledevice3.remote.remote_service_discovery import RSD_PORT, RemoteServiceDiscoveryService
28
- from pymobiledevice3.remote.tunnel_service import CoreDeviceTunnelProxy, RemotePairingProtocol, TunnelResult, \
29
- create_core_device_tunnel_service_using_rsd, get_remote_pairing_tunnel_services
30
- from pymobiledevice3.remote.utils import get_rsds, stop_remoted
31
- from pymobiledevice3.utils import asyncio_print_traceback, get_asyncio_loop
32
-
33
- logger = logging.getLogger(__name__)
34
-
35
- TUNNELD_DEFAULT_ADDRESS = ('127.0.0.1', 49151)
36
-
37
- # bugfix: after the device reboots, it might take some time for remoted to start answering the bonjour queries
38
- REATTEMPT_INTERVAL = 5
39
- REATTEMPT_COUNT = 5
40
-
41
- REMOTEPAIRING_INTERVAL = 5
42
- MOVDEV2_INTERVAL = 5
43
-
44
- USBMUX_INTERVAL = 2
45
- OSUTILS = get_os_utils()
46
-
47
-
48
- @dataclasses.dataclass
49
- class TunnelTask:
50
- task: asyncio.Task
51
- udid: Optional[str] = None
52
- tunnel: Optional[TunnelResult] = None
53
-
54
-
55
- class TunneldCore:
56
- def __init__(self, protocol: TunnelProtocol = TunnelProtocol.QUIC, wifi_monitor: bool = True,
57
- usb_monitor: bool = True, usbmux_monitor: bool = True, mobdev2_monitor: bool = True) -> None:
58
- self.protocol = protocol
59
- self.tasks: list[asyncio.Task] = []
60
- self.tunnel_tasks: dict[str, TunnelTask] = {}
61
- self.usb_monitor = usb_monitor
62
- self.wifi_monitor = wifi_monitor
63
- self.usbmux_monitor = usbmux_monitor
64
- self.mobdev2_monitor = mobdev2_monitor
65
-
66
- def start(self) -> None:
67
- """ Register all tasks """
68
- self.tasks = []
69
- if self.usb_monitor:
70
- self.tasks.append(asyncio.create_task(self.monitor_usb_task(), name='monitor-usb-task'))
71
- if self.wifi_monitor:
72
- self.tasks.append(asyncio.create_task(self.monitor_wifi_task(), name='monitor-wifi-task'))
73
- if self.usbmux_monitor:
74
- self.tasks.append(asyncio.create_task(self.monitor_usbmux_task(), name='monitor-usbmux-task'))
75
- if self.mobdev2_monitor:
76
- self.tasks.append(asyncio.create_task(self.monitor_mobdev2_task(), name='monitor-mobdev2-task'))
77
-
78
- def tunnel_exists_for_udid(self, udid: str) -> bool:
79
- for task in self.tunnel_tasks.values():
80
- if (task.udid == udid) and (task.tunnel is not None):
81
- return True
82
- return False
83
-
84
- @asyncio_print_traceback
85
- async def monitor_usb_task(self) -> None:
86
- previous_ips = []
87
- while True:
88
- current_ips = OSUTILS.get_ipv6_ips()
89
- added = [ip for ip in current_ips if ip not in previous_ips]
90
- removed = [ip for ip in previous_ips if ip not in current_ips]
91
-
92
- previous_ips = current_ips
93
-
94
- logger.debug(f'added interfaces: {added}')
95
- logger.debug(f'removed interfaces: {removed}')
96
-
97
- for ip in removed:
98
- if ip in self.tunnel_tasks:
99
- self.tunnel_tasks[ip].task.cancel()
100
- await self.tunnel_tasks[ip].task
101
-
102
- for ip in added:
103
- self.tunnel_tasks[ip] = TunnelTask(
104
- task=asyncio.create_task(self.handle_new_potential_usb_cdc_ncm_interface_task(ip),
105
- name=f'handle-new-potential-usb-cdc-ncm-interface-task-{ip}'))
106
-
107
- # wait before re-iterating
108
- await asyncio.sleep(1)
109
-
110
- @asyncio_print_traceback
111
- async def monitor_wifi_task(self) -> None:
112
- try:
113
- while True:
114
- for service in await get_remote_pairing_tunnel_services():
115
- if service.hostname in self.tunnel_tasks:
116
- # skip tunnel if already exists for this ip
117
- await service.close()
118
- continue
119
- if self.tunnel_exists_for_udid(service.remote_identifier):
120
- # skip tunnel if already exists for this udid
121
- await service.close()
122
- continue
123
- self.tunnel_tasks[service.hostname] = TunnelTask(
124
- task=asyncio.create_task(self.start_tunnel_task(service.hostname, service),
125
- name=f'start-tunnel-task-wifi-{service.hostname}'),
126
- udid=service.remote_identifier
127
- )
128
- await asyncio.sleep(REMOTEPAIRING_INTERVAL)
129
- except asyncio.CancelledError:
130
- pass
131
-
132
- @asyncio_print_traceback
133
- async def monitor_usbmux_task(self) -> None:
134
- try:
135
- while True:
136
- try:
137
- for mux_device in usbmux.list_devices():
138
- task_identifier = f'usbmux-{mux_device.serial}-{mux_device.connection_type}'
139
- if self.tunnel_exists_for_udid(mux_device.serial):
140
- continue
141
- try:
142
- service = CoreDeviceTunnelProxy(create_using_usbmux(mux_device.serial))
143
- except (MuxException, InvalidServiceError, GetProhibitedError, construct.core.StreamError,
144
- ConnectionAbortedError, DeviceNotFoundError):
145
- continue
146
- self.tunnel_tasks[task_identifier] = TunnelTask(
147
- udid=mux_device.serial,
148
- task=asyncio.create_task(
149
- self.start_tunnel_task(task_identifier,
150
- service,
151
- protocol=TunnelProtocol.TCP),
152
- name=f'start-tunnel-task-{task_identifier}'))
153
- except ConnectionFailedToUsbmuxdError:
154
- # This is exception is expected to occur repeatedly on linux running usbmuxd
155
- # as long as there isn't any physical iDevice connected
156
- logger.debug('failed to connect to usbmux. waiting for it to restart')
157
- finally:
158
- await asyncio.sleep(USBMUX_INTERVAL)
159
- except asyncio.CancelledError:
160
- pass
161
-
162
- @asyncio_print_traceback
163
- async def monitor_mobdev2_task(self) -> None:
164
- try:
165
- while True:
166
- async for ip, lockdown in get_mobdev2_lockdowns(only_paired=True):
167
- if self.tunnel_exists_for_udid(lockdown.udid):
168
- # skip tunnel if already exists for this udid
169
- continue
170
- task_identifier = f'mobdev2-{lockdown.udid}-{ip}'
171
- try:
172
- tunnel_service = CoreDeviceTunnelProxy(lockdown)
173
- except InvalidServiceError:
174
- logger.warning(f'[{task_identifier}] failed to start CoreDeviceTunnelProxy - skipping')
175
- continue
176
- self.tunnel_tasks[task_identifier] = TunnelTask(
177
- task=asyncio.create_task(self.start_tunnel_task(task_identifier, tunnel_service),
178
- name=f'start-tunnel-task-{task_identifier}'),
179
- udid=lockdown.udid
180
- )
181
- await asyncio.sleep(MOVDEV2_INTERVAL)
182
- except asyncio.CancelledError:
183
- pass
184
-
185
- @asyncio_print_traceback
186
- async def start_tunnel_task(
187
- self, task_identifier: str, protocol_handler: Union[RemotePairingProtocol, CoreDeviceTunnelProxy],
188
- queue: Optional[asyncio.Queue] = None, protocol: Optional[TunnelProtocol] = None) -> None:
189
- if protocol is None:
190
- protocol = self.protocol
191
- if isinstance(protocol_handler, CoreDeviceTunnelProxy):
192
- protocol = TunnelProtocol.TCP
193
- tun = None
194
- bailed_out = False
195
- try:
196
- if self.tunnel_exists_for_udid(protocol_handler.remote_identifier):
197
- # cancel current tunnel creation
198
- raise asyncio.CancelledError()
199
-
200
- async with start_tunnel(protocol_handler, protocol=protocol) as tun:
201
- if not self.tunnel_exists_for_udid(protocol_handler.remote_identifier):
202
- self.tunnel_tasks[task_identifier].tunnel = tun
203
- self.tunnel_tasks[task_identifier].udid = protocol_handler.remote_identifier
204
- if queue is not None:
205
- queue.put_nowait(tun)
206
- # avoid sending another message if succeeded
207
- queue = None
208
- logger.info(f'[{asyncio.current_task().get_name()}] Created tunnel --rsd {tun.address} {tun.port}')
209
- await tun.client.wait_closed()
210
- else:
211
- bailed_out = True
212
- logger.debug(
213
- f'not establishing tunnel from {asyncio.current_task().get_name()} '
214
- f'since there is already an active one for same udid')
215
- except asyncio.CancelledError:
216
- pass
217
- except (ConnectionResetError, StreamError) as e:
218
- logger.debug(f'got {e.__class__.__name__} from {asyncio.current_task().get_name()}')
219
- except (asyncio.exceptions.IncompleteReadError, TimeoutError, OSError) as e:
220
- logger.debug(f'got {e.__class__.__name__} from tunnel --rsd {tun.address} {tun.port}')
221
- except Exception:
222
- logger.error(f'got exception from {asyncio.current_task().get_name()}: {traceback.format_exc()}')
223
- finally:
224
- if queue is not None:
225
- # notify something went wrong
226
- queue.put_nowait(None)
227
-
228
- if tun is not None and not bailed_out:
229
- logger.info(f'disconnected from tunnel --rsd {tun.address} {tun.port}')
230
- await tun.client.stop_tunnel()
231
-
232
- if protocol_handler is not None:
233
- try:
234
- await protocol_handler.close()
235
- except OSError:
236
- pass
237
-
238
- if task_identifier in self.tunnel_tasks:
239
- # in case the tunnel was removed just now
240
- self.tunnel_tasks.pop(task_identifier)
241
-
242
- @asyncio_print_traceback
243
- async def handle_new_potential_usb_cdc_ncm_interface_task(self, ip: str) -> None:
244
- rsd = None
245
- try:
246
- answers = None
247
- for i in range(REATTEMPT_COUNT):
248
- answers = await browse(REMOTED_SERVICE_NAMES, [ip])
249
- if answers:
250
- break
251
- logger.debug(f'No addresses found for: {ip}')
252
- await asyncio.sleep(REATTEMPT_INTERVAL)
253
-
254
- if not answers:
255
- raise asyncio.CancelledError()
256
-
257
- peer_address = answers[0].ips[0]
258
-
259
- # establish an untrusted RSD handshake
260
- rsd = RemoteServiceDiscoveryService((peer_address, RSD_PORT))
261
-
262
- with stop_remoted():
263
- try:
264
- await rsd.connect()
265
- except (ConnectionRefusedError, TimeoutError):
266
- raise asyncio.CancelledError()
267
-
268
- if (self.protocol == TunnelProtocol.QUIC) and (Version(rsd.product_version) < Version('17.0.0')):
269
- await rsd.close()
270
- raise asyncio.CancelledError()
271
-
272
- await asyncio.create_task(
273
- self.start_tunnel_task(ip, await create_core_device_tunnel_service_using_rsd(rsd)),
274
- name=f'start-tunnel-task-usb-{ip}')
275
- except asyncio.CancelledError:
276
- pass
277
- except PairingError as e:
278
- logger.error(f'Failed to pair with {ip} with error: {e}')
279
- except RuntimeError:
280
- logger.debug(f'Got RuntimeError from: {asyncio.current_task().get_name()}')
281
- except Exception:
282
- logger.error(f'Error raised from: {asyncio.current_task().get_name()}: {traceback.format_exc()}')
283
- finally:
284
- if rsd is not None:
285
- try:
286
- await rsd.close()
287
- except OSError:
288
- pass
289
-
290
- if ip in self.tunnel_tasks:
291
- # in case the tunnel was removed just now
292
- self.tunnel_tasks.pop(ip)
293
-
294
- async def close(self) -> None:
295
- """ close all tasks """
296
- for task in self.tasks + [tunnel_task.task for tunnel_task in self.tunnel_tasks.values()]:
297
- task.cancel()
298
- with suppress(asyncio.CancelledError):
299
- await task
300
-
301
- def get_tunnels_ips(self) -> dict:
302
- """ Retrieve the available tunnel tasks and format them as {UDID: [IP]} """
303
- tunnels_ips = {}
304
- for ip, active_tunnel in self.tunnel_tasks.items():
305
- if (active_tunnel.udid is None) or (active_tunnel.tunnel is None):
306
- continue
307
- if active_tunnel.udid not in tunnels_ips:
308
- tunnels_ips[active_tunnel.udid] = [ip]
309
- else:
310
- tunnels_ips[active_tunnel.udid].append(ip)
311
- return tunnels_ips
312
-
313
- def cancel(self, udid: str) -> None:
314
- """ Cancel active tunnels """
315
- for tunnel_ip in self.get_tunnels_ips().get(udid, []):
316
- self.tunnel_tasks.pop(tunnel_ip).task.cancel()
317
- logger.info(f'canceling tunnel {tunnel_ip}')
318
-
319
- def clear(self) -> None:
320
- """ Clear active tunnels """
321
- for udid, tunnel in self.tunnel_tasks.items():
322
- logger.info(f'Removing tunnel {tunnel}')
323
- tunnel.task.cancel()
324
- self.tunnel_tasks = {}
325
-
326
-
327
- class TunneldRunner:
328
- """ TunneldRunner orchestrate between the webserver and TunneldCore """
329
-
330
- @classmethod
331
- def create(cls, host: str, port: int, protocol: TunnelProtocol = TunnelProtocol.QUIC, usb_monitor: bool = True,
332
- wifi_monitor: bool = True, usbmux_monitor: bool = True, mobdev2_monitor: bool = True) -> None:
333
- cls(host, port, protocol=protocol, usb_monitor=usb_monitor, wifi_monitor=wifi_monitor,
334
- usbmux_monitor=usbmux_monitor, mobdev2_monitor=mobdev2_monitor)._run_app()
335
-
336
- def __init__(self, host: str, port: int, protocol: TunnelProtocol = TunnelProtocol.QUIC, usb_monitor: bool = True,
337
- wifi_monitor: bool = True, usbmux_monitor: bool = True, mobdev2_monitor: bool = True):
338
- @asynccontextmanager
339
- async def lifespan(app: FastAPI):
340
- logging.getLogger('zeroconf').disabled = True
341
- self._tunneld_core.start()
342
- yield
343
- logger.info('Closing tunneld tasks...')
344
- await self._tunneld_core.close()
345
-
346
- self.host = host
347
- self.port = port
348
- self.protocol = protocol
349
- self._app = FastAPI(lifespan=lifespan)
350
- self._tunneld_core = TunneldCore(protocol=protocol, wifi_monitor=wifi_monitor, usb_monitor=usb_monitor,
351
- usbmux_monitor=usbmux_monitor, mobdev2_monitor=mobdev2_monitor)
352
-
353
- @self._app.get('/')
354
- async def list_tunnels() -> dict[str, list[dict]]:
355
- """ Retrieve the available tunnels and format them as {UUID: TUNNEL_ADDRESS} """
356
- tunnels = {}
357
- for ip, active_tunnel in self._tunneld_core.tunnel_tasks.items():
358
- if (active_tunnel.udid is None) or (active_tunnel.tunnel is None):
359
- continue
360
- if active_tunnel.udid not in tunnels:
361
- tunnels[active_tunnel.udid] = []
362
- tunnels[active_tunnel.udid].append({
363
- 'tunnel-address': active_tunnel.tunnel.address,
364
- 'tunnel-port': active_tunnel.tunnel.port,
365
- 'interface': ip})
366
- return tunnels
367
-
368
- @self._app.get('/shutdown')
369
- async def shutdown() -> fastapi.Response:
370
- """ Shutdown Tunneld """
371
- os.kill(os.getpid(), signal.SIGINT)
372
- data = {'operation': 'shutdown', 'data': True, 'message': 'Server shutting down...'}
373
- return generate_http_response(data)
374
-
375
- @self._app.get('/clear_tunnels')
376
- async def clear_tunnels() -> fastapi.Response:
377
- self._tunneld_core.clear()
378
- data = {'operation': 'clear_tunnels', 'data': True, 'message': 'Cleared tunnels...'}
379
- return generate_http_response(data)
380
-
381
- @self._app.get('/cancel')
382
- async def cancel_tunnel(udid: str) -> fastapi.Response:
383
- self._tunneld_core.cancel(udid=udid)
384
- data = {'operation': 'cancel', 'udid': udid, 'data': True, 'message': f'tunnel {udid} Canceled ...'}
385
- return generate_http_response(data)
386
-
387
- @self._app.get('/hello')
388
- async def hello() -> fastapi.Response:
389
- data = {'message': 'Hello, I\'m alive'}
390
- return generate_http_response(data)
391
-
392
- def generate_http_response(
393
- data: dict, status_code: int = 200, media_type: str = "application/json") -> fastapi.Response:
394
- return fastapi.Response(
395
- status_code=status_code,
396
- media_type=media_type,
397
- content=json.dumps(data))
398
-
399
- @self._app.get('/start-tunnel')
400
- async def start_tunnel(
401
- udid: str, ip: Optional[str] = None, connection_type: Optional[str] = None) -> fastapi.Response:
402
- udid_tunnels = [t.tunnel for t in self._tunneld_core.tunnel_tasks.values() if t.udid == udid]
403
- if len(udid_tunnels) > 0:
404
- data = {
405
- 'interface': udid_tunnels[0].interface,
406
- 'port': udid_tunnels[0].port,
407
- 'address': udid_tunnels[0].address
408
- }
409
- return generate_http_response(data)
410
-
411
- queue = asyncio.Queue()
412
- created_task = False
413
-
414
- try:
415
- if not created_task and connection_type in ('usbmux', None):
416
- task_identifier = f'usbmux-{udid}'
417
- try:
418
- service = CoreDeviceTunnelProxy(create_using_usbmux(udid))
419
- task = asyncio.create_task(
420
- self._tunneld_core.start_tunnel_task(task_identifier, service, protocol=TunnelProtocol.TCP,
421
- queue=queue),
422
- name=f'start-tunnel-task-{task_identifier}')
423
- self._tunneld_core.tunnel_tasks[task_identifier] = TunnelTask(task=task, udid=udid)
424
- created_task = True
425
- except (ConnectionFailedError, InvalidServiceError, MuxException):
426
- pass
427
- if connection_type in ('usb', None):
428
- for rsd in await get_rsds(udid=udid):
429
- rsd_ip = rsd.service.address[0]
430
- if ip is not None and rsd_ip != ip:
431
- await rsd.close()
432
- continue
433
- task = asyncio.create_task(
434
- self._tunneld_core.start_tunnel_task(rsd_ip,
435
- await create_core_device_tunnel_service_using_rsd(rsd),
436
- queue=queue),
437
- name=f'start-tunnel-usb-{rsd_ip}')
438
- self._tunneld_core.tunnel_tasks[rsd_ip] = TunnelTask(task=task, udid=rsd.udid)
439
- created_task = True
440
- if not created_task and connection_type in ('wifi', None):
441
- for remotepairing in await get_remote_pairing_tunnel_services(udid=udid):
442
- remotepairing_ip = remotepairing.hostname
443
- if ip is not None and remotepairing_ip != ip:
444
- await remotepairing.close()
445
- continue
446
- task = asyncio.create_task(
447
- self._tunneld_core.start_tunnel_task(remotepairing_ip, remotepairing, queue=queue),
448
- name=f'start-tunnel-wifi-{remotepairing_ip}')
449
- self._tunneld_core.tunnel_tasks[remotepairing_ip] = TunnelTask(
450
- task=task, udid=remotepairing.remote_identifier)
451
- created_task = True
452
- except Exception as e:
453
- return fastapi.Response(status_code=501,
454
- content=json.dumps({'error': {
455
- 'exception': e.__class__.__name__,
456
- 'traceback': traceback.format_exc(),
457
- }}))
458
-
459
- if not created_task:
460
- return fastapi.Response(status_code=501, content=json.dumps({'error': 'task not not created'}))
461
-
462
- tunnel: Optional[TunnelResult] = await queue.get()
463
- if tunnel is not None:
464
- data = {
465
- 'interface': tunnel.interface,
466
- 'port': tunnel.port,
467
- 'address': tunnel.address
468
- }
469
- return generate_http_response(data)
470
- else:
471
- return fastapi.Response(status_code=404,
472
- content=json.dumps({'error': 'something went wrong during tunnel creation'}))
473
-
474
- def _run_app(self) -> None:
475
- uvicorn.run(self._app, host=self.host, port=self.port, loop='asyncio')
476
-
477
-
478
- async def async_get_tunneld_devices(tunneld_address: tuple[str, int] = TUNNELD_DEFAULT_ADDRESS) \
479
- -> list[RemoteServiceDiscoveryService]:
480
- tunnels = _list_tunnels(tunneld_address)
481
- return await _create_rsds_from_tunnels(tunnels)
482
-
483
-
484
- def get_tunneld_devices(tunneld_address: tuple[str, int] = TUNNELD_DEFAULT_ADDRESS) \
485
- -> list[RemoteServiceDiscoveryService]:
486
- return get_asyncio_loop().run_until_complete(async_get_tunneld_devices(tunneld_address))
487
-
488
-
489
- async def async_get_tunneld_device_by_udid(udid: str, tunneld_address: tuple[str, int] = TUNNELD_DEFAULT_ADDRESS) \
490
- -> Optional[RemoteServiceDiscoveryService]:
491
- tunnels = _list_tunnels(tunneld_address)
492
- if udid not in tunnels:
493
- return None
494
- rsds = await _create_rsds_from_tunnels({udid: tunnels[udid]})
495
- return rsds[0]
496
-
497
-
498
- def get_tunneld_device_by_udid(udid: str, tunneld_address: tuple[str, int] = TUNNELD_DEFAULT_ADDRESS) \
499
- -> Optional[RemoteServiceDiscoveryService]:
500
- return get_asyncio_loop().run_until_complete(async_get_tunneld_device_by_udid(udid, tunneld_address))
501
-
502
-
503
- def _list_tunnels(tunneld_address: tuple[str, int] = TUNNELD_DEFAULT_ADDRESS) -> dict[str, list[dict]]:
504
- try:
505
- # Get the list of tunnels from the specified address
506
- resp = requests.get(f'http://{tunneld_address[0]}:{tunneld_address[1]}')
507
- tunnels = resp.json()
508
- except requests.exceptions.ConnectionError:
509
- raise TunneldConnectionError()
510
- return tunnels
511
-
512
-
513
- async def _create_rsds_from_tunnels(tunnels: dict[str, list[dict]]) -> list[RemoteServiceDiscoveryService]:
514
- rsds = []
515
- for udid, details in tunnels.items():
516
- for tunnel_details in details:
517
- rsd = RemoteServiceDiscoveryService((tunnel_details['tunnel-address'], tunnel_details['tunnel-port']),
518
- name=tunnel_details['interface'])
519
- try:
520
- await rsd.connect()
521
- rsds.append(rsd)
522
- except (TimeoutError, ConnectionError):
523
- continue
524
- return rsds