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
@@ -9,16 +9,25 @@ from typing import Optional, TextIO
9
9
  import click
10
10
 
11
11
  from pymobiledevice3.bonjour import DEFAULT_BONJOUR_TIMEOUT, browse_remotepairing_manual_pairing
12
- from pymobiledevice3.cli.cli_common import BaseCommand, RSDCommand, print_json, prompt_device_list, sudo_required, \
13
- user_requested_colored_output
12
+ from pymobiledevice3.cli.cli_common import (
13
+ BaseCommand,
14
+ RSDCommand,
15
+ print_json,
16
+ prompt_device_list,
17
+ sudo_required,
18
+ user_requested_colored_output,
19
+ )
14
20
  from pymobiledevice3.common import get_home_folder
15
21
  from pymobiledevice3.exceptions import NoDeviceConnectedError
16
22
  from pymobiledevice3.pair_records import PAIRING_RECORD_EXT, get_remote_pairing_record_filename
17
23
  from pymobiledevice3.remote.common import ConnectionType, TunnelProtocol
18
24
  from pymobiledevice3.remote.module_imports import MAX_IDLE_TIMEOUT, start_tunnel, verify_tunnel_imports
19
25
  from pymobiledevice3.remote.remote_service_discovery import RSD_PORT, RemoteServiceDiscoveryService
20
- from pymobiledevice3.remote.tunnel_service import RemotePairingManualPairingService, get_core_device_tunnel_services, \
21
- get_remote_pairing_tunnel_services
26
+ from pymobiledevice3.remote.tunnel_service import (
27
+ RemotePairingManualPairingService,
28
+ get_core_device_tunnel_services,
29
+ get_remote_pairing_tunnel_services,
30
+ )
22
31
  from pymobiledevice3.remote.utils import get_rsds
23
32
  from pymobiledevice3.tunneld.api import TUNNELD_DEFAULT_ADDRESS
24
33
  from pymobiledevice3.tunneld.server import TunneldRunner
@@ -29,27 +38,31 @@ logger = logging.getLogger(__name__)
29
38
  async def browse_rsd(timeout: float = DEFAULT_BONJOUR_TIMEOUT) -> list[dict]:
30
39
  devices = []
31
40
  for rsd in await get_rsds(timeout):
32
- devices.append({'address': rsd.service.address[0],
33
- 'port': RSD_PORT,
34
- 'UniqueDeviceID': rsd.peer_info['Properties']['UniqueDeviceID'],
35
- 'ProductType': rsd.peer_info['Properties']['ProductType'],
36
- 'OSVersion': rsd.peer_info['Properties']['OSVersion']})
41
+ devices.append({
42
+ "address": rsd.service.address[0],
43
+ "port": RSD_PORT,
44
+ "UniqueDeviceID": rsd.peer_info["Properties"]["UniqueDeviceID"],
45
+ "ProductType": rsd.peer_info["Properties"]["ProductType"],
46
+ "OSVersion": rsd.peer_info["Properties"]["OSVersion"],
47
+ })
37
48
  return devices
38
49
 
39
50
 
40
51
  async def browse_remotepairing(timeout: float = DEFAULT_BONJOUR_TIMEOUT) -> list[dict]:
41
52
  devices = []
42
53
  for remotepairing in await get_remote_pairing_tunnel_services(timeout):
43
- devices.append({'address': remotepairing.hostname,
44
- 'port': remotepairing.port,
45
- 'identifier': remotepairing.remote_identifier})
54
+ devices.append({
55
+ "address": remotepairing.hostname,
56
+ "port": remotepairing.port,
57
+ "identifier": remotepairing.remote_identifier,
58
+ })
46
59
  return devices
47
60
 
48
61
 
49
62
  async def cli_browse(timeout: float = DEFAULT_BONJOUR_TIMEOUT) -> None:
50
63
  print_json({
51
- 'usb': await browse_rsd(timeout),
52
- 'wifi': await browse_remotepairing(timeout),
64
+ "usb": await browse_rsd(timeout),
65
+ "wifi": await browse_remotepairing(timeout),
53
66
  })
54
67
 
55
68
 
@@ -58,105 +71,139 @@ def cli() -> None:
58
71
  pass
