sniffing websocket frame

This commit is contained in:
ginuerzh
2024-10-19 19:36:06 +08:00
parent a0cbee8817
commit 6b932e35bf
28 changed files with 1238 additions and 301 deletions
+22
View File
@@ -0,0 +1,22 @@
package ctx
import (
"context"
xrecorder "github.com/go-gost/x/recorder"
)
type recorderObjectCtxKey struct{}
var (
ctxKeyRecorderObject = &recorderObjectCtxKey{}
)
func ContextWithRecorderObject(ctx context.Context, ro *xrecorder.HandlerRecorderObject) context.Context {
return context.WithValue(ctx, ctxKeyRecorderObject, ro)
}
func RecorderObjectFromContext(ctx context.Context) *xrecorder.HandlerRecorderObject {
v, _ := ctx.Value(ctxKeyRecorderObject).(*xrecorder.HandlerRecorderObject)
return v
}
+198 -26
View File
@@ -32,16 +32,13 @@ import (
xhttp "github.com/go-gost/x/internal/net/http"
"github.com/go-gost/x/internal/util/sniffing"
tls_util "github.com/go-gost/x/internal/util/tls"
ws_util "github.com/go-gost/x/internal/util/ws"
stats_wrapper "github.com/go-gost/x/observer/stats/wrapper"
xrecorder "github.com/go-gost/x/recorder"
"golang.org/x/net/http/httpguts"
"golang.org/x/net/http2"
)
const (
// Default max body size to record.
DefaultBodySize = 1024 * 1024 // 1MB
)
var (
DefaultCertPool = tls_util.NewMemoryCertPool()
)
@@ -102,6 +99,9 @@ func WithLog(log logger.Logger) HandleOption {
}
type Sniffer struct {
Websocket bool
WebsocketSampleRate float64
Recorder recorder.Recorder
RecorderOptions *recorder.Options
@@ -177,7 +177,7 @@ func (h *Sniffer) HandleHTTP(ctx context.Context, conn net.Conn, opts ...HandleO
ro.Time = time.Time{}
shouldClose, err := h.httpRoundTrip(ctx, conn, cc, node, req, &pStats, &ho)
shouldClose, err := h.httpRoundTrip(ctx, xio.NewReadWriter(br, conn), cc, node, req, &pStats, &ho)
if err != nil || shouldClose {
return err
}
@@ -198,7 +198,7 @@ func (h *Sniffer) HandleHTTP(ctx context.Context, conn net.Conn, opts ...HandleO
log.Trace(string(dump))
}
if shouldClose, err := h.httpRoundTrip(ctx, conn, cc, node, req, &pStats, &ho); err != nil || shouldClose {
if shouldClose, err := h.httpRoundTrip(ctx, xio.NewReadWriter(br, conn), cc, node, req, &pStats, &ho); err != nil || shouldClose {
return err
}
}
@@ -441,11 +441,14 @@ func (h *Sniffer) httpRoundTrip(ctx context.Context, rw, cc io.ReadWriter, node
var reqBody *xhttp.Body
if opts := h.RecorderOptions; opts != nil && opts.HTTPBody {
if req.Body != nil {
maxSize := opts.MaxBodySize
if maxSize <= 0 {
maxSize = DefaultBodySize
bodySize := opts.MaxBodySize
if bodySize <= 0 {
bodySize = sniffing.DefaultBodySize
}
reqBody = xhttp.NewBody(req.Body, maxSize)
if bodySize > sniffing.MaxBodySize {
bodySize = sniffing.MaxBodySize
}
reqBody = xhttp.NewBody(req.Body, bodySize)
req.Body = reqBody
}
}
@@ -479,6 +482,11 @@ func (h *Sniffer) httpRoundTrip(ctx context.Context, rw, cc io.ReadWriter, node
log.Trace(string(dump))
}
if resp.StatusCode == http.StatusSwitchingProtocols {
h.handleUpgradeResponse(ctx, rw, cc, req, resp, ro, log)
return
}
// HTTP/1.0
if req.ProtoMajor == 1 && req.ProtoMinor == 0 {
if !resp.Close {
@@ -495,11 +503,14 @@ func (h *Sniffer) httpRoundTrip(ctx context.Context, rw, cc io.ReadWriter, node
var respBody *xhttp.Body
if opts := h.RecorderOptions; opts != nil && opts.HTTPBody {
maxSize := opts.MaxBodySize
if maxSize <= 0 {
maxSize = DefaultBodySize
bodySize := opts.MaxBodySize
if bodySize <= 0 {
bodySize = sniffing.DefaultBodySize
}
respBody = xhttp.NewBody(resp.Body, maxSize)
if bodySize > sniffing.MaxBodySize {
bodySize = sniffing.MaxBodySize
}
respBody = xhttp.NewBody(resp.Body, bodySize)
resp.Body = respBody
}
@@ -513,11 +524,166 @@ func (h *Sniffer) httpRoundTrip(ctx context.Context, rw, cc io.ReadWriter, node
ro.HTTP.Response.ContentLength = respBody.Length()
}
if resp.StatusCode == http.StatusSwitchingProtocols {
xnet.Transport(rw, cc)
return resp.Close, nil
}
func upgradeType(h http.Header) string {
if !httpguts.HeaderValuesContainsToken(h["Connection"], "Upgrade") {
return ""
}
return h.Get("Upgrade")
}
func (h *Sniffer) handleUpgradeResponse(ctx context.Context, rw io.ReadWriter, cc io.ReadWriter, req *http.Request, res *http.Response, ro *xrecorder.HandlerRecorderObject, log logger.Logger) error {
reqUpType := upgradeType(req.Header)
resUpType := upgradeType(res.Header)
if !strings.EqualFold(reqUpType, resUpType) {
return fmt.Errorf("backend tried to switch protocol %q when %q was requested", resUpType, reqUpType)
}
return resp.Close, nil
res.Body = nil
if err := res.Write(rw); err != nil {
return fmt.Errorf("response write: %v", err)
}
if reqUpType == "websocket" && h.Websocket {
return h.sniffingWebsocketFrame(ctx, rw, cc, ro, log)
}
return xnet.Transport(rw, cc)
}
func (h *Sniffer) sniffingWebsocketFrame(ctx context.Context, rw, cc io.ReadWriter, ro *xrecorder.HandlerRecorderObject, log logger.Logger) error {
errc := make(chan error, 1)
sampleRate := h.WebsocketSampleRate
if sampleRate <= 0 {
sampleRate = sniffing.DefaultSampleRate
}
if sampleRate > sniffing.MaxSampleRate {
sampleRate = sniffing.MaxSampleRate
}
d := time.Duration(1 / sampleRate * 1e9)
go func() {
ro2 := &xrecorder.HandlerRecorderObject{}
*ro2 = *ro
ro = ro2
ticker := time.NewTicker(d)
defer ticker.Stop()
buf := &bytes.Buffer{}
for {
start := time.Now()
err := h.copyWebsocketFrame(cc, rw, buf, "client", ro)
select {
case <-ticker.C:
if err != nil {
ro.Err = err.Error()
}
ro.Duration = time.Since(start)
ro.Time = time.Now()
if err := ro.Record(ctx, h.Recorder); err != nil {
log.Errorf("record: %v", err)
}
default:
}
if err != nil {
errc <- err
return
}
}
}()
go func() {
ro2 := &xrecorder.HandlerRecorderObject{}
*ro2 = *ro
ro = ro2
ticker := time.NewTicker(d)
defer ticker.Stop()
buf := &bytes.Buffer{}
for {
start := time.Now()
err := h.copyWebsocketFrame(rw, cc, buf, "server", ro)
select {
case <-ticker.C:
if err != nil {
ro.Err = err.Error()
}
ro.Duration = time.Since(start)
ro.Time = time.Now()
if err := ro.Record(ctx, h.Recorder); err != nil {
log.Errorf("record: %v", err)
}
default:
}
if err != nil {
errc <- err
return
}
}
}()
<-errc
return nil
}
func (h *Sniffer) copyWebsocketFrame(w io.Writer, r io.Reader, buf *bytes.Buffer, from string, ro *xrecorder.HandlerRecorderObject) (err error) {
fr := ws_util.Frame{}
if _, err = fr.ReadFrom(r); err != nil {
return err
}
ws := &xrecorder.WebsocketRecorderObject{
From: from,
Fin: fr.Header.Fin,
Rsv1: fr.Header.Rsv1,
Rsv2: fr.Header.Rsv2,
Rsv3: fr.Header.Rsv3,
OpCode: int(fr.Header.OpCode),
Masked: fr.Header.Masked,
MaskKey: fr.Header.MaskKey,
Length: fr.Header.PayloadLength,
}
if opts := h.RecorderOptions; opts != nil && opts.HTTPBody {
bodySize := opts.MaxBodySize
if bodySize <= 0 {
bodySize = sniffing.DefaultBodySize
}
if bodySize > sniffing.MaxBodySize {
bodySize = sniffing.MaxBodySize
}
buf.Reset()
if _, err := io.Copy(buf, io.LimitReader(fr.Data, int64(bodySize))); err != nil {
return err
}
ws.Payload = buf.Bytes()
}
ro.Websocket = ws
length := uint64(fr.Header.Length()) + uint64(fr.Header.PayloadLength)
if from == "client" {
ro.InputBytes = length
ro.OutputBytes = 0
} else {
ro.InputBytes = 0
ro.OutputBytes = length
}
fr.Data = io.MultiReader(bytes.NewReader(buf.Bytes()), fr.Data)
if _, err := fr.WriteTo(w); err != nil {
return err
}
return nil
}
func (h *Sniffer) rewriteBody(resp *http.Response, rewrites ...chain.HTTPBodyRewriteSettings) error {
@@ -914,11 +1080,14 @@ func (h *h2Handler) ServeHTTP(w http.ResponseWriter, r *http.Request) {
var reqBody *xhttp.Body
if opts := h.recorderOptions; opts != nil && opts.HTTPBody {
if req.Body != nil {
maxSize := opts.MaxBodySize
if maxSize <= 0 {
maxSize = DefaultBodySize
bodySize := opts.MaxBodySize
if bodySize <= 0 {
bodySize = sniffing.DefaultBodySize
}
reqBody = xhttp.NewBody(req.Body, maxSize)
if bodySize > sniffing.MaxBodySize {
bodySize = sniffing.MaxBodySize
}
reqBody = xhttp.NewBody(req.Body, bodySize)
req.Body = reqBody
}
}
@@ -949,11 +1118,14 @@ func (h *h2Handler) ServeHTTP(w http.ResponseWriter, r *http.Request) {
var respBody *xhttp.Body
if opts := h.recorderOptions; opts != nil && opts.HTTPBody {
maxSize := opts.MaxBodySize
if maxSize <= 0 {
maxSize = DefaultBodySize
bodySize := opts.MaxBodySize
if bodySize <= 0 {
bodySize = sniffing.DefaultBodySize
}
respBody = xhttp.NewBody(resp.Body, maxSize)
if bodySize > sniffing.MaxBodySize {
bodySize = sniffing.MaxBodySize
}
respBody = xhttp.NewBody(resp.Body, bodySize)
resp.Body = respBody
}
+209 -25
View File
@@ -28,14 +28,22 @@ import (
xnet "github.com/go-gost/x/internal/net"
xhttp "github.com/go-gost/x/internal/net/http"
tls_util "github.com/go-gost/x/internal/util/tls"
ws_util "github.com/go-gost/x/internal/util/ws"
stats_wrapper "github.com/go-gost/x/observer/stats/wrapper"
xrecorder "github.com/go-gost/x/recorder"
"golang.org/x/net/http/httpguts"
"golang.org/x/net/http2"
)
const (
// Default max body size to record.
DefaultBodySize = 1024 * 1024 // 1MB
// DefaultBodySize is the default HTTP body or websocket frame size to record.
DefaultBodySize = 64 * 1024 // 64KB
// MaxBodySize is the maximum HTTP body or websocket frame size to record.
MaxBodySize = 1024 * 1024 // 1MB
// DeafultSampleRate is the default websocket sample rate (samples per second).
DefaultSampleRate = 10.0
// MaxSampleRate is the maximum websocket sample rate (samples per second).
MaxSampleRate = 100.0
)
var (
@@ -84,6 +92,9 @@ func WithLog(log logger.Logger) HandleOption {
}
type Sniffer struct {
Websocket bool
WebsocketSampleRate float64
Recorder recorder.Recorder
RecorderOptions *recorder.Options
@@ -176,7 +187,7 @@ func (h *Sniffer) HandleHTTP(ctx context.Context, conn net.Conn, opts ...HandleO
ro.Time = time.Time{}
shouldClose, err := h.httpRoundTrip(ctx, conn, cc, req, ro, &pStats, log)
shouldClose, err := h.httpRoundTrip(ctx, xio.NewReadWriter(br, conn), cc, req, ro, &pStats, log)
if err != nil || shouldClose {
return err
}
@@ -197,7 +208,7 @@ func (h *Sniffer) HandleHTTP(ctx context.Context, conn net.Conn, opts ...HandleO
log.Trace(string(dump))
}
if shouldClose, err := h.httpRoundTrip(ctx, conn, cc, req, ro, &pStats, log); err != nil || shouldClose {
if shouldClose, err := h.httpRoundTrip(ctx, xio.NewReadWriter(br, conn), cc, req, ro, &pStats, log); err != nil || shouldClose {
return err
}
}
@@ -302,11 +313,14 @@ func (h *Sniffer) httpRoundTrip(ctx context.Context, rw, cc io.ReadWriter, req *
var reqBody *xhttp.Body
if opts := h.RecorderOptions; opts != nil && opts.HTTPBody {
if req.Body != nil {
maxSize := opts.MaxBodySize
if maxSize <= 0 {
maxSize = DefaultBodySize
bodySize := opts.MaxBodySize
if bodySize <= 0 {
bodySize = DefaultBodySize
}
reqBody = xhttp.NewBody(req.Body, maxSize)
if bodySize > MaxBodySize {
bodySize = MaxBodySize
}
reqBody = xhttp.NewBody(req.Body, bodySize)
req.Body = reqBody
}
}
@@ -323,7 +337,7 @@ func (h *Sniffer) httpRoundTrip(ctx context.Context, rw, cc io.ReadWriter, req *
xio.SetReadDeadline(cc, time.Now().Add(h.ReadTimeout))
resp, err := http.ReadResponse(bufio.NewReader(cc), req)
if err != nil {
log.Errorf("read response: %v", err)
err = fmt.Errorf("read response: %w", err)
return
}
defer resp.Body.Close()
@@ -338,6 +352,11 @@ func (h *Sniffer) httpRoundTrip(ctx context.Context, rw, cc io.ReadWriter, req *
log.Trace(string(dump))
}
if resp.StatusCode == http.StatusSwitchingProtocols {
h.handleUpgradeResponse(ctx, rw, cc, req, resp, ro, log)
return
}
// HTTP/1.0
if req.ProtoMajor == 1 && req.ProtoMinor == 0 {
if !resp.Close {
@@ -349,16 +368,19 @@ func (h *Sniffer) httpRoundTrip(ctx context.Context, rw, cc io.ReadWriter, req *
var respBody *xhttp.Body
if opts := h.RecorderOptions; opts != nil && opts.HTTPBody {
maxSize := opts.MaxBodySize
if maxSize <= 0 {
maxSize = DefaultBodySize
bodySize := opts.MaxBodySize
if bodySize <= 0 {
bodySize = DefaultBodySize
}
respBody = xhttp.NewBody(resp.Body, maxSize)
if bodySize > MaxBodySize {
bodySize = MaxBodySize
}
respBody = xhttp.NewBody(resp.Body, bodySize)
resp.Body = respBody
}
if err = resp.Write(rw); err != nil {
log.Errorf("write response: %v", err)
err = fmt.Errorf("write response: %w", err)
return
}
@@ -367,11 +389,166 @@ func (h *Sniffer) httpRoundTrip(ctx context.Context, rw, cc io.ReadWriter, req *
ro.HTTP.Response.ContentLength = respBody.Length()
}
if resp.StatusCode == http.StatusSwitchingProtocols {
xnet.Transport(rw, cc)
return resp.Close, nil
}
func upgradeType(h http.Header) string {
if !httpguts.HeaderValuesContainsToken(h["Connection"], "Upgrade") {
return ""
}
return h.Get("Upgrade")
}
func (h *Sniffer) handleUpgradeResponse(ctx context.Context, rw io.ReadWriter, cc io.ReadWriter, req *http.Request, res *http.Response, ro *xrecorder.HandlerRecorderObject, log logger.Logger) error {
reqUpType := upgradeType(req.Header)
resUpType := upgradeType(res.Header)
if !strings.EqualFold(reqUpType, resUpType) {
return fmt.Errorf("backend tried to switch protocol %q when %q was requested", resUpType, reqUpType)
}
return resp.Close, nil
res.Body = nil
if err := res.Write(rw); err != nil {
return fmt.Errorf("response write: %v", err)
}
if reqUpType == "websocket" && h.Websocket {
return h.sniffingWebsocketFrame(ctx, rw, cc, ro, log)
}
return xnet.Transport(rw, cc)
}
func (h *Sniffer) sniffingWebsocketFrame(ctx context.Context, rw, cc io.ReadWriter, ro *xrecorder.HandlerRecorderObject, log logger.Logger) error {
errc := make(chan error, 1)
sampleRate := h.WebsocketSampleRate
if sampleRate <= 0 {
sampleRate = DefaultSampleRate
}
if sampleRate > MaxSampleRate {
sampleRate = MaxSampleRate
}
d := time.Duration(1 / sampleRate * 1e9)
go func() {
ro2 := &xrecorder.HandlerRecorderObject{}
*ro2 = *ro
ro = ro2
ticker := time.NewTicker(d)
defer ticker.Stop()
buf := &bytes.Buffer{}
for {
start := time.Now()
err := h.copyWebsocketFrame(cc, rw, buf, "client", ro)
select {
case <-ticker.C:
if err != nil {
ro.Err = err.Error()
}
ro.Duration = time.Since(start)
ro.Time = time.Now()
if err := ro.Record(ctx, h.Recorder); err != nil {
log.Errorf("record: %v", err)
}
default:
}
if err != nil {
errc <- err
return
}
}
}()
go func() {
ro2 := &xrecorder.HandlerRecorderObject{}
*ro2 = *ro
ro = ro2
ticker := time.NewTicker(d)
defer ticker.Stop()
buf := &bytes.Buffer{}
for {
start := time.Now()
err := h.copyWebsocketFrame(rw, cc, buf, "server", ro)
select {
case <-ticker.C:
if err != nil {
ro.Err = err.Error()
}
ro.Duration = time.Since(start)
ro.Time = time.Now()
if err := ro.Record(ctx, h.Recorder); err != nil {
log.Errorf("record: %v", err)
}
default:
}
if err != nil {
errc <- err
return
}
}
}()
<-errc
return nil
}
func (h *Sniffer) copyWebsocketFrame(w io.Writer, r io.Reader, buf *bytes.Buffer, from string, ro *xrecorder.HandlerRecorderObject) (err error) {
fr := ws_util.Frame{}
if _, err = fr.ReadFrom(r); err != nil {
return err
}
ws := &xrecorder.WebsocketRecorderObject{
From: from,
Fin: fr.Header.Fin,
Rsv1: fr.Header.Rsv1,
Rsv2: fr.Header.Rsv2,
Rsv3: fr.Header.Rsv3,
OpCode: int(fr.Header.OpCode),
Masked: fr.Header.Masked,
MaskKey: fr.Header.MaskKey,
Length: fr.Header.PayloadLength,
}
if opts := h.RecorderOptions; opts != nil && opts.HTTPBody {
bodySize := opts.MaxBodySize
if bodySize <= 0 {
bodySize = DefaultBodySize
}
if bodySize > MaxBodySize {
bodySize = MaxBodySize
}
buf.Reset()
if _, err := io.Copy(buf, io.LimitReader(fr.Data, int64(bodySize))); err != nil {
return err
}
ws.Payload = buf.Bytes()
}
ro.Websocket = ws
length := uint64(fr.Header.Length()) + uint64(fr.Header.PayloadLength)
if from == "client" {
ro.InputBytes = length
ro.OutputBytes = 0
} else {
ro.InputBytes = 0
ro.OutputBytes = length
}
fr.Data = io.MultiReader(bytes.NewReader(buf.Bytes()), fr.Data)
if _, err := fr.WriteTo(w); err != nil {
return err
}
return nil
}
func (h *Sniffer) HandleTLS(ctx context.Context, conn net.Conn, opts ...HandleOption) error {
@@ -644,11 +821,15 @@ func (h *h2Handler) ServeHTTP(w http.ResponseWriter, r *http.Request) {
var reqBody *xhttp.Body
if opts := h.recorderOptions; opts != nil && opts.HTTPBody {
if req.Body != nil {
maxSize := opts.MaxBodySize
if maxSize <= 0 {
maxSize = DefaultBodySize
bodySize := opts.MaxBodySize
if bodySize <= 0 {
bodySize = DefaultBodySize
}
reqBody = xhttp.NewBody(req.Body, maxSize)
if bodySize > MaxBodySize {
bodySize = MaxBodySize
}
reqBody = xhttp.NewBody(req.Body, bodySize)
req.Body = reqBody
}
}
@@ -679,11 +860,14 @@ func (h *h2Handler) ServeHTTP(w http.ResponseWriter, r *http.Request) {
var respBody *xhttp.Body
if opts := h.recorderOptions; opts != nil && opts.HTTPBody {
maxSize := opts.MaxBodySize
if maxSize <= 0 {
maxSize = DefaultBodySize
bodySize := opts.MaxBodySize
if bodySize <= 0 {
bodySize = DefaultBodySize
}
respBody = xhttp.NewBody(resp.Body, maxSize)
if bodySize > MaxBodySize {
bodySize = MaxBodySize
}
respBody = xhttp.NewBody(resp.Body, bodySize)
resp.Body = respBody
}
+196
View File
@@ -0,0 +1,196 @@
package ws
import (
"encoding/binary"
"fmt"
"io"
"math"
)
// OpCode represents a WebSocket opcode.
type OpCode int
// https://tools.ietf.org/html/rfc6455#section-11.8.
const (
OpContinuation OpCode = iota
OpText
OpBinary
// 3 - 7 are reserved for further non-control frames.
_
_
_
_
_
OpClose
OpPing
OpPong
// 11-16 are reserved for further control frames.
)
// FrameHeader represents a WebSocket frame header.
// See https://tools.ietf.org/html/rfc6455#section-5.2.
type FrameHeader struct {
Fin bool
Rsv1 bool
Rsv2 bool
Rsv3 bool
OpCode OpCode
PayloadLength int64
Masked bool
MaskKey uint32
}
// ReadFrom reads a header from the reader.
// See https://tools.ietf.org/html/rfc6455#section-5.2.
func (h *FrameHeader) ReadFrom(r io.Reader) (n int64, err error) {
var buf [8]byte
// First byte. FIN/RSV1/RSV2/RSV3/OpCode(4bits)
nn, err := io.ReadFull(r, buf[:2])
n += int64(n)
if err != nil {
return
}
b := buf[0]
h.Fin = b&(1<<7) != 0
h.Rsv1 = b&(1<<6) != 0
h.Rsv2 = b&(1<<5) != 0
h.Rsv3 = b&(1<<4) != 0
h.OpCode = OpCode(b & 0xf)
b = buf[1]
h.Masked = b&(1<<7) != 0
payloadLength := b &^ (1 << 7)
switch {
case payloadLength < 126:
h.PayloadLength = int64(payloadLength)
case payloadLength == 126:
nn, err = io.ReadFull(r, buf[:2])
h.PayloadLength = int64(binary.BigEndian.Uint16(buf[:]))
case payloadLength == 127:
nn, err = io.ReadFull(r, buf[:])
h.PayloadLength = int64(binary.BigEndian.Uint64(buf[:]))
}
n += int64(nn)
if err != nil {
return
}
if h.PayloadLength < 0 {
err = fmt.Errorf("received negative payload length: %v", h.PayloadLength)
return
}
if h.Masked {
nn, err = io.ReadFull(r, buf[:4])
n += int64(nn)
if err != nil {
return
}
h.MaskKey = binary.LittleEndian.Uint32(buf[:])
}
return
}
func (h FrameHeader) Length() int {
n := 2
switch {
case h.PayloadLength > math.MaxUint16:
n += 8
case h.PayloadLength > 125:
n += 2
}
if h.Masked {
n += 4
}
return n
}
func (h *FrameHeader) WriteTo(w io.Writer) (n int64, err error) {
var buf [14]byte
pos := 0
var b byte
if h.Fin {
b |= 1 << 7
}
if h.Rsv1 {
b |= 1 << 6
}
if h.Rsv2 {
b |= 1 << 5
}
if h.Rsv3 {
b |= 1 << 4
}
b |= byte(h.OpCode)
buf[0] = b
lengthByte := byte(0)
if h.Masked {
lengthByte |= 1 << 7
}
switch {
case h.PayloadLength > math.MaxUint16:
lengthByte |= 127
case h.PayloadLength > 125:
lengthByte |= 126
case h.PayloadLength >= 0:
lengthByte |= byte(h.PayloadLength)
}
buf[1] = lengthByte
pos = 2
switch {
case h.PayloadLength > math.MaxUint16:
binary.BigEndian.PutUint64(buf[2:], uint64(h.PayloadLength))
pos += 8
case h.PayloadLength > 125:
binary.BigEndian.PutUint16(buf[2:], uint16(h.PayloadLength))
pos += 2
}
if h.Masked {
binary.LittleEndian.PutUint32(buf[pos:], h.MaskKey)
pos += 4
}
nn, err := w.Write(buf[:pos])
n = int64(nn)
return
}
type Frame struct {
Header FrameHeader
Data io.Reader
}
func (fr *Frame) ReadFrom(r io.Reader) (n int64, err error) {
if n, err = fr.Header.ReadFrom(r); err != nil {
return
}
fr.Data = io.LimitReader(r, fr.Header.PayloadLength)
return
}
func (fr *Frame) WriteTo(w io.Writer) (n int64, err error) {
n, err = fr.Header.WriteTo(w)
if err != nil {
return
}
nn, err := io.Copy(w, fr.Data)
n += nn
return
}