github.com/icodeface/tls@v0.0.0-20230910023335-34df9250cd12/internal/x/net/http2/hpack/tables_test.go (about)

     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  }