59
72
 
60
73
 
61
- @cli.group('remote')
74
+ @cli.group("remote")
62
75
  def remote_cli() -> None:
63
- """ Create RemoteXPC tunnels """
76
+ """Create RemoteXPC tunnels"""
64
77
  pass
65
78
 
66
79
 
67
- @remote_cli.command('tunneld', cls=BaseCommand)
68
- @click.option('--host', default=TUNNELD_DEFAULT_ADDRESS[0])
69
- @click.option('--port', type=click.INT, default=TUNNELD_DEFAULT_ADDRESS[1])
70
- @click.option('-d', '--daemonize', is_flag=True)
71
- @click.option('-p', '--protocol', type=click.Choice([e.value for e in TunnelProtocol]),
72
- help='Transport protocol. If python version >= 3.13 will default to TCP. Otherwise will default to QUIC',
73
- default=TunnelProtocol.DEFAULT.value)
74
- @click.option('--usb/--no-usb', default=True, help='Enable usb monitoring')
75
- @click.option('--wifi/--no-wifi', default=True, help='Enable wifi monitoring')
76
- @click.option('--usbmux/--no-usbmux', default=True, help='Enable usbmux monitoring')
77
- @click.option('--mobdev2/--no-mobdev2', default=True, help='Enable mobdev2 monitoring')
80
+ @remote_cli.command("tunneld", cls=BaseCommand)
81
+ @click.option("--host", default=TUNNELD_DEFAULT_ADDRESS[0])
82
+ @click.option("--port", type=click.INT, default=TUNNELD_DEFAULT_ADDRESS[1])
83
+ @click.option("-d", "--daemonize", is_flag=True)
84
+ @click.option(
85
+ "-p",
86
+ "--protocol",
87
+ type=click.Choice([e.value for e in TunnelProtocol]),
88
+ help="Transport protocol. If python version >= 3.13 will default to TCP. Otherwise will default to QUIC",
89
+ default=TunnelProtocol.DEFAULT.value,
90
+ )
91
+ @click.option("--usb/--no-usb", default=True, help="Enable usb monitoring")
92
+ @click.option("--wifi/--no-wifi", default=True, help="Enable wifi monitoring")
93
+ @click.option("--usbmux/--no-usbmux", default=True, help="Enable usbmux monitoring")
94
+ @click.option("--mobdev2/--no-mobdev2", default=True, help="Enable mobdev2 monitoring")
78
95
  @sudo_required
79
96
  def cli_tunneld(
80
- host: str, port: int, daemonize: bool, protocol: str, usb: bool, wifi: bool, usbmux: bool,
81
- mobdev2: bool) -> None:
82
- """ Start Tunneld service for remote tunneling """
97
+ host: str, port: int, daemonize: bool, protocol: str, usb: bool, wifi: bool, usbmux: bool, mobdev2: bool
98
+ ) -> None:
99
+ """Start Tunneld service for remote tunneling"""
83
100
  if not verify_tunnel_imports():
84
101
  return
85
102
  protocol = TunnelProtocol(protocol)
86
- tunneld_runner = partial(TunneldRunner.create, host, port, protocol=protocol, usb_monitor=usb, wifi_monitor=wifi,
87
- usbmux_monitor=usbmux, mobdev2_monitor=mobdev2)
103
+ tunneld_runner = partial(
104
+ TunneldRunner.create,
105
+ host,
106
+ port,
107
+ protocol=protocol,
108
+ usb_monitor=usb,
109
+ wifi_monitor=wifi,
110
+ usbmux_monitor=usbmux,
111
+ mobdev2_monitor=mobdev2,
112
+ )
88
113
  if daemonize:
89
114
  try:
90
115
  from daemonize import Daemonize
91
- except ImportError:
92
- raise NotImplementedError('daemonizing is only supported on unix platforms')
93
- with tempfile.NamedTemporaryFile('wt') as pid_file:
94
- daemon = Daemonize(app=f'Tunneld {host}:{port}', pid=pid_file.name,
95
- action=tunneld_runner)
96
- logger.info(f'starting Tunneld {host}:{port}')
116
+ except ImportError as e:
117
+ raise NotImplementedError("daemonizing is only supported on unix platforms") from e
118
+ with tempfile.NamedTemporaryFile("wt") as pid_file:
119
+ daemon = Daemonize(app=f"Tunneld {host}:{port}", pid=pid_file.name, action=tunneld_runner)
120
+ logger.info(f"starting Tunneld {host}:{port}")
97
121
  daemon.start()
