quicsilver 0.5.0-x86_64-linux

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 (107) hide show
  1. checksums.yaml +7 -0
  2. data/.github/workflows/ci.yml +49 -0
  3. data/.github/workflows/cibuildgem.yaml +117 -0
  4. data/.gitignore +21 -0
  5. data/.gitmodules +3 -0
  6. data/.ruby-version +1 -0
  7. data/CHANGELOG.md +175 -0
  8. data/Gemfile +6 -0
  9. data/Gemfile.lock +67 -0
  10. data/LICENSE +21 -0
  11. data/README.md +261 -0
  12. data/Rakefile +130 -0
  13. data/benchmarks/baseline.rb +175 -0
  14. data/benchmarks/compare.rb +398 -0
  15. data/benchmarks/components.rb +191 -0
  16. data/benchmarks/helpers.rb +162 -0
  17. data/benchmarks/latency.rb +128 -0
  18. data/benchmarks/loss.rb +193 -0
  19. data/benchmarks/mobile.rb +125 -0
  20. data/bin/console +14 -0
  21. data/bin/setup +8 -0
  22. data/docs/connection_identity.md +141 -0
  23. data/examples/README.md +58 -0
  24. data/examples/benchmark.rb +191 -0
  25. data/examples/connection_pool_demo.rb +47 -0
  26. data/examples/example_helper.rb +69 -0
  27. data/examples/falcon_middleware.rb +44 -0
  28. data/examples/feature_demo.rb +125 -0
  29. data/examples/grpc_style.rb +97 -0
  30. data/examples/latency_aware_routing.rb +382 -0
  31. data/examples/lifecycle_drain.rb +264 -0
  32. data/examples/minimal_http3_server.rb +14 -0
  33. data/examples/priorities.rb +60 -0
  34. data/examples/protocol_http_server.rb +31 -0
  35. data/examples/rack_http3_server.rb +42 -0
  36. data/examples/rails_feature_test.rb +280 -0
  37. data/examples/server_stats.rb +117 -0
  38. data/examples/simple_client_test.rb +26 -0
  39. data/examples/streaming_sse.rb +33 -0
  40. data/examples/trailers.rb +69 -0
  41. data/examples/webtransport_smoke.rb +546 -0
  42. data/examples/worker_load_balancing.rb +425 -0
  43. data/ext/quicsilver/extconf.rb +124 -0
  44. data/ext/quicsilver/include/msquic.h +2226 -0
  45. data/ext/quicsilver/include/msquic_posix.h +696 -0
  46. data/ext/quicsilver/include/quic_sal_stub.h +314 -0
  47. data/ext/quicsilver/msquic_patch.rb +68 -0
  48. data/ext/quicsilver/patches/reliable-reset.patch +723 -0
  49. data/ext/quicsilver/patches/stream-final-size.patch +47 -0
  50. data/ext/quicsilver/quicsilver.c +2741 -0
  51. data/lib/quicsilver/3.4/quicsilver.so +0 -0
  52. data/lib/quicsilver/4.0/quicsilver.so +0 -0
  53. data/lib/quicsilver/client/client.rb +708 -0
  54. data/lib/quicsilver/client/connection_pool.rb +202 -0
  55. data/lib/quicsilver/client/request.rb +190 -0
  56. data/lib/quicsilver/errors.rb +39 -0
  57. data/lib/quicsilver/libmsquic.so.2 +0 -0
  58. data/lib/quicsilver/protocol/adapter.rb +196 -0
  59. data/lib/quicsilver/protocol/capsule.rb +38 -0
  60. data/lib/quicsilver/protocol/connect_stream_decoder.rb +75 -0
  61. data/lib/quicsilver/protocol/control_stream_parser.rb +117 -0
  62. data/lib/quicsilver/protocol/datagram.rb +40 -0
  63. data/lib/quicsilver/protocol/frame_parser.rb +141 -0
  64. data/lib/quicsilver/protocol/frame_reader.rb +84 -0
  65. data/lib/quicsilver/protocol/frames.rb +454 -0
  66. data/lib/quicsilver/protocol/priority.rb +65 -0
  67. data/lib/quicsilver/protocol/qpack/decoder.rb +127 -0
  68. data/lib/quicsilver/protocol/qpack/encoder.rb +217 -0
  69. data/lib/quicsilver/protocol/qpack/header_block_decoder.rb +140 -0
  70. data/lib/quicsilver/protocol/qpack/huffman.rb +459 -0
  71. data/lib/quicsilver/protocol/request_encoder.rb +50 -0
  72. data/lib/quicsilver/protocol/request_parser.rb +289 -0
  73. data/lib/quicsilver/protocol/response_encoder.rb +107 -0
  74. data/lib/quicsilver/protocol/response_parser.rb +147 -0
  75. data/lib/quicsilver/protocol/stream_input.rb +98 -0
  76. data/lib/quicsilver/protocol/stream_output.rb +59 -0
  77. data/lib/quicsilver/protocol/unidirectional_stream.rb +12 -0
  78. data/lib/quicsilver/protocol/web_transport.rb +214 -0
  79. data/lib/quicsilver/rack/context.rb +33 -0
  80. data/lib/quicsilver/response.rb +40 -0
  81. data/lib/quicsilver/server/listener_data.rb +14 -0
  82. data/lib/quicsilver/server/rack_adapter.rb +77 -0
  83. data/lib/quicsilver/server/receive_queue.rb +106 -0
  84. data/lib/quicsilver/server/request_handler.rb +149 -0
  85. data/lib/quicsilver/server/request_registry.rb +63 -0
  86. data/lib/quicsilver/server/scheduler.rb +50 -0
  87. data/lib/quicsilver/server/schedulers/fiber_scheduler.rb +79 -0
  88. data/lib/quicsilver/server/schedulers/thread_scheduler.rb +76 -0
  89. data/lib/quicsilver/server/server.rb +1080 -0
  90. data/lib/quicsilver/server/web_transport_flow_control.rb +270 -0
  91. data/lib/quicsilver/server/web_transport_manager.rb +281 -0
  92. data/lib/quicsilver/server/web_transport_registry.rb +40 -0
  93. data/lib/quicsilver/server/web_transport_session.rb +812 -0
  94. data/lib/quicsilver/server/web_transport_stream.rb +326 -0
  95. data/lib/quicsilver/transport/configuration.rb +234 -0
  96. data/lib/quicsilver/transport/connection.rb +594 -0
  97. data/lib/quicsilver/transport/connection_stats.rb +53 -0
  98. data/lib/quicsilver/transport/event_loop.rb +74 -0
  99. data/lib/quicsilver/transport/inbound_stream.rb +53 -0
  100. data/lib/quicsilver/transport/stream.rb +43 -0
  101. data/lib/quicsilver/transport/stream_event.rb +49 -0
  102. data/lib/quicsilver/transport/stream_id.rb +26 -0
  103. data/lib/quicsilver/version.rb +3 -0
  104. data/lib/quicsilver.rb +96 -0
  105. data/lib/rackup/handler/quicsilver.rb +96 -0
  106. data/quicsilver.gemspec +66 -0
  107. metadata +362 -0
