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
@@ -7,13 +7,15 @@ from pymobiledevice3.remote.xpc_message import XpcInt64Type, XpcUInt64Type
7
7
 
8
8
 
9
9
  def _generate_core_device_version_dict(version: str) -> dict:
10
- version_components = version.split('.')
11
- return {'components': [XpcUInt64Type(component) for component in version_components],
12
- 'originalComponentsCount': XpcInt64Type(len(version_components)),
13
- 'stringValue': version}
10
+ version_components = version.split(".")
11
+ return {
12
+ "components": [XpcUInt64Type(component) for component in version_components],
13
+ "originalComponentsCount": XpcInt64Type(len(version_components)),
14
+ "stringValue": version,
15
+ }
14
16
 
15
17
 
16
- CORE_DEVICE_VERSION = _generate_core_device_version_dict('325.3')
18
+ CORE_DEVICE_VERSION = _generate_core_device_version_dict("325.3")
17
19
 
18
20
 
19
21
  class CoreDeviceService(RemoteService):
@@ -21,14 +23,15 @@ class CoreDeviceService(RemoteService):
21
23
  if input_ is None:
22
24
  input_ = {}
23
25
  response = await self.service.send_receive_request({
24
- 'CoreDevice.CoreDeviceDDIProtocolVersion': XpcInt64Type(0),
25
- 'CoreDevice.action': {},
26
- 'CoreDevice.coreDeviceVersion': CORE_DEVICE_VERSION,
27
- 'CoreDevice.deviceIdentifier': str(uuid.uuid4()),
28
- 'CoreDevice.featureIdentifier': feature_identifier,
29
- 'CoreDevice.input': input_,
30
- 'CoreDevice.invocationIdentifier': str(uuid.uuid4())})
31
- output = response.get('CoreDevice.output')
26
+ "CoreDevice.CoreDeviceDDIProtocolVersion": XpcInt64Type(0),
27
+ "CoreDevice.action": {},
28
+ "CoreDevice.coreDeviceVersion": CORE_DEVICE_VERSION,
29
+ "CoreDevice.deviceIdentifier": str(uuid.uuid4()),
30
+ "CoreDevice.featureIdentifier": feature_identifier,
31
+ "CoreDevice.input": input_,
32
+ "CoreDevice.invocationIdentifier": str(uuid.uuid4()),
33
+ })
34
+ output = response.get("CoreDevice.output")
32
35
  if output is None:
33
- raise CoreDeviceError(f'Failed to invoke: {feature_identifier}. Got error: {response}')
36
+ raise CoreDeviceError(f"Failed to invoke: {feature_identifier}. Got error: {response}")
34
37
  return output
@@ -7,7 +7,7 @@ class DeviceInfoService(CoreDeviceService):
7
7
  Query device information
8
8
  """
9
9
 
10
- SERVICE_NAME = 'com.apple.coredevice.deviceinfo'
10
+ SERVICE_NAME = "com.apple.coredevice.deviceinfo"
11
11
 
12
12
  def __init__(self, rsd: RemoteServiceDiscoveryService):
13
13
  super().__init__(rsd, self.SERVICE_NAME)
@@ -16,13 +16,13 @@ class DeviceInfoService(CoreDeviceService):
16
16
  """
17
17
  Get device information
18
18
  """
19
- return await self.invoke('com.apple.coredevice.feature.getdeviceinfo', {})
19
+ return await self.invoke("com.apple.coredevice.feature.getdeviceinfo", {})
20
20
 
21
21
  async def get_display_info(self) -> dict:
22
22
  """
23
23
  Get display information
24
24
  """
25
- return await self.invoke('com.apple.coredevice.feature.getdisplayinfo', {})
25
+ return await self.invoke("com.apple.coredevice.feature.getdisplayinfo", {})
26
26
 
27
27
  async def query_mobilegestalt(self, keys: list[str]) -> dict:
28
28
  """
@@ -30,10 +30,10 @@ class DeviceInfoService(CoreDeviceService):
30
30
 