98
122
  else:
99
123
  tunneld_runner()
100
124
 
101
125
 
102
- @remote_cli.command('browse', cls=BaseCommand)
103
- @click.option('--timeout', type=click.FLOAT, default=DEFAULT_BONJOUR_TIMEOUT, help='Bonjour timeout (in seconds)')
126
+ @remote_cli.command("browse", cls=BaseCommand)
127
+ @click.option("--timeout", type=click.FLOAT, default=DEFAULT_BONJOUR_TIMEOUT, help="Bonjour timeout (in seconds)")
104
128
  def browse(timeout: float) -> None:
105
- """ browse RemoteXPC devices using bonjour """
129
+ """browse RemoteXPC devices using bonjour"""
106
130
  asyncio.run(cli_browse(timeout), debug=True)
107
131
 
108
132
 
109
- @remote_cli.command('rsd-info', cls=RSDCommand)
133
+ @remote_cli.command("rsd-info", cls=RSDCommand)
110
134
  def rsd_info(service_provider: RemoteServiceDiscoveryService):
111
- """ show info extracted from RSD peer """
135
+ """show info extracted from RSD peer"""
112
136
  print_json(service_provider.peer_info)
113
137
 
114
138
 
115
139
  async def tunnel_task(
116
- service, secrets: Optional[TextIO] = None, script_mode: bool = False,
117
- max_idle_timeout: float = MAX_IDLE_TIMEOUT, protocol: TunnelProtocol = TunnelProtocol.DEFAULT) -> None:
140
+ service,
141
+ secrets: Optional[TextIO] = None,
142
+ script_mode: bool = False,
143
+ max_idle_timeout: float = MAX_IDLE_TIMEOUT,
144
+ protocol: TunnelProtocol = TunnelProtocol.DEFAULT,
145
+ ) -> None:
118
146
  async with start_tunnel(
119
- service, secrets=secrets, max_idle_timeout=max_idle_timeout, protocol=protocol) as tunnel_result:
120
- logger.info('tunnel created')
147
+ service, secrets=secrets, max_idle_timeout=max_idle_timeout, protocol=protocol
148
+ ) as tunnel_result:
149
+ logger.info("tunnel created")
121
150
  if script_mode:
122
- print(f'{tunnel_result.address} {tunnel_result.port}')
151
+ print(f"{tunnel_result.address} {tunnel_result.port}")
123
152
  else:
124
153
  if user_requested_colored_output():
125
154
  if secrets is not None:
126
- print(click.style('Secrets: ', bold=True, fg='magenta') +
127
- click.style(secrets.name, bold=True, fg='white'))
128
- print(click.style('Identifier: ', bold=True, fg='yellow') +
129
- click.style(service.remote_identifier, bold=True, fg='white'))
130
- print(click.style('Interface: ', bold=True, fg='yellow') +
131
- click.style(tunnel_result.interface, bold=True, fg='white'))
132
- print(click.style('Protocol: ', bold=True, fg='yellow') +
133
- click.style(tunnel_result.protocol, bold=True, fg='white'))
134
- print(click.style('RSD Address: ', bold=True, fg='yellow') +
135
- click.style(tunnel_result.address, bold=True, fg='white'))
136
- print(click.style('RSD Port: ', bold=True, fg='yellow') +
137
- click.style(tunnel_result.port, bold=True, fg='white'))
138
- print(click.style('Use the follow connection option:\n', bold=True, fg='yellow') +
139
- click.style(f'--rsd {tunnel_result.address} {tunnel_result.port}', bold=True, fg='cyan'))
155
+ print(
156
+ click.style("Secrets: ", bold=True, fg="magenta")
157
+ + click.style(secrets.name, bold=True, fg="white")
158
+ )
159
+ print(
160
+ click.style("Identifier: ", bold=True, fg="yellow")
161
+ + click.style(service.remote_identifier, bold=True, fg="white")
162
+ )
163
+ print(
164
+ click.style("Interface: ", bold=True, fg="yellow")
165
+ + click.style(tunnel_result.interface, bold=True, fg="white")
166
+ )
167
+ print(
168
+ click.style("Protocol: ", bold=True, fg="yellow")
169
+ + click.style(tunnel_result.protocol, bold=True, fg="white")
170
+ )
171
+ print(
172
+ click.style("RSD Address: ", bold=True, fg="yellow")
173
+ + click.style(tunnel_result.address, bold=True, fg="white")
174
+ )
175
+ print(
176
+ click.style("RSD Port: ", bold=True, fg="yellow")
177
+ + click.style(tunnel_result.port, bold=True, fg="white")
178
+ )
179
+ print(
180
+ click.style("Use the follow connection option:\n", bold=True, fg="yellow")
181
+ + click.style(f"--rsd {tunnel_result.address} {tunnel_result.port}", bold=True, fg="cyan")
182
+ )
140
183
  else:
