1package self 2 3import ( 4 "bytes" 5 "fmt" 6 "math/rand" 7 8 "github.com/marten-seemann/qpack" 9 10 . "github.com/onsi/ginkgo" 11 . "github.com/onsi/gomega" 12) 13 14var _ = Describe("Self Tests", func() { 15 getEncoder := func() (*qpack.Encoder, *bytes.Buffer) { 16 output := &bytes.Buffer{} 17 return qpack.NewEncoder(output), output 18 } 19 20 randomString := func(l int) string { 21 const charset = "abcdefghijklmnopqrstuvwxyz" + 22 "ABCDEFGHIJKLMNOPQRSTUVWXYZ" 23 s := make([]byte, l) 24 for i := range s { 25 s[i] = charset[rand.Intn(len(charset))] 26 } 27 return string(s) 28 } 29 30 It("encodes and decodes a single header field", func() { 31 hf := qpack.HeaderField{ 32 Name: randomString(15), 33 Value: randomString(15), 34 } 35 encoder, output := getEncoder() 36 Expect(encoder.WriteField(hf)).To(Succeed()) 37 headerFields, err := qpack.NewDecoder(nil).DecodeFull(output.Bytes()) 38 Expect(err).ToNot(HaveOccurred()) 39 Expect(headerFields).To(Equal([]qpack.HeaderField{hf})) 40 }) 41 42 It("encodes and decodes multiple header fields", func() { 43 hfs := []qpack.HeaderField{ 44 {Name: "foo", Value: "bar"}, 45 {Name: "lorem", Value: "ipsum"}, 46 {Name: randomString(15), Value: randomString(20)}, 47 } 48 encoder, output := getEncoder() 49 for _, hf := range hfs { 50 Expect(encoder.WriteField(hf)).To(Succeed()) 51 } 52 headerFields, err := qpack.NewDecoder(nil).DecodeFull(output.Bytes()) 53 Expect(err).ToNot(HaveOccurred()) 54 Expect(headerFields).To(Equal(hfs)) 55 }) 56 57 It("encodes and decodes multiple requests", func() { 58 hfs1 := []qpack.HeaderField{{Name: "foo", Value: "bar"}} 59 hfs2 := []qpack.HeaderField{ 60 {Name: "lorem", Value: "ipsum"}, 61 {Name: randomString(15), Value: randomString(20)}, 62 } 63 encoder, output := getEncoder() 64 for _, hf := range hfs1 { 65 Expect(encoder.WriteField(hf)).To(Succeed()) 66 } 67 req1 := append([]byte{}, output.Bytes()...) 68 output.Reset() 69 for _, hf := range hfs2 { 70 Expect(encoder.WriteField(hf)).To(Succeed()) 71 } 72 req2 := append([]byte{}, output.Bytes()...) 73 74 var headerFields []qpack.HeaderField 75 decoder := qpack.NewDecoder(func(hf qpack.HeaderField) { headerFields = append(headerFields, hf) }) 76 _, err := decoder.Write(req1) 77 Expect(err).ToNot(HaveOccurred()) 78 Expect(headerFields).To(Equal(hfs1)) 79 headerFields = nil 80 _, err = decoder.Write(req2) 81 Expect(err).ToNot(HaveOccurred()) 82 Expect(headerFields).To(Equal(hfs2)) 83 }) 84 85 // replace one character by a random character at a random position 86 replaceRandomCharacter := func(s string) string { 87 pos := rand.Intn(len(s)) 88 new := s[:pos] 89 for { 90 if c := randomString(1); c != string(s[pos]) { 91 new += c 92 break 93 } 94 } 95 new += s[pos+1:] 96 return new 97 } 98 99 check := func(encoded []byte, hf qpack.HeaderField) { 100 headerFields, err := qpack.NewDecoder(nil).DecodeFull(encoded) 101 ExpectWithOffset(1, err).ToNot(HaveOccurred()) 102 ExpectWithOffset(1, headerFields).To(HaveLen(1)) 103 ExpectWithOffset(1, headerFields[0]).To(Equal(hf)) 104 } 105 106 // use an entry with a value, for example "set-cookie" 107 It("uses the static table for field names, for fields without values", func() { 108 var hf qpack.HeaderField 109 for { 110 if entry := staticTable[rand.Intn(len(staticTable))]; len(entry.Value) == 0 { 111 hf = qpack.HeaderField{Name: entry.Name} 112 break 113 } 114 } 115 encoder, output := getEncoder() 116 Expect(encoder.WriteField(hf)).To(Succeed()) 117 encodedLen := output.Len() 118 check(output.Bytes(), hf) 119 encoder, output = getEncoder() 120 oldName := hf.Name 121 hf.Name = replaceRandomCharacter(hf.Name) 122 Expect(encoder.WriteField(hf)).To(Succeed()) 123 fmt.Fprintf(GinkgoWriter, "Encoding field name:\n\t%s: %d bytes\n\t%s: %d bytes\n", oldName, encodedLen, hf.Name, output.Len()) 124 Expect(output.Len()).To(BeNumerically(">", encodedLen)) 125 }) 126 127 // use an entry with a value, for example "set-cookie", 128 // but now use a custom value 129 It("uses the static table for field names, for fields without values", func() { 130 var hf qpack.HeaderField 131 for { 132 if entry := staticTable[rand.Intn(len(staticTable))]; len(entry.Value) == 0 { 133 hf = qpack.HeaderField{ 134 Name: entry.Name, 135 Value: randomString(5), 136 } 137 break 138 } 139 } 140 encoder, output := getEncoder() 141 Expect(encoder.WriteField(hf)).To(Succeed()) 142 encodedLen := output.Len() 143 check(output.Bytes(), hf) 144 encoder, output = getEncoder() 145 oldName := hf.Name 146 hf.Name = replaceRandomCharacter(hf.Name) 147 Expect(encoder.WriteField(hf)).To(Succeed()) 148 fmt.Fprintf(GinkgoWriter, "Encoding field name:\n\t%s: %d bytes\n\t%s: %d bytes\n", oldName, encodedLen, hf.Name, output.Len()) 149 Expect(output.Len()).To(BeNumerically(">", encodedLen)) 150 }) 151 152 // use an entry with a value, for example 153 // cache-control -> Value: "max-age=0" 154 // but encode a different value 155 // cache-control -> xyz 156 It("uses the static table for field names, for fields with values", func() { 157 var hf qpack.HeaderField 158 for { 159 // Only use values with at least 2 characters. 160 // This makes sure that Huffman enocding doesn't compress them as much as encoding it using the static table would. 161 if entry := staticTable[rand.Intn(len(staticTable))]; len(entry.Value) > 1 { 162 hf = qpack.HeaderField{ 163 Name: entry.Name, 164 Value: randomString(20), 165 } 166 break 167 } 168 } 169 encoder, output := getEncoder() 170 Expect(encoder.WriteField(hf)).To(Succeed()) 171 encodedLen := output.Len() 172 check(output.Bytes(), hf) 173 encoder, output = getEncoder() 174 oldName := hf.Name 175 hf.Name = replaceRandomCharacter(hf.Name) 176 Expect(encoder.WriteField(hf)).To(Succeed()) 177 fmt.Fprintf(GinkgoWriter, "Encoding field name:\n\t%s: %d bytes\n\t%s: %d bytes\n", oldName, encodedLen, hf.Name, output.Len()) 178 Expect(output.Len()).To(BeNumerically(">", encodedLen)) 179 }) 180 181 It("uses the static table for field values", func() { 182 var hf qpack.HeaderField 183 for { 184 // Only use values with at least 2 characters. 185 // This makes sure that Huffman enocding doesn't compress them as much as encoding it using the static table would. 186 if entry := staticTable[rand.Intn(len(staticTable))]; len(entry.Value) > 1 { 187 hf = qpack.HeaderField{ 188 Name: entry.Name, 189 Value: entry.Value, 190 } 191 break 192 } 193 } 194 encoder, output := getEncoder() 195 Expect(encoder.WriteField(hf)).To(Succeed()) 196 encodedLen := output.Len() 197 check(output.Bytes(), hf) 198 encoder, output = getEncoder() 199 oldValue := hf.Value 200 hf.Value = replaceRandomCharacter(hf.Value) 201 Expect(encoder.WriteField(hf)).To(Succeed()) 202 fmt.Fprintf(GinkgoWriter, 203 "Encoding field value:\n\t%s: %s -> %d bytes\n\t%s: %s -> %d bytes\n", 204 hf.Name, oldValue, encodedLen, 205 hf.Name, hf.Value, output.Len(), 206 ) 207 Expect(output.Len()).To(BeNumerically(">", encodedLen)) 208 }) 209}) 210