* * @throws RuntimeException */ public static function validate(mixed $data, string $providerName = 'ICE Provider'): array { if (! is_array($data)) { throw new RuntimeException("{$providerName} API returned an invalid non-array response."); } // Support wrapped responses like ['iceServers' => [...]] or ['v' => [...]] $servers = isset($data['iceServers']) && is_array($data['iceServers']) ? $data['iceServers'] : (isset($data['v']) && is_array($data['v']) ? $data['v'] : $data); if (empty($servers) || ! is_array($servers)) { throw new RuntimeException("{$providerName} API returned an empty or invalid list of ICE servers."); } $validServers = []; $validProtocols = ['stun:', 'stuns:', 'turn:', 'turns:']; foreach ($servers as $index => $server) { if (! is_array($server)) { throw new RuntimeException("{$providerName} server entry at index [{$index}] must be an object/array."); } $rawUrls = $server['urls'] ?? $server['url'] ?? null; if (empty($rawUrls)) { throw new RuntimeException("{$providerName} server entry at index [{$index}] is missing 'urls'."); } $urlList = is_array($rawUrls) ? $rawUrls : [$rawUrls]; foreach ($urlList as $url) { if (! is_string($url) || trim($url) === '') { throw new RuntimeException("{$providerName} URL at index [{$index}] must be a non-empty string."); } $hasValidProtocol = false; foreach ($validProtocols as $protocol) { if (str_starts_with(strtolower(trim($url)), $protocol)) { $hasValidProtocol = true; break; } } if (! $hasValidProtocol) { throw new RuntimeException("{$providerName} invalid ICE URL scheme [{$url}] at index [{$index}]."); } } $validServers[] = $server; } return $validServers; } }