aboutsummaryrefslogtreecommitdiffstats
path: root/test/rfc9204_SUITE.erl
blob: e8defd2a17520e13ec263b54538386e8f15a7dc4 (plain) (blame)
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
299
300
301
302
303
304
305
306
307
308
309
310
311
312
313
314
315
316
317
318
319
320
321
322
323
324
325
326
327
328
329
330
331
332
333
334
335
336
337
338
339
340
341
342
343
344
345
346
347
348
349
350
351
352
353
354
355
356
357
%% Copyright (c) 2024, Loïc Hoguin <[email protected]>
%%
%% Permission to use, copy, modify, and/or distribute this software for any
%% purpose with or without fee is hereby granted, provided that the above
%% copyright notice and this permission notice appear in all copies.
%%
%% THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES
%% WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF
%% MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR
%% ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES
%% WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN
%% ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF
%% OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE.

-module(rfc9204_SUITE).
-compile(export_all).
-compile(nowarn_export_all).

-import(ct_helper, [config/2]).
-import(ct_helper, [doc/1]).

-ifdef(COWBOY_QUICER).

-include_lib("quicer/include/quicer.hrl").

all() ->
	[{group, h3}].

groups() ->
	%% @todo Enable parallel tests but for this issues in the
	%% QUIC accept loop need to be figured out (can't connect
	%% concurrently somehow, no backlog?).
	[{h3, [], ct_helper:all(?MODULE)}].

init_per_group(Name = h3, Config) ->
	cowboy_test:init_http3(Name, #{
		env => #{dispatch => cowboy_router:compile(init_routes(Config))}
	}, Config).

end_per_group(Name, _) ->
	cowboy_test:stop_group(Name).

init_routes(_) -> [
	{"localhost", [
		{"/", hello_h, []}
	]}
].

%% Encoder.

%% 2.1
%% QPACK preserves the ordering of field lines within
%% each field section. An encoder MUST emit field
%% representations in the order they appear in the
%% input field section.

%% 2.1.1
%% If the dynamic table does not contain enough room
%% for a new entry without evicting other entries,
%% and the entries that would be evicted are not evictable,
%% the encoder MUST NOT insert that entry into the dynamic
%% table (including duplicates of existing entries).
%% In order to avoid this, an encoder that uses the
%% dynamic table has to keep track of each dynamic
%% table entry referenced by each field section until
%% those representations are acknowledged by the decoder;
%% see Section 4.4.1.

%% 2.1.2
%% The decoder specifies an upper bound on the number
%% of streams that can be blocked using the
%% SETTINGS_QPACK_BLOCKED_STREAMS setting; see Section 5.
%% An encoder MUST limit the number of streams that could
%% become blocked to the value of SETTINGS_QPACK_BLOCKED_STREAMS
%% at all times. If a decoder encounters more blocked streams
%% than it promised to support, it MUST treat this as a
%% connection error of type QPACK_DECOMPRESSION_FAILED.

%% 2.1.3
%% To avoid these deadlocks, an encoder SHOULD NOT
%% write an instruction unless sufficient stream and
%% connection flow-control credit is available for
%% the entire instruction.

%% Decoder.

%% 2.2
%% The decoder MUST emit field lines in the order their
%% representations appear in the encoded field section.

%% 2.2.1
%% While blocked, encoded field section data SHOULD
%% remain in the blocked stream's flow-control window.

%% If it encounters a Required Insert Count smaller than
%% expected, it MUST treat this as a connection error of
%% type QPACK_DECOMPRESSION_FAILED; see Section 2.2.3.

%% If it encounters a Required Insert Count larger than
%% expected, it MAY treat this as a connection error of
%% type QPACK_DECOMPRESSION_FAILED.

%% After the decoder finishes decoding a field section
%% encoded using representations containing dynamic table
%% references, it MUST emit a Section Acknowledgment
%% instruction (Section 4.4.1).

%% 2.2.2.2
%% A decoder with a maximum dynamic table capacity
%% (Section 3.2.3) equal to zero MAY omit sending Stream
%% Cancellations, because the encoder cannot have any
%% dynamic table references.

%% 2.2.3
%% If the decoder encounters a reference in a field line
%% representation to a dynamic table entry that has already
%% been evicted or that has an absolute index greater than
%% or equal to the declared Required Insert Count (Section 4.5.1),
%% it MUST treat this as a connection error of type
%% QPACK_DECOMPRESSION_FAILED.

%% If the decoder encounters a reference in an encoder
%% instruction to a dynamic table entry that has already
%% been evicted, it MUST treat this as a connection error
%% of type QPACK_ENCODER_STREAM_ERROR.

%% Static table.

