Home | History | Annotate | Download | only in hpack
      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 
      5 package hpack
      6 
      7 import (
      8 	"bufio"
      9 	"regexp"
     10 	"strconv"
     11 	"strings"
     12 	"testing"
     13 )
     14 
     15 func 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 
     92 func 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 
    118 func 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