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
@@ -2,6 +2,7 @@ import logging
2
2
  import select
3
3
  import socket
4
4
  import threading
5
+ import time
5
6
  from abc import abstractmethod
6
7
  from typing import Optional
7
8
 
@@ -18,7 +19,7 @@ class TcpForwarderBase:
18
19
  MAX_FORWARDED_CONNECTIONS = 200
19
20
  TIMEOUT = 1
20
21
 
21
- def __init__(self, src_port: int, listening_event: threading.Event = None):
22
+ def __init__(self, src_port: int, listening_event: Optional[threading.Event] = None):
22
23
  """
23
24
  Initialize a new tcp forwarder
24
25
 
@@ -37,8 +38,8 @@ class TcpForwarderBase:
37
38
  # socket to its remote socket and vice versa
38
39
  self.connections = {}
39
40
 
40
- def start(self, address='0.0.0.0'):
41
- """ forward each connection from given local machine port to remote device port """
41
+ def start(self, address="0.0.0.0"):
42
+ """forward each connection from given local machine port to remote device port"""
42
43
  # create local tcp server socket
43
44
  self.server_socket = socket.socket()
44
45
  self.server_socket.setsockopt(socket.SOL_SOCKET, socket.SO_REUSEADDR, 1)
@@ -50,40 +51,41 @@ class TcpForwarderBase:
50
51
  if self.listening_event:
51
52
  self.listening_event.set()
52
53
 
53
- while self.inputs:
54
- # will only perform the socket select on the inputs. the outputs will handled
55
- # as synchronous blocking
56
- readable, writable, exceptional = select.select(self.inputs, [], self.inputs, self.TIMEOUT)
57
- if self.stopped.is_set():
58
- self.logger.debug("Closing since stopped is set")
59
- break
60
-
61
- closed_sockets = set()
62
- for current_sock in readable:
63
- self.logger.debug("Processing %r", current_sock)
64
- if current_sock is self.server_socket:
65
- self._handle_server_connection()
66
- else:
67
- if current_sock not in closed_sockets:
68
- try:
69
- self._handle_data(current_sock, closed_sockets)
70
- except ConnectionResetError:
71
- self.logger.exception("Error when handling data")
72
- self._handle_close_or_error(current_sock)
54
+ try:
55
+ while self.inputs:
56
+ # will only perform the socket select on the inputs. the outputs will handled
57
+ # as synchronous blocking
58
+ readable, _writable, exceptional = select.select(self.inputs, [], self.inputs, self.TIMEOUT)
59
+ if self.stopped.is_set():
60
+ self.logger.debug("Closing since stopped is set")
61
+ break
62
+
63
+ closed_sockets = set()
64
+ for current_sock in readable:
65
+ self.logger.debug("Processing %r", current_sock)
66
+ if current_sock is self.server_socket:
67
+ self._handle_server_connection()
73
68
  else:
74
- self.logger.debug("Is closed")
75
-
76
- for current_sock in exceptional:
77
- self.logger.error("Sock failed: %r", current_sock)
78
- self._handle_close_or_error(current_sock)
79
-
80
- self.logger.info("Closing everything")
81
- # on stop, close all currently opened sockets
82
- for current_sock in self.inputs:
83
- current_sock.close()
69
+ if current_sock not in closed_sockets:
70
+ try:
71
+ self._handle_data(current_sock, closed_sockets)
72
+ except ConnectionResetError:
73
+ self.logger.error("Error when handling data")
74
+ self._handle_close_or_error(current_sock)
75
+ else:
76
+ self.logger.debug("Is closed")
77
+
78
+ for current_sock in exceptional:
79
+ self.logger.error("Sock failed: %r", current_sock)
80
+ self._handle_close_or_error(current_sock)
81
+ finally:
82
+ self.logger.info("Closing everything")
83
+ # on stop, close all currently opened sockets
84
+ for current_sock in self.inputs:
85
+ current_sock.close()
84
86
 
85
87
  def _handle_close_or_error(self, from_sock):
86
- """ if an error occurred its time to close the two sockets """
88
+ """if an error occurred its time to close the two sockets"""
87
89
  other_sock = self.connections[from_sock]
88
90
 
89
91
  other_sock.close()
@@ -91,56 +93,59 @@ class TcpForwarderBase:
91
93
  self.inputs.remove(other_sock)
92
94
  self.inputs.remove(from_sock)
93
95
 
94
- self.logger.info(f'connection {other_sock} was closed')
96
+ self.logger.info(f"connection {other_sock} was closed")
95
97
 
96
98
  def _handle_data(self, from_sock, closed_sockets):
97
- self.logger.debug("Handling data from %s", from_sock)
98
- data = None
99
+ self.logger.debug(f"Handling data from {from_sock}")
99
100
  try:
100
101
  data = from_sock.recv(1024)
102
+ if not data:
103
+ raise ConnectionResetError("Connection closed by the peer.")
104
+ except BlockingIOError:
105
+ self.logger.warning(f"Non-blocking read failed on {from_sock}, retrying later.")
106
+ return
101
107
  except OSError:
102
- # Socket closing is handled in another if block
103
- pass
104
-
105
- if data is None or len(data) == 0:
106
- if data is None:
107
- # data is none means we had an error reading from socket
108
- self.logger.debug("oserror when reading from_sock")
109
- else:
110
- # Empty data means socket was closed
111
- self.logger.info("No data was read from the socket")
108
+ self.logger.error(f"Error reading from socket {from_sock}")
112
109
  self._handle_close_or_error(from_sock)
113
110
  closed_sockets.add(from_sock)
114
- closed_sockets.add(self.connections[from_sock])
115
111
  return
116
112
 
117
- # when data is received from one end, just forward it to the other
118
- other_sock = self.connections[from_sock]
113
+ other_sock = self.connections.get(from_sock)
114
+ if not other_sock:
115
+ self.logger.error(f"No connection mapping found for {from_sock}.")
116
+ return
117
+
119
118
  try:
120
- # send the data in blocking manner
121
- other_sock.setblocking(True)
122
- other_sock.sendall(data)
123
- other_sock.setblocking(False)
119
+ total_sent = 0
120
+ while total_sent < len(data):
121
+ try:
122
+ sent = other_sock.send(data[total_sent:])
123
+ total_sent += sent
124
+ except BlockingIOError:
125
+ self.logger.warning(f"Socket buffer full for {other_sock}, retrying in 100ms.")
126
+ time.sleep(0.1) # Introduce a small delay
127
+ except BrokenPipeError:
128
+ self.logger.error(f"Broken pipe error on {other_sock}.")
129
+ raise
124
130
  except OSError:
125
- # Tried writing to closed socket
126
- self.logger.exception("Exception when sending data to socket")
127
- self._handle_close_or_error(other_sock)
131
+ self.logger.error("Unhandled error while forwarding data")
132
+ self._handle_close_or_error(from_sock)
128
133
  closed_sockets.add(from_sock)
129
- closed_sockets.add(self.connections[from_sock])
134
+ closed_sockets.add(other_sock)
130
135
 
131
136
  @abstractmethod
132
137
  def _establish_remote_connection(self) -> socket.socket:
133
138
  pass
134
139
 
135
140
  def _handle_server_connection(self):
136
- """ accept the connection from local machine and attempt to connect at remote """
137
- local_connection, client_address = self.server_socket.accept()
141
+ """accept the connection from local machine and attempt to connect at remote"""
142
+ local_connection, _client_address = self.server_socket.accept()
138
143
  local_connection.setblocking(False)
139
144
 
140
145
  try:
141
146
  remote_connection = self._establish_remote_connection()
142
147
  except ConnectionFailedError:
143
- self.logger.error(f'failed to connect to port: {self.dst_port}')
148
+ self.logger.error(f"failed to connect to port: {self.dst_port}")
144
149
  local_connection.close()
145
150
  return
146
151
 
@@ -154,10 +159,10 @@ class TcpForwarderBase:
154
159
  self.connections[remote_connection] = local_connection
155
160
  self.connections[local_connection] = remote_connection
156
161
 
157
- self.logger.info('connection established from local to remote')
162
+ self.logger.info("connection established from local to remote")
158
163
 
159
164
  def stop(self):
160
- """ stop forwarding """
165
+ """stop forwarding"""
161
166
  self.stopped.set()
162
167
 
163
168
 
@@ -166,8 +171,15 @@ class UsbmuxTcpForwarder(TcpForwarderBase):
166
171
  Allows forwarding local tcp connection into the device via a given lockdown connection
167
172
  """
