Skip to content

Commit 90c9691

Browse files
Merge pull request #4 from marten-seemann/encoder
implement the simplest possible encoder
2 parents db84e63 + 92d7f93 commit 90c9691

File tree

10 files changed

+240
-8
lines changed

10 files changed

+240
-8
lines changed

.circleci/config.yml

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -22,7 +22,7 @@ jobs:
2222
command: ginkgo -race -cover -v -randomizeAllSpecs
2323
- run:
2424
name: "Run integration tests"
25-
command: ginkgo -race -v -randomizeAllSpecs -trace integrationtests
25+
command: ginkgo -r -race -v -randomizeAllSpecs -trace integrationtests
2626
- run:
2727
name: "Upload coverage report to Codecov"
2828
when: on_success

.gitmodules

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -1,3 +1,3 @@
1-
[submodule "integrationtests/qifs"]
2-
path = integrationtests/qifs
1+
[submodule "integrationtests/interop/qifs"]
2+
path = integrationtests/interop/qifs
33
url = git@github.com:qpackers/qifs.git

README.md

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -17,5 +17,5 @@ git submodule update --init --recursive
1717

1818
Then run the tests:
1919
```bash
20-
ginkgo integrationtests
20+
ginkgo -r integrationtests
2121
```

encoder.go

Lines changed: 52 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,52 @@
1+
package qpack
2+
3+
import (
4+
"io"
5+
)
6+
7+
// An Encoder performs QPACK encoding.
8+
type Encoder struct {
9+
wrotePrefix bool
10+
11+
w io.Writer
12+
buf []byte
13+
}
14+
15+
// NewEncoder returns a new Encoder which performs QPACK encoding. An
16+
// encoded data is written to w.
17+
func NewEncoder(w io.Writer) *Encoder {
18+
return &Encoder{w: w}
19+
}
20+
21+
// WriteField encodes f into a single Write to e's underlying Writer.
22+
// This function may also produce bytes for the Header Block Prefix
23+
// if necessary. If produced, it is done before encoding f.
24+
func (e *Encoder) WriteField(f HeaderField) error {
25+
// write the Header Block Prefix
26+
if !e.wrotePrefix {
27+
e.buf = appendVarInt(e.buf, 8, 0)
28+
e.buf = appendVarInt(e.buf, 7, 0)
29+
e.wrotePrefix = true
30+
}
31+
32+
e.writeLiteralFieldWithoutNameReference(f)
33+
e.w.Write(e.buf)
34+
e.buf = e.buf[:0]
35+
return nil
36+
}
37+
38+
// Close declares that the encoding is complete and resets the Encoder
39+
// to be reused again for a new header block.
40+
func (e *Encoder) Close() error {
41+
e.wrotePrefix = false
42+
return nil
43+
}
44+
45+
func (e *Encoder) writeLiteralFieldWithoutNameReference(f HeaderField) {
46+
offset := len(e.buf)
47+
e.buf = appendVarInt(e.buf, 3, uint64(len(f.Name)))
48+
e.buf[offset] ^= 0x20
49+
e.buf = append(e.buf, []byte(f.Name)...)
50+
e.buf = appendVarInt(e.buf, 7, uint64(len(f.Value)))
51+
e.buf = append(e.buf, []byte(f.Value)...)
52+
}

encoder_test.go

Lines changed: 90 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,90 @@
1+
package qpack
2+
3+
import (
4+
"bytes"
5+
6+
. "github.com/onsi/ginkgo"
7+
. "github.com/onsi/gomega"
8+
)
9+
10+
var _ = Describe("Encoder", func() {
11+
var (
12+
encoder *Encoder
13+
output *bytes.Buffer
14+
)
15+
16+
BeforeEach(func() {
17+
output = &bytes.Buffer{}
18+
encoder = NewEncoder(output)
19+
})
20+
21+
readPrefix := func(data []byte) (rest []byte, requiredInsertCount uint64, deltaBase uint64) {
22+
var err error
23+
requiredInsertCount, rest, err = readVarInt(8, data)
24+
Expect(err).ToNot(HaveOccurred())
25+
deltaBase, rest, err = readVarInt(7, rest)
26+
Expect(err).ToNot(HaveOccurred())
27+
return
28+
}
29+
30+
checkHeaderField := func(data []byte, hf HeaderField) []byte {
31+
Expect(data[0] & (0x80 ^ 0x40 ^ 0x20)).To(Equal(uint8(0x20))) // 001xxxxx
32+
Expect(data[0] & 0x8).To(BeZero()) // no Huffman encoding
33+
nameLen, data, err := readVarInt(3, data)
34+
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):]
42+
}
43+
44+
It("encodes a single field", func() {
45+
hf := HeaderField{Name: "foobar", Value: "lorem ipsum"}
46+
Expect(encoder.WriteField(hf)).To(Succeed())
47+
48+
data, requiredInsertCount, deltaBase := readPrefix(output.Bytes())
49+
Expect(requiredInsertCount).To(BeZero())
50+
Expect(deltaBase).To(BeZero())
51+
52+
data = checkHeaderField(data, hf)
53+
Expect(data).To(BeEmpty())
54+
})
55+
56+
It("encodes multipe fields", func() {
57+
hf1 := HeaderField{Name: "foobar", Value: "lorem ipsum"}
58+
hf2 := HeaderField{Name: "raboof", Value: "dolor sit amet"}
59+
Expect(encoder.WriteField(hf1)).To(Succeed())
60+
Expect(encoder.WriteField(hf2)).To(Succeed())
61+
62+
data, requiredInsertCount, deltaBase := readPrefix(output.Bytes())
63+
Expect(requiredInsertCount).To(BeZero())
64+
Expect(deltaBase).To(BeZero())
65+
66+
data = checkHeaderField(data, hf1)
67+
data = checkHeaderField(data, hf2)
68+
Expect(data).To(BeEmpty())
69+
})
70+
71+
It("encodes multiple requests", func() {
72+
hf1 := HeaderField{Name: "foobar", Value: "lorem ipsum"}
73+
Expect(encoder.WriteField(hf1)).To(Succeed())
74+
data, requiredInsertCount, deltaBase := readPrefix(output.Bytes())
75+
Expect(requiredInsertCount).To(BeZero())
76+
Expect(deltaBase).To(BeZero())
77+
data = checkHeaderField(data, hf1)
78+
Expect(data).To(BeEmpty())
79+
80+
output.Reset()
81+
Expect(encoder.Close())
82+
hf2 := HeaderField{Name: "raboof", Value: "dolor sit amet"}
83+
Expect(encoder.WriteField(hf2)).To(Succeed())
84+
data, requiredInsertCount, deltaBase = readPrefix(output.Bytes())
85+
Expect(requiredInsertCount).To(BeZero())
86+
Expect(deltaBase).To(BeZero())
87+
data = checkHeaderField(data, hf2)
88+
Expect(data).To(BeEmpty())
89+
})
90+
})

