golang.org/x/net@v0.25.1-0.20240516223405-c87a5b62e243/http2/hpack/encode.go (about) 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 5 package hpack 6 7 import ( 8 "io" 9 ) 10 11 const ( 12 uint32Max = ^uint32(0) 13 initialHeaderTableSize = 4096 14 ) 15 16 type 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. 35 func 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. 50 func (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. 91 func (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. 108 func (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 // MaxDynamicTableSize returns the current dynamic header table size. 120 func (e *Encoder) MaxDynamicTableSize() (v uint32) { 121 return e.dynTab.maxSize 122 } 123 124 // SetMaxDynamicTableSizeLimit changes the maximum value that can be 125 // specified in SetMaxDynamicTableSize to v. By default, it is set to 126 // 4096, which is the same size of the default dynamic header table 127 // size described in HPACK specification. If the current maximum 128 // dynamic header table size is strictly greater than v, "Header Table 129 // Size Update" will be done in the next WriteField call and the 130 // maximum dynamic header table size is truncated to v. 131 func (e *Encoder) SetMaxDynamicTableSizeLimit(v uint32) { 132 e.maxSizeLimit = v 133 if e.dynTab.maxSize > v { 134 e.tableSizeUpdate = true 135 e.dynTab.setMaxSize(v) 136 } 137 } 138 139 // shouldIndex reports whether f should be indexed. 140 func (e *Encoder) shouldIndex(f HeaderField) bool { 141 return !f.Sensitive && f.Size() <= e.dynTab.maxSize 142 } 143 144 // appendIndexed appends index i, as encoded in "Indexed Header Field" 145 // representation, to dst and returns the extended buffer. 146 func appendIndexed(dst []byte, i uint64) []byte { 147 first := len(dst) 148 dst = appendVarInt(dst, 7, i) 149 dst[first] |= 0x80 150 return dst 151 } 152 153 // appendNewName appends f, as encoded in one of "Literal Header field 154 // - New Name" representation variants, to dst and returns the 155 // extended buffer. 156 // 157 // If f.Sensitive is true, "Never Indexed" representation is used. If 158 // f.Sensitive is false and indexing is true, "Incremental Indexing" 159 // representation is used. 160 func appendNewName(dst []byte, f HeaderField, indexing bool) []byte { 161 dst = append(dst, encodeTypeByte(indexing, f.Sensitive)) 162 dst = appendHpackString(dst, f.Name) 163 return appendHpackString(dst, f.Value) 164 } 165 166 // appendIndexedName appends f and index i referring indexed name 167 // entry, as encoded in one of "Literal Header field - Indexed Name" 168 // representation variants, to dst and returns the extended buffer. 169 // 170 // If f.Sensitive is true, "Never Indexed" representation is used. If 171 // f.Sensitive is false and indexing is true, "Incremental Indexing" 172 // representation is used. 173 func appendIndexedName(dst []byte, f HeaderField, i uint64, indexing bool) []byte { 174 first := len(dst) 175 var n byte 176 if indexing { 177 n = 6 178 } else { 179 n = 4 180 } 181 dst = appendVarInt(dst, n, i) 182 dst[first] |= encodeTypeByte(indexing, f.Sensitive) 183 return appendHpackString(dst, f.Value) 184 } 185 186 // appendTableSize appends v, as encoded in "Header Table Size Update" 187 // representation, to dst and returns the extended buffer. 188 func appendTableSize(dst []byte, v uint32) []byte { 189 first := len(dst) 190 dst = appendVarInt(dst, 5, uint64(v)) 191 dst[first] |= 0x20 192 return dst 193 } 194 195 // appendVarInt appends i, as encoded in variable integer form using n 196 // bit prefix, to dst and returns the extended buffer. 197 // 198 // See 199 // https://httpwg.org/specs/rfc7541.html#integer.representation 200 func appendVarInt(dst []byte, n byte, i uint64) []byte { 201 k := uint64((1 << n) - 1) 202 if i < k { 203 return append(dst, byte(i)) 204 } 205 dst = append(dst, byte(k)) 206 i -= k 207 for ; i >= 128; i >>= 7 { 208 dst = append(dst, byte(0x80|(i&0x7f))) 209 } 210 return append(dst, byte(i)) 211 } 212 213 // appendHpackString appends s, as encoded in "String Literal" 214 // representation, to dst and returns the extended buffer. 215 // 216 // s will be encoded in Huffman codes only when it produces strictly 217 // shorter byte string. 218 func appendHpackString(dst []byte, s string) []byte { 219 huffmanLength := HuffmanEncodeLength(s) 220 if huffmanLength < uint64(len(s)) { 221 first := len(dst) 222 dst = appendVarInt(dst, 7, huffmanLength) 223 dst = AppendHuffmanString(dst, s) 224 dst[first] |= 0x80 225 } else { 226 dst = appendVarInt(dst, 7, uint64(len(s))) 227 dst = append(dst, s...) 228 } 229 return dst 230 } 231 232 // encodeTypeByte returns type byte. If sensitive is true, type byte 233 // for "Never Indexed" representation is returned. If sensitive is 234 // false and indexing is true, type byte for "Incremental Indexing" 235 // representation is returned. Otherwise, type byte for "Without 236 // Indexing" is returned. 237 func encodeTypeByte(indexing, sensitive bool) byte { 238 if sensitive { 239 return 0x10 240 } 241 if indexing { 242 return 0x40 243 } 244 return 0 245 }