168
173
 
169
- def __init__(self, serial: str, dst_port: int, src_port: int, listening_event: threading.Event = None,
170
- usbmux_connection_type: str = None, usbmux_address: Optional[str] = None):
174
+ def __init__(
175
+ self,
176
+ serial: str,
177
+ dst_port: int,
178
+ src_port: int,
179
+ listening_event: Optional[threading.Event] = None,
180
+ usbmux_connection_type: Optional[str] = None,
181
+ usbmux_address: Optional[str] = None,
182
+ ):
171
183
  """
172
184
  Initialize a new tcp forwarder
173
185
 
@@ -186,8 +198,9 @@ class UsbmuxTcpForwarder(TcpForwarderBase):
186
198
 
187
199
  def _establish_remote_connection(self) -> socket.socket:
188
200
  # connect directly using usbmuxd
189
- mux_device = usbmux.select_device(self.serial, connection_type=self.usbmux_connection_type,
190
- usbmux_address=self.usbmux_address)
201
+ mux_device = usbmux.select_device(
202
+ self.serial, connection_type=self.usbmux_connection_type, usbmux_address=self.usbmux_address
203
+ )
191
204
  self.logger.debug("Selected device: %r", mux_device)
192
205
  if mux_device is None:
193
206
  raise ConnectionFailedError()
@@ -199,8 +212,13 @@ class LockdownTcpForwarder(TcpForwarderBase):
199
212
  Allows forwarding local tcp connection into the device via a given lockdown connection
200
213
  """