141
184
  if secrets is not None:
142
- print(f'Secrets: {secrets.name}')
143
- print(f'Identifier: {service.remote_identifier}')
144
- print(f'Interface: {tunnel_result.interface}')
145
- print(f'Protocol: {tunnel_result.protocol}')
146
- print(f'RSD Address: {tunnel_result.address}')
147
- print(f'RSD Port: {tunnel_result.port}')
148
- print(f'Use the follow connection option:\n'
149
- f'--rsd {tunnel_result.address} {tunnel_result.port}')
185
+ print(f"Secrets: {secrets.name}")
186
+ print(f"Identifier: {service.remote_identifier}")
187
+ print(f"Interface: {tunnel_result.interface}")
188
+ print(f"Protocol: {tunnel_result.protocol}")
189
+ print(f"RSD Address: {tunnel_result.address}")
190
+ print(f"RSD Port: {tunnel_result.port}")
191
+ print(f"Use the follow connection option:\n--rsd {tunnel_result.address} {tunnel_result.port}")
150
192
  sys.stdout.flush()
151
193
  await tunnel_result.client.wait_closed()
152
- logger.info('tunnel was closed')
194
+ logger.info("tunnel was closed")
153
195
 
154
196
 
155
197
  async def start_tunnel_task(
156
- connection_type: ConnectionType, secrets: TextIO, udid: Optional[str] = None, script_mode: bool = False,
157
- max_idle_timeout: float = MAX_IDLE_TIMEOUT, protocol: TunnelProtocol = TunnelProtocol.DEFAULT) -> None:
198
+ connection_type: ConnectionType,
199
+ secrets: TextIO,
200
+ udid: Optional[str] = None,
201
+ script_mode: bool = False,
202
+ max_idle_timeout: float = MAX_IDLE_TIMEOUT,
203
+ protocol: TunnelProtocol = TunnelProtocol.DEFAULT,
204
+ ) -> None:
158
205
  if start_tunnel is None:
