pymobiledevice3 5.0.0__py3-none-any.whl → 5.0.2__py3-none-any.whl
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Potentially problematic release.
This version of pymobiledevice3 might be problematic. Click here for more details.
- misc/plist_sniffer.py +15 -15
- misc/remotexpc_sniffer.py +29 -28
- pymobiledevice3/__main__.py +128 -102
- pymobiledevice3/_version.py +2 -2
- pymobiledevice3/bonjour.py +26 -49
- pymobiledevice3/ca.py +32 -24
- pymobiledevice3/cli/activation.py +7 -7
- pymobiledevice3/cli/afc.py +19 -19
- pymobiledevice3/cli/amfi.py +4 -4
- pymobiledevice3/cli/apps.py +51 -39
- pymobiledevice3/cli/backup.py +58 -32
- pymobiledevice3/cli/bonjour.py +25 -18
- pymobiledevice3/cli/cli_common.py +112 -81
- pymobiledevice3/cli/companion_proxy.py +4 -4
- pymobiledevice3/cli/completions.py +10 -10
- pymobiledevice3/cli/crash.py +37 -31
- pymobiledevice3/cli/developer.py +602 -520
- pymobiledevice3/cli/diagnostics.py +38 -33
- pymobiledevice3/cli/lockdown.py +79 -74
- pymobiledevice3/cli/mounter.py +85 -68
- pymobiledevice3/cli/notification.py +10 -10
- pymobiledevice3/cli/pcap.py +19 -14
- pymobiledevice3/cli/power_assertion.py +12 -10
- pymobiledevice3/cli/processes.py +10 -10
- pymobiledevice3/cli/profile.py +88 -77
- pymobiledevice3/cli/provision.py +17 -17
- pymobiledevice3/cli/remote.py +186 -110
- pymobiledevice3/cli/restore.py +43 -40
- pymobiledevice3/cli/springboard.py +30 -28
- pymobiledevice3/cli/syslog.py +85 -58
- pymobiledevice3/cli/usbmux.py +21 -20
- pymobiledevice3/cli/version.py +3 -2
- pymobiledevice3/cli/webinspector.py +157 -79
- pymobiledevice3/common.py +1 -1
- pymobiledevice3/exceptions.py +154 -60
- pymobiledevice3/irecv.py +49 -53
- pymobiledevice3/irecv_devices.py +1489 -492
- pymobiledevice3/lockdown.py +394 -241
- pymobiledevice3/lockdown_service_provider.py +5 -7
- pymobiledevice3/osu/os_utils.py +18 -9
- pymobiledevice3/osu/posix_util.py +28 -15
- pymobiledevice3/osu/win_util.py +14 -8
- pymobiledevice3/pair_records.py +19 -19
- pymobiledevice3/remote/common.py +4 -4
- pymobiledevice3/remote/core_device/app_service.py +94 -67
- pymobiledevice3/remote/core_device/core_device_service.py +17 -14
- pymobiledevice3/remote/core_device/device_info.py +5 -5
- pymobiledevice3/remote/core_device/diagnostics_service.py +10 -8
- pymobiledevice3/remote/core_device/file_service.py +47 -33
- pymobiledevice3/remote/remote_service_discovery.py +53 -35
- pymobiledevice3/remote/remotexpc.py +62 -41
- pymobiledevice3/remote/tunnel_service.py +371 -293
- pymobiledevice3/remote/utils.py +12 -11
- pymobiledevice3/remote/xpc_message.py +145 -125
- pymobiledevice3/resources/dsc_uuid_map.py +19 -19
- pymobiledevice3/resources/firmware_notifications.py +16 -16
- pymobiledevice3/restore/asr.py +27 -27
- pymobiledevice3/restore/base_restore.py +90 -47
- pymobiledevice3/restore/consts.py +87 -66
- pymobiledevice3/restore/device.py +11 -11
- pymobiledevice3/restore/fdr.py +46 -46
- pymobiledevice3/restore/ftab.py +19 -19
- pymobiledevice3/restore/img4.py +130 -133
- pymobiledevice3/restore/mbn.py +35 -54
- pymobiledevice3/restore/recovery.py +125 -135
- pymobiledevice3/restore/restore.py +524 -523
- pymobiledevice3/restore/restore_options.py +122 -115
- pymobiledevice3/restore/restored_client.py +25 -22
- pymobiledevice3/restore/tss.py +378 -270
- pymobiledevice3/service_connection.py +50 -46
- pymobiledevice3/services/accessibilityaudit.py +136 -126
- pymobiledevice3/services/afc.py +350 -291
- pymobiledevice3/services/amfi.py +21 -18
- pymobiledevice3/services/companion.py +23 -19
- pymobiledevice3/services/crash_reports.py +60 -46
- pymobiledevice3/services/debugserver_applist.py +3 -3
- pymobiledevice3/services/device_arbitration.py +8 -8
- pymobiledevice3/services/device_link.py +55 -47
- pymobiledevice3/services/diagnostics.py +971 -968
- pymobiledevice3/services/dtfetchsymbols.py +8 -8
- pymobiledevice3/services/dvt/dvt_secure_socket_proxy.py +4 -4
- pymobiledevice3/services/dvt/dvt_testmanaged_proxy.py +4 -4
- pymobiledevice3/services/dvt/instruments/activity_trace_tap.py +85 -74
- pymobiledevice3/services/dvt/instruments/application_listing.py +2 -3
- pymobiledevice3/services/dvt/instruments/condition_inducer.py +7 -6
- pymobiledevice3/services/dvt/instruments/core_profile_session_tap.py +466 -384
- pymobiledevice3/services/dvt/instruments/device_info.py +11 -11
- pymobiledevice3/services/dvt/instruments/energy_monitor.py +1 -1
- pymobiledevice3/services/dvt/instruments/graphics.py +1 -1
- pymobiledevice3/services/dvt/instruments/location_simulation.py +1 -1
- pymobiledevice3/services/dvt/instruments/location_simulation_base.py +10 -10
- pymobiledevice3/services/dvt/instruments/network_monitor.py +17 -17
- pymobiledevice3/services/dvt/instruments/notifications.py +1 -1
- pymobiledevice3/services/dvt/instruments/process_control.py +25 -10
- pymobiledevice3/services/dvt/instruments/screenshot.py +2 -2
- pymobiledevice3/services/dvt/instruments/sysmontap.py +15 -15
- pymobiledevice3/services/dvt/testmanaged/xcuitest.py +40 -50
- pymobiledevice3/services/file_relay.py +10 -10
- pymobiledevice3/services/heartbeat.py +8 -7
- pymobiledevice3/services/house_arrest.py +12 -15
- pymobiledevice3/services/installation_proxy.py +119 -100
- pymobiledevice3/services/lockdown_service.py +12 -5
- pymobiledevice3/services/misagent.py +22 -19
- pymobiledevice3/services/mobile_activation.py +84 -72
- pymobiledevice3/services/mobile_config.py +330 -301
- pymobiledevice3/services/mobile_image_mounter.py +137 -116
- pymobiledevice3/services/mobilebackup2.py +188 -150
- pymobiledevice3/services/notification_proxy.py +11 -11
- pymobiledevice3/services/os_trace.py +69 -51
- pymobiledevice3/services/pcapd.py +306 -306
- pymobiledevice3/services/power_assertion.py +10 -9
- pymobiledevice3/services/preboard.py +4 -4
- pymobiledevice3/services/remote_fetch_symbols.py +16 -14
- pymobiledevice3/services/remote_server.py +176 -146
- pymobiledevice3/services/restore_service.py +16 -16
- pymobiledevice3/services/screenshot.py +13 -10
- pymobiledevice3/services/simulate_location.py +7 -7
- pymobiledevice3/services/springboard.py +15 -15
- pymobiledevice3/services/syslog.py +5 -5
- pymobiledevice3/services/web_protocol/alert.py +3 -3
- pymobiledevice3/services/web_protocol/automation_session.py +180 -176
- pymobiledevice3/services/web_protocol/cdp_screencast.py +44 -36
- pymobiledevice3/services/web_protocol/cdp_server.py +19 -19
- pymobiledevice3/services/web_protocol/cdp_target.py +411 -373
- pymobiledevice3/services/web_protocol/driver.py +47 -45
- pymobiledevice3/services/web_protocol/element.py +74 -63
- pymobiledevice3/services/web_protocol/inspector_session.py +106 -102
- pymobiledevice3/services/web_protocol/selenium_api.py +2 -2
- pymobiledevice3/services/web_protocol/session_protocol.py +15 -10
- pymobiledevice3/services/web_protocol/switch_to.py +11 -12
- pymobiledevice3/services/webinspector.py +127 -116
- pymobiledevice3/tcp_forwarder.py +35 -22
- pymobiledevice3/tunneld/api.py +20 -15
- pymobiledevice3/tunneld/server.py +212 -133
- pymobiledevice3/usbmux.py +183 -138
- pymobiledevice3/utils.py +14 -11
- {pymobiledevice3-5.0.0.dist-info → pymobiledevice3-5.0.2.dist-info}/METADATA +1 -1
- pymobiledevice3-5.0.2.dist-info/RECORD +173 -0
- pymobiledevice3-5.0.0.dist-info/RECORD +0 -173
- {pymobiledevice3-5.0.0.dist-info → pymobiledevice3-5.0.2.dist-info}/WHEEL +0 -0
- {pymobiledevice3-5.0.0.dist-info → pymobiledevice3-5.0.2.dist-info}/entry_points.txt +0 -0
- {pymobiledevice3-5.0.0.dist-info → pymobiledevice3-5.0.2.dist-info}/licenses/LICENSE +0 -0
- {pymobiledevice3-5.0.0.dist-info → pymobiledevice3-5.0.2.dist-info}/top_level.txt +0 -0
|
@@ -1,5 +1,6 @@
|
|
|
1
1
|
#!/usr/bin/env python3
|
|
2
2
|
import contextlib
|
|
3
|
+
from typing import Optional
|
|
3
4
|
|
|
4
5
|
from pymobiledevice3.lockdown import LockdownClient
|
|
5
6
|
from pymobiledevice3.lockdown_service_provider import LockdownServiceProvider
|
|
@@ -7,8 +8,8 @@ from pymobiledevice3.services.lockdown_service import LockdownService
|
|
|
7
8
|
|
|
8
9
|
|
|
9
10
|
class PowerAssertionService(LockdownService):
|
|
10
|
-
RSD_SERVICE_NAME =
|
|
11
|
-
SERVICE_NAME =
|
|
11
|
+
RSD_SERVICE_NAME = "com.apple.mobile.assertion_agent.shim.remote"
|
|
12
|
+
SERVICE_NAME = "com.apple.mobile.assertion_agent"
|
|
12
13
|
|
|
13
14
|
def __init__(self, lockdown: LockdownServiceProvider):
|
|
14
15
|
if isinstance(lockdown, LockdownClient):
|
|
@@ -17,17 +18,17 @@ class PowerAssertionService(LockdownService):
|
|
|
17
18
|
super().__init__(lockdown, self.RSD_SERVICE_NAME)
|
|
18
19
|
|
|
19
20
|
@contextlib.contextmanager
|
|
20
|
-
def create_power_assertion(self, type_: str, name: str, timeout: float, details: str = None):
|
|
21
|
-
"""
|
|
21
|
+
def create_power_assertion(self, type_: str, name: str, timeout: float, details: Optional[str] = None):
|
|
22
|
+
"""Trigger IOPMAssertionCreateWithName"""
|
|
22
23
|
msg = {
|
|
23
|
-
|
|
24
|
-
|
|
25
|
-
|
|
26
|
-
|
|
24
|
+
"CommandKey": "CommandCreateAssertion",
|
|
25
|
+
"AssertionTypeKey": type_,
|
|
26
|
+
"AssertionNameKey": name,
|
|
27
|
+
"AssertionTimeoutKey": timeout,
|
|
27
28
|
}
|
|
28
29
|
|
|
29
30
|
if details is not None:
|
|
30
|
-
msg[
|
|
31
|
+
msg["AssertionDetailKey"] = details
|
|
31
32
|
|
|
32
33
|
self.service.send_recv_plist(msg)
|
|
33
34
|
yield
|
|
@@ -6,8 +6,8 @@ from pymobiledevice3.services.lockdown_service import LockdownService
|
|
|
6
6
|
|
|
7
7
|
|
|
8
8
|
class PreboardService(LockdownService):
|
|
9
|
-
RSD_SERVICE_NAME =
|
|
10
|
-
SERVICE_NAME =
|
|
9
|
+
RSD_SERVICE_NAME = "com.apple.preboardservice_v2.shim.remote"
|
|
10
|
+
SERVICE_NAME = "com.apple.preboardservice_v2"
|
|
11
11
|
|
|
12
12
|
def __init__(self, lockdown: LockdownServiceProvider):
|
|
13
13
|
if isinstance(lockdown, LockdownClient):
|
|
@@ -16,7 +16,7 @@ class PreboardService(LockdownService):
|
|
|
16
16
|
super().__init__(lockdown, self.RSD_SERVICE_NAME)
|
|
17
17
|
|
|
18
18
|
def create_stashbag(self, manifest):
|
|
19
|
-
return self.service.send_recv_plist({
|
|
19
|
+
return self.service.send_recv_plist({"Command": "CreateStashbag", "Manifest": manifest})
|
|
20
20
|
|
|
21
21
|
def commit(self, manifest):
|
|
22
|
-
return self.service.send_recv_plist({
|
|
22
|
+
return self.service.send_recv_plist({"Command": "CommitStashbag", "Manifest": manifest})
|
|
@@ -15,32 +15,34 @@ class DSCFile:
|
|
|
15
15
|
|
|
16
16
|
|
|
17
17
|
class RemoteFetchSymbolsService(RemoteService):
|
|
18
|
-
SERVICE_NAME =
|
|
18
|
+
SERVICE_NAME = "com.apple.dt.remoteFetchSymbols"
|
|
19
19
|
|
|
20
20
|
def __init__(self, rsd: RemoteServiceDiscoveryService):
|
|
21
21
|
super().__init__(rsd, self.SERVICE_NAME)
|
|
22
22
|
|
|
23
23
|
async def get_dsc_file_list(self) -> list[DSCFile]:
|
|
24
24
|
files: list[DSCFile] = []
|
|
25
|
-
response = await self.service.send_receive_request({
|
|
26
|
-
|
|
27
|
-
|
|
25
|
+
response = await self.service.send_receive_request({
|
|
26
|
+
"XPCDictionary_sideChannel": uuid.uuid4(),
|
|
27
|
+
"DSCFilePaths": [],
|
|
28
|
+
})
|
|
29
|
+
file_count = response["DSCFilePaths"]
|
|
30
|
+
for _i in range(file_count):
|
|
28
31
|
response = await self.service.receive_response()
|
|
29
|
-
response = response[
|
|
30
|
-
file_transfer = response[
|
|
31
|
-
expected_length = file_transfer[
|
|
32
|
-
file_path = response[
|
|
32
|
+
response = response["DSCFilePaths"]
|
|
33
|
+
file_transfer = response["fileTransfer"]
|
|
34
|
+
expected_length = file_transfer["expectedLength"]
|
|
35
|
+
file_path = response["filePath"]
|
|
33
36
|
files.append(DSCFile(file_path=file_path, file_size=expected_length))
|
|
34
37
|
return files
|
|
35
38
|
|
|
36
39
|
async def download(self, out: Path) -> None:
|
|
37
40
|
files = await self.get_dsc_file_list()
|
|
38
41
|
for i, file in enumerate(files):
|
|
39
|
-
self.logger.info(f
|
|
42
|
+
self.logger.info(f"Downloading {file}")
|
|
40
43
|
out_file = out / file.file_path[1:] # trim the "/" prefix
|
|
41
44
|
out_file.parent.mkdir(parents=True, exist_ok=True)
|
|
42
|
-
with open(out_file,
|
|
43
|
-
|
|
44
|
-
|
|
45
|
-
|
|
46
|
-
pb.update(len(chunk))
|
|
45
|
+
with open(out_file, "wb") as f, tqdm(total=files[i].file_size, dynamic_ncols=True) as pb:
|
|
46
|
+
async for chunk in self.service.iter_file_chunks(files[i].file_size, file_idx=i):
|
|
47
|
+
f.write(chunk)
|
|
48
|
+
pb.update(len(chunk))
|
|
@@ -1,3 +1,4 @@
|
|
|
1
|
+
import contextlib
|
|
1
2
|
import copy
|
|
2
3
|
import io
|
|
3
4
|
import os
|
|
@@ -6,18 +7,33 @@ import uuid
|
|
|
6
7
|
from functools import partial
|
|
7
8
|
from pprint import pprint
|
|
8
9
|
from queue import Empty, Queue
|
|
10
|
+
from typing import ClassVar, Optional
|
|
9
11
|
|
|
10
12
|
import IPython
|
|
11
13
|
from bpylist2 import archiver
|
|
12
|
-
from construct import
|
|
13
|
-
|
|
14
|
+
from construct import (
|
|
15
|
+
Adapter,
|
|
16
|
+
Const,
|
|
17
|
+
Default,
|
|
18
|
+
GreedyBytes,
|
|
19
|
+
GreedyRange,
|
|
20
|
+
Int16ul,
|
|
21
|
+
Int32sl,
|
|
22
|
+
Int32ul,
|
|
23
|
+
Int64ul,
|
|
24
|
+
Prefixed,
|
|
25
|
+
Select,
|
|
26
|
+
Struct,
|
|
27
|
+
Switch,
|
|
28
|
+
this,
|
|
29
|
+
)
|
|
14
30
|
from pygments import formatters, highlight, lexers
|
|
15
31
|
|
|
16
32
|
from pymobiledevice3.exceptions import DvtException, UnrecognizedSelectorError
|
|
17
33
|
from pymobiledevice3.lockdown_service_provider import LockdownServiceProvider
|
|
18
34
|
from pymobiledevice3.services.lockdown_service import LockdownService
|
|
19
35
|
|
|
20
|
-
SHELL_USAGE =
|
|
36
|
+
SHELL_USAGE = """
|
|
21
37
|
# This shell allows you to send messages to the DVTSecureSocketProxy and receive answers easily.
|
|
22
38
|
# Generally speaking, each channel represents a group of actions.
|
|
23
39
|
# Calling actions is done using a selector and auxiliary (parameters).
|
|
@@ -45,7 +61,7 @@ channel.killPid_(args, expects_reply=False) # Killing a process doesn't require
|
|
|
45
61
|
# In some rare cases, you might want to receive the auxiliary and the selector return value.
|
|
46
62
|
# For that cases you can use the recv_plist method.
|
|
47
63
|
return_value, auxiliary = developer.recv_plist()
|
|
48
|
-
|
|
64
|
+
"""
|
|
49
65
|
|
|
50
66
|
|
|
51
67
|
class BplitAdapter(Adapter):
|
|
@@ -57,29 +73,39 @@ class BplitAdapter(Adapter):
|
|
|
57
73
|
|
|
58
74
|
|
|
59
75
|
message_aux_t_struct = Struct(
|
|
60
|
-
|
|
61
|
-
|
|
62
|
-
|
|
63
|
-
|
|
64
|
-
|
|
65
|
-
|
|
66
|
-
|
|
76
|
+
"magic" / Default(Int64ul, 0x1F0),
|
|
77
|
+
"aux"
|
|
78
|
+
/ Prefixed(
|
|
79
|
+
Int64ul,
|
|
80
|
+
GreedyRange(
|
|
81
|
+
Struct(
|
|
82
|
+
"_empty_dictionary" / Select(Const(0xA, Int32ul), Int32ul),
|
|
83
|
+
"type" / Int32ul,
|
|
84
|
+
"value"
|
|
85
|
+
/ Switch(
|
|
86
|
+
this.type,
|
|
87
|
+
{2: BplitAdapter(Prefixed(Int32ul, GreedyBytes)), 3: Int32ul, 6: Int64ul},
|
|
88
|
+
default=GreedyBytes,
|
|
89
|
+
),
|
|
90
|
+
)
|
|
91
|
+
),
|
|
92
|
+
),
|
|
67
93
|
)
|
|
68
94
|
dtx_message_header_struct = Struct(
|
|
69
|
-
|
|
70
|
-
|
|
71
|
-
|
|
72
|
-
|
|
73
|
-
|
|
74
|
-
|
|
75
|
-
|
|
76
|
-
|
|
77
|
-
|
|
95
|
+
"magic" / Const(0x1F3D5B79, Int32ul),
|
|
96
|
+
"cb" / Int32ul,
|
|
97
|
+
"fragmentId" / Int16ul,
|
|
98
|
+
"fragmentCount" / Int16ul,
|
|
99
|
+
"length" / Int32ul,
|
|
100
|
+
"identifier" / Int32ul,
|
|
101
|
+
"conversationIndex" / Int32ul,
|
|
102
|
+
"channelCode" / Int32sl,
|
|
103
|
+
"expectsReply" / Int32ul,
|
|
78
104
|
)
|
|
79
105
|
dtx_message_payload_header_struct = Struct(
|
|
80
|
-
|
|
81
|
-
|
|
82
|
-
|
|
106
|
+
"flags" / Int32ul,
|
|
107
|
+
"auxiliaryLength" / Int32ul,
|
|
108
|
+
"totalLength" / Int64ul,
|
|
83
109
|
)
|
|
84
110
|
|
|
85
111
|
|
|
@@ -88,25 +114,25 @@ class MessageAux:
|
|
|
88
114
|
self.values = []
|
|
89
115
|
|
|
90
116
|
def append_int(self, value: int):
|
|
91
|
-
self.values.append({
|
|
117
|
+
self.values.append({"type": 3, "value": value})
|
|
92
118
|
return self
|
|
93
119
|
|
|
94
120
|
def append_long(self, value: int):
|
|
95
|
-
self.values.append({
|
|
121
|
+
self.values.append({"type": 6, "value": value})
|
|
96
122
|
return self
|
|
97
123
|
|
|
98
124
|
def append_obj(self, value):
|
|
99
|
-
self.values.append({
|
|
125
|
+
self.values.append({"type": 2, "value": value})
|
|
100
126
|
return self
|
|
101
127
|
|
|
102
128
|
def __bytes__(self):
|
|
103
|
-
return message_aux_t_struct.build(
|
|
129
|
+
return message_aux_t_struct.build({"aux": self.values})
|
|
104
130
|
|
|
105
131
|
|
|
106
132
|
class DTTapMessage:
|
|
107
133
|
@staticmethod
|
|
108
134
|
def decode_archive(archive_obj):
|
|
109
|
-
return archive_obj.decode(
|
|
135
|
+
return archive_obj.decode("DTTapMessagePlist")
|
|
110
136
|
|
|
111
137
|
|
|
112
138
|
class NSNull:
|
|
@@ -122,10 +148,10 @@ class NSError:
|
|
|
122
148
|
|
|
123
149
|
@staticmethod
|
|
124
150
|
def decode_archive(archive_obj):
|
|
125
|
-
user_info = archive_obj.decode(
|
|
126
|
-
if user_info.get(
|
|
151
|
+
user_info = archive_obj.decode("NSUserInfo")
|
|
152
|
+
if user_info.get("NSLocalizedDescription", "").endswith(" - it does not respond to the selector"):
|
|
127
153
|
raise UnrecognizedSelectorError(user_info)
|
|
128
|
-
raise DvtException(archive_obj.decode(
|
|
154
|
+
raise DvtException(archive_obj.decode("NSUserInfo"))
|
|
129
155
|
|
|
130
156
|
|
|
131
157
|
class NSUUID(uuid.UUID):
|
|
@@ -135,11 +161,11 @@ class NSUUID(uuid.UUID):
|
|
|
135
161
|
return NSUUID(bytes=os.urandom(16))
|
|
136
162
|
|
|
137
163
|
def encode_archive(self, archive_obj: archiver.ArchivingObject):
|
|
138
|
-
archive_obj.encode(
|
|
164
|
+
archive_obj.encode("NS.uuidbytes", self.bytes)
|
|
139
165
|
|
|
140
166
|
@staticmethod
|
|
141
167
|
def decode_archive(archive_obj: archiver.ArchivedObject):
|
|
142
|
-
return NSUUID(bytes=archive_obj.decode(
|
|
168
|
+
return NSUUID(bytes=archive_obj.decode("NS.uuidbytes"))
|
|
143
169
|
|
|
144
170
|
|
|
145
171
|
class NSURL:
|
|
@@ -148,74 +174,72 @@ class NSURL:
|
|
|
148
174
|
self.relative = relative
|
|
149
175
|
|
|
150
176
|
def encode_archive(self, archive_obj: archiver.ArchivingObject):
|
|
151
|
-
archive_obj.encode(
|
|
152
|
-
archive_obj.encode(
|
|
177
|
+
archive_obj.encode("NS.base", self.base)
|
|
178
|
+
archive_obj.encode("NS.relative", self.relative)
|
|
153
179
|
|
|
154
180
|
@staticmethod
|
|
155
181
|
def decode_archive(archive_obj: archiver.ArchivedObject):
|
|
156
|
-
return NSURL(archive_obj.decode(
|
|
182
|
+
return NSURL(archive_obj.decode("NS.base"), archive_obj.decode("NS.relative"))
|
|
157
183
|
|
|
158
184
|
|
|
159
185
|
class NSValue:
|
|
160
186
|
@staticmethod
|
|
161
187
|
def decode_archive(archive_obj: archiver.ArchivedObject):
|
|
162
|
-
return archive_obj.decode(
|
|
188
|
+
return archive_obj.decode("NS.rectval")
|
|
163
189
|
|
|
164
190
|
|
|
165
191
|
class NSMutableData:
|
|
166
192
|
@staticmethod
|
|
167
193
|
def decode_archive(archive_obj: archiver.ArchivedObject):
|
|
168
|
-
return archive_obj.decode(
|
|
194
|
+
return archive_obj.decode("NS.data")
|
|
169
195
|
|
|
170
196
|
|
|
171
197
|
class NSMutableString:
|
|
172
198
|
@staticmethod
|
|
173
199
|
def decode_archive(archive_obj: archiver.ArchivedObject):
|
|
174
|
-
return archive_obj.decode(
|
|
200
|
+
return archive_obj.decode("NS.string")
|
|
175
201
|
|
|
176
202
|
|
|
177
203
|
class XCTestConfiguration:
|
|
178
|
-
_default = {
|
|
204
|
+
_default: ClassVar = {
|
|
179
205
|
# 'testBundleURL': UID(3),
|
|
180
206
|
# 'sessionIdentifier': UID(8), # UUID
|
|
181
|
-
|
|
182
|
-
|
|
183
|
-
|
|
184
|
-
|
|
185
|
-
|
|
186
|
-
|
|
187
|
-
|
|
188
|
-
|
|
189
|
-
|
|
190
|
-
|
|
191
|
-
|
|
192
|
-
|
|
193
|
-
|
|
194
|
-
|
|
195
|
-
|
|
196
|
-
|
|
197
|
-
|
|
198
|
-
|
|
199
|
-
|
|
200
|
-
|
|
201
|
-
|
|
202
|
-
|
|
203
|
-
|
|
204
|
-
|
|
205
|
-
|
|
206
|
-
|
|
207
|
-
|
|
208
|
-
|
|
209
|
-
|
|
210
|
-
|
|
211
|
-
|
|
212
|
-
'treatMissingBaselinesAsFailures': False,
|
|
213
|
-
'userAttachmentLifetime': 1
|
|
207
|
+
"aggregateStatisticsBeforeCrash": {"XCSuiteRecordsKey": {}},
|
|
208
|
+
"automationFrameworkPath": "/Developer/Library/PrivateFrameworks/XCTAutomationSupport.framework",
|
|
209
|
+
"baselineFileRelativePath": None,
|
|
210
|
+
"baselineFileURL": None,
|
|
211
|
+
"defaultTestExecutionTimeAllowance": None,
|
|
212
|
+
"disablePerformanceMetrics": False,
|
|
213
|
+
"emitOSLogs": False,
|
|
214
|
+
"formatVersion": plistlib.UID(2), # store in UID
|
|
215
|
+
"gatherLocalizableStringsData": False,
|
|
216
|
+
"initializeForUITesting": True,
|
|
217
|
+
"maximumTestExecutionTimeAllowance": None,
|
|
218
|
+
"productModuleName": "WebDriverAgentRunner", # set to other value is also OK
|
|
219
|
+
"randomExecutionOrderingSeed": None,
|
|
220
|
+
"reportActivities": True,
|
|
221
|
+
"reportResultsToIDE": True,
|
|
222
|
+
"systemAttachmentLifetime": 2,
|
|
223
|
+
"targetApplicationArguments": [], # maybe useless
|
|
224
|
+
"targetApplicationBundleID": None,
|
|
225
|
+
"targetApplicationEnvironment": None,
|
|
226
|
+
"targetApplicationPath": "/whatever-it-does-not-matter/but-should-not-be-empty",
|
|
227
|
+
"testApplicationDependencies": {},
|
|
228
|
+
"testApplicationUserOverrides": None,
|
|
229
|
+
"testBundleRelativePath": None,
|
|
230
|
+
"testExecutionOrdering": 0,
|
|
231
|
+
"testTimeoutsEnabled": False,
|
|
232
|
+
"testsDrivenByIDE": False,
|
|
233
|
+
"testsMustRunOnMainThread": True,
|
|
234
|
+
"testsToRun": None,
|
|
235
|
+
"testsToSkip": None,
|
|
236
|
+
"treatMissingBaselinesAsFailures": False,
|
|
237
|
+
"userAttachmentLifetime": 1,
|
|
214
238
|
}
|
|
215
239
|
|
|
216
240
|
def __init__(self, kv: dict):
|
|
217
|
-
assert
|
|
218
|
-
assert
|
|
241
|
+
assert "testBundleURL" in kv
|
|
242
|
+
assert "sessionIdentifier" in kv
|
|
219
243
|
self._config = copy.deepcopy(self._default)
|
|
220
244
|
self._config.update(kv)
|
|
221
245
|
|
|
@@ -228,27 +252,29 @@ class XCTestConfiguration:
|
|
|
228
252
|
return archive_obj.object
|
|
229
253
|
|
|
230
254
|
|
|
231
|
-
archiver.update_class_map({
|
|
232
|
-
|
|
233
|
-
|
|
234
|
-
|
|
235
|
-
|
|
236
|
-
|
|
237
|
-
|
|
238
|
-
|
|
239
|
-
|
|
240
|
-
|
|
241
|
-
|
|
242
|
-
|
|
243
|
-
|
|
244
|
-
|
|
255
|
+
archiver.update_class_map({
|
|
256
|
+
"DTSysmonTapMessage": DTTapMessage,
|
|
257
|
+
"DTTapHeartbeatMessage": DTTapMessage,
|
|
258
|
+
"DTTapStatusMessage": DTTapMessage,
|
|
259
|
+
"DTKTraceTapMessage": DTTapMessage,
|
|
260
|
+
"DTActivityTraceTapMessage": DTTapMessage,
|
|
261
|
+
"DTTapMessage": DTTapMessage,
|
|
262
|
+
"NSNull": NSNull,
|
|
263
|
+
"NSError": NSError,
|
|
264
|
+
"NSUUID": NSUUID,
|
|
265
|
+
"NSURL": NSURL,
|
|
266
|
+
"NSValue": NSValue,
|
|
267
|
+
"NSMutableData": NSMutableData,
|
|
268
|
+
"NSMutableString": NSMutableString,
|
|
269
|
+
"XCTestConfiguration": XCTestConfiguration,
|
|
270
|
+
})
|
|
245
271
|
|
|
246
|
-
archiver.Archive.inline_types = list(
|
|
272
|
+
archiver.Archive.inline_types = list({*archiver.Archive.inline_types, bytes})
|
|
247
273
|
|
|
248
274
|
|
|
249
275
|
class Channel(int):
|
|
250
276
|
@classmethod
|
|
251
|
-
def create(cls, value: int, service:
|
|
277
|
+
def create(cls, value: int, service: "RemoteServer"):
|
|
252
278
|
channel = cls(value)
|
|
253
279
|
channel._service = service
|
|
254
280
|
return channel
|
|
@@ -270,10 +296,7 @@ class Channel(int):
|
|
|
270
296
|
"""
|
|
271
297
|
Sanitize python name to ObjectiveC name.
|
|
272
298
|
"""
|
|
273
|
-
if name.startswith(
|
|
274
|
-
name = '_' + name[1:].replace('_', ':')
|
|
275
|
-
else:
|
|
276
|
-
name = name.replace('_', ':')
|
|
299
|
+
name = "_" + name[1:].replace("_", ":") if name.startswith("_") else name.replace("_", ":")
|
|
277
300
|
return name
|
|
278
301
|
|
|
279
302
|
def __getitem__(self, item):
|
|
@@ -286,8 +309,8 @@ class Channel(int):
|
|
|
286
309
|
class ChannelFragmenter:
|
|
287
310
|
def __init__(self):
|
|
288
311
|
self._messages = Queue()
|
|
289
|
-
self._packet_data = b
|
|
290
|
-
self._stream_packet_data = b
|
|
312
|
+
self._packet_data = b""
|
|
313
|
+
self._stream_packet_data = b""
|
|
291
314
|
|
|
292
315
|
def get(self):
|
|
293
316
|
return self._messages.get_nowait()
|
|
@@ -298,13 +321,13 @@ class ChannelFragmenter:
|
|
|
298
321
|
if mheader.fragmentId == mheader.fragmentCount - 1:
|
|
299
322
|
# last message
|
|
300
323
|
self._messages.put(self._packet_data)
|
|
301
|
-
self._packet_data = b
|
|
324
|
+
self._packet_data = b""
|
|
302
325
|
else:
|
|
303
326
|
self._stream_packet_data += chunk
|
|
304
327
|
if mheader.fragmentId == mheader.fragmentCount - 1:
|
|
305
328
|
# last message
|
|
306
329
|
self._messages.put(self._stream_packet_data)
|
|
307
|
-
self._stream_packet_data = b
|
|
330
|
+
self._stream_packet_data = b""
|
|
308
331
|
|
|
309
332
|
|
|
310
333
|
class RemoteServer(LockdownService):
|
|
@@ -351,16 +374,22 @@ class RemoteServer(LockdownService):
|
|
|
351
374
|
}
|
|
352
375
|
}
|
|
353
376
|
```
|
|
354
|
-
"""
|
|
377
|
+
"""
|
|
378
|
+
|
|
355
379
|
BROADCAST_CHANNEL = 0
|
|
356
380
|
INSTRUMENTS_MESSAGE_TYPE = 2
|
|
357
381
|
EXPECTS_REPLY_MASK = 0x1000
|
|
358
382
|
|
|
359
|
-
def __init__(
|
|
360
|
-
|
|
383
|
+
def __init__(
|
|
384
|
+
self,
|
|
385
|
+
lockdown: LockdownServiceProvider,
|
|
386
|
+
service_name,
|
|
387
|
+
remove_ssl_context: bool = True,
|
|
388
|
+
is_developer_service: bool = True,
|
|
389
|
+
):
|
|
361
390
|
super().__init__(lockdown, service_name, is_developer_service=is_developer_service)
|
|
362
391
|
|
|
363
|
-
if remove_ssl_context and hasattr(self.service.socket,
|
|
392
|
+
if remove_ssl_context and hasattr(self.service.socket, "_sslobj"):
|
|
364
393
|
self.service.socket._sslobj = None
|
|
365
394
|
|
|
366
395
|
self.supported_identifiers = {}
|
|
@@ -372,22 +401,23 @@ class RemoteServer(LockdownService):
|
|
|
372
401
|
|
|
373
402
|
def shell(self):
|
|
374
403
|
IPython.embed(
|
|
375
|
-
header=highlight(SHELL_USAGE, lexers.PythonLexer(), formatters.Terminal256Formatter(style=
|
|
404
|
+
header=highlight(SHELL_USAGE, lexers.PythonLexer(), formatters.Terminal256Formatter(style="native")),
|
|
376
405
|
user_ns={
|
|
377
|
-
|
|
378
|
-
|
|
379
|
-
|
|
380
|
-
}
|
|
406
|
+
"developer": self,
|
|
407
|
+
"broadcast": self.broadcast,
|
|
408
|
+
"MessageAux": MessageAux,
|
|
409
|
+
},
|
|
410
|
+
)
|
|
381
411
|
|
|
382
412
|
def perform_handshake(self):
|
|
383
413
|
args = MessageAux()
|
|
384
|
-
args.append_obj({
|
|
385
|
-
self.send_message(0,
|
|
414
|
+
args.append_obj({"com.apple.private.DTXBlockCompression": 0, "com.apple.private.DTXConnection": 1})
|
|
415
|
+
self.send_message(0, "_notifyOfPublishedCapabilities:", args, expects_reply=False)
|
|
386
416
|
ret, aux = self.recv_plist()
|
|
387
|
-
if ret !=
|
|
388
|
-
raise ValueError(
|
|
417
|
+
if ret != "_notifyOfPublishedCapabilities:":
|
|
418
|
+
raise ValueError("Invalid answer")
|
|
389
419
|
if not len(aux[0]):
|
|
390
|
-
raise ValueError(
|
|
420
|
+
raise ValueError("Invalid answer")
|
|
391
421
|
self.supported_identifiers = aux[0].value
|
|
392
422
|
|
|
393
423
|
def make_channel(self, identifier) -> Channel:
|
|
@@ -399,34 +429,39 @@ class RemoteServer(LockdownService):
|
|
|
399
429
|
self.last_channel_code += 1
|
|
400
430
|
code = self.last_channel_code
|
|
401
431
|
args = MessageAux().append_int(code).append_obj(identifier)
|
|
402
|
-
self.send_message(0,
|
|
403
|
-
ret,
|
|
432
|
+
self.send_message(0, "_requestChannelWithCode:identifier:", args)
|
|
433
|
+
ret, _aux = self.recv_plist()
|
|
404
434
|
assert ret is None
|
|
405
435
|
channel = Channel.create(code, self)
|
|
406
436
|
self.channel_cache[identifier] = channel
|
|
407
437
|
self.channel_messages[code] = ChannelFragmenter()
|
|
408
438
|
return channel
|
|
409
439
|
|
|
410
|
-
def send_message(
|
|
440
|
+
def send_message(
|
|
441
|
+
self, channel: int, selector: Optional[str] = None, args: MessageAux = None, expects_reply: bool = True
|
|
442
|
+
):
|
|
411
443
|
self.cur_message += 1
|
|
412
444
|
|
|
413
|
-
aux = bytes(args) if args is not None else b
|
|
414
|
-
sel = archiver.archive(selector) if selector is not None else b
|
|
445
|
+
aux = bytes(args) if args is not None else b""
|
|
446
|
+
sel = archiver.archive(selector) if selector is not None else b""
|
|
415
447
|
flags = self.INSTRUMENTS_MESSAGE_TYPE
|
|
416
448
|
if expects_reply:
|
|
417
449
|
flags |= self.EXPECTS_REPLY_MASK
|
|
418
|
-
pheader = dtx_message_payload_header_struct.build(
|
|
419
|
-
|
|
420
|
-
|
|
421
|
-
|
|
422
|
-
|
|
423
|
-
|
|
424
|
-
|
|
425
|
-
|
|
426
|
-
|
|
427
|
-
|
|
428
|
-
|
|
429
|
-
|
|
450
|
+
pheader = dtx_message_payload_header_struct.build({
|
|
451
|
+
"flags": flags,
|
|
452
|
+
"auxiliaryLength": len(aux),
|
|
453
|
+
"totalLength": len(aux) + len(sel),
|
|
454
|
+
})
|
|
455
|
+
mheader = dtx_message_header_struct.build({
|
|
456
|
+
"cb": dtx_message_header_struct.sizeof(),
|
|
457
|
+
"fragmentId": 0,
|
|
458
|
+
"fragmentCount": 1,
|
|
459
|
+
"length": dtx_message_payload_header_struct.sizeof() + len(aux) + len(sel),
|
|
460
|
+
"identifier": self.cur_message,
|
|
461
|
+
"conversationIndex": 0,
|
|
462
|
+
"channelCode": channel,
|
|
463
|
+
"expectsReply": int(expects_reply),
|
|
464
|
+
})
|
|
430
465
|
msg = mheader + pheader + aux + sel
|
|
431
466
|
self.service.sendall(msg)
|
|
432
467
|
|
|
@@ -435,11 +470,11 @@ class RemoteServer(LockdownService):
|
|
|
435
470
|
if data is not None:
|
|
436
471
|
try:
|
|
437
472
|
data = archiver.unarchive(data)
|
|
438
|
-
except archiver.MissingClassMapping
|
|
473
|
+
except archiver.MissingClassMapping:
|
|
439
474
|
pprint(plistlib.loads(data))
|
|
440
|
-
raise
|
|
475
|
+
raise
|
|
441
476
|
except plistlib.InvalidFileException:
|
|
442
|
-
self.logger.warning(f
|
|
477
|
+
self.logger.warning(f"got an invalid plist: {data[:40]}")
|
|
443
478
|
return data, aux
|
|
444
479
|
|
|
445
480
|
def recv_message(self, channel: int = BROADCAST_CHANNEL):
|
|
@@ -448,12 +483,9 @@ class RemoteServer(LockdownService):
|
|
|
448
483
|
|
|
449
484
|
compression = (pheader.flags & 0xFF000) >> 12
|
|
450
485
|
if compression:
|
|
451
|
-
raise NotImplementedError(
|
|
486
|
+
raise NotImplementedError("Compressed")
|
|
452
487
|
|
|
453
|
-
if pheader.auxiliaryLength
|
|
454
|
-
aux = message_aux_t_struct.parse_stream(packet_stream).aux
|
|
455
|
-
else:
|
|
456
|
-
aux = None
|
|
488
|
+
aux = message_aux_t_struct.parse_stream(packet_stream).aux if pheader.auxiliaryLength else None
|
|
457
489
|
obj_size = pheader.totalLength - pheader.auxiliaryLength
|
|
458
490
|
data = packet_stream.read(obj_size) if obj_size else None
|
|
459
491
|
return data, aux
|
|
@@ -477,9 +509,8 @@ class RemoteServer(LockdownService):
|
|
|
477
509
|
if received_channel_code not in self.channel_messages:
|
|
478
510
|
self.channel_messages[received_channel_code] = ChannelFragmenter()
|
|
479
511
|
|
|
480
|
-
if not mheader.conversationIndex:
|
|
481
|
-
|
|
482
|
-
self.cur_message = mheader.identifier
|
|
512
|
+
if not mheader.conversationIndex and mheader.identifier > self.cur_message:
|
|
513
|
+
self.cur_message = mheader.identifier
|
|
483
514
|
|
|
484
515
|
if mheader.fragmentCount > 1 and mheader.fragmentId == 0:
|
|
485
516
|
# when reading multiple message fragments, the first fragment contains only a message header
|
|
@@ -493,15 +524,14 @@ class RemoteServer(LockdownService):
|
|
|
493
524
|
|
|
494
525
|
def close(self):
|
|
495
526
|
aux = MessageAux()
|
|
496
|
-
codes = [code for code in self.channel_messages
|
|
527
|
+
codes = [code for code in self.channel_messages if code > 0]
|
|
497
528
|
if codes:
|
|
498
529
|
for code in codes:
|
|
499
530
|
aux.append_int(code)
|
|
500
|
-
|
|
501
|
-
|
|
502
|
-
except OSError:
|
|
531
|
+
|
|
532
|
+
with contextlib.suppress(OSError):
|
|
503
533
|
# ignore: OSError: [Errno 9] Bad file descriptor
|
|
504
|
-
|
|
534
|
+
self.send_message(self.BROADCAST_CHANNEL, "_channelCanceled:", aux, expects_reply=False)
|
|
505
535
|
super().close()
|
|
506
536
|
|
|
507
537
|
|