@@ -211,6 +211,37 @@ pub:
211211// analogous note).
212212pub struct HandshakeDoneFrame {}
213213
214+ // NewConnectionIdFrame represents a NEW_CONNECTION_ID frame (type 0x18, RFC
215+ // 9000 §19.15): the sender offering an additional connection ID (and its
216+ // associated stateless-reset token, see generate_stateless_reset_token in
217+ // stateless_reset.v) for the peer to use. `retire_prior_to` is validated
218+ // against `sequence_number` (§19.15's "MUST be less than or equal to"
219+ // requirement) by both parse_frame and encode_new_connection_id_frame;
220+ // every other requirement in that section (the zero-length-DCID
221+ // prohibition, duplicate-sequence-number handling) needs connection state
222+ // parse_frame doesn't have, so -- same division as HandshakeDoneFrame's
223+ // role check above -- it is deferred to the caller.
224+ pub struct NewConnectionIdFrame {
225+ pub :
226+ sequence_number u64
227+ retire_prior_to u64
228+ connection_id []u8
229+ stateless_reset_token []u8 // always exactly 16 bytes (RFC 9000 §19.15)
230+ }
231+
232+ // RetireConnectionIdFrame represents a RETIRE_CONNECTION_ID frame (type
233+ // 0x19, RFC 9000 §19.16): the sender will no longer use the connection ID
234+ // with this sequence number. Every normative check in §19.16 (sequence
235+ // number not greater than any previously sent, not equal to the DCID of
236+ // the packet carrying this frame, not sent by an endpoint using a
237+ // zero-length CID) needs connection state parse_frame doesn't have, so --
238+ // same division as HandshakeDoneFrame's role check above -- it is deferred
239+ // to the caller.
240+ pub struct RetireConnectionIdFrame {
241+ pub :
242+ sequence_number u64
243+ }
244+
214245pub type QuicFrame = AckFrame
215246 | ConnectionCloseFrame
216247 | CryptoFrame
@@ -219,9 +250,11 @@ pub type QuicFrame = AckFrame
219250 | MaxDataFrame
220251 | MaxStreamDataFrame
221252 | MaxStreamsFrame
253+ | NewConnectionIdFrame
222254 | PaddingFrame
223255 | PingFrame
224256 | ResetStreamFrame
257+ | RetireConnectionIdFrame
225258 | StopSendingFrame
226259 | StreamDataBlockedFrame
227260 | StreamFrame
@@ -243,6 +276,8 @@ const frame_type_data_blocked = u64(0x14)
243276const frame_type_stream_data_blocked = u64 (0x15 )
244277const frame_type_streams_blocked_bidi = u64 (0x16 )
245278const frame_type_streams_blocked_uni = u64 (0x17 )
279+ const frame_type_new_connection_id = u64 (0x18 )
280+ const frame_type_retire_connection_id = u64 (0x19 )
246281const frame_type_connection_close_transport = u64 (0x1c )
247282const frame_type_connection_close_application = u64 (0x1d )
248283const frame_type_handshake_done = u64 (0x1e )
@@ -314,6 +349,14 @@ pub fn parse_frame(buf []u8) !(QuicFrame, int) {
314349 return parse_streams_blocked_frame (buf , typ_len , typ == frame_type_streams_blocked_uni)
315350 }
316351
352+ if typ == frame_type_new_connection_id {
353+ return parse_new_connection_id_frame (buf, typ_len)
354+ }
355+
356+ if typ == frame_type_retire_connection_id {
357+ return parse_retire_connection_id_frame (buf, typ_len)
358+ }
359+
317360 if typ == frame_type_connection_close_transport
318361 || typ == frame_type_connection_close_application {
319362 return parse_connection_close_frame (buf, typ_len,
@@ -598,6 +641,60 @@ fn parse_streams_blocked_frame(buf []u8, start int, is_uni bool) !(QuicFrame, in
598641 }), start + n1
599642}
600643
644+ fn parse_new_connection_id_frame (buf []u8 , start int ) ! (QuicFrame, int ) {
645+ mut offset := start
646+ sequence_number , n1 := decode_varint (buf[offset..])!
647+ offset + = n1
648+ retire_prior_to , n2 := decode_varint (buf[offset..])!
649+ offset + = n2
650+
651+ // RFC 9000 §19.15: "The value in the Retire Prior To field MUST be
652+ // less than or equal to the value in the Sequence Number field.
653+ // Receiving a value in the Retire Prior To field that is greater than
654+ // that in the Sequence Number field MUST be treated as a connection
655+ // error of type FRAME_ENCODING_ERROR."
656+ if retire_prior_to > sequence_number {
657+ return error ('quic: NEW_CONNECTION_ID frame: retire_prior_to ${retire_prior_to } exceeds sequence_number ${sequence_number } (RFC 9000 §19.15)' )
658+ }
659+
660+ if offset > = buf.len {
661+ return error ('quic: NEW_CONNECTION_ID frame: missing Length field' )
662+ }
663+ length := int (buf[offset])
664+ offset + = 1
665+ // RFC 9000 §19.15: "Values less than 1 and greater than 20 are invalid
666+ // and MUST be treated as a connection error of type
667+ // FRAME_ENCODING_ERROR." 20 is quic_v1_max_cid_len (header.v).
668+ if length < 1 || length > quic_v1_ max_cid_len {
669+ return error ('quic: NEW_CONNECTION_ID frame: connection ID length ${length } is outside the valid 1-${quic_v1_max_cid_len } range (RFC 9000 §19.15)' )
670+ }
671+ if offset + length > buf.len {
672+ return error ('quic: NEW_CONNECTION_ID frame: declares a ${length }-byte connection ID exceeding the remaining buffer' )
673+ }
674+ connection_id := buf[offset..offset + length].clone ()
675+ offset + = length
676+
677+ if offset + 16 > buf.len {
678+ return error ('quic: NEW_CONNECTION_ID frame: missing 16-byte stateless reset token' )
679+ }
680+ stateless_reset_token := buf[offset..offset + 16 ].clone ()
681+ offset + = 16
682+
683+ return QuicFrame (NewConnectionIdFrame{
684+ sequence_number: sequence_number
685+ retire_prior_to: retire_prior_to
686+ connection_id: connection_id
687+ stateless_reset_token: stateless_reset_token
688+ }), offset
689+ }
690+
691+ fn parse_retire_connection_id_frame (buf []u8 , start int ) ! (QuicFrame, int ) {
692+ sequence_number , n1 := decode_varint (buf[start..])!
693+ return QuicFrame (RetireConnectionIdFrame{
694+ sequence_number: sequence_number
695+ }), start + n1
696+ }
697+
601698fn parse_connection_close_frame (buf []u8 , start int , is_application_error bool ) ! (QuicFrame, int ) {
602699 mut offset := start
603700 error_code , n1 := decode_varint (buf[offset..])!
@@ -840,6 +937,38 @@ pub fn encode_streams_blocked_frame(direction StreamDirection, maximum_streams u
840937 return out
841938}
842939
940+ // encode_new_connection_id_frame serializes a NEW_CONNECTION_ID frame.
941+ // Mirrors parse_new_connection_id_frame's exact validation (RFC 9000
942+ // §19.15) so a caller cannot construct a frame this same module's own
943+ // parser would then reject.
944+ pub fn encode_new_connection_id_frame (sequence_number u64 , retire_prior_to u64 , connection_id []u8 , stateless_reset_token []u8 ) ! []u8 {
945+ if retire_prior_to > sequence_number {
946+ return error ('quic: encode_new_connection_id_frame: retire_prior_to ${retire_prior_to } exceeds sequence_number ${sequence_number } (RFC 9000 §19.15)' )
947+ }
948+ if connection_id.len < 1 || connection_id.len > quic_v1_ max_cid_len {
949+ return error ('quic: encode_new_connection_id_frame: connection ID length ${connection_id .len } is outside the valid 1-${quic_v1_max_cid_len } range (RFC 9000 §19.15)' )
950+ }
951+ if stateless_reset_token.len != 16 {
952+ return error ('quic: encode_new_connection_id_frame: stateless reset token must be exactly 16 bytes, got ${stateless_reset_token .len }' )
953+ }
954+ mut out := encode_varint (frame_type_new_connection_id)!
955+ out << encode_varint (sequence_number)!
956+ out << encode_varint (retire_prior_to)!
957+ out << u8 (connection_id.len)
958+ out << connection_id
959+ out << stateless_reset_token
960+ return out
961+ }
962+
963+ // encode_retire_connection_id_frame serializes a RETIRE_CONNECTION_ID
964+ // frame. See RetireConnectionIdFrame's doc comment for which §19.16
965+ // requirements are the caller's responsibility rather than this function's.
966+ pub fn encode_retire_connection_id_frame (sequence_number u64 ) ! []u8 {
967+ mut out := encode_varint (frame_type_retire_connection_id)!
968+ out << encode_varint (sequence_number)!
969+ return out
970+ }
971+
843972// encode_connection_close_frame serializes a CONNECTION_CLOSE frame.
844973// `frame_type` is ignored (the Frame Type field is OMITTED from the wire
845974// entirely, not encoded as a zero value) when `is_application_error` is
0 commit comments