kappanhang/controlstream.go

398 lines
15 KiB
Go

package main
import (
"bytes"
"crypto/rand"
"encoding/binary"
"errors"
"fmt"
"os/exec"
"time"
)
const controlStreamPort = 50001
const serialStreamPort = 50002
const audioStreamPort = 50003
type controlStream struct {
common streamCommon
serial serialStream
audio audioStream
deinitNeededChan chan bool
deinitFinishedChan chan bool
authInnerSendSeq uint16
authID [6]byte
gotAuthID bool
authOk bool
a8replyID [16]byte
gotA8ReplyID bool
serialAndAudioStreamOpened bool
deinitializing bool
requestSerialAndAudioTimeout *time.Timer
reauthTimeoutTimer *time.Timer
}
func (s *controlStream) sendPktLogin() error {
// The reply to the auth packet will contain a 6 bytes long auth ID with the first 2 bytes set to our ID.
var authStartID [2]byte
if _, err := rand.Read(authStartID[:]); err != nil {
return err
}
p := []byte{0x80, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
byte(s.common.localSID >> 24), byte(s.common.localSID >> 16), byte(s.common.localSID >> 8), byte(s.common.localSID),
byte(s.common.remoteSID >> 24), byte(s.common.remoteSID >> 16), byte(s.common.remoteSID >> 8), byte(s.common.remoteSID),
0x00, 0x00, 0x00, 0x70, 0x01, 0x00, 0x00, byte(s.authInnerSendSeq),
byte(s.authInnerSendSeq >> 8), 0x00, authStartID[0], authStartID[1], 0x00, 0x00, 0x00, 0x00,
0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
0x2b, 0x3f, 0x55, 0x5c, 0x00, 0x00, 0x00, 0x00, // username: beer
0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
0x2b, 0x3f, 0x55, 0x5c, 0x3f, 0x25, 0x77, 0x58, // pass: beerbeer
0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
0x69, 0x63, 0x6f, 0x6d, 0x2d, 0x70, 0x63, 0x00, // icom-pc in plain text
0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00}
if err := s.common.pkt0.sendTrackedPacket(&s.common, p); err != nil {
return err
}
s.authInnerSendSeq++
return nil
}
func (s *controlStream) sendPktAuth(magic byte) error {
// Example request from PC: 0x40, 0x00, 0x00, 0x00, 0x00, 0x00, 0x0d, 0x00,
// 0xbb, 0x41, 0x3f, 0x2b, 0xe6, 0xb2, 0x7b, 0x7b,
// 0x00, 0x00, 0x00, 0x30, 0x01, 0x05, 0x00, 0x02,
// 0x00, 0x00, 0x5d, 0x37, 0x12, 0x82, 0x3b, 0xde,
// 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
// 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
// 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
// 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00
// Example reply from radio: 0x40, 0x00, 0x00, 0x00, 0x00, 0x00, 0x0e, 0x00,
// 0xe6, 0xb2, 0x7b, 0x7b, 0xbb, 0x41, 0x3f, 0x2b,
// 0x00, 0x00, 0x00, 0x30, 0x02, 0x05, 0x00, 0x02,
// 0x00, 0x00, 0x5d, 0x37, 0x12, 0x82, 0x3b, 0xde,
// 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
// 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
// 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
// 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00
p := []byte{0x40, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
byte(s.common.localSID >> 24), byte(s.common.localSID >> 16), byte(s.common.localSID >> 8), byte(s.common.localSID),
byte(s.common.remoteSID >> 24), byte(s.common.remoteSID >> 16), byte(s.common.remoteSID >> 8), byte(s.common.remoteSID),
0x00, 0x00, 0x00, 0x30, 0x01, magic, 0x00, byte(s.authInnerSendSeq),
byte(s.authInnerSendSeq >> 8), 0x00, s.authID[0], s.authID[1], s.authID[2], s.authID[3], s.authID[4], s.authID[5],
0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00}
if err := s.common.pkt0.sendTrackedPacket(&s.common, p); err != nil {
return err
}
s.authInnerSendSeq++
return nil
}
func (s *controlStream) sendRequestSerialAndAudio() error {
log.Debug("requesting serial and audio stream")
txSeqBufLengthMs := uint16(txSeqBufLength.Milliseconds())
p := []byte{0x90, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
byte(s.common.localSID >> 24), byte(s.common.localSID >> 16), byte(s.common.localSID >> 8), byte(s.common.localSID),
byte(s.common.remoteSID >> 24), byte(s.common.remoteSID >> 16), byte(s.common.remoteSID >> 8), byte(s.common.remoteSID),
0x00, 0x00, 0x00, 0x80, 0x01, 0x03, 0x00, byte(s.authInnerSendSeq),
byte(s.authInnerSendSeq >> 8), 0x00, s.authID[0], s.authID[1], s.authID[2], s.authID[3], s.authID[4], s.authID[5],
s.a8replyID[0], s.a8replyID[1], s.a8replyID[2], s.a8replyID[3], s.a8replyID[4], s.a8replyID[5], s.a8replyID[6], s.a8replyID[7],
s.a8replyID[8], s.a8replyID[9], s.a8replyID[10], s.a8replyID[11], s.a8replyID[12], s.a8replyID[13], s.a8replyID[14], s.a8replyID[15],
0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
0x49, 0x43, 0x2d, 0x37, 0x30, 0x35, 0x00, 0x00, // IC-705 in plain text
0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
0x2b, 0x3f, 0x55, 0x5c, 0x00, 0x00, 0x00, 0x00, // username: beer
0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
0x01, 0x01, 0x04, 0x04, 0x00, 0x00, byte(audioSampleRate >> 8), byte(audioSampleRate & 0xff),
0x00, 0x00, byte(audioSampleRate >> 8), byte(audioSampleRate & 0xff),
0x00, 0x00, byte(serialStreamPort >> 8), byte(serialStreamPort & 0xff),
0x00, 0x00, byte(audioStreamPort >> 8), byte(audioStreamPort & 0xff), 0x00, 0x00,
byte(txSeqBufLengthMs >> 8), byte(txSeqBufLengthMs & 0xff), 0x01, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00}
if err := s.common.pkt0.sendTrackedPacket(&s.common, p); err != nil {
return err
}
s.authInnerSendSeq++
return nil
}
func (s *controlStream) sendRequestSerialAndAudioIfPossible() {
if !s.serialAndAudioStreamOpened && s.authOk && s.gotA8ReplyID {
if err := s.sendRequestSerialAndAudio(); err != nil {
reportError(err)
}
}
}
func (s *controlStream) handleRead(r []byte) error {
switch len(r) {
case 168:
if bytes.Equal(r[:6], []byte{0xa8, 0x00, 0x00, 0x00, 0x00, 0x00}) {
// Example answer from radio:
// 0xa8, 0x00, 0x00, 0x00, 0x00, 0x00, 0x02, 0x00,
// 0x01, 0x13, 0x11, 0x18, 0x38, 0xff, 0x55, 0x7d,
// 0x00, 0x00, 0x00, 0x98, 0x02, 0x02, 0x00, 0x07,
// 0x00, 0x00, 0x7f, 0x91, 0x00, 0x00, 0x4f, 0x0d,
// 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
// 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
// 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
// 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
// 0x00, 0x01, 0x93, 0x8a, 0x01, 0x24, 0x17, 0x64,
// 0xbc, 0x4b, 0xa3, 0xa0, 0x13, 0x58, 0x41, 0x04,
// 0x58, 0x2d, 0x49, 0x43, 0x2d, 0x37, 0x30, 0x35,
// 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
// 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
// 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
// 0x00, 0x00, 0x49, 0x43, 0x4f, 0x4d, 0x5f, 0x56,
// 0x41, 0x55, 0x44, 0x49, 0x4f, 0x00, 0x00, 0x00,
// 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
// 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
// 0x00, 0x00, 0x3f, 0x3f, 0xa4, 0x01, 0xff, 0x01,
// 0xff, 0x01, 0x01, 0x01, 0x00, 0x00, 0x4b, 0x00,
// 0x01, 0x50, 0x00, 0xb8, 0x0b, 0x00, 0x00, 0x00
copy(s.a8replyID[:], r[66:82])
s.gotA8ReplyID = true
}
case 64:
if bytes.Equal(r[:6], []byte{0x40, 0x00, 0x00, 0x00, 0x00, 0x00}) {
// Example answer from radio: 0x40, 0x00, 0x00, 0x00, 0x00, 0x00, 0x10, 0x00,
// 0x33, 0x60, 0xd4, 0xe5, 0xf4, 0x67, 0x86, 0xe1,
// 0x00, 0x00, 0x00, 0x30, 0x02, 0x05, 0x00, 0x02,
// 0x00, 0x00, 0x35, 0x34, 0x76, 0x11, 0xb9, 0xd0,
// 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
// 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
// 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
// 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00
s.reauthTimeoutTimer.Stop()
log.Debug("auth ok")
if r[21] == 0x05 { // Answer for our second auth?
s.authOk = true
s.sendRequestSerialAndAudioIfPossible()
}
}
case 80:
if bytes.Equal(r[:6], []byte{0x50, 0x00, 0x00, 0x00, 0x00, 0x00}) {
// Example answer from radio: 0x50, 0x00, 0x00, 0x00, 0x00, 0x00, 0x03, 0x00,
// 0x86, 0x1f, 0x2f, 0xcc, 0x03, 0x03, 0x89, 0x29,
// 0x00, 0x00, 0x00, 0x40, 0x02, 0x03, 0x00, 0x52,
// 0x00, 0x00, 0xf8, 0xad, 0x06, 0x8d, 0xda, 0x7b,
// 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x10,
// 0x80, 0x00, 0x00, 0x90, 0xc7, 0x0e, 0x86, 0x01,
// 0xff, 0xff, 0xff, 0xff, 0x00, 0x00, 0x00, 0x00,
// 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
// 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
// 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00
if bytes.Equal(r[48:51], []byte{0xff, 0xff, 0xff}) {
if !s.serialAndAudioStreamOpened {
return errors.New("auth failed, try rebooting the radio")
}
return errors.New("auth failed")
}
if bytes.Equal(r[48:51], []byte{0x00, 0x00, 0x00}) && r[64] == 0x01 {
return errors.New("got radio disconnected")
}
}
case 144:
if !s.serialAndAudioStreamOpened && bytes.Equal(r[:6], []byte{0x90, 0x00, 0x00, 0x00, 0x00, 0x00}) && r[96] == 1 {
// Example answer:
// 0x90, 0x00, 0x00, 0x00, 0x00, 0x00, 0x19, 0x00,
// 0xc6, 0x5f, 0x6f, 0x0c, 0x5f, 0x8b, 0x1e, 0x89,
// 0x00, 0x00, 0x00, 0x80, 0x03, 0x00, 0x00, 0x00,
// 0x00, 0x00, 0x31, 0x30, 0x31, 0x47, 0x39, 0x07,
// 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x10,
// 0x80, 0x00, 0x00, 0x90, 0xc7, 0x0e, 0x86, 0x01,
// 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
// 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
// 0x49, 0x43, 0x2d, 0x37, 0x30, 0x35, 0x00, 0x00, // IC-705 in plain text
// 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
// 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
// 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
// 0x01, 0x00, 0x00, 0x00, 0x69, 0x63, 0x6f, 0x6d,
// 0x2d, 0x70, 0x63, 0x00, 0x00, 0x00, 0x00, 0x00,
// 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
// 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
// 0x00, 0x00, 0x00, 0x00, 0xc0, 0xa8, 0x03, 0x03,
// 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00
s.requestSerialAndAudioTimeout.Stop()
devName := parseNullTerminatedString(r[64:])
log.Print("got serial and audio request success, device name: ", devName)
// Stuff can change in the meantime because of a previous login...
s.common.remoteSID = binary.BigEndian.Uint32(r[8:12])
s.common.localSID = binary.BigEndian.Uint32(r[12:16])
copy(s.authID[:], r[26:32])
s.gotAuthID = true
if err := s.serial.init(devName); err != nil {
return errors.New("serial/" + err.Error())
}
if err := s.audio.init(devName); err != nil {
return errors.New("audio/" + err.Error())
}
s.serialAndAudioStreamOpened = true
statusLog.startPeriodicPrint()
runCmdRunner.startIfNeeded(runCmd)
if enableSerialDevice {
serialCmdRunner.startIfNeeded(runCmdOnSerialPortCreated)
}
if !disableRigctld {
if _, err := exec.LookPath("rigctld"); err != nil {
log.Error("can't start rigctld: ", err)
} else {
rigctldRunner.startIfNeeded(fmt.Sprint("rigctld -m ", rigctldModel, " -r :", serialTCPPort))
}
}
}
}
return nil
}
func (s *controlStream) loop() {
netstat.reset()
s.reauthTimeoutTimer = time.NewTimer(0)
<-s.reauthTimeoutTimer.C
reauthTicker := time.NewTicker(25 * time.Second)
for {
select {
case r := <-s.common.readChan:
if !s.deinitializing {
if err := s.handleRead(r); err != nil {
reportError(err)
}
}
case <-reauthTicker.C:
log.Debug("sending auth")
s.reauthTimeoutTimer.Reset(3 * time.Second)
if err := s.sendPktAuth(0x05); err != nil {
reportError(err)
}
case <-s.reauthTimeoutTimer.C:
log.Error("auth timeout, audio/serial stream may stop")
case <-s.deinitNeededChan:
s.deinitFinishedChan <- true
return
}
}
}
func (s *controlStream) init() error {
log.Debug("init")
if err := s.common.init("control", controlStreamPort); err != nil {
return err
}
if err := s.common.start(); err != nil {
return err
}
s.common.pkt0.init(&s.common)
if err := s.sendPktLogin(); err != nil {
return err
}
log.Debug("expecting login answer")
// Example success auth packet: 0x60, 0x00, 0x00, 0x00, 0x00, 0x00, 0x01, 0x00,
// 0xe6, 0xb2, 0x7b, 0x7b, 0xbb, 0x41, 0x3f, 0x2b,
// 0x00, 0x00, 0x00, 0x50, 0x02, 0x00, 0x00, 0x00,
// 0x00, 0x00, 0x5d, 0x37, 0x12, 0x82, 0x3b, 0xde,
// 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
// 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
// 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
// 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
// 0x46, 0x54, 0x54, 0x48, 0x00, 0x00, 0x00, 0x00,
// 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
// 0x01, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
// 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00
r, err := s.common.expect(96, []byte{0x60, 0x00, 0x00, 0x00, 0x00, 0x00, 0x01, 0x00})
if err != nil {
return err
}
if bytes.Equal(r[48:52], []byte{0xff, 0xff, 0xff, 0xfe}) {
return errors.New("invalid user/password")
}
s.common.pkt7.startPeriodicSend(&s.common, 2, false)
copy(s.authID[:], r[26:32])
s.gotAuthID = true
if err := s.sendPktAuth(0x02); err != nil {
return err
}
log.Debug("login ok, first auth sent...")
s.common.pkt0.startPeriodicSend(&s.common)
if err := s.sendPktAuth(0x05); err != nil {
return err
}
log.Debug("second auth sent...")
s.requestSerialAndAudioTimeout = time.AfterFunc(5*time.Second, func() {
reportError(errors.New("login/serial/audio request timeout"))
})
s.deinitNeededChan = make(chan bool)
s.deinitFinishedChan = make(chan bool)
go s.loop()
return nil
}
func (s *controlStream) deinit() {
s.deinitializing = true
s.serialAndAudioStreamOpened = false
statusLog.stopPeriodicPrint()
if s.deinitNeededChan != nil {
s.deinitNeededChan <- true
<-s.deinitFinishedChan
}
if s.requestSerialAndAudioTimeout != nil {
s.requestSerialAndAudioTimeout.Stop()
s.requestSerialAndAudioTimeout = nil
}
if s.gotAuthID && s.common.gotRemoteSID && s.common.conn != nil {
log.Debug("sending deauth")
_ = s.sendPktAuth(0x01)
// Waiting a little bit to make sure the radio can send retransmit requests.
time.Sleep(500 * time.Millisecond)
}
s.common.deinit()
s.serial.deinit()
s.audio.deinit()
}