mirror of
https://github.com/chai2010/advanced-go-programming-book.git
synced 2025-05-24 20:52:22 +00:00
742 lines
22 KiB
Go
742 lines
22 KiB
Go
// 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"
|
|
"fmt"
|
|
"math/rand"
|
|
"reflect"
|
|
"strings"
|
|
"testing"
|
|
"time"
|
|
)
|
|
|
|
func (d *Decoder) mustAt(idx int) HeaderField {
|
|
if hf, ok := d.at(uint64(idx)); !ok {
|
|
panic(fmt.Sprintf("bogus index %d", idx))
|
|
} else {
|
|
return hf
|
|
}
|
|
}
|
|
|
|
func TestDynamicTableAt(t *testing.T) {
|
|
d := NewDecoder(4096, nil)
|
|
at := d.mustAt
|
|
if got, want := at(2), (pair(":method", "GET")); got != want {
|
|
t.Errorf("at(2) = %v; want %v", got, want)
|
|
}
|
|
d.dynTab.add(pair("foo", "bar"))
|
|
d.dynTab.add(pair("blake", "miz"))
|
|
if got, want := at(staticTable.len()+1), (pair("blake", "miz")); got != want {
|
|
t.Errorf("at(dyn 1) = %v; want %v", got, want)
|
|
}
|
|
if got, want := at(staticTable.len()+2), (pair("foo", "bar")); got != want {
|
|
t.Errorf("at(dyn 2) = %v; want %v", got, want)
|
|
}
|
|
if got, want := at(3), (pair(":method", "POST")); got != want {
|
|
t.Errorf("at(3) = %v; want %v", got, want)
|
|
}
|
|
}
|
|
|
|
func TestDynamicTableSizeEvict(t *testing.T) {
|
|
d := NewDecoder(4096, nil)
|
|
if want := uint32(0); d.dynTab.size != want {
|
|
t.Fatalf("size = %d; want %d", d.dynTab.size, want)
|
|
}
|
|
add := d.dynTab.add
|
|
add(pair("blake", "eats pizza"))
|
|
if want := uint32(15 + 32); d.dynTab.size != want {
|
|
t.Fatalf("after pizza, size = %d; want %d", d.dynTab.size, want)
|
|
}
|
|
add(pair("foo", "bar"))
|
|
if want := uint32(15 + 32 + 6 + 32); d.dynTab.size != want {
|
|
t.Fatalf("after foo bar, size = %d; want %d", d.dynTab.size, want)
|
|
}
|
|
d.dynTab.setMaxSize(15 + 32 + 1 /* slop */)
|
|
if want := uint32(6 + 32); d.dynTab.size != want {
|
|
t.Fatalf("after setMaxSize, size = %d; want %d", d.dynTab.size, want)
|
|
}
|
|
if got, want := d.mustAt(staticTable.len()+1), (pair("foo", "bar")); got != want {
|
|
t.Errorf("at(dyn 1) = %v; want %v", got, want)
|
|
}
|
|
add(pair("long", strings.Repeat("x", 500)))
|
|
if want := uint32(0); d.dynTab.size != want {
|
|
t.Fatalf("after big one, size = %d; want %d", d.dynTab.size, want)
|
|
}
|
|
}
|
|
|
|
func TestDecoderDecode(t *testing.T) {
|
|
tests := []struct {
|
|
name string
|
|
in []byte
|
|
want []HeaderField
|
|
wantDynTab []HeaderField // newest entry first
|
|
}{
|
|
// C.2.1 Literal Header Field with Indexing
|
|
// http://http2.github.io/http2-spec/compression.html#rfc.section.C.2.1
|
|
{"C.2.1", dehex("400a 6375 7374 6f6d 2d6b 6579 0d63 7573 746f 6d2d 6865 6164 6572"),
|
|
[]HeaderField{pair("custom-key", "custom-header")},
|
|
[]HeaderField{pair("custom-key", "custom-header")},
|
|
},
|
|
|
|
// C.2.2 Literal Header Field without Indexing
|
|
// http://http2.github.io/http2-spec/compression.html#rfc.section.C.2.2
|
|
{"C.2.2", dehex("040c 2f73 616d 706c 652f 7061 7468"),
|
|
[]HeaderField{pair(":path", "/sample/path")},
|
|
[]HeaderField{}},
|
|
|
|
// C.2.3 Literal Header Field never Indexed
|
|
// http://http2.github.io/http2-spec/compression.html#rfc.section.C.2.3
|
|
{"C.2.3", dehex("1008 7061 7373 776f 7264 0673 6563 7265 74"),
|
|
[]HeaderField{{"password", "secret", true}},
|
|
[]HeaderField{}},
|
|
|
|
// C.2.4 Indexed Header Field
|
|
// http://http2.github.io/http2-spec/compression.html#rfc.section.C.2.4
|
|
{"C.2.4", []byte("\x82"),
|
|
[]HeaderField{pair(":method", "GET")},
|
|
[]HeaderField{}},
|
|
}
|
|
for _, tt := range tests {
|
|
d := NewDecoder(4096, nil)
|
|
hf, err := d.DecodeFull(tt.in)
|
|
if err != nil {
|
|
t.Errorf("%s: %v", tt.name, err)
|
|
continue
|
|
}
|
|
if !reflect.DeepEqual(hf, tt.want) {
|
|
t.Errorf("%s: Got %v; want %v", tt.name, hf, tt.want)
|
|
}
|
|
gotDynTab := d.dynTab.reverseCopy()
|
|
if !reflect.DeepEqual(gotDynTab, tt.wantDynTab) {
|
|
t.Errorf("%s: dynamic table after = %v; want %v", tt.name, gotDynTab, tt.wantDynTab)
|
|
}
|
|
}
|
|
}
|
|
|
|
func (dt *dynamicTable) reverseCopy() (hf []HeaderField) {
|
|
hf = make([]HeaderField, len(dt.table.ents))
|
|
for i := range hf {
|
|
hf[i] = dt.table.ents[len(dt.table.ents)-1-i]
|
|
}
|
|
return
|
|
}
|
|
|
|
type encAndWant struct {
|
|
enc []byte
|
|
want []HeaderField
|
|
wantDynTab []HeaderField
|
|
wantDynSize uint32
|
|
}
|
|
|
|
// C.3 Request Examples without Huffman Coding
|
|
// http://http2.github.io/http2-spec/compression.html#rfc.section.C.3
|
|
func TestDecodeC3_NoHuffman(t *testing.T) {
|
|
testDecodeSeries(t, 4096, []encAndWant{
|
|
{dehex("8286 8441 0f77 7777 2e65 7861 6d70 6c65 2e63 6f6d"),
|
|
[]HeaderField{
|
|
pair(":method", "GET"),
|
|
pair(":scheme", "http"),
|
|
pair(":path", "/"),
|
|
pair(":authority", "www.example.com"),
|
|
},
|
|
[]HeaderField{
|
|
pair(":authority", "www.example.com"),
|
|
},
|
|
57,
|
|
},
|
|
{dehex("8286 84be 5808 6e6f 2d63 6163 6865"),
|
|
[]HeaderField{
|
|
pair(":method", "GET"),
|
|
pair(":scheme", "http"),
|
|
pair(":path", "/"),
|
|
pair(":authority", "www.example.com"),
|
|
pair("cache-control", "no-cache"),
|
|
},
|
|
[]HeaderField{
|
|
pair("cache-control", "no-cache"),
|
|
pair(":authority", "www.example.com"),
|
|
},
|
|
110,
|
|
},
|
|
{dehex("8287 85bf 400a 6375 7374 6f6d 2d6b 6579 0c63 7573 746f 6d2d 7661 6c75 65"),
|
|
[]HeaderField{
|
|
pair(":method", "GET"),
|
|
pair(":scheme", "https"),
|
|
pair(":path", "/index.html"),
|
|
pair(":authority", "www.example.com"),
|
|
pair("custom-key", "custom-value"),
|
|
},
|
|
[]HeaderField{
|
|
pair("custom-key", "custom-value"),
|
|
pair("cache-control", "no-cache"),
|
|
pair(":authority", "www.example.com"),
|
|
},
|
|
164,
|
|
},
|
|
})
|
|
}
|
|
|
|
// C.4 Request Examples with Huffman Coding
|
|
// http://http2.github.io/http2-spec/compression.html#rfc.section.C.4
|
|
func TestDecodeC4_Huffman(t *testing.T) {
|
|
testDecodeSeries(t, 4096, []encAndWant{
|
|
{dehex("8286 8441 8cf1 e3c2 e5f2 3a6b a0ab 90f4 ff"),
|
|
[]HeaderField{
|
|
pair(":method", "GET"),
|
|
pair(":scheme", "http"),
|
|
pair(":path", "/"),
|
|
pair(":authority", "www.example.com"),
|
|
},
|
|
[]HeaderField{
|
|
pair(":authority", "www.example.com"),
|
|
},
|
|
57,
|
|
},
|
|
{dehex("8286 84be 5886 a8eb 1064 9cbf"),
|
|
[]HeaderField{
|
|
pair(":method", "GET"),
|
|
pair(":scheme", "http"),
|
|
pair(":path", "/"),
|
|
pair(":authority", "www.example.com"),
|
|
pair("cache-control", "no-cache"),
|
|
},
|
|
[]HeaderField{
|
|
pair("cache-control", "no-cache"),
|
|
pair(":authority", "www.example.com"),
|
|
},
|
|
110,
|
|
},
|
|
{dehex("8287 85bf 4088 25a8 49e9 5ba9 7d7f 8925 a849 e95b b8e8 b4bf"),
|
|
[]HeaderField{
|
|
pair(":method", "GET"),
|
|
pair(":scheme", "https"),
|
|
pair(":path", "/index.html"),
|
|
pair(":authority", "www.example.com"),
|
|
pair("custom-key", "custom-value"),
|
|
},
|
|
[]HeaderField{
|
|
pair("custom-key", "custom-value"),
|
|
pair("cache-control", "no-cache"),
|
|
pair(":authority", "www.example.com"),
|
|
},
|
|
164,
|
|
},
|
|
})
|
|
}
|
|
|
|
// http://http2.github.io/http2-spec/compression.html#rfc.section.C.5
|
|
// "This section shows several consecutive header lists, corresponding
|
|
// to HTTP responses, on the same connection. The HTTP/2 setting
|
|
// parameter SETTINGS_HEADER_TABLE_SIZE is set to the value of 256
|
|
// octets, causing some evictions to occur."
|
|
func TestDecodeC5_ResponsesNoHuff(t *testing.T) {
|
|
testDecodeSeries(t, 256, []encAndWant{
|
|
{dehex(`
|
|
4803 3330 3258 0770 7269 7661 7465 611d
|
|
4d6f 6e2c 2032 3120 4f63 7420 3230 3133
|
|
2032 303a 3133 3a32 3120 474d 546e 1768
|
|
7474 7073 3a2f 2f77 7777 2e65 7861 6d70
|
|
6c65 2e63 6f6d
|
|
`),
|
|
[]HeaderField{
|
|
pair(":status", "302"),
|
|
pair("cache-control", "private"),
|
|
pair("date", "Mon, 21 Oct 2013 20:13:21 GMT"),
|
|
pair("location", "https://www.example.com"),
|
|
},
|
|
[]HeaderField{
|
|
pair("location", "https://www.example.com"),
|
|
pair("date", "Mon, 21 Oct 2013 20:13:21 GMT"),
|
|
pair("cache-control", "private"),
|
|
pair(":status", "302"),
|
|
},
|
|
222,
|
|
},
|
|
{dehex("4803 3330 37c1 c0bf"),
|
|
[]HeaderField{
|
|
pair(":status", "307"),
|
|
pair("cache-control", "private"),
|
|
pair("date", "Mon, 21 Oct 2013 20:13:21 GMT"),
|
|
pair("location", "https://www.example.com"),
|
|
},
|
|
[]HeaderField{
|
|
pair(":status", "307"),
|
|
pair("location", "https://www.example.com"),
|
|
pair("date", "Mon, 21 Oct 2013 20:13:21 GMT"),
|
|
pair("cache-control", "private"),
|
|
},
|
|
222,
|
|
},
|
|
{dehex(`
|
|
88c1 611d 4d6f 6e2c 2032 3120 4f63 7420
|
|
3230 3133 2032 303a 3133 3a32 3220 474d
|
|
54c0 5a04 677a 6970 7738 666f 6f3d 4153
|
|
444a 4b48 514b 425a 584f 5157 454f 5049
|
|
5541 5851 5745 4f49 553b 206d 6178 2d61
|
|
6765 3d33 3630 303b 2076 6572 7369 6f6e
|
|
3d31
|
|
`),
|
|
[]HeaderField{
|
|
pair(":status", "200"),
|
|
pair("cache-control", "private"),
|
|
pair("date", "Mon, 21 Oct 2013 20:13:22 GMT"),
|
|
pair("location", "https://www.example.com"),
|
|
pair("content-encoding", "gzip"),
|
|
pair("set-cookie", "foo=ASDJKHQKBZXOQWEOPIUAXQWEOIU; max-age=3600; version=1"),
|
|
},
|
|
[]HeaderField{
|
|
pair("set-cookie", "foo=ASDJKHQKBZXOQWEOPIUAXQWEOIU; max-age=3600; version=1"),
|
|
pair("content-encoding", "gzip"),
|
|
pair("date", "Mon, 21 Oct 2013 20:13:22 GMT"),
|
|
},
|
|
215,
|
|
},
|
|
})
|
|
}
|
|
|
|
// http://http2.github.io/http2-spec/compression.html#rfc.section.C.6
|
|
// "This section shows the same examples as the previous section, but
|
|
// using Huffman encoding for the literal values. The HTTP/2 setting
|
|
// parameter SETTINGS_HEADER_TABLE_SIZE is set to the value of 256
|
|
// octets, causing some evictions to occur. The eviction mechanism
|
|
// uses the length of the decoded literal values, so the same
|
|
// evictions occurs as in the previous section."
|
|
func TestDecodeC6_ResponsesHuffman(t *testing.T) {
|
|
testDecodeSeries(t, 256, []encAndWant{
|
|
{dehex(`
|
|
4882 6402 5885 aec3 771a 4b61 96d0 7abe
|
|
9410 54d4 44a8 2005 9504 0b81 66e0 82a6
|
|
2d1b ff6e 919d 29ad 1718 63c7 8f0b 97c8
|
|
e9ae 82ae 43d3
|
|
`),
|
|
[]HeaderField{
|
|
pair(":status", "302"),
|
|
pair("cache-control", "private"),
|
|
pair("date", "Mon, 21 Oct 2013 20:13:21 GMT"),
|
|
pair("location", "https://www.example.com"),
|
|
},
|
|
[]HeaderField{
|
|
pair("location", "https://www.example.com"),
|
|
pair("date", "Mon, 21 Oct 2013 20:13:21 GMT"),
|
|
pair("cache-control", "private"),
|
|
pair(":status", "302"),
|
|
},
|
|
222,
|
|
},
|
|
{dehex("4883 640e ffc1 c0bf"),
|
|
[]HeaderField{
|
|
pair(":status", "307"),
|
|
pair("cache-control", "private"),
|
|
pair("date", "Mon, 21 Oct 2013 20:13:21 GMT"),
|
|
pair("location", "https://www.example.com"),
|
|
},
|
|
[]HeaderField{
|
|
pair(":status", "307"),
|
|
pair("location", "https://www.example.com"),
|
|
pair("date", "Mon, 21 Oct 2013 20:13:21 GMT"),
|
|
pair("cache-control", "private"),
|
|
},
|
|
222,
|
|
},
|
|
{dehex(`
|
|
88c1 6196 d07a be94 1054 d444 a820 0595
|
|
040b 8166 e084 a62d 1bff c05a 839b d9ab
|
|
77ad 94e7 821d d7f2 e6c7 b335 dfdf cd5b
|
|
3960 d5af 2708 7f36 72c1 ab27 0fb5 291f
|
|
9587 3160 65c0 03ed 4ee5 b106 3d50 07
|
|
`),
|
|
[]HeaderField{
|
|
pair(":status", "200"),
|
|
pair("cache-control", "private"),
|
|
pair("date", "Mon, 21 Oct 2013 20:13:22 GMT"),
|
|
pair("location", "https://www.example.com"),
|
|
pair("content-encoding", "gzip"),
|
|
pair("set-cookie", "foo=ASDJKHQKBZXOQWEOPIUAXQWEOIU; max-age=3600; version=1"),
|
|
},
|
|
[]HeaderField{
|
|
pair("set-cookie", "foo=ASDJKHQKBZXOQWEOPIUAXQWEOIU; max-age=3600; version=1"),
|
|
pair("content-encoding", "gzip"),
|
|
pair("date", "Mon, 21 Oct 2013 20:13:22 GMT"),
|
|
},
|
|
215,
|
|
},
|
|
})
|
|
}
|
|
|
|
func testDecodeSeries(t *testing.T, size uint32, steps []encAndWant) {
|
|
d := NewDecoder(size, nil)
|
|
for i, step := range steps {
|
|
hf, err := d.DecodeFull(step.enc)
|
|
if err != nil {
|
|
t.Fatalf("Error at step index %d: %v", i, err)
|
|
}
|
|
if !reflect.DeepEqual(hf, step.want) {
|
|
t.Fatalf("At step index %d: Got headers %v; want %v", i, hf, step.want)
|
|
}
|
|
gotDynTab := d.dynTab.reverseCopy()
|
|
if !reflect.DeepEqual(gotDynTab, step.wantDynTab) {
|
|
t.Errorf("After step index %d, dynamic table = %v; want %v", i, gotDynTab, step.wantDynTab)
|
|
}
|
|
if d.dynTab.size != step.wantDynSize {
|
|
t.Errorf("After step index %d, dynamic table size = %v; want %v", i, d.dynTab.size, step.wantDynSize)
|
|
}
|
|
}
|
|
}
|
|
|
|
func TestHuffmanDecodeExcessPadding(t *testing.T) {
|
|
tests := [][]byte{
|
|
{0xff}, // Padding Exceeds 7 bits
|
|
{0x1f, 0xff}, // {"a", 1 byte excess padding}
|
|
{0x1f, 0xff, 0xff}, // {"a", 2 byte excess padding}
|
|
{0x1f, 0xff, 0xff, 0xff}, // {"a", 3 byte excess padding}
|
|
{0xff, 0x9f, 0xff, 0xff, 0xff}, // {"a", 29 bit excess padding}
|
|
{'R', 0xbc, '0', 0xff, 0xff, 0xff, 0xff}, // Padding ends on partial symbol.
|
|
}
|
|
for i, in := range tests {
|
|
var buf bytes.Buffer
|
|
if _, err := HuffmanDecode(&buf, in); err != ErrInvalidHuffman {
|
|
t.Errorf("test-%d: decode(%q) = %v; want ErrInvalidHuffman", i, in, err)
|
|
}
|
|
}
|
|
}
|
|
|
|
func TestHuffmanDecodeEOS(t *testing.T) {
|
|
in := []byte{0xff, 0xff, 0xff, 0xff, 0xfc} // {EOS, "?"}
|
|
var buf bytes.Buffer
|
|
if _, err := HuffmanDecode(&buf, in); err != ErrInvalidHuffman {
|
|
t.Errorf("error = %v; want ErrInvalidHuffman", err)
|
|
}
|
|
}
|
|
|
|
func TestHuffmanDecodeMaxLengthOnTrailingByte(t *testing.T) {
|
|
in := []byte{0x00, 0x01} // {"0", "0", "0"}
|
|
var buf bytes.Buffer
|
|
if err := huffmanDecode(&buf, 2, in); err != ErrStringLength {
|
|
t.Errorf("error = %v; want ErrStringLength", err)
|
|
}
|
|
}
|
|
|
|
func TestHuffmanDecodeCorruptPadding(t *testing.T) {
|
|
in := []byte{0x00}
|
|
var buf bytes.Buffer
|
|
if _, err := HuffmanDecode(&buf, in); err != ErrInvalidHuffman {
|
|
t.Errorf("error = %v; want ErrInvalidHuffman", err)
|
|
}
|
|
}
|
|
|
|
func TestHuffmanDecode(t *testing.T) {
|
|
tests := []struct {
|
|
inHex, want string
|
|
}{
|
|
{"f1e3 c2e5 f23a 6ba0 ab90 f4ff", "www.example.com"},
|
|
{"a8eb 1064 9cbf", "no-cache"},
|
|
{"25a8 49e9 5ba9 7d7f", "custom-key"},
|
|
{"25a8 49e9 5bb8 e8b4 bf", "custom-value"},
|
|
{"6402", "302"},
|
|
{"aec3 771a 4b", "private"},
|
|
{"d07a be94 1054 d444 a820 0595 040b 8166 e082 a62d 1bff", "Mon, 21 Oct 2013 20:13:21 GMT"},
|
|
{"9d29 ad17 1863 c78f 0b97 c8e9 ae82 ae43 d3", "https://www.example.com"},
|
|
{"9bd9 ab", "gzip"},
|
|
{"94e7 821d d7f2 e6c7 b335 dfdf cd5b 3960 d5af 2708 7f36 72c1 ab27 0fb5 291f 9587 3160 65c0 03ed 4ee5 b106 3d50 07",
|
|
"foo=ASDJKHQKBZXOQWEOPIUAXQWEOIU; max-age=3600; version=1"},
|
|
}
|
|
for i, tt := range tests {
|
|
var buf bytes.Buffer
|
|
in, err := hex.DecodeString(strings.Replace(tt.inHex, " ", "", -1))
|
|
if err != nil {
|
|
t.Errorf("%d. hex input error: %v", i, err)
|
|
continue
|
|
}
|
|
if _, err := HuffmanDecode(&buf, in); err != nil {
|
|
t.Errorf("%d. decode error: %v", i, err)
|
|
continue
|
|
}
|
|
if got := buf.String(); tt.want != got {
|
|
t.Errorf("%d. decode = %q; want %q", i, got, tt.want)
|
|
}
|
|
}
|
|
}
|
|
|
|
func TestAppendHuffmanString(t *testing.T) {
|
|
tests := []struct {
|
|
in, want string
|
|
}{
|
|
{"www.example.com", "f1e3 c2e5 f23a 6ba0 ab90 f4ff"},
|
|
{"no-cache", "a8eb 1064 9cbf"},
|
|
{"custom-key", "25a8 49e9 5ba9 7d7f"},
|
|
{"custom-value", "25a8 49e9 5bb8 e8b4 bf"},
|
|
{"302", "6402"},
|
|
{"private", "aec3 771a 4b"},
|
|
{"Mon, 21 Oct 2013 20:13:21 GMT", "d07a be94 1054 d444 a820 0595 040b 8166 e082 a62d 1bff"},
|
|
{"https://www.example.com", "9d29 ad17 1863 c78f 0b97 c8e9 ae82 ae43 d3"},
|
|
{"gzip", "9bd9 ab"},
|
|
{"foo=ASDJKHQKBZXOQWEOPIUAXQWEOIU; max-age=3600; version=1",
|
|
"94e7 821d d7f2 e6c7 b335 dfdf cd5b 3960 d5af 2708 7f36 72c1 ab27 0fb5 291f 9587 3160 65c0 03ed 4ee5 b106 3d50 07"},
|
|
}
|
|
for i, tt := range tests {
|
|
buf := []byte{}
|
|
want := strings.Replace(tt.want, " ", "", -1)
|
|
buf = AppendHuffmanString(buf, tt.in)
|
|
if got := hex.EncodeToString(buf); want != got {
|
|
t.Errorf("%d. encode = %q; want %q", i, got, want)
|
|
}
|
|
}
|
|
}
|
|
|
|
func TestHuffmanMaxStrLen(t *testing.T) {
|
|
const msg = "Some string"
|
|
huff := AppendHuffmanString(nil, msg)
|
|
|
|
testGood := func(max int) {
|
|
var out bytes.Buffer
|
|
if err := huffmanDecode(&out, max, huff); err != nil {
|
|
t.Errorf("For maxLen=%d, unexpected error: %v", max, err)
|
|
}
|
|
if out.String() != msg {
|
|
t.Errorf("For maxLen=%d, out = %q; want %q", max, out.String(), msg)
|
|
}
|
|
}
|
|
testGood(0)
|
|
testGood(len(msg))
|
|
testGood(len(msg) + 1)
|
|
|
|
var out bytes.Buffer
|
|
if err := huffmanDecode(&out, len(msg)-1, huff); err != ErrStringLength {
|
|
t.Errorf("err = %v; want ErrStringLength", err)
|
|
}
|
|
}
|
|
|
|
func TestHuffmanRoundtripStress(t *testing.T) {
|
|
const Len = 50 // of uncompressed string
|
|
input := make([]byte, Len)
|
|
var output bytes.Buffer
|
|
var huff []byte
|
|
|
|
n := 5000
|
|
if testing.Short() {
|
|
n = 100
|
|
}
|
|
seed := time.Now().UnixNano()
|
|
t.Logf("Seed = %v", seed)
|
|
src := rand.New(rand.NewSource(seed))
|
|
var encSize int64
|
|
for i := 0; i < n; i++ {
|
|
for l := range input {
|
|
input[l] = byte(src.Intn(256))
|
|
}
|
|
huff = AppendHuffmanString(huff[:0], string(input))
|
|
encSize += int64(len(huff))
|
|
output.Reset()
|
|
if err := huffmanDecode(&output, 0, huff); err != nil {
|
|
t.Errorf("Failed to decode %q -> %q -> error %v", input, huff, err)
|
|
continue
|
|
}
|
|
if !bytes.Equal(output.Bytes(), input) {
|
|
t.Errorf("Roundtrip failure on %q -> %q -> %q", input, huff, output.Bytes())
|
|
}
|
|
}
|
|
t.Logf("Compressed size of original: %0.02f%% (%v -> %v)", 100*(float64(encSize)/(Len*float64(n))), Len*n, encSize)
|
|
}
|
|
|
|
func TestHuffmanDecodeFuzz(t *testing.T) {
|
|
const Len = 50 // of compressed
|
|
var buf, zbuf bytes.Buffer
|
|
|
|
n := 5000
|
|
if testing.Short() {
|
|
n = 100
|
|
}
|
|
seed := time.Now().UnixNano()
|
|
t.Logf("Seed = %v", seed)
|
|
src := rand.New(rand.NewSource(seed))
|
|
numFail := 0
|
|
for i := 0; i < n; i++ {
|
|
zbuf.Reset()
|
|
if i == 0 {
|
|
// Start with at least one invalid one.
|
|
zbuf.WriteString("00\x91\xff\xff\xff\xff\xc8")
|
|
} else {
|
|
for l := 0; l < Len; l++ {
|
|
zbuf.WriteByte(byte(src.Intn(256)))
|
|
}
|
|
}
|
|
|
|
buf.Reset()
|
|
if err := huffmanDecode(&buf, 0, zbuf.Bytes()); err != nil {
|
|
if err == ErrInvalidHuffman {
|
|
numFail++
|
|
continue
|
|
}
|
|
t.Errorf("Failed to decode %q: %v", zbuf.Bytes(), err)
|
|
continue
|
|
}
|
|
}
|
|
t.Logf("%0.02f%% are invalid (%d / %d)", 100*float64(numFail)/float64(n), numFail, n)
|
|
if numFail < 1 {
|
|
t.Error("expected at least one invalid huffman encoding (test starts with one)")
|
|
}
|
|
}
|
|
|
|
func TestReadVarInt(t *testing.T) {
|
|
type res struct {
|
|
i uint64
|
|
consumed int
|
|
err error
|
|
}
|
|
tests := []struct {
|
|
n byte
|
|
p []byte
|
|
want res
|
|
}{
|
|
// Fits in a byte:
|
|
{1, []byte{0}, res{0, 1, nil}},
|
|
{2, []byte{2}, res{2, 1, nil}},
|
|
{3, []byte{6}, res{6, 1, nil}},
|
|
{4, []byte{14}, res{14, 1, nil}},
|
|
{5, []byte{30}, res{30, 1, nil}},
|
|
{6, []byte{62}, res{62, 1, nil}},
|
|
{7, []byte{126}, res{126, 1, nil}},
|
|
{8, []byte{254}, res{254, 1, nil}},
|
|
|
|
// Doesn't fit in a byte:
|
|
{1, []byte{1}, res{0, 0, errNeedMore}},
|
|
{2, []byte{3}, res{0, 0, errNeedMore}},
|
|
{3, []byte{7}, res{0, 0, errNeedMore}},
|
|
{4, []byte{15}, res{0, 0, errNeedMore}},
|
|
{5, []byte{31}, res{0, 0, errNeedMore}},
|
|
{6, []byte{63}, res{0, 0, errNeedMore}},
|
|
{7, []byte{127}, res{0, 0, errNeedMore}},
|
|
{8, []byte{255}, res{0, 0, errNeedMore}},
|
|
|
|
// Ignoring top bits:
|
|
{5, []byte{255, 154, 10}, res{1337, 3, nil}}, // high dummy three bits: 111
|
|
{5, []byte{159, 154, 10}, res{1337, 3, nil}}, // high dummy three bits: 100
|
|
{5, []byte{191, 154, 10}, res{1337, 3, nil}}, // high dummy three bits: 101
|
|
|
|
// Extra byte:
|
|
{5, []byte{191, 154, 10, 2}, res{1337, 3, nil}}, // extra byte
|
|
|
|
// Short a byte:
|
|
{5, []byte{191, 154}, res{0, 0, errNeedMore}},
|
|
|
|
// integer overflow:
|
|
{1, []byte{255, 128, 128, 128, 128, 128, 128, 128, 128, 128, 128, 128, 128, 128, 128, 128}, res{0, 0, errVarintOverflow}},
|
|
}
|
|
for _, tt := range tests {
|
|
i, remain, err := readVarInt(tt.n, tt.p)
|
|
consumed := len(tt.p) - len(remain)
|
|
got := res{i, consumed, err}
|
|
if got != tt.want {
|
|
t.Errorf("readVarInt(%d, %v ~ %x) = %+v; want %+v", tt.n, tt.p, tt.p, got, tt.want)
|
|
}
|
|
}
|
|
}
|
|
|
|
// Fuzz crash, originally reported at https://github.com/bradfitz/http2/issues/56
|
|
func TestHuffmanFuzzCrash(t *testing.T) {
|
|
got, err := HuffmanDecodeToString([]byte("00\x91\xff\xff\xff\xff\xc8"))
|
|
if got != "" {
|
|
t.Errorf("Got %q; want empty string", got)
|
|
}
|
|
if err != ErrInvalidHuffman {
|
|
t.Errorf("Err = %v; want ErrInvalidHuffman", err)
|
|
}
|
|
}
|
|
|
|
func pair(name, value string) HeaderField {
|
|
return HeaderField{Name: name, Value: value}
|
|
}
|
|
|
|
func dehex(s string) []byte {
|
|
s = strings.Replace(s, " ", "", -1)
|
|
s = strings.Replace(s, "\n", "", -1)
|
|
b, err := hex.DecodeString(s)
|
|
if err != nil {
|
|
panic(err)
|
|
}
|
|
return b
|
|
}
|
|
|
|
func TestEmitEnabled(t *testing.T) {
|
|
var buf bytes.Buffer
|
|
enc := NewEncoder(&buf)
|
|
enc.WriteField(HeaderField{Name: "foo", Value: "bar"})
|
|
enc.WriteField(HeaderField{Name: "foo", Value: "bar"})
|
|
|
|
numCallback := 0
|
|
var dec *Decoder
|
|
dec = NewDecoder(8<<20, func(HeaderField) {
|
|
numCallback++
|
|
dec.SetEmitEnabled(false)
|
|
})
|
|
if !dec.EmitEnabled() {
|
|
t.Errorf("initial emit enabled = false; want true")
|
|
}
|
|
if _, err := dec.Write(buf.Bytes()); err != nil {
|
|
t.Error(err)
|
|
}
|
|
if numCallback != 1 {
|
|
t.Errorf("num callbacks = %d; want 1", numCallback)
|
|
}
|
|
if dec.EmitEnabled() {
|
|
t.Errorf("emit enabled = true; want false")
|
|
}
|
|
}
|
|
|
|
func TestSaveBufLimit(t *testing.T) {
|
|
const maxStr = 1 << 10
|
|
var got []HeaderField
|
|
dec := NewDecoder(initialHeaderTableSize, func(hf HeaderField) {
|
|
got = append(got, hf)
|
|
})
|
|
dec.SetMaxStringLength(maxStr)
|
|
var frag []byte
|
|
frag = append(frag[:0], encodeTypeByte(false, false))
|
|
frag = appendVarInt(frag, 7, 3)
|
|
frag = append(frag, "foo"...)
|
|
frag = appendVarInt(frag, 7, 3)
|
|
frag = append(frag, "bar"...)
|
|
|
|
if _, err := dec.Write(frag); err != nil {
|
|
t.Fatal(err)
|
|
}
|
|
|
|
want := []HeaderField{{Name: "foo", Value: "bar"}}
|
|
if !reflect.DeepEqual(got, want) {
|
|
t.Errorf("After small writes, got %v; want %v", got, want)
|
|
}
|
|
|
|
frag = append(frag[:0], encodeTypeByte(false, false))
|
|
frag = appendVarInt(frag, 7, maxStr*3)
|
|
frag = append(frag, make([]byte, maxStr*3)...)
|
|
|
|
_, err := dec.Write(frag)
|
|
if err != ErrStringLength {
|
|
t.Fatalf("Write error = %v; want ErrStringLength", err)
|
|
}
|
|
}
|
|
|
|
func TestDynamicSizeUpdate(t *testing.T) {
|
|
var buf bytes.Buffer
|
|
enc := NewEncoder(&buf)
|
|
enc.SetMaxDynamicTableSize(255)
|
|
enc.WriteField(HeaderField{Name: "foo", Value: "bar"})
|
|
|
|
d := NewDecoder(4096, nil)
|
|
_, err := d.DecodeFull(buf.Bytes())
|
|
if err != nil {
|
|
t.Fatalf("unexpected error: got = %v", err)
|
|
}
|
|
|
|
// must fail since the dynamic table update must be at the beginning
|
|
_, err = d.DecodeFull(buf.Bytes())
|
|
if err == nil {
|
|
t.Fatalf("dynamic table size update not at the beginning of a header block")
|
|
}
|
|
}
|