|
| 1 | +package config |
| 2 | + |
| 3 | +import ( |
| 4 | + "bufio" |
| 5 | + "encoding/json" |
| 6 | + "fmt" |
| 7 | + "net/netip" |
| 8 | + "os" |
| 9 | + |
| 10 | + "github.com/irdkwmnsb/webrtc-grabber/packages/relay/internal/api" |
| 11 | + "github.com/pion/webrtc/v4" |
| 12 | +) |
| 13 | + |
| 14 | +// ServerConfig holds the complete server configuration loaded from conf/config.json. |
| 15 | +// It includes settings for authentication, network access control, WebRTC parameters, |
| 16 | +// and codec configuration. |
| 17 | +// |
| 18 | +// Configuration is loaded once at startup via LoadServerConfig and passed to |
| 19 | +// the server and peer manager components. |
| 20 | +type ServerConfig struct { |
| 21 | + // PlayerCredential is an optional password for admin access to player endpoints. |
| 22 | + // If nil, no authentication is required (not recommended for production). |
| 23 | + PlayerCredential *string `json:"adminCredential"` |
| 24 | + |
| 25 | + // Participants is a list of expected grabber names for monitoring and status display. |
| 26 | + // These names are used in the admin interface to show which grabbers are online/offline. |
| 27 | + Participants []string `json:"participants"` |
| 28 | + |
| 29 | + // AdminsRawNetworks defines IP address ranges allowed to access admin endpoints. |
| 30 | + // Only clients from these CIDR ranges can connect to /ws/player/admin and /ws/player/play. |
| 31 | + // Example: ["192.168.1.0/24", "10.0.0.0/8"] |
| 32 | + AdminsRawNetworks []netip.Prefix `json:"adminsNetworks"` |
| 33 | + |
| 34 | + // PeerConnectionConfig contains WebRTC peer connection parameters including |
| 35 | + // ICE servers (STUN/TURN) for NAT traversal. |
| 36 | + PeerConnectionConfig api.PeerConnectionConfig `json:"peerConnectionConfig"` |
| 37 | + |
| 38 | + PublicIP string `json:"publicIp"` |
| 39 | + |
| 40 | + // GrabberPingInterval specifies how often (in seconds) grabbers should send |
| 41 | + // ping messages to indicate they are still alive. |
| 42 | + GrabberPingInterval int `json:"grabberPingInterval"` |
| 43 | + |
| 44 | + // ServerPort is the TCP port the WebSocket server listens on. |
| 45 | + // Defaults to 8000 if not specified. |
| 46 | + ServerPort int `json:"serverPort"` |
| 47 | + |
| 48 | + // ServerTLSCrtFile is the path to the TLS certificate file for HTTPS/WSS. |
| 49 | + // If nil, the server runs without TLS (not recommended for production). |
| 50 | + ServerTLSCrtFile *string `json:"serverTLSCrtFile"` |
| 51 | + |
| 52 | + // ServerTLSKeyFile is the path to the TLS private key file for HTTPS/WSS. |
| 53 | + // Must be provided together with ServerTLSCrtFile. |
| 54 | + ServerTLSKeyFile *string `json:"serverTLSKeyFile"` |
| 55 | + |
| 56 | + // Codecs defines the list of audio and video codecs supported by the server. |
| 57 | + // Each codec specifies MIME type, clock rate, payload type, and channel count. |
| 58 | + Codecs []Codec `json:"codecs"` |
| 59 | + |
| 60 | + // WebcamTrackCount specifies the expected number of tracks for webcam streams. |
| 61 | + // Typically 2 (one video, one audio). Defaults to 2 if not specified. |
| 62 | + // Screen share streams always expect 1 track (video only). |
| 63 | + WebcamTrackCount int `json:"webcamTrackCount"` |
| 64 | + RecordTimeout uint `json:"recordTimeout"` |
| 65 | + RecordStorageDir string `json:"recordStorageDirectory"` |
| 66 | +} |
| 67 | + |
| 68 | +// RawCodec represents the JSON structure for codec configuration before conversion |
| 69 | +// to WebRTC types. This intermediate representation allows for custom JSON unmarshaling. |
| 70 | +type RawCodec struct { |
| 71 | + // Params contains the codec parameters |
| 72 | + Params struct { |
| 73 | + // MimeType identifies the codec (e.g., "video/VP8", "audio/opus") |
| 74 | + MimeType string `json:"mimeType"` |
| 75 | + |
| 76 | + // ClockRate is the codec's clock rate in Hz (e.g., 90000 for video, 48000 for audio) |
| 77 | + ClockRate uint32 `json:"clockRate"` |
| 78 | + |
| 79 | + // PayloadType is the RTP payload type identifier (96-127 for dynamic types) |
| 80 | + PayloadType uint8 `json:"payloadType"` |
| 81 | + |
| 82 | + // Channels specifies the number of audio channels (e.g., 2 for stereo, 0 for video) |
| 83 | + Channels uint16 `json:"channels"` |
| 84 | + } `json:"params"` |
| 85 | + |
| 86 | + // Type is the codec type as a string ("video" or "audio") |
| 87 | + Type string `json:"type"` |
| 88 | +} |
| 89 | + |
| 90 | +// Codec represents a fully parsed codec configuration ready for use with WebRTC API. |
| 91 | +type Codec struct { |
| 92 | + // Params contains the WebRTC codec parameters |
| 93 | + Params webrtc.RTPCodecParameters `json:"params"` |
| 94 | + |
| 95 | + // Type indicates whether this is a video or audio codec |
| 96 | + Type webrtc.RTPCodecType `json:"type"` |
| 97 | +} |
| 98 | + |
| 99 | +// RawServerConfig is the intermediate structure used when parsing the JSON configuration file. |
| 100 | +// It uses string types for fields that need validation or conversion (like IP networks). |
| 101 | +type RawServerConfig struct { |
| 102 | + PlayerCredential *string `json:"adminCredential"` |
| 103 | + Participants []string `json:"participants"` |
| 104 | + AdminsRawNetworks []string `json:"adminsNetworks"` |
| 105 | + PeerConnectionConfig api.PeerConnectionConfig `json:"peerConnectionConfig"` |
| 106 | + PublicIP string `json:"publicIp"` |
| 107 | + GrabberPingInterval int `json:"grabberPingInterval"` |
| 108 | + ServerPort int `json:"serverPort"` |
| 109 | + ServerTLSCrtFile *string `json:"serverTLSCrtFile"` |
| 110 | + ServerTLSKeyFile *string `json:"serverTLSKeyFile"` |
| 111 | + Codecs []RawCodec `json:"codecs"` |
| 112 | + WebcamTrackCount int `json:"webcamTrackCount"` |
| 113 | + RecordTimeout uint `json:"recordTimeout"` |
| 114 | + RecordStorageDir string `json:"RecordStorageDirectory"` |
| 115 | +} |
| 116 | + |
| 117 | +// LoadServerConfig reads and parses the server configuration from conf/config.json. |
| 118 | +// It performs validation and applies default values for optional settings. |
| 119 | +// |
| 120 | +// The function: |
| 121 | +// 1. Opens and reads conf/config.json |
| 122 | +// 2. Parses JSON into RawServerConfig |
| 123 | +// 3. Applies defaults: ServerPort (8000), WebcamTrackCount (2) |
| 124 | +// 4. Validates and parses AdminsRawNetworks into CIDR prefixes |
| 125 | +// 5. Converts RawCodec structures to WebRTC Codec types |
| 126 | +// |
| 127 | +// Returns an error if: |
| 128 | +// - Configuration file cannot be opened or read |
| 129 | +// - JSON is malformed |
| 130 | +// - IP network CIDR notation is invalid |
| 131 | +// |
| 132 | +// Example configuration: |
| 133 | +// |
| 134 | +// { |
| 135 | +// "adminCredential": "secretpassword", |
| 136 | +// "participants": ["office-camera", "conference-room"], |
| 137 | +// "adminsNetworks": ["192.168.1.0/24", "10.0.0.0/8"], |
| 138 | +// "peerConnectionConfig": { |
| 139 | +// "iceServers": [{"urls": ["stun:stun.l.google.com:19302"]}] |
| 140 | +// }, |
| 141 | +// "grabberPingInterval": 5, |
| 142 | +// "serverPort": 8443, |
| 143 | +// "serverTLSCrtFile": "/etc/ssl/cert.pem", |
| 144 | +// "serverTLSKeyFile": "/etc/ssl/key.pem", |
| 145 | +// "codecs": [ |
| 146 | +// { |
| 147 | +// "params": { |
| 148 | +// "mimeType": "video/VP8", |
| 149 | +// "clockRate": 90000, |
| 150 | +// "payloadType": 96, |
| 151 | +// "channels": 0 |
| 152 | +// }, |
| 153 | +// "type": "video" |
| 154 | +// } |
| 155 | +// ], |
| 156 | +// "webcamTrackCount": 2 |
| 157 | +// } |
| 158 | +func LoadServerConfig() (ServerConfig, error) { |
| 159 | + var rawConfig RawServerConfig |
| 160 | + |
| 161 | + configFile, err := os.Open("conf/config.json") |
| 162 | + |
| 163 | + if err != nil { |
| 164 | + return ServerConfig{}, fmt.Errorf("can not open config file, error - %w", err) |
| 165 | + } |
| 166 | + |
| 167 | + defer func() { _ = configFile.Close() }() |
| 168 | + |
| 169 | + err = json.NewDecoder(bufio.NewReader(configFile)).Decode(&rawConfig) |
| 170 | + |
| 171 | + if err != nil { |
| 172 | + return ServerConfig{}, fmt.Errorf("can not decode config file to json - %w", err) |
| 173 | + } |
| 174 | + |
| 175 | + if rawConfig.ServerPort == 0 { |
| 176 | + rawConfig.ServerPort = 8000 |
| 177 | + } |
| 178 | + |
| 179 | + if rawConfig.WebcamTrackCount == 0 { |
| 180 | + rawConfig.WebcamTrackCount = 2 |
| 181 | + } |
| 182 | + |
| 183 | + adminsNetworks, err := parseAdminsNetworks(rawConfig.AdminsRawNetworks) |
| 184 | + |
| 185 | + if err != nil { |
| 186 | + return ServerConfig{}, fmt.Errorf("can not parse admins networks, error - %v", err) |
| 187 | + } |
| 188 | + |
| 189 | + if rawConfig.RecordTimeout <= 0 { |
| 190 | + rawConfig.RecordTimeout = 180000 |
| 191 | + } |
| 192 | + if rawConfig.RecordStorageDir != "" { |
| 193 | + err := os.MkdirAll(rawConfig.RecordStorageDir, os.ModePerm) |
| 194 | + if err != nil { |
| 195 | + return ServerConfig{}, fmt.Errorf("can create record directory, error - %v", err) |
| 196 | + } |
| 197 | + } |
| 198 | + |
| 199 | + return ServerConfig{ |
| 200 | + PlayerCredential: rawConfig.PlayerCredential, |
| 201 | + Participants: rawConfig.Participants, |
| 202 | + AdminsRawNetworks: adminsNetworks, |
| 203 | + PeerConnectionConfig: rawConfig.PeerConnectionConfig, |
| 204 | + PublicIP: rawConfig.PublicIP, |
| 205 | + GrabberPingInterval: rawConfig.GrabberPingInterval, |
| 206 | + ServerPort: rawConfig.ServerPort, |
| 207 | + ServerTLSCrtFile: rawConfig.ServerTLSCrtFile, |
| 208 | + ServerTLSKeyFile: rawConfig.ServerTLSKeyFile, |
| 209 | + Codecs: parseCodecs(rawConfig.Codecs), |
| 210 | + WebcamTrackCount: rawConfig.WebcamTrackCount, |
| 211 | + RecordTimeout: rawConfig.RecordTimeout, |
| 212 | + RecordStorageDir: rawConfig.RecordStorageDir, |
| 213 | + }, nil |
| 214 | +} |
| 215 | + |
| 216 | +// parseCodecs converts raw codec configurations from JSON into WebRTC codec types. |
| 217 | +// It transforms the intermediate RawCodec representation into properly typed |
| 218 | +// webrtc.RTPCodecParameters and webrtc.RTPCodecType structures. |
| 219 | +// |
| 220 | +// The conversion handles: |
| 221 | +// - MIME type string to RTPCodecCapability |
| 222 | +// - Clock rate (Hz) for timing |
| 223 | +// - Channels (for audio codecs) |
| 224 | +// - Payload type for RTP packets |
| 225 | +// - Codec type string ("video"/"audio") to RTPCodecType enum |
| 226 | +func parseCodecs(rawCodecs []RawCodec) []Codec { |
| 227 | + result := make([]Codec, 0, len(rawCodecs)) |
| 228 | + |
| 229 | + for _, rawCodec := range rawCodecs { |
| 230 | + params := webrtc.RTPCodecParameters{ |
| 231 | + RTPCodecCapability: webrtc.RTPCodecCapability{ |
| 232 | + MimeType: rawCodec.Params.MimeType, |
| 233 | + ClockRate: rawCodec.Params.ClockRate, |
| 234 | + Channels: rawCodec.Params.Channels, |
| 235 | + }, |
| 236 | + PayloadType: webrtc.PayloadType(rawCodec.Params.PayloadType), |
| 237 | + } |
| 238 | + |
| 239 | + result = append(result, Codec{Params: params, Type: webrtc.NewRTPCodecType(rawCodec.Type)}) |
| 240 | + } |
| 241 | + |
| 242 | + return result |
| 243 | +} |
| 244 | + |
| 245 | +// parseAdminsNetworks converts string CIDR notations into validated netip.Prefix objects. |
| 246 | +// This allows efficient IP address range checking for admin access control. |
| 247 | +// |
| 248 | +// Input strings must be in CIDR notation: "192.168.1.0/24", "10.0.0.0/8", etc. |
| 249 | +// |
| 250 | +// Returns an error if any network string is not valid CIDR notation. |
| 251 | +// |
| 252 | +// Example inputs: |
| 253 | +// - "192.168.1.0/24" - local network |
| 254 | +// - "10.0.0.0/8" - private network |
| 255 | +// - "2001:db8::/32" - IPv6 network |
| 256 | +func parseAdminsNetworks(rawNetworks []string) ([]netip.Prefix, error) { |
| 257 | + result := make([]netip.Prefix, 0, len(rawNetworks)) |
| 258 | + |
| 259 | + for _, r := range rawNetworks { |
| 260 | + network, err := netip.ParsePrefix(r) |
| 261 | + |
| 262 | + if err != nil { |
| 263 | + return nil, err |
| 264 | + } |
| 265 | + |
| 266 | + result = append(result, network) |
| 267 | + } |
| 268 | + |
| 269 | + return result, nil |
| 270 | +} |
0 commit comments