diff --git a/channel-rainbow.json b/channel-rainbow.json index 73ad3a237..23e1e7829 100644 --- a/channel-rainbow.json +++ b/channel-rainbow.json @@ -295,5 +295,28 @@ "#bogota": "6d0864985b64350ce4cbfebf4979e970", "#peru": "7e6fc347bf29a4c128ac3156865bd521", "#lima": "5f167ce354eca08ab742463df10ef255", - "Public": "8b3387e9c5cdea6ac9e5edbaa115cd72" + "Public": "8b3387e9c5cdea6ac9e5edbaa115cd72", + "#Public": "7534fe292203ccdce93db79377d4b566", + "#newbie": "ff2ecc59f59dbf051a10d880f0b4308d", + "#power": "c4d23a51329aeaee767234644d52cff8", + "#garden": "56ff6fe3500872c2765dbc178350005c", + "#jokes": "abe610d61dee508c0ac18793c36c41c3", + "#space": "5caa27568ba7a216bcc09fcb69c77ea1", + "#path": "29eed6c759d96346b30c869e23fd86f8", + "#hamradio": "83c8b01997654265938da8765cbc7db9", + "#amrron": "68ec397a1dd4f3416911d00211ed0ef6", + "#family": "7d1b805d4bf059c2ef06ed221794663b", + "#testing": "cde5e82cf515647dcb547a79a4f065d1", + "#wicked": "05e990d48943ee03c943c41dea0ddfe1", + "#gay": "3a612fa3bfc9f3b439cc230b01cbbc00", + "#t": "7b5336e85005be805b496bc27c722ff3", + "#doom": "7034910637d6b9b6c2cc3bcd8dfe9bb5", + "#empty": "3351cfa0dad99e1777cce210c983b561", + "#puppy": "c20cf85aab9561e6e65ac4a19a852198", + "#naked": "f423f23ddcf9e472c2ace38dce7591bc", + "#cruising": "550857053360e3492755251f9142e74c", + "#nightowls": "1411c5c3732d3df1b855644f78c47417", + "#worldcup": "d5786cc7bcee5a4828bbb20c3825c5a9", + "#nofilter": "cab31f589959debcd1721b7d252259b1", + "#politics": "64cc9fdaad67f03f23b4cec8c9a4468f" } diff --git a/internal/channel/channel_rainbow_test.go b/internal/channel/channel_rainbow_test.go new file mode 100644 index 000000000..e593a6adb --- /dev/null +++ b/internal/channel/channel_rainbow_test.go @@ -0,0 +1,97 @@ +package channel + +import ( + "bytes" + "encoding/hex" + "encoding/json" + "io" + "os" + "path/filepath" + "testing" +) + +const expectedRainbowEntryCount = 320 + +func TestChannelRainbowMatchesDeriveKey(t *testing.T) { + data, err := os.ReadFile(filepath.Join("..", "..", "channel-rainbow.json")) + if err != nil { + t.Fatalf("read channel-rainbow.json: %v", err) + } + if !json.Valid(data) { + t.Fatal("channel-rainbow.json is not valid JSON") + } + + entries := decodeUniqueRainbowEntries(t, data) + if got := len(entries); got != expectedRainbowEntryCount { + t.Fatalf("channel-rainbow.json has %d entries, want %d", got, expectedRainbowEntryCount) + } + + seenKeys := make(map[string]string, len(entries)) + for name, got := range entries { + if otherName, exists := seenKeys[got]; exists { + t.Errorf("duplicate channel key %q used by %q and %q", got, otherName, name) + } else { + seenKeys[got] = name + } + + if name == "Public" { + // Public is the firmware-default PSK, not a hashtag-derived key. + if want := "8b3387e9c5cdea6ac9e5edbaa115cd72"; got != want { + t.Errorf("Public key = %q, want firmware default %q", got, want) + } + continue + } + + want := hex.EncodeToString(DeriveKey(name)) + if got != want { + t.Errorf("%s key = %q, want DeriveKey result %q", name, got, want) + } + } +} + +func decodeUniqueRainbowEntries(t *testing.T, data []byte) map[string]string { + t.Helper() + dec := json.NewDecoder(bytes.NewReader(data)) + + opening, err := dec.Token() + if err != nil { + t.Fatalf("decode opening token: %v", err) + } + if opening != json.Delim('{') { + t.Fatalf("channel-rainbow.json must contain an object, got %v", opening) + } + + entries := make(map[string]string) + for dec.More() { + nameToken, err := dec.Token() + if err != nil { + t.Fatalf("decode channel name: %v", err) + } + name, ok := nameToken.(string) + if !ok { + t.Fatalf("channel name must be a string, got %T", nameToken) + } + if _, exists := entries[name]; exists { + t.Fatalf("duplicate channel name %q", name) + } + + var key string + if err := dec.Decode(&key); err != nil { + t.Fatalf("decode key for %q: %v", name, err) + } + entries[name] = key + } + + closing, err := dec.Token() + if err != nil { + t.Fatalf("decode closing token: %v", err) + } + if closing != json.Delim('}') { + t.Fatalf("channel-rainbow.json object is not closed, got %v", closing) + } + if err := dec.Decode(&struct{}{}); err != io.EOF { + t.Fatalf("unexpected content after channel-rainbow.json object: %v", err) + } + + return entries +}