You can not select more than 25 topics
Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.
240 lines
7.0 KiB
240 lines
7.0 KiB
// 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, "Inremental 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 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 |
|
}
|
|
|