/// <summary>
        /// Main entry point
        /// </summary>
        static void Main(string[] args)
        {
            var prog = new Program();

            try {
                for (var index = 0; index < args.Length; index++)
                {
                    int tmp;
                    var opt = args[index];
                    if (string.IsNullOrEmpty(args[index]))
                    {
                        continue;
                    }
                    if (opt[0] == '-' || opt[0] == '/')
                    {
                        if (opt.Length == 1)
                        {
                            throw new ArgumentException($"Unknown option {opt}.");
                        }
                        var optc = opt[1];
                        if (optc == '-' && opt[0] != '/')
                        {
                            // Parse -- long option
                            var optl = opt.Substring(2);
                            switch (optl)
                            {
                            case "help":
                            case "source":
                            case "close":
                            case "wait":
                            case "local":
                                optc = optl[0];
                                break;

                            case "version":
                            case "relay":
                            case "websocket":
                                optc = char.ToUpperInvariant(optl[0]);
                                break;

                            case "proxy":
                                optc = 'x';
                                break;

                            default:
                                throw new ArgumentException($"Unknown option {opt}.");
                            }
                        }
                        switch (optc)
                        {
                        case 'd':
                            prog.NoStdIn = true;
                            break;

                        case 'c':
                            prog.CloseOnEof = true;
                            break;

                        case 'R':
                            prog.UseRelay = true;
                            break;

                        case 'W':
                            prog.UseWS = true;
                            break;

                        case '?':
                        case 'h':
                            PrintHelp();
                            return;

                        case 'x':
                        case 's':
                            index++;
                            SocketAddress proxy;
                            if (index >= args.Length ||
                                !SocketAddress.TryParse(args[index], out proxy))
                            {
                                throw new ArgumentException($"Bad value for {opt}.");
                            }
                            prog.Proxy = proxy;
                            break;

                        case 'w':
                            index++;
                            if (index >= args.Length ||
                                !int.TryParse(args[index], out tmp))
                            {
                                throw new ArgumentException($"Bad value for {opt}.");
                            }
                            prog.Timeout = tmp * 1000;
                            break;

                        case 'l':
                            index++;
                            if (index >= args.Length ||
                                !int.TryParse(args[index], out tmp))
                            {
                                throw new ArgumentException($"Bad value for {opt}.");
                            }
                            prog.LocalPort = (ushort)tmp;
                            break;

                        default:
                            throw new ArgumentException($"Unknown option {opt}.");
                        }

                        continue;
                    }

                    if (string.IsNullOrEmpty(prog.Host))
                    {
                        prog.Host = opt;
                    }
                    else
                    {
                        var ports = opt.Split('-');
                        if (!int.TryParse(ports[0], out tmp))
                        {
                            throw new ArgumentException($"Invalid port value {opt}.");
                        }
                        prog.Ports.Add(tmp);
                        if (ports.Length == 2)
                        {
                            if (!int.TryParse(ports[1], out var hi) || hi > ushort.MaxValue)
                            {
                                throw new ArgumentException($"Invalid upper bound for port range: {opt}.");
                            }
                            while (++tmp <= hi)
                            {
                                prog.Ports.Add(tmp);
                            }
                        }
                        else if (ports.Length > 2)
                        {
                            throw new ArgumentException($"Invalid port range: {opt}.");
                        }
                    }
                }
                prog.Validate();
            }
            catch (ArgumentException e) {
                Console.Error.WriteLine($"Error: {e.Message}");
                PrintHelp();
                return;
            }

            // Register console cancellation
            var cts = new CancellationTokenSource();

            Console.CancelKeyPress += (o, e) => {
                Console.Error.WriteLine();
                Console.Error.WriteLine("Ctrl+C => Cancellation requested...");
                cts.Cancel();
                e.Cancel = true; // Wait for cts to kill readers and exit - do not exit immediately.
            };
            prog.RunAsync(cts.Token).Wait();
        }