Skip to content

Commit 7ad0ebf

Browse files
neildgopherbot
authored andcommitted
internal/http3: qpack wire primitives
Encode and decode QPACK prefixed integers and string literals. For golang/go#70914 Change-Id: Id12d1853738fc6d0e03bbbef36b67c24298451e1 Reviewed-on: https://go-review.googlesource.com/c/net/+/642115 Reviewed-by: Jonathan Amsterdam <[email protected]> Auto-Submit: Damien Neil <[email protected]> Reviewed-by: Brad Fitzpatrick <[email protected]> LUCI-TryBot-Result: Go LUCI <[email protected]>
1 parent f6b2e53 commit 7ad0ebf

File tree

2 files changed

+326
-0
lines changed

2 files changed

+326
-0
lines changed

internal/http3/qpack.go

+153
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,153 @@
1+
// Copyright 2025 The Go Authors. All rights reserved.
2+
// Use of this source code is governed by a BSD-style
3+
// license that can be found in the LICENSE file.
4+
5+
//go:build go1.24
6+
7+
package http3
8+
9+
import (
10+
"io"
11+
12+
"golang.org/x/net/http2/hpack"
13+
)
14+
15+
// Prefixed-integer encoding from RFC 7541, section 5.1
16+
//
17+
// Prefixed integers consist of some number of bits of data,
18+
// N bits of encoded integer, and 0 or more additional bytes of
19+
// encoded integer.
20+
//
21+
// The RFCs represent this as, for example:
22+
//
23+
// 0 1 2 3 4 5 6 7
24+
// +---+---+---+---+---+---+---+---+
25+
// | 0 | 0 | 1 | Capacity (5+) |
26+
// +---+---+---+-------------------+
27+
//
28+
// "Capacity" is an integer with a 5-bit prefix.
29+
//
30+
// In the following functions, a "prefixLen" parameter is the number
31+
// of integer bits in the first byte (5 in the above example), and
32+
// a "firstByte" parameter is a byte containing the first byte of
33+
// the encoded value (0x001x_xxxx in the above example).
34+
//
35+
// https://www.rfc-editor.org/rfc/rfc9204.html#section-4.1.1
36+
// https://www.rfc-editor.org/rfc/rfc7541#section-5.1
37+
38+
// readPrefixedInt reads an RFC 7541 prefixed integer from st.
39+
func (st *stream) readPrefixedInt(prefixLen uint8) (firstByte byte, v int64, err error) {
40+
firstByte, err = st.ReadByte()
41+
if err != nil {
42+
return 0, 0, errQPACKDecompressionFailed
43+
}
44+
v, err = st.readPrefixedIntWithByte(firstByte, prefixLen)
45+
return firstByte, v, err
46+
}
47+
48+
// readPrefixedInt reads an RFC 7541 prefixed integer from st.
49+
// The first byte has already been read from the stream.
50+
func (st *stream) readPrefixedIntWithByte(firstByte byte, prefixLen uint8) (v int64, err error) {
51+
prefixMask := (byte(1) << prefixLen) - 1
52+
v = int64(firstByte & prefixMask)
53+
if v != int64(prefixMask) {
54+
return v, nil
55+
}
56+
m := 0
57+
for {
58+
b, err := st.ReadByte()
59+
if err != nil {
60+
return 0, errQPACKDecompressionFailed
61+
}
62+
v += int64(b&127) << m
63+
m += 7
64+
if b&128 == 0 {
65+
break
66+
}
67+
}
68+
return v, err
69+
}
70+
71+
// appendPrefixedInt appends an RFC 7541 prefixed integer to b.
72+
//
73+
// The firstByte parameter includes the non-integer bits of the first byte.
74+
// The other bits must be zero.
75+
func appendPrefixedInt(b []byte, firstByte byte, prefixLen uint8, i int64) []byte {
76+
u := uint64(i)
77+
prefixMask := (uint64(1) << prefixLen) - 1
78+
if u < prefixMask {
79+
return append(b, firstByte|byte(u))
80+
}
81+
b = append(b, firstByte|byte(prefixMask))
82+
u -= prefixMask
83+
for u >= 128 {
84+
b = append(b, 0x80|byte(u&0x7f))
85+
u >>= 7
86+
}
87+
return append(b, byte(u))
88+
}
89+
90+
// String literal encoding from RFC 7541, section 5.2
91+
//
92+
// String literals consist of a single bit flag indicating
93+
// whether the string is Huffman-encoded, a prefixed integer (see above),
94+
// and the string.
95+
//
96+
// https://www.rfc-editor.org/rfc/rfc9204.html#section-4.1.2
97+
// https://www.rfc-editor.org/rfc/rfc7541#section-5.2
98+
99+
// readPrefixedString reads an RFC 7541 string from st.
100+
func (st *stream) readPrefixedString(prefixLen uint8) (firstByte byte, s string, err error) {
101+
firstByte, err = st.ReadByte()
102+
if err != nil {
103+
return 0, "", errQPACKDecompressionFailed
104+
}
105+
s, err = st.readPrefixedStringWithByte(firstByte, prefixLen)
106+
return firstByte, s, err
107+
}
108+
109+
// readPrefixedString reads an RFC 7541 string from st.
110+
// The first byte has already been read from the stream.
111+
func (st *stream) readPrefixedStringWithByte(firstByte byte, prefixLen uint8) (s string, err error) {
112+
size, err := st.readPrefixedIntWithByte(firstByte, prefixLen)
113+
if err != nil {
114+
return "", errQPACKDecompressionFailed
115+
}
116+
117+
hbit := byte(1) << prefixLen
118+
isHuffman := firstByte&hbit != 0
119+
120+
// TODO: Avoid allocating here.
121+
data := make([]byte, size)
122+
if _, err := io.ReadFull(st, data); err != nil {
123+
return "", errQPACKDecompressionFailed
124+
}
125+
if isHuffman {
126+
// TODO: Move Huffman functions into a new package that hpack (HTTP/2)
127+
// and this package can both import. Most of the hpack package isn't
128+
// relevant to HTTP/3.
129+
s, err := hpack.HuffmanDecodeToString(data)
130+
if err != nil {
131+
return "", errQPACKDecompressionFailed
132+
}
133+
return s, nil
134+
}
135+
return string(data), nil
136+
}
137+
138+
// appendPrefixedString appends an RFC 7541 string to st.
139+
//
140+
// The firstByte parameter includes the non-integer bits of the first byte.
141+
// The other bits must be zero.
142+
func appendPrefixedString(b []byte, firstByte byte, prefixLen uint8, s string) []byte {
143+
huffmanLen := hpack.HuffmanEncodeLength(s)
144+
if huffmanLen < uint64(len(s)) {
145+
hbit := byte(1) << prefixLen
146+
b = appendPrefixedInt(b, firstByte|hbit, prefixLen, int64(huffmanLen))
147+
b = hpack.AppendHuffmanString(b, s)
148+
} else {
149+
b = appendPrefixedInt(b, firstByte, prefixLen, int64(len(s)))
150+
b = append(b, s...)
151+
}
152+
return b
153+
}