159
- raise NotImplementedError('failed to start the tunnel on your platform')
206
+ raise NotImplementedError("failed to start the tunnel on your platform")
160
207
  get_tunnel_services = {
161
208
  connection_type.USB: get_core_device_tunnel_services,
162
209
  connection_type.WIFI: get_remote_pairing_tunnel_services,
@@ -172,33 +219,57 @@ async def start_tunnel_task(
172
219
  # several devices were found, show prompt if none explicitly selected
173
220
  service = prompt_device_list(tunnel_services)
174
221
 
175
- await tunnel_task(service, secrets=secrets, script_mode=script_mode, max_idle_timeout=max_idle_timeout,
176
- protocol=protocol)
177
-
178
-
179
- @remote_cli.command('start-tunnel', cls=BaseCommand)
180
- @click.option('-t', '--connection-type', type=click.Choice([e.value for e in ConnectionType], case_sensitive=False),
181
- default=ConnectionType.USB.value)
182
- @click.option('--udid', help='UDID for a specific device to look for')
183
- @click.option('--secrets', type=click.File('wt'), help='TLS keyfile for decrypting with Wireshark')
184
- @click.option('--script-mode', is_flag=True,
185
- help='Show only HOST and port number to allow easy parsing from external shell scripts')
186
- @click.option('--max-idle-timeout', type=click.FLOAT, default=MAX_IDLE_TIMEOUT,
187
- help='Maximum QUIC idle time (ping interval)')
188
- @click.option('-p', '--protocol',
189
- type=click.Choice([e.value for e in TunnelProtocol], case_sensitive=False),
190
- default=TunnelProtocol.DEFAULT.value)
222
+ await tunnel_task(
223
+ service, secrets=secrets, script_mode=script_mode, max_idle_timeout=max_idle_timeout, protocol=protocol
224
+ )
225
+
226
+
227
+ @remote_cli.command("start-tunnel", cls=BaseCommand)
228
+ @click.option(
229
+ "-t",
230
+ "--connection-type",
231
+ type=click.Choice([e.value for e in ConnectionType], case_sensitive=False),
232
+ default=ConnectionType.USB.value,
233
+ )
234
+ @click.option("--udid", help="UDID for a specific device to look for")
235
+ @click.option("--secrets", type=click.File("wt"), help="TLS keyfile for decrypting with Wireshark")
236
+ @click.option(
237
+ "--script-mode",
238
+ is_flag=True,
239
+ help="Show only HOST and port number to allow easy parsing from external shell scripts",
240
+ )
241
+ @click.option(
242
+ "--max-idle-timeout", type=click.FLOAT, default=MAX_IDLE_TIMEOUT, help="Maximum QUIC idle time (ping interval)"
243
+ )
244
+ @click.option(
245
+ "-p",
246
+ "--protocol",
247
+ type=click.Choice([e.value for e in TunnelProtocol], case_sensitive=False),
248
+ default=TunnelProtocol.DEFAULT.value,
249
+ )
191
250
  @sudo_required
192
251
  def cli_start_tunnel(
193
- connection_type: ConnectionType, udid: Optional[str], secrets: TextIO, script_mode: bool,
194
- max_idle_timeout: float, protocol: str) -> None:
195
- """ start tunnel """
252
+ connection_type: ConnectionType,
253
+ udid: Optional[str],
254
+ secrets: TextIO,
255
+ script_mode: bool,
256
+ max_idle_timeout: float,
257
+ protocol: str,
258
+ ) -> None:
259
+ """start tunnel"""
196
260
  if not verify_tunnel_imports():
197
261
  return
198
262
  asyncio.run(
199
263
  start_tunnel_task(
200
- ConnectionType(connection_type), secrets, udid, script_mode, max_idle_timeout=max_idle_timeout,
201
- protocol=TunnelProtocol(protocol)), debug=True)
264
+ ConnectionType(connection_type),
265
+ secrets,
266
+ udid,
267
+ script_mode,
268
+ max_idle_timeout=max_idle_timeout,
269
+ protocol=TunnelProtocol(protocol),
270
+ ),
271
+ debug=True,
272
+ )
202
273
 
203
274
 
204
275
  @dataclasses.dataclass
@@ -211,43 +282,48 @@ class RemotePairingManualPairingDevice:
211
282
 
212
283
  async def start_remote_pair_task(device_name: str) -> None:
213
284
  if start_tunnel is None:
214
- raise NotImplementedError('failed to start the tunnel on your platform')
285
+ raise NotImplementedError("failed to start the tunnel on your platform")
215
286
 
216
287
  devices: list[RemotePairingManualPairingDevice] = []
217
288
  for answer in await browse_remotepairing_manual_pairing():
218
- current_device_name = answer.properties[b'name'].decode()
289
+ current_device_name = answer.properties[b"name"].decode()
219
290
 
220
291
  if device_name is not None and current_device_name != device_name:
221
292
  continue
222
293
 
223
294
  for address in answer.addresses:
224
295
  devices.append(
225
- RemotePairingManualPairingDevice(ip=address.full_ip, port=answer.port, device_name=current_device_name,
226
- identifier=answer.properties[b'identifier'].decode()))
296
+ RemotePairingManualPairingDevice(
297
+ ip=address.full_ip,
298
+ port=answer.port,
299
+ device_name=current_device_name,
300
+ identifier=answer.properties[b"identifier"].decode(),
301
+ )
302
+ )
227
303
 
