pymobiledevice3 6.1.5__py3-none-any.whl → 6.2.0__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.
@@ -287,14 +287,14 @@ pymobiledevice3 developer dvt launch com.apple.mobilesafari
287
287
 
288
288
  ## DVT
289
289
 
290
- One of the more interesting developer services, is the one exposed by `DTServiceHub`. It is using DTX protocol messages,
290
+ One of the more interesting developer services is the one exposed by `DTServiceHub`. It is using DTX protocol messages,
291
291
  but since it mainly wraps and allows access to stuff in `DVTFoundation.framework` we called it DVT in our
292
292
  implementation (probably standing for DeveloperTools).
293
293
 
294
294
  We don't delve too much into this protocol, but we'll say in general it allows us to invoke a whitelist of ObjC methods
295
295
  in different ObjC objects. The terminology used by DVT to each such ObjC object is called "channels".
296
296
 
297
- In order to access this different object use the following APIs:
297
+ To access this different object use the following APIs:
298
298
 
299
299
  ```python
300
300
  from pymobiledevice3.lockdown import create_using_usbmux
@@ -313,17 +313,22 @@ dvt_channel = Screenshot(dvt)
313
313
  open('/tmp/screen.png', 'wb').write(dvt_channel.get_screenshot())
314
314
  ```
315
315
 
316
- Looking for an unimplemented feature/channel? Feel free to play with it (and submit a PR afterwards 🙏) using the
316
+ Looking for an unimplemented feature/channel? Feel free to play with it (and submit a PR afterward 🙏) using the
317
317
  following shell:
318
318
 
319
319
  ```shell
320
320
  pymobiledevice3 developer dvt shell
321
321
  ```
322
322
 
