pymobiledevice3 4.27.4__py3-none-any.whl → 5.1.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.
Files changed (143) hide show
  1. misc/plist_sniffer.py +15 -15
  2. misc/remotexpc_sniffer.py +29 -28
  3. pymobiledevice3/__main__.py +123 -98
  4. pymobiledevice3/_version.py +2 -2
  5. pymobiledevice3/bonjour.py +351 -117
  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 +27 -20
  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 +601 -519
  18. pymobiledevice3/cli/diagnostics.py +38 -33
  19. pymobiledevice3/cli/lockdown.py +82 -72
  20. pymobiledevice3/cli/mounter.py +84 -67
  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 +188 -111
  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 +156 -78
  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 +400 -251
  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 +64 -42
  52. pymobiledevice3/remote/tunnel_service.py +383 -297
  53. pymobiledevice3/remote/utils.py +14 -13
  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 +587 -0
  65. pymobiledevice3/restore/recovery.py +125 -135
  66. pymobiledevice3/restore/restore.py +535 -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 +137 -127
  72. pymobiledevice3/services/afc.py +352 -292
  73. pymobiledevice3/services/amfi.py +21 -18
  74. pymobiledevice3/services/companion.py +23 -19
  75. pymobiledevice3/services/crash_reports.py +61 -47
  76. pymobiledevice3/services/debugserver_applist.py +3 -3
  77. pymobiledevice3/services/device_arbitration.py +8 -8
  78. pymobiledevice3/services/device_link.py +56 -48
  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 +42 -52
  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 +331 -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 +128 -74
  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 +183 -179
  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 +3 -3
  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 +142 -116
  132. pymobiledevice3/tcp_forwarder.py +35 -22
  133. pymobiledevice3/tunneld/api.py +20 -15
  134. pymobiledevice3/tunneld/server.py +310 -193
  135. pymobiledevice3/usbmux.py +197 -148
  136. pymobiledevice3/utils.py +14 -11
  137. {pymobiledevice3-4.27.4.dist-info → pymobiledevice3-5.1.2.dist-info}/METADATA +1 -2
  138. pymobiledevice3-5.1.2.dist-info/RECORD +173 -0
  139. pymobiledevice3-4.27.4.dist-info/RECORD +0 -172
  140. {pymobiledevice3-4.27.4.dist-info → pymobiledevice3-5.1.2.dist-info}/WHEEL +0 -0
  141. {pymobiledevice3-4.27.4.dist-info → pymobiledevice3-5.1.2.dist-info}/entry_points.txt +0 -0
  142. {pymobiledevice3-4.27.4.dist-info → pymobiledevice3-5.1.2.dist-info}/licenses/LICENSE +0 -0
  143. {pymobiledevice3-4.27.4.dist-info → pymobiledevice3-5.1.2.dist-info}/top_level.txt +0 -0
@@ -17,16 +17,18 @@ class DiagnosticsServiceService(CoreDeviceService):
17
17
  Obtain device diagnostics
18
18
  """
19
19
 
20
- SERVICE_NAME = 'com.apple.coredevice.diagnosticsservice'
20
+ SERVICE_NAME = "com.apple.coredevice.diagnosticsservice"
21
21
 
22
22
  def __init__(self, rsd: RemoteServiceDiscoveryService):
23
23
  super().__init__(rsd, self.SERVICE_NAME)
24
24
 
25
25
  async def capture_sysdiagnose(self, is_dry_run: bool) -> SysDiagnoseResponse:
26
- response = await self.invoke('com.apple.coredevice.feature.capturesysdiagnose', {
27
- 'options': {
28
- 'collectFullLogs': True
29
- }, 'isDryRun': is_dry_run})
30
- return SysDiagnoseResponse(file_size=response['fileTransfer']['expectedLength'],
31
- preferred_filename=response['preferredFilename'],
32
- generator=self.service.iter_file_chunks(response['fileTransfer']['expectedLength']))
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
+ )
@@ -20,10 +20,10 @@ class Domain(IntEnum):
20
20
 
21
21
 
22
22
  APPLE_DOMAIN_DICT = {
23
- 'appDataContainer': Domain.APP_DATA_CONTAINER,
24
- 'appGroupDataContainer': Domain.APP_GROUP_DATA_CONTAINER,
25
- 'temporary': Domain.TEMPORARY,
26
- '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,
27
27
  }
28
28
 
29
29
 
@@ -32,9 +32,9 @@ class FileServiceService(CoreDeviceService):
32
32
  Filesystem control
33
33
  """
