pymobiledevice3 5.0.1__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 +442 -421
  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.1.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.1.dist-info/RECORD +0 -173
  140. {pymobiledevice3-5.0.1.dist-info → pymobiledevice3-5.0.2.dist-info}/WHEEL +0 -0
  141. {pymobiledevice3-5.0.1.dist-info → pymobiledevice3-5.0.2.dist-info}/entry_points.txt +0 -0
  142. {pymobiledevice3-5.0.1.dist-info → pymobiledevice3-5.0.2.dist-info}/licenses/LICENSE +0 -0
  143. {pymobiledevice3-5.0.1.dist-info → pymobiledevice3-5.0.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,4 +1,5 @@
1
1
  import asyncio
2
+ import contextlib
2
3
  import sys
3
4
  from asyncio import IncompleteReadError
4
5
  from typing import AsyncIterable, Optional
@@ -6,14 +7,27 @@ from typing import AsyncIterable, Optional
6
7
  import IPython
7
8
  import nest_asyncio
8
9
  from construct import StreamError
9
- from hyperframe.frame import DataFrame, Frame, GoAwayFrame, HeadersFrame, RstStreamFrame, SettingsFrame, \
10
- WindowUpdateFrame
10
+ from hyperframe.frame import (
11
+ DataFrame,
12
+ Frame,
13
+ GoAwayFrame,
14
+ HeadersFrame,
15
+ RstStreamFrame,
16
+ SettingsFrame,
17
+ WindowUpdateFrame,
18
+ )
11
19
  from pygments import formatters, highlight, lexers
12
20
  from traitlets.config import Config
13
21
 
14
22
  from pymobiledevice3.exceptions import ProtocolError, StreamClosedError
15
- from pymobiledevice3.remote.xpc_message import XpcFlags, XpcInt64Type, XpcUInt64Type, XpcWrapper, create_xpc_wrapper, \
16
- decode_xpc_object
23
+ from pymobiledevice3.remote.xpc_message import (
24
+ XpcFlags,
25
+ XpcInt64Type,
26
+ XpcUInt64Type,
27
+ XpcWrapper,
28
+ create_xpc_wrapper,
29
+ decode_xpc_object,
30
+ )
17
31
 
18
32
  # Extracted by sniffing `remoted` traffic via Wireshark
19
33
  DEFAULT_SETTINGS_MAX_CONCURRENT_STREAMS = 100
@@ -21,7 +35,7 @@ DEFAULT_SETTINGS_INITIAL_WINDOW_SIZE = 1048576
21
35
  DEFAULT_WIN_SIZE_INCR = 983041
22
36
 
23
37
  FRAME_HEADER_SIZE = 9
24
- HTTP2_MAGIC = b'PRI * HTTP/2.0\r\n\r\nSM\r\n\r\n'
38
+ HTTP2_MAGIC = b"PRI * HTTP/2.0\r\n\r\nSM\r\n\r\n"
25
39
 
26
40
  ROOT_CHANNEL = 1
27
41
  REPLY_CHANNEL = 3
@@ -38,14 +52,14 @@ resp = await client.send_receive_request({"Command": "DoSomething"})
38
52
 
39
53
  class RemoteXPCConnection:
40
54
  def __init__(self, address: tuple[str, int]):
41
- self._previous_frame_data = b''
55
+ self._previous_frame_data = b""
42
56
  self.address = address
43
57
  self.next_message_id: dict[int, int] = {ROOT_CHANNEL: 0, REPLY_CHANNEL: 0}
44
58
  self.peer_info = None
45
59
  self._reader: Optional[asyncio.StreamReader] = None
46
60
  self._writer: Optional[asyncio.StreamWriter] = None
47
61
 
48
- async def __aenter__(self) -> 'RemoteXPCConnection':
62
+ async def __aenter__(self) -> "RemoteXPCConnection":
49
63
  await self.connect()
50
64
  return self
51
65
 
@@ -56,7 +70,7 @@ class RemoteXPCConnection:
56
70
  self._reader, self._writer = await asyncio.open_connection(self.address[0], self.address[1])
57
71
  try:
58
72
  await self._do_handshake()
59
- except Exception: # noqa: E722
73
+ except Exception:
60
74
  await self.close()
61
75
  raise
62
76
 
@@ -64,16 +78,15 @@ class RemoteXPCConnection:
64
78
  if self._writer is None:
65
79
  return
66
80
  self._writer.close()
67
- try:
81
+ with contextlib.suppress(ConnectionResetError):
68
82
  await self._writer.wait_closed()
69
- except ConnectionResetError:
70
- pass
71
83
  self._writer = None
72
84
  self._reader = None
73
85
 
74
86
  async def send_request(self, data: dict, wanting_reply: bool = False) -> None:
75
87
  xpc_wrapper = create_xpc_wrapper(
76
- data, message_id=self.next_message_id[ROOT_CHANNEL], wanting_reply=wanting_reply)
88
+ data, message_id=self.next_message_id[ROOT_CHANNEL], wanting_reply=wanting_reply
89
+ )
77
90
  self._writer.write(DataFrame(stream_id=ROOT_CHANNEL, data=xpc_wrapper).serialize())
