package http2
import (
"fmt"
"golang.org/x/net/http2/hpack"
)
// Decoder wraps hpack.Decoder with a scratch buffer and a decoded-header
// accumulator. Each (H2 stream, direction) pair owns one decoder because
// the dynamic table is shared across frames for that pair.
type Decoder struct {
d *hpack.Decoder
buf []hpack.HeaderField
}
// NewHPACK returns a new Decoder with the standard table size. The tap
// never sends SETTINGS, so we assume the 4 KiB default.
func NewHPACK() *Decoder {
dec := &Decoder{}
dec.d = hpack.NewDecoder(4096, func(f hpack.HeaderField) {
dec.buf = append(dec.buf, f)
})
return dec
}
// DecodeBlock consumes one complete header block (possibly assembled from
// multiple CONTINUATION fragments) and returns the decoded field list.
func (d *Decoder) DecodeBlock(block []byte) ([][2]string, error) {
d.buf = d.buf[:0]
if _, err := d.d.Write(block); err != nil {
return nil, fmt.Errorf("http2: hpack write: %w", err)
}
if err := d.d.Close(); err != nil {
return nil, fmt.Errorf("http2: hpack close: %w", err)
}
out := make([][2]string, len(d.buf))
for i, f := range d.buf {
out[i] = [2]string{f.Name, f.Value}
}
return out, nil
}
// SplitPseudo separates :method/:path/:status/:scheme/:authority from
// the regular headers in a decoded block. Pseudo headers precede normal
// ones per RFC 7540 section 8.1.2.1.
func SplitPseudo(h [][2]string) (pseudo, regular [][2]string) {
for _, kv := range h {
if len(kv[0]) > 0 && kv[0][0] == ':' {
pseudo = append(pseudo, kv)
} else {
regular = append(regular, kv)
}
}
return pseudo, regular
}
// PseudoGet fetches the value of a single pseudo header by name
// (including the leading colon) or returns "" if missing.
func PseudoGet(h [][2]string, name string) string {
for _, kv := range h {
if kv[0] == name {
return kv[1]
}
}
return ""
}
// AssembleStartLine reconstructs an HTTP/1.1-style start line from
// pseudo headers so that the TUI renderer can reuse the h1 layout code.
func AssembleStartLine(h [][2]string) string {
if st := PseudoGet(h, ":status"); st != "" {
return "HTTP/2 " + st
}
m := PseudoGet(h, ":method")
p := PseudoGet(h, ":path")
a := PseudoGet(h, ":authority")
if m == "" || p == "" {
return ""
}
if a != "" {
return m + " " + a + p + " HTTP/2"
}
return m + " " + p + " HTTP/2"
}