34
34
 
35
- CTRL_SERVICE_NAME = 'com.apple.coredevice.fileservice.control'
35
+ CTRL_SERVICE_NAME = "com.apple.coredevice.fileservice.control"
36
36
 
37
- def __init__(self, rsd: RemoteServiceDiscoveryService, domain: Domain, identifier: str = '') -> None:
37
+ def __init__(self, rsd: RemoteServiceDiscoveryService, domain: Domain, identifier: str = "") -> None:
38
38
  super().__init__(rsd, self.CTRL_SERVICE_NAME)
39
39
  self.domain: Domain = domain
40
40
  self.session: Optional[str] = None
@@ -43,45 +43,59 @@ class FileServiceService(CoreDeviceService):
43
43
  async def connect(self) -> None:
44
44
  await super().connect()
45
45
  response = await self.send_receive_request({
46
- 'Cmd': 'CreateSession', 'Domain': XpcUInt64Type(self.domain), 'Identifier': self.identifier, 'Session': '',
47
- 'User': 'mobile'})
48
- 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"]
49
53
 
50
- async def retrieve_directory_list(self, path: str = '.') -> AsyncGenerator[list[str], None]:
51
- return (await self.send_receive_request({
52
- 'Cmd': 'RetrieveDirectoryList', 'MessageUUID': str(uuid.uuid4()), 'Path': path, 'SessionID': self.session}
53
- ))['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"]
54
63
 
55
- async def retrieve_file(self, path: str = '.') -> bytes:
56
- response = await self.send_receive_request({
57
- 'Cmd': 'RetrieveFile', 'Path': path, 'SessionID': self.session}
58
- )
59
- 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")
60
67
  reader, writer = await asyncio.open_connection(self.service.address[0], data_service)
61
- 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))
62
69
  await writer.drain()
63
70
  await reader.readexactly(0x24)
64
- 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])
65
72
 
