/// <summary>
        /// <see cref="IDisposable.Dispose"/>
        /// </summary>
        public void Dispose()
        {
            this.disposed = true;
            this.State    = PeerToPeerNetworkState.Closed;

            NetworkChange.NetworkAddressChanged -= NetworkChange_NetworkAddressChanged;

            this.searchTimer?.Dispose();
            this.searchTimer = null;

            this.tcpListener?.Stop();
            this.tcpListener = null;

            if (this.tcpMappingAdded)
            {
                this.tcpMappingAdded = false;
                try
                {
                    this.serviceWANIPConnectionV1.DeletePortMapping(string.Empty, (ushort)this.localEndpoint.Port, "TCP");
                }
                catch (Exception)
                {
                    // Ignore
                }
            }

            if (this.udpMappingAdded)
            {
                this.udpMappingAdded = false;
                try
                {
                    this.serviceWANIPConnectionV1.DeletePortMapping(string.Empty, (ushort)this.localEndpoint.Port, "UDP");
                }
                catch (Exception)
                {
                    // Ignore
                }
            }

            this.serviceWANIPConnectionV1 = null;

            this.upnpClient?.Dispose();
            this.upnpClient = null;

            this.ipAddressesFound?.Clear();
            this.ipAddressesFound = null;

            this.ready?.Dispose();
            this.ready = null;

            this.error?.Dispose();
            this.error = null;
        }
        private void ServiceRetrieved(ServiceDescriptionDocument Scpd, IPEndPoint LocalEndPoint)
        {
            try
            {
                Dictionary <ushort, bool> TcpPortMapped = new Dictionary <ushort, bool>();
                Dictionary <ushort, bool> UdpPortMapped = new Dictionary <ushort, bool>();
                ushort PortMappingIndex;
                bool   TcpAlreadyRegistered = false;
                bool   UdpAlreadyRegistered = false;

                this.serviceWANIPConnectionV1 = new WANIPConnectionV1(Scpd);
                this.State = PeerToPeerNetworkState.RegisteringApplicationInGateway;

                this.serviceWANIPConnectionV1.GetExternalIPAddress(out string NewExternalIPAddress);
                this.externalAddress = IPAddress.Parse(NewExternalIPAddress);

                if (!IsPublicAddress(this.externalAddress))
                {
                    return;                         // TODO: Handle multiple layers of gateways.
                }
                PortMappingIndex = 0;

                try
                {
                    while (true)
                    {
                        this.serviceWANIPConnectionV1.GetGenericPortMappingEntry(PortMappingIndex, out string NewRemoteHost,
                                                                                 out ushort NewExternalPort, out string NewProtocol, out ushort NewInternalPort, out string NewInternalClient,
                                                                                 out bool NewEnabled, out string NewPortMappingDescription, out uint NewLeaseDuration);

                        if (NewPortMappingDescription == this.applicationName && NewInternalClient == LocalEndPoint.Address.ToString())
                        {
                            if (NewExternalPort == this.desiredExternalPort && this.desiredExternalPort != 0)
                            {
                                if (NewProtocol == "TCP")
                                {
                                    TcpAlreadyRegistered = true;
                                    PortMappingIndex++;
                                    continue;
                                }
                                else if (NewProtocol == "UDP")
                                {
                                    UdpAlreadyRegistered = true;
                                    PortMappingIndex++;
                                    continue;
                                }
                            }

                            this.serviceWANIPConnectionV1.DeletePortMapping(NewRemoteHost, NewExternalPort, NewProtocol);
                        }
                        else
                        {
                            switch (NewProtocol)
                            {
                            case "TCP":
                                TcpPortMapped[NewExternalPort] = true;
                                break;

                            case "UDP":
                                UdpPortMapped[NewExternalPort] = true;
                                break;
                            }

                            PortMappingIndex++;
                        }
                    }
                }
                catch (AggregateException ex)
                {
                    if (!(ex.InnerException is UPnPException))
                    {
                        throw;
                    }
                }
                catch (UPnPException)
                {
                    // No more entries.
                }

                this.localAddress = LocalEndPoint.Address;
                ushort LocalPort, ExternalPort;
                int    i;

                do
                {
                    this.tcpListener = new TcpListener(this.localAddress, this.desiredLocalPort);
                    this.tcpListener.Start(this.backlog);

                    i            = ((IPEndPoint)this.tcpListener.LocalEndpoint).Port;
                    LocalPort    = (ushort)(i);
                    ExternalPort = this.desiredExternalPort == 0 ? LocalPort : (ushort)this.desiredExternalPort;

                    if (i < 0 || i > ushort.MaxValue || TcpPortMapped.ContainsKey(ExternalPort) || UdpPortMapped.ContainsKey(ExternalPort))
                    {
                        this.tcpListener.Stop();
                        this.tcpListener = null;

                        throw new ArgumentException("Port already assigned to another application in the network.", nameof(ExternalPort));
                    }
                    else
                    {
                        try
                        {
                            this.udpClient = new UdpClient(this.tcpListener.LocalEndpoint.AddressFamily);
                            this.udpClient.Client.Bind((IPEndPoint)this.tcpListener.LocalEndpoint);
                        }
                        catch (Exception)
                        {
                            this.tcpListener.Stop();
                            this.tcpListener = null;
                        }
                    }
                }while (this.tcpListener == null);

                this.localEndpoint = new IPEndPoint(this.localAddress, LocalPort);

                if (!TcpAlreadyRegistered)
                {
                    this.serviceWANIPConnectionV1.AddPortMapping(string.Empty, ExternalPort,
                                                                 "TCP", LocalPort, LocalAddress.ToString(), true, this.applicationName, 0);
                }

                this.tcpMappingAdded = true;

                if (!UdpAlreadyRegistered)
                {
                    this.serviceWANIPConnectionV1.AddPortMapping(string.Empty, ExternalPort,
                                                                 "UDP", LocalPort, LocalAddress.ToString(), true, this.applicationName, 0);
                }

                this.udpMappingAdded = true;

                this.externalEndpoint = new IPEndPoint(this.externalAddress, ExternalPort);
                this.State            = PeerToPeerNetworkState.Ready;

                this.AcceptTcpClients();
                this.BeginReceiveUdp();
            }
            catch (Exception ex)
            {
                this.exception = ex;
                this.State     = PeerToPeerNetworkState.Error;
            }
        }
        /// <summary>
        /// <see cref="IDisposable.Dispose"/>
        /// </summary>
        public virtual void Dispose()
        {
            this.disposed = true;
            this.State    = PeerToPeerNetworkState.Closed;

            NetworkChange.NetworkAddressChanged -= NetworkChange_NetworkAddressChanged;

            this.searchTimer?.Dispose();
            this.searchTimer = null;

            foreach (InternetGatewayRegistration Registration in this.ports)
            {
                if (Registration.TcpRegistered)
                {
                    Registration.TcpRegistered = false;
                    try
                    {
                        Log.Notice("Deleting Internet Gateway port mapping.",
                                   new KeyValuePair <string, object>("Host", string.Empty),
                                   new KeyValuePair <string, object>("External Port", Registration.ExternalPort),
                                   new KeyValuePair <string, object>("Protocol", "TCP"),
                                   new KeyValuePair <string, object>("Local Port", Registration.LocalPort),
                                   new KeyValuePair <string, object>("Application", Registration.ApplicationName));

                        this.serviceWANIPConnectionV1.DeletePortMapping(string.Empty, Registration.LocalPort, "TCP");
                    }
                    catch (Exception)
                    {
                        // Ignore
                    }
                }

                if (Registration.UdpRegistered)
                {
                    Registration.UdpRegistered = false;
                    try
                    {
                        Log.Notice("Deleting Internet Gateway port mapping.",
                                   new KeyValuePair <string, object>("Host", string.Empty),
                                   new KeyValuePair <string, object>("External Port", Registration.ExternalPort),
                                   new KeyValuePair <string, object>("Protocol", "UDP"),
                                   new KeyValuePair <string, object>("Local Port", Registration.LocalPort),
                                   new KeyValuePair <string, object>("Application", Registration.ApplicationName));

                        this.serviceWANIPConnectionV1.DeletePortMapping(string.Empty, Registration.LocalPort, "UDP");
                    }
                    catch (Exception)
                    {
                        // Ignore
                    }
                }
            }

            this.serviceWANIPConnectionV1 = null;

            this.upnpClient?.Dispose();
            this.upnpClient = null;

            this.ipAddressesFound?.Clear();
            this.ipAddressesFound = null;

            this.ready?.Dispose();
            this.ready = null;

            this.error?.Dispose();
            this.error = null;
        }
        private void ServiceRetrieved(ServiceDescriptionDocument Scpd, IPEndPoint LocalEndPoint)
        {
            try
            {
                Dictionary <ushort, bool> TcpPortMapped = new Dictionary <ushort, bool>();
                Dictionary <ushort, bool> UdpPortMapped = new Dictionary <ushort, bool>();
                ushort PortMappingIndex;

                this.serviceWANIPConnectionV1 = new WANIPConnectionV1(Scpd);
                this.State = PeerToPeerNetworkState.RegisteringApplicationInGateway;

                this.serviceWANIPConnectionV1.GetExternalIPAddress(out string NewExternalIPAddress);
                this.externalAddress = IPAddress.Parse(NewExternalIPAddress);

                Log.Informational("External IP Address: " + NewExternalIPAddress);

                if (!IsPublicAddress(this.externalAddress))
                {
                    Log.Warning("External IP Address not a public IP address.");
                    return;                         // TODO: Handle multiple layers of gateways.
                }

                PortMappingIndex = 0;

                try
                {
                    string LocalAddress = LocalEndPoint.Address.ToString();

                    while (true)
                    {
                        this.serviceWANIPConnectionV1.GetGenericPortMappingEntry(PortMappingIndex, out string NewRemoteHost,
                                                                                 out ushort NewExternalPort, out string NewProtocol, out ushort NewInternalPort, out string NewInternalClient,
                                                                                 out bool NewEnabled, out string NewPortMappingDescription, out uint NewLeaseDuration);

                        if (NewInternalClient != LocalAddress)
                        {
                            PortMappingIndex++;
                            continue;
                        }

                        bool Found = false;

                        foreach (InternetGatewayRegistration Registration in this.ports)
                        {
                            if ((Registration.ExternalPort != 0 && NewExternalPort == Registration.ExternalPort) ||
                                (Registration.ExternalPort == 0 && NewPortMappingDescription == Registration.ApplicationName))
                            {
                                if (NewProtocol == "TCP")
                                {
                                    Found = true;
                                    Registration.TcpRegistered = true;
                                    break;
                                }
                                else if (NewProtocol == "UDP")
                                {
                                    Found = true;
                                    Registration.UdpRegistered = true;
                                    break;
                                }

                                Log.Notice("Deleting Internet Gateway port mapping.",
                                           new KeyValuePair <string, object>("Host", NewRemoteHost),
                                           new KeyValuePair <string, object>("External Port", NewExternalPort),
                                           new KeyValuePair <string, object>("Protocol", NewProtocol),
                                           new KeyValuePair <string, object>("Local Port", NewInternalPort),
                                           new KeyValuePair <string, object>("Local Address", NewInternalClient),
                                           new KeyValuePair <string, object>("Application", NewPortMappingDescription));

                                this.serviceWANIPConnectionV1.DeletePortMapping(NewRemoteHost, NewExternalPort, NewProtocol);
                            }
                        }

                        if (Found)
                        {
                            PortMappingIndex++;
                            continue;
                        }
                        else
                        {
                            switch (NewProtocol)
                            {
                            case "TCP":
                                TcpPortMapped[NewExternalPort] = true;
                                break;

                            case "UDP":
                                UdpPortMapped[NewExternalPort] = true;
                                break;
                            }

                            PortMappingIndex++;
                        }
                    }
                }
                catch (AggregateException ex)
                {
                    if (!(ex.InnerException is UPnPException))
                    {
                        System.Runtime.ExceptionServices.ExceptionDispatchInfo.Capture(ex).Throw();
                    }
                }
                catch (UPnPException)
                {
                    // No more entries.
                }

                this.localAddress = LocalEndPoint.Address;

                foreach (InternetGatewayRegistration Registration in this.ports)
                {
                    this.BeforeRegistration(Registration, TcpPortMapped, UdpPortMapped);

                    if ((Registration.TcpRegistered || !Registration.Tcp) &&
                        (Registration.UdpRegistered || !Registration.Udp))
                    {
                        continue;
                    }

                    if (Registration.Tcp && !Registration.TcpRegistered)
                    {
                        Log.Notice("Adding Internet Gateway port mapping.",
                                   new KeyValuePair <string, object>("Host", string.Empty),
                                   new KeyValuePair <string, object>("External Port", Registration.ExternalPort),
                                   new KeyValuePair <string, object>("Protocol", "TCP"),
                                   new KeyValuePair <string, object>("Local Port", Registration.LocalPort),
                                   new KeyValuePair <string, object>("Local Address", LocalAddress.ToString()),
                                   new KeyValuePair <string, object>("Application", Registration.ApplicationName));

                        try
                        {
                            this.serviceWANIPConnectionV1.AddPortMapping(string.Empty, Registration.ExternalPort,
                                                                         "TCP", Registration.LocalPort, LocalAddress.ToString(), true, Registration.ApplicationName, 0);

                            Registration.TcpRegistered = true;
                        }
                        catch (Exception ex)
                        {
                            Log.Error("Unable to register port in Internet Gateway: " + ex.Message,
                                      new KeyValuePair <string, object>("External Port", Registration.ExternalPort),
                                      new KeyValuePair <string, object>("Protocol", "TCP"),
                                      new KeyValuePair <string, object>("Local Port", Registration.LocalPort),
                                      new KeyValuePair <string, object>("Local Address", LocalAddress.ToString()),
                                      new KeyValuePair <string, object>("Application", Registration.ApplicationName));
                        }
                    }

                    if (Registration.Udp && !Registration.UdpRegistered)
                    {
                        Log.Notice("Adding Internet Gateway port mapping.",
                                   new KeyValuePair <string, object>("Host", string.Empty),
                                   new KeyValuePair <string, object>("External Port", Registration.ExternalPort),
                                   new KeyValuePair <string, object>("Protocol", "UDP"),
                                   new KeyValuePair <string, object>("Local Port", Registration.LocalPort),
                                   new KeyValuePair <string, object>("Local Address", LocalAddress.ToString()),
                                   new KeyValuePair <string, object>("Application", Registration.ApplicationName));

                        try
                        {
                            this.serviceWANIPConnectionV1.AddPortMapping(string.Empty, Registration.ExternalPort,
                                                                         "UDP", Registration.LocalPort, LocalAddress.ToString(), true, Registration.ApplicationName, 0);

                            Registration.UdpRegistered = true;
                        }
                        catch (Exception ex)
                        {
                            Log.Error("Unable to register port in Internet Gateway: " + ex.Message,
                                      new KeyValuePair <string, object>("External Port", Registration.ExternalPort),
                                      new KeyValuePair <string, object>("Protocol", "UDP"),
                                      new KeyValuePair <string, object>("Local Port", Registration.LocalPort),
                                      new KeyValuePair <string, object>("Local Address", LocalAddress.ToString()),
                                      new KeyValuePair <string, object>("Application", Registration.ApplicationName));
                        }
                    }
                }

                this.State = PeerToPeerNetworkState.Ready;
            }
            catch (Exception ex)
            {
                Log.Critical(ex);

                this.exception = ex;
                this.State     = PeerToPeerNetworkState.Error;
            }
        }
        /// <summary>
        /// <see cref="IDisposable.Dispose"/>
        /// </summary>
        public void Dispose()
        {
            this.State = PeerToPeerNetworkState.Closed;

            if (!(this.tcpListener is null))
            {
                this.tcpListener.Stop();
                this.tcpListener = null;
            }

            if (this.tcpMappingAdded)
            {
                this.tcpMappingAdded = false;
                try
                {
                    this.serviceWANIPConnectionV1.DeletePortMapping(string.Empty, (ushort)this.localEndpoint.Port, "TCP");
                }
                catch (Exception)
                {
                    // Ignore
                }
            }

            if (this.udpMappingAdded)
            {
                this.udpMappingAdded = false;
                try
                {
                    this.serviceWANIPConnectionV1.DeletePortMapping(string.Empty, (ushort)this.localEndpoint.Port, "UDP");
                }
                catch (Exception)
                {
                    // Ignore
                }
            }

            this.serviceWANIPConnectionV1 = null;

            if (!(this.upnpClient is null))
            {
                this.upnpClient.Dispose();
                this.upnpClient = null;
            }

            if (!(this.ipAddressesFound is null))
            {
                this.ipAddressesFound.Clear();
                this.ipAddressesFound = null;
            }

            if (!(this.ready is null))
            {
                this.ready.Close();
                this.ready = null;
            }

            if (!(this.error is null))
            {
                this.error.Close();
                this.error = null;
            }
        }
        private void ServiceRetrieved(object Sender, ServiceDescriptionEventArgs e)
        {
            try
            {
                DeviceLocationEventArgs   e2            = (DeviceLocationEventArgs)e.State;
                Dictionary <ushort, bool> TcpPortMapped = new Dictionary <ushort, bool>();
                Dictionary <ushort, bool> UdpPortMapped = new Dictionary <ushort, bool>();
                ushort PortMappingIndex;

                this.serviceWANIPConnectionV1 = new WANIPConnectionV1(e.ServiceDescriptionDocument);
                this.State = PeerToPeerNetworkState.RegisteringApplicationInGateway;

                this.serviceWANIPConnectionV1.GetExternalIPAddress(out string NewExternalIPAddress);
                this.externalAddress = IPAddress.Parse(NewExternalIPAddress);

                PortMappingIndex = 0;

                try
                {
                    while (true)
                    {
                        this.serviceWANIPConnectionV1.GetGenericPortMappingEntry(PortMappingIndex, out string NewRemoteHost,
                                                                                 out ushort NewExternalPort, out string NewProtocol, out ushort NewInternalPort, out string NewInternalClient,
                                                                                 out bool NewEnabled, out string NewPortMappingDescription, out uint NewLeaseDuration);

                        if (NewPortMappingDescription == this.applicationName && NewInternalClient == e2.LocalEndPoint.Address.ToString())
                        {
                            this.serviceWANIPConnectionV1.DeletePortMapping(NewRemoteHost, NewExternalPort, NewProtocol);
                        }
                        else
                        {
                            switch (NewProtocol)
                            {
                            case "TCP":
                                TcpPortMapped[NewExternalPort] = true;
                                break;

                            case "UDP":
                                UdpPortMapped[NewExternalPort] = true;
                                break;
                            }

                            PortMappingIndex++;
                        }
                    }
                }
                catch (UPnPException)
                {
                    // No more entries.
                }

                this.localAddress = e2.LocalEndPoint.Address;
                ushort LocalPort;
                int    i;

                do
                {
                    this.tcpListener = new TcpListener(this.localAddress, this.desiredPort);
                    this.tcpListener.Start(this.backlog);

                    i         = ((IPEndPoint)this.tcpListener.LocalEndpoint).Port;
                    LocalPort = (ushort)(i);
                    if (i < 0 || i > ushort.MaxValue || TcpPortMapped.ContainsKey(LocalPort) || UdpPortMapped.ContainsKey(LocalPort))
                    {
                        this.tcpListener.Stop();
                        this.tcpListener = null;

                        if (this.desiredPort != 0)
                        {
                            throw new ArgumentException("Port already assigned to another application in the network.", "Port");
                        }
                    }
                    else
                    {
                        try
                        {
                            this.udpClient = new UdpClient(this.tcpListener.LocalEndpoint.AddressFamily);
                            this.udpClient.Client.Bind((IPEndPoint)this.tcpListener.LocalEndpoint);
                        }
                        catch (Exception)
                        {
                            this.tcpListener.Stop();
                            this.tcpListener = null;
                        }
                    }
                }while (this.tcpListener is null);

                this.localEndpoint = new IPEndPoint(this.localAddress, LocalPort);

                this.serviceWANIPConnectionV1.AddPortMapping(string.Empty, LocalPort, "TCP", LocalPort, LocalAddress.ToString(), true, this.applicationName, 0);
                this.tcpMappingAdded = true;

                this.serviceWANIPConnectionV1.AddPortMapping(string.Empty, LocalPort, "UDP", LocalPort, LocalAddress.ToString(), true, this.applicationName, 0);
                this.udpMappingAdded = true;

                this.externalEndpoint = new IPEndPoint(this.externalAddress, LocalPort);
                this.State            = PeerToPeerNetworkState.Ready;

                this.tcpListener.BeginAcceptTcpClient(this.EndAcceptTcpClient, null);
                this.udpClient.BeginReceive(this.EndReceiveUdp, null);
            }
            catch (Exception ex)
            {
                this.exception = ex;
                this.State     = PeerToPeerNetworkState.Error;
            }
        }