228
304
  if len(devices) > 0:
229
305
  device = prompt_device_list(devices)
230
306
  else:
231
- logger.error('No devices were found during bonjour browse')
307
+ logger.error("No devices were found during bonjour browse")
232
308
  return
233
309
 
234
310
  async with RemotePairingManualPairingService(device.identifier, device.ip, device.port) as service:
235
311
  await service.connect(autopair=True)
236
312
 
237
313
 
238
- @remote_cli.command('pair', cls=BaseCommand)
239
- @click.option('--name', help='Device name for a specific device to look for')
314
+ @remote_cli.command("pair", cls=BaseCommand)
315
+ @click.option("--name", help="Device name for a specific device to look for")
240
316
  def cli_pair(name: Optional[str]) -> None:
241
- """ start remote pairing for devices which allow """
317
+ """start remote pairing for devices which allow"""
242
318
  asyncio.run(start_remote_pair_task(name), debug=True)
243
319
 
244
320
 
245
- @remote_cli.command('delete-pair', cls=BaseCommand)
246
- @click.argument('udid')
321
+ @remote_cli.command("delete-pair", cls=BaseCommand)
322
+ @click.argument("udid")
247
323
  @sudo_required
248
324
  def cli_delete_pair(udid: str):
249
- """ delete a pairing record """
250
- pair_record_path = get_home_folder() / f'{get_remote_pairing_record_filename(udid)}.{PAIRING_RECORD_EXT}'
325
+ """delete a pairing record"""
326
+ pair_record_path = get_home_folder() / f"{get_remote_pairing_record_filename(udid)}.{PAIRING_RECORD_EXT}"
251
327
  pair_record_path.unlink()
252
328
 
253
329
 
