The station reads ~/.funknetz.json, speaks protocol 2.0, and builds an unsigned funknetz.app. Co-authored-by: Cursor <cursoragent@cursor.com>
159 lines
3.1 KiB
Go
159 lines
3.1 KiB
Go
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
|
|
}
|