96 lines
2.6 KiB
Go
96 lines
2.6 KiB
Go
|
package qpack
|
||
|
|
||
|
import (
|
||
|
"io"
|
||
|
|
||
|
"golang.org/x/net/http2/hpack"
|
||
|
)
|
||
|
|
||
|
// An Encoder performs QPACK encoding.
|
||
|
type Encoder struct {
|
||
|
wrotePrefix bool
|
||
|
|
||
|
w io.Writer
|
||
|
buf []byte
|
||
|
}
|
||
|
|
||
|
// NewEncoder returns a new Encoder which performs QPACK encoding. An
|
||
|
// encoded data is written to w.
|
||
|
func NewEncoder(w io.Writer) *Encoder {
|
||
|
return &Encoder{w: w}
|
||
|
}
|
||
|
|
||
|
// WriteField encodes f into a single Write to e's underlying Writer.
|
||
|
// This function may also produce bytes for the Header Block Prefix
|
||
|
// if necessary. If produced, it is done before encoding f.
|
||
|
func (e *Encoder) WriteField(f HeaderField) error {
|
||
|
// write the Header Block Prefix
|
||
|
if !e.wrotePrefix {
|
||
|
e.buf = appendVarInt(e.buf, 8, 0)
|
||
|
e.buf = appendVarInt(e.buf, 7, 0)
|
||
|
e.wrotePrefix = true
|
||
|
}
|
||
|
|
||
|
idxAndVals, nameFound := encoderMap[f.Name]
|
||
|
if nameFound {
|
||
|
if idxAndVals.values == nil {
|
||
|
if len(f.Value) == 0 {
|
||
|
e.writeIndexedField(idxAndVals.idx)
|
||
|
} else {
|
||
|
e.writeLiteralFieldWithNameReference(&f, idxAndVals.idx)
|
||
|
}
|
||
|
} else {
|
||
|
valIdx, valueFound := idxAndVals.values[f.Value]
|
||
|
if valueFound {
|
||
|
e.writeIndexedField(valIdx)
|
||
|
} else {
|
||
|
e.writeLiteralFieldWithNameReference(&f, idxAndVals.idx)
|
||
|
}
|
||
|
}
|
||
|
} else {
|
||
|
e.writeLiteralFieldWithoutNameReference(f)
|
||
|
}
|
||
|
|
||
|
_, err := e.w.Write(e.buf)
|
||
|
e.buf = e.buf[:0]
|
||
|
return err
|
||
|
}
|
||
|
|
||
|
// Close declares that the encoding is complete and resets the Encoder
|
||
|
// to be reused again for a new header block.
|
||
|
func (e *Encoder) Close() error {
|
||
|
e.wrotePrefix = false
|
||
|
return nil
|
||
|
}
|
||
|
|
||
|
func (e *Encoder) writeLiteralFieldWithoutNameReference(f HeaderField) {
|
||
|
offset := len(e.buf)
|
||
|
e.buf = appendVarInt(e.buf, 3, hpack.HuffmanEncodeLength(f.Name))
|
||
|
e.buf[offset] ^= 0x20 ^ 0x8
|
||
|
e.buf = hpack.AppendHuffmanString(e.buf, f.Name)
|
||
|
offset = len(e.buf)
|
||
|
e.buf = appendVarInt(e.buf, 7, hpack.HuffmanEncodeLength(f.Value))
|
||
|
e.buf[offset] ^= 0x80
|
||
|
e.buf = hpack.AppendHuffmanString(e.buf, f.Value)
|
||
|
}
|
||
|
|
||
|
// Encodes a header field whose name is present in one of the tables.
|
||
|
func (e *Encoder) writeLiteralFieldWithNameReference(f *HeaderField, id uint8) {
|
||
|
offset := len(e.buf)
|
||
|
e.buf = appendVarInt(e.buf, 4, uint64(id))
|
||
|
// Set the 01NTxxxx pattern, forcing N to 0 and T to 1
|
||
|
e.buf[offset] ^= 0x50
|
||
|
offset = len(e.buf)
|
||
|
e.buf = appendVarInt(e.buf, 7, hpack.HuffmanEncodeLength(f.Value))
|
||
|
e.buf[offset] ^= 0x80
|
||
|
e.buf = hpack.AppendHuffmanString(e.buf, f.Value)
|
||
|
}
|
||
|
|
||
|
// Encodes an indexed field, meaning it's entirely defined in one of the tables.
|
||
|
func (e *Encoder) writeIndexedField(id uint8) {
|
||
|
offset := len(e.buf)
|
||
|
e.buf = appendVarInt(e.buf, 6, uint64(id))
|
||
|
// Set the 1Txxxxxx pattern, forcing T to 1
|
||
|
e.buf[offset] ^= 0xc0
|
||
|
}
|