1// Copyright 2017 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
5package hpack
6
7import (
8	"bufio"
9	"regexp"
10	"strconv"
11	"strings"
12	"testing"
13)
14
15func TestHeaderFieldTable(t *testing.T) {
16	table := &headerFieldTable{}
17	table.init()
18	table.addEntry(pair("key1", "value1-1"))
19	table.addEntry(pair("key2", "value2-1"))
20	table.addEntry(pair("key1", "value1-2"))
21	table.addEntry(pair("key3", "value3-1"))
22	table.addEntry(pair("key4", "value4-1"))
23	table.addEntry(pair("key2", "value2-2"))
24
25	// Tests will be run twice: once before evicting anything, and
26	// again after evicting the three oldest entries.
27	tests := []struct {
28		f                 HeaderField
29		beforeWantStaticI uint64
30		beforeWantMatch   bool
31		afterWantStaticI  uint64
32		afterWantMatch    bool
33	}{
34		{HeaderField{"key1", "value1-1", false}, 1, true, 0, false},
35		{HeaderField{"key1", "value1-2", false}, 3, true, 0, false},
36		{HeaderField{"key1", "value1-3", false}, 3, false, 0, false},
37		{HeaderField{"key2", "value2-1", false}, 2, true, 3, false},
38		{HeaderField{"key2", "value2-2", false}, 6, true, 3, true},
39		{HeaderField{"key2", "value2-3", false}, 6, false, 3, false},
40		{HeaderField{"key4", "value4-1", false}, 5, true, 2, true},
41		// Name match only, because sensitive.
42		{HeaderField{"key4", "value4-1", true}, 5, false, 2, false},
43		// Key not found.
44		{HeaderField{"key5", "value5-x", false}, 0, false, 0, false},
45	}
46
47	staticToDynamic := func(i uint64) uint64 {
48		if i == 0 {
49			return 0
50		}
51		return uint64(table.len()) - i + 1 // dynamic is the reversed table
52	}
53
54	searchStatic := func(f HeaderField) (uint64, bool) {
55		old := staticTable
56		staticTable = table
57		defer func() { staticTable = old }()
58		return staticTable.search(f)
59	}
60
61	searchDynamic := func(f HeaderField) (uint64, bool) {
62		return table.search(f)
63	}
64
65	for _, test := range tests {
66		gotI, gotMatch := searchStatic(test.f)
67		if wantI, wantMatch := test.beforeWantStaticI, test.beforeWantMatch; gotI != wantI || gotMatch != wantMatch {
68			t.Errorf("before evictions: searchStatic(%+v)=%v,%v want %v,%v", test.f, gotI, gotMatch, wantI, wantMatch)
69		}
70		gotI, gotMatch = searchDynamic(test.f)
71		wantDynamicI := staticToDynamic(test.beforeWantStaticI)
72		if wantI, wantMatch := wantDynamicI, test.beforeWantMatch; gotI != wantI || gotMatch != wantMatch {
73			t.Errorf("before evictions: searchDynamic(%+v)=%v,%v want %v,%v", test.f, gotI, gotMatch, wantI, wantMatch)
74		}
75	}
76
77	table.evictOldest(3)
78
79	for _, test := range tests {
80		gotI, gotMatch := searchStatic(test.f)
81		if wantI, wantMatch := test.afterWantStaticI, test.afterWantMatch; gotI != wantI || gotMatch != wantMatch {
82			t.Errorf("after evictions: searchStatic(%+v)=%v,%v want %v,%v", test.f, gotI, gotMatch, wantI, wantMatch)
83		}
84		gotI, gotMatch = searchDynamic(test.f)
85		wantDynamicI := staticToDynamic(test.afterWantStaticI)
86		if wantI, wantMatch := wantDynamicI, test.afterWantMatch; gotI != wantI || gotMatch != wantMatch {
87			t.Errorf("after evictions: searchDynamic(%+v)=%v,%v want %v,%v", test.f, gotI, gotMatch, wantI, wantMatch)
88		}
89	}
90}
91
92func TestHeaderFieldTable_LookupMapEviction(t *testing.T) {
93	table := &headerFieldTable{}
94	table.init()
95	table.addEntry(pair("key1", "value1-1"))
96	table.addEntry(pair("key2", "value2-1"))
97	table.addEntry(pair("key1", "value1-2"))
98	table.addEntry(pair("key3", "value3-1"))
99	table.addEntry(pair("key4", "value4-1"))
100	table.addEntry(pair("key2", "value2-2"))
101
102	// evict all pairs
103	table.evictOldest(table.len())
104
105	if l := table.len(); l > 0 {
106		t.Errorf("table.len() = %d, want 0", l)
107	}
108
109	if l := len(table.byName); l > 0 {
110		t.Errorf("len(table.byName) = %d, want 0", l)
111	}
112
113	if l := len(table.byNameValue); l > 0 {
114		t.Errorf("len(table.byNameValue) = %d, want 0", l)
115	}
116}
117
118func TestStaticTable(t *testing.T) {
119	fromSpec := `
120          +-------+-----------------------------+---------------+
121          | 1     | :authority                  |               |
122          | 2     | :method                     | GET           |
123          | 3     | :method                     | POST          |
124          | 4     | :path                       | /             |
125          | 5     | :path                       | /index.html   |
126          | 6     | :scheme                     | http          |
127          | 7     | :scheme                     | https         |
128          | 8     | :status                     | 200           |
129          | 9     | :status                     | 204           |
130          | 10    | :status                     | 206           |
131          | 11    | :status                     | 304           |
132          | 12    | :status                     | 400           |
133          | 13    | :status                     | 404           |
134          | 14    | :status                     | 500           |
135          | 15    | accept-charset              |               |
136          | 16    | accept-encoding             | gzip, deflate |
137          | 17    | accept-language             |               |
138          | 18    | accept-ranges               |               |
139          | 19    | accept                      |               |
140          | 20    | access-control-allow-origin |               |
141          | 21    | age                         |               |
142          | 22    | allow                       |               |
143          | 23    | authorization               |               |
144          | 24    | cache-control               |               |
145          | 25    | content-disposition         |               |
146          | 26    | content-encoding            |               |
147          | 27    | content-language            |               |
148          | 28    | content-length              |               |
149          | 29    | content-location            |               |
150          | 30    | content-range               |               |
151          | 31    | content-type                |               |
152          | 32    | cookie                      |               |
153          | 33    | date                        |               |
154          | 34    | etag                        |               |
155          | 35    | expect                      |               |
156          | 36    | expires                     |               |
157          | 37    | from                        |               |
158          | 38    | host                        |               |
159          | 39    | if-match                    |               |
160          | 40    | if-modified-since           |               |
161          | 41    | if-none-match               |               |
162          | 42    | if-range                    |               |
163          | 43    | if-unmodified-since         |               |
164          | 44    | last-modified               |               |
165          | 45    | link                        |               |
166          | 46    | location                    |               |
167          | 47    | max-forwards                |               |
168          | 48    | proxy-authenticate          |               |
169          | 49    | proxy-authorization         |               |
170          | 50    | range                       |               |
171          | 51    | referer                     |               |
172          | 52    | refresh                     |               |
173          | 53    | retry-after                 |               |
174          | 54    | server                      |               |
175          | 55    | set-cookie                  |               |
176          | 56    | strict-transport-security   |               |
177          | 57    | transfer-encoding           |               |
178          | 58    | user-agent                  |               |
179          | 59    | vary                        |               |
180          | 60    | via                         |               |
181          | 61    | www-authenticate            |               |
182          +-------+-----------------------------+---------------+
183`
184	bs := bufio.NewScanner(strings.NewReader(fromSpec))
185	re := regexp.MustCompile(`\| (\d+)\s+\| (\S+)\s*\| (\S(.*\S)?)?\s+\|`)
186	for bs.Scan() {
187		l := bs.Text()
188		if !strings.Contains(l, "|") {
189			continue
190		}
191		m := re.FindStringSubmatch(l)
192		if m == nil {
193			continue
194		}
195		i, err := strconv.Atoi(m[1])
196		if err != nil {
197			t.Errorf("Bogus integer on line %q", l)
198			continue
199		}
200		if i < 1 || i > staticTable.len() {
201			t.Errorf("Bogus index %d on line %q", i, l)
202			continue
203		}
204		if got, want := staticTable.ents[i-1].Name, m[2]; got != want {
205			t.Errorf("header index %d name = %q; want %q", i, got, want)
206		}
207		if got, want := staticTable.ents[i-1].Value, m[3]; got != want {
208			t.Errorf("header index %d value = %q; want %q", i, got, want)
209		}
210	}
211	if err := bs.Err(); err != nil {
212		t.Error(err)
213	}
214}
215