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>
227 lines
4.7 KiB
Go
227 lines
4.7 KiB
Go
package station
|
|
|
|
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
|
|
udpAllFlushed = 103
|
|
|
|
maxFrame = 32768
|
|
maxPreauth20 = 64
|
|
)
|
|
|
|
type frame struct {
|
|
typ uint16
|
|
body []byte
|
|
}
|
|
|
|
func writeFrame(w io.Writer, typ uint16, body []byte) error {
|
|
payload := make([]byte, 2+len(body))
|
|
binary.BigEndian.PutUint16(payload[:2], typ)
|
|
copy(payload[2:], body)
|
|
var hdr [4]byte
|
|
binary.BigEndian.PutUint32(hdr[:], uint32(len(payload)))
|
|
if _, err := w.Write(hdr[:]); err != nil {
|
|
return err
|
|
}
|
|
_, err := w.Write(payload)
|
|
return err
|
|
}
|
|
|
|
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 > max {
|
|
return frame{}, fmt.Errorf("frame length %d", n)
|
|
}
|
|
buf := make([]byte, n)
|
|
if _, err := io.ReadFull(r, buf); err != nil {
|
|
return frame{}, err
|
|
}
|
|
return frame{typ: binary.BigEndian.Uint16(buf[:2]), body: buf[2:]}, nil
|
|
}
|
|
|
|
func appendU16(b []byte, v uint16) []byte {
|
|
var buf [2]byte
|
|
binary.BigEndian.PutUint16(buf[:], v)
|
|
return append(b, buf[:]...)
|
|
}
|
|
|
|
func appendString(b []byte, s string) []byte {
|
|
b = appendU16(b, uint16(len(s)))
|
|
return append(b, s...)
|
|
}
|
|
|
|
func appendBlob(b []byte, p []byte) []byte {
|
|
b = appendU16(b, uint16(len(p)))
|
|
return append(b, p...)
|
|
}
|
|
|
|
func parseU16(b []byte) (uint16, []byte, error) {
|
|
if len(b) < 2 {
|
|
return 0, nil, io.ErrUnexpectedEOF
|
|
}
|
|
return binary.BigEndian.Uint16(b[:2]), b[2:], nil
|
|
}
|
|
|
|
func parseU32(b []byte) (uint32, []byte, error) {
|
|
if len(b) < 4 {
|
|
return 0, nil, io.ErrUnexpectedEOF
|
|
}
|
|
return binary.BigEndian.Uint32(b[:4]), b[4:], nil
|
|
}
|
|
|
|
func parseString(b []byte) (string, []byte, error) {
|
|
n, rest, err := parseU16(b)
|
|
if err != nil {
|
|
return "", nil, err
|
|
}
|
|
if len(rest) < int(n) {
|
|
return "", nil, io.ErrUnexpectedEOF
|
|
}
|
|
return string(rest[:n]), rest[n:], nil
|
|
}
|
|
|
|
func parseBlob(b []byte) ([]byte, []byte, error) {
|
|
n, rest, err := parseU16(b)
|
|
if err != nil {
|
|
return nil, nil, err
|
|
}
|
|
if len(rest) < int(n) {
|
|
return nil, nil, io.ErrUnexpectedEOF
|
|
}
|
|
return rest[:n], rest[n:], nil
|
|
}
|
|
|
|
func parseStringList(b []byte) ([]string, []byte, error) {
|
|
n, rest, err := parseU16(b)
|
|
if err != nil {
|
|
return nil, nil, err
|
|
}
|
|
out := make([]string, 0, n)
|
|
for range int(n) {
|
|
s, next, err := parseString(rest)
|
|
if err != nil {
|
|
return nil, nil, err
|
|
}
|
|
out = append(out, s)
|
|
rest = next
|
|
}
|
|
return out, rest, nil
|
|
}
|
|
|
|
func parseServerInfo(b []byte) (uint16, []string, error) {
|
|
_, rest, err := parseU16(b)
|
|
if err != nil {
|
|
return 0, nil, err
|
|
}
|
|
id, rest, err := parseU16(rest)
|
|
if err != nil {
|
|
return 0, nil, err
|
|
}
|
|
_, rest, err = parseStringList(rest)
|
|
if err != nil {
|
|
return 0, nil, err
|
|
}
|
|
codecs, _, err := parseStringList(rest)
|
|
if err != nil {
|
|
return 0, nil, err
|
|
}
|
|
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" {
|
|
return true
|
|
}
|
|
}
|
|
return false
|
|
}
|