| // Copyright 2014 The Go Authors. All rights reserved. | |
| // Use of this source code is governed by a BSD-style | |
| // license that can be found in the LICENSE file. | |
| package hpack | |
| import ( | |
| "bytes" | |
| "encoding/hex" | |
| "reflect" | |
| "strings" | |
| "testing" | |
| ) | |
| func TestEncoderTableSizeUpdate(t *testing.T) { | |
| tests := []struct { | |
| size1, size2 uint32 | |
| wantHex string | |
| }{ | |
| // Should emit 2 table size updates (2048 and 4096) | |
| {2048, 4096, "3fe10f 3fe11f 82"}, | |
| // Should emit 1 table size update (2048) | |
| {16384, 2048, "3fe10f 82"}, | |
| } | |
| for _, tt := range tests { | |
| var buf bytes.Buffer | |
| e := NewEncoder(&buf) | |
| e.SetMaxDynamicTableSize(tt.size1) | |
| e.SetMaxDynamicTableSize(tt.size2) | |
| if err := e.WriteField(pair(":method", "GET")); err != nil { | |
| t.Fatal(err) | |
| } | |
| want := removeSpace(tt.wantHex) | |
| if got := hex.EncodeToString(buf.Bytes()); got != want { | |
| t.Errorf("e.SetDynamicTableSize %v, %v = %q; want %q", tt.size1, tt.size2, got, want) | |
| } | |
| } | |
| } | |
| func TestEncoderWriteField(t *testing.T) { | |
| var buf bytes.Buffer | |
| e := NewEncoder(&buf) | |
| var got []HeaderField | |
| d := NewDecoder(4<<10, func(f HeaderField) { | |
| got = append(got, f) | |
| }) | |
| tests := []struct { | |
| hdrs []HeaderField | |
| }{ | |
| {[]HeaderField{ | |
| pair(":method", "GET"), | |
| pair(":scheme", "http"), | |
| pair(":path", "/"), | |
| pair(":authority", "www.example.com"), | |
| }}, | |
| {[]HeaderField{ | |
| pair(":method", "GET"), | |
| pair(":scheme", "http"), | |
| pair(":path", "/"), | |
| pair(":authority", "www.example.com"), | |
| pair("cache-control", "no-cache"), | |
| }}, | |
| {[]HeaderField{ | |
| pair(":method", "GET"), | |
| pair(":scheme", "https"), | |
| pair(":path", "/index.html"), | |
| pair(":authority", "www.example.com"), | |
| pair("custom-key", "custom-value"), | |
| }}, | |
| } | |
| for i, tt := range tests { | |
| buf.Reset() | |
| got = got[:0] | |
| for _, hf := range tt.hdrs { | |
| if err := e.WriteField(hf); err != nil { | |
| t.Fatal(err) | |
| } | |
| } | |
| _, err := d.Write(buf.Bytes()) | |
| if err != nil { | |
| t.Errorf("%d. Decoder Write = %v", i, err) | |
| } | |
| if !reflect.DeepEqual(got, tt.hdrs) { | |
| t.Errorf("%d. Decoded %+v; want %+v", i, got, tt.hdrs) | |
| } | |
| } | |
| } | |
| func TestEncoderSearchTable(t *testing.T) { | |
| e := NewEncoder(nil) | |
| e.dynTab.add(pair("foo", "bar")) | |
| e.dynTab.add(pair("blake", "miz")) | |
| e.dynTab.add(pair(":method", "GET")) | |
| tests := []struct { | |
| hf HeaderField | |
| wantI uint64 | |
| wantMatch bool | |
| }{ | |
| // Name and Value match | |
| {pair("foo", "bar"), uint64(len(staticTable) + 3), true}, | |
| {pair("blake", "miz"), uint64(len(staticTable) + 2), true}, | |
| {pair(":method", "GET"), 2, true}, | |
| // Only name match because Sensitive == true | |
| {HeaderField{":method", "GET", true}, 2, false}, | |
| // Only Name matches | |
| {pair("foo", "..."), uint64(len(staticTable) + 3), false}, | |
| {pair("blake", "..."), uint64(len(staticTable) + 2), false}, | |
| {pair(":method", "..."), 2, false}, | |
| // None match | |
| {pair("foo-", "bar"), 0, false}, | |
| } | |
| for _, tt := range tests { | |
| if gotI, gotMatch := e.searchTable(tt.hf); gotI != tt.wantI || gotMatch != tt.wantMatch { | |
| t.Errorf("d.search(%+v) = %v, %v; want %v, %v", tt.hf, gotI, gotMatch, tt.wantI, tt.wantMatch) | |
| } | |
| } | |
| } | |
| func TestAppendVarInt(t *testing.T) { | |
| tests := []struct { | |
| n byte | |
| i uint64 | |
| want []byte | |
| }{ | |
| // Fits in a byte: | |
| {1, 0, []byte{0}}, | |
| {2, 2, []byte{2}}, | |
| {3, 6, []byte{6}}, | |
| {4, 14, []byte{14}}, | |
| {5, 30, []byte{30}}, | |
| {6, 62, []byte{62}}, | |
| {7, 126, []byte{126}}, | |
| {8, 254, []byte{254}}, | |
| // Multiple bytes: | |
| {5, 1337, []byte{31, 154, 10}}, | |
| } | |
| for _, tt := range tests { | |
| got := appendVarInt(nil, tt.n, tt.i) | |
| if !bytes.Equal(got, tt.want) { | |
| t.Errorf("appendVarInt(nil, %v, %v) = %v; want %v", tt.n, tt.i, got, tt.want) | |
| } | |
| } | |
| } | |
| func TestAppendHpackString(t *testing.T) { | |
| tests := []struct { | |
| s, wantHex string | |
| }{ | |
| // Huffman encoded | |
| {"www.example.com", "8c f1e3 c2e5 f23a 6ba0 ab90 f4ff"}, | |
| // Not Huffman encoded | |
| {"a", "01 61"}, | |
| // zero length | |
| {"", "00"}, | |
| } | |
| for _, tt := range tests { | |
| want := removeSpace(tt.wantHex) | |
| buf := appendHpackString(nil, tt.s) | |
| if got := hex.EncodeToString(buf); want != got { | |
| t.Errorf("appendHpackString(nil, %q) = %q; want %q", tt.s, got, want) | |
| } | |
| } | |
| } | |
| func TestAppendIndexed(t *testing.T) { | |
| tests := []struct { | |
| i uint64 | |
| wantHex string | |
| }{ | |
| // 1 byte | |
| {1, "81"}, | |
| {126, "fe"}, | |
| // 2 bytes | |
| {127, "ff00"}, | |
| {128, "ff01"}, | |
| } | |
| for _, tt := range tests { | |
| want := removeSpace(tt.wantHex) | |
| buf := appendIndexed(nil, tt.i) | |
| if got := hex.EncodeToString(buf); want != got { | |
| t.Errorf("appendIndex(nil, %v) = %q; want %q", tt.i, got, want) | |
| } | |
| } | |
| } | |
| func TestAppendNewName(t *testing.T) { | |
| tests := []struct { | |
| f HeaderField | |
| indexing bool | |
| wantHex string | |
| }{ | |
| // Incremental indexing | |
| {HeaderField{"custom-key", "custom-value", false}, true, "40 88 25a8 49e9 5ba9 7d7f 89 25a8 49e9 5bb8 e8b4 bf"}, | |
| // Without indexing | |
| {HeaderField{"custom-key", "custom-value", false}, false, "00 88 25a8 49e9 5ba9 7d7f 89 25a8 49e9 5bb8 e8b4 bf"}, | |
| // Never indexed | |
| {HeaderField{"custom-key", "custom-value", true}, true, "10 88 25a8 49e9 5ba9 7d7f 89 25a8 49e9 5bb8 e8b4 bf"}, | |
| {HeaderField{"custom-key", "custom-value", true}, false, "10 88 25a8 49e9 5ba9 7d7f 89 25a8 49e9 5bb8 e8b4 bf"}, | |
| } | |
| for _, tt := range tests { | |
| want := removeSpace(tt.wantHex) | |
| buf := appendNewName(nil, tt.f, tt.indexing) | |
| if got := hex.EncodeToString(buf); want != got { | |
| t.Errorf("appendNewName(nil, %+v, %v) = %q; want %q", tt.f, tt.indexing, got, want) | |
| } | |
| } | |
| } | |
| func TestAppendIndexedName(t *testing.T) { | |
| tests := []struct { | |
| f HeaderField | |
| i uint64 | |
| indexing bool | |
| wantHex string | |
| }{ | |
| // Incremental indexing | |
| {HeaderField{":status", "302", false}, 8, true, "48 82 6402"}, | |
| // Without indexing | |
| {HeaderField{":status", "302", false}, 8, false, "08 82 6402"}, | |
| // Never indexed | |
| {HeaderField{":status", "302", true}, 8, true, "18 82 6402"}, | |
| {HeaderField{":status", "302", true}, 8, false, "18 82 6402"}, | |
| } | |
| for _, tt := range tests { | |
| want := removeSpace(tt.wantHex) | |
| buf := appendIndexedName(nil, tt.f, tt.i, tt.indexing) | |
| if got := hex.EncodeToString(buf); want != got { | |
| t.Errorf("appendIndexedName(nil, %+v, %v) = %q; want %q", tt.f, tt.indexing, got, want) | |
| } | |
| } | |
| } | |
| func TestAppendTableSize(t *testing.T) { | |
| tests := []struct { | |
| i uint32 | |
| wantHex string | |
| }{ | |
| // Fits into 1 byte | |
| {30, "3e"}, | |
| // Extra byte | |
| {31, "3f00"}, | |
| {32, "3f01"}, | |
| } | |
| for _, tt := range tests { | |
| want := removeSpace(tt.wantHex) | |
| buf := appendTableSize(nil, tt.i) | |
| if got := hex.EncodeToString(buf); want != got { | |
| t.Errorf("appendTableSize(nil, %v) = %q; want %q", tt.i, got, want) | |
| } | |
| } | |
| } | |
| func TestEncoderSetMaxDynamicTableSize(t *testing.T) { | |
| var buf bytes.Buffer | |
| e := NewEncoder(&buf) | |
| tests := []struct { | |
| v uint32 | |
| wantUpdate bool | |
| wantMinSize uint32 | |
| wantMaxSize uint32 | |
| }{ | |
| // Set new table size to 2048 | |
| {2048, true, 2048, 2048}, | |
| // Set new table size to 16384, but still limited to | |
| // 4096 | |
| {16384, true, 2048, 4096}, | |
| } | |
| for _, tt := range tests { | |
| e.SetMaxDynamicTableSize(tt.v) | |
| if got := e.tableSizeUpdate; tt.wantUpdate != got { | |
| t.Errorf("e.tableSizeUpdate = %v; want %v", got, tt.wantUpdate) | |
| } | |
| if got := e.minSize; tt.wantMinSize != got { | |
| t.Errorf("e.minSize = %v; want %v", got, tt.wantMinSize) | |
| } | |
| if got := e.dynTab.maxSize; tt.wantMaxSize != got { | |
| t.Errorf("e.maxSize = %v; want %v", got, tt.wantMaxSize) | |
| } | |
| } | |
| } | |
| func TestEncoderSetMaxDynamicTableSizeLimit(t *testing.T) { | |
| e := NewEncoder(nil) | |
| // 4095 < initialHeaderTableSize means maxSize is truncated to | |
| // 4095. | |
| e.SetMaxDynamicTableSizeLimit(4095) | |
| if got, want := e.dynTab.maxSize, uint32(4095); got != want { | |
| t.Errorf("e.dynTab.maxSize = %v; want %v", got, want) | |
| } | |
| if got, want := e.maxSizeLimit, uint32(4095); got != want { | |
| t.Errorf("e.maxSizeLimit = %v; want %v", got, want) | |
| } | |
| if got, want := e.tableSizeUpdate, true; got != want { | |
| t.Errorf("e.tableSizeUpdate = %v; want %v", got, want) | |
| } | |
| // maxSize will be truncated to maxSizeLimit | |
| e.SetMaxDynamicTableSize(16384) | |
| if got, want := e.dynTab.maxSize, uint32(4095); got != want { | |
| t.Errorf("e.dynTab.maxSize = %v; want %v", got, want) | |
| } | |
| // 8192 > current maxSizeLimit, so maxSize does not change. | |
| e.SetMaxDynamicTableSizeLimit(8192) | |
| if got, want := e.dynTab.maxSize, uint32(4095); got != want { | |
| t.Errorf("e.dynTab.maxSize = %v; want %v", got, want) | |
| } | |
| if got, want := e.maxSizeLimit, uint32(8192); got != want { | |
| t.Errorf("e.maxSizeLimit = %v; want %v", got, want) | |
| } | |
| } | |
| func removeSpace(s string) string { | |
| return strings.Replace(s, " ", "", -1) | |
| } |