66
- async def propose_empty_file(self, path: str = '.', file_permissions: int = 0o644, uid: int = 501, gid: int = 501,
67
- creation_time: int = time.time(), last_modification_time: int = time.time()) -> None:
68
- """ Request to write an empty file at given path. """
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."""
69
83
  await self.send_receive_request({
70
- 'Cmd': 'ProposeEmptyFile',
71
- 'FileCreationTime': XpcInt64Type(creation_time),
72
- 'FileLastModificationTime': XpcInt64Type(last_modification_time),
73
- 'FilePermissions': XpcInt64Type(file_permissions),
74
- 'FileOwnerUserID': XpcInt64Type(uid),
75
- 'FileOwnerGroupID': XpcInt64Type(gid),
76
- 'Path': path,
77
- 'SessionID': self.session
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,
78
92
  })
79
93
 
80
94
  async def send_receive_request(self, request: dict) -> dict:
81
95
  response = await self.service.send_receive_request(request)
82
- encoded_error = response.get('EncodedError')
96
+ encoded_error = response.get("EncodedError")
83
97
  if encoded_error is not None:
84
- localized_description = response.get('LocalizedDescription')
98
+ localized_description = response.get("LocalizedDescription")
85
99
  if localized_description is not None:
86
100
  raise CoreDeviceError(localized_description)
87
101
  raise CoreDeviceError(encoded_error)
@@ -6,8 +6,12 @@ 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,11 @@ 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"]
42
46
 
43
47
  @property
44
48
  def ecid(self) -> int:
45
- return self.peer_info['Properties']['UniqueChipID']
49
+ return self.peer_info["Properties"]["UniqueChipID"]
46
50
 
47
51
  @property
48
52
  def developer_mode_status(self) -> bool:
@@ -59,15 +63,18 @@ class RemoteServiceDiscoveryService(LockdownServiceProvider):
59
63
  await self.service.connect()
60
64
  try:
61
65
  self.peer_info = await self.service.receive_response()
62
- self.udid = self.peer_info['Properties']['UniqueDeviceID']
63
- self.product_type = self.peer_info['Properties']['ProductType']
66
+ self.udid = self.peer_info["Properties"]["UniqueDeviceID"]
67
+ self.product_type = self.peer_info["Properties"]["ProductType"]
64
68
  try:
65
- self.lockdown = create_using_remote(self.start_lockdown_service('com.apple.mobile.lockdown.remote.trusted'))
69
+ self.lockdown = create_using_remote(
70
+ self.start_lockdown_service("com.apple.mobile.lockdown.remote.trusted")
71
+ )
66
72
  except InvalidServiceError:
67
73
  self.lockdown = create_using_remote(
68
- self.start_lockdown_service('com.apple.mobile.lockdown.remote.untrusted'))
74
+ self.start_lockdown_service("com.apple.mobile.lockdown.remote.untrusted")
75
+ )
69
76
  self.all_values = self.lockdown.all_values
70
- except Exception: # noqa: E722
77
+ except Exception:
71
78
  await self.close()
72
79
  raise
73
80
 
@@ -80,23 +87,26 @@ class RemoteServiceDiscoveryService(LockdownServiceProvider):
80
87
  def start_lockdown_service(self, name: str, include_escrow_bag: bool = False) -> ServiceConnection:
81
88
  service = self.start_lockdown_service_without_checkin(name)
82
89
  try:
83
- checkin = {'Label': 'pymobiledevice3', 'ProtocolVersion': '2', 'Request': 'RSDCheckin'}
90
+ checkin = {"Label": "pymobiledevice3", "ProtocolVersion": "2", "Request": "RSDCheckin"}
84
91
  if include_escrow_bag:
85
- pairing_record = get_local_pairing_record(get_remote_pairing_record_filename(self.udid), get_home_folder())
86
- checkin['EscrowBag'] = base64.b64decode(pairing_record['remote_unlock_host_key'])
92
+ pairing_record = get_local_pairing_record(
93
+ get_remote_pairing_record_filename(self.udid), get_home_folder()
94
+ )
95
+ checkin["EscrowBag"] = base64.b64decode(pairing_record["remote_unlock_host_key"])
87
96
  response = service.send_recv_plist(checkin)
88
- if response['Request'] != 'RSDCheckin':
97
+ if response["Request"] != "RSDCheckin":
89
98
  raise PyMobileDevice3Exception(f'Invalid response for RSDCheckIn: {response}. Expected "RSDCheckIn"')
90
99
  response = service.recv_plist()
91
- if response['Request'] != 'StartService':
92
- raise PyMobileDevice3Exception(f'Invalid response for RSDCheckIn: {response}. Expected "ServiceService"')
93
- except Exception: # noqa: E722
100
+ if response["Request"] != "StartService":
101
+ raise PyMobileDevice3Exception(
102
+ f'Invalid response for RSDCheckIn: {response}. Expected "ServiceService"'
103
+ )
104
+ except Exception:
94
105
  service.close()
95
106
  raise
96
107
  return service
97
108
 
98
- async def aio_start_lockdown_service(
99
- self, name: str, include_escrow_bag: bool = False) -> ServiceConnection:
109
+ async def aio_start_lockdown_service(self, name: str, include_escrow_bag: bool = False) -> ServiceConnection:
100
110
  service = self.start_lockdown_service(name, include_escrow_bag=include_escrow_bag)
101
111
  await service.aio_start()
102
112
  return service
@@ -105,9 +115,9 @@ class RemoteServiceDiscoveryService(LockdownServiceProvider):
105
115
  try:
106
116
  return self.start_lockdown_service_without_checkin(name)
107
117
  except StartServiceError:
108
- logging.getLogger(self.__module__).error(
109
- 'Failed to connect to required service. Make sure DeveloperDiskImage.dmg has been mounted. '
110
- 'You can do so using: pymobiledevice3 mounter mount'
118
+ logging.getLogger(self.__module__).exception(
119
+ "Failed to connect to required service. Make sure DeveloperDiskImage.dmg has been mounted. "
120
+ "You can do so using: pymobiledevice3 mounter mount"
111
121
  )
112
122
  raise
113
123
 
@@ -116,24 +126,24 @@ class RemoteServiceDiscoveryService(LockdownServiceProvider):
116
126
  return service
117
127
 
118
128
  def start_service(self, name: str) -> Union[RemoteXPCConnection, ServiceConnection]:
119
- service = self.peer_info['Services'][name]
120
- service_properties = service.get('Properties', {})
121
- use_remote_xpc = service_properties.get('UsesRemoteXPC', False)
129
+ service = self.peer_info["Services"][name]
130
+ service_properties = service.get("Properties", {})
131
+ use_remote_xpc = service_properties.get("UsesRemoteXPC", False)
122
132
  return self.start_remote_service(name) if use_remote_xpc else self.start_lockdown_service(name)
123
133
 
124
134
  def get_service_port(self, name: str) -> int:
125
135
  """takes a service name and returns the port that service is running on if the service exists"""
126
- service = self.peer_info['Services'].get(name)
136
+ service = self.peer_info["Services"].get(name)
127
137
  if service is None:
128
- raise InvalidServiceError(f'No such service: {name}')
129
- return int(service['Port'])
138
+ raise InvalidServiceError(f"No such service: {name}")
139
+ return int(service["Port"])
130
140
 
131
141
  async def close(self) -> None:
132
142
  if self.lockdown is not None:
133
143
  self.lockdown.close()
134
144
  await self.service.close()
135
145
 
136
- async def __aenter__(self) -> 'RemoteServiceDiscoveryService':
146
+ async def __aenter__(self) -> "RemoteServiceDiscoveryService":
137
147
  await self.connect()
138
148
  return self
139
149
 
@@ -141,20 +151,28 @@ class RemoteServiceDiscoveryService(LockdownServiceProvider):
141
151
  await self.close()
142
152
 
143
153
  def __repr__(self) -> str:
144
- name_str = ''
154
+ name_str = ""
145
155
  if self.name:
146
- name_str = f' NAME:{self.name}'
147
- return (f'<{self.__class__.__name__} PRODUCT:{self.product_type} VERSION:{self.product_version} '
148
- f'UDID:{self.udid}{name_str}>')
156
+ name_str = f" NAME:{self.name}"
157
+ return (
158
+ f"<{self.__class__.__name__} PRODUCT:{self.product_type} VERSION:{self.product_version} "
159
+ f"UDID:{self.udid}{name_str}>"
160
+ )
149
161
 
150
162
 
151
163
  async def get_remoted_devices(timeout: float = DEFAULT_BONJOUR_TIMEOUT) -> list[RSDDevice]:
152
164
  result = []
153
165
  for hostname in await browse_remoted(timeout):
154
166
  with RemoteServiceDiscoveryService((hostname, RSD_PORT)) as rsd:
155
- properties = rsd.peer_info['Properties']
156
- result.append(RSDDevice(hostname=hostname, udid=properties['UniqueDeviceID'],
157
- product_type=properties['ProductType'], os_version=properties['OSVersion']))
167
+ properties = rsd.peer_info["Properties"]
168
+ result.append(
169
+ RSDDevice(
170
+ hostname=hostname,
171
+ udid=properties["UniqueDeviceID"],
172
+ product_type=properties["ProductType"],
173
+ os_version=properties["OSVersion"],
174
+ )
175
+ )
158
176
  return result
159
177
 
160
178
 
@@ -1,19 +1,34 @@
1
1
  import asyncio
2
+ import contextlib
2
3
  import sys
3
4
  from asyncio import IncompleteReadError
4
- from typing import AsyncIterable, Optional
5
+ from collections.abc import AsyncIterable
6
+ from typing import Optional
5
7
 
6
8
  import IPython
7
9
  import nest_asyncio
8
10
  from construct import StreamError
9
- from hyperframe.frame import DataFrame, Frame, GoAwayFrame, HeadersFrame, RstStreamFrame, SettingsFrame, \
10
- WindowUpdateFrame
11
+ from hyperframe.frame import (
12
+ DataFrame,
13
+ Frame,
14
+ GoAwayFrame,
15
+ HeadersFrame,
16
+ RstStreamFrame,
17
+ SettingsFrame,
18
+ WindowUpdateFrame,
19
+ )
11
20
  from pygments import formatters, highlight, lexers
12
21
  from traitlets.config import Config
13
22
 
14
23
  from pymobiledevice3.exceptions import ProtocolError, StreamClosedError
15
- from pymobiledevice3.remote.xpc_message import XpcFlags, XpcInt64Type, XpcUInt64Type, XpcWrapper, create_xpc_wrapper, \
16
- decode_xpc_object
24
+ from pymobiledevice3.remote.xpc_message import (
25
+ XpcFlags,
26
+ XpcInt64Type,
27
+ XpcUInt64Type,
28
+ XpcWrapper,
29
+ create_xpc_wrapper,
30
+ decode_xpc_object,
31
+ )
17
32
 
18
33
  # Extracted by sniffing `remoted` traffic via Wireshark
19
34
  DEFAULT_SETTINGS_MAX_CONCURRENT_STREAMS = 100
@@ -21,7 +36,7 @@ DEFAULT_SETTINGS_INITIAL_WINDOW_SIZE = 1048576
21
36
  DEFAULT_WIN_SIZE_INCR = 983041
22
37
 
23
38
  FRAME_HEADER_SIZE = 9
24
- HTTP2_MAGIC = b'PRI * HTTP/2.0\r\n\r\nSM\r\n\r\n'
39
+ HTTP2_MAGIC = b"PRI * HTTP/2.0\r\n\r\nSM\r\n\r\n"
25
40
 
26
41
  ROOT_CHANNEL = 1
27
42
  REPLY_CHANNEL = 3
@@ -38,14 +53,14 @@ resp = await client.send_receive_request({"Command": "DoSomething"})
38
53
 
39
54
  class RemoteXPCConnection:
40
55
  def __init__(self, address: tuple[str, int]):
41
- self._previous_frame_data = b''
56
+ self._previous_frame_data = b""
42
57
  self.address = address
43
58
  self.next_message_id: dict[int, int] = {ROOT_CHANNEL: 0, REPLY_CHANNEL: 0}
44
59
  self.peer_info = None
45
60
  self._reader: Optional[asyncio.StreamReader] = None
46
61
  self._writer: Optional[asyncio.StreamWriter] = None
47
62
 
48
- async def __aenter__(self) -> 'RemoteXPCConnection':
63
+ async def __aenter__(self) -> "RemoteXPCConnection":
49
64
  await self.connect()
50
65
  return self
51
66
 
@@ -56,7 +71,7 @@ class RemoteXPCConnection:
56
71
  self._reader, self._writer = await asyncio.open_connection(self.address[0], self.address[1])
57
72
  try:
58
73
  await self._do_handshake()
59
- except Exception: # noqa: E722
74
+ except Exception:
60
75
  await self.close()
61
76
  raise
62
77
 
@@ -64,16 +79,15 @@ class RemoteXPCConnection:
64
79
  if self._writer is None:
65
80
  return
66
81
  self._writer.close()
67
- try:
82
+ with contextlib.suppress(ConnectionResetError):
68
83
  await self._writer.wait_closed()
69
- except ConnectionResetError:
70
- pass
71
84
  self._writer = None
72
85
  self._reader = None
73
86
 
74
87
  async def send_request(self, data: dict, wanting_reply: bool = False) -> None:
75
88
  xpc_wrapper = create_xpc_wrapper(
76
- data, message_id=self.next_message_id[ROOT_CHANNEL], wanting_reply=wanting_reply)
89
+ data, message_id=self.next_message_id[ROOT_CHANNEL], wanting_reply=wanting_reply
90
+ )
77
91
  self._writer.write(DataFrame(stream_id=ROOT_CHANNEL, data=xpc_wrapper).serialize())
78
92
  await self._writer.drain()
79
93
 
@@ -84,7 +98,7 @@ class RemoteXPCConnection:
84
98
  while size < total_size:
85
99
  frame = await self._receive_next_data_frame()
86
100
 
87
- if 'END_STREAM' in frame.flags:
101
+ if "END_STREAM" in frame.flags:
88
102
  continue
89
103
 
90
104
  if frame.stream_id != stream_id:
@@ -92,12 +106,12 @@ class RemoteXPCConnection:
92
106
  if xpc_wrapper.flags.FILE_TX_STREAM_REQUEST:
93
107
  continue
94
108
 
95
- assert frame.stream_id == stream_id, f'got {frame.stream_id} instead of {stream_id}'
109
+ assert frame.stream_id == stream_id, f"got {frame.stream_id} instead of {stream_id}"
96
110
  size += len(frame.data)
97
111
  yield frame.data
98
112
 
99
113
  async def receive_file(self, total_size: int) -> bytes:
100
- buf = b''
114
+ buf = b""
101
115
  async for chunk in self.iter_file_chunks(total_size):
102
116
  buf += chunk
103
117
  return buf
@@ -107,7 +121,7 @@ class RemoteXPCConnection:
107
121
  frame = await self._receive_next_data_frame()
108
122
  try:
109
123
  xpc_message = XpcWrapper.parse(self._previous_frame_data + frame.data).message
110
- self._previous_frame_data = b''
124
+ self._previous_frame_data = b""
111
125
  except StreamError:
112
126
  self._previous_frame_data += frame.data
113
127
  continue
@@ -124,48 +138,56 @@ class RemoteXPCConnection:
124
138
 
125
139
  def shell(self) -> None:
126
140
  nest_asyncio.apply(asyncio.get_running_loop())
127
- sys.argv = ['a']
141
+ sys.argv = ["a"]
128
142
  config = Config()
129
- config.InteractiveShellApp.exec_lines = ['%autoawait asyncio']
130
- print(highlight(SHELL_USAGE, lexers.PythonLexer(),
131
- formatters.Terminal256Formatter(style='native')))
132
- IPython.start_ipython(config=config, user_ns={
133
- 'client': self,
134
- 'XpcInt64Type': XpcInt64Type,
135
- 'XpcUInt64Type': XpcUInt64Type,
136
- })
143
+ config.InteractiveShellApp.exec_lines = ["%autoawait asyncio"]
144
+ print(highlight(SHELL_USAGE, lexers.PythonLexer(), formatters.Terminal256Formatter(style="native")))
145
+ IPython.start_ipython(
146
+ config=config,
147
+ user_ns={
148
+ "client": self,
149
+ "XpcInt64Type": XpcInt64Type,
150
+ "XpcUInt64Type": XpcUInt64Type,
151
+ },
152
+ )
137
153
 
138
154
  async def _do_handshake(self) -> None:
139
155
  self._writer.write(HTTP2_MAGIC)
140
156
  await self._writer.drain()
141
157
 
142
158
  # send h2 headers
143
- await self._send_frame(SettingsFrame(settings={
144
- SettingsFrame.MAX_CONCURRENT_STREAMS: DEFAULT_SETTINGS_MAX_CONCURRENT_STREAMS,
145
- SettingsFrame.INITIAL_WINDOW_SIZE: DEFAULT_SETTINGS_INITIAL_WINDOW_SIZE,
146
- }))
159
+ await self._send_frame(
160
+ SettingsFrame(
161
+ settings={
162
+ SettingsFrame.MAX_CONCURRENT_STREAMS: DEFAULT_SETTINGS_MAX_CONCURRENT_STREAMS,
163
+ SettingsFrame.INITIAL_WINDOW_SIZE: DEFAULT_SETTINGS_INITIAL_WINDOW_SIZE,
164
+ }
165
+ )
166
+ )
147
167
  await self._send_frame(WindowUpdateFrame(stream_id=0, window_increment=DEFAULT_WIN_SIZE_INCR))
148
- await self._send_frame(HeadersFrame(stream_id=ROOT_CHANNEL, flags=['END_HEADERS']))
168
+ await self._send_frame(HeadersFrame(stream_id=ROOT_CHANNEL, flags=["END_HEADERS"]))
149
169
 
150
170
  # send first actual requests
151
171
  await self.send_request({})
152
- await self._send_frame(DataFrame(stream_id=ROOT_CHANNEL,
153
- data=XpcWrapper.build({'size': 0, 'flags': 0x0201, 'payload': None})))
172
+ await self._send_frame(
173
+ DataFrame(stream_id=ROOT_CHANNEL, data=XpcWrapper.build({"size": 0, "flags": 0x0201, "payload": None}))
174
+ )
154
175
  self.next_message_id[ROOT_CHANNEL] += 1
155
176
  await self._open_channel(REPLY_CHANNEL, XpcFlags.INIT_HANDSHAKE)
156
177
  self.next_message_id[REPLY_CHANNEL] += 1
157
178
 
158
179
  settings_frame = await asyncio.wait_for(self._receive_frame(), FIRST_REPLY_TIMEOUT)
159
180
  if not isinstance(settings_frame, SettingsFrame):
160
- raise ProtocolError(f'Got unexpected frame: {settings_frame} instead of a SettingsFrame')
181
+ raise ProtocolError(f"Got unexpected frame: {settings_frame} instead of a SettingsFrame")
161
182
 
162
- await self._send_frame(SettingsFrame(flags=['ACK']))
183
+ await self._send_frame(SettingsFrame(flags=["ACK"]))
163
184
 
164
185
  async def _open_channel(self, stream_id: int, flags: int) -> None:
165
186
  flags |= XpcFlags.ALWAYS_SET
166
- await self._send_frame(HeadersFrame(stream_id=stream_id, flags=['END_HEADERS']))
187
+ await self._send_frame(HeadersFrame(stream_id=stream_id, flags=["END_HEADERS"]))
167
188
  await self._send_frame(
168
- DataFrame(stream_id=stream_id, data=XpcWrapper.build({'size': 0, 'flags': flags, 'payload': None})))
189
+ DataFrame(stream_id=stream_id, data=XpcWrapper.build({"size": 0, "flags": flags, "payload": None}))
190
+ )
169
191
 
170
192
  async def _send_frame(self, frame: Frame) -> None:
171
193
  self._writer.write(frame.serialize())
@@ -176,9 +198,9 @@ class RemoteXPCConnection:
176
198
  frame = await self._receive_frame()
177
199
 
178
200
  if isinstance(frame, GoAwayFrame):
179
- raise StreamClosedError(f'Got {frame}')
201
+ raise StreamClosedError(f"Got {frame}")
180
202
  if isinstance(frame, RstStreamFrame):
181
- raise StreamClosedError(f'Got {frame}')
203
+ raise StreamClosedError(f"Got {frame}")
182
204
  if not isinstance(frame, DataFrame):
183
205
  continue
184
206
 
@@ -195,11 +217,11 @@ class RemoteXPCConnection:
195
217
  return frame
196
218
 
197
219
  async def _recvall(self, size: int) -> bytes:
198
- data = b''
220
+ data = b""
199
221
  while len(data) < size:
200
222
  try:
201
223
  chunk = await self._reader.readexactly(size - len(data))
202
- except IncompleteReadError:
203
- raise ConnectionAbortedError()
224
+ except IncompleteReadError as e:
225
+ raise ConnectionAbortedError() from e
204
226
  data += chunk
205
227
  return data