aboutsummaryrefslogtreecommitdiff
path: root/format/sideband64k/decoder.go
diff options
context:
space:
mode:
authorGravatar Runxi Yu2026-03-06 16:23:26 +0800
committerGravatar Runxi Yu2026-03-06 16:23:26 +0800
commite808bdaa15b4df61d9575192fc34cde77890da1c (patch)
tree5ca37ced633e43d5228cb0fa62520f1c6a2ca631 /format/sideband64k/decoder.go
parentci: Add go-fix (diff)
signatureNo signature
format/sideband64k: Add side-band-64k v0.1.66
Diffstat (limited to 'format/sideband64k/decoder.go')
-rw-r--r--format/sideband64k/decoder.go155
1 files changed, 155 insertions, 0 deletions
diff --git a/format/sideband64k/decoder.go b/format/sideband64k/decoder.go
new file mode 100644
index 00000000..8c2b48d5
--- /dev/null
+++ b/format/sideband64k/decoder.go
@@ -0,0 +1,155 @@
+package sideband64k
+
+import (
+ "fmt"
+ "io"
+
+ "codeberg.org/lindenii/furgit/format/pktline"
+)
+
+// ReadOptions controls sideband decoding behavior.
+type ReadOptions struct {
+ // ChompLF removes one trailing '\n' from FrameData payloads only.
+ ChompLF bool
+}
+
+// Decoder reads side-band-64k frames from an io.Reader.
+//
+// It preserves frame boundaries and supports one-frame lookahead via
+// PeekFrame.
+type Decoder struct {
+ dec *pktline.Decoder
+ maxData int
+ opts ReadOptions
+
+ peeked bool
+ peek Frame
+ peekErr error
+}
+
+// NewDecoder creates a decoder over r.
+func NewDecoder(r io.Reader, opts ReadOptions) *Decoder {
+ d := &Decoder{
+ dec: pktline.NewDecoder(r, pktline.ReadOptions{}),
+ maxData: DataMax,
+ opts: opts,
+ }
+ d.dec.SetMaxData(pktline.LargePacketDataMax)
+ return d
+}
+
+// SetMaxData sets maximum payload size accepted for one sideband data packet.
+//
+// Non-positive n resets to DataMax.
+func (d *Decoder) SetMaxData(n int) {
+ if n <= 0 {
+ d.maxData = DataMax
+ return
+ }
+
+ d.maxData = n
+}
+
+// ReadFrame reads one frame.
+func (d *Decoder) ReadFrame() (Frame, error) {
+ if d.peeked {
+ d.peeked = false
+ return cloneFrame(d.peek), d.peekErr
+ }
+
+ return d.readFrame()
+}
+
+// PeekFrame returns the next frame without consuming it.
+func (d *Decoder) PeekFrame() (Frame, error) {
+ if !d.peeked {
+ d.peek, d.peekErr = d.readFrame()
+ d.peeked = true
+ }
+
+ return cloneFrame(d.peek), d.peekErr
+}
+
+func (d *Decoder) readFrame() (Frame, error) {
+ f, err := d.dec.ReadFrame()
+ if err != nil {
+ return Frame{}, err
+ }
+
+ switch f.Type {
+ case pktline.PacketFlush:
+ return Frame{Type: FrameFlush}, nil
+ case pktline.PacketDelim:
+ return Frame{Type: FrameDelim}, nil
+ case pktline.PacketResponseEnd:
+ return Frame{Type: FrameResponseEnd}, nil
+ case pktline.PacketData:
+ if len(f.Payload) == 0 {
+ return Frame{}, &ProtocolError{Reason: "missing sideband designator"}
+ }
+
+ payload := f.Payload[1:]
+ if len(payload) > d.effectiveMaxData() {
+ return Frame{}, fmt.Errorf("%w: %d > %d", ErrTooLarge, len(payload), d.effectiveMaxData())
+ }
+
+ band := Band(f.Payload[0])
+ if !validBand(band) {
+ return Frame{}, &ProtocolError{Reason: fmt.Sprintf("%v: %d", ErrInvalidBand, band)}
+ }
+
+ payload = append([]byte(nil), payload...)
+ if d.opts.ChompLF && band == BandData && len(payload) > 0 && payload[len(payload)-1] == '\n' {
+ payload = payload[:len(payload)-1]
+ }
+
+ return Frame{
+ Type: frameTypeForBand(band),
+ Payload: payload,
+ }, nil
+ default:
+ return Frame{}, &ProtocolError{Reason: "unknown pkt-line frame type"}
+ }
+}
+
+func (d *Decoder) effectiveMaxData() int {
+ return effectiveMaxData(d.maxData)
+}
+
+func cloneFrame(f Frame) Frame {
+ if f.Type == FrameFlush || f.Type == FrameDelim || f.Type == FrameResponseEnd {
+ return Frame{Type: f.Type}
+ }
+
+ out := Frame{Type: f.Type}
+ if f.Payload != nil {
+ out.Payload = append([]byte(nil), f.Payload...)
+ }
+
+ return out
+}
+
+func validBand(band Band) bool {
+ return band == BandData || band == BandProgress || band == BandError
+}
+
+func frameTypeForBand(band Band) FrameType {
+ switch band {
+ case BandData:
+ return FrameData
+ case BandProgress:
+ return FrameProgress
+ case BandError:
+ return FrameError
+ default:
+ panic("invalid sideband64k band")
+ }
+}
+
+func effectiveMaxData(n int) int {
+ if n <= 0 || n > DataMax {
+ return DataMax
+ }
+
+ return n
+}