namespace Zcbot.WindowsNode; internal sealed record EnrollOptions(Uri ServerUrl, string NodeName, string EnrollmentCode) { internal static EnrollOptions Parse(string[] args) { var values = new Dictionary(StringComparer.OrdinalIgnoreCase); for (var index = 0; index < args.Length; index += 2) { if (index + 1 >= args.Length || !args[index].StartsWith("--", StringComparison.Ordinal)) { throw Usage(); } values[args[index][2..]] = args[index + 1]; } if (!values.TryGetValue("server", out var server) || !values.TryGetValue("name", out var name) || !values.TryGetValue("code", out var code)) { throw Usage(); } var serverUrl = NodeUri.NormalizeServerUrl(server); name = name.Trim(); code = code.Trim(); if (name.Length is < 1 or > 100 || code.Length is < 16 or > 100) { throw new NodeConfigurationException("Node name or enrollment code has an invalid length."); } return new EnrollOptions(serverUrl, name, code); } private static NodeConfigurationException Usage() => new( "Usage: Zcbot.WindowsNode enroll --server http://host:8765 --name win-origin-01 --code ZCN-..."); } internal static class NodeUri { internal static Uri NormalizeServerUrl(string raw) { if (!Uri.TryCreate(raw.Trim(), UriKind.Absolute, out var uri) || (uri.Scheme != Uri.UriSchemeHttp && uri.Scheme != Uri.UriSchemeHttps) || !string.IsNullOrEmpty(uri.UserInfo) || !string.IsNullOrEmpty(uri.Query) || !string.IsNullOrEmpty(uri.Fragment)) { throw new NodeConfigurationException("Server URL must be an absolute HTTP or HTTPS URL without credentials, query, or fragment."); } return new Uri(uri.GetLeftPart(UriPartial.Path).TrimEnd('/') + "/", UriKind.Absolute); } internal static Uri WebSocketEndpoint(Uri serverUrl) { var builder = new UriBuilder(new Uri(serverUrl, "v1/compute/nodes/connect")) { Scheme = serverUrl.Scheme == Uri.UriSchemeHttps ? "wss" : "ws" }; return builder.Uri; } }