@@ -256,8 +332,8 @@ async def cli_service_task(service_provider: RemoteServiceDiscoveryService, serv
256
332
  service.shell()
257
333
 
258
334
 
259
- @remote_cli.command('service', cls=RSDCommand)
260
- @click.argument('service_name')
335
+ @remote_cli.command("service", cls=RSDCommand)
336
+ @click.argument("service_name")
261
337
  def cli_service(service_provider: RemoteServiceDiscoveryService, service_name: str) -> None:
262
- """ start an ipython shell for interacting with given service """
338
+ """start an ipython shell for interacting with given service"""
263
339
  asyncio.run(cli_service_task(service_provider, service_name), debug=True)
@@ -32,15 +32,15 @@ print(irecv.getenv('build-version'))
32
32
  """
33
33
 
34
34
  logger = logging.getLogger(__name__)
35
- IPSWME_API = 'https://api.ipsw.me/v4/device/'
35
+ IPSWME_API = "https://api.ipsw.me/v4/device/"
36
36
 
37
37
 
38
38
  class Command(click.Command):
39
39
  def __init__(self, *args, **kwargs):
40
40
  super().__init__(*args, **kwargs)
41
41
  self.params[:0] = [
42
- click.Option(('device', '--ecid'), type=click.INT, callback=self.device),
43
- click.Option(('verbosity', '-v', '--verbose'), count=True, callback=set_verbosity, expose_value=False),
42
+ click.Option(("device", "--ecid"), type=click.INT, callback=self.device),
43
+ click.Option(("verbosity", "-v", "--verbose"), count=True, callback=set_verbosity, expose_value=False),
44
44
  ]
45
45
 
46
46
  @staticmethod
@@ -50,32 +50,32 @@ class Command(click.Command):
50
50
  return
51
51
 
52
52
  ecid = value
53
- logger.debug('searching among connected devices via lockdownd')
54
- devices = [dev for dev in usbmux.list_devices() if dev.connection_type == 'USB']
53
+ logger.debug("searching among connected devices via lockdownd")
54
+ devices = [dev for dev in usbmux.list_devices() if dev.connection_type == "USB"]
55
55
  if len(devices) > 1:
56
- raise click.ClickException('Multiple device detected')
56
+ raise click.ClickException("Multiple device detected")
57
57
  try:
58
58
  for device in devices:
59
59
  try:
60
- lockdown = create_using_usbmux(serial=device.serial, connection_type='USB')
60
+ lockdown = create_using_usbmux(serial=device.serial, connection_type="USB")
61
61
  except (ConnectionFailedError, IncorrectModeError):
62
62
  continue
63
63
  if (ecid is None) or (lockdown.ecid == value):
64
- logger.debug('found device')
64
+ logger.debug("found device")
65
65
  return lockdown
66
66
  else:
67
67
  continue
68
68
  except ConnectionFailedToUsbmuxdError:
69
69
  pass
70
70
 
71
- logger.debug('waiting for device to be available in Recovery mode')
71
+ logger.debug("waiting for device to be available in Recovery mode")
72
72
  return IRecv(ecid=ecid)
73
73
 
74
74
 
75
75
  @contextlib.contextmanager
76
76
  def tempzip_download_ctx(url: str) -> Generator[ZipFile, None, None]:
77
77
  with tempfile.TemporaryDirectory() as tmpdir:
78
- tmpzip = Path(tmpdir) / url.split('/')[-1]
78
+ tmpzip = Path(tmpdir) / url.split("/")[-1]
79
79
  file_download(url, tmpzip)
80
80
  yield ZipFile(tmpzip)
81
81
 
@@ -88,18 +88,19 @@ def zipfile_ctx(path: str) -> Generator[ZipFile, None, None]:
88
88
  class IPSWCommand(Command):
89
89
  def __init__(self, *args, **kwargs):
90
90
  super().__init__(*args, **kwargs)
91
- self.params.extend([click.Option(('ipsw_ctx', '-i', '--ipsw'), required=False,
92
- callback=self.ipsw_ctx, help='local IPSW file'),
93
- click.Option(('tss', '--tss'), type=click.File('rb'), callback=self.tss)])
91
+ self.params.extend([
92
+ click.Option(("ipsw_ctx", "-i", "--ipsw"), required=False, callback=self.ipsw_ctx, help="local IPSW file"),
93
+ click.Option(("tss", "--tss"), type=click.File("rb"), callback=self.tss),
94
+ ])
94
95
 
95
96
  @staticmethod
96
97
  def ipsw_ctx(ctx, param, value) -> Generator[ZipFile, None, None]:
97
- if value and not value.startswith(('http://', 'https://')):
98
+ if value and not value.startswith(("http://", "https://")):
98
99
  return zipfile_ctx(value)
99
100
 
100
101
  url = value
101
102
  if url is None:
102
- url = query_ipswme(ctx.params['device'].product_type)
103
+ url = query_ipswme(ctx.params["device"].product_type)
103
104
  return tempzip_download_ctx(url)
104
105
 
105
106
  @staticmethod
@@ -110,11 +111,11 @@ class IPSWCommand(Command):
110
111
 
111
112
 
112
113
  def query_ipswme(identifier: str) -> str:
113
- resp = requests.get(IPSWME_API + identifier, headers={'Accept': 'application/json'})
114
- firmwares = resp.json()['firmwares']
115
- display_list = [f'{entry["version"]}: {entry["buildid"]}' for entry in firmwares if entry['signed']]
116
- idx = prompt_selection(display_list, 'Choose version', idx=True)
117
- return firmwares[idx]['url']
114
+ resp = requests.get(IPSWME_API + identifier, headers={"Accept": "application/json"})
115
+ firmwares = resp.json()["firmwares"]
116
+ display_list = [f"{entry['version']}: {entry['buildid']}" for entry in firmwares if entry["signed"]]
117
+ idx = prompt_selection(display_list, "Choose version", idx=True)
118
+ return firmwares[idx]["url"]
118
119
 
119
120
 
120
121
  async def restore_update_task(device: Device, ipsw: ZipFile, tss: Optional[IO], erase: bool, ignore_fdr: bool) -> None:
@@ -145,38 +146,39 @@ def cli() -> None:
145
146
 
146
147
  @cli.group()
147
148
  def restore() -> None:
148
- """ Restore an IPSW or access device in recovery mode """
149
+ """Restore an IPSW or access device in recovery mode"""
149
150
  pass
150
151
 
151
152
 
152
- @restore.command('shell', cls=Command)
153
+ @restore.command("shell", cls=Command)
153
154
  def restore_shell(device):
154
- """ create an IPython shell for interacting with iBoot """
155
+ """create an IPython shell for interacting with iBoot"""
155
156
  IPython.embed(
156
- header=highlight(SHELL_USAGE, lexers.PythonLexer(), formatters.Terminal256Formatter(style='native')),
157
+ header=highlight(SHELL_USAGE, lexers.PythonLexer(), formatters.Terminal256Formatter(style="native")),
157
158
  user_ns={
158
- 'irecv': device,
159
- })
159
+ "irecv": device,
160
+ },
161
+ )
160
162
 
161
163
 
162
- @restore.command('enter', cls=Command)
164
+ @restore.command("enter", cls=Command)
163
165
  def restore_enter(device):
164
- """ enter Recovery mode """
166
+ """enter Recovery mode"""
165
167
  if isinstance(device, LockdownClient):
166
168
  device.enter_recovery()
167
169
 
168
170
 
169
- @restore.command('exit')
171
+ @restore.command("exit")
170
172
  def restore_exit():
171
- """ exit Recovery mode """
173
+ """exit Recovery mode"""
172
174
  irecv = IRecv()
173
175
  irecv.set_autoboot(True)
174
176
  irecv.reboot()
175
177
 
176
178
 
177
- @restore.command('restart', cls=Command)
179
+ @restore.command("restart", cls=Command)
178
180
  def restore_restart(device):
179
- """ restarts device """
181
+ """restarts device"""
180
182
  if isinstance(device, LockdownClient):
181
183
  with DiagnosticsService(device) as diagnostics:
182
184
  diagnostics.restart()
@@ -200,10 +202,10 @@ async def restore_tss_task(device: Device, ipsw_ctx: Generator, tss: IO, out: Op
200
202
  print_json(tss)
201
203
 
202
204
 
203
- @restore.command('tss', cls=IPSWCommand)
204
- @click.argument('out', type=click.File('wb'), required=False)
205
+ @restore.command("tss", cls=IPSWCommand)
206
+ @click.argument("out", type=click.File("wb"), required=False)
205
207
  def restore_tss(device: Device, ipsw_ctx: Generator, tss: IO, out: Optional[IO]) -> None:
206
- """ query SHSH blobs """
208
+ """query SHSH blobs"""
207
209
  asyncio.run(restore_tss_task(device, ipsw_ctx, tss, out), debug=True)
208
210
 
209
211
 
@@ -220,7 +222,7 @@ async def restore_ramdisk_task(device: Device, ipsw_ctx: Generator) -> None:
220
222
  await Recovery(ipsw, device).boot_ramdisk()
221
223
 
222
224
 
223
- @restore.command('ramdisk', cls=IPSWCommand)
225
+ @restore.command("ramdisk", cls=IPSWCommand)
224
226
  def restore_ramdisk(device: Device, ipsw_ctx: Generator, tss: IO) -> None:
225
227
  """
226
228
  don't perform an actual restore. just enter the update ramdisk
@@ -230,10 +232,11 @@ def restore_ramdisk(device: Device, ipsw_ctx: Generator, tss: IO) -> None:
230
232
  asyncio.run(restore_ramdisk_task(device, ipsw_ctx), debug=True)
231
233
 
232
234
 
233
- @restore.command('update', cls=IPSWCommand)
234
- @click.option('--erase', is_flag=True, help='use the Erase BuildIdentity (full factory-reset)')
235
- @click.option('--ignore-fdr', is_flag=True, help='only establish an FDR service connection, but don\'t proxy any '
236
- 'traffic')
235
+ @restore.command("update", cls=IPSWCommand)
236
+ @click.option("--erase", is_flag=True, help="use the Erase BuildIdentity (full factory-reset)")
237
+ @click.option(
238
+ "--ignore-fdr", is_flag=True, help="only establish an FDR service connection, but don't proxy any traffic"
239
+ )
237
240
  def restore_update(device: Device, ipsw_ctx: Generator, tss: IO, erase: bool, ignore_fdr: bool) -> None:
238
241
  """
239
242
  perform an update