integrationtests/integrationtests_suite_test.go renamed to integrationtests/interop/interop_suite_test.go

Lines changed: 3 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,4 @@
1-
package integrationtests
1+
package interop
22

33
import (
44
"bufio"
@@ -13,9 +13,9 @@ import (
1313
. "github.com/onsi/gomega"
1414
)
1515

16-
func TestIntegrationtests(t *testing.T) {
16+
func TestInterop(t *testing.T) {
1717
RegisterFailHandler(Fail)
18-
RunSpecs(t, "Integration Tests Suite")
18+
RunSpecs(t, "Interop Suite")
1919
}
2020

2121
type request struct {
Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,4 @@
1-
package integrationtests
1+
package interop
22

33
import (
44
"encoding/binary"
Lines changed: 77 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,77 @@
1+
package self
2+
3+
import (
4+
"bytes"
5+
6+
"github.com/marten-seemann/qpack"
7+
8+
. "github.com/onsi/ginkgo"
9+
. "github.com/onsi/gomega"
10+
)
11+
12+
var _ = Describe("Self Tests", func() {
13+
var (
14+
// for the encoder
15+
output *bytes.Buffer
16+
encoder *qpack.Encoder
17+
// for the decoder
18+
headerFields []qpack.HeaderField
19+
decoder *qpack.Decoder
20+
)
21+
22+
BeforeEach(func() {
23+
output = &bytes.Buffer{}
24+
encoder = qpack.NewEncoder(output)
25+
headerFields = nil
26+
decoder = qpack.NewDecoder(func(hf qpack.HeaderField) {
27+
headerFields = append(headerFields, hf)
28+
})
29+
})
30+
31+
It("encodes and decodes a single header field", func() {
32+
hf := qpack.HeaderField{Name: "foo", Value: "bar"}
33+
Expect(encoder.WriteField(hf)).To(Succeed())
34+
_, err := decoder.Write(output.Bytes())
35+
Expect(err).ToNot(HaveOccurred())
36+
Expect(headerFields).To(Equal([]qpack.HeaderField{hf}))
37+
})
38+
39+
It("encodes and decodes multiple header fields", func() {
40+
hfs := []qpack.HeaderField{
41+
{Name: "foo", Value: "bar"},
42+
{Name: "lorem", Value: "ipsum"},
43+
{Name: "name", Value: "value"},
44+
}
45+
for _, hf := range hfs {
46+
Expect(encoder.WriteField(hf)).To(Succeed())
47+
}
48+
_, err := decoder.Write(output.Bytes())
49+
Expect(err).ToNot(HaveOccurred())
50+
Expect(headerFields).To(Equal(hfs))
51+
})
52+
53+
It("encodes and decodes multiple requests", func() {
54+
hfs1 := []qpack.HeaderField{{Name: "foo", Value: "bar"}}
55+
hfs2 := []qpack.HeaderField{
56+
{Name: "lorem", Value: "ipsum"},
57+
{Name: "name", Value: "value"},
58+
}
59+
for _, hf := range hfs1 {
60+
Expect(encoder.WriteField(hf)).To(Succeed())
61+
}
62+
req1 := append([]byte{}, output.Bytes()...)
63+
output.Reset()
64+
for _, hf := range hfs2 {
65+
Expect(encoder.WriteField(hf)).To(Succeed())
66+
}
67+
req2 := append([]byte{}, output.Bytes()...)
68+
69+
_, err := decoder.Write(req1)
70+
Expect(err).ToNot(HaveOccurred())
71+
Expect(headerFields).To(Equal(hfs1))
72+
headerFields = nil
73+
_, err = decoder.Write(req2)
74+
Expect(err).ToNot(HaveOccurred())
75+
Expect(headerFields).To(Equal(hfs2))
76+
})
77+
})
Lines changed: 13 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,13 @@
1+
package self
2+
3+
import (
4+
"testing"
5+
6+
. "github.com/onsi/ginkgo"
7+
. "github.com/onsi/gomega"
8+
)
9+
10+
func TestSelf(t *testing.T) {
11+
RegisterFailHandler(Fail)
12+
RunSpecs(t, "Self Suite")
13+
}

0 commit comments

Comments
 (0)