OSDN Git Service

new repo
[bytom/vapor.git] / vendor / golang.org / x / net / http2 / hpack / encode_test.go
1 // Copyright 2014 The Go Authors. All rights reserved.
2 // Use of this source code is governed by a BSD-style
3 // license that can be found in the LICENSE file.
4
5 package hpack
6
7 import (
8         "bytes"
9         "encoding/hex"
10         "fmt"
11         "math/rand"
12         "reflect"
13         "strings"
14         "testing"
15 )
16
17 func TestEncoderTableSizeUpdate(t *testing.T) {
18         tests := []struct {
19                 size1, size2 uint32
20                 wantHex      string
21         }{
22                 // Should emit 2 table size updates (2048 and 4096)
23                 {2048, 4096, "3fe10f 3fe11f 82"},
24
25                 // Should emit 1 table size update (2048)
26                 {16384, 2048, "3fe10f 82"},
27         }
28         for _, tt := range tests {
29                 var buf bytes.Buffer
30                 e := NewEncoder(&buf)
31                 e.SetMaxDynamicTableSize(tt.size1)
32                 e.SetMaxDynamicTableSize(tt.size2)
33                 if err := e.WriteField(pair(":method", "GET")); err != nil {
34                         t.Fatal(err)
35                 }
36                 want := removeSpace(tt.wantHex)
37                 if got := hex.EncodeToString(buf.Bytes()); got != want {
38                         t.Errorf("e.SetDynamicTableSize %v, %v = %q; want %q", tt.size1, tt.size2, got, want)
39                 }
40         }
41 }
42
43 func TestEncoderWriteField(t *testing.T) {
44         var buf bytes.Buffer
45         e := NewEncoder(&buf)
46         var got []HeaderField
47         d := NewDecoder(4<<10, func(f HeaderField) {
48                 got = append(got, f)
49         })
50
51         tests := []struct {
52                 hdrs []HeaderField
53         }{
54                 {[]HeaderField{
55                         pair(":method", "GET"),
56                         pair(":scheme", "http"),
57                         pair(":path", "/"),
58                         pair(":authority", "www.example.com"),
59                 }},
60                 {[]HeaderField{
61                         pair(":method", "GET"),
62                         pair(":scheme", "http"),
63                         pair(":path", "/"),
64                         pair(":authority", "www.example.com"),
65                         pair("cache-control", "no-cache"),
66                 }},
67                 {[]HeaderField{
68                         pair(":method", "GET"),
69                         pair(":scheme", "https"),
70                         pair(":path", "/index.html"),
71                         pair(":authority", "www.example.com"),
72                         pair("custom-key", "custom-value"),
73                 }},
74         }
75         for i, tt := range tests {
76                 buf.Reset()
77                 got = got[:0]
78                 for _, hf := range tt.hdrs {
79                         if err := e.WriteField(hf); err != nil {
80                                 t.Fatal(err)
81                         }
82                 }
83                 _, err := d.Write(buf.Bytes())
84                 if err != nil {
85                         t.Errorf("%d. Decoder Write = %v", i, err)
86                 }
87                 if !reflect.DeepEqual(got, tt.hdrs) {
88                         t.Errorf("%d. Decoded %+v; want %+v", i, got, tt.hdrs)
89                 }
90         }
91 }
92
93 func TestEncoderSearchTable(t *testing.T) {
94         e := NewEncoder(nil)
95
96         e.dynTab.add(pair("foo", "bar"))
97         e.dynTab.add(pair("blake", "miz"))
98         e.dynTab.add(pair(":method", "GET"))
99
100         tests := []struct {
101                 hf        HeaderField
102                 wantI     uint64
103                 wantMatch bool
104         }{
105                 // Name and Value match
106                 {pair("foo", "bar"), uint64(staticTable.len()) + 3, true},
107                 {pair("blake", "miz"), uint64(staticTable.len()) + 2, true},
108                 {pair(":method", "GET"), 2, true},
109
110                 // Only name match because Sensitive == true. This is allowed to match
111                 // any ":method" entry. The current implementation uses the last entry
112                 // added in newStaticTable.
113                 {HeaderField{":method", "GET", true}, 3, false},
114
115                 // Only Name matches
116                 {pair("foo", "..."), uint64(staticTable.len()) + 3, false},
117                 {pair("blake", "..."), uint64(staticTable.len()) + 2, false},
118                 // As before, this is allowed to match any ":method" entry.
119                 {pair(":method", "..."), 3, false},
120
121                 // None match
122                 {pair("foo-", "bar"), 0, false},
123         }
124         for _, tt := range tests {
125                 if gotI, gotMatch := e.searchTable(tt.hf); gotI != tt.wantI || gotMatch != tt.wantMatch {
126                         t.Errorf("d.search(%+v) = %v, %v; want %v, %v", tt.hf, gotI, gotMatch, tt.wantI, tt.wantMatch)
127                 }
128         }
129 }
130
131 func TestAppendVarInt(t *testing.T) {
132         tests := []struct {
133                 n    byte
134                 i    uint64
135                 want []byte
136         }{
137                 // Fits in a byte:
138                 {1, 0, []byte{0}},
139                 {2, 2, []byte{2}},
140                 {3, 6, []byte{6}},
141                 {4, 14, []byte{14}},
142                 {5, 30, []byte{30}},
143                 {6, 62, []byte{62}},
144                 {7, 126, []byte{126}},
145                 {8, 254, []byte{254}},
146
147                 // Multiple bytes:
148                 {5, 1337, []byte{31, 154, 10}},
149         }
150         for _, tt := range tests {
151                 got := appendVarInt(nil, tt.n, tt.i)
152                 if !bytes.Equal(got, tt.want) {
153                         t.Errorf("appendVarInt(nil, %v, %v) = %v; want %v", tt.n, tt.i, got, tt.want)
154                 }
155         }
156 }
157
158 func TestAppendHpackString(t *testing.T) {
159         tests := []struct {
160                 s, wantHex string
161         }{
162                 // Huffman encoded
163                 {"www.example.com", "8c f1e3 c2e5 f23a 6ba0 ab90 f4ff"},
164
165                 // Not Huffman encoded
166                 {"a", "01 61"},
167
168                 // zero length
169                 {"", "00"},
170         }
171         for _, tt := range tests {
172                 want := removeSpace(tt.wantHex)
173                 buf := appendHpackString(nil, tt.s)
174                 if got := hex.EncodeToString(buf); want != got {
175                         t.Errorf("appendHpackString(nil, %q) = %q; want %q", tt.s, got, want)
176                 }
177         }
178 }
179
180 func TestAppendIndexed(t *testing.T) {
181         tests := []struct {
182                 i       uint64
183                 wantHex string
184         }{
185                 // 1 byte
186                 {1, "81"},
187                 {126, "fe"},
188
189                 // 2 bytes
190                 {127, "ff00"},
191                 {128, "ff01"},
192         }
193         for _, tt := range tests {
194                 want := removeSpace(tt.wantHex)
195                 buf := appendIndexed(nil, tt.i)
196                 if got := hex.EncodeToString(buf); want != got {
197                         t.Errorf("appendIndex(nil, %v) = %q; want %q", tt.i, got, want)
198                 }
199         }
200 }
201
202 func TestAppendNewName(t *testing.T) {
203         tests := []struct {
204                 f        HeaderField
205                 indexing bool
206                 wantHex  string
207         }{
208                 // Incremental indexing
209                 {HeaderField{"custom-key", "custom-value", false}, true, "40 88 25a8 49e9 5ba9 7d7f 89 25a8 49e9 5bb8 e8b4 bf"},
210
211                 // Without indexing
212                 {HeaderField{"custom-key", "custom-value", false}, false, "00 88 25a8 49e9 5ba9 7d7f 89 25a8 49e9 5bb8 e8b4 bf"},
213
214                 // Never indexed
215                 {HeaderField{"custom-key", "custom-value", true}, true, "10 88 25a8 49e9 5ba9 7d7f 89 25a8 49e9 5bb8 e8b4 bf"},
216                 {HeaderField{"custom-key", "custom-value", true}, false, "10 88 25a8 49e9 5ba9 7d7f 89 25a8 49e9 5bb8 e8b4 bf"},
217         }
218         for _, tt := range tests {
219                 want := removeSpace(tt.wantHex)
220                 buf := appendNewName(nil, tt.f, tt.indexing)
221                 if got := hex.EncodeToString(buf); want != got {
222                         t.Errorf("appendNewName(nil, %+v, %v) = %q; want %q", tt.f, tt.indexing, got, want)
223                 }
224         }
225 }
226
227 func TestAppendIndexedName(t *testing.T) {
228         tests := []struct {
229                 f        HeaderField
230                 i        uint64
231                 indexing bool
232                 wantHex  string
233         }{
234                 // Incremental indexing
235                 {HeaderField{":status", "302", false}, 8, true, "48 82 6402"},
236
237                 // Without indexing
238                 {HeaderField{":status", "302", false}, 8, false, "08 82 6402"},
239
240                 // Never indexed
241                 {HeaderField{":status", "302", true}, 8, true, "18 82 6402"},
242                 {HeaderField{":status", "302", true}, 8, false, "18 82 6402"},
243         }
244         for _, tt := range tests {
245                 want := removeSpace(tt.wantHex)
246                 buf := appendIndexedName(nil, tt.f, tt.i, tt.indexing)
247                 if got := hex.EncodeToString(buf); want != got {
248                         t.Errorf("appendIndexedName(nil, %+v, %v) = %q; want %q", tt.f, tt.indexing, got, want)
249                 }
250         }
251 }
252
253 func TestAppendTableSize(t *testing.T) {
254         tests := []struct {
255                 i       uint32
256                 wantHex string
257         }{
258                 // Fits into 1 byte
259                 {30, "3e"},
260
261                 // Extra byte
262                 {31, "3f00"},
263                 {32, "3f01"},
264         }
265         for _, tt := range tests {
266                 want := removeSpace(tt.wantHex)
267                 buf := appendTableSize(nil, tt.i)
268                 if got := hex.EncodeToString(buf); want != got {
269                         t.Errorf("appendTableSize(nil, %v) = %q; want %q", tt.i, got, want)
270                 }
271         }
272 }
273
274 func TestEncoderSetMaxDynamicTableSize(t *testing.T) {
275         var buf bytes.Buffer
276         e := NewEncoder(&buf)
277         tests := []struct {
278                 v           uint32
279                 wantUpdate  bool
280                 wantMinSize uint32
281                 wantMaxSize uint32
282         }{
283                 // Set new table size to 2048
284                 {2048, true, 2048, 2048},
285
286                 // Set new table size to 16384, but still limited to
287                 // 4096
288                 {16384, true, 2048, 4096},
289         }
290         for _, tt := range tests {
291                 e.SetMaxDynamicTableSize(tt.v)
292                 if got := e.tableSizeUpdate; tt.wantUpdate != got {
293                         t.Errorf("e.tableSizeUpdate = %v; want %v", got, tt.wantUpdate)
294                 }
295                 if got := e.minSize; tt.wantMinSize != got {
296                         t.Errorf("e.minSize = %v; want %v", got, tt.wantMinSize)
297                 }
298                 if got := e.dynTab.maxSize; tt.wantMaxSize != got {
299                         t.Errorf("e.maxSize = %v; want %v", got, tt.wantMaxSize)
300                 }
301         }
302 }
303
304 func TestEncoderSetMaxDynamicTableSizeLimit(t *testing.T) {
305         e := NewEncoder(nil)
306         // 4095 < initialHeaderTableSize means maxSize is truncated to
307         // 4095.
308         e.SetMaxDynamicTableSizeLimit(4095)
309         if got, want := e.dynTab.maxSize, uint32(4095); got != want {
310                 t.Errorf("e.dynTab.maxSize = %v; want %v", got, want)
311         }
312         if got, want := e.maxSizeLimit, uint32(4095); got != want {
313                 t.Errorf("e.maxSizeLimit = %v; want %v", got, want)
314         }
315         if got, want := e.tableSizeUpdate, true; got != want {
316                 t.Errorf("e.tableSizeUpdate = %v; want %v", got, want)
317         }
318         // maxSize will be truncated to maxSizeLimit
319         e.SetMaxDynamicTableSize(16384)
320         if got, want := e.dynTab.maxSize, uint32(4095); got != want {
321                 t.Errorf("e.dynTab.maxSize = %v; want %v", got, want)
322         }
323         // 8192 > current maxSizeLimit, so maxSize does not change.
324         e.SetMaxDynamicTableSizeLimit(8192)
325         if got, want := e.dynTab.maxSize, uint32(4095); got != want {
326                 t.Errorf("e.dynTab.maxSize = %v; want %v", got, want)
327         }
328         if got, want := e.maxSizeLimit, uint32(8192); got != want {
329                 t.Errorf("e.maxSizeLimit = %v; want %v", got, want)
330         }
331 }
332
333 func removeSpace(s string) string {
334         return strings.Replace(s, " ", "", -1)
335 }
336
337 func BenchmarkEncoderSearchTable(b *testing.B) {
338         e := NewEncoder(nil)
339
340         // A sample of possible header fields.
341         // This is not based on any actual data from HTTP/2 traces.
342         var possible []HeaderField
343         for _, f := range staticTable.ents {
344                 if f.Value == "" {
345                         possible = append(possible, f)
346                         continue
347                 }
348                 // Generate 5 random values, except for cookie and set-cookie,
349                 // which we know can have many values in practice.
350                 num := 5
351                 if f.Name == "cookie" || f.Name == "set-cookie" {
352                         num = 25
353                 }
354                 for i := 0; i < num; i++ {
355                         f.Value = fmt.Sprintf("%s-%d", f.Name, i)
356                         possible = append(possible, f)
357                 }
358         }
359         for k := 0; k < 10; k++ {
360                 f := HeaderField{
361                         Name:      fmt.Sprintf("x-header-%d", k),
362                         Sensitive: rand.Int()%2 == 0,
363                 }
364                 for i := 0; i < 5; i++ {
365                         f.Value = fmt.Sprintf("%s-%d", f.Name, i)
366                         possible = append(possible, f)
367                 }
368         }
369
370         // Add a random sample to the dynamic table. This very loosely simulates
371         // a history of 100 requests with 20 header fields per request.
372         for r := 0; r < 100*20; r++ {
373                 f := possible[rand.Int31n(int32(len(possible)))]
374                 // Skip if this is in the staticTable verbatim.
375                 if _, has := staticTable.search(f); !has {
376                         e.dynTab.add(f)
377                 }
378         }
379
380         b.ResetTimer()
381         for n := 0; n < b.N; n++ {
382                 for _, f := range possible {
383                         e.searchTable(f)
384                 }
385         }
386 }