]> Cypherpunks repositories - gostls13.git/commitdiff
vendor: update golang_org/x/net/http2 packages
authorAndreas Auernhammer <aead@mail.de>
Fri, 14 Apr 2017 21:12:04 +0000 (23:12 +0200)
committerBrad Fitzpatrick <bradfitz@golang.org>
Sat, 15 Apr 2017 23:36:55 +0000 (23:36 +0000)
Update the http2/hpack package to rev 05d3205.

Introduce the following changes:
 - 05d3205 http2/hpack: fix memory leak in headerFieldTable lookup maps
 - bce15e7 http2/hpack: speedup Encoder.searchTable
 - dd2d9a6 http2/hpack: remove hpack's constant time string comparison
 - 357296a all: single space after period
 - 71a0359 x/net/http2: Fix various typos in doc comments.

Updates #19967

Change-Id: Ie2c8edcaaf96abde515cb995dfa503b54776abfe
Reviewed-on: https://go-review.googlesource.com/40833
Reviewed-by: Brad Fitzpatrick <bradfitz@golang.org>
Run-TryBot: Brad Fitzpatrick <bradfitz@golang.org>
TryBot-Result: Gobot Gobot <gobot@golang.org>

src/vendor/golang_org/x/net/http2/hpack/encode.go
src/vendor/golang_org/x/net/http2/hpack/encode_test.go
src/vendor/golang_org/x/net/http2/hpack/hpack.go
src/vendor/golang_org/x/net/http2/hpack/hpack_test.go
src/vendor/golang_org/x/net/http2/hpack/tables.go
src/vendor/golang_org/x/net/http2/hpack/tables_test.go [new file with mode: 0644]

index f9bb03398483533dac826a0f7febcaf1343e1ecf..54726c2a3c521e6a636cdde32b96e1dbda76b9ca 100644 (file)
@@ -39,13 +39,14 @@ func NewEncoder(w io.Writer) *Encoder {
                tableSizeUpdate: false,
                w:               w,
        }
+       e.dynTab.table.init()
        e.dynTab.setMaxSize(initialHeaderTableSize)
        return e
 }
 
 // WriteField encodes f into a single Write to e's underlying Writer.
 // This function may also produce bytes for "Header Table Size Update"