323
+ > **NOTE:** The full list of the methods that can be invoked on a DVT channel can be found by looking at all ObjC
324
+ > classes in `DVTInstrumentsFoundation.framework` implementing the `DTXAllowedRPC` protocol.
325
+ > There is an existing [Anubis rule](https://github.com/netanelc305/anubis/blob/9da337178ebd7e9f168e9df2d82b192eba4f1b30/example_rules.yaml#L14-L17)
326
+ > I use to diff new methods against the existing ones.
327
+
323
328
  ## RemoteXPC
324
329
 
325
330
  Starting at iOS 17.0, Apple made a large refactor in the manner we all interact with the developer services. There can
326
- be multiple reasons for that decision, but in general this refactor main key points are:
331
+ be multiple reasons for that decision, but in general these refactor main key points are:
327
332
 
328
333
  - Create a single standard for interacting with the new lockdown services (XPC Messages, Apple's proprietary IPC)
329
334
  - Optimize the protocol for large file transfers (such as the dyld_shared_cache)
@@ -347,7 +352,7 @@ Since all this communication is IP-based, but without any additional exported TC
347
352
  help us here. Instead, starting at iOS 16.0, when connecting an iDevice, it exports another non-standard USB-Ethernet
348
353
  adapter (with IPv6 link-local address), placing us in a subnet with the device's `remoted`.
349
354
 
350
- As we've said this communication is non-standard, and requires either:
355
+ As we've said, this communication is non-standard and requires either:
351
356
 
352
357
  - macOS Monterey or higher
353
358
  - Special driver on your linux/Windows machine
@@ -28,7 +28,7 @@ version_tuple: VERSION_TUPLE
28
28
  commit_id: COMMIT_ID
29
29
  __commit_id__: COMMIT_ID
30
30
 
31
- __version__ = version = '6.1.5'
32
- __version_tuple__ = version_tuple = (6, 1, 5)
31
+ __version__ = version = '6.2.0'
32
+ __version_tuple__ = version_tuple = (6, 2, 0)
33
33
 
34
34
  __commit_id__ = commit_id = None
@@ -13,7 +13,7 @@ import sys
13
13
  import time
14
14
  from collections import namedtuple
15
15
  from dataclasses import asdict
16
- from datetime import datetime
16
+ from datetime import datetime, timezone
17
17
  from pathlib import Path
18
18
  from typing import IO, Optional
19
19
 
@@ -419,6 +419,91 @@ def sysmon_process_single(service_provider: LockdownClient, attributes: list[str
419
419
  print_json(result)
420
420
 
421
421
 
422
+ @sysmon_process.command("monitor-single", cls=Command)
423
+ @click.option(
424
+ "-a",
425
+ "--attributes",
426
+ multiple=True,
427
+ help="filter processes by attribute (key=value). Multiple filters on same attribute use OR logic, different attributes use AND.",
428
+ )
429
+ @click.option(
430
+ "-o",
431
+ "--output",
432
+ type=click.Path(),
433
+ default=None,
434
+ help="output file path for JSONL format (optional, defaults to stdout)",
435
+ )
436
+ @click.option(
437
+ "-i", "--interval", type=click.INT, default=None, help="minimum interval in milliseconds between outputs (optional)"
438
+ )
439
+ @click.option(
440
+ "-d",
441
+ "--duration",
442
+ type=click.INT,
443
+ default=None,
444
+ help="maximum duration in milliseconds to run monitoring (optional)",
445
+ )
446
+ def sysmon_process_monitor_single(
447
+ service_provider: LockdownClient,
448
+ attributes: list[str],
449
+ output: Optional[str],
450
+ interval: Optional[int],
451
+ duration: Optional[int],
452
+ ):
453
+ """Continuously monitor a single process with comprehensive metrics."""
454
+ count = 0
455
+ start_time = None
456
+
457
+ # Parse attributes into grouped filters: same attribute uses OR, different attributes use AND
458
+ parsed_filters: dict[str, list[str]] = {}
459
+ if attributes:
460
+ for raw in attributes:
461
+ key, value = raw.split("=", 1)
462
+ parsed_filters.setdefault(key, []).append(value)
463
+
464
+ def matches_filters(proc: dict) -> bool:
465
+ """Check if process matches all filter criteria."""
466
+ if not parsed_filters:
467
+ return True
468
+ return all(str(proc.get(key)) in values for key, values in parsed_filters.items())
469
+
470
+ with contextlib.ExitStack() as stack:
471
+ output_file = stack.enter_context(open(output, "w")) if output else None
472
+
473
+ dvt = stack.enter_context(DvtSecureSocketProxyService(lockdown=service_provider))
474
+ sysmon = stack.enter_context(Sysmontap(dvt))
475
+
476
+ for process_snapshot in sysmon.iter_processes():
477
+ count += 1
478
+
479
+ if count < 2:
480
+ continue
481
+
482
+ if start_time is None:
483
+ start_time = time.time()
484
+
485
+ if duration is not None:
486
+ elapsed_ms = (time.time() - start_time) * 1000
487
+ if elapsed_ms >= duration:
488
+ break
489
+
490
+ for process in process_snapshot:
491
+ if not matches_filters(process):
492
+ continue
493
+
494
+ process["timestamp"] = datetime.now(timezone.utc).isoformat()
495
+
496
+ if output_file:
497
+ json_output = json.dumps(process, default=default_json_encoder)
498
+ output_file.write(json_output + "\n")
499
+ output_file.flush()
500
+ else:
501
+ print_json(process)
502
+
503
+ if interval:
504
+ time.sleep(interval / 1000.0)
505
+
506
+
422
507
  @sysmon.command("system", cls=Command)
423
508
  @click.option("-f", "--fields", help='field names splitted by ",".')
424
509
  def sysmon_system(service_provider: LockdownClient, fields):
@@ -18,13 +18,15 @@ import struct
18
18
  import sys
19
19
  import warnings
20
20
  from collections import namedtuple
21
+ from dataclasses import dataclass, field
21
22
  from datetime import datetime
22
23
  from re import Pattern
23
24
  from typing import Callable, Optional, Union
24
25
 
25
26
  import hexdump
26
27
  from click.exceptions import Exit
27
- from construct import Const, Container, CString, Enum, GreedyRange, Int64ul, Struct, Tell
28
+ from construct import Const, CString, GreedyRange, Int64ul, Tell
29
+ from construct_typed import DataclassMixin, EnumBase, TEnum, TStruct, csfield
28
30
  from parameter_decorators import path_to_str
29
31
  from pygments import formatters, highlight, lexers
30
32
  from pygnuutils.cli.ls import ls as ls_cli
@@ -63,188 +65,210 @@ StatResult = namedtuple(
63
65
  ],
64
66
  )
65
67
 
66
- afc_opcode_t = Enum(
67
- Int64ul,
68
- STATUS=0x00000001,
69
- DATA=0x00000002, # Data */
70
- READ_DIR=0x00000003, # ReadDir */
71
- READ_FILE=0x00000004, # ReadFile */
72
- WRITE_FILE=0x00000005, # WriteFile */
73
- WRITE_PART=0x00000006, # WritePart */
74
- TRUNCATE=0x00000007, # TruncateFile */
75
- REMOVE_PATH=0x00000008, # RemovePath */
76
- MAKE_DIR=0x00000009, # MakeDir */
77
- GET_FILE_INFO=0x0000000A, # GetFileInfo */
78
- GET_DEVINFO=0x0000000B, # GetDeviceInfo */
79
- WRITE_FILE_ATOM=0x0000000C, # WriteFileAtomic (tmp file+rename) */
80
- FILE_OPEN=0x0000000D, # FileRefOpen */
81
- FILE_OPEN_RES=0x0000000E, # FileRefOpenResult */
82
- READ=0x0000000F, # FileRefRead */
83
- WRITE=0x00000010, # FileRefWrite */
84
- FILE_SEEK=0x00000011, # FileRefSeek */
85
- FILE_TELL=0x00000012, # FileRefTell */
86
- FILE_TELL_RES=0x00000013, # FileRefTellResult */
87
- FILE_CLOSE=0x00000014, # FileRefClose */
88
- FILE_SET_SIZE=0x00000015, # FileRefSetFileSize (ftruncate) */
89
- GET_CON_INFO=0x00000016, # GetConnectionInfo */
90
- SET_CON_OPTIONS=0x00000017, # SetConnectionOptions */
91
- RENAME_PATH=0x00000018, # RenamePath */
92
- SET_FS_BS=0x00000019, # SetFSBlockSize (0x800000) */
93
- SET_SOCKET_BS=0x0000001A, # SetSocketBlockSize (0x800000) */
94
- FILE_LOCK=0x0000001B, # FileRefLock */
95
- MAKE_LINK=0x0000001C, # MakeLink */
96
- SET_FILE_TIME=0x0000001E, # set st_mtime */
97
- )
98
-
99
- afc_error_t = Enum(
100
- Int64ul,
101
- SUCCESS=0,
102
- UNKNOWN_ERROR=1,
103
- OP_HEADER_INVALID=2,
104
- NO_RESOURCES=3,
105
- READ_ERROR=4,
106
- WRITE_ERROR=5,
107
- UNKNOWN_PACKET_TYPE=6,
108
- INVALID_ARG=7,
109
- OBJECT_NOT_FOUND=8,
110
- OBJECT_IS_DIR=9,
111
- PERM_DENIED=10,
112
- SERVICE_NOT_CONNECTED=11,
113
- OP_TIMEOUT=12,
114
- TOO_MUCH_DATA=13,
115
- END_OF_DATA=14,
116
- OP_NOT_SUPPORTED=15,
117
- OBJECT_EXISTS=16,
118
- OBJECT_BUSY=17,
119
- NO_SPACE_LEFT=18,
120
- OP_WOULD_BLOCK=19,
121
- IO_ERROR=20,
122
- OP_INTERRUPTED=21,
123
- OP_IN_PROGRESS=22,
124
- INTERNAL_ERROR=23,
125
- MUX_ERROR=30,
126
- NO_MEM=31,
127
- NOT_ENOUGH_DATA=32,
128
- DIR_NOT_EMPTY=33,
129
- )
130
-
131
- afc_link_type_t = Enum(
132
- Int64ul,
133
- HARDLINK=1,
134
- SYMLINK=2,
135
- )
136
68
 
137
- afc_fopen_mode_t = Enum(
138
- Int64ul,
139
- RDONLY=0x00000001, # /**< r O_RDONLY */
140
- RW=0x00000002, # /**< r+ O_RDWR | O_CREAT */
141
- WRONLY=0x00000003, # /**< w O_WRONLY | O_CREAT | O_TRUNC */
142
- WR=0x00000004, # /**< w+ O_RDWR | O_CREAT | O_TRUNC */
143
- APPEND=0x00000005, # /**< a O_WRONLY | O_APPEND | O_CREAT */
144
- RDAPPEND=0x00000006, # /**< a+ O_RDWR | O_APPEND | O_CREAT */
145
- )
69
+ class AfcOpcode(EnumBase):
70
+ STATUS = 0x00000001
71
+ DATA = 0x00000002 # Data
72
+ READ_DIR = 0x00000003 # ReadDir
73
+ READ_FILE = 0x00000004 # ReadFile
74
+ WRITE_FILE = 0x00000005 # WriteFile
75
+ WRITE_PART = 0x00000006 # WritePart
76
+ TRUNCATE = 0x00000007 # TruncateFile
77
+ REMOVE_PATH = 0x00000008 # RemovePath
78
+ MAKE_DIR = 0x00000009 # MakeDir
79
+ GET_FILE_INFO = 0x0000000A # GetFileInfo
80
+ GET_DEVINFO = 0x0000000B # GetDeviceInfo
81
+ WRITE_FILE_ATOM = 0x0000000C # WriteFileAtomic (tmp file+rename)
82
+ FILE_OPEN = 0x0000000D # FileRefOpen
83
+ FILE_OPEN_RES = 0x0000000E # FileRefOpenResult
84
+ READ = 0x0000000F # FileRefRead
85
+ WRITE = 0x00000010 # FileRefWrite
86
+ FILE_SEEK = 0x00000011 # FileRefSeek
87
+ FILE_TELL = 0x00000012 # FileRefTell
88
+ FILE_TELL_RES = 0x00000013 # FileRefTellResult
89
+ FILE_CLOSE = 0x00000014 # FileRefClose
90
+ FILE_SET_SIZE = 0x00000015 # FileRefSetFileSize (ftruncate)
91
+ GET_CON_INFO = 0x00000016 # GetConnectionInfo
92
+ SET_CON_OPTIONS = 0x00000017 # SetConnectionOptions
93
+ RENAME_PATH = 0x00000018 # RenamePath
94
+ SET_FS_BS = 0x00000019 # SetFSBlockSize (0x800000)
95
+ SET_SOCKET_BS = 0x0000001A # SetSocketBlockSize (0x800000)
96
+ FILE_LOCK = 0x0000001B # FileRefLock
97
+ MAKE_LINK = 0x0000001C # MakeLink
98
+ SET_FILE_TIME = 0x0000001E # set st_mtime
99
+
100
+
101
+ class AfcError(EnumBase):
102
+ SUCCESS = 0
103
+ UNKNOWN_ERROR = 1
104
+ OP_HEADER_INVALID = 2
105
+ NO_RESOURCES = 3
106
+ READ_ERROR = 4
107
+ WRITE_ERROR = 5
108
+ UNKNOWN_PACKET_TYPE = 6
109
+ INVALID_ARG = 7
110
+ OBJECT_NOT_FOUND = 8
111
+ OBJECT_IS_DIR = 9
112
+ PERM_DENIED = 10
113
+ SERVICE_NOT_CONNECTED = 11
114
+ OP_TIMEOUT = 12
115
+ TOO_MUCH_DATA = 13
116
+ END_OF_DATA = 14
117
+ OP_NOT_SUPPORTED = 15
118
+ OBJECT_EXISTS = 16
119
+ OBJECT_BUSY = 17
120
+ NO_SPACE_LEFT = 18
121
+ OP_WOULD_BLOCK = 19
122
+ IO_ERROR = 20
123
+ OP_INTERRUPTED = 21
124
+ OP_IN_PROGRESS = 22
125
+ INTERNAL_ERROR = 23
126
+ MUX_ERROR = 30
127
+ NO_MEM = 31
128
+ NOT_ENOUGH_DATA = 32
129
+ DIR_NOT_EMPTY = 33
130
+
131
+
132
+ class AfcLinkType(EnumBase):
133
+ HARDLINK = 1
134
+ SYMLINK = 2
135
+
136
+
137
+ class AfcFopenMode(EnumBase):
138
+ RDONLY = 0x00000001 # r O_RDONLY
139
+ RW = 0x00000002 # r+ O_RDWR | O_CREAT
140
+ WRONLY = 0x00000003 # w O_WRONLY | O_CREAT | O_TRUNC
141
+ WR = 0x00000004 # w+ O_RDWR | O_CREAT | O_TRUNC
142
+ APPEND = 0x00000005 # a O_WRONLY | O_APPEND | O_CREAT
143
+ RDAPPEND = 0x00000006 # a+ O_RDWR | O_APPEND | O_CREAT
144
+
145
+
146
+ # typed construct adapters for the enums
147
+ afc_opcode_t = TEnum(Int64ul, AfcOpcode)
148
+ afc_error_construct = TEnum(Int64ul, AfcError)
149
+ afc_link_type_construct = TEnum(Int64ul, AfcLinkType)
150
+ afc_fopen_mode_construct = TEnum(Int64ul, AfcFopenMode)
146
151
 
147
152
  AFC_FOPEN_TEXTUAL_MODES = {
148
- "r": afc_fopen_mode_t.RDONLY,
149
- "r+": afc_fopen_mode_t.RW,
150
- "w": afc_fopen_mode_t.WRONLY,
151
- "w+": afc_fopen_mode_t.WR,
152
- "a": afc_fopen_mode_t.APPEND,
153
- "a+": afc_fopen_mode_t.RDAPPEND,
153
+ "r": AfcFopenMode.RDONLY,
154
+ "r+": AfcFopenMode.RW,
155
+ "w": AfcFopenMode.WRONLY,
156
+ "w+": AfcFopenMode.WR,
157
+ "a": AfcFopenMode.APPEND,
158
+ "a+": AfcFopenMode.RDAPPEND,
154
159
  }
155
160
 
156
- AFC_LOCK_SH = 1 | 4 # /**< shared lock */
157
- AFC_LOCK_EX = 2 | 4 # /**< exclusive lock */
158
- AFC_LOCK_UN = 8 | 4 # /**< unlock */
161
+ AFC_LOCK_SH = 1 | 4 # shared lock
162
+ AFC_LOCK_EX = 2 | 4 # exclusive lock
163
+ AFC_LOCK_UN = 8 | 4 # unlock
159
164
 
160
165
  MAXIMUM_WRITE_SIZE = 1 << 30
161
166
 
162
167
  AFCMAGIC = b"CFA6LPAA"
163
168
 
164
- afc_header_t = Struct(
165
- "magic" / Const(AFCMAGIC),
166
- "entire_length" / Int64ul,
167
- "this_length" / Int64ul,
168
- "packet_num" / Int64ul,
169
- "operation" / afc_opcode_t,
170
- "_data_offset" / Tell,
171
- )
172
169
 
173
- afc_read_dir_req_t = Struct(
174
- "filename" / CString("utf8"),
175
- )
170
+ @dataclass
171
+ class AfcHeader(DataclassMixin):
172
+ magic: bytes = csfield(Const(AFCMAGIC))
173
+ entire_length: int = csfield(Int64ul)
174
+ this_length: int = csfield(Int64ul)
175
+ packet_num: int = csfield(Int64ul)
176
+ operation: AfcOpcode = csfield(afc_opcode_t)
177
+ _data_offset: int = field(default=0, init=False, metadata={"subcon": Tell})
176
178
 
177
- afc_read_dir_resp_t = Struct(
178
- "filenames" / GreedyRange(CString("utf8")),
179
- )
180
179
 
181
- afc_mkdir_req_t = Struct(
182
- "filename" / CString("utf8"),
183
- )
180
+ @dataclass
181
+ class AfcReadDirRequest(DataclassMixin):
182
+ filename: str = csfield(CString("utf8"))
184
183
 
185
- afc_stat_t = Struct(
186
- "filename" / CString("utf8"),
187
- )
188
184
 
189
- afc_make_link_req_t = Struct(
190
- "type" / afc_link_type_t,
191
- "target" / CString("utf8"),
192
- "source" / CString("utf8"),
193
- )
185
+ @dataclass
186
+ class AfcReadDirResponse(DataclassMixin):
187
+ filenames: list[str] = csfield(GreedyRange(CString("utf8")))
194
188
 
195
- afc_fopen_req_t = Struct(
196
- "mode" / afc_fopen_mode_t,
197
- "filename" / CString("utf8"),
198
- )
199
189
 
200
- afc_fopen_resp_t = Struct(
201
- "handle" / Int64ul,
202
- )
190
+ @dataclass
191
+ class AfcMkdirRequest(DataclassMixin):
192
+ filename: str = csfield(CString("utf8"))
203
193
 
204
- afc_fclose_req_t = Struct(
205
- "handle" / Int64ul,
206
- )
207
194
 
208
- afc_rm_req_t = Struct(
209
- "filename" / CString("utf8"),
210
- )
195
+ @dataclass
196
+ class AfcStatRequest(DataclassMixin):
197
+ filename: str = csfield(CString("utf8"))
211
198
 
212
- afc_rename_req_t = Struct(
213
- "source" / CString("utf8"),
214
- "target" / CString("utf8"),
215
- )
216
199
 
217
- afc_fread_req_t = Struct(
218
- "handle" / Int64ul,
219
- "size" / Int64ul,
220
- )
200
+ @dataclass
201
+ class AfcMakeLinkRequest(DataclassMixin):
202
+ type: AfcLinkType = csfield(afc_link_type_construct)
203
+ target: str = csfield(CString("utf8"))
204
+ source: str = csfield(CString("utf8"))
221
205
 
222
- afc_lock_t = Struct(
223
- "handle" / Int64ul,
224
- "op" / Int64ul,
225
- )
206
+
207
+ @dataclass
208
+ class AfcFopenRequest(DataclassMixin):
209
+ mode: AfcFopenMode = csfield(afc_fopen_mode_construct)
210
+ filename: str = csfield(CString("utf8"))
226
211
 
227
212
 
228
- def list_to_dict(d):
213
+ @dataclass
214
+ class AfcFopenResponse(DataclassMixin):
215
+ handle: int = csfield(Int64ul)
216
+
217
+
218
+ @dataclass
219
+ class AfcFcloseRequest(DataclassMixin):
220
+ handle: int = csfield(Int64ul)
221
+
222
+
223
+ @dataclass
224
+ class AfcRmRequest(DataclassMixin):
225
+ filename: str = csfield(CString("utf8"))
226
+
227
+
228
+ @dataclass
229
+ class AfcRenameRequest(DataclassMixin):
230
+ source: str = csfield(CString("utf8"))
231
+ target: str = csfield(CString("utf8"))
232
+
233
+
234
+ @dataclass
235
+ class AfcFreadRequest(DataclassMixin):
236
+ handle: int = csfield(Int64ul)
237
+ size: int = csfield(Int64ul)
238
+
239
+
240
+ @dataclass
241
+ class AfcLockRequest(DataclassMixin):
242
+ handle: int = csfield(Int64ul)
243
+ op: int = csfield(Int64ul)
244
+
245
+
246
+ afc_header_t = TStruct(AfcHeader)
247
+ afc_read_dir_req_t = TStruct(AfcReadDirRequest)
248
+ afc_read_dir_resp_t = TStruct(AfcReadDirResponse)
249
+ afc_mkdir_req_t = TStruct(AfcMkdirRequest)
250
+ afc_stat_t = TStruct(AfcStatRequest)
251
+ afc_make_link_req_t = TStruct(AfcMakeLinkRequest)
252
+ afc_fopen_req_t = TStruct(AfcFopenRequest)
253
+ afc_fopen_resp_t = TStruct(AfcFopenResponse)
254
+ afc_fclose_req_t = TStruct(AfcFcloseRequest)
255
+ afc_rm_req_t = TStruct(AfcRmRequest)
256
+ afc_rename_req_t = TStruct(AfcRenameRequest)
257
+ afc_fread_req_t = TStruct(AfcFreadRequest)
258
+ afc_lock_t = TStruct(AfcLockRequest)
259
+
260
+
261
+ def list_to_dict(raw: bytes) -> dict[str, str]:
229
262
  """
230
263
  Convert a null-terminated key-value list to a dictionary.
231
264
 
232
265
  The input is expected to be a byte string with alternating keys and values,
233
- each separated by null bytes (\x00).
234
-
235
- :param d: Byte string containing null-terminated key-value pairs
236
- :return: Dictionary mapping keys to values
237
- :raises: AssertionError if the list doesn't contain an even number of elements
266
+ each separated by null bytes (``\\x00``).
238
267
  """
239
- d = d.decode("utf-8")
240
- t = d.split("\x00")
241
- t = t[:-1]
242
-
243
- assert len(t) % 2 == 0
244
- res = {}
245
- for i in range(int(len(t) / 2)):
246
- res[t[i * 2]] = t[i * 2 + 1]
247
- return res
268
+ parts = raw.decode("utf-8").split("\x00")[:-1]
269
+ if len(parts) % 2:
270
+ raise ValueError("AFC list is not key/value aligned")
271
+ return dict(zip(parts[::2], parts[1::2]))
248
272
 
249
273
 
250
274
  class AfcService(LockdownService):
@@ -314,15 +338,13 @@ class AfcService(LockdownService):
314
338
  if src_size <= MAXIMUM_READ_SIZE:
315
339
  f.write(self.get_file_contents(src))
316
340
  else:
317
- left_size = src_size
318
341
  handle = self.fopen(src)
342
+ iterator = range(0, src_size, MAXIMUM_READ_SIZE)
319
343
  if progress_bar:
320
- pb = trange(src_size // MAXIMUM_READ_SIZE + 1)
321
- else:
322
- pb = range(src_size // MAXIMUM_READ_SIZE + 1)
323
- for _ in pb:
324
- f.write(self.fread(handle, min(MAXIMUM_READ_SIZE, left_size)))
325
- left_size -= MAXIMUM_READ_SIZE
344
+ iterator = trange(0, src_size, MAXIMUM_READ_SIZE)
345
+ for offset in iterator:
346
+ to_read = min(MAXIMUM_READ_SIZE, src_size - offset)
347
+ f.write(self.fread(handle, to_read))
326
348
  self.fclose(handle)
327
349
  os.utime(dst, (os.stat(dst).st_atime, self.stat(src)["st_mtime"].timestamp()))
328
350
  if callback is not None:
@@ -464,7 +486,7 @@ class AfcService(LockdownService):
464
486
  :rtype: bool
465
487
  """
466
488
  try:
467
- self._do_operation(afc_opcode_t.REMOVE_PATH, afc_rm_req_t.build({"filename": filename}), filename)
489
+ self._do_operation(AfcOpcode.REMOVE_PATH, afc_rm_req_t.build(AfcRmRequest(filename=filename)), filename)
468
490
  except AfcException:
469
491
  if force:
470
492
  return False
@@ -532,7 +554,7 @@ class AfcService(LockdownService):
532
554
 
533
555
  :return: Dictionary containing device file system information
534
556
  """
535
- return list_to_dict(self._do_operation(afc_opcode_t.GET_DEVINFO))
557
+ return list_to_dict(self._do_operation(AfcOpcode.GET_DEVINFO))
536
558
 
537
559
  @path_to_str()
538
560
  def listdir(self, filename: str):
@@ -543,7 +565,7 @@ class AfcService(LockdownService):
543
565
  :return: List of filenames in the directory (excluding '.' and '..')
544
566
  :raises: AfcException if the path is not a directory or doesn't exist
545
567
  """
546
- data = self._do_operation(afc_opcode_t.READ_DIR, afc_read_dir_req_t.build({"filename": filename}))
568
+ data = self._do_operation(AfcOpcode.READ_DIR, afc_read_dir_req_t.build(AfcReadDirRequest(filename=filename)))
547
569
  return afc_read_dir_resp_t.parse(data).filenames[2:] # skip the . and ..
548
570
 
549
571
  @path_to_str()
@@ -556,7 +578,7 @@ class AfcService(LockdownService):
556
578
  :param filename: Path of the directory to create
557
579
  :return: Response data from the operation
558
580
  """
559
- return self._do_operation(afc_opcode_t.MAKE_DIR, afc_mkdir_req_t.build({"filename": filename}))
581
+ return self._do_operation(AfcOpcode.MAKE_DIR, afc_mkdir_req_t.build(AfcMkdirRequest(filename=filename)))
560
582
 
561
583
  @path_to_str()
562
584
  def isdir(self, filename: str) -> bool:
@@ -580,10 +602,12 @@ class AfcService(LockdownService):
580
602
  """
581
603
  try:
582
604
  stat = list_to_dict(
583
- self._do_operation(afc_opcode_t.GET_FILE_INFO, afc_stat_t.build({"filename": filename}), filename)
605
+ self._do_operation(
606
+ AfcOpcode.GET_FILE_INFO, afc_stat_t.build(AfcStatRequest(filename=filename)), filename
607
+ )
584
608
  )
585
609
  except AfcException as e:
586
- if e.status != afc_error_t.READ_ERROR:
610
+ if e.status != AfcError.READ_ERROR:
587
611
  raise
588
612
  raise AfcFileNotFoundError(e.args[0], e.status) from e
589
613
 
@@ -629,7 +653,7 @@ class AfcService(LockdownService):
629
653
  )
630
654
 
631
655
  @path_to_str()
632
- def link(self, target: str, source: str, type_=afc_link_type_t.SYMLINK):
656
+ def link(self, target: str, source: str, type_=AfcLinkType.SYMLINK):
633
657
  """
634
658
  Create a symbolic or hard link on the device.
635
659
 
@@ -639,7 +663,8 @@ class AfcService(LockdownService):
639
663
  :return: Response data from the operation
640
664
  """
641
665
  return self._do_operation(
642
- afc_opcode_t.MAKE_LINK, afc_make_link_req_t.build({"type": type_, "target": target, "source": source})
666
+ AfcOpcode.MAKE_LINK,
667
+ afc_make_link_req_t.build(AfcMakeLinkRequest(type=type_, target=target, source=source)),
643
668
  )
644
669
 
645
670
  @path_to_str()
@@ -656,7 +681,10 @@ class AfcService(LockdownService):
656
681
  raise ArgumentError(f"mode can be only one of: {AFC_FOPEN_TEXTUAL_MODES.keys()}")
657
682
 
658
683
  data = self._do_operation(
659
- afc_opcode_t.FILE_OPEN, afc_fopen_req_t.build({"mode": AFC_FOPEN_TEXTUAL_MODES[mode], "filename": filename})
684
+ AfcOpcode.FILE_OPEN,
685
+ afc_fopen_req_t.build(
686
+ AfcFopenRequest(mode=AFC_FOPEN_TEXTUAL_MODES[mode], filename=filename),
687
+ ),
660
688
  )
661
689
  return afc_fopen_resp_t.parse(data).handle
662
690
 
@@ -667,7 +695,7 @@ class AfcService(LockdownService):
667
695
  :param handle: File handle returned from fopen
668
696
  :return: Response data from the operation
669
697
  """
670
- return self._do_operation(afc_opcode_t.FILE_CLOSE, afc_fclose_req_t.build({"handle": handle}))
698
+ return self._do_operation(AfcOpcode.FILE_CLOSE, afc_fclose_req_t.build(AfcFcloseRequest(handle=handle)))
671
699
 
672
700
  @path_to_str()
673
701
  def rename(self, source: str, target: str) -> None:
@@ -680,8 +708,8 @@ class AfcService(LockdownService):
680
708
  """
681
709
  try:
682
710
  self._do_operation(
683
- afc_opcode_t.RENAME_PATH,
684
- afc_rename_req_t.build({"source": source, "target": target}, filename=f"{source}->{target}"),
711
+ AfcOpcode.RENAME_PATH,
712
+ afc_rename_req_t.build(AfcRenameRequest(source=source, target=target)),
685
713
  )
686
714
  except AfcException as e:
687
715
  if self.exists(source):
@@ -690,7 +718,7 @@ class AfcService(LockdownService):
690
718
  f"Failed to rename {source} into {target}. Got status: {e.status}", e.args[0], str(e.status)
691
719
  ) from e
692
720
 
693
- def fread(self, handle: int, sz: bytes) -> bytes:
721
+ def fread(self, handle: int, sz: int) -> bytes:
694
722
  """
695
723
  Read data from an open file handle.
696
724
 
@@ -704,15 +732,15 @@ class AfcService(LockdownService):
704
732
  data = b""
705
733
  while sz > 0:
706
734
  to_read = MAXIMUM_READ_SIZE if sz > MAXIMUM_READ_SIZE else sz
707
- self._dispatch_packet(afc_opcode_t.READ, afc_fread_req_t.build({"handle": handle, "size": to_read}))
735
+ self._dispatch_packet(AfcOpcode.READ, afc_fread_req_t.build(AfcFreadRequest(handle=handle, size=to_read)))
708
736
  status, chunk = self._receive_data()
709
- if status != afc_error_t.SUCCESS:
737
+ if status != AfcError.SUCCESS:
710
738
  raise AfcException("fread error", status)
711
739
  sz -= to_read
712
740
  data += chunk
713
741
  return data
714
742
 
715
- def fwrite(self, handle, data, chunk_size=MAXIMUM_WRITE_SIZE):
743
+ def fwrite(self, handle: int, data: bytes, chunk_size: int = MAXIMUM_WRITE_SIZE) -> None:
716
744
  """
717
745
  Write data to an open file handle.
718
746
 
@@ -725,24 +753,20 @@ class AfcService(LockdownService):
725
753
  """
726
754
  file_handle = struct.pack("<Q", handle)
727
755
  chunks_count = len(data) // chunk_size
728
- b = b""
729
756
  for i in range(chunks_count):
730
757
  chunk = data[i * chunk_size : (i + 1) * chunk_size]
731
- self._dispatch_packet(afc_opcode_t.WRITE, file_handle + chunk, this_length=48)
732
- b += chunk
758
+ self._dispatch_packet(AfcOpcode.WRITE, file_handle + chunk, this_length=48)
733
759
 
734
760
  status, _response = self._receive_data()
735
- if status != afc_error_t.SUCCESS:
761
+ if status != AfcError.SUCCESS:
736
762
  raise AfcException(f"failed to write chunk: {status}", status)
737
763
 
738
764
  if len(data) % chunk_size:
739
765
  chunk = data[chunks_count * chunk_size :]
740
- self._dispatch_packet(afc_opcode_t.WRITE, file_handle + chunk, this_length=48)
741
-
742
- b += chunk
766
+ self._dispatch_packet(AfcOpcode.WRITE, file_handle + chunk, this_length=48)
743
767
 
744
768
  status, _response = self._receive_data()
745
- if status != afc_error_t.SUCCESS:
769
+ if status != AfcError.SUCCESS:
746
770
  raise AfcException(f"failed to write last chunk: {status}", status)
747
771
 
748
772
  @path_to_str()
@@ -777,7 +801,7 @@ class AfcService(LockdownService):
777
801
  info = self.stat(filename)
778
802
 
779
803
  if info["st_ifmt"] != "S_IFREG":
780
- raise AfcException(f"{filename} isn't a file", afc_error_t.INVALID_ARG)
804
+ raise AfcException(f"{filename} isn't a file", AfcError.INVALID_ARG)
781
805
 
782
806
  h = self.fopen(filename)
783
807
  if not h:
@@ -855,9 +879,9 @@ class AfcService(LockdownService):
855
879
  :param operation: Lock operation (AFC_LOCK_SH, AFC_LOCK_EX, or AFC_LOCK_UN)
856
880
  :return: Response data from the operation
857
881
  """
858
- return self._do_operation(afc_opcode_t.FILE_LOCK, afc_lock_t.build({"handle": handle, "op": operation}))
882
+ return self._do_operation(AfcOpcode.FILE_LOCK, afc_lock_t.build(AfcLockRequest(handle=handle, op=operation)))
859
883
 
860
- def _dispatch_packet(self, operation, data, this_length=0):
884
+ def _dispatch_packet(self, operation: AfcOpcode, data: bytes, this_length: int = 0) -> None:
861
885
  """
862
886
  Send an AFC protocol packet to the device.
863
887
 
@@ -865,16 +889,15 @@ class AfcService(LockdownService):
865
889
  :param data: Packet payload data
866
890
  :param this_length: Override for the packet length field (0 for auto-calculation)
867
891
  """
868
- afcpack = Container(
869
- magic=AFCMAGIC,
870
- entire_length=afc_header_t.sizeof() + len(data),
871
- this_length=afc_header_t.sizeof() + len(data),
872
- packet_num=self.packet_num,
873
- operation=operation,
892
+ entire_length = afc_header_t.sizeof() + len(data)
893
+ header = afc_header_t.build(
894
+ AfcHeader(
895
+ entire_length=entire_length,
896
+ this_length=this_length or entire_length,
897
+ packet_num=self.packet_num,
898
+ operation=operation,
899
+ )
874
900
  )
875
- if this_length:
876
- afcpack.this_length = this_length
877
- header = afc_header_t.build(afcpack)
878
901
  self.packet_num += 1
879
902
  self.service.sendall(header + data)
880
903
 
@@ -884,23 +907,23 @@ class AfcService(LockdownService):
884
907
 
885
908
  :return: Tuple of (status_code, response_data)
886
909
  """
887
- res = self.service.recvall(afc_header_t.sizeof())
888
- status = afc_error_t.SUCCESS
889
- data = ""
890
- if res:
891
- res = afc_header_t.parse(res)
892
- assert res["entire_length"] >= afc_header_t.sizeof()
893
- length = res["entire_length"] - afc_header_t.sizeof()
910
+ header_bytes = self.service.recvall(afc_header_t.sizeof())
911
+ status = AfcError.SUCCESS
912
+ data = b""
913
+ if header_bytes:
914
+ header = afc_header_t.parse(header_bytes)
915
+ assert header.entire_length >= afc_header_t.sizeof()
916
+ length = header.entire_length - afc_header_t.sizeof()
894
917
  data = self.service.recvall(length)
895
- if res.operation == afc_opcode_t.STATUS:
918
+ if header.operation == AfcOpcode.STATUS:
896
919
  if length != 8:
897
920
  self.logger.error("Status length != 8")
898
- status = afc_error_t.parse(data)
899
- elif res.operation != afc_opcode_t.DATA:
900
- pass
921
+ status = afc_error_construct.parse(data)
922
+ elif header.operation != AfcOpcode.DATA:
923
+ self.logger.debug("Unexpected AFC opcode %s", header.operation)
901
924
  return status, data
902
925
 
903
- def _do_operation(self, opcode: int, data: bytes = b"", filename: Optional[str] = None) -> bytes:
926
+ def _do_operation(self, opcode: AfcOpcode, data: bytes = b"", filename: Optional[str] = None) -> bytes:
904
927
  """
905
928
  Performs a low-level operation using the specified opcode and additional data.
906
929
 
@@ -924,11 +947,12 @@ class AfcService(LockdownService):
924
947
  status, data = self._receive_data()
925
948
 
926
949
  exception = AfcException
927
- if status != afc_error_t.SUCCESS:
928
- if status == afc_error_t.OBJECT_NOT_FOUND:
950
+ if status != AfcError.SUCCESS:
951
+ if status == AfcError.OBJECT_NOT_FOUND:
929
952
  exception = AfcFileNotFoundError
930
953
 
931
- message = f"Opcode: {opcode} failed with status: {status}"
954
+ opcode_name = opcode.name if isinstance(opcode, AfcOpcode) else opcode
955
+ message = f"Opcode: {opcode_name} failed with status: {status}"
932
956
  if filename is not None:
933
957
  message += f" for file: {filename}"
934
958
  raise exception(message, status, filename)
@@ -1257,7 +1281,7 @@ class AfcShell:
1257
1281
  :param target: Target path that the link will point to
1258
1282
  :param source: Path where the link will be created
1259
1283
  """
1260
- self.afc.link(self.relative_path(target), self.relative_path(source), afc_link_type_t.SYMLINK)
1284
+ self.afc.link(self.relative_path(target), self.relative_path(source), AfcLinkType.SYMLINK)
1261
1285
 
1262
1286
  def _do_cd(self, directory: Annotated[str, Arg(completer=dir_completer)]) -> None:
1263
1287
  """
@@ -1320,8 +1344,8 @@ class AfcShell:
1320
1344
  self,
1321
1345
  remote_path: Annotated[str, Arg(completer=path_completer)],
1322
1346
  local_path: str,
1323
- ignore_errors: bool = False,
1324
- progress_bar: bool = False,
1347
+ ignore_errors: Annotated[bool, Arg("--ignore-errors", action="store_true")] = False,
1348
+ progress_bar: Annotated[bool, Arg("--progress-bar", action="store_true")] = False,
1325
1349
  ) -> None:
1326
1350
  """
1327
1351
  Pull a file or directory from device to local machine.
@@ -16,7 +16,7 @@ from pymobiledevice3.exceptions import (
16
16
  )
17
17
  from pymobiledevice3.lockdown import LockdownClient
18
18
  from pymobiledevice3.lockdown_service_provider import LockdownServiceProvider
19
- from pymobiledevice3.services.afc import AFC_LOCK_EX, AFC_LOCK_UN, AfcService, afc_error_t
19
+ from pymobiledevice3.services.afc import AFC_LOCK_EX, AFC_LOCK_UN, AfcError, AfcService
20
20
  from pymobiledevice3.services.device_link import DeviceLink
21
21
  from pymobiledevice3.services.installation_proxy import InstallationProxyService
22
22
  from pymobiledevice3.services.lockdown_service import LockdownService
@@ -386,7 +386,7 @@ class Mobilebackup2Service(LockdownService):
386
386
  try:
387
387
  afc.lock(lockfile, AFC_LOCK_EX)
388
388
  except AfcException as e:
389
- if e.status == afc_error_t.OP_WOULD_BLOCK:
389
+ if e.status == AfcError.OP_WOULD_BLOCK:
390
390
  time.sleep(0.2)
391
391
  else:
392
392
  afc.fclose(lockfile)
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.4
2
2
  Name: pymobiledevice3
3
- Version: 6.1.5
3
+ Version: 6.2.0
4
4
  Summary: Pure python3 implementation for working with iDevices (iPhone, etc...)
5
5
  Author-email: doronz88 <doron88@gmail.com>, matan <matan1008@gmail.com>
6
6
  Maintainer-email: doronz88 <doron88@gmail.com>, matan <matan1008@gmail.com>
@@ -21,6 +21,7 @@ Requires-Python: >=3.9
21
21
  Description-Content-Type: text/markdown
22
22
  License-File: LICENSE
23
23
  Requires-Dist: construct>=2.9.29
24
+ Requires-Dist: construct-typing>=0.7.0
24
25
  Requires-Dist: asn1
25
26
  Requires-Dist: click
26
27
  Requires-Dist: coloredlogs
@@ -4,11 +4,11 @@ misc/RemoteXPC.md,sha256=1wfXK_LWKDi-hZlScHffAsD1ZIWjR7bNIA2B82BhW4Y,34110
4
4
  misc/plist_sniffer.py,sha256=lL6jJ3bBIlj6U-AEUCijkMFyd_B0kB8x_gV2qvu_KuM,1912
5
5
  misc/pyinstaller.md,sha256=-j0uoOoaSoUQSpYhLzszT3oqrgEuBz3jXXvMp2Ix0D8,933
6
6
  misc/remotexpc_sniffer.py,sha256=EMhJusdbOhFHJCYF2TLtNABAnzAibIeq8hxRu4xaVhA,7979
7
- misc/understanding_idevice_protocol_layers.md,sha256=8tEqRXWOUPoxOJLZVh7C7H9JGCh2sQ3B5UH8_AymaQc,18805
7
+ misc/understanding_idevice_protocol_layers.md,sha256=FMJQ-ik2j9kFLPS15JzDZg62uk1FzxDnfHO6Nf5EM-A,19207
8
8
  misc/usbmux_sniff.sh,sha256=iWtbucOEQ9_UEFXk9x-2VNt48Jg5zrPsnUbZ_LfZxwA,212
9
9
  pymobiledevice3/__init__.py,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
10
10
  pymobiledevice3/__main__.py,sha256=hpLgWH1Pmwkf1pX-JvYkfJUkphpEb4YhoRV3qQUwbOs,12718
11
- pymobiledevice3/_version.py,sha256=3eMt21QUh0Chgwa74h-IlQoGIeAzU-PF4adgdy9304Y,704
11
+ pymobiledevice3/_version.py,sha256=MGegedBcnKNfp3Nvfm9slO_ZjPCndTvM5j6GLLC1ZEA,704
12
12
  pymobiledevice3/bonjour.py,sha256=y1Zd-__GnvK2ShxmvqFpNfi5NGF6PEWGcuKp8mJVFNA,13419
13
13
  pymobiledevice3/ca.py,sha256=5_Y4F-zDFX_KeDL-M_TRCKKyrRRb9h1lBE8MGTWv91o,10606
14
14
  pymobiledevice3/common.py,sha256=FZzF0BQYV5fCEUPbLo6jbt2Ig9s5YwR8AvX_iR124Ew,329
@@ -33,7 +33,7 @@ pymobiledevice3/cli/cli_common.py,sha256=Czm8H93QSGVI5lfE0cc9TQqgx-vqcKuZN2YJrPC
33
33
  pymobiledevice3/cli/companion_proxy.py,sha256=MXJOYo2N6phkfIMIGajTyhiMbGly0aacNirZMaAWhBY,577
34
34
  pymobiledevice3/cli/completions.py,sha256=zVLU9H-E99evvFWyfXEZjvBsbBZDKbKlcexqdYda_fM,1722
35
35
  pymobiledevice3/cli/crash.py,sha256=VSZP3PMBmSM3j0ivBeF6oIjj3d2EKDIno5kLcopRi_4,3177
36
- pymobiledevice3/cli/developer.py,sha256=GoxNZjFljy2yBr5NAeLXq2MhsZUmgRsn79e_1opsMmM,61868
36
+ pymobiledevice3/cli/developer.py,sha256=bN8XK1U3Lyttblow89Bh1OqOwqNLhBcEm1bLmp0v-lU,64642
37
37
  pymobiledevice3/cli/diagnostics.py,sha256=FvmIGup9IAPj_gGcJDzjLQkKUvGOtrEA01eXf5iio1w,3628
38
38
  pymobiledevice3/cli/idam.py,sha256=6GXwnvhPd7AjhduQz667ZpWGeBcyQCK3HlMHFVBnSJM,1165
39
39
  pymobiledevice3/cli/lockdown.py,sha256=Vl0hbgNtjD-cHpgMoH_x8zzbKUt66paHwHPzUGl_fjM,7104
@@ -100,7 +100,7 @@ pymobiledevice3/restore/restored_client.py,sha256=tv1hyIV7UBDb_OUwj_w6qJsH_x36oB
100
100
  pymobiledevice3/restore/tss.py,sha256=EY8XpUaexHyDTtUCuXQnOgLVIFAikcs9p0ysNSG1Q0U,30818
101
101
  pymobiledevice3/services/__init__.py,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
102
102
  pymobiledevice3/services/accessibilityaudit.py,sha256=7AvGqfAqvsSULib06mpxP94-P-Zr4SwSZohgmbXLuog,15474
103
- pymobiledevice3/services/afc.py,sha256=5hoyY8D5dSa7oxduI3OY4bAFLP4D93VwupZ-c7mKNIs,53504
103
+ pymobiledevice3/services/afc.py,sha256=cIjOyeVDRkE1iKVLpSNt2UGI6jVbOiPoS-siLTeDyWw,55008
104
104
  pymobiledevice3/services/amfi.py,sha256=SWGh5UQtf3YhD_4cT8M3dhH1sPE-mnIzkmiZjJ8d2x4,2522
105
105
  pymobiledevice3/services/companion.py,sha256=6rvL1KF2-Gflv77rL9txINMoiQplVouL_nnGKNq-Maw,2612
106
106
  pymobiledevice3/services/crash_reports.py,sha256=ODsgT3WgpOHIFM-ht9za_-xI9AAh7dKiq50NsRB5q3I,10945
@@ -119,7 +119,7 @@ pymobiledevice3/services/misagent.py,sha256=CGh1EhN_f1rV9RhZfP53OBirXdY0elX_nZS-
119
119
  pymobiledevice3/services/mobile_activation.py,sha256=0zvQn2CMmf47--OtDqv3eOK5Ofu-eBw-ab1TxZSrOlY,9230
120
120
  pymobiledevice3/services/mobile_config.py,sha256=2UmdqYNikznxI6bA2lkyIWS3NcHg3pTb5i6yLl8yZAY,18170
121
121
  pymobiledevice3/services/mobile_image_mounter.py,sha256=PZEN9O7XtLW9VH1qNTJ19zo_tFZhX68bTX0O-Uy9sck,15034
122
- pymobiledevice3/services/mobilebackup2.py,sha256=M2sVY-vXl6f25QFom-z5bUkeTdA4Twbiw1tH-4Tb79M,18303
122
+ pymobiledevice3/services/mobilebackup2.py,sha256=7hB0tNpNoLoneDNbMUmCpg0UhahT5QnF8fxSPwaqVWc,18297
123
123
  pymobiledevice3/services/notification_proxy.py,sha256=mk4kxLRi6aDTXKOazgldliZG4q0bME7jBcxPyJsSpDw,2125
124
124
  pymobiledevice3/services/os_trace.py,sha256=nTODlyWvb10fpqWHDU3m7i9sADIMF0ezfyo9Ql2BZa8,7422
125
125
  pymobiledevice3/services/pcapd.py,sha256=Gd6xN9eBHnLIQ5M2LM-y4Z-eCquxVEnSKuyBECwZlRs,11960
@@ -167,9 +167,9 @@ pymobiledevice3/services/web_protocol/switch_to.py,sha256=TCdVrMfsvd18o-vZ0owVrE
167
167
  pymobiledevice3/tunneld/__init__.py,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
168
168
  pymobiledevice3/tunneld/api.py,sha256=Lwl1OdhPTgX6Zqezy8T4dEcXRfaEPwyGNClioTx3fUc,2338
169
169
  pymobiledevice3/tunneld/server.py,sha256=dMEZAv_X-76l0vSalpq4x0IVkbE-MNGR77T-u1TiHuE,25752
170
- pymobiledevice3-6.1.5.dist-info/licenses/LICENSE,sha256=jOtLnuWt7d5Hsx6XXB2QxzrSe2sWWh3NgMfFRetluQM,35147
171
- pymobiledevice3-6.1.5.dist-info/METADATA,sha256=L3nlHKbbuGkKQuNkeheTU80XX4WAGdkpcRJ64We8d7U,17416
172
- pymobiledevice3-6.1.5.dist-info/WHEEL,sha256=_zCd3N1l69ArxyTb8rzEoP9TpbYXkqRFSNOD5OuxnTs,91
173
- pymobiledevice3-6.1.5.dist-info/entry_points.txt,sha256=jJMlOanHlVwUxcY__JwvKeWPrvBJr_wJyEq4oHIZNKE,66
174
- pymobiledevice3-6.1.5.dist-info/top_level.txt,sha256=MjZoRqcWPOh5banG-BbDOnKEfsS3kCxqV9cv-nzyg2Q,21
175
- pymobiledevice3-6.1.5.dist-info/RECORD,,
170
+ pymobiledevice3-6.2.0.dist-info/licenses/LICENSE,sha256=jOtLnuWt7d5Hsx6XXB2QxzrSe2sWWh3NgMfFRetluQM,35147
171
+ pymobiledevice3-6.2.0.dist-info/METADATA,sha256=Ds3KQTIbhKOcATx4Kx2dhONSwrQN10rPCeP5cuwfIdo,17455
172
+ pymobiledevice3-6.2.0.dist-info/WHEEL,sha256=_zCd3N1l69ArxyTb8rzEoP9TpbYXkqRFSNOD5OuxnTs,91
173
+ pymobiledevice3-6.2.0.dist-info/entry_points.txt,sha256=jJMlOanHlVwUxcY__JwvKeWPrvBJr_wJyEq4oHIZNKE,66
174
+ pymobiledevice3-6.2.0.dist-info/top_level.txt,sha256=MjZoRqcWPOh5banG-BbDOnKEfsS3kCxqV9cv-nzyg2Q,21
175
+ pymobiledevice3-6.2.0.dist-info/RECORD,,