mirror of
https://github.com/status-im/consul.git
synced 2025-01-12 14:55:02 +00:00
e9835610f3
This is in its own separate package so that it will be a separate test binary that runs thus isolating the go runtime from other tests and allowing accurate go routine leak checking. This test would ideally use goleak.VerifyTestMain but that will fail 100% of the time due to some architectural things (blocking queries and net/rpc uncancellability). This test is not comprehensive. We should enable/exercise more features and more cluster configurations. However its a start.
241 lines
7.0 KiB
Go
241 lines
7.0 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 (
|
|
"io"
|
|
)
|
|
|
|
const (
|
|
uint32Max = ^uint32(0)
|
|
initialHeaderTableSize = 4096
|
|
)
|
|
|
|
type Encoder struct {
|
|
dynTab dynamicTable
|
|
// minSize is the minimum table size set by
|
|
// SetMaxDynamicTableSize after the previous Header Table Size
|
|
// Update.
|
|
minSize uint32
|
|
// maxSizeLimit is the maximum table size this encoder
|
|
// supports. This will protect the encoder from too large
|
|
// size.
|
|
maxSizeLimit uint32
|
|
// tableSizeUpdate indicates whether "Header Table Size
|
|
// Update" is required.
|
|
tableSizeUpdate bool
|
|
w io.Writer
|
|
buf []byte
|
|
}
|
|
|
|
// NewEncoder returns a new Encoder which performs HPACK encoding. An
|
|
// encoded data is written to w.
|
|
func NewEncoder(w io.Writer) *Encoder {
|
|
e := &Encoder{
|
|
minSize: uint32Max,
|
|
maxSizeLimit: initialHeaderTableSize,
|
|
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.
|
|
func (e *Encoder) WriteField(f HeaderField) error {
|
|
e.buf = e.buf[:0]
|
|
|
|
if e.tableSizeUpdate {
|
|
e.tableSizeUpdate = false
|
|
if e.minSize < e.dynTab.maxSize {
|
|
e.buf = appendTableSize(e.buf, e.minSize)
|
|
}
|
|
e.minSize = uint32Max
|
|
e.buf = appendTableSize(e.buf, e.dynTab.maxSize)
|
|
}
|
|
|
|
idx, nameValueMatch := e.searchTable(f)
|
|
if nameValueMatch {
|
|
e.buf = appendIndexed(e.buf, idx)
|
|
} else {
|
|
indexing := e.shouldIndex(f)
|
|
if indexing {
|
|
e.dynTab.add(f)
|
|
}
|
|
|
|
if idx == 0 {
|
|
e.buf = appendNewName(e.buf, f, indexing)
|
|
} else {
|
|
e.buf = appendIndexedName(e.buf, f, idx, indexing)
|
|
}
|
|
}
|
|
n, err := e.w.Write(e.buf)
|
|
if err == nil && n != len(e.buf) {
|
|
err = io.ErrShortWrite
|
|
}
|
|
return err
|
|
}
|
|
|
|
// searchTable searches f in both stable and dynamic header tables.
|
|
// The static header table is searched first. Only when there is no
|
|
// exact match for both name and value, the dynamic header table is
|
|
// then searched. 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.
|
|
func (e *Encoder) searchTable(f HeaderField) (i uint64, nameValueMatch bool) {
|
|
i, nameValueMatch = staticTable.search(f)
|
|
if nameValueMatch {
|
|
return i, true
|
|
}
|
|
|
|
j, nameValueMatch := e.dynTab.table.search(f)
|
|
if nameValueMatch || (i == 0 && j != 0) {
|
|
return j + uint64(staticTable.len()), nameValueMatch
|
|
}
|
|
|
|
return i, false
|
|
}
|
|
|
|
// SetMaxDynamicTableSize changes the dynamic header table size to v.
|
|
// The actual size is bounded by the value passed to
|
|
// SetMaxDynamicTableSizeLimit.
|
|
func (e *Encoder) SetMaxDynamicTableSize(v uint32) {
|
|
if v > e.maxSizeLimit {
|
|
v = e.maxSizeLimit
|
|
}
|
|
if v < e.minSize {
|
|
e.minSize = v
|
|
}
|
|
e.tableSizeUpdate = true
|
|
e.dynTab.setMaxSize(v)
|
|
}
|
|
|
|
// SetMaxDynamicTableSizeLimit changes the maximum value that can be
|
|
// specified in SetMaxDynamicTableSize to v. By default, it is set to
|
|
// 4096, which is the same size of the default dynamic header table
|
|
// size described in HPACK specification. If the current maximum
|
|
// dynamic header table size is strictly greater than v, "Header Table
|
|
// Size Update" will be done in the next WriteField call and the
|
|
// maximum dynamic header table size is truncated to v.
|
|
func (e *Encoder) SetMaxDynamicTableSizeLimit(v uint32) {
|
|
e.maxSizeLimit = v
|
|
if e.dynTab.maxSize > v {
|
|
e.tableSizeUpdate = true
|
|
e.dynTab.setMaxSize(v)
|
|
}
|
|
}
|
|
|
|
// shouldIndex reports whether f should be indexed.
|
|
func (e *Encoder) shouldIndex(f HeaderField) bool {
|
|
return !f.Sensitive && f.Size() <= e.dynTab.maxSize
|
|
}
|
|
|
|
// appendIndexed appends index i, as encoded in "Indexed Header Field"
|
|
// representation, to dst and returns the extended buffer.
|
|
func appendIndexed(dst []byte, i uint64) []byte {
|
|
first := len(dst)
|
|
dst = appendVarInt(dst, 7, i)
|
|
dst[first] |= 0x80
|
|
return dst
|
|
}
|
|
|
|
// appendNewName appends f, as encoded in one of "Literal Header field
|
|
// - New Name" representation variants, to dst and returns the
|
|
// extended buffer.
|
|
//
|
|
// If f.Sensitive is true, "Never Indexed" representation is used. If
|
|
// f.Sensitive is false and indexing is true, "Incremental Indexing"
|
|
// representation is used.
|
|
func appendNewName(dst []byte, f HeaderField, indexing bool) []byte {
|
|
dst = append(dst, encodeTypeByte(indexing, f.Sensitive))
|
|
dst = appendHpackString(dst, f.Name)
|
|
return appendHpackString(dst, f.Value)
|
|
}
|
|
|
|
// appendIndexedName appends f and index i referring indexed name
|
|
// entry, as encoded in one of "Literal Header field - Indexed Name"
|
|
// representation variants, to dst and returns the extended buffer.
|
|
//
|
|
// If f.Sensitive is true, "Never Indexed" representation is used. If
|
|
// f.Sensitive is false and indexing is true, "Incremental Indexing"
|
|
// representation is used.
|
|
func appendIndexedName(dst []byte, f HeaderField, i uint64, indexing bool) []byte {
|
|
first := len(dst)
|
|
var n byte
|
|
if indexing {
|
|
n = 6
|
|
} else {
|
|
n = 4
|
|
}
|
|
dst = appendVarInt(dst, n, i)
|
|
dst[first] |= encodeTypeByte(indexing, f.Sensitive)
|
|
return appendHpackString(dst, f.Value)
|
|
}
|
|
|
|
// appendTableSize appends v, as encoded in "Header Table Size Update"
|
|
// representation, to dst and returns the extended buffer.
|
|
func appendTableSize(dst []byte, v uint32) []byte {
|
|
first := len(dst)
|
|
dst = appendVarInt(dst, 5, uint64(v))
|
|
dst[first] |= 0x20
|
|
return dst
|
|
}
|
|
|
|
// appendVarInt appends i, as encoded in variable integer form using n
|
|
// bit prefix, to dst and returns the extended buffer.
|
|
//
|
|
// See
|
|
// http://http2.github.io/http2-spec/compression.html#integer.representation
|
|
func appendVarInt(dst []byte, n byte, i uint64) []byte {
|
|
k := uint64((1 << n) - 1)
|
|
if i < k {
|
|
return append(dst, byte(i))
|
|
}
|
|
dst = append(dst, byte(k))
|
|
i -= k
|
|
for ; i >= 128; i >>= 7 {
|
|
dst = append(dst, byte(0x80|(i&0x7f)))
|
|
}
|
|
return append(dst, byte(i))
|
|
}
|
|
|
|
// appendHpackString appends s, as encoded in "String Literal"
|
|
// representation, to dst and returns the extended buffer.
|
|
//
|
|
// s will be encoded in Huffman codes only when it produces strictly
|
|
// shorter byte string.
|
|
func appendHpackString(dst []byte, s string) []byte {
|
|
huffmanLength := HuffmanEncodeLength(s)
|
|
if huffmanLength < uint64(len(s)) {
|
|
first := len(dst)
|
|
dst = appendVarInt(dst, 7, huffmanLength)
|
|
dst = AppendHuffmanString(dst, s)
|
|
dst[first] |= 0x80
|
|
} else {
|
|
dst = appendVarInt(dst, 7, uint64(len(s)))
|
|
dst = append(dst, s...)
|
|
}
|
|
return dst
|
|
}
|
|
|
|
// encodeTypeByte returns type byte. If sensitive is true, type byte
|
|
// for "Never Indexed" representation is returned. If sensitive is
|
|
// false and indexing is true, type byte for "Incremental Indexing"
|
|
// representation is returned. Otherwise, type byte for "Without
|
|
// Indexing" is returned.
|
|
func encodeTypeByte(indexing, sensitive bool) byte {
|
|
if sensitive {
|
|
return 0x10
|
|
}
|
|
if indexing {
|
|
return 0x40
|
|
}
|
|
return 0
|
|
}
|