-// if necessary.  If produced, it is done before encoding f.
+// if necessary. If produced, it is done before encoding f.
 func (e *Encoder) WriteField(f HeaderField) error {
        e.buf = e.buf[:0]
 
@@ -88,29 +89,17 @@ func (e *Encoder) WriteField(f HeaderField) error {
 // only name matches, i points to that index and nameValueMatch
 // becomes false.
 func (e *Encoder) searchTable(f HeaderField) (i uint64, nameValueMatch bool) {
-       for idx, hf := range staticTable {
-               if !constantTimeStringCompare(hf.Name, f.Name) {
-                       continue
-               }
-               if i == 0 {
-                       i = uint64(idx + 1)
-               }
-               if f.Sensitive {
-                       continue
-               }
-               if !constantTimeStringCompare(hf.Value, f.Value) {
-                       continue
-               }
-               i = uint64(idx + 1)
-               nameValueMatch = true
-               return
+       i, nameValueMatch = staticTable.search(f)
+       if nameValueMatch {
+               return i, true
        }
 
-       j, nameValueMatch := e.dynTab.search(f)
+       j, nameValueMatch := e.dynTab.table.search(f)
        if nameValueMatch || (i == 0 && j != 0) {
-               i = j + uint64(len(staticTable))
+               return j + uint64(staticTable.len()), nameValueMatch
        }
-       return
+
+       return i, false
 }
 
 // SetMaxDynamicTableSize changes the dynamic header table size to v.
index 92286f3bad1fb74ab96c79b851a1ef76f9f7ff02..05f12db9cdc8790387c8ba31b4f610ce9c73b2cf 100644 (file)
@@ -7,6 +7,8 @@ package hpack
 import (
        "bytes"
        "encoding/hex"
+       "fmt"
+       "math/rand"
        "reflect"
        "strings"
        "testing"
@@ -101,17 +103,20 @@ func TestEncoderSearchTable(t *testing.T) {
                wantMatch bool
        }{
                // Name and Value match
-               {pair("foo", "bar"), uint64(len(staticTable) + 3), true},
-               {pair("blake", "miz"), uint64(len(staticTable) + 2), true},
+               {pair("foo", "bar"), uint64(staticTable.len()) + 3, true},
+               {pair("blake", "miz"), uint64(staticTable.len()) + 2, true},
                {pair(":method", "GET"), 2, true},
 
-               // Only name match because Sensitive == true
-               {HeaderField{":method", "GET", true}, 2, false},
+               // Only name match because Sensitive == true. This is allowed to match
+               // any ":method" entry. The current implementation uses the last entry
+               // added in newStaticTable.
+               {HeaderField{":method", "GET", true}, 3, false},
 
                // Only Name matches
-               {pair("foo", "..."), uint64(len(staticTable) + 3), false},
-               {pair("blake", "..."), uint64(len(staticTable) + 2), false},
-               {pair(":method", "..."), 2, false},
+               {pair("foo", "..."), uint64(staticTable.len()) + 3, false},
+               {pair("blake", "..."), uint64(staticTable.len()) + 2, false},
+               // As before, this is allowed to match any ":method" entry.
+               {pair(":method", "..."), 3, false},
 
                // None match
                {pair("foo-", "bar"), 0, false},
@@ -328,3 +333,54 @@ func TestEncoderSetMaxDynamicTableSizeLimit(t *testing.T) {
 func removeSpace(s string) string {
        return strings.Replace(s, " ", "", -1)
 }
+
+func BenchmarkEncoderSearchTable(b *testing.B) {
+       e := NewEncoder(nil)
+
+       // A sample of possible header fields.
+       // This is not based on any actual data from HTTP/2 traces.
+       var possible []HeaderField
+       for _, f := range staticTable.ents {
+               if f.Value == "" {
+                       possible = append(possible, f)
+                       continue
+               }
+               // Generate 5 random values, except for cookie and set-cookie,
+               // which we know can have many values in practice.
+               num := 5
+               if f.Name == "cookie" || f.Name == "set-cookie" {
+                       num = 25
+               }
+               for i := 0; i < num; i++ {
+                       f.Value = fmt.Sprintf("%s-%d", f.Name, i)
+                       possible = append(possible, f)
+               }
+       }
+       for k := 0; k < 10; k++ {
+               f := HeaderField{
+                       Name:      fmt.Sprintf("x-header-%d", k),
+                       Sensitive: rand.Int()%2 == 0,
+               }
+               for i := 0; i < 5; i++ {
+                       f.Value = fmt.Sprintf("%s-%d", f.Name, i)
+                       possible = append(possible, f)
+               }
+       }
+
+       // Add a random sample to the dynamic table. This very loosely simulates
+       // a history of 100 requests with 20 header fields per request.
+       for r := 0; r < 100*20; r++ {
+               f := possible[rand.Int31n(int32(len(possible)))]
+               // Skip if this is in the staticTable verbatim.
+               if _, has := staticTable.search(f); !has {
+                       e.dynTab.add(f)
+               }
+       }
+
+       b.ResetTimer()
+       for n := 0; n < b.N; n++ {
+               for _, f := range possible {
+                       e.searchTable(f)
+               }
+       }
+}
index 8aa197ad678d3ba8d627644a3cf91d8948c44a20..176644acdaceca5cc935e263598ee4155623a312 100644 (file)
@@ -57,11 +57,11 @@ func (hf HeaderField) String() string {
        return fmt.Sprintf("header field %q = %q%s", hf.Name, hf.Value, suffix)
 }
 
-// Size returns the size of an entry per RFC 7540 section 5.2.
+// Size returns the size of an entry per RFC 7541 section 4.1.
 func (hf HeaderField) Size() uint32 {
        // http://http2.github.io/http2-spec/compression.html#rfc.section.4.1
        // "The size of the dynamic table is the sum of the size of
-       // its entries.  The size of an entry is the sum of its name's
+       // its entries. The size of an entry is the sum of its name's
        // length in octets (as defined in Section 5.2), its value's
        // length in octets (see Section 5.2), plus 32.  The size of
        // an entry is calculated using the length of the name and
@@ -102,6 +102,7 @@ func NewDecoder(maxDynamicTableSize uint32, emitFunc func(f HeaderField)) *Decod
                emit:        emitFunc,
                emitEnabled: true,
        }
+       d.dynTab.table.init()
        d.dynTab.allowedMaxSize = maxDynamicTableSize
        d.dynTab.setMaxSize(maxDynamicTableSize)
        return d
@@ -154,12 +155,9 @@ func (d *Decoder) SetAllowedMaxDynamicTableSize(v uint32) {
 }
 
 type dynamicTable struct {
-       // ents is the FIFO described at
        // http://http2.github.io/http2-spec/compression.html#rfc.section.2.3.2
-       // The newest (low index) is append at the end, and items are
-       // evicted from the front.
-       ents           []HeaderField
-       size           uint32
+       table          headerFieldTable
+       size           uint32 // in bytes
        maxSize        uint32 // current maxSize
        allowedMaxSize uint32 // maxSize may go up to this, inclusive
 }
@@ -169,95 +167,45 @@ func (dt *dynamicTable) setMaxSize(v uint32) {
        dt.evict()
 }
 
-// TODO: change dynamicTable to be a struct with a slice and a size int field,
-// per http://http2.github.io/http2-spec/compression.html#rfc.section.4.1:
-//
-//
-// Then make add increment the size. maybe the max size should move from Decoder to
-// dynamicTable and add should return an ok bool if there was enough space.
-//
-// Later we'll need a remove operation on dynamicTable.
-
 func (dt *dynamicTable) add(f HeaderField) {
-       dt.ents = append(dt.ents, f)
+       dt.table.addEntry(f)
        dt.size += f.Size()
        dt.evict()
 }
 
-// If we're too big, evict old stuff (front of the slice)
+// If we're too big, evict old stuff.
 func (dt *dynamicTable) evict() {
-       base := dt.ents // keep base pointer of slice
-       for dt.size > dt.maxSize {
-               dt.size -= dt.ents[0].Size()
-               dt.ents = dt.ents[1:]
-       }
-
-       // Shift slice contents down if we evicted things.
-       if len(dt.ents) != len(base) {
-               copy(base, dt.ents)
-               dt.ents = base[:len(dt.ents)]
+       var n int
+       for dt.size > dt.maxSize && n < dt.table.len() {
+               dt.size -= dt.table.ents[n].Size()
+               n++
        }
-}
-
-// constantTimeStringCompare compares string a and b in a constant
-// time manner.
-func constantTimeStringCompare(a, b string) bool {
-       if len(a) != len(b) {
-               return false
-       }
-
-       c := byte(0)
-
-       for i := 0; i < len(a); i++ {
-               c |= a[i] ^ b[i]
-       }
-
-       return c == 0
-}
-
-// Search searches f in the table. The return value i is 0 if there is
-// no name match. If there is name match or name/value match, i is the
-// index of that entry (1-based). If both name and value match,
-// nameValueMatch becomes true.
-func (dt *dynamicTable) search(f HeaderField) (i uint64, nameValueMatch bool) {
-       l := len(dt.ents)
-       for j := l - 1; j >= 0; j-- {
-               ent := dt.ents[j]
-               if !constantTimeStringCompare(ent.Name, f.Name) {
-                       continue
-               }
-               if i == 0 {
-                       i = uint64(l - j)
-               }
-               if f.Sensitive {
-                       continue
-               }
-               if !constantTimeStringCompare(ent.Value, f.Value) {
-                       continue
-               }
-               i = uint64(l - j)
-               nameValueMatch = true
-               return
-       }
-       return
+       dt.table.evictOldest(n)
 }
 
 func (d *Decoder) maxTableIndex() int {
-       return len(d.dynTab.ents) + len(staticTable)
+       // This should never overflow. RFC 7540 Section 6.5.2 limits the size of
+       // the dynamic table to 2^32 bytes, where each entry will occupy more than
+       // one byte. Further, the staticTable has a fixed, small length.
+       return d.dynTab.table.len() + staticTable.len()
 }
 
 func (d *Decoder) at(i uint64) (hf HeaderField, ok bool) {
-       if i < 1 {
+       // See Section 2.3.3.
+       if i == 0 {
                return
        }
+       if i <= uint64(staticTable.len()) {
+               return staticTable.ents[i-1], true
+       }
        if i > uint64(d.maxTableIndex()) {
                return
        }
-       if i <= uint64(len(staticTable)) {
-               return staticTable[i-1], true
-       }
-       dents := d.dynTab.ents
-       return dents[len(dents)-(int(i)-len(staticTable))], true
+       // In the dynamic table, newer entries have lower indices.
+       // However, dt.ents[0] is the oldest entry. Hence, dt.ents is
+       // the reversed dynamic table.
+       dt := d.dynTab.table
+       return dt.ents[dt.len()-(int(i)-staticTable.len())], true
 }
 
 // Decode decodes an entire block.
@@ -307,7 +255,7 @@ func (d *Decoder) Write(p []byte) (n int, err error) {
                err = d.parseHeaderFieldRepr()
                if err == errNeedMore {
                        // Extra paranoia, making sure saveBuf won't
-                       // get too large.  All the varint and string
+                       // get too large. All the varint and string
                        // reading code earlier should already catch
                        // overlong things and return ErrStringLength,
                        // but keep this as a last resort.
index 4c7b17bfb14c0bfedf806571fc2f2e453377eef3..c2f8fd102b45e60cfd7017ce1f7f8c0a9af5e6b6 100644 (file)
 package hpack
 
 import (
-       "bufio"
        "bytes"
        "encoding/hex"
        "fmt"
        "math/rand"
        "reflect"
-       "regexp"
-       "strconv"
        "strings"
        "testing"
        "time"
 )
 
-func TestStaticTable(t *testing.T) {
-       fromSpec := `
-          +-------+-----------------------------+---------------+
-          | 1     | :authority                  |               |
-          | 2     | :method                     | GET           |
-          | 3     | :method                     | POST          |
-          | 4     | :path                       | /             |
-          | 5     | :path                       | /index.html   |
-          | 6     | :scheme                     | http          |
-          | 7     | :scheme                     | https         |
-          | 8     | :status                     | 200           |
-          | 9     | :status                     | 204           |
-          | 10    | :status                     | 206           |
-          | 11    | :status                     | 304           |
-          | 12    | :status                     | 400           |
-          | 13    | :status                     | 404           |
-          | 14    | :status                     | 500           |
-          | 15    | accept-charset              |               |
-          | 16    | accept-encoding             | gzip, deflate |
-          | 17    | accept-language             |               |
-          | 18    | accept-ranges               |               |
-          | 19    | accept                      |               |
-          | 20    | access-control-allow-origin |               |
-          | 21    | age                         |               |
-          | 22    | allow                       |               |
-          | 23    | authorization               |               |
-          | 24    | cache-control               |               |
-          | 25    | content-disposition         |               |
-          | 26    | content-encoding            |               |
-          | 27    | content-language            |               |
-          | 28    | content-length              |               |
-          | 29    | content-location            |               |
-          | 30    | content-range               |               |
-          | 31    | content-type                |               |
-          | 32    | cookie                      |               |
-          | 33    | date                        |               |
-          | 34    | etag                        |               |
-          | 35    | expect                      |               |
-          | 36    | expires                     |               |
-          | 37    | from                        |               |
-          | 38    | host                        |               |
-          | 39    | if-match                    |               |
-          | 40    | if-modified-since           |               |
-          | 41    | if-none-match               |               |
-          | 42    | if-range                    |               |
-          | 43    | if-unmodified-since         |               |
-          | 44    | last-modified               |               |
-          | 45    | link                        |               |
-          | 46    | location                    |               |
-          | 47    | max-forwards                |               |
-          | 48    | proxy-authenticate          |               |
-          | 49    | proxy-authorization         |               |
-          | 50    | range                       |               |
-          | 51    | referer                     |               |
-          | 52    | refresh                     |               |
-          | 53    | retry-after                 |               |
-          | 54    | server                      |               |
-          | 55    | set-cookie                  |               |
-          | 56    | strict-transport-security   |               |
-          | 57    | transfer-encoding           |               |
-          | 58    | user-agent                  |               |
-          | 59    | vary                        |               |
-          | 60    | via                         |               |
-          | 61    | www-authenticate            |               |
-          +-------+-----------------------------+---------------+
-`
-       bs := bufio.NewScanner(strings.NewReader(fromSpec))
-       re := regexp.MustCompile(`\| (\d+)\s+\| (\S+)\s*\| (\S(.*\S)?)?\s+\|`)
-       for bs.Scan() {
-               l := bs.Text()
-               if !strings.Contains(l, "|") {
-                       continue
-               }
-               m := re.FindStringSubmatch(l)
-               if m == nil {
-                       continue
-               }
-               i, err := strconv.Atoi(m[1])
-               if err != nil {
-                       t.Errorf("Bogus integer on line %q", l)
-                       continue
-               }
-               if i < 1 || i > len(staticTable) {
-                       t.Errorf("Bogus index %d on line %q", i, l)
-                       continue
-               }
-               if got, want := staticTable[i-1].Name, m[2]; got != want {
-                       t.Errorf("header index %d name = %q; want %q", i, got, want)
-               }
-               if got, want := staticTable[i-1].Value, m[3]; got != want {
-                       t.Errorf("header index %d value = %q; want %q", i, got, want)
-               }
-       }
-       if err := bs.Err(); err != nil {
-               t.Error(err)
-       }
-}
-
 func (d *Decoder) mustAt(idx int) HeaderField {
        if hf, ok := d.at(uint64(idx)); !ok {
                panic(fmt.Sprintf("bogus index %d", idx))
@@ -132,10 +31,10 @@ func TestDynamicTableAt(t *testing.T) {
        }
        d.dynTab.add(pair("foo", "bar"))
        d.dynTab.add(pair("blake", "miz"))
-       if got, want := at(len(staticTable)+1), (pair("blake", "miz")); got != want {
+       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(len(staticTable)+2), (pair("foo", "bar")); 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 {
@@ -143,41 +42,6 @@ func TestDynamicTableAt(t *testing.T) {
        }
 }
 
-func TestDynamicTableSearch(t *testing.T) {
-       dt := dynamicTable{}
-       dt.setMaxSize(4096)
-
-       dt.add(pair("foo", "bar"))
-       dt.add(pair("blake", "miz"))
-       dt.add(pair(":method", "GET"))
-
-       tests := []struct {
-               hf        HeaderField
-               wantI     uint64
-               wantMatch bool
-       }{
-               // Name and Value match
-               {pair("foo", "bar"), 3, true},
-               {pair(":method", "GET"), 1, true},
-
-               // Only name match because of Sensitive == true
-               {HeaderField{"blake", "miz", true}, 2, false},
-
-               // Only Name matches
-               {pair("foo", "..."), 3, false},
-               {pair("blake", "..."), 2, false},
-               {pair(":method", "..."), 1, false},
-
-               // None match
-               {pair("foo-", "bar"), 0, false},
-       }
-       for _, tt := range tests {
-               if gotI, gotMatch := dt.search(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 TestDynamicTableSizeEvict(t *testing.T) {
        d := NewDecoder(4096, nil)
        if want := uint32(0); d.dynTab.size != want {
@@ -196,7 +60,7 @@ func TestDynamicTableSizeEvict(t *testing.T) {
        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(len(staticTable)+1), (pair("foo", "bar")); got != 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)))
@@ -255,9 +119,9 @@ func TestDecoderDecode(t *testing.T) {
 }
 
 func (dt *dynamicTable) reverseCopy() (hf []HeaderField) {
-       hf = make([]HeaderField, len(dt.ents))
+       hf = make([]HeaderField, len(dt.table.ents))
        for i := range hf {
-               hf[i] = dt.ents[len(dt.ents)-1-i]
+               hf[i] = dt.table.ents[len(dt.table.ents)-1-i]
        }
        return
 }
index b9283a023302311c3719bc2cc2db94b458c53edf..31bd5a553f29c6026cb209ff3b8f6928d31d5760 100644 (file)
 
 package hpack
 
+import (
+       "fmt"
+)
+
+// headerFieldTable implements a list of HeaderFields.
+// This is used to implement the static and dynamic tables.
+type headerFieldTable struct {
+       // For static tables, entries are never evicted.
+       //
+       // For dynamic tables, entries are evicted from ents[0] and added to the end.
+       // Each entry has a unique id that starts at one and increments for each
+       // entry that is added. This unique id is stable across evictions, meaning
+       // it can be used as a pointer to a specific entry. As in hpack, unique ids
+       // are 1-based. The unique id for ents[k] is k + evictCount + 1.
+       //
+       // Zero is not a valid unique id.
+       //
+       // evictCount should not overflow in any remotely practical situation. In
+       // practice, we will have one dynamic table per HTTP/2 connection. If we
+       // assume a very powerful server that handles 1M QPS per connection and each
+       // request adds (then evicts) 100 entries from the table, it would still take
+       // 2M years for evictCount to overflow.
+       ents       []HeaderField
+       evictCount uint64
+
+       // byName maps a HeaderField name to the unique id of the newest entry with
+       // the same name. See above for a definition of "unique id".
+       byName map[string]uint64
+
+       // byNameValue maps a HeaderField name/value pair to the unique id of the newest
+       // entry with the same name and value. See above for a definition of "unique id".
+       byNameValue map[pairNameValue]uint64
+}
+
+type pairNameValue struct {
+       name, value string
+}
+
+func (t *headerFieldTable) init() {
+       t.byName = make(map[string]uint64)
+       t.byNameValue = make(map[pairNameValue]uint64)
+}
+
+// len reports the number of entries in the table.
+func (t *headerFieldTable) len() int {
+       return len(t.ents)
+}
+
+// addEntry adds a new entry.
+func (t *headerFieldTable) addEntry(f HeaderField) {
+       id := uint64(t.len()) + t.evictCount + 1
+       t.byName[f.Name] = id
+       t.byNameValue[pairNameValue{f.Name, f.Value}] = id
+       t.ents = append(t.ents, f)
+}
+
+// evictOldest evicts the n oldest entries in the table.
+func (t *headerFieldTable) evictOldest(n int) {
+       if n > t.len() {
+               panic(fmt.Sprintf("evictOldest(%v) on table with %v entries", n, t.len()))
+       }
+       for k := 0; k < n; k++ {
+               f := t.ents[k]
+               id := t.evictCount + uint64(k) + 1
+               if t.byName[f.Name] == id {
+                       delete(t.byName, f.Name)
+               }
+               if p := (pairNameValue{f.Name, f.Value}); t.byNameValue[p] == id {
+                       delete(t.byNameValue, p)
+               }
+       }
+       copy(t.ents, t.ents[n:])
+       for k := t.len() - n; k < t.len(); k++ {
+               t.ents[k] = HeaderField{} // so strings can be garbage collected
+       }
+       t.ents = t.ents[:t.len()-n]
+       if t.evictCount+uint64(n) < t.evictCount {
+               panic("evictCount overflow")
+       }
+       t.evictCount += uint64(n)
+}
+
+// search finds f in the table. If there is no match, i is 0.
+// If both name and value match, i is the matched index and nameValueMatch
+// becomes true. If only name matches, i points to that index and
+// nameValueMatch becomes false.
+//
+// The returned index is a 1-based HPACK index. For dynamic tables, HPACK says
+// that index 1 should be the newest entry, but t.ents[0] is the oldest entry,
+// meaning t.ents is reversed for dynamic tables. Hence, when t is a dynamic
+// table, the return value i actually refers to the entry t.ents[t.len()-i].
+//
+// All tables are assumed to be a dynamic tables except for the global
+// staticTable pointer.
+//
+// See Section 2.3.3.
+func (t *headerFieldTable) search(f HeaderField) (i uint64, nameValueMatch bool) {
+       if !f.Sensitive {
+               if id := t.byNameValue[pairNameValue{f.Name, f.Value}]; id != 0 {
+                       return t.idToIndex(id), true
+               }
+       }
+       if id := t.byName[f.Name]; id != 0 {
+               return t.idToIndex(id), false
+       }
+       return 0, false
+}
+
+// idToIndex converts a unique id to an HPACK index.
+// See Section 2.3.3.
+func (t *headerFieldTable) idToIndex(id uint64) uint64 {
+       if id <= t.evictCount {
+               panic(fmt.Sprintf("id (%v) <= evictCount (%v)", id, t.evictCount))
+       }
+       k := id - t.evictCount - 1 // convert id to an index t.ents[k]
+       if t != staticTable {
+               return uint64(t.len()) - k // dynamic table
+       }
+       return k + 1
+}
+
 func pair(name, value string) HeaderField {
        return HeaderField{Name: name, Value: value}
 }
 
 // http://tools.ietf.org/html/draft-ietf-httpbis-header-compression-07#appendix-B
-var staticTable = [...]HeaderField{
-       pair(":authority", ""), // index 1 (1-based)
-       pair(":method", "GET"),
-       pair(":method", "POST"),
-       pair(":path", "/"),
-       pair(":path", "/index.html"),
-       pair(":scheme", "http"),
-       pair(":scheme", "https"),
-       pair(":status", "200"),
-       pair(":status", "204"),
-       pair(":status", "206"),
-       pair(":status", "304"),
-       pair(":status", "400"),
-       pair(":status", "404"),
-       pair(":status", "500"),
-       pair("accept-charset", ""),
-       pair("accept-encoding", "gzip, deflate"),
-       pair("accept-language", ""),
-       pair("accept-ranges", ""),
-       pair("accept", ""),
-       pair("access-control-allow-origin", ""),
-       pair("age", ""),
-       pair("allow", ""),
-       pair("authorization", ""),
-       pair("cache-control", ""),
-       pair("content-disposition", ""),
-       pair("content-encoding", ""),
-       pair("content-language", ""),
-       pair("content-length", ""),
-       pair("content-location", ""),
-       pair("content-range", ""),
-       pair("content-type", ""),
-       pair("cookie", ""),
-       pair("date", ""),
-       pair("etag", ""),
-       pair("expect", ""),
-       pair("expires", ""),
-       pair("from", ""),
-       pair("host", ""),
-       pair("if-match", ""),
-       pair("if-modified-since", ""),
-       pair("if-none-match", ""),
-       pair("if-range", ""),
-       pair("if-unmodified-since", ""),
-       pair("last-modified", ""),
-       pair("link", ""),
-       pair("location", ""),
-       pair("max-forwards", ""),
-       pair("proxy-authenticate", ""),
-       pair("proxy-authorization", ""),
-       pair("range", ""),
-       pair("referer", ""),
-       pair("refresh", ""),
-       pair("retry-after", ""),
-       pair("server", ""),
-       pair("set-cookie", ""),
-       pair("strict-transport-security", ""),
-       pair("transfer-encoding", ""),
-       pair("user-agent", ""),
-       pair("vary", ""),
-       pair("via", ""),
-       pair("www-authenticate", ""),
+var staticTable = newStaticTable()
+
+func newStaticTable() *headerFieldTable {
+       t := &headerFieldTable{}
+       t.init()
+       t.addEntry(pair(":authority", ""))
+       t.addEntry(pair(":method", "GET"))
+       t.addEntry(pair(":method", "POST"))
+       t.addEntry(pair(":path", "/"))
+       t.addEntry(pair(":path", "/index.html"))
+       t.addEntry(pair(":scheme", "http"))
+       t.addEntry(pair(":scheme", "https"))
+       t.addEntry(pair(":status", "200"))
+       t.addEntry(pair(":status", "204"))
+       t.addEntry(pair(":status", "206"))
+       t.addEntry(pair(":status", "304"))
+       t.addEntry(pair(":status", "400"))
+       t.addEntry(pair(":status", "404"))
+       t.addEntry(pair(":status", "500"))
+       t.addEntry(pair("accept-charset", ""))
+       t.addEntry(pair("accept-encoding", "gzip, deflate"))
+       t.addEntry(pair("accept-language", ""))
+       t.addEntry(pair("accept-ranges", ""))
+       t.addEntry(pair("accept", ""))
+       t.addEntry(pair("access-control-allow-origin", ""))
+       t.addEntry(pair("age", ""))
+       t.addEntry(pair("allow", ""))
+       t.addEntry(pair("authorization", ""))
+       t.addEntry(pair("cache-control", ""))
+       t.addEntry(pair("content-disposition", ""))
+       t.addEntry(pair("content-encoding", ""))
+       t.addEntry(pair("content-language", ""))
+       t.addEntry(pair("content-length", ""))
+       t.addEntry(pair("content-location", ""))
+       t.addEntry(pair("content-range", ""))
+       t.addEntry(pair("content-type", ""))
+       t.addEntry(pair("cookie", ""))
+       t.addEntry(pair("date", ""))
+       t.addEntry(pair("etag", ""))
+       t.addEntry(pair("expect", ""))
+       t.addEntry(pair("expires", ""))
+       t.addEntry(pair("from", ""))
+       t.addEntry(pair("host", ""))
+       t.addEntry(pair("if-match", ""))
+       t.addEntry(pair("if-modified-since", ""))
+       t.addEntry(pair("if-none-match", ""))
+       t.addEntry(pair("if-range", ""))
+       t.addEntry(pair("if-unmodified-since", ""))
+       t.addEntry(pair("last-modified", ""))
+       t.addEntry(pair("link", ""))
+       t.addEntry(pair("location", ""))
+       t.addEntry(pair("max-forwards", ""))
+       t.addEntry(pair("proxy-authenticate", ""))
+       t.addEntry(pair("proxy-authorization", ""))
+       t.addEntry(pair("range", ""))
+       t.addEntry(pair("referer", ""))
+       t.addEntry(pair("refresh", ""))
+       t.addEntry(pair("retry-after", ""))
+       t.addEntry(pair("server", ""))
+       t.addEntry(pair("set-cookie", ""))
+       t.addEntry(pair("strict-transport-security", ""))
+       t.addEntry(pair("transfer-encoding", ""))
+       t.addEntry(pair("user-agent", ""))
+       t.addEntry(pair("vary", ""))
+       t.addEntry(pair("via", ""))
+       t.addEntry(pair("www-authenticate", ""))
+       return t
 }
 
 var huffmanCodes = [256]uint32{
diff --git a/src/vendor/golang_org/x/net/http2/hpack/tables_test.go b/src/vendor/golang_org/x/net/http2/hpack/tables_test.go
new file mode 100644 (file)
index 0000000..d963f36
--- /dev/null
@@ -0,0 +1,214 @@
+// Copyright 2017 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 (
+       "bufio"
+       "regexp"
+       "strconv"
+       "strings"
+       "testing"
+)
+
+func TestHeaderFieldTable(t *testing.T) {
+       table := &headerFieldTable{}
+       table.init()
+       table.addEntry(pair("key1", "value1-1"))
+       table.addEntry(pair("key2", "value2-1"))
+       table.addEntry(pair("key1", "value1-2"))
+       table.addEntry(pair("key3", "value3-1"))
+       table.addEntry(pair("key4", "value4-1"))
+       table.addEntry(pair("key2", "value2-2"))
+
+       // Tests will be run twice: once before evicting anything, and
+       // again after evicting the three oldest entries.
+       tests := []struct {
+               f                 HeaderField
+               beforeWantStaticI uint64
+               beforeWantMatch   bool
+               afterWantStaticI  uint64
+               afterWantMatch    bool
+       }{
+               {HeaderField{"key1", "value1-1", false}, 1, true, 0, false},
+               {HeaderField{"key1", "value1-2", false}, 3, true, 0, false},
+               {HeaderField{"key1", "value1-3", false}, 3, false, 0, false},
+               {HeaderField{"key2", "value2-1", false}, 2, true, 3, false},
+               {HeaderField{"key2", "value2-2", false}, 6, true, 3, true},
+               {HeaderField{"key2", "value2-3", false}, 6, false, 3, false},
+               {HeaderField{"key4", "value4-1", false}, 5, true, 2, true},
+               // Name match only, because sensitive.
+               {HeaderField{"key4", "value4-1", true}, 5, false, 2, false},
+               // Key not found.
+               {HeaderField{"key5", "value5-x", false}, 0, false, 0, false},
+       }
+
+       staticToDynamic := func(i uint64) uint64 {
+               if i == 0 {
+                       return 0
+               }
+               return uint64(table.len()) - i + 1 // dynamic is the reversed table
+       }
+
+       searchStatic := func(f HeaderField) (uint64, bool) {
+               old := staticTable
+               staticTable = table
+               defer func() { staticTable = old }()
+               return staticTable.search(f)
+       }
+
+       searchDynamic := func(f HeaderField) (uint64, bool) {
+               return table.search(f)
+       }
+
+       for _, test := range tests {
+               gotI, gotMatch := searchStatic(test.f)
+               if wantI, wantMatch := test.beforeWantStaticI, test.beforeWantMatch; gotI != wantI || gotMatch != wantMatch {
+                       t.Errorf("before evictions: searchStatic(%+v)=%v,%v want %v,%v", test.f, gotI, gotMatch, wantI, wantMatch)
+               }
+               gotI, gotMatch = searchDynamic(test.f)
+               wantDynamicI := staticToDynamic(test.beforeWantStaticI)
+               if wantI, wantMatch := wantDynamicI, test.beforeWantMatch; gotI != wantI || gotMatch != wantMatch {
+                       t.Errorf("before evictions: searchDynamic(%+v)=%v,%v want %v,%v", test.f, gotI, gotMatch, wantI, wantMatch)
+               }
+       }
+
+       table.evictOldest(3)
+
+       for _, test := range tests {
+               gotI, gotMatch := searchStatic(test.f)
+               if wantI, wantMatch := test.afterWantStaticI, test.afterWantMatch; gotI != wantI || gotMatch != wantMatch {
+                       t.Errorf("after evictions: searchStatic(%+v)=%v,%v want %v,%v", test.f, gotI, gotMatch, wantI, wantMatch)
+               }
+               gotI, gotMatch = searchDynamic(test.f)
+               wantDynamicI := staticToDynamic(test.afterWantStaticI)
+               if wantI, wantMatch := wantDynamicI, test.afterWantMatch; gotI != wantI || gotMatch != wantMatch {
+                       t.Errorf("after evictions: searchDynamic(%+v)=%v,%v want %v,%v", test.f, gotI, gotMatch, wantI, wantMatch)
+               }
+       }
+}
+
+func TestHeaderFieldTable_LookupMapEviction(t *testing.T) {
+       table := &headerFieldTable{}
+       table.init()
+       table.addEntry(pair("key1", "value1-1"))
+       table.addEntry(pair("key2", "value2-1"))
+       table.addEntry(pair("key1", "value1-2"))
+       table.addEntry(pair("key3", "value3-1"))
+       table.addEntry(pair("key4", "value4-1"))
+       table.addEntry(pair("key2", "value2-2"))
+
+       // evict all pairs
+       table.evictOldest(table.len())
+
+       if l := table.len(); l > 0 {
+               t.Errorf("table.len() = %d, want 0", l)
+       }
+
+       if l := len(table.byName); l > 0 {
+               t.Errorf("len(table.byName) = %d, want 0", l)
+       }
+
+       if l := len(table.byNameValue); l > 0 {
+               t.Errorf("len(table.byNameValue) = %d, want 0", l)
+       }
+}
+
+func TestStaticTable(t *testing.T) {
+       fromSpec := `
+          +-------+-----------------------------+---------------+
+          | 1     | :authority                  |               |
+          | 2     | :method                     | GET           |
+          | 3     | :method                     | POST          |
+          | 4     | :path                       | /             |
+          | 5     | :path                       | /index.html   |
+          | 6     | :scheme                     | http          |
+          | 7     | :scheme                     | https         |
+          | 8     | :status                     | 200           |
+          | 9     | :status                     | 204           |
+          | 10    | :status                     | 206           |
+          | 11    | :status                     | 304           |
+          | 12    | :status                     | 400           |
+          | 13    | :status                     | 404           |
+          | 14    | :status                     | 500           |
+          | 15    | accept-charset              |               |
+          | 16    | accept-encoding             | gzip, deflate |
+          | 17    | accept-language             |               |
+          | 18    | accept-ranges               |               |
+          | 19    | accept                      |               |
+          | 20    | access-control-allow-origin |               |
+          | 21    | age                         |               |
+          | 22    | allow                       |               |
+          | 23    | authorization               |               |
+          | 24    | cache-control               |               |
+          | 25    | content-disposition         |               |
+          | 26    | content-encoding            |               |
+          | 27    | content-language            |               |
+          | 28    | content-length              |               |
+          | 29    | content-location            |               |
+          | 30    | content-range               |               |
+          | 31    | content-type                |               |
+          | 32    | cookie                      |               |
+          | 33    | date                        |               |
+          | 34    | etag                        |               |
+          | 35    | expect                      |               |
+          | 36    | expires                     |               |
+          | 37    | from                        |               |
+          | 38    | host                        |               |
+          | 39    | if-match                    |               |
+          | 40    | if-modified-since           |               |
+          | 41    | if-none-match               |               |
+          | 42    | if-range                    |               |
+          | 43    | if-unmodified-since         |               |
+          | 44    | last-modified               |               |
+          | 45    | link                        |               |
+          | 46    | location                    |               |
+          | 47    | max-forwards                |               |
+          | 48    | proxy-authenticate          |               |
+          | 49    | proxy-authorization         |               |
+          | 50    | range                       |               |
+          | 51    | referer                     |               |
+          | 52    | refresh                     |               |
+          | 53    | retry-after                 |               |
+          | 54    | server                      |               |
+          | 55    | set-cookie                  |               |
+          | 56    | strict-transport-security   |               |
+          | 57    | transfer-encoding           |               |
+          | 58    | user-agent                  |               |
+          | 59    | vary                        |               |
+          | 60    | via                         |               |
+          | 61    | www-authenticate            |               |
+          +-------+-----------------------------+---------------+
+`
+       bs := bufio.NewScanner(strings.NewReader(fromSpec))
+       re := regexp.MustCompile(`\| (\d+)\s+\| (\S+)\s*\| (\S(.*\S)?)?\s+\|`)
+       for bs.Scan() {
+               l := bs.Text()
+               if !strings.Contains(l, "|") {
+                       continue
+               }
+               m := re.FindStringSubmatch(l)
+               if m == nil {
+                       continue
+               }
+               i, err := strconv.Atoi(m[1])
+               if err != nil {
+                       t.Errorf("Bogus integer on line %q", l)
+                       continue
+               }
+               if i < 1 || i > staticTable.len() {
+                       t.Errorf("Bogus index %d on line %q", i, l)
+                       continue
+               }
+               if got, want := staticTable.ents[i-1].Name, m[2]; got != want {
+                       t.Errorf("header index %d name = %q; want %q", i, got, want)
+               }
+               if got, want := staticTable.ents[i-1].Value, m[3]; got != want {
+                       t.Errorf("header index %d value = %q; want %q", i, got, want)
+               }
+       }
+       if err := bs.Err(); err != nil {
+               t.Error(err)
+       }
+}