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+)\.$`) var atLeastVersion = regexp.MustCompile(`^Unsupported protocol version \d+\.\d+\. Must be at least (\d+)\.(\d+)\.$`) // wantsOther reports that the reflector refused the announced major and named the other one. func wantsOther(announced uint16, msg string) bool { if announced >= 3 { return fallback20(msg) } return fallback30(msg) } // acceptsOther reports that a downgrade names the protocol this station did not announce. func acceptsOther(announced, gotMajor, gotMinor uint16) bool { if announced >= 3 { return gotMajor == 2 } return gotMajor == 3 && gotMinor == 0 } // 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) } // fallback30 reports that the reflector's minimum is exactly 3.0. func fallback30(msg string) bool { m := atLeastVersion.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 major == 3 && minor == 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 }