pymobiledevice3 4.14.6__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/plist_sniffer.py +15 -15
- misc/remotexpc_sniffer.py +29 -28
- misc/understanding_idevice_protocol_layers.md +15 -10
- pymobiledevice3/__main__.py +317 -127
- pymobiledevice3/_version.py +22 -4
- pymobiledevice3/bonjour.py +358 -113
- pymobiledevice3/ca.py +253 -16
- pymobiledevice3/cli/activation.py +31 -23
- pymobiledevice3/cli/afc.py +49 -40
- pymobiledevice3/cli/amfi.py +16 -21
- pymobiledevice3/cli/apps.py +87 -42
- pymobiledevice3/cli/backup.py +160 -90
- pymobiledevice3/cli/bonjour.py +44 -40
- pymobiledevice3/cli/cli_common.py +204 -198
- pymobiledevice3/cli/companion_proxy.py +14 -14
- pymobiledevice3/cli/crash.py +105 -56
- 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 +108 -103
- pymobiledevice3/cli/mounter.py +158 -99
- pymobiledevice3/cli/notification.py +38 -26
- pymobiledevice3/cli/pcap.py +45 -24
- pymobiledevice3/cli/power_assertion.py +18 -17
- pymobiledevice3/cli/processes.py +17 -23
- pymobiledevice3/cli/profile.py +165 -109
- pymobiledevice3/cli/provision.py +35 -34
- pymobiledevice3/cli/remote.py +217 -129
- pymobiledevice3/cli/restore.py +159 -143
- pymobiledevice3/cli/springboard.py +63 -53
- pymobiledevice3/cli/syslog.py +193 -86
- pymobiledevice3/cli/usbmux.py +73 -33
- pymobiledevice3/cli/version.py +5 -7
- pymobiledevice3/cli/webinspector.py +376 -214
- pymobiledevice3/common.py +3 -1
- pymobiledevice3/exceptions.py +182 -58
- pymobiledevice3/irecv.py +52 -53
- pymobiledevice3/irecv_devices.py +1489 -464
- pymobiledevice3/lockdown.py +473 -275
- pymobiledevice3/lockdown_service_provider.py +15 -8
- pymobiledevice3/osu/os_utils.py +27 -9
- pymobiledevice3/osu/posix_util.py +34 -15
- pymobiledevice3/osu/win_util.py +14 -8
- pymobiledevice3/pair_records.py +102 -21
- pymobiledevice3/remote/common.py +8 -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 +19 -4
- pymobiledevice3/remote/core_device/file_service.py +53 -23
- pymobiledevice3/remote/remote_service_discovery.py +79 -45
- pymobiledevice3/remote/remotexpc.py +73 -44
- pymobiledevice3/remote/tunnel_service.py +442 -317
- pymobiledevice3/remote/utils.py +14 -13
- pymobiledevice3/remote/xpc_message.py +145 -125
- pymobiledevice3/resources/dsc_uuid_map.py +19 -19
- pymobiledevice3/resources/firmware_notifications.py +20 -16
- pymobiledevice3/resources/notifications.txt +144 -0
- pymobiledevice3/restore/asr.py +27 -27
- pymobiledevice3/restore/base_restore.py +110 -21
- pymobiledevice3/restore/consts.py +87 -66
- pymobiledevice3/restore/device.py +59 -12
- pymobiledevice3/restore/fdr.py +46 -48
- pymobiledevice3/restore/ftab.py +19 -19
- pymobiledevice3/restore/img4.py +163 -0
- pymobiledevice3/restore/mbn.py +587 -0
- pymobiledevice3/restore/recovery.py +151 -151
- pymobiledevice3/restore/restore.py +562 -544
- pymobiledevice3/restore/restore_options.py +131 -110
- pymobiledevice3/restore/restored_client.py +51 -31
- pymobiledevice3/restore/tss.py +385 -267
- pymobiledevice3/service_connection.py +252 -59
- pymobiledevice3/services/accessibilityaudit.py +202 -120
- pymobiledevice3/services/afc.py +962 -365
- pymobiledevice3/services/amfi.py +24 -30
- pymobiledevice3/services/companion.py +23 -19
- pymobiledevice3/services/crash_reports.py +71 -47
- pymobiledevice3/services/debugserver_applist.py +3 -3
- pymobiledevice3/services/device_arbitration.py +8 -8
- pymobiledevice3/services/device_link.py +101 -79
- pymobiledevice3/services/diagnostics.py +973 -967
- 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 +20 -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 +35 -10
- pymobiledevice3/services/dvt/instruments/screenshot.py +2 -2
- pymobiledevice3/services/dvt/instruments/sysmontap.py +15 -15
- pymobiledevice3/services/dvt/testmanaged/xcuitest.py +42 -52
- pymobiledevice3/services/file_relay.py +10 -10
- pymobiledevice3/services/heartbeat.py +9 -8
- pymobiledevice3/services/house_arrest.py +16 -15
- pymobiledevice3/services/idam.py +20 -0
- pymobiledevice3/services/installation_proxy.py +173 -81
- pymobiledevice3/services/lockdown_service.py +20 -10
- pymobiledevice3/services/misagent.py +22 -19
- pymobiledevice3/services/mobile_activation.py +147 -64
- pymobiledevice3/services/mobile_config.py +331 -294
- pymobiledevice3/services/mobile_image_mounter.py +141 -113
- pymobiledevice3/services/mobilebackup2.py +203 -145
- pymobiledevice3/services/notification_proxy.py +11 -11
- pymobiledevice3/services/os_trace.py +134 -74
- pymobiledevice3/services/pcapd.py +314 -302
- pymobiledevice3/services/power_assertion.py +10 -9
- pymobiledevice3/services/preboard.py +4 -4
- pymobiledevice3/services/remote_fetch_symbols.py +21 -14
- pymobiledevice3/services/remote_server.py +176 -146
- pymobiledevice3/services/restore_service.py +16 -16
- pymobiledevice3/services/screenshot.py +15 -12
- 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 +11 -11
- pymobiledevice3/services/web_protocol/automation_session.py +251 -239
- pymobiledevice3/services/web_protocol/cdp_screencast.py +46 -37
- pymobiledevice3/services/web_protocol/cdp_server.py +19 -19
- pymobiledevice3/services/web_protocol/cdp_target.py +411 -373
- pymobiledevice3/services/web_protocol/driver.py +114 -111
- pymobiledevice3/services/web_protocol/element.py +124 -111
- pymobiledevice3/services/web_protocol/inspector_session.py +106 -102
- pymobiledevice3/services/web_protocol/selenium_api.py +49 -49
- pymobiledevice3/services/web_protocol/session_protocol.py +18 -12
- pymobiledevice3/services/web_protocol/switch_to.py +30 -27
- pymobiledevice3/services/webinspector.py +189 -155
- pymobiledevice3/tcp_forwarder.py +87 -69
- pymobiledevice3/tunneld/__init__.py +0 -0
- pymobiledevice3/tunneld/api.py +63 -0
- pymobiledevice3/tunneld/server.py +603 -0
- pymobiledevice3/usbmux.py +198 -147
- pymobiledevice3/utils.py +14 -11
- {pymobiledevice3-4.14.6.dist-info → pymobiledevice3-7.0.6.dist-info}/METADATA +55 -28
- pymobiledevice3-7.0.6.dist-info/RECORD +188 -0
- {pymobiledevice3-4.14.6.dist-info → pymobiledevice3-7.0.6.dist-info}/WHEEL +1 -1
- pymobiledevice3/cli/developer.py +0 -1215
- pymobiledevice3/cli/diagnostics.py +0 -99
- pymobiledevice3/tunneld.py +0 -524
- pymobiledevice3-4.14.6.dist-info/RECORD +0 -168
- {pymobiledevice3-4.14.6.dist-info → pymobiledevice3-7.0.6.dist-info}/entry_points.txt +0 -0
- {pymobiledevice3-4.14.6.dist-info → pymobiledevice3-7.0.6.dist-info/licenses}/LICENSE +0 -0
- {pymobiledevice3-4.14.6.dist-info → pymobiledevice3-7.0.6.dist-info}/top_level.txt +0 -0
|
@@ -1,20 +1,34 @@
|
|
|
1
1
|
import asyncio
|
|
2
|
+
import contextlib
|
|
2
3
|
import sys
|
|
3
4
|
from asyncio import IncompleteReadError
|
|
4
|
-
from collections.abc import
|
|
5
|
+
from collections.abc import AsyncIterable
|
|
5
6
|
from typing import Optional
|
|
6
7
|
|
|
7
8
|
import IPython
|
|
8
9
|
import nest_asyncio
|
|
9
10
|
from construct import StreamError
|
|
10
|
-
from hyperframe.frame import
|
|
11
|
-
|
|
11
|
+
from hyperframe.frame import (
|
|
12
|
+
DataFrame,
|
|
13
|
+
Frame,
|
|
14
|
+
GoAwayFrame,
|
|
15
|
+
HeadersFrame,
|
|
16
|
+
RstStreamFrame,
|
|
17
|
+
SettingsFrame,
|
|
18
|
+
WindowUpdateFrame,
|
|
19
|
+
)
|
|
12
20
|
from pygments import formatters, highlight, lexers
|
|
13
21
|
from traitlets.config import Config
|
|
14
22
|
|
|
15
|
-
from pymobiledevice3.exceptions import StreamClosedError
|
|
16
|
-
from pymobiledevice3.remote.xpc_message import
|
|
17
|
-
|
|
23
|
+
from pymobiledevice3.exceptions import ProtocolError, StreamClosedError
|
|
24
|
+
from pymobiledevice3.remote.xpc_message import (
|
|
25
|
+
XpcFlags,
|
|
26
|
+
XpcInt64Type,
|
|
27
|
+
XpcUInt64Type,
|
|
28
|
+
XpcWrapper,
|
|
29
|
+
create_xpc_wrapper,
|
|
30
|
+
decode_xpc_object,
|
|
31
|
+
)
|
|
18
32
|
|
|
19
33
|
# Extracted by sniffing `remoted` traffic via Wireshark
|
|
20
34
|
DEFAULT_SETTINGS_MAX_CONCURRENT_STREAMS = 100
|
|
@@ -22,11 +36,13 @@ DEFAULT_SETTINGS_INITIAL_WINDOW_SIZE = 1048576
|
|
|
22
36
|
DEFAULT_WIN_SIZE_INCR = 983041
|
|
23
37
|
|
|
24
38
|
FRAME_HEADER_SIZE = 9
|
|
25
|
-
HTTP2_MAGIC = b
|
|
39
|
+
HTTP2_MAGIC = b"PRI * HTTP/2.0\r\n\r\nSM\r\n\r\n"
|
|
26
40
|
|
|
27
41
|
ROOT_CHANNEL = 1
|
|
28
42
|
REPLY_CHANNEL = 3
|
|
29
43
|
|
|
44
|
+
FIRST_REPLY_TIMEOUT = 3
|
|
45
|
+
|
|
30
46
|
SHELL_USAGE = """
|
|
31
47
|
# This shell allows you to communicate directly with every RemoteXPC service.
|
|
32
48
|
|
|
@@ -37,14 +53,14 @@ resp = await client.send_receive_request({"Command": "DoSomething"})
|
|
|
37
53
|
|
|
38
54
|
class RemoteXPCConnection:
|
|
39
55
|
def __init__(self, address: tuple[str, int]):
|
|
40
|
-
self._previous_frame_data = b
|
|
56
|
+
self._previous_frame_data = b""
|
|
41
57
|
self.address = address
|
|
42
58
|
self.next_message_id: dict[int, int] = {ROOT_CHANNEL: 0, REPLY_CHANNEL: 0}
|
|
43
59
|
self.peer_info = None
|
|
44
60
|
self._reader: Optional[asyncio.StreamReader] = None
|
|
45
61
|
self._writer: Optional[asyncio.StreamWriter] = None
|
|
46
62
|
|
|
47
|
-
async def __aenter__(self) ->
|
|
63
|
+
async def __aenter__(self) -> "RemoteXPCConnection":
|
|
48
64
|
await self.connect()
|
|
49
65
|
return self
|
|
50
66
|
|
|
@@ -53,33 +69,36 @@ class RemoteXPCConnection:
|
|
|
53
69
|
|
|
54
70
|
async def connect(self) -> None:
|
|
55
71
|
self._reader, self._writer = await asyncio.open_connection(self.address[0], self.address[1])
|
|
56
|
-
|
|
72
|
+
try:
|
|
73
|
+
await self._do_handshake()
|
|
74
|
+
except Exception:
|
|
75
|
+
await self.close()
|
|
76
|
+
raise
|
|
57
77
|
|
|
58
78
|
async def close(self) -> None:
|
|
59
79
|
if self._writer is None:
|
|
60
80
|
return
|
|
61
81
|
self._writer.close()
|
|
62
|
-
|
|
82
|
+
with contextlib.suppress(ConnectionResetError):
|
|
63
83
|
await self._writer.wait_closed()
|
|
64
|
-
except ConnectionResetError:
|
|
65
|
-
pass
|
|
66
84
|
self._writer = None
|
|
67
85
|
self._reader = None
|
|
68
86
|
|
|
69
87
|
async def send_request(self, data: dict, wanting_reply: bool = False) -> None:
|
|
70
88
|
xpc_wrapper = create_xpc_wrapper(
|
|
71
|
-
data, message_id=self.next_message_id[ROOT_CHANNEL], wanting_reply=wanting_reply
|
|
89
|
+
data, message_id=self.next_message_id[ROOT_CHANNEL], wanting_reply=wanting_reply
|
|
90
|
+
)
|
|
72
91
|
self._writer.write(DataFrame(stream_id=ROOT_CHANNEL, data=xpc_wrapper).serialize())
|
|
73
92
|
await self._writer.drain()
|
|
74
93
|
|
|
75
|
-
async def iter_file_chunks(self, total_size: int, file_idx: int = 0) ->
|
|
94
|
+
async def iter_file_chunks(self, total_size: int, file_idx: int = 0) -> AsyncIterable[bytes]:
|
|
76
95
|
stream_id = (file_idx + 1) * 2
|
|
77
96
|
await self._open_channel(stream_id, XpcFlags.FILE_TX_STREAM_RESPONSE)
|
|
78
97
|
size = 0
|
|
79
98
|
while size < total_size:
|
|
80
99
|
frame = await self._receive_next_data_frame()
|
|
81
100
|
|
|
82
|
-
if
|
|
101
|
+
if "END_STREAM" in frame.flags:
|
|
83
102
|
continue
|
|
84
103
|
|
|
85
104
|
if frame.stream_id != stream_id:
|
|
@@ -87,12 +106,12 @@ class RemoteXPCConnection:
|
|
|
87
106
|
if xpc_wrapper.flags.FILE_TX_STREAM_REQUEST:
|
|
88
107
|
continue
|
|
89
108
|
|
|
90
|
-
assert frame.stream_id == stream_id, f
|
|
109
|
+
assert frame.stream_id == stream_id, f"got {frame.stream_id} instead of {stream_id}"
|
|
91
110
|
size += len(frame.data)
|
|
92
111
|
yield frame.data
|
|
93
112
|
|
|
94
113
|
async def receive_file(self, total_size: int) -> bytes:
|
|
95
|
-
buf = b
|
|
114
|
+
buf = b""
|
|
96
115
|
async for chunk in self.iter_file_chunks(total_size):
|
|
97
116
|
buf += chunk
|
|
98
117
|
return buf
|
|
@@ -102,7 +121,7 @@ class RemoteXPCConnection:
|
|
|
102
121
|
frame = await self._receive_next_data_frame()
|
|
103
122
|
try:
|
|
104
123
|
xpc_message = XpcWrapper.parse(self._previous_frame_data + frame.data).message
|
|
105
|
-
self._previous_frame_data = b
|
|
124
|
+
self._previous_frame_data = b""
|
|
106
125
|
except StreamError:
|
|
107
126
|
self._previous_frame_data += frame.data
|
|
108
127
|
continue
|
|
@@ -119,46 +138,56 @@ class RemoteXPCConnection:
|
|
|
119
138
|
|
|
120
139
|
def shell(self) -> None:
|
|
121
140
|
nest_asyncio.apply(asyncio.get_running_loop())
|
|
122
|
-
sys.argv = [
|
|
141
|
+
sys.argv = ["a"]
|
|
123
142
|
config = Config()
|
|
124
|
-
config.InteractiveShellApp.exec_lines = [
|
|
125
|
-
print(highlight(SHELL_USAGE, lexers.PythonLexer(),
|
|
126
|
-
|
|
127
|
-
|
|
128
|
-
|
|
129
|
-
|
|
130
|
-
|
|
131
|
-
|
|
143
|
+
config.InteractiveShellApp.exec_lines = ["%autoawait asyncio"]
|
|
144
|
+
print(highlight(SHELL_USAGE, lexers.PythonLexer(), formatters.Terminal256Formatter(style="native")))
|
|
145
|
+
IPython.start_ipython(
|
|
146
|
+
config=config,
|
|
147
|
+
user_ns={
|
|
148
|
+
"client": self,
|
|
149
|
+
"XpcInt64Type": XpcInt64Type,
|
|
150
|
+
"XpcUInt64Type": XpcUInt64Type,
|
|
151
|
+
},
|
|
152
|
+
)
|
|
132
153
|
|
|
133
154
|
async def _do_handshake(self) -> None:
|
|
134
155
|
self._writer.write(HTTP2_MAGIC)
|
|
135
156
|
await self._writer.drain()
|
|
136
157
|
|
|
137
158
|
# send h2 headers
|
|
138
|
-
await self._send_frame(
|
|
139
|
-
SettingsFrame
|
|
140
|
-
|
|
141
|
-
|
|
159
|
+
await self._send_frame(
|
|
160
|
+
SettingsFrame(
|
|
161
|
+
settings={
|
|
162
|
+
SettingsFrame.MAX_CONCURRENT_STREAMS: DEFAULT_SETTINGS_MAX_CONCURRENT_STREAMS,
|
|
163
|
+
SettingsFrame.INITIAL_WINDOW_SIZE: DEFAULT_SETTINGS_INITIAL_WINDOW_SIZE,
|
|
164
|
+
}
|
|
165
|
+
)
|
|
166
|
+
)
|
|
142
167
|
await self._send_frame(WindowUpdateFrame(stream_id=0, window_increment=DEFAULT_WIN_SIZE_INCR))
|
|
143
|
-
await self._send_frame(HeadersFrame(stream_id=ROOT_CHANNEL, flags=[
|
|
168
|
+
await self._send_frame(HeadersFrame(stream_id=ROOT_CHANNEL, flags=["END_HEADERS"]))
|
|
144
169
|
|
|
145
170
|
# send first actual requests
|
|
146
171
|
await self.send_request({})
|
|
147
|
-
await self._send_frame(
|
|
148
|
-
|
|
172
|
+
await self._send_frame(
|
|
173
|
+
DataFrame(stream_id=ROOT_CHANNEL, data=XpcWrapper.build({"size": 0, "flags": 0x0201, "payload": None}))
|
|
174
|
+
)
|
|
149
175
|
self.next_message_id[ROOT_CHANNEL] += 1
|
|
150
176
|
await self._open_channel(REPLY_CHANNEL, XpcFlags.INIT_HANDSHAKE)
|
|
151
177
|
self.next_message_id[REPLY_CHANNEL] += 1
|
|
152
178
|
|
|
153
|
-
|
|
179
|
+
settings_frame = await asyncio.wait_for(self._receive_frame(), FIRST_REPLY_TIMEOUT)
|
|
180
|
+
if not isinstance(settings_frame, SettingsFrame):
|
|
181
|
+
raise ProtocolError(f"Got unexpected frame: {settings_frame} instead of a SettingsFrame")
|
|
154
182
|
|
|
155
|
-
await self._send_frame(SettingsFrame(flags=[
|
|
183
|
+
await self._send_frame(SettingsFrame(flags=["ACK"]))
|
|
156
184
|
|
|
157
185
|
async def _open_channel(self, stream_id: int, flags: int) -> None:
|
|
158
186
|
flags |= XpcFlags.ALWAYS_SET
|
|
159
|
-
await self._send_frame(HeadersFrame(stream_id=stream_id, flags=[
|
|
187
|
+
await self._send_frame(HeadersFrame(stream_id=stream_id, flags=["END_HEADERS"]))
|
|
160
188
|
await self._send_frame(
|
|
161
|
-
DataFrame(stream_id=stream_id, data=XpcWrapper.build({
|
|
189
|
+
DataFrame(stream_id=stream_id, data=XpcWrapper.build({"size": 0, "flags": flags, "payload": None}))
|
|
190
|
+
)
|
|
162
191
|
|
|
163
192
|
async def _send_frame(self, frame: Frame) -> None:
|
|
164
193
|
self._writer.write(frame.serialize())
|
|
@@ -169,9 +198,9 @@ class RemoteXPCConnection:
|
|
|
169
198
|
frame = await self._receive_frame()
|
|
170
199
|
|
|
171
200
|
if isinstance(frame, GoAwayFrame):
|
|
172
|
-
raise StreamClosedError(f
|
|
201
|
+
raise StreamClosedError(f"Got {frame}")
|
|
173
202
|
if isinstance(frame, RstStreamFrame):
|
|
174
|
-
raise StreamClosedError(f
|
|
203
|
+
raise StreamClosedError(f"Got {frame}")
|
|
175
204
|
if not isinstance(frame, DataFrame):
|
|
176
205
|
continue
|
|
177
206
|
|
|
@@ -188,11 +217,11 @@ class RemoteXPCConnection:
|
|
|
188
217
|
return frame
|
|
189
218
|
|
|
190
219
|
async def _recvall(self, size: int) -> bytes:
|
|
191
|
-
data = b
|
|
220
|
+
data = b""
|
|
192
221
|
while len(data) < size:
|
|
193
222
|
try:
|
|
194
223
|
chunk = await self._reader.readexactly(size - len(data))
|
|
195
|
-
except IncompleteReadError:
|
|
196
|
-
raise ConnectionAbortedError()
|
|
224
|
+
except IncompleteReadError as e:
|
|
225
|
+
raise ConnectionAbortedError() from e
|
|
197
226
|
data += chunk
|
|
198
227
|
return data
|