Files
go-omni/transport/http2/frameObserver.go

107 lines
2.4 KiB
Go

package http2
import (
"crypto/tls"
"net"
"sync"
)
const (
frameTypeRSTStream byte = 0x3
frameTypeGoAway byte = 0x7
)
type frameDirection uint8
const (
frameRead frameDirection = iota
frameWrite
)
// observedConn keeps the HTTP/2 implementation opaque while exposing the
// small amount of frame-level telemetry that x/net/http2 does not provide.
// It parses only the nine-byte frame header and skips payload bytes without
// buffering them, so large DATA frames cannot increase telemetry memory.
type observedConn struct {
net.Conn
onFrame func(frameDirection, byte)
readParser frameParser
writeParser frameParser
writeMu sync.Mutex
}
func (c *observedConn) Read(buffer []byte) (int, error) {
n, err := c.Conn.Read(buffer)
if n > 0 {
c.readParser.observe(buffer[:n], frameRead, c.onFrame)
}
return n, err
}
func (c *observedConn) Write(buffer []byte) (int, error) {
c.writeMu.Lock()
defer c.writeMu.Unlock()
n, err := c.Conn.Write(buffer)
if n > 0 {
c.writeParser.observe(buffer[:n], frameWrite, c.onFrame)
}
return n, err
}
// observedTLSConn preserves the connection state hook used by x/net/http2 so
// handlers still receive Request.TLS after frame observation is added.
type observedTLSConn struct {
*observedConn
state tls.ConnectionState
}
func (c *observedTLSConn) ConnectionState() tls.ConnectionState { return c.state }
type frameParser struct {
prefaceRemain int
header [9]byte
headerBytes int
payloadRemain int
frameType byte
}
func (p *frameParser) observe(buffer []byte, direction frameDirection, onFrame func(frameDirection, byte)) {
for len(buffer) > 0 {
if p.prefaceRemain > 0 {
skip := p.prefaceRemain
if skip > len(buffer) {
skip = len(buffer)
}
p.prefaceRemain -= skip
buffer = buffer[skip:]
continue
}
if p.payloadRemain > 0 {
skip := p.payloadRemain
if skip > len(buffer) {
skip = len(buffer)
}
p.payloadRemain -= skip
buffer = buffer[skip:]
if p.payloadRemain > 0 {
continue
}
onFrame(direction, p.frameType)
continue
}
copied := copy(p.header[p.headerBytes:], buffer)
p.headerBytes += copied
buffer = buffer[copied:]
if p.headerBytes < len(p.header) {
continue
}
p.payloadRemain = int(p.header[0])<<16 | int(p.header[1])<<8 | int(p.header[2])
p.frameType = p.header[3]
p.headerBytes = 0
if p.payloadRemain == 0 {
onFrame(direction, p.frameType)
}
}
}