@@ -12,6 +12,7 @@ use std::{
12
12
} ;
13
13
14
14
use bytes:: { Buf , BufMut } ;
15
+ use rand:: { Rng as _, RngCore } ;
15
16
use thiserror:: Error ;
16
17
17
18
use crate :: {
@@ -99,6 +100,10 @@ macro_rules! make_struct {
99
100
pub ( crate ) stateless_reset_token: Option <ResetToken >,
100
101
/// The server's preferred address for communication after handshake completion
101
102
pub ( crate ) preferred_address: Option <PreferredAddress >,
103
+ /// The randomly generated reserved transport parameter to sustain future extensibility
104
+ /// of transport parameter extensions.
105
+ /// When present, it is included during serialization but ignored during deserialization.
106
+ pub ( crate ) grease_transport_parameter: Option <ReservedTransportParameter >,
102
107
}
103
108
104
109
// We deliberately don't implement the `Default` trait, since that would be public, and
@@ -120,6 +125,7 @@ macro_rules! make_struct {
120
125
retry_src_cid: None ,
121
126
stateless_reset_token: None ,
122
127
preferred_address: None ,
128
+ grease_transport_parameter: None ,
123
129
}
124
130
}
125
131
}
@@ -135,6 +141,7 @@ impl TransportParameters {
135
141
cid_gen : & dyn ConnectionIdGenerator ,
136
142
initial_src_cid : ConnectionId ,
137
143
server_config : Option < & ServerConfig > ,
144
+ rng : & mut impl RngCore ,
138
145
) -> Self {
139
146
Self {
140
147
initial_src_cid : Some ( initial_src_cid) ,
@@ -160,6 +167,7 @@ impl TransportParameters {
160
167
min_ack_delay : Some (
161
168
VarInt :: from_u64 ( u64:: try_from ( TIMER_GRANULARITY . as_micros ( ) ) . unwrap ( ) ) . unwrap ( ) ,
162
169
) ,
170
+ grease_transport_parameter : Some ( ReservedTransportParameter :: random ( rng) ) ,
163
171
..Self :: default ( )
164
172
}
165
173
}
@@ -300,9 +308,9 @@ impl TransportParameters {
300
308
}
301
309
apply_params ! ( write_params) ;
302
310
303
- // Add a reserved parameter to keep people on their toes
304
- w . write_var ( 31 * 5 + 27 ) ;
305
- w . write_var ( 0 ) ;
311
+ if let Some ( param ) = self . grease_transport_parameter {
312
+ param . write ( w ) ;
313
+ }
306
314
307
315
if let Some ( ref x) = self . stateless_reset_token {
308
316
w. write_var ( 0x02 ) ;
@@ -467,6 +475,81 @@ impl TransportParameters {
467
475
}
468
476
}
469
477
478
+ /// A reserved transport parameter.
479
+ ///
480
+ /// It has an identifier of the form 31 * N + 27 for the integer value of N.
481
+ /// Such identifiers are reserved to exercise the requirement that unknown transport parameters be ignored.
482
+ /// The reserved transport parameter has no semantics and can carry arbitrary values.
483
+ /// It may be included in transport parameters sent to the peer, and should be ignored when received.
484
+ ///
485
+ /// See spec: <https://www.rfc-editor.org/rfc/rfc9000.html#section-18.1>
486
+ #[ derive( Debug , Copy , Clone , Eq , PartialEq ) ]
487
+ pub ( crate ) struct ReservedTransportParameter {
488
+ /// The reserved identifier of the transport parameter
489
+ id : VarInt ,
490
+
491
+ /// Buffer to store the parameter payload
492
+ payload : [ u8 ; Self :: MAX_PAYLOAD_LEN ] ,
493
+
494
+ /// The number of bytes to include in the wire format from the `payload` buffer
495
+ payload_len : usize ,
496
+ }
497
+
498
+ impl ReservedTransportParameter {
499
+ /// Generates a transport parameter with a random payload and a reserved ID.
500
+ ///
501
+ /// The implementation is inspired by quic-go and quiche:
502
+ /// 1. <https://github.com/quic-go/quic-go/blob/3e0a67b2476e1819752f04d75968de042b197b56/internal/wire/transport_parameters.go#L338-L344>
503
+ /// 2. <https://github.com/google/quiche/blob/cb1090b20c40e2f0815107857324e99acf6ec567/quiche/quic/core/crypto/transport_parameters.cc#L843-L860>
504
+ fn random ( rng : & mut impl RngCore ) -> Self {
505
+ let id = Self :: generate_reserved_id ( rng) ;
506
+
507
+ let payload_len = rng. gen_range ( 0 ..Self :: MAX_PAYLOAD_LEN ) ;
508
+
509
+ let payload = {
510
+ let mut slice = [ 0u8 ; Self :: MAX_PAYLOAD_LEN ] ;
511
+ rng. fill_bytes ( & mut slice[ ..payload_len] ) ;
512
+ slice
513
+ } ;
514
+
515
+ Self {
516
+ id,
517
+ payload,
518
+ payload_len,
519
+ }
520
+ }
521
+
522
+ fn write ( & self , w : & mut impl BufMut ) {
523
+ w. write_var ( self . id . 0 ) ;
524
+ w. write_var ( self . payload_len as u64 ) ;
525
+ w. put_slice ( & self . payload [ ..self . payload_len ] ) ;
526
+ }
527
+
528
+ /// Generates a random reserved identifier of the form `31 * N + 27`, as required by RFC 9000.
529
+ /// Reserved transport parameter identifiers are used to test compliance with the requirement
530
+ /// that unknown transport parameters must be ignored by peers.
531
+ /// See: <https://www.rfc-editor.org/rfc/rfc9000.html#section-18.1> and <https://www.rfc-editor.org/rfc/rfc9000.html#section-22.3>
532
+ fn generate_reserved_id ( rng : & mut impl RngCore ) -> VarInt {
533
+ let id = {
534
+ let rand = rng. gen_range ( 0u64 ..( 1 << 62 ) - 27 ) ;
535
+ let n = rand / 31 ;
536
+ 31 * n + 27
537
+ } ;
538
+ debug_assert ! (
539
+ id % 31 == 27 ,
540
+ "generated id does not have the form of 31 * N + 27"
541
+ ) ;
542
+ VarInt :: from_u64 ( id) . expect (
543
+ "generated id does fit into range of allowed transport parameter IDs: [0; 2^62)" ,
544
+ )
545
+ }
546
+
547
+ /// The maximum length of the payload to include as the parameter payload.
548
+ /// This value is not a specification-imposed limit but is chosen to match
549
+ /// the limit used by other implementations of QUIC, e.g., quic-go and quiche.
550
+ const MAX_PAYLOAD_LEN : usize = 16 ;
551
+ }
552
+
470
553
fn decode_cid ( len : usize , value : & mut Option < ConnectionId > , r : & mut impl Buf ) -> Result < ( ) , Error > {
471
554
if len > MAX_CID_SIZE || value. is_some ( ) || r. remaining ( ) < len {
472
555
return Err ( Error :: Malformed ) ;
@@ -507,6 +590,52 @@ mod test {
507
590
) ;
508
591
}
509
592
593
+ #[ test]
594
+ fn reserved_transport_parameter_generate_reserved_id ( ) {
595
+ use rand:: rngs:: mock:: StepRng ;
596
+ let mut rngs = [
597
+ StepRng :: new ( 0 , 1 ) ,
598
+ StepRng :: new ( 1 , 1 ) ,
599
+ StepRng :: new ( 27 , 1 ) ,
600
+ StepRng :: new ( 31 , 1 ) ,
601
+ StepRng :: new ( u32:: MAX as u64 , 1 ) ,
602
+ StepRng :: new ( u32:: MAX as u64 - 1 , 1 ) ,
603
+ StepRng :: new ( u32:: MAX as u64 + 1 , 1 ) ,
604
+ StepRng :: new ( u32:: MAX as u64 - 27 , 1 ) ,
605
+ StepRng :: new ( u32:: MAX as u64 + 27 , 1 ) ,
606
+ StepRng :: new ( u32:: MAX as u64 - 31 , 1 ) ,
607
+ StepRng :: new ( u32:: MAX as u64 + 31 , 1 ) ,
608
+ StepRng :: new ( u64:: MAX , 1 ) ,
609
+ StepRng :: new ( u64:: MAX - 1 , 1 ) ,
610
+ StepRng :: new ( u64:: MAX - 27 , 1 ) ,
611
+ StepRng :: new ( u64:: MAX - 31 , 1 ) ,
612
+ StepRng :: new ( 1 << 62 , 1 ) ,
613
+ StepRng :: new ( ( 1 << 62 ) - 1 , 1 ) ,
614
+ StepRng :: new ( ( 1 << 62 ) + 1 , 1 ) ,
615
+ StepRng :: new ( ( 1 << 62 ) - 27 , 1 ) ,
616
+ StepRng :: new ( ( 1 << 62 ) + 27 , 1 ) ,
617
+ StepRng :: new ( ( 1 << 62 ) - 31 , 1 ) ,
618
+ StepRng :: new ( ( 1 << 62 ) + 31 , 1 ) ,
619
+ ] ;
620
+ for rng in & mut rngs {
621
+ let id = ReservedTransportParameter :: generate_reserved_id ( rng) ;
622
+ assert ! ( id. 0 % 31 == 27 )
623
+ }
624
+ }
625
+
626
+ #[ test]
627
+ fn reserved_transport_parameter_ignored_when_read ( ) {
628
+ let mut buf = Vec :: new ( ) ;
629
+ let reserved_parameter = ReservedTransportParameter :: random ( & mut rand:: thread_rng ( ) ) ;
630
+ assert ! ( reserved_parameter. payload_len < ReservedTransportParameter :: MAX_PAYLOAD_LEN ) ;
631
+ assert ! ( reserved_parameter. id. 0 % 31 == 27 ) ;
632
+
633
+ reserved_parameter. write ( & mut buf) ;
634
+ assert ! ( !buf. is_empty( ) ) ;
635
+ let read_params = TransportParameters :: read ( Side :: Server , & mut buf. as_slice ( ) ) . unwrap ( ) ;
636
+ assert_eq ! ( read_params, TransportParameters :: default ( ) ) ;
637
+ }
638
+
510
639
#[ test]
511
640
fn read_semantic_validation ( ) {
512
641
#[ allow( clippy:: type_complexity) ]
0 commit comments