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) } } }