123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252 |
- // 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.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) {
- 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
- }
- j, nameValueMatch := e.dynTab.search(f)
- if nameValueMatch || (i == 0 && j != 0) {
- i = j + uint64(len(staticTable))
- }
- return
- }
- // 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
- }
|