package station_test import ( "bytes" "context" "crypto/hmac" "crypto/sha1" "testing" "time" "gitea.neitzel.de/konrad/funknetz/station" ) func TestLoginAnnouncesProtocolAndJoinsTheTalkGroup(t *testing.T) { refl := newReflector(t) path := writeConfigFile(t, configJSON(t, " 127.0.0.1 ", refl.port, " dl1abc ", "auth key with spaces", 20)) clock := newFakeClock() st, err := station.New(station.Options{ConfigPath: path, Clock: clock}) if err != nil { t.Fatal(err) } ctx, cancel := context.WithCancel(t.Context()) defer cancel() go func() { _ = st.Run(ctx) }() got, err := st.Wait(ctx, func(s station.State) bool { return s.Connection == "Connecting" }) if err != nil { t.Fatal(err) } if got.Callsign != "DL1ABC" || got.TalkGroup != 20 || got.Detail != "Idle" || got.Protocol != "" { t.Fatalf("State = %+v, want DL1ABC talk group 20 Idle and a blank protocol", got) } refl.accept(t) typ, body := refl.readFrame(t) if typ != 5 || !versionIs(body, 3, 0) { t.Fatalf("first frame type %d body %v, want protocol 3.0", typ, body) } refl.writeFrame(t, 6, versionWire(2, 0)) refl.accept(t) typ, body = refl.readFrame(t) if typ != 5 || !versionIs(body, 2, 0) { t.Fatalf("fallback frame type %d body %v, want protocol 2.0", typ, body) } challenge := make([]byte, 20) for i := range challenge { challenge[i] = byte(i + 1) } refl.writeFrame(t, 10, appendBytes(nil, challenge)) typ, body = refl.readFrame(t) if typ != 11 { t.Fatalf("auth response type %d, want 11", typ) } call, rest := takeStr(t, body) digest, rest := takeBytes(t, rest) if len(rest) != 0 { t.Fatalf("auth response has %d trailing bytes", len(rest)) } if call != "DL1ABC" { t.Fatalf("callsign = %q, want DL1ABC", call) } mac := hmac.New(sha1.New, []byte("auth key with spaces")) mac.Write(challenge) if !hmac.Equal(digest, mac.Sum(nil)) { t.Fatalf("digest = %x, want hmac-sha1 of the challenge under the stored key", digest) } if bytes.Contains(body, []byte("auth key with spaces")) { t.Fatal("auth response contains the auth key") } refl.writeFrame(t, 12, nil) refl.writeFrame(t, 100, serverInfo(0x1234, []string{"db0abc"}, []string{"GSM", "OPUS"})) typ, body = refl.readFrame(t) if typ != 111 { t.Fatalf("frame type %d, want software identity", typ) } requireSoftwareIdentity(t, body) typ, body = refl.readFrame(t) if typ != 106 { t.Fatalf("frame type %d, want select talk group", typ) } tg, rest := takeU32(t, body) if tg != 20 || len(rest) != 0 { t.Fatalf("select talk group = %d rest %d, want 20", tg, len(rest)) } utyp, id, seq, ubody := refl.readUDP(t) if utyp != 1 || id != 0x1234 || seq != 0 || len(ubody) != 0 { t.Fatalf("udp heartbeat type %d id %d seq %d len %d, want type 1 id 0x1234 seq 0", utyp, id, seq, len(ubody)) } got, err = st.Wait(ctx, func(s station.State) bool { return s.Connection == "Connected" }) if err != nil { t.Fatal(err) } if got.Detail != "Idle" || got.Talker != "" || got.Protocol != "2.0" { t.Fatalf("State = %+v, want Connected Idle protocol 2.0", got) } // No other talk group: the next TCP message is a heartbeat, not a monitor set. clock.BlockUntilWaiting(t) clock.Advance(10 * time.Second) typ, _ = refl.readFrame(t) if typ != 1 { t.Fatalf("quiet TCP frame type %d, want heartbeat 1", typ) } }