diff --git a/integrationtests/self/integration_test.go b/integrationtests/self/integration_test.go index 3aa04e1..884ec9e 100644 --- a/integrationtests/self/integration_test.go +++ b/integrationtests/self/integration_test.go @@ -2,6 +2,8 @@ package self import ( "bytes" + "fmt" + "math/rand" "github.com/marten-seemann/qpack" @@ -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()) @@ -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()) @@ -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()) @@ -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)) + }) }) diff --git a/integrationtests/self/self_suite_test.go b/integrationtests/self/self_suite_test.go index aa63296..fdfb8d5 100644 --- a/integrationtests/self/self_suite_test.go +++ b/integrationtests/self/self_suite_test.go @@ -1,7 +1,11 @@ package self import ( + "math/rand" "testing" + _ "unsafe" + + "github.com/marten-seemann/qpack" . "github.com/onsi/ginkgo" . "github.com/onsi/gomega" @@ -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() +} diff --git a/static_table.go b/static_table.go index b1bc33e..01ab1f5 100644 --- a/static_table.go +++ b/static_table.go @@ -102,6 +102,12 @@ var staticTableEntries = [...]HeaderField{ {Name: "x-frame-options", Value: "sameorigin"}, } +// Only needed for tests. +// use go:linkname to retrieve the static table. +func getStaticTable() []HeaderField { + return staticTableEntries[:] +} + type indexAndValues struct { idx uint8 values map[string]uint8