pymobiledevice3 5.0.4__py3-none-any.whl → 7.0.6__py3-none-any.whl
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- misc/understanding_idevice_protocol_layers.md +10 -5
- pymobiledevice3/__main__.py +171 -46
- pymobiledevice3/_version.py +2 -2
- pymobiledevice3/bonjour.py +22 -21
- pymobiledevice3/cli/activation.py +24 -22
- pymobiledevice3/cli/afc.py +49 -41
- pymobiledevice3/cli/amfi.py +13 -18
- pymobiledevice3/cli/apps.py +71 -65
- pymobiledevice3/cli/backup.py +134 -93
- pymobiledevice3/cli/bonjour.py +31 -29
- pymobiledevice3/cli/cli_common.py +175 -232
- pymobiledevice3/cli/companion_proxy.py +12 -12
- pymobiledevice3/cli/crash.py +95 -52
- pymobiledevice3/cli/developer/__init__.py +62 -0
- pymobiledevice3/cli/developer/accessibility/__init__.py +65 -0
- pymobiledevice3/cli/developer/accessibility/settings.py +43 -0
- pymobiledevice3/cli/developer/arbitration.py +50 -0
- pymobiledevice3/cli/developer/condition.py +33 -0
- pymobiledevice3/cli/developer/core_device.py +294 -0
- pymobiledevice3/cli/developer/debugserver.py +244 -0
- pymobiledevice3/cli/developer/dvt/__init__.py +438 -0
- pymobiledevice3/cli/developer/dvt/core_profile_session.py +295 -0
- pymobiledevice3/cli/developer/dvt/simulate_location.py +56 -0
- pymobiledevice3/cli/developer/dvt/sysmon/__init__.py +69 -0
- pymobiledevice3/cli/developer/dvt/sysmon/process.py +188 -0
- pymobiledevice3/cli/developer/fetch_symbols.py +108 -0
- pymobiledevice3/cli/developer/simulate_location.py +51 -0
- pymobiledevice3/cli/diagnostics/__init__.py +75 -0
- pymobiledevice3/cli/diagnostics/battery.py +47 -0
- pymobiledevice3/cli/idam.py +42 -0
- pymobiledevice3/cli/lockdown.py +70 -75
- pymobiledevice3/cli/mounter.py +99 -57
- pymobiledevice3/cli/notification.py +38 -26
- pymobiledevice3/cli/pcap.py +36 -20
- pymobiledevice3/cli/power_assertion.py +15 -16
- pymobiledevice3/cli/processes.py +11 -17
- pymobiledevice3/cli/profile.py +120 -75
- pymobiledevice3/cli/provision.py +27 -26
- pymobiledevice3/cli/remote.py +109 -100
- pymobiledevice3/cli/restore.py +134 -129
- pymobiledevice3/cli/springboard.py +50 -50
- pymobiledevice3/cli/syslog.py +145 -65
- pymobiledevice3/cli/usbmux.py +66 -27
- pymobiledevice3/cli/version.py +2 -5
- pymobiledevice3/cli/webinspector.py +232 -156
- pymobiledevice3/exceptions.py +6 -2
- pymobiledevice3/lockdown.py +5 -1
- pymobiledevice3/lockdown_service_provider.py +5 -0
- pymobiledevice3/remote/remote_service_discovery.py +18 -10
- pymobiledevice3/restore/device.py +28 -4
- pymobiledevice3/restore/restore.py +2 -2
- pymobiledevice3/service_connection.py +15 -12
- pymobiledevice3/services/afc.py +731 -220
- pymobiledevice3/services/device_link.py +45 -31
- pymobiledevice3/services/idam.py +20 -0
- pymobiledevice3/services/lockdown_service.py +12 -9
- pymobiledevice3/services/mobile_config.py +1 -0
- pymobiledevice3/services/mobilebackup2.py +6 -3
- pymobiledevice3/services/os_trace.py +97 -55
- pymobiledevice3/services/remote_fetch_symbols.py +13 -8
- pymobiledevice3/services/screenshot.py +2 -2
- pymobiledevice3/services/web_protocol/alert.py +8 -8
- pymobiledevice3/services/web_protocol/automation_session.py +87 -79
- pymobiledevice3/services/web_protocol/cdp_screencast.py +2 -1
- pymobiledevice3/services/web_protocol/driver.py +71 -70
- pymobiledevice3/services/web_protocol/element.py +58 -56
- pymobiledevice3/services/web_protocol/selenium_api.py +47 -47
- pymobiledevice3/services/web_protocol/session_protocol.py +3 -2
- pymobiledevice3/services/web_protocol/switch_to.py +23 -19
- pymobiledevice3/services/webinspector.py +42 -67
- {pymobiledevice3-5.0.4.dist-info → pymobiledevice3-7.0.6.dist-info}/METADATA +5 -3
- {pymobiledevice3-5.0.4.dist-info → pymobiledevice3-7.0.6.dist-info}/RECORD +76 -61
- pymobiledevice3/cli/completions.py +0 -50
- pymobiledevice3/cli/developer.py +0 -1539
- pymobiledevice3/cli/diagnostics.py +0 -110
- {pymobiledevice3-5.0.4.dist-info → pymobiledevice3-7.0.6.dist-info}/WHEEL +0 -0
- {pymobiledevice3-5.0.4.dist-info → pymobiledevice3-7.0.6.dist-info}/entry_points.txt +0 -0
- {pymobiledevice3-5.0.4.dist-info → pymobiledevice3-7.0.6.dist-info}/licenses/LICENSE +0 -0
- {pymobiledevice3-5.0.4.dist-info → pymobiledevice3-7.0.6.dist-info}/top_level.txt +0 -0
|
@@ -3,9 +3,12 @@ import datetime
|
|
|
3
3
|
import shutil
|
|
4
4
|
import struct
|
|
5
5
|
import warnings
|
|
6
|
+
from collections.abc import Iterable, Mapping, Sequence
|
|
6
7
|
from pathlib import Path
|
|
8
|
+
from typing import Any, Callable, Optional, cast
|
|
7
9
|
|
|
8
10
|
from pymobiledevice3.exceptions import NotEnoughDiskSpaceError, PyMobileDevice3Exception
|
|
11
|
+
from pymobiledevice3.service_connection import ServiceConnection
|
|
9
12
|
|
|
10
13
|
SIZE_FORMAT = ">I"
|
|
11
14
|
CODE_FORMAT = ">B"
|
|
@@ -26,12 +29,16 @@ ERRNO_TO_DEVICE_ERROR = {
|
|
|
26
29
|
28: -15,
|
|
27
30
|
}
|
|
28
31
|
|
|
32
|
+
DLMessage = Sequence[Any]
|
|
33
|
+
ProgressCallback = Callable[[Any], None]
|
|
34
|
+
DLHandler = Callable[[DLMessage], None]
|
|
35
|
+
|
|
29
36
|
|
|
30
37
|
class DeviceLink:
|
|
31
|
-
def __init__(self, service, root_path: Path):
|
|
32
|
-
self.service = service
|
|
33
|
-
self.root_path = root_path
|
|
34
|
-
self._dl_handlers = {
|
|
38
|
+
def __init__(self, service: ServiceConnection, root_path: Path) -> None:
|
|
39
|
+
self.service: ServiceConnection = service
|
|
40
|
+
self.root_path: Path = root_path
|
|
41
|
+
self._dl_handlers: dict[str, DLHandler] = {
|
|
35
42
|
"DLMessageCreateDirectory": self.create_directory,
|
|
36
43
|
"DLMessageUploadFiles": self.upload_files,
|
|
37
44
|
"DLMessageGetFreeDiskSpace": self.get_free_disk_space,
|
|
@@ -43,7 +50,12 @@ class DeviceLink:
|
|
|
43
50
|
"DLMessagePurgeDiskSpace": self.purge_disk_space,
|
|
44
51
|
}
|
|
45
52
|
|
|
46
|
-
def dl_loop(self, progress_callback=
|
|
53
|
+
def dl_loop(self, progress_callback: Optional[ProgressCallback] = None) -> Any:
|
|
54
|
+
def _noop(_: Any) -> None:
|
|
55
|
+
return None
|
|
56
|
+
|
|
57
|
+
callback: ProgressCallback = progress_callback if progress_callback is not None else _noop
|
|
58
|
+
|
|
47
59
|
while True:
|
|
48
60
|
message = self.receive_message()
|
|
49
61
|
command = message[0]
|
|
@@ -55,9 +67,9 @@ class DeviceLink:
|
|
|
55
67
|
"DLMessageRemoveFiles",
|
|
56
68
|
"DLMessageRemoveItems",
|
|
57
69
|
):
|
|
58
|
-
|
|
70
|
+
callback(message[3])
|
|
59
71
|
elif command == "DLMessageUploadFiles":
|
|
60
|
-
|
|
72
|
+
callback(message[2])
|
|
61
73
|
|
|
62
74
|
if command == "DLMessageProcessMessage":
|
|
63
75
|
if not message[1]["ErrorCode"]:
|
|
@@ -66,7 +78,7 @@ class DeviceLink:
|
|
|
66
78
|
raise PyMobileDevice3Exception(f"Device link error: {message[1]}")
|
|
67
79
|
self._dl_handlers[command](message)
|
|
68
80
|
|
|
69
|
-
def version_exchange(self):
|
|
81
|
+
def version_exchange(self) -> None:
|
|
70
82
|
dl_message_version_exchange = self.receive_message()
|
|
71
83
|
version_major = dl_message_version_exchange[1]
|
|
72
84
|
self.service.send_plist(["DLMessageVersionExchange", "DLVersionsOk", version_major])
|
|
@@ -74,12 +86,13 @@ class DeviceLink:
|
|
|
74
86
|
if dl_message_device_ready[0] != "DLMessageDeviceReady":
|
|
75
87
|
raise PyMobileDevice3Exception("Device link didn't return ready state")
|
|
76
88
|
|
|
77
|
-
def send_process_message(self, message):
|
|
89
|
+
def send_process_message(self, message: Mapping[str, Any]) -> None:
|
|
78
90
|
self.service.send_plist(["DLMessageProcessMessage", message])
|
|
79
91
|
|
|
80
|
-
def download_files(self, message):
|
|
81
|
-
status = {}
|
|
82
|
-
|
|
92
|
+
def download_files(self, message: DLMessage) -> None:
|
|
93
|
+
status: dict[str, dict[str, Any]] = {}
|
|
94
|
+
files = cast(Iterable[str], message[1])
|
|
95
|
+
for file in files:
|
|
83
96
|
self.service.sendall(struct.pack(SIZE_FORMAT, len(file)))
|
|
84
97
|
self.service.sendall(file.encode())
|
|
85
98
|
|
|
@@ -114,9 +127,9 @@ class DeviceLink:
|
|
|
114
127
|
else:
|
|
115
128
|
self.status_response(0)
|
|
116
129
|
|
|
117
|
-
def contents_of_directory(self, message):
|
|
130
|
+
def contents_of_directory(self, message: DLMessage) -> None:
|
|
118
131
|
data = {}
|
|
119
|
-
path = self.root_path / message[1]
|
|
132
|
+
path = self.root_path / cast(str, message[1])
|
|
120
133
|
for file in path.iterdir():
|
|
121
134
|
ftype = "DLFileTypeUnknown"
|
|
122
135
|
if file.is_dir():
|
|
@@ -132,7 +145,7 @@ class DeviceLink:
|
|
|
132
145
|
}
|
|
133
146
|
self.status_response(0, status_dict=data)
|
|
134
147
|
|
|
135
|
-
def upload_files(self,
|
|
148
|
+
def upload_files(self, _message: DLMessage) -> None:
|
|
136
149
|
while True:
|
|
137
150
|
device_name = self._prefixed_recv()
|
|
138
151
|
if not device_name:
|
|
@@ -158,20 +171,21 @@ class DeviceLink:
|
|
|
158
171
|
assert code == CODE_SUCCESS
|
|
159
172
|
self.status_response(0)
|
|
160
173
|
|
|
161
|
-
def get_free_disk_space(self,
|
|
174
|
+
def get_free_disk_space(self, _message: DLMessage) -> None:
|
|
162
175
|
freespace = shutil.disk_usage(self.root_path).free
|
|
163
176
|
self.status_response(0, status_dict=freespace)
|
|
164
177
|
|
|
165
|
-
def move_items(self, message):
|
|
166
|
-
|
|
178
|
+
def move_items(self, message: DLMessage) -> None:
|
|
179
|
+
items = cast(Mapping[str, str], message[1])
|
|
180
|
+
for src, dst in items.items():
|
|
167
181
|
dest = self.root_path / dst
|
|
168
182
|
dest.parent.mkdir(parents=True, exist_ok=True)
|
|
169
183
|
shutil.move(self.root_path / src, dest)
|
|
170
184
|
self.status_response(0)
|
|
171
185
|
|
|
172
|
-
def copy_item(self, message):
|
|
173
|
-
src = self.root_path / message[1]
|
|
174
|
-
dest = self.root_path / message[2]
|
|
186
|
+
def copy_item(self, message: DLMessage) -> None:
|
|
187
|
+
src = self.root_path / cast(str, message[1])
|
|
188
|
+
dest = self.root_path / cast(str, message[2])
|
|
175
189
|
dest.parent.mkdir(parents=True, exist_ok=True)
|
|
176
190
|
if src.is_dir():
|
|
177
191
|
shutil.copytree(src, dest)
|
|
@@ -179,11 +193,11 @@ class DeviceLink:
|
|
|
179
193
|
shutil.copy(src, dest)
|
|
180
194
|
self.status_response(0)
|
|
181
195
|
|
|
182
|
-
def purge_disk_space(self,
|
|
196
|
+
def purge_disk_space(self, _message: DLMessage) -> None:
|
|
183
197
|
raise NotEnoughDiskSpaceError()
|
|
184
198
|
|
|
185
|
-
def remove_items(self, message):
|
|
186
|
-
for path in message[1]:
|
|
199
|
+
def remove_items(self, message: DLMessage) -> None:
|
|
200
|
+
for path in cast(Iterable[str], message[1]):
|
|
187
201
|
rm_path = self.root_path / path
|
|
188
202
|
if rm_path.is_dir():
|
|
189
203
|
shutil.rmtree(rm_path)
|
|
@@ -191,12 +205,12 @@ class DeviceLink:
|
|
|
191
205
|
rm_path.unlink(missing_ok=True)
|
|
192
206
|
self.status_response(0)
|
|
193
207
|
|
|
194
|
-
def create_directory(self, message):
|
|
195
|
-
path = message[1]
|
|
208
|
+
def create_directory(self, message: DLMessage) -> None:
|
|
209
|
+
path = cast(str, message[1])
|
|
196
210
|
(self.root_path / path).mkdir(parents=True, exist_ok=True)
|
|
197
211
|
self.status_response(0)
|
|
198
212
|
|
|
199
|
-
def status_response(self, status_code, status_str="", status_dict=None):
|
|
213
|
+
def status_response(self, status_code: int, status_str: str = "", status_dict: Any = None) -> None:
|
|
200
214
|
self.service.send_plist([
|
|
201
215
|
"DLMessageStatusResponse",
|
|
202
216
|
ctypes.c_uint64(status_code).value,
|
|
@@ -204,12 +218,12 @@ class DeviceLink:
|
|
|
204
218
|
status_dict if status_dict is not None else {},
|
|
205
219
|
])
|
|
206
220
|
|
|
207
|
-
def receive_message(self):
|
|
208
|
-
return self.service.recv_plist()
|
|
221
|
+
def receive_message(self) -> DLMessage:
|
|
222
|
+
return cast(DLMessage, self.service.recv_plist())
|
|
209
223
|
|
|
210
|
-
def disconnect(self):
|
|
224
|
+
def disconnect(self) -> None:
|
|
211
225
|
self.service.send_plist(["DLMessageDisconnect", "___EmptyParameterString___"])
|
|
212
226
|
|
|
213
|
-
def _prefixed_recv(self):
|
|
227
|
+
def _prefixed_recv(self) -> str:
|
|
214
228
|
(size,) = struct.unpack(SIZE_FORMAT, self.service.recvall(struct.calcsize(SIZE_FORMAT)))
|
|
215
229
|
return self.service.recvall(size).decode()
|
|
@@ -0,0 +1,20 @@
|
|
|
1
|
+
from pymobiledevice3.lockdown import LockdownClient
|
|
2
|
+
from pymobiledevice3.lockdown_service_provider import LockdownServiceProvider
|
|
3
|
+
from pymobiledevice3.services.lockdown_service import LockdownService
|
|
4
|
+
|
|
5
|
+
|
|
6
|
+
class IDAMService(LockdownService):
|
|
7
|
+
RSD_SERVICE_NAME = "com.apple.idamd.shim.remote"
|
|
8
|
+
SERVICE_NAME = "com.apple.idamd"
|
|
9
|
+
|
|
10
|
+
def __init__(self, lockdown: LockdownServiceProvider) -> None:
|
|
11
|
+
if isinstance(lockdown, LockdownClient):
|
|
12
|
+
super().__init__(lockdown, self.SERVICE_NAME)
|
|
13
|
+
else:
|
|
14
|
+
super().__init__(lockdown, self.RSD_SERVICE_NAME)
|
|
15
|
+
|
|
16
|
+
def configuration_inquiry(self) -> dict:
|
|
17
|
+
return self.service.send_recv_plist({"Configuration Inquiry": True})
|
|
18
|
+
|
|
19
|
+
def set_idam_configuration(self, value: bool) -> None:
|
|
20
|
+
self.service.send_recv_plist({"Set IDAM Configuration": value})
|
|
@@ -1,4 +1,7 @@
|
|
|
1
1
|
import logging
|
|
2
|
+
from typing import Optional
|
|
3
|
+
|
|
4
|
+
from typing_extensions import Self
|
|
2
5
|
|
|
3
6
|
from pymobiledevice3.lockdown_service_provider import LockdownServiceProvider
|
|
4
7
|
from pymobiledevice3.service_connection import ServiceConnection
|
|
@@ -9,10 +12,10 @@ class LockdownService:
|
|
|
9
12
|
self,
|
|
10
13
|
lockdown: LockdownServiceProvider,
|
|
11
14
|
service_name: str,
|
|
12
|
-
is_developer_service=False,
|
|
13
|
-
service: ServiceConnection = None,
|
|
15
|
+
is_developer_service: bool = False,
|
|
16
|
+
service: Optional[ServiceConnection] = None,
|
|
14
17
|
include_escrow_bag: bool = False,
|
|
15
|
-
):
|
|
18
|
+
) -> None:
|
|
16
19
|
"""
|
|
17
20
|
:param lockdown: server provider
|
|
18
21
|
:param service_name: wrapped service name - will attempt
|
|
@@ -26,15 +29,15 @@ class LockdownService:
|
|
|
26
29
|
)
|
|
27
30
|
service = start_service(service_name, include_escrow_bag=include_escrow_bag)
|
|
28
31
|
|
|
29
|
-
self.service_name = service_name
|
|
30
|
-
self.lockdown = lockdown
|
|
31
|
-
self.service = service
|
|
32
|
-
self.logger = logging.getLogger(self.__module__)
|
|
32
|
+
self.service_name: str = service_name
|
|
33
|
+
self.lockdown: LockdownServiceProvider = lockdown
|
|
34
|
+
self.service: ServiceConnection = service
|
|
35
|
+
self.logger: logging.Logger = logging.getLogger(self.__module__)
|
|
33
36
|
|
|
34
|
-
def __enter__(self):
|
|
37
|
+
def __enter__(self) -> Self:
|
|
35
38
|
return self
|
|
36
39
|
|
|
37
|
-
async def __aenter__(self) ->
|
|
40
|
+
async def __aenter__(self) -> Self:
|
|
38
41
|
return self
|
|
39
42
|
|
|
40
43
|
def __exit__(self, exc_type, exc_val, exc_tb):
|
|
@@ -5,6 +5,7 @@ import uuid
|
|
|
5
5
|
from contextlib import contextmanager, suppress
|
|
6
6
|
from datetime import datetime
|
|
7
7
|
from pathlib import Path
|
|
8
|
+
from typing import Union
|
|
8
9
|
|
|
9
10
|
from pymobiledevice3.exceptions import (
|
|
10
11
|
AfcException,
|
|
@@ -16,7 +17,7 @@ from pymobiledevice3.exceptions import (
|
|
|
16
17
|
)
|
|
17
18
|
from pymobiledevice3.lockdown import LockdownClient
|
|
18
19
|
from pymobiledevice3.lockdown_service_provider import LockdownServiceProvider
|
|
19
|
-
from pymobiledevice3.services.afc import AFC_LOCK_EX, AFC_LOCK_UN,
|
|
20
|
+
from pymobiledevice3.services.afc import AFC_LOCK_EX, AFC_LOCK_UN, AfcError, AfcService
|
|
20
21
|
from pymobiledevice3.services.device_link import DeviceLink
|
|
21
22
|
from pymobiledevice3.services.installation_proxy import InstallationProxyService
|
|
22
23
|
from pymobiledevice3.services.lockdown_service import LockdownService
|
|
@@ -60,7 +61,9 @@ class Mobilebackup2Service(LockdownService):
|
|
|
60
61
|
except LockdownError:
|
|
61
62
|
return False
|
|
62
63
|
|
|
63
|
-
def backup(
|
|
64
|
+
def backup(
|
|
65
|
+
self, full: bool = True, backup_directory: Union[str, Path] = ".", progress_callback=lambda x: None
|
|
66
|
+
) -> None:
|
|
64
67
|
"""
|
|
65
68
|
Backup a device.
|
|
66
69
|
:param full: Whether to do a full backup. If full is True, any previous backup attempts will be discarded.
|
|
@@ -386,7 +389,7 @@ class Mobilebackup2Service(LockdownService):
|
|
|
386
389
|
try:
|
|
387
390
|
afc.lock(lockfile, AFC_LOCK_EX)
|
|
388
391
|
except AfcException as e:
|
|
389
|
-
if e.status ==
|
|
392
|
+
if e.status == AfcError.OP_WOULD_BLOCK:
|
|
390
393
|
time.sleep(0.2)
|
|
391
394
|
else:
|
|
392
395
|
afc.fclose(lockfile)
|
|
@@ -9,8 +9,6 @@ from enum import IntEnum
|
|
|
9
9
|
from pathlib import Path
|
|
10
10
|
from tarfile import TarFile
|
|
11
11
|
|
|
12
|
-
from construct import Adapter, Byte, Bytes, Computed, Enum, Int16ul, Int32ul, Optional, RepeatUntil, Struct, this
|
|
13
|
-
|
|
14
12
|
from pymobiledevice3.exceptions import PyMobileDevice3Exception
|
|
15
13
|
from pymobiledevice3.lockdown import LockdownClient
|
|
16
14
|
from pymobiledevice3.lockdown_service_provider import LockdownServiceProvider
|
|
@@ -45,51 +43,107 @@ class SyslogEntry:
|
|
|
45
43
|
timestamp: datetime
|
|
46
44
|
level: SyslogLogLevel
|
|
47
45
|
image_name: str
|
|
46
|
+
image_offset: int
|
|
48
47
|
filename: str
|
|
49
48
|
message: str
|
|
50
49
|
label: typing.Optional[SyslogLabel] = None
|
|
51
50
|
|
|
52
51
|
|
|
53
|
-
|
|
54
|
-
|
|
55
|
-
|
|
56
|
-
|
|
57
|
-
|
|
58
|
-
|
|
59
|
-
|
|
60
|
-
|
|
61
|
-
|
|
62
|
-
|
|
63
|
-
|
|
64
|
-
|
|
65
|
-
|
|
66
|
-
|
|
67
|
-
|
|
68
|
-
|
|
69
|
-
|
|
70
|
-
|
|
71
|
-
|
|
72
|
-
|
|
73
|
-
|
|
74
|
-
|
|
75
|
-
|
|
76
|
-
|
|
77
|
-
|
|
78
|
-
|
|
79
|
-
|
|
80
|
-
|
|
81
|
-
|
|
82
|
-
|
|
83
|
-
|
|
84
|
-
|
|
85
|
-
|
|
86
|
-
|
|
87
|
-
|
|
88
|
-
|
|
89
|
-
|
|
90
|
-
|
|
91
|
-
)
|
|
92
|
-
|
|
52
|
+
def parse_syslog_entry(data: bytes) -> SyslogEntry:
|
|
53
|
+
"""
|
|
54
|
+
Parse a syslog entry from binary data.
|
|
55
|
+
|
|
56
|
+
:param data: Raw binary data
|
|
57
|
+
:return: SyslogEntry
|
|
58
|
+
"""
|
|
59
|
+
offset = 0
|
|
60
|
+
|
|
61
|
+
# Skip first 9 bytes
|
|
62
|
+
offset += 9
|
|
63
|
+
|
|
64
|
+
# Parse pid (4 bytes, little-endian unsigned int)
|
|
65
|
+
pid = struct.unpack("<I", data[offset : offset + 4])[0]
|
|
66
|
+
offset += 4
|
|
67
|
+
|
|
68
|
+
# Skip 42 bytes
|
|
69
|
+
offset += 42
|
|
70
|
+
|
|
71
|
+
# Parse timestamp
|
|
72
|
+
seconds = struct.unpack("<I", data[offset : offset + 4])[0]
|
|
73
|
+
offset += 4
|
|
74
|
+
offset += 4 # Skip 4 bytes
|
|
75
|
+
microseconds = struct.unpack("<I", data[offset : offset + 4])[0]
|
|
76
|
+
offset += 4
|
|
77
|
+
timestamp = datetime.fromtimestamp(seconds + (microseconds / 1000000))
|
|
78
|
+
|
|
79
|
+
# Skip 1 byte
|
|
80
|
+
offset += 1
|
|
81
|
+
|
|
82
|
+
# Parse level (1 byte)
|
|
83
|
+
level = data[offset]
|
|
84
|
+
offset += 1
|
|
85
|
+
|
|
86
|
+
# Skip 38 bytes
|
|
87
|
+
offset += 38
|
|
88
|
+
|
|
89
|
+
# Parse image_name_size (2 bytes, little-endian unsigned short)
|
|
90
|
+
image_name_size = struct.unpack("<H", data[offset : offset + 2])[0]
|
|
91
|
+
offset += 2
|
|
92
|
+
|
|
93
|
+
# Parse message_size (2 bytes, little-endian unsigned short)
|
|
94
|
+
message_size = struct.unpack("<H", data[offset : offset + 2])[0]
|
|
95
|
+
offset += 2
|
|
96
|
+
|
|
97
|
+
# Skip 2 bytes
|
|
98
|
+
offset += 2
|
|
99
|
+
|
|
100
|
+
# Parse sender_image_offset (4 bytes, little-endian unsigned int)
|
|
101
|
+
sender_image_offset = struct.unpack("<I", data[offset : offset + 4])[0]
|
|
102
|
+
offset += 4
|
|
103
|
+
|
|
104
|
+
# Parse subsystem_size (4 bytes, little-endian unsigned int)
|
|
105
|
+
subsystem_size = struct.unpack("<I", data[offset : offset + 4])[0]
|
|
106
|
+
offset += 4
|
|
107
|
+
|
|
108
|
+
# Parse category_size (4 bytes, little-endian unsigned int)
|
|
109
|
+
category_size = struct.unpack("<I", data[offset : offset + 4])[0]
|
|
110
|
+
offset += 4
|
|
111
|
+
|
|
112
|
+
# Skip 4 bytes
|
|
113
|
+
offset += 4
|
|
114
|
+
|
|
115
|
+
# Parse filename (null-terminated)
|
|
116
|
+
filename_end = data.find(b"\x00", offset)
|
|
117
|
+
filename = try_decode(data[offset:filename_end])
|
|
118
|
+
offset = filename_end + 1
|
|
119
|
+
|
|
120
|
+
# Parse image_name
|
|
121
|
+
image_name = try_decode(data[offset : offset + image_name_size - 1])
|
|
122
|
+
offset += image_name_size
|
|
123
|
+
|
|
124
|
+
# Parse message
|
|
125
|
+
message = try_decode(data[offset : offset + message_size - 1])
|
|
126
|
+
offset += message_size
|
|
127
|
+
|
|
128
|
+
# Parse label (optional)
|
|
129
|
+
label = None
|
|
130
|
+
if subsystem_size > 0 and category_size > 0:
|
|
131
|
+
subsystem = try_decode(data[offset : offset + subsystem_size - 1])
|
|
132
|
+
offset += subsystem_size
|
|
133
|
+
category = try_decode(data[offset : offset + category_size - 1])
|
|
134
|
+
offset += category_size
|
|
135
|
+
label = SyslogLabel(subsystem=subsystem, category=category)
|
|
136
|
+
|
|
137
|
+
return SyslogEntry(
|
|
138
|
+
pid=pid,
|
|
139
|
+
timestamp=timestamp,
|
|
140
|
+
level=SyslogLogLevel(level),
|
|
141
|
+
image_name=image_name,
|
|
142
|
+
image_offset=sender_image_offset,
|
|
143
|
+
filename=filename,
|
|
144
|
+
message=message,
|
|
145
|
+
label=label,
|
|
146
|
+
)
|
|
93
147
|
|
|
94
148
|
|
|
95
149
|
class OsTraceService(LockdownService):
|
|
@@ -185,16 +239,4 @@ class OsTraceService(LockdownService):
|
|
|
185
239
|
assert self.service.recvall(1) == b"\x02"
|
|
186
240
|
(length,) = struct.unpack("<I", self.service.recvall(4))
|
|
187
241
|
line = self.service.recvall(length)
|
|
188
|
-
|
|
189
|
-
label = None
|
|
190
|
-
if entry.label is not None:
|
|
191
|
-
label = SyslogLabel(subsystem=entry.label.subsystem, category=entry.label.category)
|
|
192
|
-
yield SyslogEntry(
|
|
193
|
-
pid=entry.pid,
|
|
194
|
-
timestamp=entry.timestamp,
|
|
195
|
-
level=SyslogLogLevel(int(entry.level)),
|
|
196
|
-
image_name=entry.image_name,
|
|
197
|
-
filename=entry.filename,
|
|
198
|
-
message=entry.message,
|
|
199
|
-
label=label,
|
|
200
|
-
)
|
|
242
|
+
yield parse_syslog_entry(line)
|
|
@@ -38,11 +38,16 @@ class RemoteFetchSymbolsService(RemoteService):
|
|
|
38
38
|
|
|
39
39
|
async def download(self, out: Path) -> None:
|
|
40
40
|
files = await self.get_dsc_file_list()
|
|
41
|
-
|
|
42
|
-
|
|
43
|
-
|
|
44
|
-
|
|
45
|
-
|
|
46
|
-
|
|
47
|
-
|
|
48
|
-
|
|
41
|
+
with tqdm(total=len(files), dynamic_ncols=True, desc="Downloading DSC") as total_pb:
|
|
42
|
+
for i, file in enumerate(files):
|
|
43
|
+
total_pb.set_description("Downloading DSC")
|
|
44
|
+
out_file = out / file.file_path[1:] # trim the "/" prefix
|
|
45
|
+
out_file.parent.mkdir(parents=True, exist_ok=True)
|
|
46
|
+
with (
|
|
47
|
+
open(out_file, "wb") as f,
|
|
48
|
+
tqdm(total=files[i].file_size, dynamic_ncols=True, desc=file.file_path.rsplit("/", 1)[-1]) as pb,
|
|
49
|
+
):
|
|
50
|
+
async for chunk in self.service.iter_file_chunks(files[i].file_size, file_idx=i):
|
|
51
|
+
f.write(chunk)
|
|
52
|
+
pb.update(len(chunk))
|
|
53
|
+
total_pb.update(1)
|
|
@@ -1,12 +1,12 @@
|
|
|
1
1
|
from pymobiledevice3.exceptions import PyMobileDevice3Exception
|
|
2
|
-
from pymobiledevice3.
|
|
2
|
+
from pymobiledevice3.lockdown_service_provider import LockdownServiceProvider
|
|
3
3
|
from pymobiledevice3.services.lockdown_service import LockdownService
|
|
4
4
|
|
|
5
5
|
|
|
6
6
|
class ScreenshotService(LockdownService):
|
|
7
7
|
SERVICE_NAME = "com.apple.mobile.screenshotr"
|
|
8
8
|
|
|
9
|
-
def __init__(self, lockdown:
|
|
9
|
+
def __init__(self, lockdown: LockdownServiceProvider) -> None:
|
|
10
10
|
super().__init__(lockdown, self.SERVICE_NAME, is_developer_service=True)
|
|
11
11
|
|
|
12
12
|
dl_message_version_exchange = self.service.recv_plist()
|
|
@@ -5,22 +5,22 @@ class Alert:
|
|
|
5
5
|
"""
|
|
6
6
|
self.session = session
|
|
7
7
|
|
|
8
|
-
def accept(self):
|
|
8
|
+
async def accept(self):
|
|
9
9
|
"""Accepts the alert available."""
|
|
10
|
-
self.session.accept_current_javascript_dialog()
|
|
10
|
+
await self.session.accept_current_javascript_dialog()
|
|
11
11
|
|
|
12
|
-
def dismiss(self):
|
|
12
|
+
async def dismiss(self):
|
|
13
13
|
"""Dismisses the alert available."""
|
|
14
|
-
self.session.dismiss_current_javascript_dialog()
|
|
14
|
+
await self.session.dismiss_current_javascript_dialog()
|
|
15
15
|
|
|
16
|
-
def send_keys(self, text: str):
|
|
16
|
+
async def send_keys(self, text: str):
|
|
17
17
|
"""
|
|
18
18
|
Send Keys to the Alert.
|
|
19
19
|
:param text: Text to send to prompts.
|
|
20
20
|
"""
|
|
21
|
-
self.session.set_user_input_for_current_javascript_prompt(text)
|
|
21
|
+
await self.session.set_user_input_for_current_javascript_prompt(text)
|
|
22
22
|
|
|
23
23
|
@property
|
|
24
|
-
def text(self) -> str:
|
|
24
|
+
async def text(self) -> str:
|
|
25
25
|
"""Gets the text of the Alert."""
|
|
26
|
-
return self.session.message_of_current_javascript_dialog()
|
|
26
|
+
return await self.session.message_of_current_javascript_dialog()
|