Skip to content

add more tests for encoding using the static table #15

New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Merged
merged 2 commits into from
Aug 8, 2020
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
138 changes: 135 additions & 3 deletions integrationtests/self/integration_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -2,6 +2,8 @@ package self

import (
"bytes"
"fmt"
"math/rand"

"github.com/marten-seemann/qpack"

Expand All @@ -28,8 +30,21 @@ var _ = Describe("Self Tests", func() {
})
})

randomString := func(l int) string {
const charset = "abcdefghijklmnopqrstuvwxyz" +
"ABCDEFGHIJKLMNOPQRSTUVWXYZ"
s := make([]byte, l)
for i := range s {
s[i] = charset[rand.Intn(len(charset))]
}
return string(s)
}

It("encodes and decodes a single header field", func() {
hf := qpack.HeaderField{Name: "foo", Value: "bar"}
hf := qpack.HeaderField{
Name: randomString(15),
Value: randomString(15),
}
Expect(encoder.WriteField(hf)).To(Succeed())
_, err := decoder.Write(output.Bytes())
Expect(err).ToNot(HaveOccurred())
Expand All @@ -40,7 +55,7 @@ var _ = Describe("Self Tests", func() {
hfs := []qpack.HeaderField{
{Name: "foo", Value: "bar"},
{Name: "lorem", Value: "ipsum"},
{Name: "name", Value: "value"},
{Name: randomString(15), Value: randomString(20)},
}
for _, hf := range hfs {
Expect(encoder.WriteField(hf)).To(Succeed())
Expand All @@ -54,7 +69,7 @@ var _ = Describe("Self Tests", func() {
hfs1 := []qpack.HeaderField{{Name: "foo", Value: "bar"}}
hfs2 := []qpack.HeaderField{
{Name: "lorem", Value: "ipsum"},
{Name: "name", Value: "value"},
{Name: randomString(15), Value: randomString(20)},
}
for _, hf := range hfs1 {
Expect(encoder.WriteField(hf)).To(Succeed())
Expand All @@ -74,4 +89,121 @@ var _ = Describe("Self Tests", func() {
Expect(err).ToNot(HaveOccurred())
Expect(headerFields).To(Equal(hfs2))
})

// replace one character by a random character at a random position
replaceRandomCharacter := func(s string) string {
pos := rand.Intn(len(s))
new := s[:pos]
for {
if c := randomString(1); c != string(s[pos]) {
new += c
break
}
}
new += s[pos+1:]
return new
}

check := func(encoded []byte, hf qpack.HeaderField) {
hfs, err := decoder.DecodeFull(encoded)
ExpectWithOffset(1, err).ToNot(HaveOccurred())
Expect(hfs).To(HaveLen(1))
Expect(hfs[0]).To(Equal(hf))
}

// use an entry with a value, for example "set-cookie"
It("uses the static table for field names, for fields without values", func() {
var hf qpack.HeaderField
for {
if entry := staticTable[rand.Intn(len(staticTable))]; len(entry.Value) == 0 {
hf = qpack.HeaderField{Name: entry.Name}
break
}
}
Expect(encoder.WriteField(hf)).To(Succeed())
encodedLen := output.Len()
check(output.Bytes(), hf)
output.Reset()
oldName := hf.Name
hf.Name = replaceRandomCharacter(hf.Name)
Expect(encoder.WriteField(hf)).To(Succeed())
fmt.Fprintf(GinkgoWriter, "Encoding field name:\n\t%s: %d bytes\n\t%s: %d bytes\n", oldName, encodedLen, hf.Name, output.Len())
Expect(output.Len()).To(BeNumerically(">", encodedLen))
})

// use an entry with a value, for example "set-cookie",
// but now use a custom value
It("uses the static table for field names, for fields without values", func() {
var hf qpack.HeaderField
for {
if entry := staticTable[rand.Intn(len(staticTable))]; len(entry.Value) == 0 {
hf = qpack.HeaderField{
Name: entry.Name,
Value: randomString(5),
}
break
}
}
Expect(encoder.WriteField(hf)).To(Succeed())
encodedLen := output.Len()
check(output.Bytes(), hf)
output.Reset()
oldName := hf.Name
hf.Name = replaceRandomCharacter(hf.Name)
Expect(encoder.WriteField(hf)).To(Succeed())
fmt.Fprintf(GinkgoWriter, "Encoding field name:\n\t%s: %d bytes\n\t%s: %d bytes\n", oldName, encodedLen, hf.Name, output.Len())
Expect(output.Len()).To(BeNumerically(">", encodedLen))
})

// use an entry with a value, for example
// cache-control -> Value: "max-age=0"
// but encode a different value
// cache-control -> xyz
It("uses the static table for field names, for fields with values", func() {
var hf qpack.HeaderField
for {
if entry := staticTable[rand.Intn(len(staticTable))]; len(entry.Value) > 0 {
hf = qpack.HeaderField{
Name: entry.Name,
Value: randomString(20),
}
break
}
}
Expect(encoder.WriteField(hf)).To(Succeed())
encodedLen := output.Len()
check(output.Bytes(), hf)
output.Reset()
oldName := hf.Name
hf.Name = replaceRandomCharacter(hf.Name)
Expect(encoder.WriteField(hf)).To(Succeed())
fmt.Fprintf(GinkgoWriter, "Encoding field name:\n\t%s: %d bytes\n\t%s: %d bytes\n", oldName, encodedLen, hf.Name, output.Len())
Expect(output.Len()).To(BeNumerically(">", encodedLen))
})

It("uses the static table for field values", func() {
var hf qpack.HeaderField
for {
if entry := staticTable[rand.Intn(len(staticTable))]; len(entry.Value) > 0 {
hf = qpack.HeaderField{
Name: entry.Name,
Value: entry.Value,
}
break
}
}
Expect(encoder.WriteField(hf)).To(Succeed())
encodedLen := output.Len()
check(output.Bytes(), hf)
output.Reset()
oldValue := hf.Value
hf.Value = replaceRandomCharacter(hf.Value)
Expect(encoder.WriteField(hf)).To(Succeed())
fmt.Fprintf(GinkgoWriter,
"Encoding field value:\n\t%s: %s -> %d bytes\n\t%s: %s -> %d bytes\n",
hf.Name, oldValue, encodedLen,
hf.Name, hf.Value, output.Len(),
)
Expect(output.Len()).To(BeNumerically(">", encodedLen))
})
})
17 changes: 17 additions & 0 deletions integrationtests/self/self_suite_test.go
Original file line number Diff line number Diff line change
@@ -1,7 +1,11 @@
package self

import (
"math/rand"
"testing"
_ "unsafe"

"github.com/marten-seemann/qpack"

. "github.com/onsi/ginkgo"
. "github.com/onsi/gomega"
Expand All @@ -11,3 +15,16 @@ func TestSelf(t *testing.T) {
RegisterFailHandler(Fail)
RunSpecs(t, "Self Suite")
}

var _ = BeforeSuite(func() {
rand.Seed(GinkgoRandomSeed())
})

var staticTable []qpack.HeaderField

//go:linkname getStaticTable github.com/marten-seemann/qpack.getStaticTable
func getStaticTable() []qpack.HeaderField

func init() {
staticTable = getStaticTable()
}
7 changes: 7 additions & 0 deletions static_table.go
Original file line number Diff line number Diff line change
Expand Up @@ -102,6 +102,13 @@ var staticTableEntries = [...]HeaderField{
{Name: "x-frame-options", Value: "sameorigin"},
}

// Only needed for tests.
// use go:linkname to retrieve the static table.
//nolint:deadcode,unused
func getStaticTable() []HeaderField {
return staticTableEntries[:]
}

type indexAndValues struct {
idx uint8
values map[string]uint8
Expand Down
17 changes: 15 additions & 2 deletions static_table_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -6,8 +6,7 @@ import (
)

var _ = Describe("StaticTable", func() {

It("verifies that encoderMap and staticTableEntries are coherent", func() {
It("verifies that encoderMap has a value for every staticTableEntries entry", func() {
for idx, hf := range staticTableEntries {
if len(hf.Value) == 0 {
Expect(encoderMap[hf.Name].idx).To(Equal(uint8(idx)))
Expand All @@ -17,4 +16,18 @@ var _ = Describe("StaticTable", func() {
}
})

It("verifies that staticTableEntries has a value for every encoderMap entry", func() {
for name, indexAndVal := range encoderMap {
if len(indexAndVal.values) == 0 {
id := indexAndVal.idx
Expect(staticTableEntries[id].Name).To(Equal(name))
Expect(staticTableEntries[id].Value).To(BeEmpty())
} else {
for value, id := range indexAndVal.values {
Expect(staticTableEntries[id].Name).To(Equal(name))
Expect(staticTableEntries[id].Value).To(Equal(value))
}
}
}
})
})