Join one FM-Funknetz talk group from a headset window.
The station reads ~/.funknetz.json, speaks protocol 2.0, and builds an unsigned funknetz.app. Co-authored-by: Cursor <cursoragent@cursor.com>
This commit is contained in:
@@ -0,0 +1,158 @@
|
||||
package station
|
||||
|
||||
import (
|
||||
"encoding/binary"
|
||||
"fmt"
|
||||
"io"
|
||||
)
|
||||
|
||||
const (
|
||||
tcpHeartbeat = 1
|
||||
tcpProtoVer = 5
|
||||
tcpAuthChallenge = 10
|
||||
tcpAuthResponse = 11
|
||||
tcpAuthOK = 12
|
||||
tcpError = 13
|
||||
tcpServerInfo = 100
|
||||
tcpTalkerStart = 104
|
||||
tcpTalkerStop = 105
|
||||
tcpSelectTG = 106
|
||||
|
||||
udpHeartbeat = 1
|
||||
udpAudio = 101
|
||||
udpFlush = 102
|
||||
|
||||
maxFrame = 32768
|
||||
)
|
||||
|
||||
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) {
|
||||
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 {
|
||||
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 hasOPUS(codecs []string) bool {
|
||||
for _, c := range codecs {
|
||||
if c == "OPUS" {
|
||||
return true
|
||||
}
|
||||
}
|
||||
return false
|
||||
}
|
||||
Reference in New Issue
Block a user