@ajksunkang-aios/kgraph-linux-x64 0.1.2 → 0.1.3

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.
Files changed (196) hide show
  1. package/bin/kgraph-launcher +15 -3
  2. package/lib/kgraph/scripts/build-bundle.sh +17 -4
  3. package/lib/site-packages/outcome/__init__.py +20 -0
  4. package/lib/site-packages/outcome/_impl.py +239 -0
  5. package/lib/site-packages/outcome/_util.py +33 -0
  6. package/lib/site-packages/outcome/_version.py +7 -0
  7. package/lib/site-packages/outcome/py.typed +0 -0
  8. package/lib/site-packages/outcome-1.3.0.post0.dist-info/INSTALLER +1 -0
  9. package/lib/site-packages/outcome-1.3.0.post0.dist-info/LICENSE +3 -0
  10. package/lib/site-packages/outcome-1.3.0.post0.dist-info/LICENSE.APACHE2 +202 -0
  11. package/lib/site-packages/outcome-1.3.0.post0.dist-info/LICENSE.MIT +20 -0
  12. package/lib/site-packages/outcome-1.3.0.post0.dist-info/METADATA +63 -0
  13. package/lib/site-packages/outcome-1.3.0.post0.dist-info/RECORD +13 -0
  14. package/lib/site-packages/outcome-1.3.0.post0.dist-info/WHEEL +6 -0
  15. package/lib/site-packages/outcome-1.3.0.post0.dist-info/top_level.txt +1 -0
  16. package/lib/site-packages/sniffio/__init__.py +17 -0
  17. package/lib/site-packages/sniffio/_impl.py +95 -0
  18. package/lib/site-packages/sniffio/_tests/__init__.py +0 -0
  19. package/lib/site-packages/sniffio/_tests/test_sniffio.py +84 -0
  20. package/lib/site-packages/sniffio/_version.py +3 -0
  21. package/lib/site-packages/sniffio/py.typed +0 -0
  22. package/lib/site-packages/sniffio-1.3.1.dist-info/INSTALLER +1 -0
  23. package/lib/site-packages/sniffio-1.3.1.dist-info/LICENSE +3 -0
  24. package/lib/site-packages/sniffio-1.3.1.dist-info/LICENSE.APACHE2 +202 -0
  25. package/lib/site-packages/sniffio-1.3.1.dist-info/LICENSE.MIT +20 -0
  26. package/lib/site-packages/sniffio-1.3.1.dist-info/METADATA +104 -0
  27. package/lib/site-packages/sniffio-1.3.1.dist-info/RECORD +14 -0
  28. package/lib/site-packages/sniffio-1.3.1.dist-info/WHEEL +5 -0
  29. package/lib/site-packages/sniffio-1.3.1.dist-info/top_level.txt +1 -0
  30. package/lib/site-packages/sortedcontainers/__init__.py +74 -0
  31. package/lib/site-packages/sortedcontainers/sorteddict.py +812 -0
  32. package/lib/site-packages/sortedcontainers/sortedlist.py +2646 -0
  33. package/lib/site-packages/sortedcontainers/sortedset.py +733 -0
  34. package/lib/site-packages/sortedcontainers-2.4.0.dist-info/INSTALLER +1 -0
  35. package/lib/site-packages/sortedcontainers-2.4.0.dist-info/LICENSE +13 -0
  36. package/lib/site-packages/sortedcontainers-2.4.0.dist-info/METADATA +264 -0
  37. package/lib/site-packages/sortedcontainers-2.4.0.dist-info/RECORD +10 -0
  38. package/lib/site-packages/sortedcontainers-2.4.0.dist-info/WHEEL +6 -0
  39. package/lib/site-packages/sortedcontainers-2.4.0.dist-info/top_level.txt +1 -0
  40. package/lib/site-packages/trio/__init__.py +133 -0
  41. package/lib/site-packages/trio/__main__.py +3 -0
  42. package/lib/site-packages/trio/_abc.py +714 -0
  43. package/lib/site-packages/trio/_channel.py +610 -0
  44. package/lib/site-packages/trio/_core/__init__.py +94 -0
  45. package/lib/site-packages/trio/_core/_asyncgens.py +243 -0
  46. package/lib/site-packages/trio/_core/_concat_tb.py +26 -0
  47. package/lib/site-packages/trio/_core/_entry_queue.py +223 -0
  48. package/lib/site-packages/trio/_core/_exceptions.py +169 -0
  49. package/lib/site-packages/trio/_core/_generated_instrumentation.py +50 -0
  50. package/lib/site-packages/trio/_core/_generated_io_epoll.py +98 -0
  51. package/lib/site-packages/trio/_core/_generated_io_kqueue.py +153 -0
  52. package/lib/site-packages/trio/_core/_generated_io_windows.py +204 -0
  53. package/lib/site-packages/trio/_core/_generated_run.py +269 -0
  54. package/lib/site-packages/trio/_core/_generated_windows_ffi.py +10 -0
  55. package/lib/site-packages/trio/_core/_instrumentation.py +117 -0
  56. package/lib/site-packages/trio/_core/_io_common.py +31 -0
  57. package/lib/site-packages/trio/_core/_io_epoll.py +385 -0
  58. package/lib/site-packages/trio/_core/_io_kqueue.py +292 -0
  59. package/lib/site-packages/trio/_core/_io_windows.py +1036 -0
  60. package/lib/site-packages/trio/_core/_ki.py +271 -0
  61. package/lib/site-packages/trio/_core/_local.py +104 -0
  62. package/lib/site-packages/trio/_core/_mock_clock.py +165 -0
  63. package/lib/site-packages/trio/_core/_parking_lot.py +317 -0
  64. package/lib/site-packages/trio/_core/_run.py +3148 -0
  65. package/lib/site-packages/trio/_core/_run_context.py +15 -0
  66. package/lib/site-packages/trio/_core/_tests/__init__.py +0 -0
  67. package/lib/site-packages/trio/_core/_tests/test_asyncgen.py +339 -0
  68. package/lib/site-packages/trio/_core/_tests/test_cancelled.py +222 -0
  69. package/lib/site-packages/trio/_core/_tests/test_exceptiongroup_gc.py +103 -0
  70. package/lib/site-packages/trio/_core/_tests/test_guest_mode.py +755 -0
  71. package/lib/site-packages/trio/_core/_tests/test_instrumentation.py +315 -0
  72. package/lib/site-packages/trio/_core/_tests/test_io.py +522 -0
  73. package/lib/site-packages/trio/_core/_tests/test_ki.py +703 -0
  74. package/lib/site-packages/trio/_core/_tests/test_local.py +118 -0
  75. package/lib/site-packages/trio/_core/_tests/test_mock_clock.py +193 -0
  76. package/lib/site-packages/trio/_core/_tests/test_parking_lot.py +389 -0
  77. package/lib/site-packages/trio/_core/_tests/test_run.py +3024 -0
  78. package/lib/site-packages/trio/_core/_tests/test_thread_cache.py +227 -0
  79. package/lib/site-packages/trio/_core/_tests/test_tutil.py +13 -0
  80. package/lib/site-packages/trio/_core/_tests/test_unbounded_queue.py +154 -0
  81. package/lib/site-packages/trio/_core/_tests/test_windows.py +305 -0
  82. package/lib/site-packages/trio/_core/_tests/tutil.py +117 -0
  83. package/lib/site-packages/trio/_core/_tests/type_tests/nursery_start.py +79 -0
  84. package/lib/site-packages/trio/_core/_tests/type_tests/run.py +51 -0
  85. package/lib/site-packages/trio/_core/_thread_cache.py +317 -0
  86. package/lib/site-packages/trio/_core/_traps.py +318 -0
  87. package/lib/site-packages/trio/_core/_unbounded_queue.py +163 -0
  88. package/lib/site-packages/trio/_core/_wakeup_socketpair.py +75 -0
  89. package/lib/site-packages/trio/_core/_windows_cffi.py +313 -0
  90. package/lib/site-packages/trio/_deprecate.py +171 -0
  91. package/lib/site-packages/trio/_dtls.py +1380 -0
  92. package/lib/site-packages/trio/_file_io.py +513 -0
  93. package/lib/site-packages/trio/_highlevel_generic.py +125 -0
  94. package/lib/site-packages/trio/_highlevel_open_tcp_listeners.py +251 -0
  95. package/lib/site-packages/trio/_highlevel_open_tcp_stream.py +397 -0
  96. package/lib/site-packages/trio/_highlevel_open_unix_stream.py +65 -0
  97. package/lib/site-packages/trio/_highlevel_serve_listeners.py +148 -0
  98. package/lib/site-packages/trio/_highlevel_socket.py +423 -0
  99. package/lib/site-packages/trio/_highlevel_ssl_helpers.py +180 -0
  100. package/lib/site-packages/trio/_path.py +289 -0
  101. package/lib/site-packages/trio/_repl.py +159 -0
  102. package/lib/site-packages/trio/_signals.py +185 -0
  103. package/lib/site-packages/trio/_socket.py +1326 -0
  104. package/lib/site-packages/trio/_ssl.py +964 -0
  105. package/lib/site-packages/trio/_subprocess.py +1178 -0
  106. package/lib/site-packages/trio/_subprocess_platform/__init__.py +123 -0
  107. package/lib/site-packages/trio/_subprocess_platform/kqueue.py +48 -0
  108. package/lib/site-packages/trio/_subprocess_platform/waitid.py +113 -0
  109. package/lib/site-packages/trio/_subprocess_platform/windows.py +11 -0
  110. package/lib/site-packages/trio/_sync.py +908 -0
  111. package/lib/site-packages/trio/_tests/__init__.py +0 -0
  112. package/lib/site-packages/trio/_tests/astrill-codesigning-cert.cer +0 -0
  113. package/lib/site-packages/trio/_tests/check_type_completeness.py +247 -0
  114. package/lib/site-packages/trio/_tests/module_with_deprecations.py +22 -0
  115. package/lib/site-packages/trio/_tests/pytest_plugin.py +54 -0
  116. package/lib/site-packages/trio/_tests/test_abc.py +72 -0
  117. package/lib/site-packages/trio/_tests/test_channel.py +750 -0
  118. package/lib/site-packages/trio/_tests/test_contextvars.py +56 -0
  119. package/lib/site-packages/trio/_tests/test_deprecate.py +277 -0
  120. package/lib/site-packages/trio/_tests/test_deprecate_strict_exception_groups_false.py +64 -0
  121. package/lib/site-packages/trio/_tests/test_dtls.py +950 -0
  122. package/lib/site-packages/trio/_tests/test_exports.py +626 -0
  123. package/lib/site-packages/trio/_tests/test_fakenet.py +317 -0
  124. package/lib/site-packages/trio/_tests/test_file_io.py +269 -0
  125. package/lib/site-packages/trio/_tests/test_highlevel_generic.py +98 -0
  126. package/lib/site-packages/trio/_tests/test_highlevel_open_tcp_listeners.py +419 -0
  127. package/lib/site-packages/trio/_tests/test_highlevel_open_tcp_stream.py +693 -0
  128. package/lib/site-packages/trio/_tests/test_highlevel_open_unix_stream.py +86 -0
  129. package/lib/site-packages/trio/_tests/test_highlevel_serve_listeners.py +186 -0
  130. package/lib/site-packages/trio/_tests/test_highlevel_socket.py +336 -0
  131. package/lib/site-packages/trio/_tests/test_highlevel_ssl_helpers.py +169 -0
  132. package/lib/site-packages/trio/_tests/test_path.py +279 -0
  133. package/lib/site-packages/trio/_tests/test_repl.py +428 -0
  134. package/lib/site-packages/trio/_tests/test_scheduler_determinism.py +47 -0
  135. package/lib/site-packages/trio/_tests/test_signals.py +186 -0
  136. package/lib/site-packages/trio/_tests/test_socket.py +1253 -0
  137. package/lib/site-packages/trio/_tests/test_ssl.py +1371 -0
  138. package/lib/site-packages/trio/_tests/test_subprocess.py +767 -0
  139. package/lib/site-packages/trio/_tests/test_sync.py +735 -0
  140. package/lib/site-packages/trio/_tests/test_testing.py +682 -0
  141. package/lib/site-packages/trio/_tests/test_testing_raisesgroup.py +1128 -0
  142. package/lib/site-packages/trio/_tests/test_threads.py +1173 -0
  143. package/lib/site-packages/trio/_tests/test_timeouts.py +281 -0
  144. package/lib/site-packages/trio/_tests/test_tracing.py +88 -0
  145. package/lib/site-packages/trio/_tests/test_trio.py +8 -0
  146. package/lib/site-packages/trio/_tests/test_unix_pipes.py +288 -0
  147. package/lib/site-packages/trio/_tests/test_util.py +349 -0
  148. package/lib/site-packages/trio/_tests/test_wait_for_object.py +225 -0
  149. package/lib/site-packages/trio/_tests/test_windows_pipes.py +112 -0
  150. package/lib/site-packages/trio/_tests/tools/__init__.py +0 -0
  151. package/lib/site-packages/trio/_tests/tools/test_gen_exports.py +179 -0
  152. package/lib/site-packages/trio/_tests/tools/test_mypy_annotate.py +140 -0
  153. package/lib/site-packages/trio/_tests/tools/test_sync_requirements.py +80 -0
  154. package/lib/site-packages/trio/_tests/type_tests/check_wraps.py +9 -0
  155. package/lib/site-packages/trio/_tests/type_tests/open_memory_channel.py +4 -0
  156. package/lib/site-packages/trio/_tests/type_tests/path.py +140 -0
  157. package/lib/site-packages/trio/_tests/type_tests/subprocesses.py +23 -0
  158. package/lib/site-packages/trio/_tests/type_tests/task_status.py +29 -0
  159. package/lib/site-packages/trio/_threads.py +610 -0
  160. package/lib/site-packages/trio/_timeouts.py +197 -0
  161. package/lib/site-packages/trio/_tools/__init__.py +0 -0
  162. package/lib/site-packages/trio/_tools/gen_exports.py +401 -0
  163. package/lib/site-packages/trio/_tools/mypy_annotate.py +126 -0
  164. package/lib/site-packages/trio/_tools/sync_requirements.py +98 -0
  165. package/lib/site-packages/trio/_tools/windows_ffi_build.py +220 -0
  166. package/lib/site-packages/trio/_unix_pipes.py +197 -0
  167. package/lib/site-packages/trio/_util.py +385 -0
  168. package/lib/site-packages/trio/_version.py +3 -0
  169. package/lib/site-packages/trio/_wait_for_object.py +67 -0
  170. package/lib/site-packages/trio/_windows_pipes.py +144 -0
  171. package/lib/site-packages/trio/abc.py +23 -0
  172. package/lib/site-packages/trio/from_thread.py +13 -0
  173. package/lib/site-packages/trio/lowlevel.py +95 -0
  174. package/lib/site-packages/trio/py.typed +0 -0
  175. package/lib/site-packages/trio/socket.py +602 -0
  176. package/lib/site-packages/trio/testing/__init__.py +58 -0
  177. package/lib/site-packages/trio/testing/_check_streams.py +570 -0
  178. package/lib/site-packages/trio/testing/_checkpoints.py +69 -0
  179. package/lib/site-packages/trio/testing/_fake_net.py +584 -0
  180. package/lib/site-packages/trio/testing/_memory_streams.py +633 -0
  181. package/lib/site-packages/trio/testing/_network.py +36 -0
  182. package/lib/site-packages/trio/testing/_raises_group.py +1015 -0
  183. package/lib/site-packages/trio/testing/_sequencer.py +87 -0
  184. package/lib/site-packages/trio/testing/_trio_test.py +50 -0
  185. package/lib/site-packages/trio/to_thread.py +4 -0
  186. package/lib/site-packages/trio-0.33.0.dist-info/INSTALLER +1 -0
  187. package/lib/site-packages/trio-0.33.0.dist-info/METADATA +186 -0
  188. package/lib/site-packages/trio-0.33.0.dist-info/RECORD +156 -0
  189. package/lib/site-packages/trio-0.33.0.dist-info/REQUESTED +0 -0
  190. package/lib/site-packages/trio-0.33.0.dist-info/WHEEL +5 -0
  191. package/lib/site-packages/trio-0.33.0.dist-info/entry_points.txt +2 -0
  192. package/lib/site-packages/trio-0.33.0.dist-info/licenses/LICENSE +3 -0
  193. package/lib/site-packages/trio-0.33.0.dist-info/licenses/LICENSE.APACHE2 +202 -0
  194. package/lib/site-packages/trio-0.33.0.dist-info/licenses/LICENSE.MIT +22 -0
  195. package/lib/site-packages/trio-0.33.0.dist-info/top_level.txt +1 -0
  196. package/package.json +1 -1
