Skip to content

Commit

Permalink
quic, internal/quic/quicwire: split wire encode/decode functions to n…
Browse files Browse the repository at this point in the history
…ew package

HTTP/3 also uses QUIC varints. Move the more general-purpose wire
encoding/decoding functions into a new internal/quic/quicwire package
so they can be shared.

For golang/go#70914

Change-Id: Id888baf131e90a12247e15a6f7bc7dc37c6dc572
Reviewed-on: https://go-review.googlesource.com/c/net/+/641496
Auto-Submit: Damien Neil <[email protected]>
Reviewed-by: Jonathan Amsterdam <[email protected]>
LUCI-TryBot-Result: Go LUCI <[email protected]>
  • Loading branch information
neild authored and gopherbot committed Jan 9, 2025
1 parent 0a5dcdd commit 35e1007
Show file tree
Hide file tree
Showing 13 changed files with 272 additions and 250 deletions.
52 changes: 27 additions & 25 deletions quic/wire.go → internal/quic/quicwire/wire.go
Original file line number Diff line number Diff line change
Expand Up @@ -4,20 +4,22 @@

//go:build go1.21

package quic
// Package quicwire encodes and decode QUIC/HTTP3 wire encoding types,
// particularly variable-length integers.
package quicwire

import "encoding/binary"

const (
maxVarintSize = 8 // encoded size in bytes
maxVarint = (1 << 62) - 1
MaxVarintSize = 8 // encoded size in bytes
MaxVarint = (1 << 62) - 1
)

