public async Task DiscoverDevicesAsync(Func <HttpDiscoveredDeviceContext, Task <bool> > onDeviceDiscovered, CancellationToken cancellationToken = default(CancellationToken))
        {
            string discoverRequest =
                "M-SEARCH * HTTP/1.1\n" +
                "Host: 239.255.255.250:1900\n" +
                "Man: \"ssdp:discover\"\n" +
                "ST: roku:ecp\n" +
                "";
            var bytes = Encoding.UTF8.GetBytes(discoverRequest);

            using (var udpClient = new UdpClient(endPoint)) {
                await udpClient.SendAsync(bytes, bytes.Length, "239.255.255.250", 1900).ConfigureAwait(false);

                while (!cancellationToken.IsCancellationRequested)
                {
                    var receiveTask = Task.Run(
                        async() => {
                        try {
                            return(await udpClient.ReceiveAsync().ConfigureAwait(false));
                        } catch (ObjectDisposedException) {
                            // NOTE: We assume that a disposal exception is an attempt to cancel an
                            //       outstanding ReceiveAsync() by closing the socket (disposing the
                            //       UdpClient).

                            throw new OperationCanceledException();
                        }
                    });

                    var cancellationTask = Task.Delay(TimeSpan.FromMilliseconds(-1), cancellationToken);

                    var completedTask = await Task.WhenAny(receiveTask, cancellationTask).ConfigureAwait(false);

                    if (completedTask == cancellationTask)
                    {
                        // NOTE: We allow the OperationCanceledException to bubble up, causing disposal of the
                        //       UdpClient which would force any pending ReceiveAsync() to throw an
                        //       ObjectDisposedException.

                        await cancellationTask.ConfigureAwait(false);

                        return;
                    }

                    var rawResponse = await receiveTask.ConfigureAwait(false);

                    var response = await ParseResponseAsync(rawResponse.Buffer).ConfigureAwait(false);

                    if (response.StatusCode == 200 &&
                        response.Headers.TryGetValue("ST", out string stHeader) &&
                        stHeader == "roku:ecp" &&
                        response.Headers.TryGetValue("LOCATION", out string location) &&
                        Uri.TryCreate(location, UriKind.Absolute, out Uri locationUri) &&
                        response.Headers.TryGetValue("USN", out string usn))
                    {
                        var serialNumber = usn.StartsWith("uuid:roku:ecp:")
                                                        ? usn.Substring("uuid:roku:ecp:".Length)
                                                        : usn;

                        var device = new HttpRokuDevice(serialNumber, locationUri, this.handler);

                        bool cancelDiscovery = false;
                        var  context         = new HttpDiscoveredDeviceContext(device, serialNumber);

                        if (onDeviceDiscovered != null)
                        {
                            cancelDiscovery = await onDeviceDiscovered(context).ConfigureAwait(false);
                        }

                        var args = new HttpDeviceDiscoveredEventArgs(context)
                        {
                            CancelDiscovery = cancelDiscovery
                        };

                        this.DeviceDiscovered?.Invoke(this, args);

                        cancelDiscovery = args.CancelDiscovery;

                        var baseArgs = new DeviceDiscoveredEventArgs(context)
                        {
                            CancelDiscovery = cancelDiscovery
                        };

                        this.baseDeviceDiscovered?.Invoke(this, baseArgs);

                        cancelDiscovery = baseArgs.CancelDiscovery;

                        if (cancelDiscovery)
                        {
                            return;
                        }
                    }
                }
            }
        }
 public HttpDeviceDiscoveredEventArgs(HttpDiscoveredDeviceContext context)
     : base(context)
 {
     this.Context = context;
 }