/// <summary>
        /// Stop and dispose xmpp-related source together with handlers
        /// </summary>
        public void StopXmppAndCleanup()
        {
            StopXmpp();

            // Unsubscribe all handlers for xmpp subscription
            OnIncomingPrintJobs = null;
            OnXmppDebugLogging?.Invoke(this, "Clean up event handlers.");
            OnXmppDebugLogging = null;
        }
 /// <summary>
 /// Stop and dispose xmpp-related resource
 /// </summary>
 private void StopXmpp()
 {
     OnXmppDebugLogging?.Invoke(this, "Xmpp Connection - Closing & Cleaning up socket connection with google.");
     // Cleanup stream & client
     if (_xmppSslStream != null)
     {
         _xmppSslStream.Dispose();
         _xmppSslStream = null;
     }
     if (_xmppTcpClient != null)
     {
         _xmppTcpClient.Dispose();
         _xmppTcpClient = null;
     }
 }
 /// <summary>
 /// Stop and dispose xmpp-related resource
 /// </summary>
 private void StopXmpp()
 {
     OnXmppDebugLogging?.Invoke(this, "Xmpp Connection - Closing & Cleaning up socket connection with google.");
     // Cleanup stream & client
     if (_xmppSslStream != null)
     {
         _xmppSslStream.Dispose();
         _xmppSslStream = null;
     }
     if (_xmppTcpClient != null)
     {
         _xmppTcpClient.Dispose();
         _xmppTcpClient = null;
     }
     _started = false;
     OnPrinterStopped?.Invoke(this, new EventArgs());
 }
        /// <summary>
        /// Listener loop for subscribing google talk events
        /// </summary>
        /// <returns>Returns if the loop is manually break or not</returns>
        private async Task <bool> ListenForIncomingJobsAsync()
        {
            // Async task to listen to google stream for new additions to joblist
            OnXmppDebugLogging?.Invoke(this, "Xmpp Connection - Listener loop started.");

            // Only end when _xmppSslStream is cleaned up (set to null)
            while (_xmppSslStream != null)
            {
                try
                {
                    OnXmppDebugLogging?.Invoke(this, $"Xmpp Connection - Ping at '{DateTime.Now.ToUniversalTime()}'");
                    OnPing?.Invoke(this, new EventArgs());
                    // Asnynchronously wait for new xml incoming from google
                    var xmlDoc = await RecieveXmlAsync().ConfigureAwait(false);

                    if (xmlDoc != null && OnIncomingPrintJobs != null)
                    {
                        // If xml node matches 'push:data', it must contain incoming printjob notification from google
                        var pushData = xmlDoc.GetElementsByTagName("push:data");
                        if (pushData.Count > 0)
                        {
                            // Retrieve printerId & Log
                            var printerIdEncoded = pushData[0].InnerText;
                            var printerId        = Encoding.UTF8.GetString(Convert.FromBase64String(printerIdEncoded));
                            OnXmppDebugLogging?.Invoke(this, $"Xmpp Connection: Recieved job addition event from printer '{printerId}'");

                            OnIncomingPrintJobs?.Invoke(this, new JobRecievedEventArgs {
                                PrinterId = printerId
                            });
                        }
                    }
                }
                catch (System.Exception ex)
                {
                    OnXmppDebugLogging?.Invoke(this, $"Xmpp Connection - An Exception was thrown while listening to google, the listener loop is terminated. Exception: '{ex.Message}'");
                    StopXmpp();
                    return(false);
                }
            }

            OnXmppDebugLogging?.Invoke(this, "Xmpp Connection - Listener loop ended.");
            return(true);
        }
        private async Task <XmlDocument> RecieveXmlAsync()
        {
            // asynchrnously recieve message from google & parse as xml if possible.
            var xmlString = await RecieveAsync();

            if (xmlString.Length <= 1)
            {
                return(null);
            }
            var         strFormat      = xmlString.Contains("<stream:stream") ? "{0}</stream:stream>" : "<stream:stream xmlns:stream=\"http://etherx.jabber.org/streams\">{0}</stream:stream>";
            var         validXmlString = string.Format(strFormat, xmlString);
            XmlDocument xmlDoc         = new XmlDocument();

            try
            {
                xmlDoc.LoadXml(validXmlString);
            }
            catch (System.Exception ex)
            {
                OnXmppDebugLogging?.Invoke(this, $"Xmpp Connection - Failed to parse xml '{validXmlString}' with error '{ex.Message}', instead returning null as recieved XML...");
                return(null);
            }
            return(xmlDoc);
        }
        public async Task InitXmppAsync(string xmppJid)
        {
            await UpdateTokenAsync().ConfigureAwait(false);

            ConnectConversation = "\n[ConversationBegin]\n";
            try
            {
                OnXmppDebugLogging?.Invoke(this, "Xmpp Connection - Opening new socket connection with google.");

                // Setup socket connection
                _xmppTcpClient = new TcpClient();
                await _xmppTcpClient.ConnectAsync(xmppServerHost, xmppServerPort).ConfigureAwait(false);

                // Setup SSL Wrapper
                var tcpStream = _xmppTcpClient.GetStream();
                _xmppSslStream = new SslStream(tcpStream, false, new RemoteCertificateValidationCallback((s, c, ch, ss) => true), null);

                // Authenticate
                await _xmppSslStream.AuthenticateAsClientAsync(xmppServerHost).ConfigureAwait(false);

                // Begin conversation with google
                // 1st initial stream request
                await SendAsync("<stream:stream to=\"gmail.com\" xml:lang=\"en\" version=\"1.0\" xmlns:stream=\"http://etherx.jabber.org/streams\" xmlns=\"jabber:client\">").ConfigureAwait(false);

                var streamResp11 = await RecieveAsync().ConfigureAwait(false);

                var streamResp12 = await RecieveAsync().ConfigureAwait(false);

                OnXmppDebugLogging?.Invoke(this, "Xmpp Connection - Authenticating with google.");

                // Authenticate using Oauth2
                var authB64 = Convert.ToBase64String(Encoding.UTF8.GetBytes($"\0{xmppJid}\0{_token.AccessToken}"));
                await SendAsync("<auth xmlns=\"urn:ietf:params:xml:ns:xmpp-sasl\" mechanism=\"X-OAUTH2\" auth:service=\"oauth2\" xmlns:auth=\"http://www.google.com/talk/protocol/auth\">{0}</auth>", authB64).ConfigureAwait(false);

                var authResp = await RecieveXmlAsync().ConfigureAwait(false);

                if (authResp == null || authResp.GetElementsByTagName("success").Count < 1)
                {
                    throw new GoogleCloudPrintException($"Xmpp Connection - Authentication failed, xmpp conversation: '{ConnectConversation}'.");
                }

                // 2nd stream request (now authenticated)
                await SendAsync("<stream:stream to=\"gmail.com\" xml:lang=\"en\" version=\"1.0\" xmlns:stream=\"http://etherx.jabber.org/streams\" xmlns=\"jabber:client\">").ConfigureAwait(false);

                var streamResp21 = await RecieveAsync().ConfigureAwait(false);

                var streamResp22 = await RecieveAsync().ConfigureAwait(false);

                OnXmppDebugLogging?.Invoke(this, "Xmpp Connection - Subscribing to google cloud print events...");

                // Bind Request (to retrieve jid)
                await SendAsync("<iq type=\"set\" id=\"0\"><bind xmlns=\"urn:ietf:params:xml:ns:xmpp-bind\"><resource>cloud_print</resource></bind></iq>").ConfigureAwait(false);

                var bindResp = await RecieveXmlAsync().ConfigureAwait(false);

                if (bindResp == null)
                {
                    throw new GoogleCloudPrintException($"Xmpp Connection - Bind failed, xmpp conversation: '{ConnectConversation}'.");
                }

                var fullJid = bindResp.GetElementsByTagName("jid")[0].InnerText;
                var bareJid = fullJid.Substring(0, fullJid.IndexOf('/'));

                // Establish session
                await SendAsync("<iq type=\"set\" id=\"2\"><session xmlns=\"urn:ietf:params:xml:ns:xmpp-session\"/></iq>").ConfigureAwait(false);

                var sessionResp1 = await RecieveAsync().ConfigureAwait(false);

                var sessionResp2 = await RecieveAsync().ConfigureAwait(false);

                // Use jid to now subscribe to google cloud print events
                await SendAsync("<iq type=\"set\" id=\"3\" to=\"{0}\"><subscribe xmlns=\"google:push\"><item channel=\"cloudprint.google.com\" from=\"cloudprint.google.com\"/></subscribe></iq>", bareJid).ConfigureAwait(false);

                var subscribeResp = await RecieveAsync().ConfigureAwait(false);

                OnXmppDebugLogging?.Invoke(this, "Xmpp Connection - Established connection to google and Subscribed to events successfully.");

                // Finally begin asynchronously listening for events
                new Task(async() =>
                {
                    // Re-init the handshake if the loop is not manually break
                    if (!await ListenForIncomingJobsAsync().ConfigureAwait(false))
                    {
                        await InitXmppAsync(xmppJid).ConfigureAwait(false);
                    }
                }).Start();

                OnXmppDebugLogging?.Invoke(this, "Xmpp Connection - Ready.");
            }
            catch (GoogleCloudPrintException ex)
            {
                StopXmpp();
                OnXmppDebugLogging?.Invoke(this, ex.Message);
                throw ex;
            }
            catch (System.Exception ex)
            {
                StopXmpp();
                var message = $"Xmpp Connection - Exception occured while attempting to establish secure stream with google exception: '{ex.Message}', conversation: '{ConnectConversation}'";
                OnXmppDebugLogging?.Invoke(this, message);
                throw new GoogleCloudPrintException(message);
            }
        }