package udp import ( "net" "testing" "time" ) // feed runs one datagram through a remote-call listener and returns the event // it produced, or nil. func feed(t *testing.T, pkt []byte) *Event { t.Helper() out := make(chan Event, 4) s := &Server{ cfg: Config{ID: 1, Name: "DX HUNTER", ServiceType: ServiceRemoteCall, Port: 2241}, out: out, } s.handle(pkt, &net.UDPAddr{IP: net.IPv4(127, 0, 0, 1), Port: 2241}) select { case ev := <-out: return &ev case <-time.After(200 * time.Millisecond): return nil } } // A RadioInfo datagram must never be read as a remote-call request. // // This is the loop from a reported session. An inbound remote-call row and an // outbound RadioInfo row shared port 2241, so every datagram OpsLog sent came // straight back on the loopback. The tag-stripping heuristic read its last // token — 1 — as the callsign "1", and as // a tune request for the frequency the rig was already on. // // It stayed harmless only while was misread as MHz: the tune failed "out // of the CAT range" and the loop died there. Reading the unit correctly closed // it, and with JTDX "Fake It" — which shifts the dial for every over — each // transmission set off a burst of sets echoing between OpsLog and itself until // the rig stopped answering IF; and the shared CAT link dropped. func TestRadioInfoIsNotARemoteCall(t *testing.T) { pkt := BuildN1MMRadioInfo("F5PHW", 24_915_000, 24_915_000, "FT8", "F5PHW") // The trap this closes: the payload really does parse as a plausible dial // frequency, so nothing downstream would have questioned it. m := remoteFreqRe.FindStringSubmatch(string(pkt)) if m == nil { t.Fatal("the RadioInfo no longer carries a — this test is checking nothing") } if hz := remoteTuneHz(m[1]); hz != 24_915_000 { t.Fatalf("remoteTuneHz(%q) = %d — the dial frequency back is what made the loop live", m[1], hz) } if ev := feed(t, pkt); ev != nil { t.Errorf("a RadioInfo produced a remote-call event %+v — the rig would be re-tuned to where it already is", *ev) } } // A callsign has a letter in it. Refusing by shape as well as by name means the // next program to broadcast its state on this port cannot drive the rig either. func TestRemoteCallNeedsALetter(t *testing.T) { for _, body := range []string{"1", "0", "12345"} { if ev := feed(t, []byte(body)); ev != nil { t.Errorf("%q was accepted as a callsign: %+v", body, *ev) } } // A genuine spot click still gets through, tune request and all. ev := feed(t, []byte("OJ0YL10.112CW")) if ev == nil { t.Fatal("a genuine spot click produced no event") } if ev.DXCall != "OJ0YL" || ev.TuneFreqHz != 10_112_000 || ev.TuneMode != "CW" { t.Errorf("spot click decoded as %+v, want OJ0YL / 10112000 Hz / CW", *ev) } }