• Home
  • Line#
  • Scopes#
  • Navigate#
  • Raw
  • Download
1// Copyright 2014 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
5package hpack
6
7import (
8	"io"
9)
10
11const (
12	uint32Max              = ^uint32(0)
13	initialHeaderTableSize = 4096
14)
15
16type Encoder struct {
17	dynTab dynamicTable
18	// minSize is the minimum table size set by
19	// SetMaxDynamicTableSize after the previous Header Table Size
20	// Update.
21	minSize uint32
22	// maxSizeLimit is the maximum table size this encoder
23	// supports. This will protect the encoder from too large
24	// size.
25	maxSizeLimit uint32
26	// tableSizeUpdate indicates whether "Header Table Size
27	// Update" is required.
28	tableSizeUpdate bool
29	w               io.Writer
30	buf             []byte
31}
32
33// NewEncoder returns a new Encoder which performs HPACK encoding. An
34// encoded data is written to w.
35func NewEncoder(w io.Writer) *Encoder {
36	e := &Encoder{
37		minSize:         uint32Max,
38		maxSizeLimit:    initialHeaderTableSize,
39		tableSizeUpdate: false,
40		w:               w,
41	}
42	e.dynTab.table.init()
43	e.dynTab.setMaxSize(initialHeaderTableSize)
44	return e
45}
46
47// WriteField encodes f into a single Write to e's underlying Writer.
48// This function may also produce bytes for "Header Table Size Update"
49// if necessary. If produced, it is done before encoding f.
50func (e *Encoder) WriteField(f HeaderField) error {
51	e.buf = e.buf[:0]
52
53	if e.tableSizeUpdate {
54		e.tableSizeUpdate = false
55		if e.minSize < e.dynTab.maxSize {
56			e.buf = appendTableSize(e.buf, e.minSize)
57		}
58		e.minSize = uint32Max
59		e.buf = appendTableSize(e.buf, e.dynTab.maxSize)
60	}
61
62	idx, nameValueMatch := e.searchTable(f)
63	if nameValueMatch {
64		e.buf = appendIndexed(e.buf, idx)
65	} else {
66		indexing := e.shouldIndex(f)
67		if indexing {
68			e.dynTab.add(f)
69		}
70
71		if idx == 0 {
72			e.buf = appendNewName(e.buf, f, indexing)
73		} else {
74			e.buf = appendIndexedName(e.buf, f, idx, indexing)
75		}
76	}
77	n, err := e.w.Write(e.buf)
78	if err == nil && n != len(e.buf) {
79		err = io.ErrShortWrite
80	}
81	return err
82}
83
84// searchTable searches f in both stable and dynamic header tables.
85// The static header table is searched first. Only when there is no
86// exact match for both name and value, the dynamic header table is
87// then searched. If there is no match, i is 0. If both name and value
88// match, i is the matched index and nameValueMatch becomes true. If
89// only name matches, i points to that index and nameValueMatch
90// becomes false.
91func (e *Encoder) searchTable(f HeaderField) (i uint64, nameValueMatch bool) {
92	i, nameValueMatch = staticTable.search(f)
93	if nameValueMatch {
94		return i, true
95	}
96
97	j, nameValueMatch := e.dynTab.table.search(f)
98	if nameValueMatch || (i == 0 && j != 0) {
99		return j + uint64(staticTable.len()), nameValueMatch
100	}
101
102	return i, false
103}
104
105// SetMaxDynamicTableSize changes the dynamic header table size to v.
106// The actual size is bounded by the value passed to
107// SetMaxDynamicTableSizeLimit.
108func (e *Encoder) SetMaxDynamicTableSize(v uint32) {
109	if v > e.maxSizeLimit {
110		v = e.maxSizeLimit
111	}
112	if v < e.minSize {
113		e.minSize = v
114	}
115	e.tableSizeUpdate = true
116	e.dynTab.setMaxSize(v)
117}
118
119// SetMaxDynamicTableSizeLimit changes the maximum value that can be
120// specified in SetMaxDynamicTableSize to v. By default, it is set to
121// 4096, which is the same size of the default dynamic header table
122// size described in HPACK specification. If the current maximum
123// dynamic header table size is strictly greater than v, "Header Table
124// Size Update" will be done in the next WriteField call and the
125// maximum dynamic header table size is truncated to v.
126func (e *Encoder) SetMaxDynamicTableSizeLimit(v uint32) {
127	e.maxSizeLimit = v
128	if e.dynTab.maxSize > v {
129		e.tableSizeUpdate = true
130		e.dynTab.setMaxSize(v)
131	}
132}
133
134// shouldIndex reports whether f should be indexed.
135func (e *Encoder) shouldIndex(f HeaderField) bool {
136	return !f.Sensitive && f.Size() <= e.dynTab.maxSize
137}
138
139// appendIndexed appends index i, as encoded in "Indexed Header Field"
140// representation, to dst and returns the extended buffer.
141func appendIndexed(dst []byte, i uint64) []byte {
142	first := len(dst)
143	dst = appendVarInt(dst, 7, i)
144	dst[first] |= 0x80
145	return dst
146}
147
148// appendNewName appends f, as encoded in one of "Literal Header field
149// - New Name" representation variants, to dst and returns the
150// extended buffer.
151//
152// If f.Sensitive is true, "Never Indexed" representation is used. If
153// f.Sensitive is false and indexing is true, "Inremental Indexing"
154// representation is used.
155func appendNewName(dst []byte, f HeaderField, indexing bool) []byte {
156	dst = append(dst, encodeTypeByte(indexing, f.Sensitive))
157	dst = appendHpackString(dst, f.Name)
158	return appendHpackString(dst, f.Value)
159}
160
161// appendIndexedName appends f and index i referring indexed name
162// entry, as encoded in one of "Literal Header field - Indexed Name"
163// representation variants, to dst and returns the extended buffer.
164//
165// If f.Sensitive is true, "Never Indexed" representation is used. If
166// f.Sensitive is false and indexing is true, "Incremental Indexing"
167// representation is used.
168func appendIndexedName(dst []byte, f HeaderField, i uint64, indexing bool) []byte {
169	first := len(dst)
170	var n byte
171	if indexing {
172		n = 6
173	} else {
174		n = 4
175	}
176	dst = appendVarInt(dst, n, i)
177	dst[first] |= encodeTypeByte(indexing, f.Sensitive)
178	return appendHpackString(dst, f.Value)
179}
180
181// appendTableSize appends v, as encoded in "Header Table Size Update"
182// representation, to dst and returns the extended buffer.
183func appendTableSize(dst []byte, v uint32) []byte {
184	first := len(dst)
185	dst = appendVarInt(dst, 5, uint64(v))
186	dst[first] |= 0x20
187	return dst
188}
189
190// appendVarInt appends i, as encoded in variable integer form using n
191// bit prefix, to dst and returns the extended buffer.
192//
193// See
194// http://http2.github.io/http2-spec/compression.html#integer.representation
195func appendVarInt(dst []byte, n byte, i uint64) []byte {
196	k := uint64((1 << n) - 1)
197	if i < k {
198		return append(dst, byte(i))
199	}
200	dst = append(dst, byte(k))
201	i -= k
202	for ; i >= 128; i >>= 7 {
203		dst = append(dst, byte(0x80|(i&0x7f)))
204	}
205	return append(dst, byte(i))
206}
207
208// appendHpackString appends s, as encoded in "String Literal"
209// representation, to dst and returns the extended buffer.
210//
211// s will be encoded in Huffman codes only when it produces strictly
212// shorter byte string.
213func appendHpackString(dst []byte, s string) []byte {
214	huffmanLength := HuffmanEncodeLength(s)
215	if huffmanLength < uint64(len(s)) {
216		first := len(dst)
217		dst = appendVarInt(dst, 7, huffmanLength)
218		dst = AppendHuffmanString(dst, s)
219		dst[first] |= 0x80
220	} else {
221		dst = appendVarInt(dst, 7, uint64(len(s)))
222		dst = append(dst, s...)
223	}
224	return dst
225}
226
227// encodeTypeByte returns type byte. If sensitive is true, type byte
228// for "Never Indexed" representation is returned. If sensitive is
229// false and indexing is true, type byte for "Incremental Indexing"
230// representation is returned. Otherwise, type byte for "Without
231// Indexing" is returned.
232func encodeTypeByte(indexing, sensitive bool) byte {
233	if sensitive {
234		return 0x10
235	}
236	if indexing {
237		return 0x40
238	}
239	return 0
240}
241