From 9d5477ab85a528587d1feded0254f40362235297 Mon Sep 17 00:00:00 2001 From: Steven Livingstone-Perez Date: Thu, 27 Oct 2016 10:00:40 +0100 Subject: [PATCH 01/22] Added support for specifying the XMPP server to connect to when it is different from the XMPP hostname in the Jid. --- Client/XmppClient.cs | 71 +++++++++++++++++--- Core/XmppCore.cs | 155 +++++++++++++++++++++++++++++++++---------- Im/XmppIm.cs | 62 +++++++++++++++-- Sharp.Xmpp.csproj | 7 +- packages.config | 2 +- 5 files changed, 240 insertions(+), 57 deletions(-) diff --git a/Client/XmppClient.cs b/Client/XmppClient.cs index b2f5ec23..c6f44c7e 100644 --- a/Client/XmppClient.cs +++ b/Client/XmppClient.cs @@ -697,12 +697,38 @@ public event EventHandler Error /// Use this constructor if you wish to connect to an XMPP server using /// an existing set of user credentials. public XmppClient(string hostname, string username, string password, - int port = 5222, bool tls = true, RemoteCertificateValidationCallback validate = null) - { - im = new XmppIm(hostname, username, password, port, tls, validate); - // Initialize the various extension modules. - LoadExtensions(); - } + int port = 5222, bool tls = true, RemoteCertificateValidationCallback validate = null) : + this(hostname, username, password, null, port, tls, validate) { } + + /// + /// Initializes a new instance of the XmppClient class. + /// + /// The hostname of the XMPP server to connect to. + /// The username with which to authenticate. In XMPP jargon + /// this is known as the 'node' part of the JID. + /// The password with which to authenticate. + /// The IP address or domain of the XMPP server, if different from the hostname. eg. xmpp.server.com + /// The port number of the XMPP service of the server. + /// If true the session will be TLS/SSL-encrypted if the server + /// supports TLS/SSL-encryption. + /// A delegate used for verifying the remote Secure Sockets + /// Layer (SSL) certificate which is used for authentication. Can be null if not + /// needed. + /// The hostname parameter or the + /// username parameter or the password parameter is null. + /// The hostname parameter or the username + /// parameter is the empty string. + /// The value of the port parameter + /// is not a valid port number. + /// Use this constructor if you wish to connect to an XMPP server using + /// an existing set of user credentials. + public XmppClient(string hostname, string username, string password, string server, + int port = 5222, bool tls = true, RemoteCertificateValidationCallback validate = null) + { + im = new XmppIm(hostname, username, password, server, port, tls, validate); + // Initialize the various extension modules. + LoadExtensions(); + } /// /// Initializes a new instance of the XmppClient class. @@ -723,11 +749,34 @@ public XmppClient(string hostname, string username, string password, /// Use this constructor if you wish to register an XMPP account using /// the in-band account registration process supported by some servers. public XmppClient(string hostname, int port = 5222, bool tls = true, - RemoteCertificateValidationCallback validate = null) - { - im = new XmppIm(hostname, port, tls, validate); - LoadExtensions(); - } + RemoteCertificateValidationCallback validate = null) : + this(hostname, null, port, tls, validate) { } + + /// + /// Initializes a new instance of the XmppClient class. + /// + /// The hostname of the XMPP server to connect to. + /// The IP address or domain of the XMPP server, if different from the hostname eg. xmpp.server.com + /// The port number of the XMPP service of the server. + /// If true the session will be TLS/SSL-encrypted if the server + /// supports TLS/SSL-encryption. + /// A delegate used for verifying the remote Secure Sockets + /// Layer (SSL) certificate which is used for authentication. Can be null if not + /// needed. + /// The hostname parameter is + /// null. + /// The hostname parameter is the empty + /// string. + /// The value of the port parameter + /// is not a valid port number. + /// Use this constructor if you wish to register an XMPP account using + /// the in-band account registration process supported by some servers. + public XmppClient(string hostname, string server, int port = 5222, bool tls = true, + RemoteCertificateValidationCallback validate = null) + { + im = new XmppIm(hostname, server, port, tls, validate); + LoadExtensions(); + } /// /// Establishes a connection to the XMPP server. diff --git a/Core/XmppCore.cs b/Core/XmppCore.cs index 6ebd67ad..aa00c0c8 100644 --- a/Core/XmppCore.cs +++ b/Core/XmppCore.cs @@ -72,6 +72,11 @@ public class XmppCore : IDisposable /// private string hostname; + /// + /// The server IP or domain name of the XMPP server to connect to. + /// + private string server; + /// /// The username with which to authenticate. /// @@ -157,6 +162,22 @@ public string Hostname } } + /// + /// The server IP address or domain name of the XMPP server, if different from the Hostname. + /// + public string Server + { + get + { + return server; + } + + set + { + server = value; + } + } + /// /// The port number of the XMPP service of the server. /// @@ -339,24 +360,57 @@ public bool Authenticated /// The value of the port parameter /// is not a valid port number. public XmppCore(string hostname, string username, string password, - int port = 5222, bool tls = true, RemoteCertificateValidationCallback validate = null) - { - moveNextSrvDNS(hostname); - if (dnsCurrent != null) - { - Hostname = dnsCurrent.Target.ToString(); - Port = dnsCurrent.Port; - } - else - { - Hostname = hostname; - Port = port; - } - Username = username; - Password = password; - Tls = tls; - Validate = validate; - } + int port = 5222, bool tls = true, RemoteCertificateValidationCallback validate = null): + this(hostname, username, password, null, port, tls, validate) { } + + /// + /// Initializes a new instance of the XmppCore class. + /// + /// The hostname of the XMPP server to connect to. + /// The username with which to authenticate. In XMPP jargon + /// this is known as the 'node' part of the JID. + /// The password with which to authenticate. + /// The IP address or domain of the XMPP server, if different from the hostname eg. xmpp.server.com + /// The port number of the XMPP service of the server. + /// If true the session will be TLS/SSL-encrypted if the server + /// supports TLS/SSL-encryption. + /// A delegate used for verifying the remote Secure Sockets + /// Layer (SSL) certificate which is used for authentication. Can be null if not + /// needed. + /// The hostname parameter or the + /// username parameter or the password parameter is null. + /// The hostname parameter or the username + /// parameter is the empty string. + /// The value of the port parameter + /// is not a valid port number. + public XmppCore(string hostname, string username, string password, string server, + int port = 5222, bool tls = true, RemoteCertificateValidationCallback validate = null) + { + if (String.IsNullOrWhiteSpace(server)) { + moveNextSrvDNS(hostname); + if (dnsCurrent != null) + { + Hostname = dnsCurrent.Target.ToString(); + Port = dnsCurrent.Port; + } + else + { + Hostname = hostname; + Server = hostname; + Port = port; + } + } + else { + Server = server; + Hostname = hostname; + Port = port; + } + + Username = username; + Password = password; + Tls = tls; + Validate = validate; + } /// /// Initializes a new instance of the XmppCore class. @@ -375,22 +429,53 @@ public XmppCore(string hostname, string username, string password, /// The value of the port parameter /// is not a valid port number. public XmppCore(string hostname, int port = 5222, bool tls = true, - RemoteCertificateValidationCallback validate = null) - { - moveNextSrvDNS(hostname); - if (dnsCurrent != null) - { - Hostname = dnsCurrent.Target.ToString(); - Port = dnsCurrent.Port; - } - else - { - Hostname = hostname; - Port = port; - } - Tls = tls; - Validate = validate; - } + RemoteCertificateValidationCallback validate = null): + this(hostname, null, port, tls, validate) { } + + /// + /// Initializes a new instance of the XmppCore class. + /// + /// The hostname of the XMPP server to connect to. + /// The IP address or domain of the XMPP server, if different from the hostname eg. xmpp.server.com + /// The port number of the XMPP service of the server. + /// If true the session will be TLS/SSL-encrypted if the server + /// supports TLS/SSL-encryption. + /// A delegate used for verifying the remote Secure Sockets + /// Layer (SSL) certificate which is used for authentication. Can be null if not + /// needed. + /// The hostname parameter is + /// null. + /// The hostname parameter is the empty + /// string. + /// The value of the port parameter + /// is not a valid port number. + public XmppCore(string hostname, string server, int port = 5222, bool tls = true, + RemoteCertificateValidationCallback validate = null) + { + if (String.IsNullOrWhiteSpace(server)) + { + moveNextSrvDNS(hostname); + if (dnsCurrent != null) + { + Hostname = dnsCurrent.Target.ToString(); + Port = dnsCurrent.Port; + } + else + { + Hostname = hostname; + Server = hostname; + Port = port; + } + } + else { + Server = server; + Hostname = hostname; + Port = port; + } + + Tls = tls; + Validate = validate; + } /// /// Initialises and resolves the DNS Domain, and set to dnsCurrent the next @@ -472,7 +557,7 @@ public void Connect(string resource = null) this.resource = resource; try { - client = new TcpClient(Hostname, Port); + client = new TcpClient(Server, Port); stream = client.GetStream(); // Sets up the connection which includes TLS and possibly SASL negotiation. SetupConnection(this.resource); diff --git a/Im/XmppIm.cs b/Im/XmppIm.cs index 388226b1..56513755 100644 --- a/Im/XmppIm.cs +++ b/Im/XmppIm.cs @@ -299,11 +299,36 @@ public CustomIqRequestDelegate CustomIqDelegate /// The value of the port parameter /// is not a valid port number. public XmppIm(string hostname, string username, string password, - int port = 5222, bool tls = true, RemoteCertificateValidationCallback validate = null) - { - core = new XmppCore(hostname, username, password, port, tls, validate); - SetupEventHandlers(); - } + int port = 5222, bool tls = true, RemoteCertificateValidationCallback validate = null) : + this (hostname, username, password, null, port, tls, validate) + { } + + /// + /// Initializes a new instance of the XmppIm. + /// + /// The hostname of the XMPP server to connect to. + /// The username with which to authenticate. In XMPP jargon + /// this is known as the 'node' part of the JID. + /// The password with which to authenticate. + /// The IP address or domain of the XMPP server, if different from the hostname eg. xmpp.server.com + /// The port number of the XMPP service of the server. + /// If true the session will be TLS/SSL-encrypted if the server + /// supports TLS/SSL-encryption. + /// A delegate used for verifying the remote Secure Sockets + /// Layer (SSL) certificate which is used for authentication. Can be null if not + /// needed. + /// The hostname parameter or the + /// username parameter or the password parameter is null. + /// The hostname parameter or the username + /// parameter is the empty string. + /// The value of the port parameter + /// is not a valid port number. + public XmppIm(string hostname, string username, string password, string server, + int port = 5222, bool tls = true, RemoteCertificateValidationCallback validate = null) + { + core = new XmppCore(hostname, username, password, server, port, tls, validate); + SetupEventHandlers(); + } /// /// Initializes a new instance of the XmppIm. @@ -322,12 +347,37 @@ public XmppIm(string hostname, string username, string password, /// The value of the port parameter /// is not a valid port number. public XmppIm(string hostname, int port = 5222, bool tls = true, - RemoteCertificateValidationCallback validate = null) + RemoteCertificateValidationCallback validate = null) : + this (hostname, null, port , tls, validate) { core = new XmppCore(hostname, port, tls, validate); SetupEventHandlers(); } + /// + /// Initializes a new instance of the XmppIm. + /// + /// The hostname of the XMPP server to connect to. + /// The IP address or domain of the XMPP server, if different from the hostname eg. xmpp.server.com + /// The port number of the XMPP service of the server. + /// If true the session will be TLS/SSL-encrypted if the server + /// supports TLS/SSL-encryption. + /// A delegate used for verifying the remote Secure Sockets + /// Layer (SSL) certificate which is used for authentication. Can be null if not + /// needed. + /// The hostname parameter is + /// null. + /// The hostname parameter is the empty + /// string. + /// The value of the port parameter + /// is not a valid port number. + public XmppIm(string hostname, string server, int port = 5222, bool tls = true, + RemoteCertificateValidationCallback validate = null) + { + core = new XmppCore(hostname, server, port, tls, validate); + SetupEventHandlers(); + } + /// /// Establishes a connection to the XMPP server. /// diff --git a/Sharp.Xmpp.csproj b/Sharp.Xmpp.csproj index 30a27614..e1f5ac51 100644 --- a/Sharp.Xmpp.csproj +++ b/Sharp.Xmpp.csproj @@ -52,10 +52,6 @@ MinimumRecommendedRules.ruleset - - ..\..\..\..\..\Desktop\SharpXmppWpf\SharpXmppWpf\packages\ARSoft.Tools.Net.2.2.5\lib\net45\ARSoft.Tools.Net.dll - True - ..\..\..\..\..\Desktop\SharpXmppWpf\SharpXmppWpf\packages\BouncyCastle.1.8.1\lib\BouncyCastle.Crypto.dll True @@ -67,6 +63,9 @@ + + packages\ARSoft.Tools.Net.2.2.6\lib\net45\ARSoft.Tools.Net.dll + diff --git a/packages.config b/packages.config index b446d709..8599ab23 100644 --- a/packages.config +++ b/packages.config @@ -1,5 +1,5 @@  - + \ No newline at end of file From a62509546d9200efbdb4b7c7e4a39126e08b3bde Mon Sep 17 00:00:00 2001 From: Steven Livingstone-Perez Date: Fri, 21 Apr 2017 21:42:38 +0100 Subject: [PATCH 02/22] Added some support for custom namespaces as sibling to the body. --- Im/Message.cs | 40 ++++++++++++++++++++++++++++++++++++++++ Sharp.Xmpp.csproj | 4 ---- Sharp.Xmpp.userprefs | 36 ++++++++++++++++++++++++++++++++++++ Xml.cs | 19 +++++++++++++++++-- 4 files changed, 93 insertions(+), 6 deletions(-) create mode 100644 Sharp.Xmpp.userprefs diff --git a/Im/Message.cs b/Im/Message.cs index b4ea7232..018b6d8d 100644 --- a/Im/Message.cs +++ b/Im/Message.cs @@ -235,6 +235,46 @@ public Message(Jid to, IDictionary bodies, Thread = thread; } + /// + /// Initializes a new instance of the Message class. + /// + /// The JID of the intended recipient. + /// The content of the message. + /// The tag name the others items below will be wrapped in - they will have the appropriate namespace assigned. + /// A dictionary of additional message elements after body. The dictionary + /// keys denote the namespace of the message bodies. The element name should be the same for all items but you can have multiple - + /// each new element will have the namespace associated with it. + /// A dictionary of message subjects. The dictionary + /// keys denote the languages of the message subjects and must be valid + /// ISO 2 letter language codes. + /// The conversation thread this message belongs to. + /// The type of the message. Can be one of the values from + /// the MessagType enumeration. + /// The language of the XML character data of + /// the stanza. + /// The to parametr or the bodies + /// parameter is null. + public Message(Jid to, String body, string othertagname, IDictionary others, + IDictionary subjects = null, string thread = null, + MessageType type = MessageType.Normal, CultureInfo language = null) + : base(to, null, null, null, language) + { + to.ThrowIfNull("to"); + others.ThrowIfNull("bodies"); + AlternateSubjects = new XmlDictionary(element, "subject", "xml:lang"); + Body = body; + AlternateBodies = new XmlDictionary(element, othertagname, "xmlns"); + Type = type; + foreach (var pair in others) + AlternateBodies.Add(pair.Key, pair.Value); + if (subjects != null) + { + foreach (var pair in subjects) + AlternateSubjects.Add(pair.Key, pair.Value); + } + Thread = thread; + } + /// /// Initializes a new instance of the Message class from the specified /// instance. diff --git a/Sharp.Xmpp.csproj b/Sharp.Xmpp.csproj index e1f5ac51..8477546a 100644 --- a/Sharp.Xmpp.csproj +++ b/Sharp.Xmpp.csproj @@ -52,10 +52,6 @@ MinimumRecommendedRules.ruleset - - ..\..\..\..\..\Desktop\SharpXmppWpf\SharpXmppWpf\packages\BouncyCastle.1.8.1\lib\BouncyCastle.Crypto.dll - True - diff --git a/Sharp.Xmpp.userprefs b/Sharp.Xmpp.userprefs new file mode 100644 index 00000000..dce5e58f --- /dev/null +++ b/Sharp.Xmpp.userprefs @@ -0,0 +1,36 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + \ No newline at end of file diff --git a/Xml.cs b/Xml.cs index 8b8223c1..c4f81564 100644 --- a/Xml.cs +++ b/Xml.cs @@ -81,16 +81,31 @@ public static XmlElement Text(this XmlElement e, string text) public static string ToXmlString(this XmlElement e, bool xmlDeclaration = false, bool leaveOpen = false) { + // avoid duplicate namespaces on elements by storing each in the list + System.Collections.Generic.IList ns = new System.Collections.Generic.List(); + // Can't use e.OuterXml because it "messes up" namespaces for elements with // a prefix, i.e. stream:stream (What it does is probably correct, but just // not what we need for XMPP). StringBuilder b = new StringBuilder("<" + e.Name); - if (!String.IsNullOrEmpty(e.NamespaceURI)) + if (!String.IsNullOrEmpty(e.NamespaceURI))) b.Append(" xmlns='" + e.NamespaceURI + "'"); foreach (XmlAttribute a in e.Attributes) { + // 21 April 2017 - Removed skipping the namespaces so we can add custom namespaces. + //if (a.Name == "xmlns") + // continue; if (a.Name == "xmlns") - continue; + { + // If the namespace above was added to it, we don't want to add a duplicate. Causes of building Xml through strings. + if (!String.IsNullOrEmpty(e.NamespaceURI)) continue; + + if (ns.Contains(a.Value)) + continue; + else + ns.Add(a.Value); + } + if (a.Value != null) b.Append(" " + a.Name + "='" + SecurityElement.Escape(a.Value.ToString()) + "'"); From 734f432831d0fc038f89df618921843ffcc4e32b Mon Sep 17 00:00:00 2001 From: Steven Livingstone-Perez Date: Sat, 6 May 2017 17:54:16 +0100 Subject: [PATCH 03/22] Added support for stream management, stream resumption and stream resolution when connections are dropped. --- Core/XmppCore.cs | 309 ++++++++++++++++++++++++++++++++++++++++++- Sharp.Xmpp.csproj | 3 + Sharp.Xmpp.sln | 6 + Sharp.Xmpp.userprefs | 36 ++--- Xml.cs | 2 +- 5 files changed, 323 insertions(+), 33 deletions(-) diff --git a/Core/XmppCore.cs b/Core/XmppCore.cs index aa00c0c8..46cb6968 100644 --- a/Core/XmppCore.cs +++ b/Core/XmppCore.cs @@ -550,7 +550,7 @@ private SrvRecord moveNextSrvDNS(string domain) /// disposed. /// If a username has been supplied, this method automatically performs /// authentication. - public void Connect(string resource = null) + public void Connect(string resource = null, bool bind = true) { if (disposed) throw new ObjectDisposedException(GetType().FullName); @@ -560,7 +560,7 @@ public void Connect(string resource = null) client = new TcpClient(Server, Port); stream = client.GetStream(); // Sets up the connection which includes TLS and possibly SASL negotiation. - SetupConnection(this.resource); + SetupConnection(this.resource, bind); // We are connected. Connected = true; // Set up the listener and dispatcher tasks. @@ -989,6 +989,7 @@ private void AssertValid() /// /// The resource identifier to bind with. If this is null, /// it is assigned by the server. + /// Do we bind - this is false in Stream Resumption but usually true. /// The resource binding process failed. /// Invalid or unexpected XML data has been /// received from the XMPP server. @@ -996,7 +997,7 @@ private void AssertValid() /// trying to establish a secure connection, or the provided credentials were /// rejected by the server, or the server requires TLS/SSL and TLS has been /// turned off. - private void SetupConnection(string resource = null) + private void SetupConnection(string resource = null, bool bind = true) { // Request the initial stream. XmlElement feats = InitiateStream(Hostname); @@ -1029,7 +1030,7 @@ private void SetupConnection(string resource = null) feats = Authenticate(list, Username, Password, Hostname); // FIXME: How is the client's JID constructed if the server does not support // resource binding? - if (feats["bind"] != null) + if (bind && feats["bind"] != null) Jid = BindResource(resource); } catch (SaslException e) @@ -1308,7 +1309,7 @@ private void ReadXmlStream() { while (true) { - XmlElement elem = parser.NextElement("iq", "message", "presence"); + XmlElement elem = parser.NextElement("iq", "message", "presence", "enabled", "resumed"); // Parse element and dispatch. switch (elem.Name) { @@ -1327,6 +1328,16 @@ private void ReadXmlStream() case "presence": stanzaQueue.Add(new Presence(elem)); break; + + // xep 1098 ### + + case "enabled": + HandleStreamManagementEnabledResponse(elem); + break; + + case "resumed": + HandleResumedStreamResponse(elem); + break; } } } @@ -1351,6 +1362,294 @@ private void ReadXmlStream() } } + #region xep-0198 ### + + /// + /// The event that is raised when stream management is enabled. + /// + public event EventHandler StreamManagementEnabled; + + /// + /// The event that is raised when a stream is resumed. + /// + public event EventHandler StreamResumed; + + /// + /// Is stream management enabled. + /// + private bool streamManagementEnabled = false; + + /// + /// Is stream management resumption enabled. + /// + private bool resumptionEnabled = false; + + /// + /// The resumption id that can be used if the stream drops. + /// + private string resumptionId = null; + + /// + /// The maximum time between a connection being dropped and being allowed to reconnect the stream. + /// The server can choose to override what is set here. + /// + private int maxResumptionPeriodInSeconds = 30; + + /// + /// The maximum number of times we can try to resume a broken connection + /// + private const int MAX_RESUMPTION_ATTEMPTS = 3; + + /// + /// The maximum number of times we can try to create a broken stream + /// + private const int MAX_STREAM_ATTEMPTS = 3; + + /// + /// The current resumption attempt downward counter + /// + private int currentResumptionAttempt = 0; + + /// + /// The current stream attempt downward counter + /// + private int currentStreamAttempt = 0; + + /// + /// The last time any kind of confirmation was asked of the server. + /// Acknowledgements, Resumption and so on. DO NOT KNOW IF I NEED THIS?? + /// + private DateTime lastConfirmationAttemptServerTime = DateTime.MinValue; + + /// + /// The last sequence number we have that was confirmed by the server + /// + private int lastConfirmedServerSequence = 0; + + /// + /// When the server confirmed the above sequence number. + /// + private DateTime lastConfirmedServerTime = DateTime.MinValue; + + /// + /// The maximum time without any kind of confirmation from the server. + /// + private int maxTimeBetweenConfirmationsInSeconds = 13; + + /// + /// The namespace for stream management. + /// + const string STREAM_MANAGEMENT_NS = "urn:xmpp:sm:3"; + + /// + /// Enables stream management. You should listen for the StreamManagementEnabled event + /// to know when it is ready. + /// Whether we should enabled resumption on the stream. + /// The max timeout client request - the server can override this. + /// + public void EnableStreamManagement(bool withresumption = true, int maxTimeout = 60) + { + // Send + XmlElement sm = Xml.Element("enable", STREAM_MANAGEMENT_NS); + sm.SetAttribute("resume", withresumption.ToString().ToLower()); + sm.SetAttribute("max", maxTimeout.ToString()); + + // send to the server - a message will be sent back later + Send(sm); + } + + /// + /// The callback when stream management is enabled. + /// + /// Enabled. + private void HandleStreamManagementEnabledResponse(XmlElement enabled) + { + // reset other variables - usually these are set when there was a previous stream + isAttemptingNewStream = false; + lastAttemptAtNewStreamTime = null; + isAttemptingStreamResumption = false; + lastAttemptAtStreamResumptionTime = null; + lastConfirmedServerTime = DateTime.Now; + currentResumptionAttempt = 0; //reset for next time + currentStreamAttempt = 0; + + // we have stream management enabled so lets get started + streamManagementEnabled = true; + resumptionEnabled = Boolean.Parse(enabled.GetAttribute("resume")); + resumptionId = enabled.GetAttribute("id"); + int.TryParse(enabled.GetAttribute("max"), out maxResumptionPeriodInSeconds); + + // manage the stream uptime + CheckStreamCycle(); + + // throw an event to say we're ready with resumption + StreamManagementEnabled.Raise(this, null); + } + + /// + /// A global so we know if we are in the process of trying to resume the stream + /// + private bool isAttemptingStreamResumption = false; + + /// + /// A record of when the last attempt to resume the stream started - must reset it on success. + /// + private DateTime? lastAttemptAtStreamResumptionTime = null; + + /// + /// The max time we will wait before we regard resumption is failed and lost. + /// + private int maxStreamResumptionTimeoutInSecond = 30; + + /// + /// A global so we know if we are in the process of trying to create a new stream + /// + private bool isAttemptingNewStream = false; + + /// + /// A record of when the last attempt to create a new stream - must reset it on success. + /// + private DateTime? lastAttemptAtNewStreamTime = null; + + /// + /// The max time we will wait before we regard creating a new stream has failed. + /// + private int maxNewStreamTimeoutInSecond = 30; + + /// + /// This will periodically check whether the server connection is up and + /// if not it will kick of a process to try and resume it, or create a new stream. + /// + private void CheckStreamCycle() + { + int streamCycleCheckTimeInSeconds = 10; + System.Timers.Timer timer = new System.Timers.Timer(); + timer.Interval = streamCycleCheckTimeInSeconds * 1000; + timer.Enabled = true; + + // inside here we manage the stream uptime - AT THE MOMENT we assume one attempt at stream resumption and then one attempt at connecting + timer.Elapsed += (sender, e) => { + + // if we are in the process of trying to create a new stream and that has been going on too long throw an error (for now) + if (isAttemptingNewStream + && currentStreamAttempt > MAX_STREAM_ATTEMPTS + && lastAttemptAtNewStreamTime.HasValue + && DateTime.Now > lastAttemptAtNewStreamTime.Value.AddSeconds(maxNewStreamTimeoutInSecond)) + { + var connex = new XmppDisconnectionException("Unable to create a new connection in the time period."); + Error.Raise(this, new ErrorEventArgs(connex)); + } + else if (isAttemptingNewStream && currentStreamAttempt > MAX_STREAM_ATTEMPTS) // we are in the process of creating so let it run + return; + + // if we are in the process of trying to resume and that has been going on too long, consider it failed and restart the stream + if (isAttemptingStreamResumption + && currentResumptionAttempt > MAX_RESUMPTION_ATTEMPTS + && lastAttemptAtStreamResumptionTime.HasValue + && DateTime.Now > lastAttemptAtStreamResumptionTime.Value.AddSeconds(maxStreamResumptionTimeoutInSecond)) + { + // full stream restart - we cannot use resumption in this case as it is a brand new stream + isAttemptingNewStream = true; + lastAttemptAtNewStreamTime = DateTime.Now; + + try + { + // we will try getting the connection back again + currentStreamAttempt++; + + // try to create a new connection + Connect(this.resource); + + // finally, we enable stream management if it is on - IF WE DO THIS HERE + // ARE THERE ANY RACE CONDITIONS BY RESETING THE VARIBLES ABOVE BEFORE THE RESPONSE ? + if (streamManagementEnabled) + { + // we will reset the variables below when we get a stream management response + EnableStreamManagement(resumptionEnabled, maxResumptionPeriodInSeconds); + + } else { + + // if successful then reset + isAttemptingNewStream = false; + lastAttemptAtNewStreamTime = null; + isAttemptingStreamResumption = false; + lastAttemptAtStreamResumptionTime = null; + lastConfirmedServerTime = DateTime.Now; + currentResumptionAttempt = 0; //reset for next time + currentStreamAttempt = 0; + } + + return; + } + catch { + + // a network error of some kind - timer will ensure a rerty is done shortly. + return; + } + } + else if (isAttemptingStreamResumption + && currentResumptionAttempt > MAX_RESUMPTION_ATTEMPTS) // we are in the process of resuming so let that run + return; + + // Normal path here - if we have had no response from the server at all in a given period despite an attempt we will try to resume the stream + if (DateTime.Now > lastConfirmedServerTime.AddSeconds(maxTimeBetweenConfirmationsInSeconds)) + { + // we will try getting the connection back again + currentResumptionAttempt++; + + // try to resume the connection + ResumeStream(); + } + }; + + timer.Start(); + } + + /// + /// This will try to resume a stream, often caused by a dropped connection. + /// + private void ResumeStream() + { + // don't run multiple of these + if (isAttemptingStreamResumption) return; + + // set these management vars + isAttemptingStreamResumption = true; + lastAttemptAtStreamResumptionTime = DateTime.Now; + + // recreate the connection without binding + Connect(this.resource, false); + + // Send + XmlElement rs = Xml.Element("resume", STREAM_MANAGEMENT_NS); + rs.SetAttribute("h", lastConfirmedServerSequence.ToString()); + rs.SetAttribute("previd", resumptionId); + + // send to the server - a message will be sent back later + Send(rs); + } + + /// + /// The callback when stream is resumed. + /// + /// Resumed xml element. + private void HandleResumedStreamResponse(XmlElement resumed) + { + // reset as we are now no longer trying to resume the stream + isAttemptingStreamResumption = false; + lastAttemptAtStreamResumptionTime = null; + currentResumptionAttempt = 0; //reset for next time + + // what is the last item the server is aware of and record at what point that is + lastConfirmedServerSequence = int.Parse(resumed.GetAttribute("h")); + lastConfirmedServerTime = DateTime.Now; + + // throw an event to say we're ready with resumption + StreamResumed.Raise(this, null); + } + + #endregion + /// /// Continously removes stanzas from the FIFO of incoming stanzas and raises /// the respective events. diff --git a/Sharp.Xmpp.csproj b/Sharp.Xmpp.csproj index 8477546a..c900b06e 100644 --- a/Sharp.Xmpp.csproj +++ b/Sharp.Xmpp.csproj @@ -255,6 +255,9 @@ + + + - \ No newline at end of file + diff --git a/XMPPEngineer.sln b/XMPPEngineer.sln new file mode 100644 index 00000000..c92547e7 --- /dev/null +++ b/XMPPEngineer.sln @@ -0,0 +1,23 @@ + +Microsoft Visual Studio Solution File, Format Version 12.00 +# Visual Studio 2012 +Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "XMPPEngineer", "XMPPEngineer.csproj", "{C462F45F-CC89-4E71-971D-79E4495B6C2B}" +EndProject +Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "XMPPEngineerTest", "XMPPEngineerTest\XMPPEngineerTest.csproj", "{5FB57B73-A453-4489-8DBA-ED489832D81E}" +EndProject +Global + GlobalSection(SolutionConfigurationPlatforms) = preSolution + Debug|Any CPU = Debug|Any CPU + Release|Any CPU = Release|Any CPU + EndGlobalSection + GlobalSection(ProjectConfigurationPlatforms) = postSolution + {C462F45F-CC89-4E71-971D-79E4495B6C2B}.Debug|Any CPU.ActiveCfg = Debug|Any CPU + {C462F45F-CC89-4E71-971D-79E4495B6C2B}.Debug|Any CPU.Build.0 = Debug|Any CPU + {C462F45F-CC89-4E71-971D-79E4495B6C2B}.Release|Any CPU.ActiveCfg = Release|Any CPU + {C462F45F-CC89-4E71-971D-79E4495B6C2B}.Release|Any CPU.Build.0 = Release|Any CPU + {5FB57B73-A453-4489-8DBA-ED489832D81E}.Debug|Any CPU.ActiveCfg = Debug|Any CPU + {5FB57B73-A453-4489-8DBA-ED489832D81E}.Debug|Any CPU.Build.0 = Debug|Any CPU + {5FB57B73-A453-4489-8DBA-ED489832D81E}.Release|Any CPU.ActiveCfg = Release|Any CPU + {5FB57B73-A453-4489-8DBA-ED489832D81E}.Release|Any CPU.Build.0 = Release|Any CPU + EndGlobalSection +EndGlobal diff --git a/XMPPEngineer.userprefs b/XMPPEngineer.userprefs new file mode 100644 index 00000000..94c4d9df --- /dev/null +++ b/XMPPEngineer.userprefs @@ -0,0 +1,10 @@ + + + + + + + + + + \ No newline at end of file diff --git a/XMPPEngineerTest/Program (copy).cs b/XMPPEngineerTest/Program (copy).cs new file mode 100644 index 00000000..82013842 --- /dev/null +++ b/XMPPEngineerTest/Program (copy).cs @@ -0,0 +1,111 @@ +//using System; + +//namespace XMPPEngineerTest +//{ +// class MainClass +// { +// public static void Main(string[] args) +// { +// /* +// XMPPEngineer.Core.XmppCore client = new XMPPEngineer.Core.XmppCore( +// "alchemy.local", +// "test", +// "eyJ0eXAiOiJKV1QiLCJhbGciOiJIUzI1NiJ9.eyJpc3MiOiJPbmxpbmUgSldUIEJ1aWxkZXIiLCJpYXQiOjE0OTI2Mzc1NzMsImV4cCI6MTUyNDE3MzU3MywiYXVkIjoid3d3LmV4YW1wbGUuY29tIiwic3ViIjoianJvY2tldEBleGFtcGxlLmNvbSIsInVzZXJuYW1lIjoidGVzdCIsIm5ldHdvcmsiOiJhbGNoZW15LmxvY2FsIn0.0IYkS8KH22kai_j6hRcjy5x7Lvwb6TG5XyRY8n9fym0", +// "dev-lite-citym-access.westeurope.cloudapp.azure.com" +// );*/ + +// XMPPEngineer.Client.XmppClient client = new XMPPEngineer.Client.XmppClient( +// "alchemy.local", +// "steven", +// "test", +// "dev-lite-citym-access.westeurope.cloudapp.azure.com" +// ); + +// client.Connect("mobile"); + +// client.Error += (sender, e) => { +// Console.WriteLine(e.ToString()); +// }; +// client.Message += (sender, e) => { +// Console.WriteLine(e.ToString()); +// }; +// //client.Iq += (sender, e) => { +// // Console.WriteLine(e.ToString()); +// //}; +// //client.Presence += (sender, e) => +// //{ +// // Console.WriteLine(e.ToString()); +// //}; +// ///client.A += (sender, e) => +// ///{ +// ///Console.WriteLine(e.ToString()); +// ///}; + +// //RunBasic(client); +// //RunStreamManagement(client); +// RunStreamManagement2(client); + +// Console.ReadKey(); +// } + +// private static void RunBasic(XMPPEngineer.Core.XmppCore client) +// { +// XMPPEngineer.Im.Message message = new XMPPEngineer.Im.Message(new XMPPEngineer.Jid("admin@alchemy.local"), "ok - " + DateTime.Now.ToLongTimeString()); +// client.SendMessage(message); +// } + +// private static void RunStreamManagement(XMPPEngineer.Core.XmppCore client) +// { +// client.StreamManagementEnabled += (sdr, evt) => +// { +// //XMPPEngineer.Im.Message message = new XMPPEngineer.Im.Message(new XMPPEngineer.Jid("admin@alchemy.local"), "ok - " + DateTime.Now.ToLongTimeString()); +// //client.SendMessage(message); + +// // send a message in a loop + +// System.Timers.Timer timer = new System.Timers.Timer(10000); +// timer.Elapsed += (sender, e) => { +// XMPPEngineer.Im.Message messagex = new XMPPEngineer.Im.Message(new XMPPEngineer.Jid("admin@alchemy.local"), "ok - " + DateTime.Now.ToLongTimeString()); +// client.SendMessage(messagex); +// }; +// timer.Enabled = true; +// timer.Start(); + +// // // Resumes a stream that was disconnected for some reason - this will only work within the time window allows by the server to stream resumption - which defaults to 20 seconds +// // // We only need to do this if we detect we have lost a connection to the server - probably from no acks. +// // ///client.StreamManagementResumed += (sender, e) => { +// // // continue in here +// // ///}; +// // //client.ResumeStream(); + +// // ///XMPPEngineer.Im.Message message = new XMPPEngineer.Im.Message(new XMPPEngineer.Jid("admin@alchemy.local"), "ok - " + DateTime.Now.ToLongTimeString()); +// // ///client.SendMessage(message); +// }; + +// // enable stream management and recovery mode +// ///client.NumberOfItemsBeforeServerAck = 3; +// ///client.NumberOfSecondsBeforeServerAck = 15;//30; +// client.EnableStreamManagement(true); +// } + +// private static void RunStreamManagement2(XMPPEngineer.Client.XmppClient client) +// { +// client.StreamManagementEnabled += (sdr, evt) => +// { +// // send a message in a loop + +// System.Timers.Timer timer = new System.Timers.Timer(10000); +// timer.Elapsed += (sender, e) => +// { +// XMPPEngineer.Im.Message messagex = new XMPPEngineer.Im.Message(new XMPPEngineer.Jid("admin@alchemy.local"), "ok - " + DateTime.Now.ToLongTimeString()); +// client.SendMessage(messagex); +// }; +// timer.Enabled = true; +// timer.Start(); +// }; + +// // enable stream management and recovery mode +// client.EnableStreamManagement(true); +// } +// } +//} diff --git a/XMPPEngineerTest/Program.cs b/XMPPEngineerTest/Program.cs new file mode 100644 index 00000000..c9846eae --- /dev/null +++ b/XMPPEngineerTest/Program.cs @@ -0,0 +1,74 @@ +using System; +using XMPPEngineer; +using XMPPEngineer.Client; +using XMPPEngineer.Im; + +namespace XMPPEngineerTest +{ + class MainClass + { + public static void Main(string[] args) + { + // basic + /* + using (XmppClient client = new XmppClient("domain", "user", "password")) + { + client.Connect(); + + Message message = new Message(new Jid("user@domain"), "Hello, World."); + client.SendMessage(message); + }*/ + + // with stream management + using (XmppClient clientsm = new XmppClient( + "alchemy.local", + "steven", + "test", + "dev-lite-citym-access.westeurope.cloudapp.azure.com", + XmppClient.AvailableExtensions.Default | XmppClient.AvailableExtensions.Ping)) + //XmppClient.AvailableExtensions.DataForms + //XmppClient.AvailableExtensions.MessageCarbons + /*using (XMPPEngineer.Core.XmppCore clientsm = new XMPPEngineer.Core.XmppCore( + "alchemy.local", + "steven", + "test", + "dev-lite-citym-access.westeurope.cloudapp.azure.com"))*/ + { + clientsm.Error += (sender, e) => + { + Console.WriteLine(e.ToString()); + }; + clientsm.Message += (sender, e) => + { + Console.WriteLine(e.Message); + }; + + clientsm.RetrieveRoster = false; + clientsm.Connect("mobile"); + + clientsm.StreamManagementEnabled += (sdr, evt) => + { + // normal send + Message messagesm = new Message(new Jid("admin@alchemy.local"), "ok - " + DateTime.Now.ToLongTimeString()); + clientsm.SendMessage(messagesm); + + // xep-0033 - multicast can send to a jid that can then route to multiple users + /* + System.Collections.Generic.List jids = new System.Collections.Generic.List(); + jids.Add(new Jid("admin@alchemy.local")); + jids.Add(new Jid("test@alchemy.local")); + jids.Add(new Jid("steven@alchemy.local")); + + Message multimessagesm = new Message(new Jid("multicast.alchemy.local"), "ok5 - " + DateTime.Now.ToLongTimeString(), null, jids); + clientsm.SendMessage(multimessagesm); + */ + }; + + // enable stream management and recovery mode - // xep-0198 + clientsm.EnableStreamManagement(true); + + Console.ReadKey(); + } + } + } +} diff --git a/XMPPEngineerTest/Properties/AssemblyInfo.cs b/XMPPEngineerTest/Properties/AssemblyInfo.cs new file mode 100644 index 00000000..416f7714 --- /dev/null +++ b/XMPPEngineerTest/Properties/AssemblyInfo.cs @@ -0,0 +1,26 @@ +using System.Reflection; +using System.Runtime.CompilerServices; + +// Information about this assembly is defined by the following attributes. +// Change them to the values specific to your project. + +[assembly: AssemblyTitle("XMPPEngineerTest")] +[assembly: AssemblyDescription("")] +[assembly: AssemblyConfiguration("")] +[assembly: AssemblyCompany("")] +[assembly: AssemblyProduct("")] +[assembly: AssemblyCopyright("${AuthorCopyright}")] +[assembly: AssemblyTrademark("")] +[assembly: AssemblyCulture("")] + +// The assembly version has the format "{Major}.{Minor}.{Build}.{Revision}". +// The form "{Major}.{Minor}.*" will automatically update the build and revision, +// and "{Major}.{Minor}.{Build}.*" will update just the revision. + +[assembly: AssemblyVersion("1.0.*")] + +// The following attributes are used to specify the signing key for the assembly, +// if desired. See the Mono documentation for more information about signing. + +//[assembly: AssemblyDelaySign(false)] +//[assembly: AssemblyKeyFile("")] diff --git a/XMPPEngineerTest/XMPPEngineerTest.csproj b/XMPPEngineerTest/XMPPEngineerTest.csproj new file mode 100644 index 00000000..76b3991e --- /dev/null +++ b/XMPPEngineerTest/XMPPEngineerTest.csproj @@ -0,0 +1,11 @@ + + + + Exe + netcoreapp2.0 + + + + + + diff --git a/packages.config b/packages.config deleted file mode 100644 index 8599ab23..00000000 --- a/packages.config +++ /dev/null @@ -1,5 +0,0 @@ - - - - - \ No newline at end of file From 1a2a2f1c0cf2cd99e98e911a4c8b68aef7702472 Mon Sep 17 00:00:00 2001 From: Steven Livingstone-Perez Date: Thu, 8 Feb 2018 16:13:50 +0000 Subject: [PATCH 18/22] Upgraded to .NET Core 2 --- Properties/AssemblyInfo.cs | 26 --------------------- XMPPEngineer.userprefs | 6 ++++- XMPPEngineerTest/Program.cs | 6 ++--- XMPPEngineerTest/Properties/AssemblyInfo.cs | 26 --------------------- 4 files changed, 8 insertions(+), 56 deletions(-) delete mode 100644 Properties/AssemblyInfo.cs delete mode 100644 XMPPEngineerTest/Properties/AssemblyInfo.cs diff --git a/Properties/AssemblyInfo.cs b/Properties/AssemblyInfo.cs deleted file mode 100644 index 6122c321..00000000 --- a/Properties/AssemblyInfo.cs +++ /dev/null @@ -1,26 +0,0 @@ -using System.Reflection; -using System.Runtime.CompilerServices; - -// Information about this assembly is defined by the following attributes. -// Change them to the values specific to your project. - -[assembly: AssemblyTitle("XMPPEngineer")] -[assembly: AssemblyDescription("XMPPEngineer is a multiplatform, Windows and Android, .NET XMPP client assembly. XMPPEngineer supports IM functionality and a variety of XMPP extensions, is simple and is extensively tested against Android Xamarin. It is a fork of the currently frozen excellent Sharp.XMPP and S22.Xmpp projects.")] -[assembly: AssemblyConfiguration("")] -[assembly: AssemblyCompany("PGStath")] -[assembly: AssemblyProduct("XMPPEngineer")] -[assembly: AssemblyCopyright("XMPPEngineer Steven Livingstone-Perez 2016,2017,2018; Sharp.XMPP Panagiotis Georgiou Stathopoulos 2015; S22.Xmpp Torben Könke 2013")] -[assembly: AssemblyTrademark("")] -[assembly: AssemblyCulture("")] - -// The assembly version has the format "{Major}.{Minor}.{Build}.{Revision}". -// The form "{Major}.{Minor}.*" will automatically update the build and revision, -// and "{Major}.{Minor}.{Build}.*" will update just the revision. - -[assembly: AssemblyVersion("1.0.*")] - -// The following attributes are used to specify the signing key for the assembly, -// if desired. See the Mono documentation for more information about signing. - -//[assembly: AssemblyDelaySign(false)] -//[assembly: AssemblyKeyFile("")] diff --git a/XMPPEngineer.userprefs b/XMPPEngineer.userprefs index 94c4d9df..c993b3db 100644 --- a/XMPPEngineer.userprefs +++ b/XMPPEngineer.userprefs @@ -1,5 +1,9 @@  - + + + + + diff --git a/XMPPEngineerTest/Program.cs b/XMPPEngineerTest/Program.cs index c9846eae..43580d89 100644 --- a/XMPPEngineerTest/Program.cs +++ b/XMPPEngineerTest/Program.cs @@ -22,9 +22,9 @@ public static void Main(string[] args) // with stream management using (XmppClient clientsm = new XmppClient( "alchemy.local", - "steven", - "test", - "dev-lite-citym-access.westeurope.cloudapp.azure.com", + "admin", + "PASSWORD", + "alchemy.local", XmppClient.AvailableExtensions.Default | XmppClient.AvailableExtensions.Ping)) //XmppClient.AvailableExtensions.DataForms //XmppClient.AvailableExtensions.MessageCarbons diff --git a/XMPPEngineerTest/Properties/AssemblyInfo.cs b/XMPPEngineerTest/Properties/AssemblyInfo.cs deleted file mode 100644 index 416f7714..00000000 --- a/XMPPEngineerTest/Properties/AssemblyInfo.cs +++ /dev/null @@ -1,26 +0,0 @@ -using System.Reflection; -using System.Runtime.CompilerServices; - -// Information about this assembly is defined by the following attributes. -// Change them to the values specific to your project. - -[assembly: AssemblyTitle("XMPPEngineerTest")] -[assembly: AssemblyDescription("")] -[assembly: AssemblyConfiguration("")] -[assembly: AssemblyCompany("")] -[assembly: AssemblyProduct("")] -[assembly: AssemblyCopyright("${AuthorCopyright}")] -[assembly: AssemblyTrademark("")] -[assembly: AssemblyCulture("")] - -// The assembly version has the format "{Major}.{Minor}.{Build}.{Revision}". -// The form "{Major}.{Minor}.*" will automatically update the build and revision, -// and "{Major}.{Minor}.{Build}.*" will update just the revision. - -[assembly: AssemblyVersion("1.0.*")] - -// The following attributes are used to specify the signing key for the assembly, -// if desired. See the Mono documentation for more information about signing. - -//[assembly: AssemblyDelaySign(false)] -//[assembly: AssemblyKeyFile("")] From b2e0ae8bf85814c4634990f2d65de0212b47b67d Mon Sep 17 00:00:00 2001 From: Steven Livingstone-Perez Date: Thu, 8 Feb 2018 16:31:50 +0000 Subject: [PATCH 19/22] Removed dependency on ARSoft for DNS resolution that was rarely used & caused compile & deployment issues.. --- Core/XmppCore.cs | 104 ++++------------------------------------- XMPPEngineer.csproj | 3 -- XMPPEngineer.userprefs | 6 +-- 3 files changed, 9 insertions(+), 104 deletions(-) diff --git a/Core/XmppCore.cs b/Core/XmppCore.cs index 18b4a450..d18e1392 100644 --- a/Core/XmppCore.cs +++ b/Core/XmppCore.cs @@ -1,5 +1,4 @@ -using ARSoft.Tools.Net.Dns; -using XMPPEngineer.Core.Sasl; +using XMPPEngineer.Core.Sasl; using System; using System.Collections.Concurrent; using System.Collections.Generic; @@ -22,21 +21,6 @@ namespace XMPPEngineer.Core /// For implementation details, refer to RFC 3920. public class XmppCore : IDisposable { - /// - /// The DNS SRV name records - /// - private List dnsRecordList; - - /// - /// The current SRV DNS record to use - /// - private SrvRecord dnsCurrent; - - /// - /// Bool variable indicating whether DNS records are initialised - /// - private bool dnsIsInit = false; - /// /// The TCP connection to the XMPP server. /// @@ -387,18 +371,9 @@ public XmppCore(string hostname, string username, string password, string server int port = 5222, bool tls = true, RemoteCertificateValidationCallback validate = null) { if (String.IsNullOrWhiteSpace(server)) { - moveNextSrvDNS(hostname); - if (dnsCurrent != null) - { - Hostname = dnsCurrent.Target.ToString(); - Port = dnsCurrent.Port; - } - else - { - Hostname = hostname; - Server = hostname; - Port = port; - } + Hostname = hostname; + Server = hostname; + Port = port; } else { Server = server; @@ -454,18 +429,10 @@ public XmppCore(string hostname, string server, int port = 5222, bool tls = true { if (String.IsNullOrWhiteSpace(server)) { - moveNextSrvDNS(hostname); - if (dnsCurrent != null) - { - Hostname = dnsCurrent.Target.ToString(); - Port = dnsCurrent.Port; - } - else - { - Hostname = hostname; - Server = hostname; - Port = port; - } + + Hostname = hostname; + Server = hostname; + Port = port; } else { Server = server; @@ -477,61 +444,6 @@ public XmppCore(string hostname, string server, int port = 5222, bool tls = true Validate = validate; } - /// - /// Initialises and resolves the DNS Domain, and set to dnsCurrent the next - /// SRV record to use - /// - /// XMPP Domain - /// XMPP server hostname for the Domain - private SrvRecord moveNextSrvDNS(string domain) - { - domain.ThrowIfNullOrEmpty("domain"); - //If already a lookup has being made return - if (dnsIsInit) - { - //If it is already init we remove the current - if (dnsRecordList != null && dnsCurrent != null) dnsRecordList.Remove(dnsCurrent); - dnsCurrent = dnsRecordList.FirstOrDefault(); - return dnsCurrent; - }; - dnsIsInit = true; - - var domainName = ARSoft.Tools.Net.DomainName.Parse(("_xmpp-client._tcp." + domain)); - DnsMessage dnsMessage = DnsClient.Default.Resolve(domainName, RecordType.Srv); - if ((dnsMessage == null) || ((dnsMessage.ReturnCode != ReturnCode.NoError) && (dnsMessage.ReturnCode != ReturnCode.NxDomain))) - { - //If DNS SRV records lookup fails then continue with the host name -#if DEBUG - System.Diagnostics.Debug.WriteLine("DNS Lookup Failed"); -#endif - return null; - } - else - { - var tempList = new List(); - - foreach (DnsRecordBase dnsRecord in dnsMessage.AnswerRecords) - { - SrvRecord srvRecord = dnsRecord as SrvRecord; - if (srvRecord != null) - { - tempList.Add(srvRecord); - Console.WriteLine(srvRecord.ToString()); - Console.WriteLine(" |--- Name " + srvRecord.Name); - Console.WriteLine(" |--- Port: " + srvRecord.Port); - Console.WriteLine(" |--- Priority" + srvRecord.Priority); - Console.WriteLine(" |--- Type " + srvRecord.RecordType); - Console.WriteLine(" |--- Target: " + srvRecord.Target); - Console.WriteLine(); - } - } - dnsRecordList = tempList.OrderBy(o => o.Priority).ThenBy(order => order.Weight).ToList(); - - dnsCurrent = dnsRecordList.FirstOrDefault(); - return dnsCurrent; - } - } - /// /// Establishes a connection to the XMPP server. /// diff --git a/XMPPEngineer.csproj b/XMPPEngineer.csproj index a47bbff0..5766db61 100644 --- a/XMPPEngineer.csproj +++ b/XMPPEngineer.csproj @@ -4,7 +4,4 @@ netcoreapp2.0 - - - diff --git a/XMPPEngineer.userprefs b/XMPPEngineer.userprefs index c993b3db..94c4d9df 100644 --- a/XMPPEngineer.userprefs +++ b/XMPPEngineer.userprefs @@ -1,9 +1,5 @@  - - - - - + From caace91eede8305a17d815e0be8a5ddc4e4ca4d0 Mon Sep 17 00:00:00 2001 From: livz Date: Mon, 30 Apr 2018 23:10:45 +0100 Subject: [PATCH 20/22] Updated order of build target framework or it does not complie. --- XMPPEngineer.csproj | 6 ++---- XMPPEngineer.userprefs | 10 ---------- 2 files changed, 2 insertions(+), 14 deletions(-) delete mode 100644 XMPPEngineer.userprefs diff --git a/XMPPEngineer.csproj b/XMPPEngineer.csproj index 5766db61..881f91ac 100644 --- a/XMPPEngineer.csproj +++ b/XMPPEngineer.csproj @@ -1,7 +1,5 @@ - - netcoreapp2.0 + netcoreapp2.0;netstandard2.0; - - + \ No newline at end of file diff --git a/XMPPEngineer.userprefs b/XMPPEngineer.userprefs deleted file mode 100644 index 94c4d9df..00000000 --- a/XMPPEngineer.userprefs +++ /dev/null @@ -1,10 +0,0 @@ - - - - - - - - - - \ No newline at end of file From 248a81a44af8e857d9de13f243468b3aaf51382a Mon Sep 17 00:00:00 2001 From: livz Date: Tue, 1 May 2018 22:49:36 +0100 Subject: [PATCH 21/22] Removed .Net Standard reference - just using Core. --- XMPPEngineer.csproj | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/XMPPEngineer.csproj b/XMPPEngineer.csproj index 881f91ac..6a6c1b7a 100644 --- a/XMPPEngineer.csproj +++ b/XMPPEngineer.csproj @@ -1,5 +1,5 @@ - netcoreapp2.0;netstandard2.0; + netcoreapp2.0 \ No newline at end of file From e1296212899997d06f5dffb3facd4543ee92cc8c Mon Sep 17 00:00:00 2001 From: Steven Livingstone-Perez Date: Wed, 10 Oct 2018 10:21:42 +0100 Subject: [PATCH 22/22] Updated to use .Net Standard 2.0 to enable compatability with Xamarin projects. --- XMPPEngineer.csproj | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/XMPPEngineer.csproj b/XMPPEngineer.csproj index 6a6c1b7a..9f9cb45a 100644 --- a/XMPPEngineer.csproj +++ b/XMPPEngineer.csproj @@ -1,5 +1,5 @@ - netcoreapp2.0 + netstandard2.0 \ No newline at end of file