Ejemplo n.º 1
0
        /// <summary>
        /// Starts the gateway.
        /// </summary>
        /// <param name="ConsoleOutput">If console output is permitted.</param>
        public static bool Start(bool ConsoleOutput)
        {
            gatewayRunning = new Semaphore(1, 1, "Waher.IoTGateway.Running");
            if (!gatewayRunning.WaitOne(1000))
            {
                return(false);                // Is running in another process.
            }
            Semaphore StartingServer = new Semaphore(1, 1, "Waher.IoTGateway.Starting");

            if (!StartingServer.WaitOne(1000))
            {
                gatewayRunning.Release();
                gatewayRunning.Dispose();
                gatewayRunning = null;

                StartingServer.Dispose();
                return(false);                // Being started in another process.
            }

            try
            {
                Initialize();

                appDataFolder = Environment.GetFolderPath(Environment.SpecialFolder.CommonApplicationData);
                if (!appDataFolder.EndsWith(new string(Path.DirectorySeparatorChar, 1)))
                {
                    appDataFolder += Path.DirectorySeparatorChar;
                }

                appDataFolder += "IoT Gateway" + Path.DirectorySeparatorChar;

                Log.Register(new XmlFileEventSink("XML File Event Sink",
                                                  appDataFolder + "Events" + Path.DirectorySeparatorChar + "Event Log %YEAR%-%MONTH%-%DAY%T%HOUR%.xml",
                                                  appDataFolder + "Transforms" + Path.DirectorySeparatorChar + "EventXmlToHtml.xslt", 7));

                Log.Informational("Server starting up.");

                beforeUninstallCommandNr = Gateway.RegisterServiceCommand(BeforeUninstall);

                rootFolder = appDataFolder + "Root" + Path.DirectorySeparatorChar;
                if (!Directory.Exists(rootFolder))
                {
                    appDataFolder = string.Empty;
                    rootFolder    = "Root" + Path.DirectorySeparatorChar;
                }

                Types.SetModuleParameter("AppData", appDataFolder);
                Types.SetModuleParameter("Root", rootFolder);

                scheduler = new Scheduler();
                rnd       = RandomNumberGenerator.Create();

                Task.Run(() => CodeContent.GraphViz.Init());

                XmlDocument Config = new XmlDocument();

                string GatewayConfigFileName = appDataFolder + "Gateway.config";
                if (!File.Exists(GatewayConfigFileName))
                {
                    GatewayConfigFileName = "Gateway.config";
                }

                Config.Load(GatewayConfigFileName);
                XSL.Validate("Gateway.config", Config, "GatewayConfiguration", "http://waher.se/Schema/GatewayConfiguration.xsd",
                             XSL.LoadSchema(typeof(Gateway).Namespace + ".Schema.GatewayConfiguration.xsd", typeof(Gateway).Assembly));

                domain = Config.DocumentElement["Domain"].InnerText;

                XmlElement DatabaseConfig = Config.DocumentElement["Database"];
                if (!CommonTypes.TryParse(DatabaseConfig.Attributes["encrypted"].Value, out bool Encrypted))
                {
                    Encrypted = true;
                }

                databaseProvider = new FilesProvider(appDataFolder + DatabaseConfig.Attributes["folder"].Value,
                                                     DatabaseConfig.Attributes["defaultCollectionName"].Value,
                                                     int.Parse(DatabaseConfig.Attributes["blockSize"].Value),
                                                     int.Parse(DatabaseConfig.Attributes["blocksInCache"].Value),
                                                     int.Parse(DatabaseConfig.Attributes["blobBlockSize"].Value), Encoding.UTF8,
                                                     int.Parse(DatabaseConfig.Attributes["timeoutMs"].Value),
                                                     Encrypted, false);
                Database.Register(databaseProvider);

                PersistedEventLog PersistedEventLog = new PersistedEventLog(7, new TimeSpan(4, 15, 0));
                Log.Register(PersistedEventLog);
                PersistedEventLog.Queue(new Event(EventType.Informational, "Server starting up.", string.Empty, string.Empty, string.Empty, EventLevel.Minor, string.Empty, string.Empty, string.Empty));

                xmppConfigFileName = Config.DocumentElement["XmppClient"].Attributes["configFileName"].Value;
                if (!File.Exists(xmppConfigFileName))
                {
                    xmppConfigFileName = appDataFolder + xmppConfigFileName;
                }

                if (ConsoleOutput)
                {
                    xmppConfiguration = SimpleXmppConfiguration.GetConfigUsingSimpleConsoleDialog(xmppConfigFileName,
                                                                                                  Guid.NewGuid().ToString().Replace("-", string.Empty), // Default user name.
                                                                                                  Guid.NewGuid().ToString().Replace("-", string.Empty), // Default password.
                                                                                                  FormSignatureKey, FormSignatureSecret, typeof(Gateway).Assembly);
                }
                else if (File.Exists(xmppConfigFileName))
                {
                    xmppConfiguration = new SimpleXmppConfiguration(xmppConfigFileName);
                    RuntimeSettings.Set("XMPP.CONFIG", xmppConfiguration.ExportSimpleXmppConfiguration());
                }
                else
                {
                    string      XmppConfig = RuntimeSettings.Get("XMPP.CONFIG", string.Empty);
                    XmlDocument Doc        = new XmlDocument();
                    Doc.LoadXml(XmppConfig);
                    xmppConfiguration = new SimpleXmppConfiguration(Doc);
                }

                xmppClient = xmppConfiguration.GetClient("en", typeof(Gateway).Assembly, false);
                xmppClient.AllowRegistration(FormSignatureKey, FormSignatureSecret);
                xmppClient.OnValidateSender += XmppClient_OnValidateSender;
                Types.SetModuleParameter("XMPP", xmppClient);

                if (xmppConfiguration.Sniffer)
                {
                    ISniffer Sniffer;

                    if (ConsoleOutput)
                    {
                        Sniffer = new ConsoleOutSniffer(BinaryPresentationMethod.ByteCount, LineEnding.PadWithSpaces);
                        xmppClient.Add(Sniffer);
                    }

                    Sniffer = new XmlFileSniffer(appDataFolder + "XMPP" + Path.DirectorySeparatorChar +
                                                 "XMPP Log %YEAR%-%MONTH%-%DAY%T%HOUR%.xml",
                                                 appDataFolder + "Transforms" + Path.DirectorySeparatorChar + "SnifferXmlToHtml.xslt",
                                                 7, BinaryPresentationMethod.ByteCount);
                    xmppClient.Add(Sniffer);
                }

                if (!string.IsNullOrEmpty(xmppConfiguration.Events))
                {
                    Log.Register(new XmppEventSink("XMPP Event Sink", xmppClient, xmppConfiguration.Events, false));
                }

                if (!string.IsNullOrEmpty(xmppConfiguration.ThingRegistry))
                {
                    thingRegistryClient           = new ThingRegistryClient(xmppClient, xmppConfiguration.ThingRegistry);
                    thingRegistryClient.Claimed  += ThingRegistryClient_Claimed;
                    thingRegistryClient.Disowned += ThingRegistryClient_Disowned;
                    thingRegistryClient.Removed  += ThingRegistryClient_Removed;
                }

                if (!string.IsNullOrEmpty(xmppConfiguration.Provisioning))
                {
                    provisioningClient = new ProvisioningClient(xmppClient, xmppConfiguration.Provisioning);
                }

                DateTime Now      = DateTime.Now;
                int      MsToNext = 60000 - (Now.Second * 1000 + Now.Millisecond);

                connectionTimer                   = new Timer(CheckConnection, null, MsToNext, 60000);
                xmppClient.OnStateChanged        += XmppClient_OnStateChanged;
                xmppClient.OnPresenceSubscribe   += XmppClient_OnPresenceSubscribe;
                xmppClient.OnPresenceUnsubscribe += XmppClient_OnPresenceUnsubscribe;
                xmppClient.OnRosterItemUpdated   += XmppClient_OnRosterItemUpdated;

                ibbClient = new Networking.XMPP.InBandBytestreams.IbbClient(xmppClient, MaxChunkSize);
                Types.SetModuleParameter("IBB", ibbClient);

                socksProxy = new Networking.XMPP.P2P.SOCKS5.Socks5Proxy(xmppClient);
                Types.SetModuleParameter("SOCKS5", socksProxy);

                string CertificateLocalFileName = Config.DocumentElement["Certificate"].Attributes["configFileName"].Value;
                string CertificateFileName;
                string CertificateXml;
                string CertificatePassword;
                byte[] CertificateRaw;

                try
                {
                    CertificateRaw      = Convert.FromBase64String(RuntimeSettings.Get("CERTIFICATE.BASE64", string.Empty));
                    CertificatePassword = RuntimeSettings.Get("CERTIFICATE.PWD", string.Empty);

                    certificate = new X509Certificate2(CertificateRaw, CertificatePassword);
                }
                catch (Exception)
                {
                    certificate = null;
                }

                if (File.Exists(CertificateFileName = appDataFolder + CertificateLocalFileName))
                {
                    CertificateXml = File.ReadAllText(CertificateFileName);
                }
                else if (File.Exists(CertificateFileName = CertificateLocalFileName) && certificate == null)
                {
                    CertificateXml = File.ReadAllText(CertificateFileName);
                }
                else
                {
                    CertificateFileName = null;
                    CertificateXml      = null;
                }

                if (CertificateXml != null)
                {
                    XmlDocument CertificateConfig = new XmlDocument();
                    CertificateConfig.LoadXml(CertificateXml);

                    XSL.Validate(CertificateLocalFileName, CertificateConfig, "CertificateConfiguration", "http://waher.se/Schema/CertificateConfiguration.xsd",
                                 XSL.LoadSchema(typeof(Gateway).Namespace + ".Schema.CertificateConfiguration.xsd", typeof(Gateway).Assembly));

                    CertificateLocalFileName = CertificateConfig.DocumentElement["FileName"].InnerText;

                    if (File.Exists(appDataFolder + CertificateLocalFileName))
                    {
                        CertificateLocalFileName = appDataFolder + CertificateLocalFileName;
                    }

                    CertificateRaw      = File.ReadAllBytes(CertificateLocalFileName);
                    CertificatePassword = CertificateConfig.DocumentElement["Password"].InnerText;

                    certificate = new X509Certificate2(CertificateRaw, CertificatePassword);

                    RuntimeSettings.Set("CERTIFICATE.BASE64", Convert.ToBase64String(CertificateRaw));
                    RuntimeSettings.Set("CERTIFICATE.PWD", CertificatePassword);

                    if (CertificateLocalFileName != "certificate.pfx" || CertificatePassword != "testexamplecom")
                    {
                        try
                        {
                            File.Delete(CertificateLocalFileName);
                        }
                        catch (Exception)
                        {
                            Log.Warning("Unable to delete " + CertificateLocalFileName + " after importing it into the encrypted database.");
                        }

                        try
                        {
                            File.Delete(CertificateFileName);
                        }
                        catch (Exception)
                        {
                            Log.Warning("Unable to delete " + CertificateFileName + " after importing it into the encrypted database.");
                        }
                    }
                }

                foreach (XmlNode N in Config.DocumentElement["Ports"].ChildNodes)
                {
                    if (N.LocalName == "Port")
                    {
                        XmlElement E        = (XmlElement)N;
                        string     Protocol = XML.Attribute(E, "protocol");
                        if (!string.IsNullOrEmpty(Protocol) && int.TryParse(E.InnerText, out int Port))
                        {
                            ports.AddLast(new KeyValuePair <string, int>(Protocol, Port));
                        }
                    }
                }

                webServer = new HttpServer(GetConfigPorts("HTTP"), GetConfigPorts("HTTPS"), certificate);
                Types.SetModuleParameter("HTTP", webServer);

                StringBuilder sb = new StringBuilder();

                foreach (int Port in webServer.OpenPorts)
                {
                    sb.AppendLine(Port.ToString());
                }

                try
                {
                    File.WriteAllText(appDataFolder + "Ports.txt", sb.ToString());
                }
                catch (Exception ex)
                {
                    Log.Critical(ex);
                }

                HttpFolderResource HttpFolderResource;
                HttpxProxy         HttpxProxy;

                webServer.Register(new HttpFolderResource("/Graphics", "Graphics", false, false, true, false));                      // TODO: Add authentication mechanisms for PUT & DELETE.
                webServer.Register(new HttpFolderResource("/highlight", "Highlight", false, false, true, false));                    // Syntax highlighting library, provided by http://highlightjs.org
                webServer.Register(HttpFolderResource = new HttpFolderResource(string.Empty, rootFolder, false, false, true, true)); // TODO: Add authentication mechanisms for PUT & DELETE.
                webServer.Register(HttpxProxy         = new HttpxProxy("/HttpxProxy", xmppClient, MaxChunkSize));
                webServer.Register("/", (req, resp) =>
                {
                    throw new TemporaryRedirectException(Config.DocumentElement["DefaultPage"].InnerText);
                });
                webServer.Register(clientEvents = new ClientEvents());

                HttpFolderResource.AllowTypeConversion();
                MarkdownToHtmlConverter.EmojiSource = Emoji1_24x24;

                XmlElement FileFolders = Config.DocumentElement["FileFolders"];
                if (FileFolders != null)
                {
                    foreach (XmlNode N in FileFolders.ChildNodes)
                    {
                        if (N is XmlElement E && E.LocalName == "FileFolder")
                        {
                            string WebFolder  = XML.Attribute(E, "webFolder");
                            string FolderPath = XML.Attribute(E, "folderPath");

                            webServer.Register(new HttpFolderResource(WebFolder, FolderPath, false, false, true, true));
                        }
                    }
                }

                httpxServer = new HttpxServer(xmppClient, webServer, MaxChunkSize);
                Types.SetModuleParameter("HTTPX", HttpxProxy);
                Types.SetModuleParameter("HTTPXS", httpxServer);

                HttpxProxy.IbbClient  = ibbClient;
                httpxServer.IbbClient = ibbClient;

                HttpxProxy.Socks5Proxy  = socksProxy;
                httpxServer.Socks5Proxy = socksProxy;

                if (xmppConfiguration.Sniffer)
                {
                    ISniffer Sniffer;

                    Sniffer = new XmlFileSniffer(appDataFolder + "HTTP" + Path.DirectorySeparatorChar +
                                                 "HTTP Log %YEAR%-%MONTH%-%DAY%T%HOUR%.xml",
                                                 appDataFolder + "Transforms" + Path.DirectorySeparatorChar + "SnifferXmlToHtml.xslt",
                                                 7, BinaryPresentationMethod.ByteCount);
                    webServer.Add(Sniffer);
                }

                coapEndpoint = new CoapEndpoint();
                Types.SetModuleParameter("CoAP", coapEndpoint);

                concentratorServer = new ConcentratorServer(xmppClient, provisioningClient, new MeteringTopology());
                Types.SetModuleParameter("Concentrator", concentratorServer);
                Types.SetModuleParameter("Sensor", concentratorServer.SensorServer);
                Types.SetModuleParameter("Control", concentratorServer.ControlServer);
            }
            catch (Exception ex)
            {
                Log.Critical(ex);

                gatewayRunning.Release();
                gatewayRunning.Dispose();
                gatewayRunning = null;

                StartingServer.Release();
                StartingServer.Dispose();

                ExceptionDispatchInfo.Capture(ex).Throw();
            }

            Task.Run(async() =>
            {
                try
                {
                    try
                    {
                        string BinaryFolder    = AppDomain.CurrentDomain.BaseDirectory;
                        string[] LanguageFiles = Directory.GetFiles(BinaryFolder, "*.lng", SearchOption.AllDirectories);
                        string FileName;

                        if (LanguageFiles.Length > 0)
                        {
                            XmlSchema Schema = XSL.LoadSchema(Translator.SchemaResource, typeof(Translator).Assembly);

                            foreach (string LanguageFile in LanguageFiles)
                            {
                                try
                                {
                                    FileName = LanguageFile;
                                    if (FileName.StartsWith(BinaryFolder))
                                    {
                                        FileName = FileName.Substring(BinaryFolder.Length);
                                    }

                                    DateTime LastWriteTime    = File.GetLastWriteTime(LanguageFile);
                                    DateTime LastImportedTime = await RuntimeSettings.GetAsync(FileName, DateTime.MinValue);

                                    if (LastWriteTime > LastImportedTime)
                                    {
                                        Log.Informational("Importing language file.", FileName);

                                        string Xml      = File.ReadAllText(LanguageFile);
                                        XmlDocument Doc = new XmlDocument();
                                        Doc.LoadXml(Xml);

                                        XSL.Validate(FileName, Doc, Translator.SchemaRoot, Translator.SchemaNamespace, Schema);

                                        using (XmlReader r = new XmlNodeReader(Doc))
                                        {
                                            await Translator.ImportAsync(r);
                                        }

                                        RuntimeSettings.Set(FileName, LastWriteTime);
                                    }
                                }
                                catch (Exception ex)
                                {
                                    Log.Critical(ex, LanguageFile);
                                }
                            }
                        }

                        Types.StartAllModules(int.MaxValue);
                    }
                    finally
                    {
                        StartingServer.Release();
                        StartingServer.Dispose();
                    }
                }
                catch (Exception ex)
                {
                    Log.Critical(ex);
                }
                finally
                {
                    xmppClient.Connect();
                }
            });

            return(true);
        }
