From 5b0d76048a3916ce6e7ccafb8d76ae6dfc34ff39 Mon Sep 17 00:00:00 2001 From: Jan Date: Mon, 6 Apr 2026 09:54:37 +0200 Subject: [PATCH] config: enforce rds text lengths --- internal/config/config.go | 6 +++ internal/config/config_test.go | 95 +++++++++++++++++++++++++++------- 2 files changed, 81 insertions(+), 20 deletions(-) diff --git a/internal/config/config.go b/internal/config/config.go index 6c73382..2eaa227 100644 --- a/internal/config/config.go +++ b/internal/config/config.go @@ -183,6 +183,12 @@ func (c Config) Validate() error { if c.RDS.PTY < 0 || c.RDS.PTY > 31 { return fmt.Errorf("rds.pty out of range (0-31)") } + if len(c.RDS.PS) > 8 { + return fmt.Errorf("rds.ps must be <= 8 characters") + } + if len(c.RDS.RadioText) > 64 { + return fmt.Errorf("rds.radioText must be <= 64 characters") + } return nil } diff --git a/internal/config/config_test.go b/internal/config/config_test.go index aee3ada..079d9c0 100644 --- a/internal/config/config_test.go +++ b/internal/config/config_test.go @@ -3,11 +3,14 @@ package config import ( "os" "path/filepath" + "strings" "testing" ) func TestDefaultValidate(t *testing.T) { - if err := Default().Validate(); err != nil { t.Fatalf("default invalid: %v", err) } + if err := Default().Validate(); err != nil { + t.Fatalf("default invalid: %v", err) + } } func TestLoadAndValidate(t *testing.T) { @@ -15,56 +18,108 @@ func TestLoadAndValidate(t *testing.T) { path := filepath.Join(dir, "config.json") os.WriteFile(path, []byte(`{"audio":{"toneLeftHz":900,"toneRightHz":1700,"toneAmplitude":0.3},"fm":{"frequencyMHz":99.9},"backend":{"kind":"file","outputPath":"out.f32"},"control":{"listenAddress":"127.0.0.1:8088"}}`), 0o644) cfg, err := Load(path) - if err != nil { t.Fatalf("load: %v", err) } - if cfg.Audio.ToneLeftHz != 900 { t.Fatalf("unexpected left tone: %v", cfg.Audio.ToneLeftHz) } + if err != nil { + t.Fatalf("load: %v", err) + } + if cfg.Audio.ToneLeftHz != 900 { + t.Fatalf("unexpected left tone: %v", cfg.Audio.ToneLeftHz) + } } func TestValidateRejectsBadFrequency(t *testing.T) { - cfg := Default(); cfg.FM.FrequencyMHz = 200 - if err := cfg.Validate(); err == nil { t.Fatal("expected error") } + cfg := Default() + cfg.FM.FrequencyMHz = 200 + if err := cfg.Validate(); err == nil { + t.Fatal("expected error") + } } func TestValidateRejectsBadPreEmphasis(t *testing.T) { - cfg := Default(); cfg.FM.PreEmphasisTauUS = 150 - if err := cfg.Validate(); err == nil { t.Fatal("expected error") } + cfg := Default() + cfg.FM.PreEmphasisTauUS = 150 + if err := cfg.Validate(); err == nil { + t.Fatal("expected error") + } } func TestDefaultPreEmphasis(t *testing.T) { - if Default().FM.PreEmphasisTauUS != 50 { t.Fatal("expected 50") } + if Default().FM.PreEmphasisTauUS != 50 { + t.Fatal("expected 50") + } } func TestDefaultFMModulation(t *testing.T) { cfg := Default() - if !cfg.FM.FMModulationEnabled { t.Fatal("expected true") } - if cfg.FM.MaxDeviationHz != 75000 { t.Fatal("expected 75000") } + if !cfg.FM.FMModulationEnabled { + t.Fatal("expected true") + } + if cfg.FM.MaxDeviationHz != 75000 { + t.Fatal("expected 75000") + } } func TestParsePI(t *testing.T) { - tests := []struct{ in string; want uint16; ok bool }{ + tests := []struct { + in string + want uint16 + ok bool + }{ {"1234", 0x1234, true}, {"0xBEEF", 0xBEEF, true}, {"0XCAFE", 0xCAFE, true}, {" 0x2345 ", 0x2345, true}, {"", 0, false}, {"nope", 0, false}, } for _, tt := range tests { got, err := ParsePI(tt.in) - if tt.ok && err != nil { t.Fatalf("ParsePI(%q): %v", tt.in, err) } - if !tt.ok && err == nil { t.Fatalf("ParsePI(%q): expected error", tt.in) } - if tt.ok && got != tt.want { t.Fatalf("ParsePI(%q): got %x want %x", tt.in, got, tt.want) } + if tt.ok && err != nil { + t.Fatalf("ParsePI(%q): %v", tt.in, err) + } + if !tt.ok && err == nil { + t.Fatalf("ParsePI(%q): expected error", tt.in) + } + if tt.ok && got != tt.want { + t.Fatalf("ParsePI(%q): got %x want %x", tt.in, got, tt.want) + } } } func TestValidateRejectsInvalidPI(t *testing.T) { - cfg := Default(); cfg.RDS.PI = "nope" - if err := cfg.Validate(); err == nil { t.Fatal("expected error") } + cfg := Default() + cfg.RDS.PI = "nope" + if err := cfg.Validate(); err == nil { + t.Fatal("expected error") + } } func TestValidateRejectsEmptyPI(t *testing.T) { - cfg := Default(); cfg.RDS.PI = "" - if err := cfg.Validate(); err == nil { t.Fatal("expected error") } + cfg := Default() + cfg.RDS.PI = "" + if err := cfg.Validate(); err == nil { + t.Fatal("expected error") + } +} + +func TestValidateRejectsLongPS(t *testing.T) { + cfg := Default() + cfg.RDS.PS = "TOO_LONG_PS" + if err := cfg.Validate(); err == nil { + t.Fatal("expected error for PS longer than 8 characters") + } +} + +func TestValidateRejectsLongRadioText(t *testing.T) { + cfg := Default() + cfg.RDS.RadioText = strings.Repeat("x", 65) + if err := cfg.Validate(); err == nil { + t.Fatal("expected error for RadioText longer than 64 characters") + } } func TestEffectiveDeviceRate(t *testing.T) { cfg := Default() - if cfg.EffectiveDeviceRate() != float64(cfg.FM.CompositeRateHz) { t.Fatal("expected composite rate") } + if cfg.EffectiveDeviceRate() != float64(cfg.FM.CompositeRateHz) { + t.Fatal("expected composite rate") + } cfg.Backend.DeviceSampleRateHz = 912000 - if cfg.EffectiveDeviceRate() != 912000 { t.Fatal("expected 912000") } + if cfg.EffectiveDeviceRate() != 912000 { + t.Fatal("expected 912000") + } }