%% 3.1
%% When the decoder encounters an invalid static table index
%% in a field line representation, it MUST treat this as a
%% connection error of type QPACK_DECOMPRESSION_FAILED.
%%
%% If this index is received on the encoder stream, this
%% MUST be treated as a connection error of type
%% QPACK_ENCODER_STREAM_ERROR.

%% Dynamic table.

%% 3.2
%% The dynamic table can contain duplicate entries
%% (i.e., entries with the same name and same value).
%% Therefore, duplicate entries MUST NOT be treated
%% as an error by the decoder.

%% 3.2.2
%% The encoder MUST NOT cause a dynamic table entry to be
%% evicted unless that entry is evictable; see Section 2.1.1.

%% It is an error if the encoder attempts to add an entry
%% that is larger than the dynamic table capacity; the
%% decoder MUST treat this as a connection error of type
%% QPACK_ENCODER_STREAM_ERROR.

%% 3.2.3
%% The encoder MUST NOT set a dynamic table capacity that
%% exceeds this maximum, but it can choose to use a lower
%% dynamic table capacity; see Section 4.3.1.

%% When the client's 0-RTT value of the SETTING is zero,
%% the server MAY set it to a non-zero value in its SETTINGS
%% frame. If the remembered value is non-zero, the server
%% MUST send the same non-zero value in its SETTINGS frame.
%% If it specifies any other value, or omits
%% SETTINGS_QPACK_MAX_TABLE_CAPACITY from SETTINGS,
%% the encoder must treat this as a connection error of
%% type QPACK_DECODER_STREAM_ERROR.

%% When the maximum table capacity is zero, the encoder
%% MUST NOT insert entries into the dynamic table and
%% MUST NOT send any encoder instructions on the encoder stream.

%% Wire format.

%% 4.1.1
%% QPACK implementations MUST be able to decode integers
%% up to and including 62 bits long.

%% Encoder and decoder streams.

decoder_reject_multiple(Config) ->
	doc("Endpoints must not create multiple decoder streams. (RFC9204 4.2)"),
	rfc9114_SUITE:do_critical_reject_multiple(Config, <<3>>).

encoder_reject_multiple(Config) ->
	doc("Endpoints must not create multiple encoder streams. (RFC9204 4.2)"),
	rfc9114_SUITE:do_critical_reject_multiple(Config, <<2>>).

%% 4.2
%% The sender MUST NOT close either of these streams,
%% and the receiver MUST NOT request that the sender close
%% either of these streams. Closure of either unidirectional
%% stream type MUST be treated as a connection error of type
%% H3_CLOSED_CRITICAL_STREAM.

decoder_local_closed_abort(Config) ->
	doc("Endpoints must not close the decoder stream. (RFC9204 4.2)"),
	rfc9114_SUITE:do_critical_local_closed_abort(Config, <<3>>).

decoder_local_closed_graceful(Config) ->
	doc("Endpoints must not close the decoder stream. (RFC9204 4.2)"),
	rfc9114_SUITE:do_critical_local_closed_graceful(Config, <<3>>).