31
31
  Can only be performed to specific devices
32
32
  """
33
- return await self.invoke('com.apple.coredevice.feature.querymobilegestalt', {'keys': keys})
33
+ return await self.invoke("com.apple.coredevice.feature.querymobilegestalt", {"keys": keys})
34
34
 
35
35
  async def get_lockstate(self) -> dict:
36
36
  """
37
37
  Get lockstate
38
38
  """
39
- return await self.invoke('com.apple.coredevice.feature.getlockstate', {})
39
+ return await self.invoke("com.apple.coredevice.feature.getlockstate", {})
@@ -1,19 +1,34 @@
1
+ import dataclasses
1
2
  from collections.abc import AsyncGenerator
2
3
 
3
4
  from pymobiledevice3.remote.core_device.core_device_service import CoreDeviceService
4
5
  from pymobiledevice3.remote.remote_service_discovery import RemoteServiceDiscoveryService
5
6
 
6
7
 
8
+ @dataclasses.dataclass
9
+ class SysDiagnoseResponse:
10
+ preferred_filename: str
11
+ file_size: int
12
+ generator: AsyncGenerator[bytes, None]
13
+
14
+
7
15
  class DiagnosticsServiceService(CoreDeviceService):
8
16
  """
9
17
  Obtain device diagnostics
10
18
  """
11
19
 
12
- SERVICE_NAME = 'com.apple.coredevice.diagnosticsservice'
20
+ SERVICE_NAME = "com.apple.coredevice.diagnosticsservice"
13
21
 
14
22
  def __init__(self, rsd: RemoteServiceDiscoveryService):
15
23
  super().__init__(rsd, self.SERVICE_NAME)
16
24
 
17
- async def capture_sysdiagnose(self, is_dry_run: bool) -> AsyncGenerator[bytes, None]:
18
- response = await self.invoke('com.apple.coredevice.feature.capturesysdiagnose', {'isDryRun': is_dry_run})
19
- return self.service.iter_file_chunks(response['fileTransfer']['expectedLength'])
25
+ async def capture_sysdiagnose(self, is_dry_run: bool) -> SysDiagnoseResponse:
26
+ response = await self.invoke(
27
+ "com.apple.coredevice.feature.capturesysdiagnose",
28
+ {"options": {"collectFullLogs": True}, "isDryRun": is_dry_run},
29
+ )
30
+ return SysDiagnoseResponse(
31
+ file_size=response["fileTransfer"]["expectedLength"],
32
+ preferred_filename=response["preferredFilename"],
33
+ generator=self.service.iter_file_chunks(response["fileTransfer"]["expectedLength"]),
34
+ )
@@ -1,5 +1,6 @@
1
1
  import asyncio
2
2
  import struct
3
+ import time
3
4
  import uuid
4
5
  from collections.abc import AsyncGenerator
5
6
  from enum import IntEnum
@@ -8,7 +9,7 @@ from typing import Optional
8
9
  from pymobiledevice3.exceptions import CoreDeviceError
9
10
  from pymobiledevice3.remote.core_device.core_device_service import CoreDeviceService
10
11
  from pymobiledevice3.remote.remote_service_discovery import RemoteServiceDiscoveryService
11
- from pymobiledevice3.remote.xpc_message import XpcUInt64Type
12
+ from pymobiledevice3.remote.xpc_message import XpcInt64Type, XpcUInt64Type
12
13
 
13
14
 
14
15
  class Domain(IntEnum):
@@ -19,10 +20,10 @@ class Domain(IntEnum):
19
20
 
20
21
 
21
22
  APPLE_DOMAIN_DICT = {
22
- 'appDataContainer': Domain.APP_DATA_CONTAINER,
23
- 'appGroupDataContainer': Domain.APP_GROUP_DATA_CONTAINER,
24
- 'temporary': Domain.TEMPORARY,
25
- 'systemCrashLogs': Domain.SYSTEM_CRASH_LOGS
23
+ "appDataContainer": Domain.APP_DATA_CONTAINER,
24
+ "appGroupDataContainer": Domain.APP_GROUP_DATA_CONTAINER,
25
+ "temporary": Domain.TEMPORARY,
26
+ "systemCrashLogs": Domain.SYSTEM_CRASH_LOGS,
26
27
  }
27
28
 
28
29
 
@@ -31,41 +32,70 @@ class FileServiceService(CoreDeviceService):
31
32
  Filesystem control
32
33
  """