78
91
  await self._writer.drain()
79
92
 
@@ -84,7 +97,7 @@ class RemoteXPCConnection:
84
97
  while size < total_size:
85
98
  frame = await self._receive_next_data_frame()
86
99
 
87
- if 'END_STREAM' in frame.flags:
100
+ if "END_STREAM" in frame.flags:
88
101
  continue
89
102
 
90
103
  if frame.stream_id != stream_id:
@@ -92,12 +105,12 @@ class RemoteXPCConnection:
92
105
  if xpc_wrapper.flags.FILE_TX_STREAM_REQUEST:
93
106
  continue
94
107
 
95
- assert frame.stream_id == stream_id, f'got {frame.stream_id} instead of {stream_id}'
108
+ assert frame.stream_id == stream_id, f"got {frame.stream_id} instead of {stream_id}"
96
109
  size += len(frame.data)
97
110
  yield frame.data
98
111
 
99
112
  async def receive_file(self, total_size: int) -> bytes:
100
- buf = b''
113
+ buf = b""
101
114
  async for chunk in self.iter_file_chunks(total_size):
102
115
  buf += chunk
103
116
  return buf
@@ -107,7 +120,7 @@ class RemoteXPCConnection:
107
120
  frame = await self._receive_next_data_frame()
108
121
  try:
109
122
  xpc_message = XpcWrapper.parse(self._previous_frame_data + frame.data).message
110
- self._previous_frame_data = b''
123
+ self._previous_frame_data = b""
111
124
  except StreamError:
112
125
  self._previous_frame_data += frame.data
113
126
  continue
@@ -124,48 +137,56 @@ class RemoteXPCConnection:
124
137
 
125
138
  def shell(self) -> None:
126
139
  nest_asyncio.apply(asyncio.get_running_loop())
127
- sys.argv = ['a']
140
+ sys.argv = ["a"]
128
141
  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
- })
142
+ config.InteractiveShellApp.exec_lines = ["%autoawait asyncio"]
143
+ print(highlight(SHELL_USAGE, lexers.PythonLexer(), formatters.Terminal256Formatter(style="native")))
144
+ IPython.start_ipython(
145
+ config=config,
146
+ user_ns={
147
+ "client": self,
148
+ "XpcInt64Type": XpcInt64Type,
149
+ "XpcUInt64Type": XpcUInt64Type,
150
+ },
151
+ )
137
152
 
138
153
  async def _do_handshake(self) -> None:
139
154
  self._writer.write(HTTP2_MAGIC)
140
155
  await self._writer.drain()
141
156
 
142
157
  # 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
