Speak protocol 3.0 and fall back to 2.0 only when that is the reflector's maximum.

A reflector that has moved to 3.0 will not accept a station that always announces 2.0, and a 2.x reflector still needs one explicit plaintext session.

Co-authored-by: Cursor <cursoragent@cursor.com>
This commit is contained in:
2026-10-07 14:24:00 +02:00
co-authored by Cursor
parent 0930d4b727
commit 3d1a2a1669
24 changed files with 3180 additions and 244 deletions
+72 -5
View File
@@ -4,26 +4,47 @@ import (
"encoding/binary"
"fmt"
"io"
"regexp"
"strconv"
)
const (
tcpHeartbeat = 1
tcpProtoVer = 5
tcpProtoDown = 6
tcpAuthChallenge = 10
tcpAuthResponse = 11
tcpAuthOK = 12
tcpError = 13
tcpStartEncReq = 14
tcpStartEnc = 15
tcpCsrReq = 16
tcpCsr = 17
tcpClientCert = 18
tcpCAInfo = 19
tcpCABundleReq = 20
tcpCABundle = 21
tcpServerInfo = 100
tcpNodeList = 101
tcpNodeJoined = 102
tcpNodeLeft = 103
tcpTalkerStart = 104
tcpTalkerStop = 105
tcpSelectTG = 106
tcpRequestQSY = 109
tcpStateEvent = 110
tcpNodeInfo = 111
tcpSignal = 112
tcpTxStatus = 113
tcpStartUDPEnc = 114
udpHeartbeat = 1
udpAudio = 101
udpFlush = 102
udpHeartbeat = 1
udpAudio = 101
udpFlush = 102
udpAllFlushed = 103
maxFrame = 32768
maxFrame = 32768
maxPreauth20 = 64
)
type frame struct {
@@ -45,12 +66,16 @@ func writeFrame(w io.Writer, typ uint16, body []byte) error {
}
func readFrame(r io.Reader) (frame, error) {
return readFrameMax(r, maxFrame)
}
func readFrameMax(r io.Reader, max uint32) (frame, error) {
var hdr [4]byte
if _, err := io.ReadFull(r, hdr[:]); err != nil {
return frame{}, err
}
n := binary.BigEndian.Uint32(hdr[:])
if n < 2 || n > maxFrame {
if n < 2 || n > max {
return frame{}, fmt.Errorf("frame length %d", n)
}
buf := make([]byte, n)
@@ -149,6 +174,48 @@ func parseServerInfo(b []byte) (uint16, []string, error) {
return id, codecs, nil
}
func versionWire(major, minor uint16) []byte {
return appendU16(appendU16(nil, major), minor)
}
func parseVersion(b []byte) (uint16, uint16, error) {
major, rest, err := parseU16(b)
if err != nil {
return 0, 0, err
}
minor, rest, err := parseU16(rest)
if err != nil {
return 0, 0, err
}
if len(rest) != 0 {
return 0, 0, io.ErrUnexpectedEOF
}
return major, minor, nil
}
var atMostVersion = regexp.MustCompile(`^Unsupported protocol version \d+\.\d+\. May be at most (\d+)\.(\d+)\.$`)
// fallback20 reports that the reflector's maximum is at least 2.0 and below 3.0.
func fallback20(msg string) bool {
m := atMostVersion.FindStringSubmatch(msg)
if m == nil {
return false
}
major, err1 := strconv.Atoi(m[1])
minor, err2 := strconv.Atoi(m[2])
if err1 != nil || err2 != nil {
return false
}
return !versionBelow(major, minor, 2, 0) && versionBelow(major, minor, 3, 0)
}
func versionBelow(major, minor, maj, min int) bool {
if major != maj {
return major < maj
}
return minor < min
}
func hasOPUS(codecs []string) bool {
for _, c := range codecs {
if c == "OPUS" {