internal/http3/qpack_test.go

+173
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,173 @@
1+
// Copyright 2025 The Go Authors. All rights reserved.
2+
// Use of this source code is governed by a BSD-style
3+
// license that can be found in the LICENSE file.
4+
5+
//go:build go1.24
6+
7+
package http3
8+
9+
import (
10+
"bytes"
11+
"testing"
12+
)
13+
14+
func TestPrefixedInt(t *testing.T) {
15+
st1, st2 := newStreamPair(t)
16+
for _, test := range []struct {
17+
value int64
18+
prefixLen uint8
19+
encoded []byte
20+
}{
21+
// https://www.rfc-editor.org/rfc/rfc7541#appendix-C.1.1
22+
{
23+
value: 10,
24+
prefixLen: 5,
25+
encoded: []byte{
26+
0b_0000_1010,
27+
},
28+
},
29+
// https://www.rfc-editor.org/rfc/rfc7541#appendix-C.1.2
30+
{
31+
value: 1337,
32+
prefixLen: 5,
33+
encoded: []byte{
34+
0b0001_1111,
35+
0b1001_1010,
36+
0b0000_1010,
37+
},
38+
},
39+
// https://www.rfc-editor.org/rfc/rfc7541#appendix-C.1.3
40+
{
41+
value: 42,
42+
prefixLen: 8,
43+
encoded: []byte{
44+
0b0010_1010,
45+
},
46+
},
47+
} {
48+
highBitMask := ^((byte(1) << test.prefixLen) - 1)
49+
for _, highBits := range []byte{
50+
0, highBitMask, 0b1010_1010 & highBitMask,
51+
} {
52+
gotEnc := appendPrefixedInt(nil, highBits, test.prefixLen, test.value)
53+
wantEnc := append([]byte{}, test.encoded...)
54+
wantEnc[0] |= highBits
55+
if !bytes.Equal(gotEnc, wantEnc) {
56+
t.Errorf("appendPrefixedInt(nil, 0b%08b, %v, %v) = {%x}, want {%x}",
57+
highBits, test.prefixLen, test.value, gotEnc, wantEnc)
58+
}
59+
60+
st1.Write(gotEnc)
61+
if err := st1.Flush(); err != nil {
62+
t.Fatal(err)
63+
}
64+
gotFirstByte, v, err := st2.readPrefixedInt(test.prefixLen)
65+
if err != nil || gotFirstByte&highBitMask != highBits || v != test.value {
66+
t.Errorf("st.readPrefixedInt(%v) = 0b%08b, %v, %v; want 0b%08b, %v, nil", test.prefixLen, gotFirstByte, v, err, highBits, test.value)
67+
}
68+
}
69+
}
70+
}
71+
72+
func TestPrefixedString(t *testing.T) {
73+
st1, st2 := newStreamPair(t)
74+
for _, test := range []struct {
75+
value string
76+
prefixLen uint8
77+
encoded []byte
78+
}{
79+
// https://www.rfc-editor.org/rfc/rfc7541#appendix-C.6.1
80+
{
81+
value: "302",
82+
prefixLen: 7,
83+
encoded: []byte{
84+
0x82, // H bit + length 2
85+
0x64, 0x02,
86+
},
87+
},
88+
{
89+
value: "private",
90+
prefixLen: 5,
91+
encoded: []byte{
92+
0x25, // H bit + length 5
93+
0xae, 0xc3, 0x77, 0x1a, 0x4b,
94+
},
95+
},
96+
{
97+
value: "Mon, 21 Oct 2013 20:13:21 GMT",
98+
prefixLen: 7,
99+
encoded: []byte{
100+
0x96, // H bit + length 22
101+
0xd0, 0x7a, 0xbe, 0x94, 0x10, 0x54, 0xd4, 0x44,
102+
0xa8, 0x20, 0x05, 0x95, 0x04, 0x0b, 0x81, 0x66,
103+
0xe0, 0x82, 0xa6, 0x2d, 0x1b, 0xff,
104+
},
105+
},
106+
{
107+
value: "https://www.example.com",
108+
prefixLen: 7,
109+
encoded: []byte{
110+
0x91, // H bit + length 17
111+
0x9d, 0x29, 0xad, 0x17, 0x18, 0x63, 0xc7, 0x8f,
112+
0x0b, 0x97, 0xc8, 0xe9, 0xae, 0x82, 0xae, 0x43,
113+
0xd3,
114+
},
115+
},
116+
// Not Huffman encoded (encoded size == unencoded size).
117+
{
118+
value: "a",
119+
prefixLen: 7,
120+
encoded: []byte{
121+
0x01, // length 1
122+
0x61,
123+
},
124+
},
125+
// Empty string.
126+
{
127+
value: "",
128+
prefixLen: 7,
129+
encoded: []byte{
130+
0x00, // length 0
131+
},
132+
},
133+
} {
134+
highBitMask := ^((byte(1) << (test.prefixLen + 1)) - 1)
135+
for _, highBits := range []byte{
136+
0, highBitMask, 0b1010_1010 & highBitMask,
137+
} {
138+
gotEnc := appendPrefixedString(nil, highBits, test.prefixLen, test.value)
139+
wantEnc := append([]byte{}, test.encoded...)
140+
wantEnc[0] |= highBits
141+
if !bytes.Equal(gotEnc, wantEnc) {
142+
t.Errorf("appendPrefixedString(nil, 0b%08b, %v, %v) = {%x}, want {%x}",
143+
highBits, test.prefixLen, test.value, gotEnc, wantEnc)
144+
}
145+
146+
st1.Write(gotEnc)
147+
if err := st1.Flush(); err != nil {
148+
t.Fatal(err)
149+
}
150+
gotFirstByte, v, err := st2.readPrefixedString(test.prefixLen)
151+
if err != nil || gotFirstByte&highBitMask != highBits || v != test.value {
152+
t.Errorf("st.readPrefixedInt(%v) = 0b%08b, %q, %v; want 0b%08b, %q, nil", test.prefixLen, gotFirstByte, v, err, highBits, test.value)
153+
}
154+
}
155+
}
156+
}
157+
158+
func TestHuffmanDecodingFailure(t *testing.T) {
159+
st1, st2 := newStreamPair(t)
160+
st1.Write([]byte{
161+
0x82, // H bit + length 4
162+
0b_1111_1111,
163+
0b_1111_1111,
164+
0b_1111_1111,
165+
0b_1111_1111,
166+
})
167+
if err := st1.Flush(); err != nil {
168+
t.Fatal(err)
169+
}
170+
if b, v, err := st2.readPrefixedString(7); err == nil {
171+
t.Fatalf("readPrefixedString(7) = %x, %v, nil; want error", b, v)
172+
}
173+
}

0 commit comments

Comments
 (0)