201
214
 
202
- def __init__(self, service_provider: LockdownServiceProvider, src_port: int, service_name: str,
203
- listening_event: threading.Event = None):
215
+ def __init__(
216
+ self,
217
+ service_provider: LockdownServiceProvider,
218
+ src_port: int,
219
+ service_name: str,
220
+ listening_event: Optional[threading.Event] = None,
221
+ ):
204
222
  """
205
223
  Initialize a new tcp forwarder
206
224
 
File without changes
@@ -0,0 +1,63 @@
1
+ from typing import Optional
2
+
3
+ import requests
4
+
5
+ from pymobiledevice3.exceptions import TunneldConnectionError
6
+ from pymobiledevice3.remote.remote_service_discovery import RemoteServiceDiscoveryService
7
+ from pymobiledevice3.utils import get_asyncio_loop
8
+
9
+ TUNNELD_DEFAULT_ADDRESS = ("127.0.0.1", 49151)
10
+
11
+
12
+ async def async_get_tunneld_devices(
13
+ tunneld_address: tuple[str, int] = TUNNELD_DEFAULT_ADDRESS,
14
+ ) -> list[RemoteServiceDiscoveryService]:
15
+ tunnels = _list_tunnels(tunneld_address)
16
+ return await _create_rsds_from_tunnels(tunnels)
17
+
18
+
19
+ def get_tunneld_devices(
20
+ tunneld_address: tuple[str, int] = TUNNELD_DEFAULT_ADDRESS,
21
+ ) -> list[RemoteServiceDiscoveryService]:
22
+ return get_asyncio_loop().run_until_complete(async_get_tunneld_devices(tunneld_address))
23
+
24
+
25
+ async def async_get_tunneld_device_by_udid(
26
+ udid: str, tunneld_address: tuple[str, int] = TUNNELD_DEFAULT_ADDRESS
27
+ ) -> Optional[RemoteServiceDiscoveryService]:
28
+ tunnels = _list_tunnels(tunneld_address)
29
+ if udid not in tunnels:
30
+ return None
31
+ rsds = await _create_rsds_from_tunnels({udid: tunnels[udid]})
32
+ return rsds[0]
33
+
34
+
35
+ def get_tunneld_device_by_udid(
36
+ udid: str, tunneld_address: tuple[str, int] = TUNNELD_DEFAULT_ADDRESS
37
+ ) -> Optional[RemoteServiceDiscoveryService]:
38
+ return get_asyncio_loop().run_until_complete(async_get_tunneld_device_by_udid(udid, tunneld_address))
39
+
40
+
41
+ def _list_tunnels(tunneld_address: tuple[str, int] = TUNNELD_DEFAULT_ADDRESS) -> dict[str, list[dict]]:
42
+ try:
43
+ # Get the list of tunnels from the specified address
44
+ resp = requests.get(f"http://{tunneld_address[0]}:{tunneld_address[1]}")
45
+ tunnels = resp.json()
46
+ except requests.exceptions.ConnectionError as e:
47
+ raise TunneldConnectionError() from e
48
+ return tunnels
49
+
50
+
51
+ async def _create_rsds_from_tunnels(tunnels: dict[str, list[dict]]) -> list[RemoteServiceDiscoveryService]:
52
+ rsds = []
53
+ for _udid, details in tunnels.items():
54
+ for tunnel_details in details:
55
+ rsd = RemoteServiceDiscoveryService(
56
+ (tunnel_details["tunnel-address"], tunnel_details["tunnel-port"]), name=tunnel_details["interface"]
57
+ )
58
+ try:
59
+ await rsd.connect()
60
+ rsds.append(rsd)
61
+ except (TimeoutError, ConnectionError):
62
+ continue
63
+ return rsds