// consumeVarint parses a variable-length integer, reporting its length.
// ConsumeVarint parses a variable-length integer, reporting its length.
// It returns a negative length upon an error.
//
// https://www.rfc-editor.org/rfc/rfc9000.html#section-16
func consumeVarint(b []byte) (v uint64, n int) {
func ConsumeVarint(b []byte) (v uint64, n int) {
if len(b) < 1 {
return 0, -1
}
Expand Down Expand Up @@ -45,16 +47,16 @@ func consumeVarint(b []byte) (v uint64, n int) {
}

// consumeVarintInt64 parses a variable-length integer as an int64.
func consumeVarintInt64(b []byte) (v int64, n int) {
u, n := consumeVarint(b)
func ConsumeVarintInt64(b []byte) (v int64, n int) {
u, n := ConsumeVarint(b)
// QUIC varints are 62-bits large, so this conversion can never overflow.
return int64(u), n
}

// appendVarint appends a variable-length integer to b.
// AppendVarint appends a variable-length integer to b.
//
// https://www.rfc-editor.org/rfc/rfc9000.html#section-16
func appendVarint(b []byte, v uint64) []byte {
func AppendVarint(b []byte, v uint64) []byte {
switch {
case v <= 63:
return append(b, byte(v))
Expand All @@ -69,8 +71,8 @@ func appendVarint(b []byte, v uint64) []byte {
}
}

// sizeVarint returns the size of the variable-length integer encoding of f.
func sizeVarint(v uint64) int {
// SizeVarint returns the size of the variable-length integer encoding of f.
func SizeVarint(v uint64) int {
switch {
case v <= 63:
return 1
Expand All @@ -85,28 +87,28 @@ func sizeVarint(v uint64) int {
}
}

// consumeUint32 parses a 32-bit fixed-length, big-endian integer, reporting its length.
// ConsumeUint32 parses a 32-bit fixed-length, big-endian integer, reporting its length.
// It returns a negative length upon an error.
func consumeUint32(b []byte) (uint32, int) {
func ConsumeUint32(b []byte) (uint32, int) {
if len(b) < 4 {
return 0, -1
}
return binary.BigEndian.Uint32(b), 4
}

// consumeUint64 parses a 64-bit fixed-length, big-endian integer, reporting its length.
// ConsumeUint64 parses a 64-bit fixed-length, big-endian integer, reporting its length.
// It returns a negative length upon an error.
func consumeUint64(b []byte) (uint64, int) {
func ConsumeUint64(b []byte) (uint64, int) {
if len(b) < 8 {
return 0, -1
}
return binary.BigEndian.Uint64(b), 8
}

// consumeUint8Bytes parses a sequence of bytes prefixed with an 8-bit length,
// ConsumeUint8Bytes parses a sequence of bytes prefixed with an 8-bit length,
// reporting the total number of bytes consumed.
// It returns a negative length upon an error.
func consumeUint8Bytes(b []byte) ([]byte, int) {
func ConsumeUint8Bytes(b []byte) ([]byte, int) {
if len(b) < 1 {
return nil, -1
}
Expand All @@ -118,8 +120,8 @@ func consumeUint8Bytes(b []byte) ([]byte, int) {
return b[n:][:size], size + n
}

// appendUint8Bytes appends a sequence of bytes prefixed by an 8-bit length.
func appendUint8Bytes(b, v []byte) []byte {
// AppendUint8Bytes appends a sequence of bytes prefixed by an 8-bit length.
func AppendUint8Bytes(b, v []byte) []byte {
if len(v) > 0xff {
panic("uint8-prefixed bytes too large")
}
Expand All @@ -128,11 +130,11 @@ func appendUint8Bytes(b, v []byte) []byte {
return b
}

// consumeVarintBytes parses a sequence of bytes preceded by a variable-length integer length,
// ConsumeVarintBytes parses a sequence of bytes preceded by a variable-length integer length,
// reporting the total number of bytes consumed.
// It returns a negative length upon an error.
func consumeVarintBytes(b []byte) ([]byte, int) {
size, n := consumeVarint(b)
func ConsumeVarintBytes(b []byte) ([]byte, int) {
size, n := ConsumeVarint(b)
if n < 0 {
return nil, -1
}
Expand All @@ -142,9 +144,9 @@ func consumeVarintBytes(b []byte) ([]byte, int) {
return b[n:][:size], int(size) + n
}

// appendVarintBytes appends a sequence of bytes prefixed by a variable-length integer length.
func appendVarintBytes(b, v []byte) []byte {
b = appendVarint(b, uint64(len(v)))
// AppendVarintBytes appends a sequence of bytes prefixed by a variable-length integer length.
func AppendVarintBytes(b, v []byte) []byte {
b = AppendVarint(b, uint64(len(v)))
b = append(b, v...)
return b
}
70 changes: 35 additions & 35 deletions quic/wire_test.go → internal/quic/quicwire/wire_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -4,7 +4,7 @@

//go:build go1.21

package quic
package quicwire

import (
"bytes"
Expand Down Expand Up @@ -32,22 +32,22 @@ func TestConsumeVarint(t *testing.T) {
{[]byte{0x25}, 37, 1},
{[]byte{0x40, 0x25}, 37, 2},
} {
got, gotLen := consumeVarint(test.b)
got, gotLen := ConsumeVarint(test.b)
if got != test.want || gotLen != test.wantLen {
t.Errorf("consumeVarint(%x) = %v, %v; want %v, %v", test.b, got, gotLen, test.want, test.wantLen)
t.Errorf("ConsumeVarint(%x) = %v, %v; want %v, %v", test.b, got, gotLen, test.want, test.wantLen)
}
// Extra data in the buffer is ignored.
b := append(test.b, 0)
got, gotLen = consumeVarint(b)
got, gotLen = ConsumeVarint(b)
if got != test.want || gotLen != test.wantLen {
t.Errorf("consumeVarint(%x) = %v, %v; want %v, %v", b, got, gotLen, test.want, test.wantLen)
t.Errorf("ConsumeVarint(%x) = %v, %v; want %v, %v", b, got, gotLen, test.want, test.wantLen)
}
// Short buffer results in an error.
for i := 1; i <= len(test.b); i++ {
b = test.b[:len(test.b)-i]
got, gotLen = consumeVarint(b)
got, gotLen = ConsumeVarint(b)
if got != 0 || gotLen >= 0 {
t.Errorf("consumeVarint(%x) = %v, %v; want 0, -1", b, got, gotLen)
t.Errorf("ConsumeVarint(%x) = %v, %v; want 0, -1", b, got, gotLen)
}
}
}
Expand All @@ -69,11 +69,11 @@ func TestAppendVarint(t *testing.T) {
{15293, []byte{0x7b, 0xbd}},
{37, []byte{0x25}},
} {
got := appendVarint([]byte{}, test.v)
got := AppendVarint([]byte{}, test.v)
if !bytes.Equal(got, test.want) {
t.Errorf("AppendVarint(nil, %v) = %x, want %x", test.v, got, test.want)
}
if gotLen, wantLen := sizeVarint(test.v), len(got); gotLen != wantLen {
if gotLen, wantLen := SizeVarint(test.v), len(got); gotLen != wantLen {
t.Errorf("SizeVarint(%v) = %v, want %v", test.v, gotLen, wantLen)
}
}
Expand All @@ -88,8 +88,8 @@ func TestConsumeUint32(t *testing.T) {
{[]byte{0x01, 0x02, 0x03, 0x04}, 0x01020304, 4},
{[]byte{0x01, 0x02, 0x03}, 0, -1},
} {
if got, n := consumeUint32(test.b); got != test.want || n != test.wantLen {
t.Errorf("consumeUint32(%x) = %v, %v; want %v, %v", test.b, got, n, test.want, test.wantLen)
if got, n := ConsumeUint32(test.b); got != test.want || n != test.wantLen {
t.Errorf("ConsumeUint32(%x) = %v, %v; want %v, %v", test.b, got, n, test.want, test.wantLen)
}
}
}
Expand All @@ -103,8 +103,8 @@ func TestConsumeUint64(t *testing.T) {
{[]byte{0x01, 0x02, 0x03, 0x04, 0x05, 0x06, 0x07, 0x08}, 0x0102030405060708, 8},
{[]byte{0x01, 0x02, 0x03, 0x04, 0x05, 0x06, 0x07}, 0, -1},
} {
if got, n := consumeUint64(test.b); got != test.want || n != test.wantLen {
t.Errorf("consumeUint32(%x) = %v, %v; want %v, %v", test.b, got, n, test.want, test.wantLen)
if got, n := ConsumeUint64(test.b); got != test.want || n != test.wantLen {
t.Errorf("ConsumeUint32(%x) = %v, %v; want %v, %v", test.b, got, n, test.want, test.wantLen)
}
}
}
Expand All @@ -120,22 +120,22 @@ func TestConsumeVarintBytes(t *testing.T) {
{[]byte{0x04, 0x01, 0x02, 0x03, 0x04}, []byte{0x01, 0x02, 0x03, 0x04}, 5},
{[]byte{0x40, 0x04, 0x01, 0x02, 0x03, 0x04}, []byte{0x01, 0x02, 0x03, 0x04}, 6},
} {
got, gotLen := consumeVarintBytes(test.b)
got, gotLen := ConsumeVarintBytes(test.b)
if !bytes.Equal(got, test.want) || gotLen != test.wantLen {
t.Errorf("consumeVarintBytes(%x) = {%x}, %v; want {%x}, %v", test.b, got, gotLen, test.want, test.wantLen)
t.Errorf("ConsumeVarintBytes(%x) = {%x}, %v; want {%x}, %v", test.b, got, gotLen, test.want, test.wantLen)
}
// Extra data in the buffer is ignored.
b := append(test.b, 0)
got, gotLen = consumeVarintBytes(b)
got, gotLen = ConsumeVarintBytes(b)
if !bytes.Equal(got, test.want) || gotLen != test.wantLen {
t.Errorf("consumeVarintBytes(%x) = {%x}, %v; want {%x}, %v", b, got, gotLen, test.want, test.wantLen)
t.Errorf("ConsumeVarintBytes(%x) = {%x}, %v; want {%x}, %v", b, got, gotLen, test.want, test.wantLen)
}
// Short buffer results in an error.
for i := 1; i <= len(test.b); i++ {
b = test.b[:len(test.b)-i]
got, gotLen := consumeVarintBytes(b)
got, gotLen := ConsumeVarintBytes(b)
if len(got) > 0 || gotLen > 0 {
t.Errorf("consumeVarintBytes(%x) = {%x}, %v; want {}, -1", b, got, gotLen)
t.Errorf("ConsumeVarintBytes(%x) = {%x}, %v; want {}, -1", b, got, gotLen)
}
}

Expand All @@ -147,9 +147,9 @@ func TestConsumeVarintBytesErrors(t *testing.T) {
{0x01},
{0x40, 0x01},
} {
got, gotLen := consumeVarintBytes(b)
got, gotLen := ConsumeVarintBytes(b)
if len(got) > 0 || gotLen > 0 {
t.Errorf("consumeVarintBytes(%x) = {%x}, %v; want {}, -1", b, got, gotLen)
t.Errorf("ConsumeVarintBytes(%x) = {%x}, %v; want {}, -1", b, got, gotLen)
}
}
}
Expand All @@ -164,22 +164,22 @@ func TestConsumeUint8Bytes(t *testing.T) {
{[]byte{0x01, 0x00}, []byte{0x00}, 2},
{[]byte{0x04, 0x01, 0x02, 0x03, 0x04}, []byte{0x01, 0x02, 0x03, 0x04}, 5},
} {
got, gotLen := consumeUint8Bytes(test.b)
got, gotLen := ConsumeUint8Bytes(test.b)
if !bytes.Equal(got, test.want) || gotLen != test.wantLen {
t.Errorf("consumeUint8Bytes(%x) = {%x}, %v; want {%x}, %v", test.b, got, gotLen, test.want, test.wantLen)
t.Errorf("ConsumeUint8Bytes(%x) = {%x}, %v; want {%x}, %v", test.b, got, gotLen, test.want, test.wantLen)
}
// Extra data in the buffer is ignored.
b := append(test.b, 0)
got, gotLen = consumeUint8Bytes(b)
got, gotLen = ConsumeUint8Bytes(b)
if !bytes.Equal(got, test.want) || gotLen != test.wantLen {
t.Errorf("consumeUint8Bytes(%x) = {%x}, %v; want {%x}, %v", b, got, gotLen, test.want, test.wantLen)
t.Errorf("ConsumeUint8Bytes(%x) = {%x}, %v; want {%x}, %v", b, got, gotLen, test.want, test.wantLen)
}
// Short buffer results in an error.
for i := 1; i <= len(test.b); i++ {
b = test.b[:len(test.b)-i]
got, gotLen := consumeUint8Bytes(b)
got, gotLen := ConsumeUint8Bytes(b)
if len(got) > 0 || gotLen > 0 {
t.Errorf("consumeUint8Bytes(%x) = {%x}, %v; want {}, -1", b, got, gotLen)
t.Errorf("ConsumeUint8Bytes(%x) = {%x}, %v; want {}, -1", b, got, gotLen)
}
}

Expand All @@ -191,35 +191,35 @@ func TestConsumeUint8BytesErrors(t *testing.T) {
{0x01},
{0x04, 0x01, 0x02, 0x03},
} {
got, gotLen := consumeUint8Bytes(b)
got, gotLen := ConsumeUint8Bytes(b)
if len(got) > 0 || gotLen > 0 {
t.Errorf("consumeUint8Bytes(%x) = {%x}, %v; want {}, -1", b, got, gotLen)
t.Errorf("ConsumeUint8Bytes(%x) = {%x}, %v; want {}, -1", b, got, gotLen)
}
}
}

func TestAppendUint8Bytes(t *testing.T) {
var got []byte
got = appendUint8Bytes(got, []byte{})
got = appendUint8Bytes(got, []byte{0xaa, 0xbb})
got = AppendUint8Bytes(got, []byte{})
got = AppendUint8Bytes(got, []byte{0xaa, 0xbb})
want := []byte{
0x00,
0x02, 0xaa, 0xbb,
}
if !bytes.Equal(got, want) {
t.Errorf("appendUint8Bytes {}, {aabb} = {%x}; want {%x}", got, want)
t.Errorf("AppendUint8Bytes {}, {aabb} = {%x}; want {%x}", got, want)
}
}

func TestAppendVarintBytes(t *testing.T) {
var got []byte
got = appendVarintBytes(got, []byte{})
got = appendVarintBytes(got, []byte{0xaa, 0xbb})
got = AppendVarintBytes(got, []byte{})
got = AppendVarintBytes(got, []byte{0xaa, 0xbb})
want := []byte{
0x00,
0x02, 0xaa, 0xbb,
}
if !bytes.Equal(got, want) {
t.Errorf("appendVarintBytes {}, {aabb} = {%x}; want {%x}", got, want)
t.Errorf("AppendVarintBytes {}, {aabb} = {%x}; want {%x}", got, want)
}
}
8 changes: 5 additions & 3 deletions quic/config.go
Original file line number Diff line number Diff line change
Expand Up @@ -11,6 +11,8 @@ import (
"log/slog"
"math"
"time"

"golang.org/x/net/internal/quic/quicwire"
)

// A Config structure configures a QUIC endpoint.
Expand Down Expand Up @@ -134,15 +136,15 @@ func (c *Config) maxUniRemoteStreams() int64 {
}

func (c *Config) maxStreamReadBufferSize() int64 {
return configDefault(c.MaxStreamReadBufferSize, 1<<20, maxVarint)
return configDefault(c.MaxStreamReadBufferSize, 1<<20, quicwire.MaxVarint)
}

func (c *Config) maxStreamWriteBufferSize() int64 {
return configDefault(c.MaxStreamWriteBufferSize, 1<<20, maxVarint)
return configDefault(c.MaxStreamWriteBufferSize, 1<<20, quicwire.MaxVarint)
}

func (c *Config) maxConnReadBufferSize() int64 {
return configDefault(c.MaxConnReadBufferSize, 1<<20, maxVarint)
return configDefault(c.MaxConnReadBufferSize, 1<<20, quicwire.MaxVarint)
}

func (c *Config) handshakeTimeout() time.Duration {
Expand Down
16 changes: 9 additions & 7 deletions quic/packet.go
Original file line number Diff line number Diff line change
Expand Up @@ -9,6 +9,8 @@ package quic
import (
"encoding/binary"
"fmt"

"golang.org/x/net/internal/quic/quicwire"
)

// packetType is a QUIC packet type.
Expand Down Expand Up @@ -196,10 +198,10 @@ func parseVersionNegotiation(pkt []byte) (dstConnID, srcConnID, versions []byte)
// appendVersionNegotiation appends a Version Negotiation packet to pkt,
// returning the result.
func appendVersionNegotiation(pkt, dstConnID, srcConnID []byte, versions ...uint32) []byte {
pkt = append(pkt, headerFormLong|fixedBit) // header byte
pkt = append(pkt, 0, 0, 0, 0) // Version (0 for Version Negotiation)
pkt = appendUint8Bytes(pkt, dstConnID) // Destination Connection ID
pkt = appendUint8Bytes(pkt, srcConnID) // Source Connection ID
pkt = append(pkt, headerFormLong|fixedBit) // header byte
pkt = append(pkt, 0, 0, 0, 0) // Version (0 for Version Negotiation)
pkt = quicwire.AppendUint8Bytes(pkt, dstConnID) // Destination Connection ID
pkt = quicwire.AppendUint8Bytes(pkt, srcConnID) // Source Connection ID
for _, v := range versions {
pkt = binary.BigEndian.AppendUint32(pkt, v) // Supported Version
}
Expand Down Expand Up @@ -243,21 +245,21 @@ func parseGenericLongHeaderPacket(b []byte) (p genericLongPacket, ok bool) {
b = b[1:]
// Version (32),
var n int
p.version, n = consumeUint32(b)
p.version, n = quicwire.ConsumeUint32(b)
if n < 0 {
return genericLongPacket{}, false
}
b = b[n:]
// Destination Connection ID Length (8),
// Destination Connection ID (0..2048),
p.dstConnID, n = consumeUint8Bytes(b)
p.dstConnID, n = quicwire.ConsumeUint8Bytes(b)
if n < 0 || len(p.dstConnID) > 2048/8 {
return genericLongPacket{}, false
}
b = b[n:]
// Source Connection ID Length (8),
// Source Connection ID (0..2048),
p.srcConnID, n = consumeUint8Bytes(b)
p.srcConnID, n = quicwire.ConsumeUint8Bytes(b)
if n < 0 || len(p.dstConnID) > 2048/8 {
return genericLongPacket{}, false
}
Expand Down
Loading

0 comments on commit 35e1007

Please sign in to comment.