- }))
158
+ await self._send_frame(
159
+ SettingsFrame(
160
+ settings={
161
+ SettingsFrame.MAX_CONCURRENT_STREAMS: DEFAULT_SETTINGS_MAX_CONCURRENT_STREAMS,
162
+ SettingsFrame.INITIAL_WINDOW_SIZE: DEFAULT_SETTINGS_INITIAL_WINDOW_SIZE,
163
+ }
164
+ )
165
+ )
147
166
  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']))
167
+ await self._send_frame(HeadersFrame(stream_id=ROOT_CHANNEL, flags=["END_HEADERS"]))
149
168
 
150
169
  # send first actual requests
151
170
  await self.send_request({})
152
- await self._send_frame(DataFrame(stream_id=ROOT_CHANNEL,
153
- data=XpcWrapper.build({'size': 0, 'flags': 0x0201, 'payload': None})))
171
+ await self._send_frame(
172
+ DataFrame(stream_id=ROOT_CHANNEL, data=XpcWrapper.build({"size": 0, "flags": 0x0201, "payload": None}))
173
+ )
154
174
  self.next_message_id[ROOT_CHANNEL] += 1
155
175
  await self._open_channel(REPLY_CHANNEL, XpcFlags.INIT_HANDSHAKE)
156
176
  self.next_message_id[REPLY_CHANNEL] += 1
157
177
 
158
178
  settings_frame = await asyncio.wait_for(self._receive_frame(), FIRST_REPLY_TIMEOUT)
159
179
  if not isinstance(settings_frame, SettingsFrame):
160
- raise ProtocolError(f'Got unexpected frame: {settings_frame} instead of a SettingsFrame')
180
+ raise ProtocolError(f"Got unexpected frame: {settings_frame} instead of a SettingsFrame")
161
181
 
162
- await self._send_frame(SettingsFrame(flags=['ACK']))
182
+ await self._send_frame(SettingsFrame(flags=["ACK"]))
163
183
 
164
184
  async def _open_channel(self, stream_id: int, flags: int) -> None:
165
185
  flags |= XpcFlags.ALWAYS_SET
166
- await self._send_frame(HeadersFrame(stream_id=stream_id, flags=['END_HEADERS']))
186
+ await self._send_frame(HeadersFrame(stream_id=stream_id, flags=["END_HEADERS"]))
167
187
  await self._send_frame(
168
- DataFrame(stream_id=stream_id, data=XpcWrapper.build({'size': 0, 'flags': flags, 'payload': None})))
188
+ DataFrame(stream_id=stream_id, data=XpcWrapper.build({"size": 0, "flags": flags, "payload": None}))
189
+ )
169
190
 
170
191
  async def _send_frame(self, frame: Frame) -> None:
171
192
  self._writer.write(frame.serialize())
@@ -176,9 +197,9 @@ class RemoteXPCConnection:
176
197
  frame = await self._receive_frame()
177
198
 
178
199
  if isinstance(frame, GoAwayFrame):
179
- raise StreamClosedError(f'Got {frame}')
200
+ raise StreamClosedError(f"Got {frame}")
180
201
  if isinstance(frame, RstStreamFrame):
181
- raise StreamClosedError(f'Got {frame}')
202
+ raise StreamClosedError(f"Got {frame}")
182
203
  if not isinstance(frame, DataFrame):
183
204
  continue
184
205
 
@@ -195,11 +216,11 @@ class RemoteXPCConnection:
195
216
  return frame
196
217
 
197
218
  async def _recvall(self, size: int) -> bytes:
198
- data = b''
219
+ data = b""
199
220
  while len(data) < size:
200
221
  try:
201
222
  chunk = await self._reader.readexactly(size - len(data))
202
- except IncompleteReadError:
203
- raise ConnectionAbortedError()
223
+ except IncompleteReadError as e:
224
+ raise ConnectionAbortedError() from e
204
225
  data += chunk
205
226
  return data