package tls import ( "crypto/rand" "encoding/binary" "math" "math/big" "github.com/quic-go/quic-go/quicvarint" ) const ( // RFC IDs max_idle_timeout uint64 = 0x1 max_udp_payload_size uint64 = 0x3 initial_max_data uint64 = 0x4 initial_max_stream_data_bidi_local uint64 = 0x5 initial_max_stream_data_bidi_remote uint64 = 0x6 initial_max_stream_data_uni uint64 = 0x7 initial_max_streams_bidi uint64 = 0x8 initial_max_streams_uni uint64 = 0x9 max_ack_delay uint64 = 0xb disable_active_migration uint64 = 0xc active_connection_id_limit uint64 = 0xe initial_source_connection_id uint64 = 0xf version_information uint64 = 0x11 // RFC 9368 padding uint64 = 0x15 max_datagram_frame_size uint64 = 0x20 // RFC 9221 grease_quic_bit uint64 = 0x2ab2 // Legacy IDs from draft version_information_legacy uint64 = 0xff73db // draft-ietf-quic-version-negotiation-13 and early ) type TransportParameters []TransportParameter func (tps TransportParameters) Marshal() []byte { var b []byte for _, tp := range tps { b = quicvarint.Append(b, tp.ID()) b = quicvarint.Append(b, uint64(len(tp.Value()))) b = append(b, tp.Value()...) } return b } // TransportParameter represents a QUIC transport parameter. // // Caller will write the following to the wire: // // var b []byte // b = quicvarint.Append(b, ID()) // b = quicvarint.Append(b, len(Value())) // b = append(b, Value()) // // Therefore Value() should return the exact bytes to be written to the wire AFTER the length field, // i.e., the bytes MAY be a Variable Length Integer per RFC depending on the type of the transport // parameter, but MUST NOT including the length field unless the parameter is defined so. type TransportParameter interface { ID() uint64 Value() []byte } type GREASETransportParameter struct { IdOverride uint64 // if set to a valid GREASE ID, use this instead of randomly generated one. Length uint16 // if len(ValueOverride) == 0, will generate random data of this size. ValueOverride []byte // if len(ValueOverride) > 0, use this instead of random bytes. } const ( GREASE_MAX_MULTIPLIER = (0x3FFFFFFFFFFFFFFF - 27) / 31 ) // IsGREASEID returns true if id is a valid GREASE ID for // transport parameters. func (GREASETransportParameter) IsGREASEID(id uint64) bool { return id >= 27 && (id-27)%31 == 0 } // GetGREASEID returns a random valid GREASE ID for transport parameters. func (GREASETransportParameter) GetGREASEID() uint64 { max := big.NewInt(GREASE_MAX_MULTIPLIER) randMultiply, err := rand.Int(rand.Reader, max) if err != nil { return 27 } return 27 + randMultiply.Uint64()*31 } func (g *GREASETransportParameter) ID() uint64 { if !g.IsGREASEID(g.IdOverride) { g.IdOverride = g.GetGREASEID() } return g.IdOverride } func (g *GREASETransportParameter) Value() []byte { if len(g.ValueOverride) == 0 { g.ValueOverride = make([]byte, g.Length) rand.Read(g.ValueOverride) } return g.ValueOverride } type MaxIdleTimeout uint64 // in milliseconds func (MaxIdleTimeout) ID() uint64 { return max_idle_timeout } func (m MaxIdleTimeout) Value() []byte { return quicvarint.Append([]byte{}, uint64(m)) } type MaxUDPPayloadSize uint64 func (MaxUDPPayloadSize) ID() uint64 { return max_udp_payload_size } func (m MaxUDPPayloadSize) Value() []byte { return quicvarint.Append([]byte{}, uint64(m)) } type InitialMaxData uint64 func (InitialMaxData) ID() uint64 { return initial_max_data } func (i InitialMaxData) Value() []byte { return quicvarint.Append([]byte{}, uint64(i)) } type InitialMaxStreamDataBidiLocal uint64 func (InitialMaxStreamDataBidiLocal) ID() uint64 { return initial_max_stream_data_bidi_local } func (i InitialMaxStreamDataBidiLocal) Value() []byte { return quicvarint.Append([]byte{}, uint64(i)) } type InitialMaxStreamDataBidiRemote uint64 func (InitialMaxStreamDataBidiRemote) ID() uint64 { return initial_max_stream_data_bidi_remote } func (i InitialMaxStreamDataBidiRemote) Value() []byte { return quicvarint.Append([]byte{}, uint64(i)) } type InitialMaxStreamDataUni uint64 func (InitialMaxStreamDataUni) ID() uint64 { return initial_max_stream_data_uni } func (i InitialMaxStreamDataUni) Value() []byte { return quicvarint.Append([]byte{}, uint64(i)) } type InitialMaxStreamsBidi uint64 func (InitialMaxStreamsBidi) ID() uint64 { return initial_max_streams_bidi } func (i InitialMaxStreamsBidi) Value() []byte { return quicvarint.Append([]byte{}, uint64(i)) } type InitialMaxStreamsUni uint64 func (InitialMaxStreamsUni) ID() uint64 { return initial_max_streams_uni } func (i InitialMaxStreamsUni) Value() []byte { return quicvarint.Append([]byte{}, uint64(i)) } type MaxAckDelay uint64 func (MaxAckDelay) ID() uint64 { return max_ack_delay } func (m MaxAckDelay) Value() []byte { return quicvarint.Append([]byte{}, uint64(m)) } type DisableActiveMigration struct{} func (*DisableActiveMigration) ID() uint64 { return disable_active_migration } // Its Value MUST ALWAYS be empty. func (*DisableActiveMigration) Value() []byte { return []byte{} } type ActiveConnectionIDLimit uint64 func (ActiveConnectionIDLimit) ID() uint64 { return active_connection_id_limit } func (a ActiveConnectionIDLimit) Value() []byte { return quicvarint.Append([]byte{}, uint64(a)) } type InitialSourceConnectionID []byte // if empty, will be set to the Connection ID used for the Initial packet. func (InitialSourceConnectionID) ID() uint64 { return initial_source_connection_id } func (i InitialSourceConnectionID) Value() []byte { return []byte(i) } type VersionInformation struct { ChoosenVersion uint32 AvailableVersions []uint32 // Also known as "Other Versions" in early drafts. LegacyID bool // If true, use the legacy-assigned ID (0xff73db) instead of the RFC-assigned one (0x11). } const ( VERSION_NEGOTIATION uint32 = 0x00000000 // rfc9000 VERSION_1 uint32 = 0x00000001 // rfc9000 VERSION_2 uint32 = 0x6b3343cf // rfc9369 VERSION_GREASE uint32 = 0x0a0a0a0a // -> 0x?a?a?a?a ) func (v *VersionInformation) ID() uint64 { if v.LegacyID { return version_information_legacy } return version_information } func (v *VersionInformation) Value() []byte { var b []byte b = binary.BigEndian.AppendUint32(b, v.ChoosenVersion) for _, version := range v.AvailableVersions { if version != VERSION_GREASE { b = binary.BigEndian.AppendUint32(b, version) } else { b = binary.BigEndian.AppendUint32(b, v.GetGREASEVersion()) } } return b } func (*VersionInformation) GetGREASEVersion() uint32 { // get a random uint32 max := big.NewInt(math.MaxUint32) randVal, err := rand.Int(rand.Reader, max) if err != nil { return VERSION_GREASE } return uint32(randVal.Uint64()&math.MaxUint32) | 0x0a0a0a0a // all GREASE versions are in 0x?a?a?a?a } type PaddingTransportParameter []byte func (PaddingTransportParameter) ID() uint64 { return padding } func (p PaddingTransportParameter) Value() []byte { return p } type MaxDatagramFrameSize uint64 func (MaxDatagramFrameSize) ID() uint64 { return max_datagram_frame_size } func (m MaxDatagramFrameSize) Value() []byte { return quicvarint.Append([]byte{}, uint64(m)) } type GREASEQUICBit struct{} func (*GREASEQUICBit) ID() uint64 { return grease_quic_bit } // Its Value MUST ALWAYS be empty. func (*GREASEQUICBit) Value() []byte { return []byte{} } type FakeQUICTransportParameter struct { Id uint64 Val []byte } func (f *FakeQUICTransportParameter) ID() uint64 { if f.Id == 0 { panic("it is not allowed to use a FakeQUICTransportParameter without setting the ID") } return f.Id } func (f *FakeQUICTransportParameter) Value() []byte { return f.Val }