33
34
 
34
- CTRL_SERVICE_NAME = 'com.apple.coredevice.fileservice.control'
35
+ CTRL_SERVICE_NAME = "com.apple.coredevice.fileservice.control"
35
36
 
36
- def __init__(self, rsd: RemoteServiceDiscoveryService, domain: Domain) -> None:
37
+ def __init__(self, rsd: RemoteServiceDiscoveryService, domain: Domain, identifier: str = "") -> None:
37
38
  super().__init__(rsd, self.CTRL_SERVICE_NAME)
38
39
  self.domain: Domain = domain
39
40
  self.session: Optional[str] = None
41
+ self.identifier = identifier
40
42
 
41
43
  async def connect(self) -> None:
42
44
  await super().connect()
43
45
  response = await self.send_receive_request({
44
- 'Cmd': 'CreateSession', 'Domain': XpcUInt64Type(self.domain), 'Identifier': '', 'Session': '',
45
- 'User': 'mobile'})
46
- self.session = response['NewSessionID']
46
+ "Cmd": "CreateSession",
47
+ "Domain": XpcUInt64Type(self.domain),
48
+ "Identifier": self.identifier,
49
+ "Session": "",
50
+ "User": "mobile",
51
+ })
52
+ self.session = response["NewSessionID"]
47
53
 
48
- async def retrieve_directory_list(self, path: str = '.') -> AsyncGenerator[list[str], None]:
49
- return (await self.send_receive_request({
50
- 'Cmd': 'RetrieveDirectoryList', 'MessageUUID': str(uuid.uuid4()), 'Path': path, 'SessionID': self.session}
51
- ))['FileList']
54
+ async def retrieve_directory_list(self, path: str = ".") -> AsyncGenerator[list[str], None]:
55
+ return (
56
+ await self.send_receive_request({
57
+ "Cmd": "RetrieveDirectoryList",
58
+ "MessageUUID": str(uuid.uuid4()),
59
+ "Path": path,
60
+ "SessionID": self.session,
61
+ })
62
+ )["FileList"]
52
63
 
53
- async def retrieve_file(self, path: str = '.') -> bytes:
54
- response = await self.send_receive_request({
55
- 'Cmd': 'RetrieveFile', 'Path': path, 'SessionID': self.session}
56
- )
57
- data_service = self.rsd.get_service_port('com.apple.coredevice.fileservice.data')
64
+ async def retrieve_file(self, path: str = ".") -> bytes:
65
+ response = await self.send_receive_request({"Cmd": "RetrieveFile", "Path": path, "SessionID": self.session})
66
+ data_service = self.rsd.get_service_port("com.apple.coredevice.fileservice.data")
58
67
  reader, writer = await asyncio.open_connection(self.service.address[0], data_service)
59
- writer.write(b'rwb!FILE' + struct.pack('>QQQQ', response['Response'], 0, response['NewFileID'], 0))
68
+ writer.write(b"rwb!FILE" + struct.pack(">QQQQ", response["Response"], 0, response["NewFileID"], 0))
60
69
  await writer.drain()
61
70
  await reader.readexactly(0x24)
