Skip to content

Commit 1c46d0b

Browse files
use Huffman encoding for field names and values
1 parent b578e54 commit 1c46d0b

File tree

3 files changed

+45
-36
lines changed

3 files changed

+45
-36
lines changed

encoder.go

Lines changed: 19 additions & 17 deletions
Original file line numberDiff line numberDiff line change
@@ -2,6 +2,8 @@ package qpack
22

33
import (
44
"io"
5+
6+
"golang.org/x/net/http2/hpack"
57
)
68

79
// An Encoder performs QPACK encoding.
@@ -64,31 +66,31 @@ func (e *Encoder) Close() error {
6466

6567
func (e *Encoder) writeLiteralFieldWithoutNameReference(f HeaderField) {
6668
offset := len(e.buf)
67-
e.buf = appendVarInt(e.buf, 3, uint64(len(f.Name)))
68-
e.buf[offset] ^= 0x20
69-
e.buf = append(e.buf, []byte(f.Name)...)
70-
e.buf = appendVarInt(e.buf, 7, uint64(len(f.Value)))
71-
e.buf = append(e.buf, []byte(f.Value)...)
69+
e.buf = appendVarInt(e.buf, 3, hpack.HuffmanEncodeLength(f.Name))
70+
e.buf[offset] ^= 0x20 ^ 0x8
71+
e.buf = hpack.AppendHuffmanString(e.buf, f.Name)
72+
offset = len(e.buf)
73+
e.buf = appendVarInt(e.buf, 7, hpack.HuffmanEncodeLength(f.Value))
74+
e.buf[offset] ^= 0x80
75+
e.buf = hpack.AppendHuffmanString(e.buf, f.Value)
7276
}
7377

74-
// Encodes a header field whose name is present in one of the
75-
// tables.
76-
func (e *Encoder) writeLiteralFieldWithNameReference(
77-
f *HeaderField, idx uint8) {
78+
// Encodes a header field whose name is present in one of the tables.
79+
func (e *Encoder) writeLiteralFieldWithNameReference(f *HeaderField, id uint8) {
7880
offset := len(e.buf)
79-
e.buf = appendVarInt(e.buf, 4, uint64(idx))
81+
e.buf = appendVarInt(e.buf, 4, uint64(id))
8082
// Set the 01NTxxxx pattern, forcing N to 0 and T to 1
8183
e.buf[offset] ^= 0x50
82-
83-
e.buf = appendVarInt(e.buf, 7, uint64(len(f.Value)))
84-
e.buf = append(e.buf, []byte(f.Value)...)
84+
offset = len(e.buf)
85+
e.buf = appendVarInt(e.buf, 7, hpack.HuffmanEncodeLength(f.Value))
86+
e.buf[offset] ^= 0x80
87+
e.buf = hpack.AppendHuffmanString(e.buf, f.Value)
8588
}
8689

87-
// Encodes an indexed field, meaning it's entirely defined in one of the
88-
// tables.
89-
func (e *Encoder) writeIndexedField(idx uint8) {
90+
// Encodes an indexed field, meaning it's entirely defined in one of the tables.
91+
func (e *Encoder) writeIndexedField(id uint8) {
9092
offset := len(e.buf)
91-
e.buf = appendVarInt(e.buf, 6, uint64(idx))
93+
e.buf = appendVarInt(e.buf, 6, uint64(id))
9294
// Set the 1Txxxxxx pattern, forcing T to 1
9395
e.buf[offset] ^= 0xc0
9496
}

encoder_test.go

Lines changed: 20 additions & 17 deletions
Original file line numberDiff line numberDiff line change
@@ -3,6 +3,8 @@ package qpack
33
import (
44
"bytes"
55

6+
"golang.org/x/net/http2/hpack"
7+
68
. "github.com/onsi/ginkgo"
79
. "github.com/onsi/gomega"
810
)
@@ -29,41 +31,42 @@ var _ = Describe("Encoder", func() {
2931

3032
checkHeaderField := func(data []byte, hf HeaderField) []byte {
3133
Expect(data[0] & (0x80 ^ 0x40 ^ 0x20)).To(Equal(uint8(0x20))) // 001xxxxx
32-
Expect(data[0] & 0x8).To(BeZero()) // no Huffman encoding
34+
Expect(data[0] & 0x8).ToNot(BeZero()) // Huffman encoding
3335
nameLen, data, err := readVarInt(3, data)
3436
Expect(err).ToNot(HaveOccurred())
35-
Expect(nameLen).To(BeEquivalentTo(len(hf.Name)))
36-
Expect(string(data[:len(hf.Name)])).To(Equal(hf.Name))
37-
valueLen, data, err := readVarInt(7, data[len(hf.Name):])
38-
Expect(err).ToNot(HaveOccurred())
39-
Expect(valueLen).To(BeEquivalentTo(len(hf.Value)))
40-
Expect(string(data[:len(hf.Value)])).To(Equal(hf.Value))
41-
return data[len(hf.Value):]
37+
l := hpack.HuffmanEncodeLength(hf.Name)
38+
Expect(nameLen).To(BeEquivalentTo(l))
39+
Expect(hpack.HuffmanDecodeToString(data[:l])).To(Equal(hf.Name))
40+
valueLen, data, err := readVarInt(7, data[l:])
41+
l = hpack.HuffmanEncodeLength(hf.Value)
42+
Expect(valueLen).To(BeEquivalentTo(l))
43+
Expect(hpack.HuffmanDecodeToString(data[:l])).To(Equal(hf.Value))
44+
return data[l:]
4245
}
4346

44-
// Reads one indexed field line representation from data and verifies it
45-
// matches expected_hf.
47+
// Reads one indexed field line representation from data and verifies it matches hf.
4648
// Returns the leftover bytes from data.
47-
checkIndexedHeaderField := func(data []byte, expected_hf HeaderField) []byte {
49+
checkIndexedHeaderField := func(data []byte, hf HeaderField) []byte {
4850
Expect(data[0] >> 7).To(Equal(uint8(1))) // 1Txxxxxx
4951
index, data, err := readVarInt(6, data)
5052
Expect(err).ToNot(HaveOccurred())
51-
Expect(staticTableEntries[index]).To(Equal(expected_hf))
53+
Expect(staticTableEntries[index]).To(Equal(hf))
5254
return data
5355
}
5456

55-
checkHeaderFieldWithNameRef := func(data []byte, expected_hf HeaderField) []byte {
57+
checkHeaderFieldWithNameRef := func(data []byte, hf HeaderField) []byte {
5658
// read name reference
5759
Expect(data[0] >> 6).To(Equal(uint8(1))) // 01NTxxxx
5860
index, data, err := readVarInt(4, data)
5961
Expect(err).ToNot(HaveOccurred())
60-
Expect(staticTableEntries[index].Name).To(Equal(expected_hf.Name))
62+
Expect(staticTableEntries[index].Name).To(Equal(hf.Name))
6163
// read literal value
6264
valueLen, data, err := readVarInt(7, data)
6365
Expect(err).ToNot(HaveOccurred())
64-
Expect(valueLen).To(BeEquivalentTo(len(expected_hf.Value)))
65-
Expect(string(data[:len(expected_hf.Value)])).To(Equal(expected_hf.Value))
66-
return data[len(expected_hf.Value):]
66+
l := hpack.HuffmanEncodeLength(hf.Value)
67+
Expect(valueLen).To(BeEquivalentTo(l))
68+
Expect(hpack.HuffmanDecodeToString(data[:l])).To(Equal(hf.Value))
69+
return data[l:]
6770
}
6871

6972
It("encodes a single field", func() {

integrationtests/self/integration_test.go

Lines changed: 6 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -162,7 +162,9 @@ var _ = Describe("Self Tests", func() {
162162
It("uses the static table for field names, for fields with values", func() {
163163
var hf qpack.HeaderField
164164
for {
165-
if entry := staticTable[rand.Intn(len(staticTable))]; len(entry.Value) > 0 {
165+
// Only use values with at least 2 characters.
166+
// This makes sure that Huffman enocding doesn't compress them as much as encoding it using the static table would.
167+
if entry := staticTable[rand.Intn(len(staticTable))]; len(entry.Value) > 1 {
166168
hf = qpack.HeaderField{
167169
Name: entry.Name,
168170
Value: randomString(20),
@@ -184,7 +186,9 @@ var _ = Describe("Self Tests", func() {
184186
It("uses the static table for field values", func() {
185187
var hf qpack.HeaderField
186188
for {
187-
if entry := staticTable[rand.Intn(len(staticTable))]; len(entry.Value) > 0 {
189+
// Only use values with at least 2 characters.
190+
// This makes sure that Huffman enocding doesn't compress them as much as encoding it using the static table would.
191+
if entry := staticTable[rand.Intn(len(staticTable))]; len(entry.Value) > 1 {
188192
hf = qpack.HeaderField{
189193
Name: entry.Name,
190194
Value: entry.Value,

0 commit comments

Comments
 (0)