decoder_remote_closed_abort(Config) ->
	doc("Endpoints must not close the decoder stream. (RFC9204 4.2)"),
	#{conn := Conn} = rfc9114_SUITE:do_connect(Config, #{peer_unidi_stream_count => 3}),
	{ok, #{decoder := StreamRef}} = do_wait_unidi_streams(Conn, #{}),
	%% Close the control stream.
	quicer:async_shutdown_stream(StreamRef, ?QUIC_STREAM_SHUTDOWN_FLAG_ABORT, 0),
	%% The connection should have been closed.
	#{reason := h3_closed_critical_stream} = rfc9114_SUITE:do_wait_connection_closed(Conn),
	ok.

encoder_local_closed_abort(Config) ->
	doc("Endpoints must not close the encoder stream. (RFC9204 4.2)"),
	rfc9114_SUITE:do_critical_local_closed_abort(Config, <<2>>).

encoder_local_closed_graceful(Config) ->
	doc("Endpoints must not close the encoder stream. (RFC9204 4.2)"),
	rfc9114_SUITE:do_critical_local_closed_graceful(Config, <<2>>).

encoder_remote_closed_abort(Config) ->
	doc("Endpoints must not close the encoder stream. (RFC9204 4.2)"),
	#{conn := Conn} = rfc9114_SUITE:do_connect(Config, #{peer_unidi_stream_count => 3}),
	{ok, #{encoder := StreamRef}} = do_wait_unidi_streams(Conn, #{}),
	%% Close the control stream.
	quicer:async_shutdown_stream(StreamRef, ?QUIC_STREAM_SHUTDOWN_FLAG_ABORT, 0),
	%% The connection should have been closed.
	#{reason := h3_closed_critical_stream} = rfc9114_SUITE:do_wait_connection_closed(Conn),
	ok.

do_wait_unidi_streams(_, Acc=#{decoder := _, encoder := _}) ->
	{ok, Acc};
do_wait_unidi_streams(Conn, Acc) ->
	receive
		{quic, new_stream, StreamRef, #{flags := Flags}} ->
			ok = quicer:setopt(StreamRef, active, true),
			true = quicer:is_unidirectional(Flags),
			receive {quic, <<TypeValue>>, StreamRef, _} ->
				Type = case TypeValue of
					2 -> encoder;
					3 -> decoder
				end,
				do_wait_unidi_streams(Conn, Acc#{Type => StreamRef})
			after 5000 ->
				{error, timeout}
			end
	after 5000 ->
		{error, timeout}
	end.

%% An endpoint MAY avoid creating an encoder stream if it will
%% not be used (for example, if its encoder does not wish to
%% use the dynamic table or if the maximum size of the dynamic
%% table permitted by the peer is zero).

%% An endpoint MAY avoid creating a decoder stream if its
%% decoder sets the maximum capacity of the dynamic table to zero.

%% An endpoint MUST allow its peer to create an encoder stream
%% and a decoder stream even if the connection's settings
%% prevent their use.

%% Encoder instructions.

%% 4.3.1
%% The new capacity MUST be lower than or equal to the limit
%% described in Section 3.2.3. In HTTP/3, this limit is the
%% value of the SETTINGS_QPACK_MAX_TABLE_CAPACITY parameter
%% (Section 5) received from the decoder. The decoder MUST
%% treat a new dynamic table capacity value that exceeds this
%% limit as a connection error of type QPACK_ENCODER_STREAM_ERROR.

%% Reducing the dynamic table capacity can cause entries to be
%% evicted; see Section 3.2.2. This MUST NOT cause the eviction
%% of entries that are not evictable; see Section 2.1.1.

%% Decoder instructions.

%% 4.4.1
%% If an encoder receives a Section Acknowledgment instruction
%% referring to a stream on which every encoded field section
%% with a non-zero Required Insert Count has already been
%% acknowledged, this MUST be treated as a connection error
%% of type QPACK_DECODER_STREAM_ERROR.

%% 4.4.3
%% An encoder that receives an Increment field equal to zero,
%% or one that increases the Known Received Count beyond what
%% the encoder has sent, MUST treat this as a connection error
%% of type QPACK_DECODER_STREAM_ERROR.

%% Field line representation.

%% 4.5.1.1
%% If the decoder encounters a value of EncodedInsertCount that
%% could not have been produced by a conformant encoder, it MUST
%% treat this as a connection error of type QPACK_DECOMPRESSION_FAILED.

%% 4.5.1.2
%% The value of Base MUST NOT be negative. Though the protocol
%% might operate correctly with a negative Base using post-Base
%% indexing, it is unnecessary and inefficient. An endpoint MUST
%% treat a field block with a Sign bit of 1 as invalid if the
%% value of Required Insert Count is less than or equal to the
%% value of Delta Base.

%% 4.5.4
%% When the 'N' bit is set, the encoded field line MUST always
%% be encoded with a literal representation. In particular,
%% when a peer sends a field line that it received represented
%% as a literal field line with the 'N' bit set, it MUST use a
%% literal representation to forward this field line. This bit
%% is intended for protecting field values that are not to be
%% put at risk by compressing them; see Section 7.1 for more details.

%% Configuration.

%% 5
%% SETTINGS_QPACK_MAX_TABLE_CAPACITY
%% SETTINGS_QPACK_BLOCKED_STREAMS

%% Security considerations.

%% 7.1.2
%% (security if used as a proxy merging many connections into one)
%% An ideal solution segregates access to the dynamic table
%% based on the entity that is constructing the message.
%% Field values that are added to the table are attributed
%% to an entity, and only the entity that created a particular
%% value can extract that value.

%% 7.1.3
%% An intermediary MUST NOT re-encode a value that uses a
%% literal representation with the 'N' bit set with another
%% representation that would index it. If QPACK is used for
%% re-encoding, a literal representation with the 'N' bit set
%% MUST be used. If HPACK is used for re-encoding, the
%% never-indexed literal representation (see Section 6.2.3
%% of [RFC7541]) MUST be used.

%% 7.4
%% An implementation has to set a limit for the values it
%% accepts for integers, as well as for the encoded length;
%% see Section 4.1.1. In the same way, it has to set a limit
%% to the length it accepts for string literals; see Section 4.1.2.
%% These limits SHOULD be large enough to process the largest
%% individual field the HTTP implementation can be configured
%% to accept.

%% If an implementation encounters a value larger than it is
%% able to decode, this MUST be treated as a stream error of
%% type QPACK_DECOMPRESSION_FAILED if on a request stream or
%% a connection error of the appropriate type if on the
%% encoder or decoder stream.

-endif.