17
17
% % Parsing.
18
18
-export ([parse /1 ]).
19
19
-export ([parse_unidi_stream_header /1 ]).
20
+ -export ([parse_datagram /1 ]).
20
21
-export ([code_to_error /1 ]).
22
+ -export ([parse_int /1 ]).
21
23
22
24
% % Building.
23
25
-export ([data /1 ]).
24
26
-export ([headers /1 ]).
25
27
-export ([settings /1 ]).
28
+ -export ([webtransport_stream_header /2 ]).
29
+ -export ([datagram /2 ]).
26
30
-export ([error_to_code /1 ]).
27
31
-export ([encode_int /1 ]).
28
32
32
36
-type push_id () :: non_neg_integer ().
33
37
-export_type ([push_id / 0 ]).
34
38
39
+ -type h3_non_neg_integer () :: 0 ..16#3fffffffffffffff .
40
+
35
41
-type settings () :: #{
36
- qpack_max_table_capacity => 0 ..16#3fffffffffffffff ,
37
- max_field_section_size => 0 ..16#3fffffffffffffff ,
38
- qpack_blocked_streams => 0 ..16#3fffffffffffffff ,
39
- enable_connect_protocol => boolean ()
42
+ qpack_max_table_capacity => h3_non_neg_integer (),
43
+ max_field_section_size => h3_non_neg_integer (),
44
+ qpack_blocked_streams => h3_non_neg_integer (),
45
+ enable_connect_protocol => boolean (),
46
+ % % Extensions.
47
+ h3_datagram => boolean (),
48
+ webtransport_max_sessions => h3_non_neg_integer (),
49
+ webtransport_initial_max_streams_uni => h3_non_neg_integer (),
50
+ webtransport_initial_max_streams_bidi => h3_non_neg_integer (),
51
+ webtransport_initial_max_data => h3_non_neg_integer ()
40
52
}.
41
53
-export_type ([settings / 0 ]).
42
54
55
+ -type wt_app_error_code () :: 0 ..16#ffffffff .
56
+ -export_type ([wt_app_error_code / 0 ]).
57
+
43
58
-type error () :: h3_no_error
44
59
| h3_general_protocol_error
45
60
| h3_internal_error
56
71
| h3_request_incomplete
57
72
| h3_message_error
58
73
| h3_connect_error
59
- | h3_version_fallback .
74
+ | h3_version_fallback
75
+ % % Extensions.
76
+ | h3_datagram_error
77
+ | webtransport_buffered_stream_rejected
78
+ | webtransport_session_gone
79
+ | {webtransport_application_error , wt_app_error_code ()}.
60
80
-export_type ([error / 0 ]).
61
81
62
82
-type frame () :: {data , binary ()}
72
92
73
93
-spec parse (binary ())
74
94
-> {ok , frame (), binary ()}
95
+ | {webtransport_stream_header , stream_id (), binary ()}
75
96
| {more , {data , binary ()} | ignore , non_neg_integer ()}
76
97
| {ignore , binary ()}
77
98
| {connection_error , h3_frame_error | h3_frame_unexpected | h3_settings_error , atom ()}
@@ -191,6 +212,19 @@ parse(<<13, _/bits>>) ->
191
212
{connection_error , h3_frame_error ,
192
213
'MAX_PUSH_ID frames payload MUST be 1, 2, 4 or 8 bytes wide. (RFC9114 7.1, RFC9114 7.2.6)' };
193
214
% %
215
+ % % WebTransport stream header.
216
+ % %
217
+ parse (<<1 :2 , 16#41 :14 , 0 :2 , SessionID :6 , Rest /bits >>) ->
218
+ {webtransport_stream_header , SessionID , Rest };
219
+ parse (<<1 :2 , 16#41 :14 , 1 :2 , SessionID :14 , Rest /bits >>) ->
220
+ {webtransport_stream_header , SessionID , Rest };
221
+ parse (<<1 :2 , 16#41 :14 , 2 :2 , SessionID :30 , Rest /bits >>) ->
222
+ {webtransport_stream_header , SessionID , Rest };
223
+ parse (<<1 :2 , 16#41 :14 , 3 :2 , SessionID :62 , Rest /bits >>) ->
224
+ {webtransport_stream_header , SessionID , Rest };
225
+ parse (<<16#41 , _ /bits >>) ->
226
+ more ;
227
+ % %
194
228
% % HTTP/2 frame types must be rejected.
195
229
% %
196
230
parse (<<2 , _ /bits >>) ->
@@ -294,6 +328,26 @@ parse_settings_id_val(Rest, Len, Settings, Identifier, Value) ->
294
328
8 ->
295
329
{connection_error , h3_settings_error ,
296
330
'The SETTINGS_ENABLE_CONNECT_PROTOCOL value MUST be 0 or 1. (RFC9220 3, RFC8441 3)' };
331
+ % % SETTINGS_H3_DATAGRAM (RFC9297).
332
+ 16#33 when Value =:= 0 ->
333
+ parse_settings_key_val (Rest , Len , Settings , h3_datagram , false );
334
+ 16#33 when Value =:= 1 ->
335
+ parse_settings_key_val (Rest , Len , Settings , h3_datagram , true );
336
+ 16#33 ->
337
+ {connection_error , h3_settings_error ,
338
+ 'The SETTINGS_H3_DATAGRAM value MUST be 0 or 1. (RFC9297 2.1.1)' };
339
+ % % SETTINGS_WEBTRANSPORT_MAX_SESSIONS (draft-ietf-webtrans-http3).
340
+ 16#c671706a ->
341
+ parse_settings_key_val (Rest , Len , Settings , webtransport_max_sessions , Value );
342
+ % % SETTINGS_WEBTRANSPORT_INITIAL_MAX_STREAMS_UNI (draft-ietf-webtrans-http3).
343
+ 16#2b64 ->
344
+ parse_settings_key_val (Rest , Len , Settings , webtransport_initial_max_streams_uni , Value );
345
+ % % SETTINGS_WEBTRANSPORT_INITIAL_MAX_STREAMS_BIDI (draft-ietf-webtrans-http3).
346
+ 16#2b65 ->
347
+ parse_settings_key_val (Rest , Len , Settings , webtransport_initial_max_streams_bidi , Value );
348
+ % % SETTINGS_WEBTRANSPORT_INITIAL_MAX_DATA (draft-ietf-webtrans-http3).
349
+ 16#2b61 ->
350
+ parse_settings_key_val (Rest , Len , Settings , webtransport_initial_max_data , Value );
297
351
_ when Identifier < 6 ->
298
352
{connection_error , h3_settings_error ,
299
353
'HTTP/2 setting not defined for HTTP/3 must be rejected. (RFC9114 7.2.4.1)' };
@@ -335,8 +389,9 @@ parse_ignore(Data, Len) ->
335
389
end .
336
390
337
391
-spec parse_unidi_stream_header (binary ())
338
- -> {ok , control | push | encoder | decoder , binary ()}
339
- | {undefined , binary ()}.
392
+ -> {ok , control | push | encoder | decoder | {webtransport , stream_id ()}, binary ()}
393
+ | {undefined , binary ()}
394
+ | more .
340
395
341
396
parse_unidi_stream_header (<<0 , Rest /bits >>) ->
342
397
{ok , control , Rest };
@@ -346,6 +401,18 @@ parse_unidi_stream_header(<<2, Rest/bits>>) ->
346
401
{ok , encoder , Rest };
347
402
parse_unidi_stream_header (<<3 , Rest /bits >>) ->
348
403
{ok , decoder , Rest };
404
+ % % WebTransport unidi streams.
405
+ parse_unidi_stream_header (<<1 :2 , 16#54 :14 , 0 :2 , SessionID :6 , Rest /bits >>) ->
406
+ {ok , {webtransport , SessionID }, Rest };
407
+ parse_unidi_stream_header (<<1 :2 , 16#54 :14 , 1 :2 , SessionID :14 , Rest /bits >>) ->
408
+ {ok , {webtransport , SessionID }, Rest };
409
+ parse_unidi_stream_header (<<1 :2 , 16#54 :14 , 2 :2 , SessionID :30 , Rest /bits >>) ->
410
+ {ok , {webtransport , SessionID }, Rest };
411
+ parse_unidi_stream_header (<<1 :2 , 16#54 :14 , 3 :2 , SessionID :62 , Rest /bits >>) ->
412
+ {ok , {webtransport , SessionID }, Rest };
413
+ parse_unidi_stream_header (<<1 :2 , 16#54 :14 , _ /bits >>) ->
414
+ more ;
415
+ % % Unknown unidi streams.
349
416
parse_unidi_stream_header (<<0 :2 , _ :6 , Rest /bits >>) ->
350
417
{undefined , Rest };
351
418
parse_unidi_stream_header (<<1 :2 , _ :14 , Rest /bits >>) ->
@@ -355,6 +422,13 @@ parse_unidi_stream_header(<<2:2, _:30, Rest/bits>>) ->
355
422
parse_unidi_stream_header (<<3 :2 , _ :62 , Rest /bits >>) ->
356
423
{undefined , Rest }.
357
424
425
+ -spec parse_datagram (binary ()) -> {stream_id (), binary ()}.
426
+
427
+ parse_datagram (Data ) ->
428
+ {QuarterID , Rest } = parse_int (Data ),
429
+ SessionID = QuarterID * 4 ,
430
+ {SessionID , Rest }.
431
+
358
432
-spec code_to_error (non_neg_integer ()) -> error ().
359
433
360
434
code_to_error (16#0100 ) -> h3_no_error ;
@@ -374,10 +448,36 @@ code_to_error(16#010d) -> h3_request_incomplete;
374
448
code_to_error (16#010e ) -> h3_message_error ;
375
449
code_to_error (16#010f ) -> h3_connect_error ;
376
450
code_to_error (16#0110 ) -> h3_version_fallback ;
451
+ % % Extensions.
452
+ code_to_error (16#33 ) -> h3_datagram_error ;
453
+ code_to_error (16#3994bd84 ) -> webtransport_buffered_stream_rejected ;
454
+ code_to_error (16#170d7b68 ) -> webtransport_session_gone ;
455
+ code_to_error (Code ) when Code >= 16#52e4a40fa8db , Code =< 16#52e5ac983162 ->
456
+ case (Code - 16#21 ) rem 16#1f of
457
+ 0 -> h3_no_error ;
458
+ _ ->
459
+ % % @todo We need tests for this.
460
+ Shifted = Code - 16#52e4a40fa8db ,
461
+ {webtransport_application_error ,
462
+ Shifted - Shifted div 16#1f }
463
+ end ;
377
464
% % Unknown/reserved error codes must be treated
378
465
% % as equivalent to H3_NO_ERROR.
379
466
code_to_error (_ ) -> h3_no_error .
380
467
468
+ -spec parse_int (binary ()) -> {non_neg_integer (), binary ()} | more .
469
+
470
+ parse_int (<<0 :2 , Int :6 , Rest /bits >>) ->
471
+ {Int , Rest };
472
+ parse_int (<<1 :2 , Int :14 , Rest /bits >>) ->
473
+ {Int , Rest };
474
+ parse_int (<<2 :2 , Int :30 , Rest /bits >>) ->
475
+ {Int , Rest };
476
+ parse_int (<<3 :2 , Int :62 , Rest /bits >>) ->
477
+ {Int , Rest };
478
+ parse_int (_ ) ->
479
+ more .
480
+
381
481
% % Building.
382
482
383
483
-spec data (iodata ()) -> iolist ().
@@ -414,12 +514,45 @@ settings_payload(Settings) ->
414
514
qpack_blocked_streams -> [encode_int (1 ), encode_int (Value )];
415
515
% % SETTINGS_ENABLE_CONNECT_PROTOCOL (RFC9220).
416
516
enable_connect_protocol when Value -> [encode_int (8 ), encode_int (1 )];
417
- enable_connect_protocol -> [encode_int (8 ), encode_int (0 )]
517
+ enable_connect_protocol -> [encode_int (8 ), encode_int (0 )];
518
+ % % SETTINGS_H3_DATAGRAM (RFC9297).
519
+ h3_datagram when Value -> [encode_int (16#33 ), encode_int (1 )];
520
+ h3_datagram -> [encode_int (16#33 ), encode_int (0 )];
521
+ % % SETTINGS_ENABLE_WEBTRANSPORT (draft-ietf-webtrans-http3-02, for compatibility).
522
+ enable_webtransport when Value -> [encode_int (16#2b603742 ), encode_int (1 )];
523
+ enable_webtransport -> [encode_int (16#2b603742 ), encode_int (0 )];
524
+ % % SETTINGS_WEBTRANSPORT_MAX_SESSIONS (draft-ietf-webtrans-http3).
525
+ webtransport_max_sessions when Value =:= 0 -> <<>>;
526
+ webtransport_max_sessions -> [encode_int (16#c671706a ), encode_int (Value )];
527
+ % % SETTINGS_WEBTRANSPORT_INITIAL_MAX_STREAMS_UNI (draft-ietf-webtrans-http3).
528
+ webtransport_initial_max_streams_uni when Value =:= 0 -> <<>>;
529
+ webtransport_initial_max_streams_uni -> [encode_int (16#2b64 ), encode_int (Value )];
530
+ % % SETTINGS_WEBTRANSPORT_INITIAL_MAX_STREAMS_BIDI (draft-ietf-webtrans-http3).
531
+ webtransport_initial_max_streams_bidi when Value =:= 0 -> <<>>;
532
+ webtransport_initial_max_streams_bidi -> [encode_int (16#2b65 ), encode_int (Value )];
533
+ % % SETTINGS_WEBTRANSPORT_INITIAL_MAX_DATA (draft-ietf-webtrans-http3).
534
+ webtransport_initial_max_data when Value =:= 0 -> <<>>;
535
+ webtransport_initial_max_data -> [encode_int (16#2b61 ), encode_int (Value )]
418
536
end || {Key , Value } <- maps :to_list (Settings )],
419
537
% % Include one reserved identifier in addition.
420
538
ReservedType = 16#1f * (rand :uniform (148764065110560900 ) - 1 ) + 16#21 ,
421
539
[encode_int (ReservedType ), encode_int (rand :uniform (15384 ) - 1 )|Payload ].
422
540
541
+ -spec webtransport_stream_header (stream_id (), unidi | bidi ) -> iolist ().
542
+
543
+ webtransport_stream_header (SessionID , StreamType ) ->
544
+ Signal = case StreamType of
545
+ unidi -> 16#54 ;
546
+ bidi -> 16#41
547
+ end ,
548
+ [encode_int (Signal ), encode_int (SessionID )].
549
+
550
+ -spec datagram (stream_id (), iodata ()) -> iolist ().
551
+
552
+ datagram (SessionID , Data ) ->
553
+ QuarterID = SessionID div 4 ,
554
+ [encode_int (QuarterID ), Data ].
555
+
423
556
-spec error_to_code (error ()) -> non_neg_integer ().
424
557
425
558
error_to_code (h3_no_error ) ->
@@ -444,9 +577,15 @@ error_to_code(h3_request_cancelled) -> 16#010c;
444
577
error_to_code (h3_request_incomplete ) -> 16#010d ;
445
578
error_to_code (h3_message_error ) -> 16#010e ;
446
579
error_to_code (h3_connect_error ) -> 16#010f ;
447
- error_to_code (h3_version_fallback ) -> 16#0110 .
448
-
449
- -spec encode_int (0 ..16#3fffffffffffffff ) -> binary ().
580
+ error_to_code (h3_version_fallback ) -> 16#0110 ;
581
+ % % Extensions.
582
+ error_to_code (h3_datagram_error ) -> 16#33 ;
583
+ error_to_code (webtransport_buffered_stream_rejected ) -> 16#3994bd84 ;
584
+ error_to_code (webtransport_session_gone ) -> 16#170d7b68 ;
585
+ error_to_code ({webtransport_application_error , AppErrorCode }) ->
586
+ 16#52e4a40fa8db + AppErrorCode + AppErrorCode div 16#1e .
587
+
588
+ -spec encode_int (h3_non_neg_integer ()) -> binary ().
450
589
451
590
encode_int (I ) when I < 64 ->
452
591
<<0 :2 , I :6 >>;
0 commit comments