package station_test import ( "context" "net" "os" "testing" "time" "gitea.neitzel.de/konrad/funknetz/station" ) func TestVersionErrorFallsBackWhenTheMaximumIs2(t *testing.T) { for _, msg := range []string{ "Unsupported protocol version 3.0. May be at most 2.0.", "Unsupported protocol version 3.0. May be at most 2.5.", } { t.Run(msg, func(t *testing.T) { refl := newReflector(t) path := writeConfigFile(t, configJSON(t, "127.0.0.1", refl.port, "DL1ABC", "key", 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() { // Run's error is observed through State, which is the station seam. _ = st.Run(ctx) }() refl.accept(t) typ, body := refl.readFrame(t) if typ != 5 || !versionIs(body, 3, 0) { t.Fatalf("type %d body %v", typ, body) } refl.writeFrame(t, 13, appendStr(nil, msg)) refl.accept(t) typ, body = refl.readFrame(t) if typ != 5 || !versionIs(body, 2, 0) { t.Fatalf("fallback type %d body %v", typ, body) } }) } } func TestVersionErrorBelow2Stops(t *testing.T) { for _, msg := range []string{ "Unsupported protocol version 3.0. May be at most 1.9.", "Unsupported protocol version 3.0. May be at most 0.6.", } { t.Run(msg, func(t *testing.T) { refl := newReflector(t) path := writeConfigFile(t, configJSON(t, "127.0.0.1", refl.port, "DL1ABC", "key", 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() { // Run's error is observed through State, which is the station seam. _ = st.Run(ctx) }() refl.accept(t) refl.readFrame(t) refl.writeFrame(t, 13, appendStr(nil, msg)) got, err := st.Wait(ctx, func(s station.State) bool { return s.Detail == msg }) if err != nil { t.Fatal(err) } if got.Connection != "Disconnected" || got.Protocol != "" { t.Fatalf("State = %+v", got) } clock.Advance(2 * time.Second) refl.ln.(*net.TCPListener).SetDeadline(time.Now().Add(150 * time.Millisecond)) if conn, err := refl.ln.Accept(); err == nil { conn.Close() t.Fatal("retried") } }) } } func TestDowngradeOtherThan2Stops(t *testing.T) { for _, tc := range []struct { major, minor uint16 want string }{ {1, 0, "Unsupported protocol version 1.0"}, {4, 2, "Unsupported protocol version 4.2"}, } { t.Run(tc.want, func(t *testing.T) { refl := newReflector(t) path := writeConfigFile(t, configJSON(t, "127.0.0.1", refl.port, "DL1ABC", "key", 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() { // Run's error is observed through State, which is the station seam. _ = st.Run(ctx) }() refl.accept(t) refl.readFrame(t) refl.writeFrame(t, 6, versionWire(tc.major, tc.minor)) got, err := st.Wait(ctx, func(s station.State) bool { return s.Detail == tc.want }) if err != nil { t.Fatal(err) } if got.Protocol != "" || got.Connection != "Disconnected" { t.Fatalf("State = %+v", got) } clock.Advance(2 * time.Second) refl.ln.(*net.TCPListener).SetDeadline(time.Now().Add(150 * time.Millisecond)) if conn, err := refl.ln.Accept(); err == nil { conn.Close() t.Fatal("retried") } }) } } func TestDowngradeDoesNotCreateTheCertificateDirectory(t *testing.T) { refl := newReflector(t) dir := t.TempDir() path := dir + "/funknetz.json" body := configWithDir(t, "127.0.0.1", refl.port, "DL1ABC", "key", 20, "./funknetz", "") if err := os.WriteFile(path, []byte(body), 0o600); err != nil { t.Fatal(err) } 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() { // Run's error is observed through State, which is the station seam. _ = st.Run(ctx) }() refl.accept(t) declineTo2(t, refl) if _, err := os.Stat(dir + "/funknetz"); err == nil { t.Fatal("created the certificate directory on a 2.0 fallback") } }