Ejemplo n.º 2
0
        private async Task Client_OnStateChanged(object Sender, XmppState NewState)
        {
            if (!(Sender is XmppClient Client))
            {
                return;
            }

            if (!Client.TryGetTag("TabID", out object Obj) || !(Obj is string TabID))
            {
                return;
            }

            try
            {
                string Msg;

                switch (NewState)
                {
                case XmppState.Authenticating:
                    Client.SetTag("StartedAuthentication", true);
                    Client.SetTag("EncyptionSuccessful", true);
                    if (this.Step == 0)
                    {
                        ClientEvents.PushEvent(new string[] { TabID }, "ConnectionOK0", "Connection established.", false, "User");

                        this.client.Dispose();
                        this.client = null;

                        this.Step    = 1;
                        this.Updated = DateTime.Now;
                        await Database.Update(this);

                        return;
                    }
                    else
                    {
                        Msg = "Authenticating user.";
                    }
                    break;

                case XmppState.Binding:
                    Msg = "Binding to resource.";
                    break;

                case XmppState.Connected:
                    this.bareJid = Client.BareJID;

                    if (this.bareJid != defaultFacility)
                    {
                        defaultFacility = this.bareJid;

                        if (string.IsNullOrEmpty(defaultFacilityKey))
                        {
                            defaultFacilityKey = Hashes.BinaryToString(Gateway.NextBytes(32));
                        }

                        foreach (IEventSink Sink in Log.Sinks)
                        {
                            if (Sink is PersistedEventLog PersistedEventLog)
                            {
                                try
                                {
                                    PersistedEventLog.SetDefaultFacility(defaultFacility, defaultFacilityKey);
                                }
                                catch (Exception ex)
                                {
                                    Log.Critical(ex);
                                }

                                break;
                            }
                        }
                    }

                    if (this.createAccount && !string.IsNullOrEmpty(this.accountHumanReadableName))
                    {
                        ClientEvents.PushEvent(new string[] { TabID }, "ShowStatus", "Setting vCard.", false, "User");

                        StringBuilder Xml = new StringBuilder();

                        Xml.Append("<vCard xmlns='vcard-temp'>");
                        Xml.Append("<FN>");
                        Xml.Append(XML.Encode(this.accountHumanReadableName));
                        Xml.Append("</FN>");
                        Xml.Append("<JABBERID>");
                        Xml.Append(XML.Encode(this.client.BareJID));
                        Xml.Append("</JABBERID>");
                        Xml.Append("</vCard>");

                        await Client.IqSetAsync(this.client.BareJID, Xml.ToString());
                    }

                    ClientEvents.PushEvent(new string[] { TabID }, "ShowStatus", "Checking server features.", false, "User");
                    ServiceDiscoveryEventArgs e = await Client.ServiceDiscoveryAsync(null, string.Empty, string.Empty);

                    if (e.Ok)
                    {
                        this.offlineMessages = e.HasFeature("msgoffline");
                        this.blocking        = e.HasFeature(Networking.XMPP.Abuse.AbuseClient.NamespaceBlocking);
                        this.reporting       = e.HasFeature(Networking.XMPP.Abuse.AbuseClient.NamespaceReporting);
                        this.abuse           = e.HasFeature(Networking.XMPP.Abuse.AbuseClient.NamespaceAbuseReason);
                        this.spam            = e.HasFeature(Networking.XMPP.Abuse.AbuseClient.NamespaceSpamReason);
                        this.mail            = e.HasFeature("urn:xmpp:smtp");
                    }
                    else
                    {
                        this.offlineMessages = false;
                        this.blocking        = false;
                        this.reporting       = false;
                        this.abuse           = false;
                        this.spam            = false;
                        this.mail            = false;
                    }

                    ClientEvents.PushEvent(new string[] { TabID }, "ShowStatus", "Checking account features.", false, "User");
                    e = await Client.ServiceDiscoveryAsync(null, Client.BareJID, string.Empty);

                    this.pep = e.Ok && this.ContainsIdentity("pep", "pubsub", e);

                    ClientEvents.PushEvent(new string[] { TabID }, "ShowStatus", "Checking server components.", false, "User");
                    ServiceItemsDiscoveryEventArgs e2 = await Client.ServiceItemsDiscoveryAsync(null, string.Empty, string.Empty);

                    this.thingRegistry = string.Empty;
                    this.provisioning  = string.Empty;
                    this.events        = string.Empty;
                    this.pubSub        = string.Empty;
                    this.legal         = string.Empty;
                    this.software      = string.Empty;

                    if (e2.Ok)
                    {
                        foreach (Item Item in e2.Items)
                        {
                            ClientEvents.PushEvent(new string[] { TabID }, "ShowStatus", "Checking component features for " + Item.JID, false, "User");

                            e = await Client.ServiceDiscoveryAsync(null, Item.JID, string.Empty);

                            if (e.HasFeature(Networking.XMPP.Provisioning.ThingRegistryClient.NamespaceDiscovery))
                            {
                                this.thingRegistry = Item.JID;
                            }

                            if (e.HasFeature(Networking.XMPP.Provisioning.ProvisioningClient.NamespaceProvisioningDevice))
                            {
                                this.provisioning = Item.JID;
                            }

                            if (e.HasFeature(Networking.XMPP.PubSub.PubSubClient.NamespacePubSub) && this.ContainsIdentity("service", "pubsub", e))
                            {
                                this.pubSub = Item.JID;
                            }

                            if (e.HasFeature(Waher.Events.XMPP.XmppEventSink.NamespaceEventLogging))
                            {
                                this.events = Item.JID;
                            }

                            if (e.HasFeature(Networking.XMPP.Contracts.ContractsClient.NamespaceLegalIdentities))
                            {
                                this.legal = Item.JID;
                            }

                            if (e.HasFeature(Networking.XMPP.Software.SoftwareUpdateClient.NamespaceSoftwareUpdates))
                            {
                                this.software = Item.JID;
                            }
                        }
                    }

                    Dictionary <string, object> ConnectionInfo = new Dictionary <string, object>()
                    {
                        { "msg", "Connection successful." },
                        { "offlineMsg", this.offlineMessages },
                        { "blocking", this.blocking },
                        { "reporting", this.reporting },
                        { "abuse", this.abuse },
                        { "spam", this.spam },
                        { "mail", this.mail },
                        { "pep", this.pep ? this.bareJid : string.Empty },
                        { "thingRegistry", this.thingRegistry },
                        { "provisioning", this.provisioning },
                        { "eventLog", this.events },
                        { "pubSub", this.pubSub },
                        { "legal", this.legal },
                        { "software", this.software }
                    };

                    ClientEvents.PushEvent(new string[] { TabID }, "ConnectionOK1", JSON.Encode(ConnectionInfo, false), true, "User");

                    this.client.Dispose();
                    this.client = null;

                    this.Step    = 2;
                    this.Updated = DateTime.Now;
                    await Database.Update(this);

                    return;

                case XmppState.Connecting:
                    Msg = "Connecting to server.";
                    break;

                case XmppState.Error:
                    bool Error = false;
                    Msg = string.Empty;

                    if (this.Step == 0 && this.transportMethod == XmppTransportMethod.C2S)
                    {
                        this.customBinding = true;

                        ClientEvents.PushEvent(new string[] { TabID }, "ShowStatus", "Unable to connect properly. Looking for alternative ways to connect.", false, "User");
                        ClientEvents.PushEvent(new string[] { TabID }, "ShowCustomProperties", "{\"visible\":true}", true, "User");

                        using (HttpClient HttpClient = new HttpClient(new HttpClientHandler()
                        {
#if !NETFW
                            ServerCertificateCustomValidationCallback = this.RemoteCertificateValidationCallback,
#endif
                            UseCookies = false
                        })
                        {
                            Timeout = TimeSpan.FromMilliseconds(60000)
                        })
                        {
                            try
                            {
                                HttpResponseMessage Response = await HttpClient.GetAsync("http://" + this.host + "/.well-known/host-meta");

                                Response.EnsureSuccessStatusCode();

                                Stream Stream = await Response.Content.ReadAsStreamAsync();                                         // Regardless of status code, we check for XML content.

                                byte[] Bin = await Response.Content.ReadAsByteArrayAsync();

                                string   CharSet = Response.Content.Headers.ContentType.CharSet;
                                Encoding Encoding;

                                if (string.IsNullOrEmpty(CharSet))
                                {
                                    Encoding = Encoding.UTF8;
                                }
                                else
                                {
                                    Encoding = InternetContent.GetEncoding(CharSet);
                                }

                                string      XmlResponse = Encoding.GetString(Bin);
                                XmlDocument Doc         = new XmlDocument();
                                Doc.LoadXml(XmlResponse);

                                if (Doc.DocumentElement != null && Doc.DocumentElement.LocalName == "XRD")
                                {
                                    string BoshUrl = null;
                                    string WsUrl   = null;

                                    foreach (XmlNode N in Doc.DocumentElement.ChildNodes)
                                    {
                                        if (N is XmlElement E && E.LocalName == "Link")
                                        {
                                            switch (XML.Attribute(E, "rel"))
                                            {
                                            case "urn:xmpp:alt-connections:xbosh":
                                                BoshUrl = XML.Attribute(E, "href");
                                                break;

                                            case "urn:xmpp:alt-connections:websocket":
                                                WsUrl = XML.Attribute(E, "href");
                                                break;
                                            }
                                        }
                                    }

                                    if (!string.IsNullOrEmpty(WsUrl))
                                    {
                                        this.wsUrl           = WsUrl;
                                        this.transportMethod = XmppTransportMethod.WS;

                                        ClientEvents.PushEvent(new string[] { TabID }, "ShowTransport", "{\"method\":\"WS\"}", true, "User");

                                        this.Connect(TabID);

                                        return;
                                    }
                                    else if (!string.IsNullOrEmpty(BoshUrl))
                                    {
                                        this.boshUrl         = BoshUrl;
                                        this.transportMethod = XmppTransportMethod.BOSH;

                                        ClientEvents.PushEvent(new string[] { TabID }, "ShowTransport", "{\"method\":\"BOSH\"}", true, "User");

                                        this.Connect(TabID);

                                        return;
                                    }
                                }
                            }
                            catch (Exception)
                            {
                                // Ignore.
                            }

                            Msg   = "No alternative binding methods found.";
                            Error = true;
                        }
                    }
                    else
                    {
                        Msg   = "Unable to connect properly.";
                        Error = true;

                        if (Client.TryGetTag("StartedAuthentication", out Obj) && Obj is bool b && b)
                        {
                            if (this.createAccount)
                            {
                                ClientEvents.PushEvent(new string[] { TabID }, "ShowFail2", Msg, false, "User");
                            }
                            else
                            {
                                ClientEvents.PushEvent(new string[] { TabID }, "ShowFail1", Msg, false, "User");
                            }
                            return;
                        }
                    }

                    if (Error)
                    {
                        ClientEvents.PushEvent(new string[] { TabID }, "ConnectionError", Msg, false, "User");

                        this.client.Dispose();
                        this.client = null;

                        return;
                    }
                    break;

                case XmppState.FetchingRoster:
                    Msg = "Fetching roster from server.";
                    break;

                case XmppState.Offline:
                    Msg = "Offline.";
                    break;

                case XmppState.Registering:
                    Msg = "Registering account.";
                    break;

                case XmppState.RequestingSession:
                    Msg = "Requesting session.";
                    break;

                case XmppState.SettingPresence:
                    Msg = "Setting presence.";
                    break;

                case XmppState.StartingEncryption:
                    Msg = "Starting encryption.";
                    Client.SetTag("StartedEncryption", true);
                    break;

                case XmppState.StreamNegotiation:
                    Msg = "Negotiating stream.";
                    break;

                case XmppState.StreamOpened:
                    Msg = "Stream opened.";
                    break;

                default:
                    Msg = NewState.ToString();
                    break;
                }

                ClientEvents.PushEvent(new string[] { TabID }, "ShowStatus", Msg, false, "User");
            }
            catch (Exception ex)
            {
                Log.Critical(ex);
                ClientEvents.PushEvent(new string[] { TabID }, "ShowStatus", ex.Message, false, "User");
            }
        }