mirror of
https://github.com/MeshTender/MeshTender.git
synced 2026-09-01 17:38:15 +00:00
Cap the wsbridge KISS reassembly buffer
This commit is contained in:
@@ -13,12 +13,27 @@ import (
|
||||
"bytes"
|
||||
"context"
|
||||
"encoding/json"
|
||||
"errors"
|
||||
"sync"
|
||||
|
||||
"github.com/coder/websocket"
|
||||
"github.com/meshcore-go/meshcore-go/hardware"
|
||||
)
|
||||
|
||||
// maxKissBuffer bounds the inbound reassembly buffer. A single KISS frame carries
|
||||
// at most one MeshCore packet — MAX_TRANS_UNIT is 255 bytes over the air (firmware
|
||||
// src/MeshCore.h) and meshcore-go caps a KISS packet at KISS_MAX_PACKET_SIZE=255 —
|
||||
// so even with worst-case KISS byte-stuffing (every byte escaped ×2) plus FEND
|
||||
// framing a frame is ~520 bytes on the wire. This buffer only ever holds one
|
||||
// not-yet-FEND-terminated frame, so 4 KiB is generous headroom; if this much
|
||||
// accumulates with no frame delimiter the peer is sending garbage, so we drop it
|
||||
// instead of letting it grow without bound.
|
||||
const maxKissBuffer = 4096
|
||||
|
||||
// ErrKissBufferOverflow is reported to the error handler when the reassembly
|
||||
// buffer is dropped for exceeding maxKissBuffer with no frame boundary.
|
||||
var ErrKissBufferOverflow = errors.New("wsbridge: inbound KISS buffer overflow; discarding")
|
||||
|
||||
// Conn implements hardware.Transport over a WebSocket.
|
||||
type Conn struct {
|
||||
ws *websocket.Conn
|
||||
@@ -101,8 +116,18 @@ func (c *Conn) Feed(data []byte) {
|
||||
c.buf = append(c.buf, data...)
|
||||
last := bytes.LastIndexByte(c.buf, hardware.KISS_FEND)
|
||||
if last < 0 {
|
||||
// No complete frame boundary yet. Cap the buffer so a peer that never sends
|
||||
// a FEND can't grow it without bound; drop it and report the overflow.
|
||||
var eh func(error)
|
||||
if len(c.buf) > maxKissBuffer {
|
||||
c.buf = nil
|
||||
eh = c.errH
|
||||
}
|
||||
c.mu.Unlock()
|
||||
return // no complete frame boundary yet
|
||||
if eh != nil {
|
||||
eh(ErrKissBufferOverflow)
|
||||
}
|
||||
return
|
||||
}
|
||||
processable := c.buf[:last+1]
|
||||
c.buf = append([]byte(nil), c.buf[last+1:]...) // bytes after the last FEND
|
||||
|
||||
@@ -1,7 +1,9 @@
|
||||
package wsbridge
|
||||
|
||||
import (
|
||||
"bytes"
|
||||
"context"
|
||||
"errors"
|
||||
"testing"
|
||||
|
||||
"github.com/meshcore-go/meshcore-go/hardware"
|
||||
@@ -42,6 +44,40 @@ func TestFeedReassemblesAcrossChunkBoundaries(t *testing.T) {
|
||||
assertCmds(t, got, wantCmds, "byte-by-byte")
|
||||
}
|
||||
|
||||
// TestFeedCapsBufferWithoutFrameBoundary verifies that a peer streaming bytes
|
||||
// with no FEND delimiter can't grow the reassembly buffer without bound: past the
|
||||
// cap the buffer is dropped and the overflow is reported, and framing recovers on
|
||||
// the next valid frame.
|
||||
func TestFeedCapsBufferWithoutFrameBoundary(t *testing.T) {
|
||||
t.Parallel()
|
||||
c := New(context.Background(), nil)
|
||||
|
||||
var errs []error
|
||||
c.SetErrorHandler(func(err error) { errs = append(errs, err) })
|
||||
var got []*hardware.KissFrame
|
||||
c.SetFrameHandler(func(f *hardware.KissFrame) { got = append(got, f) })
|
||||
|
||||
// Stream well past the cap with no FEND (0xC0) byte anywhere.
|
||||
junk := bytes.Repeat([]byte{0x00}, maxKissBuffer+1024)
|
||||
c.Feed(junk)
|
||||
|
||||
c.mu.Lock()
|
||||
buffered := len(c.buf)
|
||||
c.mu.Unlock()
|
||||
if buffered > maxKissBuffer {
|
||||
t.Fatalf("buffer = %d bytes, want it dropped to <= %d", buffered, maxKissBuffer)
|
||||
}
|
||||
if len(errs) != 1 || !errors.Is(errs[0], ErrKissBufferOverflow) {
|
||||
t.Fatalf("errs = %v, want one ErrKissBufferOverflow", errs)
|
||||
}
|
||||
|
||||
// Framing still works after the drop: a valid frame decodes normally.
|
||||
c.Feed(hardware.EncodeDataFrame([]byte{0xAA, 0xBB}))
|
||||
if len(got) != 1 || got[0].Command != hardware.KISS_CMD_DATA {
|
||||
t.Fatalf("got %d frames after overflow, want 1 data frame", len(got))
|
||||
}
|
||||
}
|
||||
|
||||
func feedChunks(t *testing.T, chunks ...[]byte) []*hardware.KissFrame {
|
||||
t.Helper()
|
||||
c := New(context.Background(), nil) // Feed doesn't touch the websocket
|
||||
|
||||
Reference in New Issue
Block a user