fix(rigctld): never leave the rig keyed when the client goes away
The Kenwood/Elecraft backend deliberately suspends its wire poll while PTT is held — a K3 answers "?;" to IF; during transmit, and treating that as a fault used to drop the whole CAT link. The consequence was that nothing watched the transmitter: a client that crashed, was closed, or simply had its socket shut under it left the rig on air. And shutting the socket is routine. reloadCATShare tears the sharing server down and rebuilds it on every settings save, so a Save while WSJT-X held PTT was enough. A K3 operator's log shows exactly that: "TX;" at 17:53:09, no "RX;" ever, the poll silent, and the rig still keyed 29 s later when the CAT link happened to be rebuilt. The server now drops PTT when a connection ends and when Stop() is called. Stop() runs before reloadCAT restarts the backend, so the unkey still reaches the radio. An atomic Swap keeps it once-only across the two paths.
This commit is contained in:
@@ -7,6 +7,7 @@ import (
|
||||
"strings"
|
||||
"sync"
|
||||
"testing"
|
||||
"time"
|
||||
)
|
||||
|
||||
// fakeRig stands in for the CAT manager.
|
||||
@@ -234,6 +235,66 @@ func TestServerOverTCP(t *testing.T) {
|
||||
}
|
||||
}
|
||||
|
||||
// A client that vanishes mid-over must not leave the transmitter keyed. Nothing
|
||||
// else would notice: the Kenwood/Elecraft backend stops polling while PTT is
|
||||
// held, so a K3 was seen sitting in transmit for 29 s after WSJT-X's socket was
|
||||
// closed under it by a settings save.
|
||||
func TestPTTIsDroppedWhenTheClientDisappears(t *testing.T) {
|
||||
rig := &fakeRig{freq: 14074000, mode: "FT8"}
|
||||
s := New(0, rig, nil)
|
||||
ln, err := net.Listen("tcp", "127.0.0.1:0")
|
||||
if err != nil {
|
||||
t.Fatalf("listen: %v", err)
|
||||
}
|
||||
s.mu.Lock()
|
||||
s.ln = ln
|
||||
s.mu.Unlock()
|
||||
go func() {
|
||||
for {
|
||||
c, err := ln.Accept()
|
||||
if err != nil {
|
||||
return
|
||||
}
|
||||
go s.serve(c)
|
||||
}
|
||||
}()
|
||||
defer s.Stop()
|
||||
|
||||
c, err := net.DialTimeout("tcp", ln.Addr().String(), dialTimeout)
|
||||
if err != nil {
|
||||
t.Fatalf("dial: %v", err)
|
||||
}
|
||||
r := bufio.NewReader(c)
|
||||
// "T 3" is what WSJT-X sends for PTT_ON_DATA — the form seen in the field.
|
||||
if _, err := c.Write([]byte("T 3\n")); err != nil {
|
||||
t.Fatalf("write: %v", err)
|
||||
}
|
||||
line, _ := r.ReadString('\n')
|
||||
if strings.TrimSpace(line) != "RPRT 0" {
|
||||
t.Fatalf("set_ptt = %q, want RPRT 0", strings.TrimSpace(line))
|
||||
}
|
||||
rig.mu.Lock()
|
||||
keyed := rig.ptt
|
||||
rig.mu.Unlock()
|
||||
if !keyed {
|
||||
t.Fatalf("rig is not keyed after T 3 — the test proves nothing")
|
||||
}
|
||||
|
||||
_ = c.Close()
|
||||
// The unkey happens on the serve goroutine's defer, so poll briefly.
|
||||
deadline := time.Now().Add(2 * time.Second)
|
||||
for time.Now().Before(deadline) {
|
||||
rig.mu.Lock()
|
||||
keyed = rig.ptt
|
||||
rig.mu.Unlock()
|
||||
if !keyed {
|
||||
return
|
||||
}
|
||||
time.Sleep(10 * time.Millisecond)
|
||||
}
|
||||
t.Error("rig still keyed after the client disconnected — the transmitter was left on air")
|
||||
}
|
||||
|
||||
func TestModeMapping(t *testing.T) {
|
||||
for _, c := range []struct{ adif, hamlib string }{
|
||||
{"SSB", "USB"}, {"LSB", "LSB"}, {"CW", "CW"}, {"RTTY", "RTTY"},
|
||||
|
||||
Reference in New Issue
Block a user