@@ -0,0 +1,950 @@
1
+ from __future__ import annotations
2
+
3
+ import random
4
+ from contextlib import asynccontextmanager
5
+ from itertools import count
6
+ from typing import TYPE_CHECKING, NoReturn
7
+
8
+ import attrs
9
+ import pytest
10
+
11
+ from trio._tests.pytest_plugin import skip_if_optional_else_raise
12
+
13
+ try:
14
+ import trustme
15
+ from OpenSSL import SSL
16
+ except ImportError as error:
17
+ skip_if_optional_else_raise(error)
18
+
19
+
20
+ import trio
21
+ import trio.testing
22
+ from trio import DTLSChannel, DTLSEndpoint
23
+ from trio.testing._fake_net import FakeNet, UDPPacket
24
+
25
+ from .._core._tests.tutil import binds_ipv6, gc_collect_harder, slow
26
+
27
+ if TYPE_CHECKING:
28
+ from collections.abc import AsyncGenerator
29
+
30
+ ca = trustme.CA()
31
+ server_cert = ca.issue_cert("example.com")
32
+
33
+
34
+ @pytest.fixture
35
+ def server_ctx() -> SSL.Context:
36
+ ctx = SSL.Context(SSL.DTLS_METHOD)
37
+ server_cert.configure_cert(ctx)
38
+ return ctx
39
+
40
+
41
+ def client_ctx_fn() -> SSL.Context:
42
+ ctx = SSL.Context(SSL.DTLS_METHOD)
43
+ ca.configure_trust(ctx)
44
+ return ctx
45
+
46
+
47
+ @pytest.fixture
48
+ def client_ctx() -> SSL.Context:
49
+ return client_ctx_fn()
50
+
51
+
52
+ parametrize_ipv6 = pytest.mark.parametrize(
53
+ "ipv6",
54
+ [False, pytest.param(True, marks=binds_ipv6)],
55
+ ids=["ipv4", "ipv6"],
56
+ )
57
+
58
+
59
+ def endpoint(**kwargs: int | bool) -> DTLSEndpoint:
60
+ ipv6 = kwargs.pop("ipv6", False)
61
+ family = trio.socket.AF_INET6 if ipv6 else trio.socket.AF_INET
62
+ sock = trio.socket.socket(type=trio.socket.SOCK_DGRAM, family=family)
63
+ return DTLSEndpoint(sock, **kwargs)
64
+
65
+
66
+ @asynccontextmanager
67
+ async def dtls_echo_server(
68
+ *,
69
+ server_ctx: SSL.Context,
70
+ autocancel: bool = True,
71
+ mtu: int | None = None,
72
+ ipv6: bool = False,
73
+ ) -> AsyncGenerator[tuple[DTLSEndpoint, tuple[str, int]], None]:
74
+ with endpoint(ipv6=ipv6) as server:
75
+ localhost = "::1" if ipv6 else "127.0.0.1"
76
+ await server.socket.bind((localhost, 0))
77
+ async with trio.open_nursery() as nursery:
78
+
79
+ async def echo_handler(dtls_channel: DTLSChannel) -> None:
80
+ print(
81
+ "echo handler started: "
82
+ f"server {dtls_channel.endpoint.socket.getsockname()!r} "
83
+ f"client {dtls_channel.peer_address!r}",
84
+ )
85
+ if mtu is not None:
86
+ dtls_channel.set_ciphertext_mtu(mtu)
87
+ try:
88
+ print("server starting do_handshake")
89
+ await dtls_channel.do_handshake()
90
+ print("server finished do_handshake")
91
+ # no branch for leaving this for loop because we only leave
92
+ # a channel by cancellation.
93
+ async for packet in dtls_channel: # pragma: no branch
94
+ print(f"echoing {packet!r} -> {dtls_channel.peer_address!r}")
95
+ await dtls_channel.send(packet)
96
+ except trio.BrokenResourceError: # pragma: no cover
97
+ print("echo handler channel broken")
98
+
99
+ await nursery.start(server.serve, server_ctx, echo_handler)
100
+
101
+ yield server, server.socket.getsockname()
102
+
103
+ if autocancel:
104
+ nursery.cancel_scope.cancel()
105
+
106
+
107
+ @parametrize_ipv6
108
+ async def test_smoke(
109
+ ipv6: bool, server_ctx: SSL.Context, client_ctx: SSL.Context
110
+ ) -> None:
111
+ async with dtls_echo_server(ipv6=ipv6, server_ctx=server_ctx) as (
112
+ _server_endpoint,
113
+ address,
114
+ ):
115
+ with endpoint(ipv6=ipv6) as client_endpoint:
116
+ client_channel = client_endpoint.connect(address, client_ctx)
117
+ with pytest.raises(trio.NeedHandshakeError):
118
+ client_channel.get_cleartext_mtu()
119
+
120
+ await client_channel.do_handshake()
121
+ await client_channel.send(b"hello")
122
+ assert await client_channel.receive() == b"hello"
123
+ await client_channel.send(b"goodbye")
124
+ assert await client_channel.receive() == b"goodbye"
125
+
126
+ with pytest.raises(
127
+ ValueError,
128
+ match=r"^openssl doesn't support sending empty DTLS packets$",
129
+ ):
130
+ await client_channel.send(b"")
131
+
132
+ client_channel.set_ciphertext_mtu(1234)
133
+ cleartext_mtu_1234 = client_channel.get_cleartext_mtu()
134
+ client_channel.set_ciphertext_mtu(4321)
135
+ assert client_channel.get_cleartext_mtu() > cleartext_mtu_1234
136
+ client_channel.set_ciphertext_mtu(1234)
137
+ assert client_channel.get_cleartext_mtu() == cleartext_mtu_1234
138
+
139
+
140
+ @slow
141
+ async def test_handshake_over_terrible_network(
142
+ autojump_clock: trio.testing.MockClock,
143
+ server_ctx: SSL.Context,
144
+ ) -> None:
145
+ HANDSHAKES = 100
146
+ r = random.Random(0)
147
+ fn = FakeNet()
148
+ fn.enable()
149
+ # avoid spurious timeouts on slow machines
150
+ autojump_clock.autojump_threshold = 0.001
151
+
152
+ async with dtls_echo_server(server_ctx=server_ctx) as (_, address):
153
+ async with trio.open_nursery() as nursery:
154
+
155
+ async def route_packet(packet: UDPPacket) -> None:
156
+ while True:
157
+ op = r.choices(
158
+ ["deliver", "drop", "dupe", "delay"],
159
+ weights=[0.7, 0.1, 0.1, 0.1],
160
+ )[0]
161
+ print(f"{packet.source} -> {packet.destination}: {op}")
162
+ if op == "drop":
163
+ return
164
+ elif op == "dupe":
165
+ fn.send_packet(packet)
166
+ elif op == "delay":
167
+ await trio.sleep(r.random() * 3)
168
+ # I wanted to test random packet corruption too, but it turns out
169
+ # openssl has a bug in the following scenario:
170
+ #
171
+ # - client sends ClientHello
172
+ # - server sends HelloVerifyRequest with cookie -- but cookie is
173
+ # invalid b/c either the ClientHello or HelloVerifyRequest was
174
+ # corrupted
175
+ # - client re-sends ClientHello with invalid cookie
176
+ # - server replies with new HelloVerifyRequest and correct cookie
177
+ #
178
+ # At this point, the client *should* switch to the new, valid
179
+ # cookie. But OpenSSL doesn't; it stubbornly insists on re-sending
180
+ # the original, invalid cookie over and over. In theory we could
181
+ # work around this by detecting cookie changes and starting over
182
+ # with a whole new SSL object, but (a) it doesn't seem worth it, (b)
183
+ # when I tried then I ran into another issue where OpenSSL got stuck
184
+ # in an infinite loop sending alerts over and over, which I didn't
185
+ # dig into because see (a).
186
+ #
187
+ # elif op == "distort":
188
+ # payload = bytearray(packet.payload)
189
+ # payload[r.randrange(len(payload))] ^= 1 << r.randrange(8)
190
+ # packet = attrs.evolve(packet, payload=payload)
191
+ else:
192
+ assert op == "deliver"
193
+ print(
194
+ f"{packet.source} -> {packet.destination}: delivered"
195
+ f" {packet.payload.hex()}",
196
+ )
197
+ fn.deliver_packet(packet)
198
+ break
199
+
200
+ def route_packet_wrapper(packet: UDPPacket) -> None:
201
+ try: # noqa: SIM105 # suppressible-exception
202
+ nursery.start_soon(route_packet, packet)
203
+ except RuntimeError: # pragma: no cover
204
+ # We're exiting the nursery, so any remaining packets can just get
205
+ # dropped
206
+ pass
207
+
208
+ fn.route_packet = route_packet_wrapper # type: ignore[assignment] # TODO: Fix FakeNet typing
209
+
210
+ for i in range(HANDSHAKES):
211
+ print("#" * 80)
212
+ print("#" * 80)
213
+ print("#" * 80)
214
+ with endpoint() as client_endpoint:
215
+ client = client_endpoint.connect(address, client_ctx_fn())
216
+ print("client starting do_handshake")
217
+ await client.do_handshake()
218
+ print("client finished do_handshake")
219
+ msg = str(i).encode()
220
+ # Make multiple attempts to send data, because the network might
221
+ # drop it
222
+ while True:
223
+ with trio.move_on_after(10) as cscope:
224
+ await client.send(msg)
225
+ assert await client.receive() == msg
226
+ if not cscope.cancelled_caught:
227
+ break
228
+
229
+
230
+ async def test_implicit_handshake(
231
+ server_ctx: SSL.Context, client_ctx: SSL.Context
232
+ ) -> None:
233
+ async with dtls_echo_server(server_ctx=server_ctx) as (_, address):
234
+ with endpoint() as client_endpoint:
235
+ client = client_endpoint.connect(address, client_ctx)
236
+
237
+ # Implicit handshake
238
+ await client.send(b"xyz")
239
+ assert await client.receive() == b"xyz"
240
+
241
+
242
+ async def test_full_duplex(server_ctx: SSL.Context, client_ctx: SSL.Context) -> None:
243
+ # Tests simultaneous send/receive, and also multiple methods implicitly invoking
244
+ # do_handshake simultaneously.
245
+ with endpoint() as server_endpoint, endpoint() as client_endpoint:
246
+ await server_endpoint.socket.bind(("127.0.0.1", 0))
247
+ async with trio.open_nursery() as server_nursery:
248
+
249
+ async def handler(channel: DTLSChannel) -> None:
250
+ async with trio.open_nursery() as nursery:
251
+ nursery.start_soon(channel.send, b"from server")
252
+ nursery.start_soon(channel.receive)
253
+
254
+ await server_nursery.start(server_endpoint.serve, server_ctx, handler)
255
+
256
+ client = client_endpoint.connect(
257
+ server_endpoint.socket.getsockname(),
258
+ client_ctx,
259
+ )
260
+ async with trio.open_nursery() as nursery:
261
+ nursery.start_soon(client.send, b"from client")
262
+ nursery.start_soon(client.receive)
263
+
264
+ server_nursery.cancel_scope.cancel()
265
+
266
+
267
+ async def test_channel_closing(
268
+ server_ctx: SSL.Context, client_ctx: SSL.Context
269
+ ) -> None:
270
+ async with dtls_echo_server(server_ctx=server_ctx) as (_, address):
271
+ with endpoint() as client_endpoint:
272
+ client = client_endpoint.connect(address, client_ctx)
273
+ await client.do_handshake()
274
+ client.close()
275
+
276
+ with pytest.raises(trio.ClosedResourceError):
277
+ await client.send(b"abc")
278
+ with pytest.raises(trio.ClosedResourceError):
279
+ await client.receive()
280
+
281
+ # close is idempotent
282
+ client.close()
283
+ # can also aclose
284
+ await client.aclose()
285
+
286
+
287
+ async def test_serve_exits_cleanly_on_close(server_ctx: SSL.Context) -> None:
288
+ async with dtls_echo_server(autocancel=False, server_ctx=server_ctx) as (
289
+ server_endpoint,
290
+ _address,
291
+ ):
292
+ server_endpoint.close()
293
+ # Testing that the nursery exits even without being cancelled
294
+ # close is idempotent
295
+ server_endpoint.close()
296
+
297
+
298
+ async def test_client_multiplex(server_ctx: SSL.Context) -> None:
299
+ async with (
300
+ dtls_echo_server(server_ctx=server_ctx) as (_, address1),
301
+ dtls_echo_server(server_ctx=server_ctx) as (_, address2),
302
+ ):
303
+ with endpoint() as client_endpoint:
304
+ client1 = client_endpoint.connect(address1, client_ctx_fn())
305
+ client2 = client_endpoint.connect(address2, client_ctx_fn())
306
+
307
+ await client1.send(b"abc")
308
+ await client2.send(b"xyz")
309
+ assert await client2.receive() == b"xyz"
310
+ assert await client1.receive() == b"abc"
311
+
312
+ client_endpoint.close()
313
+
314
+ with pytest.raises(trio.ClosedResourceError):
315
+ await client1.send(b"xxx")
316
+ with pytest.raises(trio.ClosedResourceError):
317
+ await client2.receive()
318
+ with pytest.raises(trio.ClosedResourceError):
319
+ client_endpoint.connect(address1, client_ctx_fn())
320
+
321
+ async def null_handler(_: object) -> None: # pragma: no cover
322
+ pass
323
+
324
+ async with trio.open_nursery() as nursery:
325
+ with pytest.raises(trio.ClosedResourceError):
326
+ await nursery.start(client_endpoint.serve, server_ctx, null_handler)
327
+
328
+
329
+ async def test_dtls_over_dgram_only() -> None:
330
+ with trio.socket.socket() as s:
331
+ with pytest.raises(ValueError, match=r"^DTLS requires a SOCK_DGRAM socket$"):
332
+ DTLSEndpoint(s)
333
+
334
+
335
+ async def test_double_serve(server_ctx: SSL.Context) -> None:
336
+ async def null_handler(_: object) -> None: # pragma: no cover
337
+ pass
338
+
339
+ with endpoint() as server_endpoint:
340
+ await server_endpoint.socket.bind(("127.0.0.1", 0))
341
+ async with trio.open_nursery() as nursery:
342
+ await nursery.start(server_endpoint.serve, server_ctx, null_handler)
343
+ with pytest.raises(trio.BusyResourceError):
344
+ await nursery.start(server_endpoint.serve, server_ctx, null_handler)
345
+
346
+ nursery.cancel_scope.cancel()
347
+
348
+ async with trio.open_nursery() as nursery:
349
+ await nursery.start(server_endpoint.serve, server_ctx, null_handler)
350
+ nursery.cancel_scope.cancel()
351
+
352
+
353
+ async def test_connect_to_non_server(
354
+ autojump_clock: trio.abc.Clock, client_ctx: SSL.Context
355
+ ) -> None:
356
+ fn = FakeNet()
357
+ fn.enable()
358
+ with endpoint() as client1, endpoint() as client2:
359
+ await client1.socket.bind(("127.0.0.1", 0))
360
+ # This should just time out
361
+ with trio.move_on_after(100) as cscope:
362
+ channel = client2.connect(client1.socket.getsockname(), client_ctx)
363
+ await channel.do_handshake()
364
+ assert cscope.cancelled_caught
365
+
366
+
367
+ @pytest.mark.parametrize("buffer_size", [10, 20])
368
+ async def test_incoming_buffer_overflow(
369
+ autojump_clock: trio.abc.Clock,
370
+ server_ctx: SSL.Context,
371
+ client_ctx: SSL.Context,
372
+ buffer_size: int,
373
+ ) -> None:
374
+ fn = FakeNet()
375
+ fn.enable()
376
+ async with dtls_echo_server(server_ctx=server_ctx) as (_, address):
377
+ with endpoint(incoming_packets_buffer=buffer_size) as client_endpoint:
378
+ assert client_endpoint.incoming_packets_buffer == buffer_size
379
+ client = client_endpoint.connect(address, client_ctx)
380
+ for i in range(buffer_size + 15):
381
+ await client.send(str(i).encode())
382
+ await trio.sleep(1)
383
+ stats = client.statistics()
384
+ assert stats.incoming_packets_dropped_in_trio == 15
385
+ for i in range(buffer_size):
386
+ assert await client.receive() == str(i).encode()
387
+ await client.send(b"buffer clear now")
388
+ assert await client.receive() == b"buffer clear now"
389
+
390
+
391
+ async def test_server_socket_doesnt_crash_on_garbage(
392
+ autojump_clock: trio.abc.Clock, server_ctx: SSL.Context
393
+ ) -> None:
394
+ fn = FakeNet()
395
+ fn.enable()
396
+
397
+ from trio._dtls import (
398
+ ContentType,
399
+ HandshakeFragment,
400
+ HandshakeType,
401
+ ProtocolVersion,
402
+ Record,
403
+ encode_handshake_fragment,
404
+ encode_record,
405
+ )
406
+
407
+ client_hello = encode_record(
408
+ Record(
409
+ content_type=ContentType.handshake,
410
+ version=ProtocolVersion.DTLS10,
411
+ epoch_seqno=0,
412
+ payload=encode_handshake_fragment(
413
+ HandshakeFragment(
414
+ msg_type=HandshakeType.client_hello,
415
+ msg_len=10,
416
+ msg_seq=0,
417
+ frag_offset=0,
418
+ frag_len=10,
419
+ frag=bytes(10),
420
+ ),
421
+ ),
422
+ ),
423
+ )
424
+
425
+ client_hello_extended = client_hello + b"\x00"
426
+ client_hello_short = client_hello[:-1]
427
+ # cuts off in middle of handshake message header
428
+ client_hello_really_short = client_hello[:14]
429
+ client_hello_corrupt_record_len = bytearray(client_hello)
430
+ client_hello_corrupt_record_len[11] = 0xFF
431
+
432
+ client_hello_fragmented = encode_record(
433
+ Record(
434
+ content_type=ContentType.handshake,
435
+ version=ProtocolVersion.DTLS10,
436
+ epoch_seqno=0,
437
+ payload=encode_handshake_fragment(
438
+ HandshakeFragment(
439
+ msg_type=HandshakeType.client_hello,
440
+ msg_len=20,
441
+ msg_seq=0,
442
+ frag_offset=0,
443
+ frag_len=10,
444
+ frag=bytes(10),
445
+ ),
446
+ ),
447
+ ),
448
+ )
449
+
450
+ client_hello_trailing_data_in_record = encode_record(
451
+ Record(
452
+ content_type=ContentType.handshake,
453
+ version=ProtocolVersion.DTLS10,
454
+ epoch_seqno=0,
455
+ payload=encode_handshake_fragment(
456
+ HandshakeFragment(
457
+ msg_type=HandshakeType.client_hello,
458
+ msg_len=20,
459
+ msg_seq=0,
460
+ frag_offset=0,
461
+ frag_len=10,
462
+ frag=bytes(10),
463
+ ),
464
+ )
465
+ + b"\x00",
466
+ ),
467
+ )
468
+
469
+ handshake_empty = encode_record(
470
+ Record(
471
+ content_type=ContentType.handshake,
472
+ version=ProtocolVersion.DTLS10,
473
+ epoch_seqno=0,
474
+ payload=b"",
475
+ ),
476
+ )
477
+
478
+ client_hello_truncated_in_cookie = encode_record(
479
+ Record(
480
+ content_type=ContentType.handshake,
481
+ version=ProtocolVersion.DTLS10,
482
+ epoch_seqno=0,
483
+ payload=bytes(2 + 32 + 1) + b"\xff",
484
+ ),
485
+ )
486
+
487
+ async with dtls_echo_server(server_ctx=server_ctx) as (_, address):
488
+ with trio.socket.socket(type=trio.socket.SOCK_DGRAM) as sock:
489
+ for bad_packet in [
490
+ b"",
491
+ b"xyz",
492
+ client_hello_extended,
493
+ client_hello_short,
494
+ client_hello_really_short,
495
+ client_hello_corrupt_record_len,
496
+ client_hello_fragmented,
497
+ client_hello_trailing_data_in_record,
498
+ handshake_empty,
499
+ client_hello_truncated_in_cookie,
500
+ ]:
501
+ await sock.sendto(bad_packet, address)
502
+ await trio.sleep(1)
503
+
504
+
505
+ async def test_invalid_cookie_rejected(
506
+ autojump_clock: trio.abc.Clock, server_ctx: SSL.Context, client_ctx: SSL.Context
507
+ ) -> None:
508
+ fn = FakeNet()
509
+ fn.enable()
510
+
511
+ from trio._dtls import BadPacket, decode_client_hello_untrusted
512
+
513
+ with trio.CancelScope() as cscope:
514
+ # the first 11 bytes of ClientHello aren't protected by the cookie, so only test
515
+ # corrupting bytes after that.
516
+ offset_to_corrupt = count(11)
517
+
518
+ def route_packet(packet: UDPPacket) -> None:
519
+ try:
520
+ _, cookie, _ = decode_client_hello_untrusted(packet.payload)
521
+ except BadPacket:
522
+ pass
523
+ else:
524
+ if len(cookie) != 0:
525
+ # this is a challenge response packet
526
+ # let's corrupt the next offset so the handshake should fail
527
+ payload = bytearray(packet.payload)
528
+ offset = next(offset_to_corrupt)
529
+ if offset >= len(payload):
530
+ # We've tried all offsets. Clamp offset to the end of the
531
+ # payload, and terminate the test.
532
+ offset = len(payload) - 1
533
+ cscope.cancel()
534
+ payload[offset] ^= 0x01
535
+ packet = attrs.evolve(packet, payload=payload)
536
+
537
+ fn.deliver_packet(packet)
538
+
539
+ fn.route_packet = route_packet # type: ignore[assignment] # TODO: Fix FakeNet typing
540
+
541
+ async with dtls_echo_server(server_ctx=server_ctx) as (_, address):
542
+ while True:
543
+ with endpoint() as client:
544
+ channel = client.connect(address, client_ctx)
545
+ await channel.do_handshake()
546
+ assert cscope.cancelled_caught
547
+
548
+
549
+ async def test_client_cancels_handshake_and_starts_new_one(
550
+ autojump_clock: trio.abc.Clock, server_ctx: SSL.Context
551
+ ) -> None:
552
+ # if a client disappears during the handshake, and then starts a new handshake from
553
+ # scratch, then the first handler's channel should fail, and a new handler get
554
+ # started
555
+ fn = FakeNet()
556
+ fn.enable()
557
+
558
+ with endpoint() as server, endpoint() as client:
559
+ await server.socket.bind(("127.0.0.1", 0))
560
+ async with trio.open_nursery() as nursery:
561
+ first_time = True
562
+
563
+ async def handler(channel: DTLSChannel) -> None:
564
+ nonlocal first_time
565
+ if first_time:
566
+ first_time = False
567
+ print("handler: first time, cancelling connect")
568
+ connect_cscope.cancel()
569
+ await trio.sleep(0.5)
570
+ print("handler: handshake should fail now")
571
+ with pytest.raises(trio.BrokenResourceError):
572
+ await channel.do_handshake()
573
+ else:
574
+ print("handler: not first time, sending hello")
575
+ await channel.send(b"hello")
576
+
577
+ await nursery.start(server.serve, server_ctx, handler)
578
+
579
+ print("client: starting first connect")
580
+ with trio.CancelScope() as connect_cscope:
581
+ channel = client.connect(server.socket.getsockname(), client_ctx_fn())
582
+ await channel.do_handshake()
583
+ assert connect_cscope.cancelled_caught
584
+
585
+ print("client: starting second connect")
586
+ channel = client.connect(server.socket.getsockname(), client_ctx_fn())
587
+ assert await channel.receive() == b"hello"
588
+
589
+ # Give handlers a chance to finish
590
+ await trio.sleep(10)
591
+ nursery.cancel_scope.cancel()
592
+
593
+
594
+ async def test_swap_client_server(server_ctx: SSL.Context) -> None:
595
+ with endpoint() as a, endpoint() as b:
596
+ await a.socket.bind(("127.0.0.1", 0))
597
+ await b.socket.bind(("127.0.0.1", 0))
598
+
599
+ async def echo_handler(channel: DTLSChannel) -> None:
600
+ async for packet in channel:
601
+ await channel.send(packet)
602
+
603
+ async def crashing_echo_handler(channel: DTLSChannel) -> None:
604
+ with pytest.raises(trio.BrokenResourceError):
605
+ await echo_handler(channel)
606
+
607
+ async with trio.open_nursery() as nursery:
608
+ await nursery.start(a.serve, server_ctx, crashing_echo_handler)
609
+ await nursery.start(b.serve, server_ctx, echo_handler)
610
+
611
+ b_to_a = b.connect(a.socket.getsockname(), client_ctx_fn())
612
+ await b_to_a.send(b"b as client")
613
+ assert await b_to_a.receive() == b"b as client"
614
+
615
+ a_to_b = a.connect(b.socket.getsockname(), client_ctx_fn())
616
+ await a_to_b.do_handshake()
617
+ with pytest.raises(trio.BrokenResourceError):
618
+ await b_to_a.send(b"association broken")
619
+ await a_to_b.send(b"a as client")
620
+ assert await a_to_b.receive() == b"a as client"
621
+
622
+ nursery.cancel_scope.cancel()
623
+
624
+
625
+ @slow
626
+ async def test_openssl_retransmit_doesnt_break_stuff(
627
+ server_ctx: SSL.Context, client_ctx: SSL.Context
628
+ ) -> None:
629
+ # can't use autojump_clock here, because the point of the test is to wait for
630
+ # openssl's built-in retransmit timer to expire, which is hard-coded to use
631
+ # wall-clock time.
632
+ fn = FakeNet()
633
+ fn.enable()
634
+
635
+ blackholed = True
636
+
637
+ def route_packet(packet: UDPPacket) -> None:
638
+ if blackholed:
639
+ print("dropped packet", packet)
640
+ return
641
+ print("delivered packet", packet)
642
+ # packets.append(
643
+ # scapy.all.IP(
644
+ # src=packet.source.ip.compressed, dst=packet.destination.ip.compressed
645
+ # )
646
+ # / scapy.all.UDP(sport=packet.source.port, dport=packet.destination.port)
647
+ # / packet.payload
648
+ # )
649
+ fn.deliver_packet(packet)
650
+
651
+ fn.route_packet = route_packet # type: ignore[assignment] # TODO add type annotations for FakeNet
652
+
653
+ async with dtls_echo_server(server_ctx=server_ctx) as (server_endpoint, address):
654
+ with endpoint() as client_endpoint:
655
+ async with trio.open_nursery() as nursery:
656
+
657
+ async def connecter() -> None:
658
+ client = client_endpoint.connect(address, client_ctx)
659
+ await client.do_handshake(initial_retransmit_timeout=1.5)
660
+ await client.send(b"hi")
661
+ assert await client.receive() == b"hi"
662
+
663
+ nursery.start_soon(connecter)
664
+
665
+ # openssl's default timeout is 1 second, so this ensures that it thinks
666
+ # the timeout has expired
667
+ await trio.sleep(1.1)
668
+ # disable blackholing and send a garbage packet to wake up openssl so it
669
+ # notices the timeout has expired
670
+ blackholed = False
671
+ await server_endpoint.socket.sendto(
672
+ b"xxx",
673
+ client_endpoint.socket.getsockname(),
674
+ )
675
+ # now the client task should finish connecting and exit cleanly
676
+
677
+ # scapy.all.wrpcap("/tmp/trace.pcap", packets)
678
+
679
+
680
+ async def test_initial_retransmit_timeout_configuration(
681
+ autojump_clock: trio.abc.Clock, server_ctx: SSL.Context
682
+ ) -> None:
683
+ fn = FakeNet()
684
+ fn.enable()
685
+
686
+ blackholed = True
687
+
688
+ def route_packet(packet: UDPPacket) -> None:
689
+ nonlocal blackholed
690
+ if blackholed:
691
+ blackholed = False
692
+ else:
693
+ fn.deliver_packet(packet)
694
+
695
+ fn.route_packet = route_packet # type: ignore[assignment] # TODO: add type annotations for FakeNet
696
+
697
+ async with dtls_echo_server(server_ctx=server_ctx) as (_, address):
698
+ for t in [1, 2, 4]:
699
+ with endpoint() as client:
700
+ before = trio.current_time()
701
+ blackholed = True
702
+ channel = client.connect(address, client_ctx_fn())
703
+ await channel.do_handshake(initial_retransmit_timeout=t)
704
+ after = trio.current_time()
705
+ assert after - before == t
706
+
707
+
708
+ async def test_explicit_tiny_mtu_is_respected(
709
+ server_ctx: SSL.Context, client_ctx: SSL.Context
710
+ ) -> None:
711
+ # ClientHello is ~240 bytes, and it can't be fragmented, so our mtu has to
712
+ # be larger than that. (300 is still smaller than any real network though.)
713
+ MTU = 300
714
+
715
+ fn = FakeNet()
716
+ fn.enable()
717
+
718
+ def route_packet(packet: UDPPacket) -> None:
719
+ print(f"delivering {packet}")
720
+ print(f"payload size: {len(packet.payload)}")
721
+ assert len(packet.payload) <= MTU
722
+ fn.deliver_packet(packet)
723
+
724
+ fn.route_packet = route_packet # type: ignore[assignment] # TODO add type annotations for FakeNet
725
+
726
+ async with dtls_echo_server(mtu=MTU, server_ctx=server_ctx) as (_server, address):
727
+ with endpoint() as client:
728
+ channel = client.connect(address, client_ctx)
729
+ channel.set_ciphertext_mtu(MTU)
730
+ await channel.do_handshake()
731
+ await channel.send(b"hi")
732
+ assert await channel.receive() == b"hi"
733
+
734
+
735
+ @parametrize_ipv6
736
+ async def test_handshake_handles_minimum_network_mtu(
737
+ ipv6: bool,
738
+ autojump_clock: trio.abc.Clock,
739
+ server_ctx: SSL.Context,
740
+ client_ctx: SSL.Context,
741
+ ) -> None:
742
+ # Fake network that has the minimum allowable MTU for whatever protocol we're using.
743
+ fn = FakeNet()
744
+ fn.enable()
745
+
746
+ mtu = 1280 - 48 if ipv6 else 576 - 28
747
+
748
+ def route_packet(packet: UDPPacket) -> None:
749
+ if len(packet.payload) > mtu:
750
+ print(f"dropping {packet}")
751
+ else:
752
+ print(f"delivering {packet}")
753
+ fn.deliver_packet(packet)
754
+
755
+ fn.route_packet = route_packet # type: ignore[assignment] # TODO: add type annotations for FakeNet
756
+
757
+ # See if we can successfully do a handshake -- some of the volleys will get dropped,
758
+ # and the retransmit logic should detect this and back off the MTU to something
759
+ # smaller until it succeeds.
760
+ async with dtls_echo_server(ipv6=ipv6, server_ctx=server_ctx) as (_, address):
761
+ with endpoint(ipv6=ipv6) as client_endpoint:
762
+ client = client_endpoint.connect(address, client_ctx)
763
+ # the handshake mtu backoff shouldn't affect the return value from
764
+ # get_cleartext_mtu, b/c that's under the user's control via
765
+ # set_ciphertext_mtu
766
+ client.set_ciphertext_mtu(9999)
767
+ await client.send(b"xyz")
768
+ assert await client.receive() == b"xyz"
769
+ assert client.get_cleartext_mtu() > 9000 # as vegeta said
770
+
771
+
772
+ @pytest.mark.filterwarnings("always:unclosed DTLS:ResourceWarning")
773
+ async def test_system_task_cleaned_up_on_gc(client_ctx: SSL.Context) -> None:
774
+ before_tasks = trio.lowlevel.current_statistics().tasks_living
775
+
776
+ # We put this into a sub-function so that everything automatically becomes garbage
777
+ # when the frame exits. For some reason just doing 'del e' wasn't enough on pypy
778
+ # with coverage enabled -- I think we were hitting this bug:
779
+ # https://foss.heptapod.net/pypy/pypy/-/issues/3656
780
+ async def start_and_forget_endpoint() -> int:
781
+ e = endpoint()
782
+
783
+ # This connection/handshake attempt can't succeed. The only purpose is to force
784
+ # the endpoint to set up a receive loop.
785
+ with trio.socket.socket(type=trio.socket.SOCK_DGRAM) as s:
786
+ await s.bind(("127.0.0.1", 0))
787
+ c = e.connect(s.getsockname(), client_ctx)
788
+ async with trio.open_nursery() as nursery:
789
+ nursery.start_soon(c.do_handshake)
790
+ await trio.testing.wait_all_tasks_blocked()
791
+ nursery.cancel_scope.cancel()
792
+
793
+ during_tasks = trio.lowlevel.current_statistics().tasks_living
794
+ return during_tasks
795
+
796
+ with pytest.warns(ResourceWarning): # noqa: PT031
797
+ during_tasks = await start_and_forget_endpoint()
798
+ await trio.testing.wait_all_tasks_blocked()
799
+ gc_collect_harder()
800
+
801
+ await trio.testing.wait_all_tasks_blocked()
802
+
803
+ after_tasks = trio.lowlevel.current_statistics().tasks_living
804
+ assert before_tasks < during_tasks
805
+ assert before_tasks == after_tasks
806
+
807
+
808
+ @pytest.mark.filterwarnings("always:unclosed DTLS:ResourceWarning")
809
+ async def test_gc_before_system_task_starts() -> None:
810
+ e = endpoint()
811
+
812
+ with pytest.warns(ResourceWarning): # noqa: PT031
813
+ del e
814
+ gc_collect_harder()
815
+
816
+ await trio.testing.wait_all_tasks_blocked()
817
+
818
+
819
+ @pytest.mark.filterwarnings("always:unclosed DTLS:ResourceWarning")
820
+ async def test_gc_as_packet_received() -> None:
821
+ fn = FakeNet()
822
+ fn.enable()
823
+
824
+ e = endpoint()
825
+ await e.socket.bind(("127.0.0.1", 0))
826
+ e._ensure_receive_loop()
827
+
828
+ await trio.testing.wait_all_tasks_blocked()
829
+
830
+ with trio.socket.socket(type=trio.socket.SOCK_DGRAM) as s:
831
+ await s.sendto(b"xxx", e.socket.getsockname())
832
+ # At this point, the endpoint's receive loop has been marked runnable because it
833
+ # just received a packet; closing the endpoint socket won't interrupt that. But by
834
+ # the time it wakes up to process the packet, the endpoint will be gone.
835
+ with pytest.warns(ResourceWarning): # noqa: PT031
836
+ del e
837
+ gc_collect_harder()
838
+
839
+
840
+ @pytest.mark.filterwarnings("always:unclosed DTLS:ResourceWarning")
841
+ def test_gc_after_trio_exits() -> None:
842
+ async def main() -> DTLSEndpoint:
843
+ # We use fakenet just to make sure no real sockets can leak out of the test
844
+ # case - on pypy somehow the socket was outliving the gc_collect_harder call
845
+ # below. Since the test is just making sure DTLSEndpoint.__del__ doesn't explode
846
+ # when called after trio exits, it doesn't need a real socket.
847
+ fn = FakeNet()
848
+ fn.enable()
849
+ return endpoint()
850
+
851
+ e = trio.run(main)
852
+ with pytest.warns(ResourceWarning): # noqa: PT031
853
+ del e
854
+ gc_collect_harder()
855
+
856
+
857
+ async def test_already_closed_socket_doesnt_crash() -> None:
858
+ with endpoint() as e:
859
+ # We close the socket before checkpointing, so the socket will already be closed
860
+ # when the system task starts up
861
+ e.socket.close()
862
+ # Now give it a chance to start up, and hopefully not crash
863
+ await trio.testing.wait_all_tasks_blocked()
864
+
865
+
866
+ async def test_socket_closed_while_processing_clienthello(
867
+ autojump_clock: trio.abc.Clock, server_ctx: SSL.Context, client_ctx: SSL.Context
868
+ ) -> None:
869
+ fn = FakeNet()
870
+ fn.enable()
871
+
872
+ # Check what happens if the socket is discovered to be closed when sending a
873
+ # HelloVerifyRequest, since that has its own sending logic
874
+ async with dtls_echo_server(server_ctx=server_ctx) as (server, address):
875
+
876
+ def route_packet(packet: UDPPacket) -> None:
877
+ fn.deliver_packet(packet)
878
+ server.socket.close()
879
+
880
+ fn.route_packet = route_packet # type: ignore[assignment] # TODO add type annotations for FakeNet
881
+
882
+ with endpoint() as client_endpoint:
883
+ with trio.move_on_after(10):
884
+ client = client_endpoint.connect(address, client_ctx)
885
+ await client.do_handshake()
886
+
887
+
888
+ async def test_association_replaced_while_handshake_running(
889
+ autojump_clock: trio.abc.Clock, server_ctx: SSL.Context
890
+ ) -> None:
891
+ fn = FakeNet()
892
+ fn.enable()
893
+
894
+ def route_packet(packet: UDPPacket) -> None:
895
+ pass
896
+
897
+ fn.route_packet = route_packet # type: ignore[assignment] # TODO: add type annotations for FakeNet
898
+
899
+ async with dtls_echo_server(server_ctx=server_ctx) as (_, address):
900
+ with endpoint() as client_endpoint:
901
+ # TODO: should this have the same exact client_ctx?
902
+ c1 = client_endpoint.connect(address, client_ctx_fn())
903
+ async with trio.open_nursery() as nursery:
904
+
905
+ async def doomed_handshake() -> None:
906
+ with pytest.raises(trio.BrokenResourceError):
907
+ await c1.do_handshake()
908
+
909
+ nursery.start_soon(doomed_handshake)
910
+
911
+ await trio.sleep(10)
912
+
913
+ client_endpoint.connect(address, client_ctx_fn())
914
+
915
+
916
+ async def test_association_replaced_before_handshake_starts(
917
+ server_ctx: SSL.Context,
918
+ ) -> None:
919
+ fn = FakeNet()
920
+ fn.enable()
921
+
922
+ # This test shouldn't send any packets
923
+ def route_packet(packet: UDPPacket) -> NoReturn: # pragma: no cover
924
+ raise AssertionError()
925
+
926
+ fn.route_packet = route_packet # type: ignore[assignment] # TODO add type annotations for FakeNet
927
+
928
+ async with dtls_echo_server(server_ctx=server_ctx) as (_, address):
929
+ with endpoint() as client_endpoint:
930
+ # TODO: should this use the same client_ctx?
931
+ c1 = client_endpoint.connect(address, client_ctx_fn())
932
+ client_endpoint.connect(address, client_ctx_fn())
933
+ with pytest.raises(trio.BrokenResourceError):
934
+ await c1.do_handshake()
935
+
936
+
937
+ async def test_send_to_closed_local_port(server_ctx: SSL.Context) -> None:
938
+ # On Windows, sending a UDP packet to a closed local port can cause a weird
939
+ # ECONNRESET error later, inside the receive task. Make sure we're handling it
940
+ # properly.
941
+ async with dtls_echo_server(server_ctx=server_ctx) as (_, address):
942
+ with endpoint() as client_endpoint:
943
+ async with trio.open_nursery() as nursery:
944
+ for i in range(1, 10):
945
+ channel = client_endpoint.connect(("127.0.0.1", i), client_ctx_fn())
946
+ nursery.start_soon(channel.do_handshake)
947
+ channel = client_endpoint.connect(address, client_ctx_fn())
948
+ await channel.send(b"xxx")
949
+ assert await channel.receive() == b"xxx"
950
+ nursery.cancel_scope.cancel()