62
- return await reader.readexactly(struct.unpack('>I', await reader.readexactly(4))[0])
71
+ return await reader.readexactly(struct.unpack(">I", await reader.readexactly(4))[0])
72
+
73
+ async def propose_empty_file(
74
+ self,
75
+ path: str = ".",
76
+ file_permissions: int = 0o644,
77
+ uid: int = 501,
78
+ gid: int = 501,
79
+ creation_time: int = time.time(),
80
+ last_modification_time: int = time.time(),
81
+ ) -> None:
82
+ """Request to write an empty file at given path."""
83
+ await self.send_receive_request({
84
+ "Cmd": "ProposeEmptyFile",
85
+ "FileCreationTime": XpcInt64Type(creation_time),
86
+ "FileLastModificationTime": XpcInt64Type(last_modification_time),
87
+ "FilePermissions": XpcInt64Type(file_permissions),
88
+ "FileOwnerUserID": XpcInt64Type(uid),
89
+ "FileOwnerGroupID": XpcInt64Type(gid),
90
+ "Path": path,
91
+ "SessionID": self.session,
92
+ })
63
93
 
64
94
  async def send_receive_request(self, request: dict) -> dict:
65
95
  response = await self.service.send_receive_request(request)
66
- encoded_error = response.get('EncodedError')
96
+ encoded_error = response.get("EncodedError")
67
97
  if encoded_error is not None:
68
- localized_description = response.get('LocalizedDescription')
98
+ localized_description = response.get("LocalizedDescription")
69
99
  if localized_description is not None:
70
100
  raise CoreDeviceError(localized_description)
71
101
  raise CoreDeviceError(encoded_error)
@@ -2,12 +2,16 @@ import base64
2
2
  import logging
3
3
  from dataclasses import dataclass
4
4
  from datetime import datetime
5
- from typing import Optional, Union
5
+ from typing import Any, Optional, Union
6
6
 
7
7
  from pymobiledevice3.bonjour import DEFAULT_BONJOUR_TIMEOUT, browse_remoted
8
8
  from pymobiledevice3.common import get_home_folder
9
- from pymobiledevice3.exceptions import InvalidServiceError, NoDeviceConnectedError, PyMobileDevice3Exception, \
10
- StartServiceError
9
+ from pymobiledevice3.exceptions import (
10
+ InvalidServiceError,
11
+ NoDeviceConnectedError,
12
+ PyMobileDevice3Exception,
13
+ StartServiceError,
14
+ )
11
15
  from pymobiledevice3.lockdown import LockdownClient, create_using_remote
12
16
  from pymobiledevice3.lockdown_service_provider import LockdownServiceProvider
13
17
  from pymobiledevice3.pair_records import get_local_pairing_record, get_remote_pairing_record_filename
@@ -38,11 +42,15 @@ class RemoteServiceDiscoveryService(LockdownServiceProvider):
38
42
 
39
43
  @property
40
44
  def product_version(self) -> str:
41
- return self.peer_info['Properties']['OSVersion']
45
+ return self.peer_info["Properties"]["OSVersion"]
46
+
47
+ @property
48
+ def product_build_version(self) -> str:
49
+ return self.peer_info["Properties"]["BuildVersion"]
42
50
 
43
51
  @property
44
52
  def ecid(self) -> int:
45
- return self.peer_info['Properties']['UniqueChipID']
53
+ return self.peer_info["Properties"]["UniqueChipID"]
46
54
 
47
55
  @property
48
56
  def developer_mode_status(self) -> bool:
@@ -57,17 +65,24 @@ class RemoteServiceDiscoveryService(LockdownServiceProvider):
57
65
 
58
66
  async def connect(self) -> None:
59
67
  await self.service.connect()
60
- self.peer_info = await self.service.receive_response()
61
- self.udid = self.peer_info['Properties']['UniqueDeviceID']
62
- self.product_type = self.peer_info['Properties']['ProductType']
63
68
  try:
64
- self.lockdown = create_using_remote(self.start_lockdown_service('com.apple.mobile.lockdown.remote.trusted'))
65
- except InvalidServiceError:
66
- self.lockdown = create_using_remote(
67
- self.start_lockdown_service('com.apple.mobile.lockdown.remote.untrusted'))
68
- self.all_values = self.lockdown.all_values
69
+ self.peer_info = await self.service.receive_response()
70
+ self.udid = self.peer_info["Properties"]["UniqueDeviceID"]
71
+ self.product_type = self.peer_info["Properties"]["ProductType"]
72
+ try:
73
+ self.lockdown = create_using_remote(
74
+ self.start_lockdown_service("com.apple.mobile.lockdown.remote.trusted")
75
+ )
76
+ except InvalidServiceError:
77
+ self.lockdown = create_using_remote(
78
+ self.start_lockdown_service("com.apple.mobile.lockdown.remote.untrusted")
79
+ )
80
+ self.all_values = self.lockdown.all_values
81
+ except Exception:
82
+ await self.close()
83
+ raise
69
84
 
70
- def get_value(self, domain: str = None, key: str = None):
85
+ def get_value(self, domain: Optional[str] = None, key: Optional[str] = None) -> Any:
71
86
  return self.lockdown.get_value(domain, key)
72
87
 
73
88
  def start_lockdown_service_without_checkin(self, name: str) -> ServiceConnection:
@@ -75,20 +90,30 @@ class RemoteServiceDiscoveryService(LockdownServiceProvider):
75
90
 
76
91
  def start_lockdown_service(self, name: str, include_escrow_bag: bool = False) -> ServiceConnection:
77
92
  service = self.start_lockdown_service_without_checkin(name)
78
- checkin = {'Label': 'pymobiledevice3', 'ProtocolVersion': '2', 'Request': 'RSDCheckin'}
79
- if include_escrow_bag:
80
- pairing_record = get_local_pairing_record(get_remote_pairing_record_filename(self.udid), get_home_folder())
81
- checkin['EscrowBag'] = base64.b64decode(pairing_record['remote_unlock_host_key'])
82
- response = service.send_recv_plist(checkin)
83
- if response['Request'] != 'RSDCheckin':
84
- raise PyMobileDevice3Exception(f'Invalid response for RSDCheckIn: {response}. Expected "RSDCheckIn"')
85
- response = service.recv_plist()
86
- if response['Request'] != 'StartService':
87
- raise PyMobileDevice3Exception(f'Invalid response for RSDCheckIn: {response}. Expected "ServiceService"')
93
+ try:
94
+ checkin = {"Label": "pymobiledevice3", "ProtocolVersion": "2", "Request": "RSDCheckin"}
95
+ if include_escrow_bag:
96
+ pairing_record = get_local_pairing_record(
97
+ get_remote_pairing_record_filename(self.udid), get_home_folder()
98
+ )
99
+ checkin["EscrowBag"] = base64.b64decode(pairing_record["remote_unlock_host_key"])
100
+ response = service.send_recv_plist(checkin)
101
+ if response["Request"] != "RSDCheckin":
102
+ raise PyMobileDevice3Exception(f'Invalid response for RSDCheckIn: {response}. Expected "RSDCheckIn"')
103
+ response = service.recv_plist()
104
+ if response["Request"] != "StartService":
105
+ raise PyMobileDevice3Exception(
106
+ f'Invalid response for RSDCheckIn: {response}. Expected "ServiceService"'
107
+ )
108
+ error = response.get("Error")
109
+ if error is not None:
110
+ raise StartServiceError(name, error)
111
+ except Exception:
112
+ service.close()
113
+ raise
88
114
  return service
89
115
 
90
- async def aio_start_lockdown_service(
91
- self, name: str, include_escrow_bag: bool = False) -> ServiceConnection:
116
+ async def aio_start_lockdown_service(self, name: str, include_escrow_bag: bool = False) -> ServiceConnection:
92
117
  service = self.start_lockdown_service(name, include_escrow_bag=include_escrow_bag)
93
118
  await service.aio_start()
94
119
  return service
@@ -97,9 +122,9 @@ class RemoteServiceDiscoveryService(LockdownServiceProvider):
97
122
  try:
98
123
  return self.start_lockdown_service_without_checkin(name)
99
124
  except StartServiceError:
