carlos / platform Public

Clone
git clone https://amadan.net/carlos/platform

Plain git — no account needed to clone.

Download

Download this file

1package relay
2
3import (
4 "encoding/base64"
5 "encoding/json"
6 "fmt"
7)
8
9// RegionMaster is one region's active issuer master (spec §4): the master
10// key bytes and the kid it's issued under.
11type RegionMaster struct {
12 Region string
13 Kid string
14 Master []byte
15}
16
17// RegionURLs returns the STUN/TURN URLs for a region, built from the
18// platform domain.
19type RegionURLs func(region string) []string
20
21// ConfigEntry is one region's worth of the CARLOS_RELAY value (spec §2):
22// the URLs to try, the derived key, and the username suffix a client
23// appends its own expiry to.
24type ConfigEntry struct {
25 Region string `json:"region"`
26 URLs []string `json:"urls"`
27 Key string `json:"key"`
28 UsernameSuffix string `json:"username_suffix"`
29}
30
31// BuildConfig renders CARLOS_RELAY's value for r: base64(std, padded) of
32// the JSON array from spec §2, one entry per region in r.Regions order.
33// masters must cover every region in r.Regions, or BuildConfig refuses
34// rather than issue a config a region can't actually serve.
35func BuildConfig(acct, app string, r *Record, masters []RegionMaster, urls RegionURLs) (string, map[string]string, error) {
36 byRegion := map[string]RegionMaster{}
37 for _, m := range masters {
38 byRegion[m.Region] = m
39 }
40 entries := make([]ConfigEntry, 0, len(r.Regions))
41 suffixes := map[string]string{}
42 for _, region := range r.Regions {
43 m, ok := byRegion[region]
44 if !ok {
45 return "", nil, fmt.Errorf("relay: no issuer master for region %s", region)
46 }
47 key := DeriveKey(m.Master, acct, app, r.Incarnation)
48 suffix := Suffix(acct, app, r.Incarnation, m.Kid)
49 suffixes[region] = suffix
50 entries = append(entries, ConfigEntry{Region: region, URLs: urls(region), Key: base64.StdEncoding.EncodeToString(key), UsernameSuffix: suffix})
51 }
52 js, err := json.Marshal(entries)
53 if err != nil {
54 return "", nil, err
55 }
56 return base64.StdEncoding.EncodeToString(js), suffixes, nil
57}
58
59// ParseConfig decodes a CARLOS_RELAY value back into its entries, for the
60// config-ack (Task 12) and tests.
61func ParseConfig(value string) ([]ConfigEntry, error) {
62 js, err := base64.StdEncoding.DecodeString(value)
63 if err != nil {
64 return nil, fmt.Errorf("relay: CARLOS_RELAY is not base64: %w", err)
65 }
66 var out []ConfigEntry
67 if err := json.Unmarshal(js, &out); err != nil {
68 return nil, fmt.Errorf("relay: CARLOS_RELAY is not a JSON array: %w", err)
69 }
70 return out, nil
71}
72