@@ -0,0 +1,723 @@
1
+ diff --git c/src/core/api.c i/src/core/api.c
2
+ index 2281da55a..c88b0b641 100644
3
+ --- c/src/core/api.c
4
+ +++ i/src/core/api.c
5
+ @@ -1259,6 +1259,7 @@ MsQuicStreamSend(
6
+ QueueOper = FALSE; // Not necessary if the previous send hasn't been flushed yet.
7
+ }
8
+ *ApiSendRequestsTail = SendRequest;
9
+ + Stream->ApiSubmittedOffset += SendRequest->TotalLength;
10
+ Status = QUIC_STATUS_SUCCESS;
11
+
12
+ if (!SendInline && QueueOper) {
13
+ diff --git c/src/core/connection.c i/src/core/connection.c
14
+ index d2194a749..4bf4fb3d6 100644
15
+ --- c/src/core/connection.c
16
+ +++ i/src/core/connection.c
17
+ @@ -2203,7 +2203,9 @@ QuicConnRecvResumptionTicket(
18
+ //
19
+ // Validate resumed TP are <= current settings
20
+ //
21
+ - if (ResumedTP.ActiveConnectionIdLimit > QUIC_ACTIVE_CONNECTION_ID_LIMIT ||
22
+ + if (((ResumedTP.Flags & QUIC_TP_FLAG_RELIABLE_RESET_ENABLED) &&
23
+ + !Connection->Settings.ReliableResetEnabled) ||
24
+ + ResumedTP.ActiveConnectionIdLimit > QUIC_ACTIVE_CONNECTION_ID_LIMIT ||
25
+ ResumedTP.InitialMaxData > Connection->Send.MaxData ||
26
+ ResumedTP.InitialMaxStreamDataBidiLocal > Connection->Settings.StreamRecvWindowBidiLocalDefault ||
27
+ ResumedTP.InitialMaxStreamDataBidiRemote > Connection->Settings.StreamRecvWindowBidiRemoteDefault ||
28
+ @@ -2288,6 +2290,9 @@ QuicConnRecvResumptionTicket(
29
+
30
+ Error:
31
+
32
+ + if (!ResumptionAccepted) {
33
+ + Connection->Crypto.TicketValidationPending = FALSE;
34
+ + }
35
+ QuicCryptoTlsCleanupTransportParameters(&ResumedTP);
36
+ return ResumptionAccepted;
37
+ }
38
+ @@ -3001,6 +3006,23 @@ QuicConnProcessPeerTransportParameters(
39
+ Connection->SourceCidLimit = QUIC_TP_ACTIVE_CONNECTION_ID_LIMIT_DEFAULT;
40
+ }
41
+
42
+ + // Restore the cached capability before 0-RTT sends, then replace it with
43
+ + // the peer's actual capability when the new handshake supplies parameters.
44
+ + if (Connection->Settings.ReliableResetEnabled) {
45
+ + Connection->State.ReliableResetStreamNegotiated =
46
+ + !!(Connection->PeerTransportParams.Flags & QUIC_TP_FLAG_RELIABLE_RESET_ENABLED);
47
+ +
48
+ + QUIC_CONNECTION_EVENT Event;
49
+ + Event.Type = QUIC_CONNECTION_EVENT_RELIABLE_RESET_NEGOTIATED;
50
+ + Event.RELIABLE_RESET_NEGOTIATED.IsNegotiated = Connection->State.ReliableResetStreamNegotiated;
51
+ + QuicTraceLogConnVerbose(
52
+ + IndicateReliableResetNegotiated,
53
+ + Connection,
54
+ + "Indicating QUIC_CONNECTION_EVENT_RELIABLE_RESET_NEGOTIATED [IsNegotiated=%hhu]",
55
+ + Event.RELIABLE_RESET_NEGOTIATED.IsNegotiated);
56
+ + QuicConnIndicateEvent(Connection, &Event);
57
+ + }
58
+ +
59
+ if (!FromResumptionTicket) {
60
+ if (Connection->Settings.VersionNegotiationExtEnabled &&
61
+ Connection->PeerTransportParams.Flags & QUIC_TP_FLAG_VERSION_NEGOTIATION) {
62
+ @@ -3065,25 +3087,6 @@ QuicConnProcessPeerTransportParameters(
63
+ Connection->Stats.GreaseBitNegotiated = TRUE;
64
+ }
65
+
66
+ - if (Connection->Settings.ReliableResetEnabled) {
67
+ - Connection->State.ReliableResetStreamNegotiated =
68
+ - !!(Connection->PeerTransportParams.Flags & QUIC_TP_FLAG_RELIABLE_RESET_ENABLED);
69
+ -
70
+ - //
71
+ - // Send event to app to indicate result of negotiation if app cares.
72
+ - //
73
+ - QUIC_CONNECTION_EVENT Event;
74
+ - Event.Type = QUIC_CONNECTION_EVENT_RELIABLE_RESET_NEGOTIATED;
75
+ - Event.RELIABLE_RESET_NEGOTIATED.IsNegotiated = Connection->State.ReliableResetStreamNegotiated;
76
+ -
77
+ - QuicTraceLogConnVerbose(
78
+ - IndicateReliableResetNegotiated,
79
+ - Connection,
80
+ - "Indicating QUIC_CONNECTION_EVENT_RELIABLE_RESET_NEGOTIATED [IsNegotiated=%hhu]",
81
+ - Event.RELIABLE_RESET_NEGOTIATED.IsNegotiated);
82
+ - QuicConnIndicateEvent(Connection, &Event);
83
+ - }
84
+ -
85
+ if (Connection->Settings.OneWayDelayEnabled) {
86
+ Connection->State.TimestampSendNegotiated = // Peer wants to recv, so we can send
87
+ !!(Connection->PeerTransportParams.Flags & QUIC_TP_FLAG_TIMESTAMP_RECV_ENABLED);
88
+ @@ -7732,7 +7735,8 @@ QuicConnApplyNewSettings(
89
+ Connection->Stats.GreaseBitNegotiated = TRUE;
90
+ }
91
+
92
+ - if (QuicConnIsServer(Connection) && Connection->Settings.ReliableResetEnabled) {
93
+ + if (Connection->Settings.ReliableResetEnabled &&
94
+ + (QuicConnIsServer(Connection) || Connection->State.PeerTransportParameterValid)) {
95
+ Connection->State.ReliableResetStreamNegotiated =
96
+ !!(Connection->PeerTransportParams.Flags & QUIC_TP_FLAG_RELIABLE_RESET_ENABLED);
97
+
98
+ diff --git c/src/core/crypto.c i/src/core/crypto.c
99
+ index 91235bbc9..2dab61879 100644
100
+ --- c/src/core/crypto.c
101
+ +++ i/src/core/crypto.c
102
+ @@ -2578,6 +2578,7 @@ QuicCryptoEncodeServerTicket(
103
+ //
104
+ QUIC_TRANSPORT_PARAMETERS HSTPCopy = *HandshakeTP;
105
+ HSTPCopy.Flags &= (
106
+ + QUIC_TP_FLAG_RELIABLE_RESET_ENABLED |
107
+ QUIC_TP_FLAG_ACTIVE_CONNECTION_ID_LIMIT |
108
+ QUIC_TP_FLAG_INITIAL_MAX_DATA |
109
+ QUIC_TP_FLAG_INITIAL_MAX_STRM_DATA_BIDI_LOCAL |
110
+ diff --git c/src/core/crypto_tls.c i/src/core/crypto_tls.c
111
+ index 4fa6b8cc7..a905e624f 100644
112
+ --- c/src/core/crypto_tls.c
113
+ +++ i/src/core/crypto_tls.c
114
+ @@ -66,7 +66,7 @@ typedef enum eSniNameType {
115
+ #define QUIC_TP_ID_MIN_ACK_DELAY 0xFF04DE1BULL // varint
116
+ #define QUIC_TP_ID_CIBIR_ENCODING 0x1000 // {varint, varint}
117
+ #define QUIC_TP_ID_GREASE_QUIC_BIT 0x2AB2 // N/A
118
+ -#define QUIC_TP_ID_RELIABLE_RESET_ENABLED 0x17f7586d2cb570 // varint
119
+ +#define QUIC_TP_ID_RELIABLE_RESET_ENABLED 0x1d // empty reset_stream_at
120
+ #define QUIC_TP_ID_ENABLE_TIMESTAMP 0x7158 // varint
121
+
122
+ BOOLEAN
123
+ diff --git c/src/core/frame.c i/src/core/frame.c
124
+ index c5afcc628..fd2465fb0 100644
125
+ --- c/src/core/frame.c
126
+ +++ i/src/core/frame.c
127
+ @@ -457,7 +457,8 @@ QuicReliableResetFrameDecode(
128
+ if (!QuicVarIntDecode(BufferLength, Buffer, Offset, &Frame->StreamID) ||
129
+ !QuicVarIntDecode(BufferLength, Buffer, Offset, &Frame->ErrorCode) ||
130
+ !QuicVarIntDecode(BufferLength, Buffer, Offset, &Frame->FinalSize) ||
131
+ - !QuicVarIntDecode(BufferLength, Buffer, Offset, &Frame->ReliableSize)) {
132
+ + !QuicVarIntDecode(BufferLength, Buffer, Offset, &Frame->ReliableSize) ||
133
+ + Frame->ReliableSize > Frame->FinalSize) {
134
+ return FALSE;
135
+ }
136
+ return TRUE;
137
+ diff --git c/src/core/frame.h i/src/core/frame.h
138
+ index 563332740..7719f1301 100644
139
+ --- c/src/core/frame.h
140
+ +++ i/src/core/frame.h
141
+ @@ -149,9 +149,9 @@ typedef enum QUIC_FRAME_TYPE {
142
+ QUIC_FRAME_CONNECTION_CLOSE = 0x1cULL, // to 0x1d
143
+ QUIC_FRAME_CONNECTION_CLOSE_1 = 0x1dULL,
144
+ QUIC_FRAME_HANDSHAKE_DONE = 0x1eULL,
145
+ - /* 0x1f to 0x20 are unused currently */
146
+ - QUIC_FRAME_RELIABLE_RESET_STREAM = 0x21ULL, // intentionally ignore type 0x20 of QUIC_RESET_STREAM cause it's likely to be removed from RFC.
147
+ - /* 0x22 to 0x2f are unused currently */
148
+ + /* 0x20 to 0x23 are unused currently */
149
+ + QUIC_FRAME_RELIABLE_RESET_STREAM = 0x24ULL, // RESET_STREAM_AT
150
+ + /* 0x25 to 0x2f are unused currently */
151
+ QUIC_FRAME_DATAGRAM = 0x30ULL, // to 0x31
152
+ QUIC_FRAME_DATAGRAM_1 = 0x31ULL,
153
+ /* 0x32 to 0xad are unused currently */
154
+ diff --git c/src/core/stream.c i/src/core/stream.c
155
+ index ec426b406..f34388cb6 100644
156
+ --- c/src/core/stream.c
157
+ +++ i/src/core/stream.c
158
+ @@ -704,8 +704,15 @@ QuicStreamParamSet(
159
+ break;
160
+ }
161
+
162
+ + // StreamSend may still be queued when the application sets the boundary.
163
+ + // ApiSubmittedOffset counts everything passed to StreamSend and is
164
+ + // maintained under the same lock, so there is no window in which a
165
+ + // flush in progress makes a submitted send invisible.
166
+ + CxPlatDispatchLockAcquire(&Stream->ApiSendRequestLock);
167
+ + uint64_t SubmittedOffset = Stream->ApiSubmittedOffset;
168
+ + CxPlatDispatchLockRelease(&Stream->ApiSendRequestLock);
169
+ if (!Stream->Connection->State.ReliableResetStreamNegotiated ||
170
+ - *(uint64_t*)Buffer > Stream->QueuedSendOffset) {
171
+ + *(uint64_t*)Buffer > SubmittedOffset) {
172
+ Status = QUIC_STATUS_INVALID_STATE;
173
+ break;
174
+ }
175
+ @@ -962,7 +969,7 @@ QuicStreamParamGet(
176
+ break;
177
+ }
178
+
179
+ - *(uint64_t*)Buffer = Stream->RecvMaxLength;
180
+ + *(uint64_t*)Buffer = Stream->ReliableOffsetRecv;
181
+ Status = QUIC_STATUS_SUCCESS;
182
+ break;
183
+
184
+ diff --git c/src/core/stream.h i/src/core/stream.h
185
+ index 5a985692e..c4509ae6b 100644
186
+ --- c/src/core/stream.h
187
+ +++ i/src/core/stream.h
188
+ @@ -125,7 +125,7 @@ typedef union QUIC_STREAM_FLAGS {
189
+ BOOLEAN LocalCloseReset : 1; // Locally closed (locally aborted).
190
+ BOOLEAN LocalCloseResetReliable : 1; // Indicates that we should shutdown the send path once we sent/ACK'd ReliableOffsetSend bytes.
191
+ BOOLEAN LocalCloseResetReliableAcked : 1; // Indicates the peer has acknowledged we will stop sending once we sent/ACK'd ReliableOffsetSend bytes.
192
+ - BOOLEAN RemoteCloseResetReliable : 1; // Indicates that the peer initiated a reliable reset. Keep Recv path available for RecvMaxLength bytes.
193
+ + BOOLEAN RemoteCloseResetReliable : 1; // Indicates that the peer initiated a reliable reset. Keep Recv path available for ReliableOffsetRecv bytes.
194
+ BOOLEAN ReceivedStopSending : 1; // Peer sent STOP_SENDING frame.
195
+ BOOLEAN LocalCloseAcked : 1; // Any close acknowledged.
196
+ BOOLEAN FinAcked : 1; // Our FIN was acknowledged.
197
+ @@ -310,6 +310,16 @@ typedef struct QUIC_STREAM {
198
+ CXPLAT_DISPATCH_LOCK ApiSendRequestLock;
199
+ QUIC_SEND_REQUEST* ApiSendRequests;
200
+
201
+ + //
202
+ + // Total bytes the application has passed to StreamSend, whether or not
203
+ + // the worker has flushed them yet. Updated under ApiSendRequestLock.
204
+ + // QUIC_PARAM_STREAM_RELIABLE_OFFSET is bounded by this rather than by
205
+ + // QueuedSendOffset plus the ApiSendRequests list: the flush clears that
206
+ + // list before it advances QueuedSendOffset, so a concurrent reader of
207
+ + // the pair can see neither and reject a valid offset.
208
+ + //
209
+ + uint64_t ApiSubmittedOffset;
210
+ +
211
+ //
212
+ // Queued send requests.
213
+ //
214
+ @@ -435,6 +445,10 @@ typedef struct QUIC_STREAM {
215
+ //
216
+ uint64_t RecvMaxLength;
217
+
218
+ + // Reliable delivery boundary; RecvMaxLength remains the immutable final size.
219
+ + uint64_t ReliableOffsetRecv;
220
+ + QUIC_VAR_INT RecvResetErrorCode;
221
+ +
222
+ //
223
+ // The number of bytes that are currently outstanding up to the app.
224
+ //
225
+ diff --git c/src/core/stream_recv.c i/src/core/stream_recv.c
226
+ index 8d9640a9f..cd477447f 100644
227
+ --- c/src/core/stream_recv.c
228
+ +++ i/src/core/stream_recv.c
229
+ @@ -82,7 +82,8 @@ QuicStreamRecvShutdown(
230
+ // the receives to that point. Just treat the shutdown as if it was
231
+ // already acknowledged by a reset frame.
232
+ //
233
+ - QuicStreamProcessResetFrame(Stream, Stream->RecvMaxLength, 0);
234
+ + QuicStreamProcessResetFrame(Stream, Stream->RecvMaxLength,
235
+ + Stream->Flags.RemoteCloseResetReliable ? Stream->RecvResetErrorCode : 0);
236
+ Silent = TRUE; // To indicate we try to shutdown complete.
237
+ goto Exit;
238
+ }
239
+ @@ -189,6 +190,7 @@ _IRQL_requires_max_(PASSIVE_LEVEL)
240
+ void
241
+ QuicStreamProcessReliableResetFrame(
242
+ _In_ QUIC_STREAM* Stream,
243
+ + _In_ uint64_t FinalSize,
244
+ _In_ QUIC_VAR_INT ErrorCode,
245
+ _In_ QUIC_VAR_INT ReliableOffset
246
+ )
247
+ @@ -206,35 +208,43 @@ QuicStreamProcessReliableResetFrame(
248
+ return;
249
+ }
250
+
251
+ - if (Stream->RecvMaxLength == 0 || ReliableOffset < Stream->RecvMaxLength) {
252
+ - //
253
+ - // As outlined in the spec, if we receive multiple CLOSE_STREAM frames, we only accept strictly
254
+ - // decreasing offsets.
255
+ - //
256
+ - Stream->RecvMaxLength = ReliableOffset;
257
+ + if (ReliableOffset > FinalSize) {
258
+ + QuicConnTransportError(Stream->Connection, QUIC_ERROR_FRAME_ENCODING_ERROR);
259
+ + return;
260
+ + }
261
+ + if ((Stream->RecvMaxLength != UINT64_MAX && Stream->RecvMaxLength != FinalSize) ||
262
+ + QuicRecvBufferGetTotalLength(&Stream->RecvBuffer) > FinalSize) {
263
+ + QuicConnTransportError(Stream->Connection, QUIC_ERROR_FINAL_SIZE_ERROR);
264
+ + return;
265
+ + }
266
+ + if ((Stream->Flags.RemoteCloseResetReliable || Stream->Flags.RemoteCloseReset) &&
267
+ + Stream->RecvResetErrorCode != ErrorCode) {
268
+ + QuicConnTransportError(Stream->Connection, QUIC_ERROR_STREAM_STATE_ERROR);
269
+ + return;
270
+ + }
271
+ + if (Stream->Flags.RemoteCloseReset || Stream->Flags.RemoteCloseFin) {
272
+ + return;
273
+ + }
274
+ + if (!Stream->Flags.RemoteCloseResetReliable) {
275
+ + uint64_t Increase = FinalSize - QuicRecvBufferGetTotalLength(&Stream->RecvBuffer);
276
+ + if (FinalSize > Stream->MaxAllowedRecvOffset ||
277
+ + Increase > Stream->Connection->Send.MaxData - Stream->Connection->Send.OrderedStreamBytesReceived) {
278
+ + QuicConnTransportError(Stream->Connection, QUIC_ERROR_FLOW_CONTROL_ERROR);
279
+ + return;
280
+ + }
281
+ + // Charge the final size once, including bytes still missing from the buffer.
282
+ + Stream->Connection->Send.OrderedStreamBytesReceived += Increase;
283
+ + Stream->RecvMaxLength = FinalSize;
284
+ + Stream->ReliableOffsetRecv = ReliableOffset;
285
+ + Stream->RecvResetErrorCode = ErrorCode;
286
+ Stream->Flags.RemoteCloseResetReliable = TRUE;
287
+ -
288
+ - QuicTraceLogStreamInfo(
289
+ - ReliableRecvOffsetSet,
290
+ - Stream,
291
+ - "Reliable recv offset set to %llu",
292
+ - ReliableOffset);
293
+ + } else if (ReliableOffset < Stream->ReliableOffsetRecv) {
294
+ + Stream->ReliableOffsetRecv = ReliableOffset;
295
+ + }
296
+ + if (Stream->RecvBuffer.BaseOffset >= Stream->ReliableOffsetRecv || Stream->Flags.SentStopSending) {
297
+ + QuicStreamProcessResetFrame(Stream, FinalSize, ErrorCode);
298
+ }
299
+
300
+ - if (Stream->RecvBuffer.BaseOffset >= Stream->RecvMaxLength) {
301
+ - QuicTraceEvent(
302
+ - StreamRecvState,
303
+ - "[strm][%p] Recv State: %hhu",
304
+ - Stream,
305
+ - QuicStreamRecvGetState(Stream));
306
+ - QuicStreamIndicatePeerSendAbortedEvent(Stream, ErrorCode);
307
+ - QuicStreamRecvShutdown(Stream, TRUE, ErrorCode);
308
+ - } else {
309
+ - //
310
+ - // We still have data to deliver to the app, just cache the error code for later.
311
+ - //
312
+ - Stream->RecvShutdownErrorCode = ErrorCode;
313
+ - }
314
+ }
315
+
316
+ //
317
+ @@ -248,10 +258,22 @@ QuicStreamProcessResetFrame(
318
+ _In_ QUIC_VAR_INT ErrorCode
319
+ )
320
+ {
321
+ + if (Stream->RecvMaxLength != UINT64_MAX && FinalSize != Stream->RecvMaxLength) {
322
+ + QuicConnTransportError(Stream->Connection, QUIC_ERROR_FINAL_SIZE_ERROR);
323
+ + return;
324
+ + }
325
+ + if (Stream->Flags.RemoteCloseResetReliable || Stream->Flags.RemoteCloseReset) {
326
+ + if (ErrorCode != Stream->RecvResetErrorCode) {
327
+ + QuicConnTransportError(Stream->Connection, QUIC_ERROR_STREAM_STATE_ERROR);
328
+ + return;
329
+ + }
330
+ + }
331
+ //
332
+ // Make sure the stream is remotely closed if not already.
333
+ //
334
+ Stream->Flags.RemoteCloseReset = TRUE;
335
+ + Stream->RecvMaxLength = FinalSize;
336
+ + Stream->RecvResetErrorCode = ErrorCode;
337
+
338
+ if (!Stream->Flags.RemoteCloseAcked) {
339
+ Stream->Flags.RemoteCloseAcked = TRUE;
340
+ @@ -272,7 +294,7 @@ QuicStreamProcessResetFrame(
341
+ return;
342
+ }
343
+
344
+ - if (TotalRecvLength < FinalSize) {
345
+ + if (TotalRecvLength < FinalSize && !Stream->Flags.RemoteCloseResetReliable) {
346
+ //
347
+ // The final offset is indicating that more data was sent than we
348
+ // have actually received. Make sure to update our flow control
349
+ @@ -442,19 +464,8 @@ QuicStreamProcessStreamFrame(
350
+ goto Error;
351
+ }
352
+
353
+ - if (Stream->Flags.RemoteCloseResetReliable) {
354
+ - if (Stream->RecvBuffer.BaseOffset >= Stream->RecvMaxLength) {
355
+ - //
356
+ - // We've aborted reliably, but the stream goes past reliable offset, we can just
357
+ - // ignore it.
358
+ - //
359
+ - Status = QUIC_STATUS_SUCCESS;
360
+ - goto Error;
361
+ - }
362
+ - } else if (EndOffset > Stream->RecvMaxLength) {
363
+ - //
364
+ - // Frame goes past the FIN, and the stream is not reset reliably.
365
+ - //
366
+ + if (EndOffset > Stream->RecvMaxLength) {
367
+ + QuicConnTransportError(Stream->Connection, QUIC_ERROR_FINAL_SIZE_ERROR);
368
+ Status = QUIC_STATUS_INVALID_PARAMETER;
369
+ goto Error;
370
+ }
371
+ @@ -481,9 +492,9 @@ QuicStreamProcessStreamFrame(
372
+ // On return from QuicRecvBufferWrite, this represents the
373
+ // actual number of bytes written.
374
+ //
375
+ - const uint64_t FlowControlQuota =
376
+ - Stream->Connection->Send.MaxData -
377
+ - Stream->Connection->Send.OrderedStreamBytesReceived;
378
+ + const uint64_t FlowControlQuota = Stream->Flags.RemoteCloseResetReliable ?
379
+ + Stream->RecvMaxLength - QuicRecvBufferGetTotalLength(&Stream->RecvBuffer) :
380
+ + Stream->Connection->Send.MaxData - Stream->Connection->Send.OrderedStreamBytesReceived;
381
+ uint64_t QuotaConsumed = 0;
382
+ uint64_t BufferSizeNeeded = 0;
383
+
384
+ @@ -541,7 +552,9 @@ QuicStreamProcessStreamFrame(
385
+ //
386
+ // Keep track of the total ordered bytes received.
387
+ //
388
+ - Stream->Connection->Send.OrderedStreamBytesReceived += QuotaConsumed;
389
+ + if (!Stream->Flags.RemoteCloseResetReliable) {
390
+ + Stream->Connection->Send.OrderedStreamBytesReceived += QuotaConsumed;
391
+ + }
392
+ CXPLAT_DBG_ASSERT(Stream->Connection->Send.OrderedStreamBytesReceived <= Stream->Connection->Send.MaxData);
393
+ CXPLAT_DBG_ASSERT(Stream->Connection->Send.OrderedStreamBytesReceived >= QuotaConsumed);
394
+
395
+ @@ -735,6 +748,7 @@ QuicStreamRecv(
396
+
397
+ QuicStreamProcessReliableResetFrame(
398
+ Stream,
399
+ + Frame.FinalSize,
400
+ Frame.ErrorCode,
401
+ Frame.ReliableSize);
402
+
403
+ @@ -981,7 +995,8 @@ QuicStreamRecvFlush(
404
+ //
405
+ }
406
+
407
+ - if (Event.RECEIVE.AbsoluteOffset + Event.RECEIVE.TotalBufferLength == Stream->RecvMaxLength) {
408
+ + if (!Stream->Flags.RemoteCloseResetReliable &&
409
+ + Event.RECEIVE.AbsoluteOffset + Event.RECEIVE.TotalBufferLength == Stream->RecvMaxLength) {
410
+ //
411
+ // This data goes all the way to the FIN.
412
+ //
413
+ @@ -1158,6 +1173,11 @@ QuicStreamReceiveComplete(
414
+ return FALSE;
415
+ }
416
+
417
+ + if (Stream->Flags.RemoteCloseResetReliable && Stream->RecvBuffer.BaseOffset >= Stream->ReliableOffsetRecv) {
418
+ + QuicStreamProcessResetFrame(Stream, Stream->RecvMaxLength, Stream->RecvResetErrorCode);
419
+ + return FALSE;
420
+ + }
421
+ +
422
+ if (Stream->Flags.ReceiveDataPending) {
423
+ //
424
+ // There is still more data for the app to process and it still has
425
+ @@ -1204,18 +1224,6 @@ QuicStreamReceiveComplete(
426
+ &Stream->Connection->Send,
427
+ Stream,
428
+ QUIC_STREAM_SEND_FLAG_MAX_DATA | QUIC_STREAM_SEND_FLAG_RECV_ABORT);
429
+ - } else if (Stream->Flags.RemoteCloseResetReliable && Stream->RecvBuffer.BaseOffset >= Stream->RecvMaxLength) {
430
+ - //
431
+ - // ReliableReset was initiated by the peer, and we sent enough data to the app, we can alert the app
432
+ - // we're done and shutdown the RECV direction of this stream.
433
+ - //
434
+ - QuicTraceEvent(
435
+ - StreamRecvState,
436
+ - "[strm][%p] Recv State: %hhu",
437
+ - Stream,
438
+ - QuicStreamRecvGetState(Stream));
439
+ - QuicStreamIndicatePeerSendAbortedEvent(Stream, Stream->RecvShutdownErrorCode);
440
+ - QuicStreamRecvShutdown(Stream, TRUE, Stream->RecvShutdownErrorCode);
441
+ }
442
+
443
+ return FALSE;
444
+ diff --git c/src/core/stream_send.c i/src/core/stream_send.c
445
+ index 0ac7dd101..ca70133df 100644
446
+ --- c/src/core/stream_send.c
447
+ +++ i/src/core/stream_send.c
448
+ @@ -119,7 +119,8 @@ QuicStreamSendShutdown(
449
+ _In_ QUIC_VAR_INT ErrorCode // Only for !Graceful
450
+ )
451
+ {
452
+ - if (Stream->Flags.LocalCloseAcked) {
453
+ + if (Stream->Flags.LocalCloseAcked ||
454
+ + (Stream->Flags.LocalCloseResetReliable && !Silent)) {
455
+ //
456
+ // We have already closed (graceful or abortive) and it has been
457
+ // acknowledged by the peer. Nothing else to be done.
458
+ @@ -275,7 +276,7 @@ QuicStreamSendShutdown(
459
+ QuicSendSetStreamSendFlag(
460
+ &Stream->Connection->Send,
461
+ Stream,
462
+ - QUIC_STREAM_SEND_FLAG_RELIABLE_ABORT,
463
+ + QUIC_STREAM_SEND_FLAG_RELIABLE_ABORT | QUIC_STREAM_SEND_FLAG_DATA,
464
+ FALSE);
465
+ }
466
+
467
+ @@ -361,6 +362,10 @@ QuicStreamSendCanWriteDataFrames(
468
+ return TRUE;
469
+ }
470
+
471
+ + if (Stream->Flags.LocalCloseResetReliable &&
472
+ + Stream->NextSendOffset >= CXPLAT_MAX(Stream->MaxSentLength, Stream->ReliableOffsetSend)) {
473
+ + return FALSE;
474
+ + }
475
+ if (Stream->NextSendOffset == Stream->QueuedSendOffset) {
476
+ //
477
+ // No unsent data. Can send only if a FIN is needed.
478
+ @@ -392,7 +397,11 @@ QuicStreamCanSendNow(
479
+ return FALSE;
480
+ }
481
+
482
+ - if (HasStreamControlFrames(Stream->SendFlags) ||
483
+ + uint32_t SendFlags = Stream->SendFlags;
484
+ + if (Stream->MaxSentLength < Stream->ReliableOffsetSend) {
485
+ + SendFlags &= ~QUIC_STREAM_SEND_FLAG_RELIABLE_ABORT;
486
+ + }
487
+ + if (HasStreamControlFrames(SendFlags) ||
488
+ (Stream->SendFlags & QUIC_STREAM_SEND_FLAG_OPEN)) {
489
+ //
490
+ // We can always send control frames and/or open new streams.
491
+ @@ -963,6 +972,12 @@ QuicStreamWriteStreamFrames(
492
+ }
493
+ }
494
+
495
+ + if (Stream->Flags.LocalCloseResetReliable) {
496
+ + const uint64_t FinalSize = CXPLAT_MAX(Stream->MaxSentLength, Stream->ReliableOffsetSend);
497
+ + if (Left >= FinalSize) { break; }
498
+ + Right = CXPLAT_MIN(Right, FinalSize);
499
+ + }
500
+ +
501
+ //
502
+ // Stream flow control
503
+ //
504
+ @@ -1150,7 +1165,8 @@ QuicStreamSendWrite(
505
+ }
506
+ }
507
+
508
+ - if (Stream->SendFlags & QUIC_STREAM_SEND_FLAG_RELIABLE_ABORT) {
509
+ + if ((Stream->SendFlags & QUIC_STREAM_SEND_FLAG_RELIABLE_ABORT) &&
510
+ + Stream->MaxSentLength >= Stream->ReliableOffsetSend) {
511
+ QUIC_RELIABLE_RESET_STREAM_EX Frame = { Stream->ID, Stream->SendShutdownErrorCode, Stream->MaxSentLength, Stream->ReliableOffsetSend };
512
+
513
+ if (QuicReliableResetFrameEncode(
514
+ diff --git c/src/core/unittest/CMakeLists.txt i/src/core/unittest/CMakeLists.txt
515
+ index ae5411ae9..04f79f63c 100644
516
+ --- c/src/core/unittest/CMakeLists.txt
517
+ +++ i/src/core/unittest/CMakeLists.txt
518
+ @@ -9,6 +9,7 @@ set(SOURCES
519
+ PacketNumberTest.cpp
520
+ PartitionTest.cpp
521
+ RangeTest.cpp
522
+ + ReliableResetTest.cpp
523
+ RecvBufferTest.cpp
524
+ SettingsTest.cpp
525
+ SlidingWindowExtremumTest.cpp
526
+ diff --git c/src/core/unittest/FrameTest.cpp i/src/core/unittest/FrameTest.cpp
527
+ index f3cde7e73..dcd7edb02 100644
528
+ --- c/src/core/unittest/FrameTest.cpp
529
+ +++ i/src/core/unittest/FrameTest.cpp
530
+ @@ -243,6 +243,26 @@ TEST(FrameTest, ReliableResetStreamFrameEncodeDecode)
531
+ ASSERT_EQ(Frame.ReliableSize, DecodedFrame.ReliableSize);
532
+ }
533
+
534
+ +// draft-ietf-quic-reliable-stream-reset-09, sections 4 and 8.2.
535
+ +TEST(FrameTest, ResetStreamAtUsesRegisteredWireType)
536
+ +{
537
+ + QUIC_RELIABLE_RESET_STREAM_EX Frame = {0, 7, 10, 3};
538
+ + uint8_t Buffer[5];
539
+ + uint16_t Offset = 0;
540
+ + ASSERT_TRUE(QuicReliableResetFrameEncode(&Frame, &Offset, sizeof(Buffer), Buffer));
541
+ + const uint8_t Expected[] = {0x24, 0, 7, 10, 3};
542
+ + ASSERT_EQ(sizeof(Expected), Offset);
543
+ + ASSERT_EQ(0, memcmp(Expected, Buffer, sizeof(Expected)));
544
+ +}
545
+ +
546
+ +TEST(FrameTest, ResetStreamAtRejectsReliableSizeBeyondFinalSize)
547
+ +{
548
+ + const uint8_t Buffer[] = {0x24, 0, 7, 3, 4};
549
+ + uint16_t Offset = 1;
550
+ + QUIC_RELIABLE_RESET_STREAM_EX Frame = {};
551
+ + ASSERT_FALSE(QuicReliableResetFrameDecode(sizeof(Buffer), Buffer, &Offset, &Frame));
552
+ +}
553
+ +
554
+ //
555
+ // Tests if the reordering threshold has been hit. This function initializes a
556
+ // QUIC_ACK_TRACKER and populates it with the provided packet numbers. It then
557
+ diff --git c/src/core/unittest/ReliableResetTest.cpp i/src/core/unittest/ReliableResetTest.cpp
558
+ new file mode 100644
559
+ index 000000000..8c4e15294
560
+ --- /dev/null
561
+ +++ i/src/core/unittest/ReliableResetTest.cpp
562
+ @@ -0,0 +1,33 @@
563
+ +// Copyright (c) Microsoft Corporation.
564
+ +// Licensed under the MIT License.
565
+ +#include "main.h"
566
+ +
567
+ +extern "C" void QuicStreamProcessReliableResetFrame(
568
+ + QUIC_STREAM*, uint64_t, QUIC_VAR_INT, QUIC_VAR_INT);
569
+ +
570
+ +TEST(ReliableResetTest, FinalSizeIsChargedOnceWhileWaitingForPrefix)
571
+ +{
572
+ + QUIC_CONNECTION Connection = {};
573
+ + Connection.State.ReliableResetStreamNegotiated = TRUE;
574
+ + Connection.Send.MaxData = 100;
575
+ + QUIC_STREAM Stream = {};
576
+ + Stream.Connection = &Connection;
577
+ + Stream.RecvMaxLength = UINT64_MAX;
578
+ + Stream.MaxAllowedRecvOffset = 100;
579
+ +
580
+ + QuicStreamProcessReliableResetFrame(&Stream, 50, 42, 10);
581
+ + EXPECT_EQ(50u, Connection.Send.OrderedStreamBytesReceived);
582
+ + EXPECT_EQ(50u, Stream.RecvMaxLength);
583
+ + EXPECT_EQ(10u, Stream.ReliableOffsetRecv);
584
+ + EXPECT_EQ(100u, Connection.Send.MaxData); // No credit returned before consumption.
585
+ +
586
+ + QuicStreamProcessReliableResetFrame(&Stream, 50, 42, 5);
587
+ + EXPECT_EQ(50u, Connection.Send.OrderedStreamBytesReceived);
588
+ + EXPECT_EQ(50u, Stream.RecvMaxLength);
589
+ + EXPECT_EQ(5u, Stream.ReliableOffsetRecv);
590
+ +
591
+ + // A reordered copy must not increase the reliable boundary or charge again.
592
+ + QuicStreamProcessReliableResetFrame(&Stream, 50, 42, 10);
593
+ + EXPECT_EQ(50u, Connection.Send.OrderedStreamBytesReceived);
594
+ + EXPECT_EQ(5u, Stream.ReliableOffsetRecv);
595
+ +}
596
+ diff --git c/src/core/unittest/TicketTest.cpp i/src/core/unittest/TicketTest.cpp
597
+ index 6a55d9e11..493f6c5c8 100644
598
+ --- c/src/core/unittest/TicketTest.cpp
599
+ +++ i/src/core/unittest/TicketTest.cpp
600
+ @@ -57,7 +57,8 @@ TEST(ResumptionTicketTest, ClientEncDec)
601
+ QUIC_TP_FLAG_INITIAL_MAX_STRM_DATA_BIDI_REMOTE |
602
+ QUIC_TP_FLAG_INITIAL_MAX_STRM_DATA_UNI |
603
+ QUIC_TP_FLAG_INITIAL_MAX_STRMS_BIDI |
604
+ - QUIC_TP_FLAG_INITIAL_MAX_STRMS_UNI;
605
+ + QUIC_TP_FLAG_INITIAL_MAX_STRMS_UNI |
606
+ + QUIC_TP_FLAG_RELIABLE_RESET_ENABLED;
607
+ ClientTP.ActiveConnectionIdLimit = QUIC_TP_ACTIVE_CONNECTION_ID_LIMIT_MIN;
608
+
609
+ TEST_QUIC_SUCCEEDED(
610
+ @@ -481,7 +482,8 @@ TEST(ResumptionTicketTest, ServerEncDec)
611
+ QUIC_TP_FLAG_INITIAL_MAX_STRM_DATA_BIDI_REMOTE |
612
+ QUIC_TP_FLAG_INITIAL_MAX_STRM_DATA_UNI |
613
+ QUIC_TP_FLAG_INITIAL_MAX_STRMS_BIDI |
614
+ - QUIC_TP_FLAG_INITIAL_MAX_STRMS_UNI;
615
+ + QUIC_TP_FLAG_INITIAL_MAX_STRMS_UNI |
616
+ + QUIC_TP_FLAG_RELIABLE_RESET_ENABLED;
617
+ ServerTP.ActiveConnectionIdLimit = QUIC_TP_ACTIVE_CONNECTION_ID_LIMIT_MIN;
618
+
619
+ TEST_QUIC_SUCCEEDED(
620
+ diff --git c/src/core/unittest/TransportParamTest.cpp i/src/core/unittest/TransportParamTest.cpp
621
+ index d5ad1e7f7..555e9a302 100644
622
+ --- c/src/core/unittest/TransportParamTest.cpp
623
+ +++ i/src/core/unittest/TransportParamTest.cpp
624
+ @@ -262,6 +262,25 @@ TEST(TransportParamTest, GreaseQuicBit)
625
+ EncodeDecodeAndCompare(&OriginalTP, true);
626
+ }
627
+
628
+ +TEST(TransportParamTest, ResetStreamAtRecognizesRegisteredParameter)
629
+ +{
630
+ + const uint8_t Buffer[] = {0x1d, 0};
631
+ + QUIC_TRANSPORT_PARAMETERS Decoded = {};
632
+ + TransportParametersScope Scope(&Decoded);
633
+ + ASSERT_TRUE(QuicCryptoTlsDecodeTransportParameters(
634
+ + &JunkConnection, false, Buffer, sizeof(Buffer), &Decoded));
635
+ + ASSERT_NE(0u, Decoded.Flags & QUIC_TP_FLAG_RELIABLE_RESET_ENABLED);
636
+ +}
637
+ +
638
+ +TEST(TransportParamTest, ResetStreamAtRejectsNonemptyParameter)
639
+ +{
640
+ + const uint8_t Buffer[] = {0x1d, 1, 0};
641
+ + QUIC_TRANSPORT_PARAMETERS Decoded = {};
642
+ + TransportParametersScope Scope(&Decoded);
643
+ + ASSERT_FALSE(QuicCryptoTlsDecodeTransportParameters(
644
+ + &JunkConnection, false, Buffer, sizeof(Buffer), &Decoded));
645
+ +}
646
+ +
647
+ TEST(TransportParamTest, ReliableResetEnabled)
648
+ {
649
+ QUIC_TRANSPORT_PARAMETERS OriginalTP;
650
+ diff --git c/src/test/lib/DataTest.cpp i/src/test/lib/DataTest.cpp
651
+ index e1c2ca2ac..84a87d9db 100644
652
+ --- c/src/test/lib/DataTest.cpp
653
+ +++ i/src/test/lib/DataTest.cpp
654
+ @@ -4415,6 +4415,9 @@ QuicTestStreamReliableReset(
655
+ MsQuicStream Stream(Connection, QUIC_STREAM_OPEN_FLAG_NONE, CleanUpManual, StreamReliableReset::ClientStreamCallback, &Context);
656
+ TEST_QUIC_SUCCEEDED(Stream.GetInitStatus());
657
+ TEST_QUIC_SUCCEEDED(Stream.Start());
658
+ + // Each loss pattern must deliver its own reliable prefix and error.
659
+ + Context.ReceivedBufferSize = 0;
660
+ + Context.ShutdownErrorCode = 0;
661
+ SendContext send1 = {FALSE, 0};
662
+ TEST_QUIC_SUCCEEDED(Stream.Send(&SendBuffer, 1, QUIC_SEND_FLAG_DELAY_SEND, &send1));
663
+ TEST_QUIC_STATUS(
664
+ diff --git c/src/test/lib/HandshakeTest.cpp i/src/test/lib/HandshakeTest.cpp
665
+ index a787edf70..a16a1fb2b 100644
666
+ --- c/src/test/lib/HandshakeTest.cpp
667
+ +++ i/src/test/lib/HandshakeTest.cpp
668
+ @@ -2815,6 +2815,7 @@ QuicTestReliableResetNegotiation(
669
+ MsQuicSettings ServerSettings;
670
+ MsQuicSettings ClientSettings;
671
+ ServerSettings.SetReliableResetEnabled(ServerSupport);
672
+ + ServerSettings.SetServerResumptionLevel(QUIC_SERVER_RESUME_AND_ZERORTT);
673
+ ClientSettings.SetReliableResetEnabled(ClientSupport);
674
+
675
+ MsQuicConfiguration ServerConfiguration(Registration, "MsQuicTest", ServerSettings, ServerSelfSignedCredConfig);
676
+ @@ -2856,6 +2857,47 @@ QuicTestReliableResetNegotiation(
677
+ } else {
678
+ TEST_FALSE(ServerContext.CallbackReceived);
679
+ }
680
+ +
681
+ + TEST_QUIC_SUCCEEDED(Listener.LastConnection->SendResumptionTicket(QUIC_SEND_RESUMPTION_FLAG_NONE));
682
+ + TEST_TRUE(Connection.ResumptionTicketReceivedEvent.WaitTimeout(TestWaitTimeout));
683
+ +
684
+ + for (bool SettingsBeforeTicket : {false, true}) {
685
+ + Context ResumedContext;
686
+ + MsQuicConnection Resumed(Registration, CleanUpManual, Context::s_ConnectionCallback, &ResumedContext);
687
+ + if (SettingsBeforeTicket) {
688
+ + TEST_QUIC_SUCCEEDED(Resumed.SetSettings(ClientSettings));
689
+ + }
690
+ + TEST_QUIC_SUCCEEDED(Resumed.SetResumptionTicket(Connection.ResumptionTicket, Connection.ResumptionTicketLength));
691
+ + if (!SettingsBeforeTicket) {
692
+ + TEST_QUIC_SUCCEEDED(Resumed.SetSettings(ClientSettings));
693
+ + }
694
+ + if (ClientSupport) {
695
+ + // The cached capability must be available before the new handshake.
696
+ + TEST_TRUE(ResumedContext.CallbackReceived);
697
+ + TEST_TRUE(ResumedContext.Negotiated == ServerSupport);
698
+ + }
699
+ + TEST_QUIC_SUCCEEDED(Resumed.Start(ClientConfiguration, ServerLocalAddr.GetFamily(), QUIC_TEST_LOOPBACK_FOR_AF(ServerLocalAddr.GetFamily()), ServerLocalAddr.GetPort()));
700
+ + TEST_TRUE(Resumed.HandshakeCompleteEvent.WaitTimeout(TestWaitTimeout));
701
+ + TEST_TRUE(Resumed.HandshakeComplete);
702
+ + TEST_TRUE(Resumed.HandshakeResumed);
703
+ + TEST_TRUE(ResumedContext.Negotiated == (ServerSupport && ClientSupport));
704
+ + }
705
+ +
706
+ + if (ServerSupport && ClientSupport) {
707
+ + MsQuicSettings DisabledSettings;
708
+ + DisabledSettings.SetReliableResetEnabled(false);
709
+ + TEST_QUIC_SUCCEEDED(ServerConfiguration.SetSettings(DisabledSettings));
710
+ +
711
+ + Context DowngradedContext;
712
+ + MsQuicConnection Downgraded(Registration, CleanUpManual, Context::s_ConnectionCallback, &DowngradedContext);
713
+ + TEST_QUIC_SUCCEEDED(Downgraded.SetSettings(ClientSettings));
714
+ + TEST_QUIC_SUCCEEDED(Downgraded.SetResumptionTicket(Connection.ResumptionTicket, Connection.ResumptionTicketLength));
715
+ + TEST_QUIC_SUCCEEDED(Downgraded.Start(ClientConfiguration, ServerLocalAddr.GetFamily(), QUIC_TEST_LOOPBACK_FOR_AF(ServerLocalAddr.GetFamily()), ServerLocalAddr.GetPort()));
716
+ + TEST_TRUE(Downgraded.HandshakeCompleteEvent.WaitTimeout(TestWaitTimeout));
717
+ + TEST_TRUE(Downgraded.HandshakeComplete);
718
+ + TEST_FALSE(Downgraded.HandshakeResumed);
719
+ + TEST_FALSE(DowngradedContext.Negotiated);
720
+ + }
721
+ }
722
+
723
+ void