100
- logging.getLogger(self.__module__).error(
101
- 'Failed to connect to required service. Make sure DeveloperDiskImage.dmg has been mounted. '
102
- 'You can do so using: pymobiledevice3 mounter mount'
125
+ logging.getLogger(self.__module__).exception(
126
+ "Failed to connect to required service. Make sure DeveloperDiskImage.dmg has been mounted. "
127
+ "You can do so using: pymobiledevice3 mounter mount"
103
128
  )
104
129
  raise
105
130
 
@@ -108,24 +133,24 @@ class RemoteServiceDiscoveryService(LockdownServiceProvider):
108
133
  return service
109
134
 
110
135
  def start_service(self, name: str) -> Union[RemoteXPCConnection, ServiceConnection]:
111
- service = self.peer_info['Services'][name]
112
- service_properties = service.get('Properties', {})
113
- use_remote_xpc = service_properties.get('UsesRemoteXPC', False)
136
+ service = self.peer_info["Services"][name]
137
+ service_properties = service.get("Properties", {})
138
+ use_remote_xpc = service_properties.get("UsesRemoteXPC", False)
114
139
  return self.start_remote_service(name) if use_remote_xpc else self.start_lockdown_service(name)
115
140
 
116
141
  def get_service_port(self, name: str) -> int:
117
142
  """takes a service name and returns the port that service is running on if the service exists"""
118
- service = self.peer_info['Services'].get(name)
143
+ service = self.peer_info["Services"].get(name)
119
144
  if service is None:
120
- raise InvalidServiceError(f'No such service: {name}')
121
- return int(service['Port'])
145
+ raise InvalidServiceError(f"No such service: {name}")
146
+ return int(service["Port"])
122
147
 
123
148
  async def close(self) -> None:
124
149
  if self.lockdown is not None:
125
150
  self.lockdown.close()
126
151
  await self.service.close()
127
152
 
128
- async def __aenter__(self) -> 'RemoteServiceDiscoveryService':
153
+ async def __aenter__(self) -> "RemoteServiceDiscoveryService":
129
154
  await self.connect()
130
155
  return self
131
156
 
@@ -133,20 +158,29 @@ class RemoteServiceDiscoveryService(LockdownServiceProvider):
133
158
  await self.close()
134
159
 
135
160
  def __repr__(self) -> str:
136
- name_str = ''
161
+ name_str = ""
137
162
  if self.name:
138
- name_str = f' NAME:{self.name}'
139
- return (f'<{self.__class__.__name__} PRODUCT:{self.product_type} VERSION:{self.product_version} '
140
- f'UDID:{self.udid}{name_str}>')
163
+ name_str = f" NAME:{self.name}"
164
+ return (
165
+ f"<{self.__class__.__name__} PRODUCT:{self.product_type} VERSION:{self.product_version} "
166
+ f"UDID:{self.udid}{name_str}>"
167
+ )
141
168
 
142
169
 
143
170
  async def get_remoted_devices(timeout: float = DEFAULT_BONJOUR_TIMEOUT) -> list[RSDDevice]:
144
171
  result = []
145
- for hostname in await browse_remoted(timeout):
146
- with RemoteServiceDiscoveryService((hostname, RSD_PORT)) as rsd:
147
- properties = rsd.peer_info['Properties']
148
- result.append(RSDDevice(hostname=hostname, udid=properties['UniqueDeviceID'],
149
- product_type=properties['ProductType'], os_version=properties['OSVersion']))
172
+ for instance in await browse_remoted(timeout):
173
+ for address in instance.addresses:
174
+ with RemoteServiceDiscoveryService((address.full_ip, RSD_PORT)) as rsd:
175
+ properties = rsd.peer_info["Properties"]
176
+ result.append(
177
+ RSDDevice(
178
+ hostname=address.full_ip,
179
+ udid=properties["UniqueDeviceID"],
180
+ product_type=properties["ProductType"],
181
+ os_version=properties["OSVersion"],
182
+ )
183
+ )
150
184
  return result
151
185
 
152
186