mirror of
https://github.com/Quad4-Software/Reticulum-Go
synced 2026-08-29 23:48:44 -04:00
121 lines
3 KiB
Go
121 lines
3 KiB
Go
// SPDX-License-Identifier: Apache-2.0
|
|
// Copyright (c) 2024-2026 Quad4.io
|
|
|
|
package interfaces
|
|
|
|
// hdlcStreamDecoder incrementally parses HDLC-framed packets from a byte stream.
|
|
// Payload bytes are unescaped during assembly. onFrame receives a view of the
|
|
// assembler buffer that is reused after the callback returns. Callers that
|
|
// retain the frame must copy it. Transport.HandlePacket already copies.
|
|
type hdlcStreamDecoder struct {
|
|
mtu int
|
|
minPayload int
|
|
inFrame bool
|
|
escape bool
|
|
toggle bool
|
|
data []byte
|
|
maxFrame int
|
|
onFrame func([]byte)
|
|
}
|
|
|
|
func newHDLCStreamDecoder(mtu int, onFrame func([]byte)) *hdlcStreamDecoder {
|
|
return newHDLCStreamDecoderOpts(mtu, false, 0, onFrame)
|
|
}
|
|
|
|
// newHDLCToggleStreamDecoder uses PPP-style flag toggling, matching TCP read loops.
|
|
func newHDLCToggleStreamDecoder(mtu int, onFrame func([]byte)) *hdlcStreamDecoder {
|
|
return newHDLCStreamDecoderOpts(mtu, true, 0, onFrame)
|
|
}
|
|
|
|
// newTCPHDLCStreamDecoder matches Python TCPClientInterface.check_frame_len (RNS 1.3.9).
|
|
func newTCPHDLCStreamDecoder(mtu int, onFrame func([]byte)) *hdlcStreamDecoder {
|
|
return newHDLCStreamDecoderOpts(mtu, true, reticulumHeaderMinSize, onFrame)
|
|
}
|
|
|
|
func newHDLCStreamDecoderOpts(mtu int, toggle bool, minPayload int, onFrame func([]byte)) *hdlcStreamDecoder {
|
|
maxFrame := 2*mtu + 32
|
|
if maxFrame < 256 {
|
|
maxFrame = 2048
|
|
}
|
|
return &hdlcStreamDecoder{
|
|
mtu: mtu,
|
|
minPayload: minPayload,
|
|
toggle: toggle,
|
|
maxFrame: maxFrame,
|
|
data: make([]byte, 0, mtu),
|
|
onFrame: onFrame,
|
|
}
|
|
}
|
|
|
|
func (d *hdlcStreamDecoder) reset() {
|
|
d.inFrame = false
|
|
d.escape = false
|
|
d.data = d.data[:0]
|
|
}
|
|
|
|
func (d *hdlcStreamDecoder) feed(buf []byte) {
|
|
for _, b := range buf {
|
|
d.feedByte(b)
|
|
}
|
|
}
|
|
|
|
// dropPartial clears an incomplete frame. Used when a serial inter-byte idle
|
|
// timeout expires so noise does not stick forever in the assembler.
|
|
func (d *hdlcStreamDecoder) dropPartial() bool {
|
|
if !d.inFrame && len(d.data) == 0 && !d.escape {
|
|
return false
|
|
}
|
|
had := d.inFrame || len(d.data) > 0 || d.escape
|
|
d.reset()
|
|
if !d.toggle {
|
|
d.inFrame = false
|
|
}
|
|
return had
|
|
}
|
|
|
|
func (d *hdlcStreamDecoder) feedByte(b byte) {
|
|
if b == HDLCFlag {
|
|
if d.inFrame && len(d.data) > 0 {
|
|
// maxFrame allows escaped assembly headroom; delivered payload
|
|
// must still fit the interface MTU (matching KISS). TCP/Backbone
|
|
// also reject frames at or below HEADER_MINSIZE (RNS 1.3.9).
|
|
ok := d.onFrame != nil && (d.mtu <= 0 || len(d.data) <= d.mtu)
|
|
if ok && d.minPayload > 0 && len(d.data) <= d.minPayload {
|
|
ok = false
|
|
}
|
|
if ok {
|
|
d.onFrame(d.data)
|
|
}
|
|
}
|
|
d.data = d.data[:0]
|
|
if d.toggle {
|
|
d.inFrame = !d.inFrame
|
|
} else {
|
|
d.inFrame = true
|
|
}
|
|
d.escape = false
|
|
return
|
|
}
|
|
if !d.inFrame {
|
|
return
|
|
}
|
|
if b == HDLCEsc {
|
|
d.escape = true
|
|
return
|
|
}
|
|
if d.escape {
|
|
b ^= HDLCEscMask
|
|
d.escape = false
|
|
}
|
|
limit := d.maxFrame
|
|
if d.mtu > 0 && d.mtu < limit {
|
|
limit = d.mtu
|
|
}
|
|
if len(d.data) >= limit {
|
|
d.data = d.data[:0]
|
|
d.inFrame = false
|
|
d.escape = false
|
|
return
|
|
}
|
|
d.data = append(d.data, b)
|
|
}
|