package relay import ( "encoding/base64" "encoding/json" "fmt" ) // RegionMaster is one region's active issuer master (spec §4): the master // key bytes and the kid it's issued under. type RegionMaster struct { Region string Kid string Master []byte } // RegionURLs returns the STUN/TURN URLs for a region, built from the // platform domain. type RegionURLs func(region string) []string // ConfigEntry is one region's worth of the CARLOS_RELAY value (spec §2): // the URLs to try, the derived key, and the username suffix a client // appends its own expiry to. type ConfigEntry struct { Region string `json:"region"` URLs []string `json:"urls"` Key string `json:"key"` UsernameSuffix string `json:"username_suffix"` } // BuildConfig renders CARLOS_RELAY's value for r: base64(std, padded) of // the JSON array from spec §2, one entry per region in r.Regions order. // masters must cover every region in r.Regions, or BuildConfig refuses // rather than issue a config a region can't actually serve. func BuildConfig(acct, app string, r *Record, masters []RegionMaster, urls RegionURLs) (string, map[string]string, error) { byRegion := map[string]RegionMaster{} for _, m := range masters { byRegion[m.Region] = m } entries := make([]ConfigEntry, 0, len(r.Regions)) suffixes := map[string]string{} for _, region := range r.Regions { m, ok := byRegion[region] if !ok { return "", nil, fmt.Errorf("relay: no issuer master for region %s", region) } key := DeriveKey(m.Master, acct, app, r.Incarnation) suffix := Suffix(acct, app, r.Incarnation, m.Kid) suffixes[region] = suffix entries = append(entries, ConfigEntry{Region: region, URLs: urls(region), Key: base64.StdEncoding.EncodeToString(key), UsernameSuffix: suffix}) } js, err := json.Marshal(entries) if err != nil { return "", nil, err } return base64.StdEncoding.EncodeToString(js), suffixes, nil } // ParseConfig decodes a CARLOS_RELAY value back into its entries, for the // config-ack (Task 12) and tests. func ParseConfig(value string) ([]ConfigEntry, error) { js, err := base64.StdEncoding.DecodeString(value) if err != nil { return nil, fmt.Errorf("relay: CARLOS_RELAY is not base64: %w", err) } var out []ConfigEntry if err := json.Unmarshal(js, &out); err != nil { return nil, fmt.Errorf("relay: CARLOS_RELAY is not a JSON array: %w", err) } return out, nil }