Skip to content

Instantly share code, notes, and snippets.

@Jezternz
Created January 12, 2017 04:00
Show Gist options
  • Save Jezternz/fb4ad1f46eb38b0324cc24786f8af556 to your computer and use it in GitHub Desktop.
Save Jezternz/fb4ad1f46eb38b0324cc24786f8af556 to your computer and use it in GitHub Desktop.
using GoogleCloudPrintApi;
using GoogleCloudPrintApi.Models;
using Microsoft.Win32;
using Newtonsoft.Json;
using System;
using System.Diagnostics;
using System.IO;
using System.Net.Security;
using System.Net.Sockets;
using System.Text;
using System.Threading.Tasks;
using System.Xml;
namespace GCPXMPPTest
{
class Program
{
private const string clientId = ""; // Client id created from google
private const string clientSecret = ""; // Secret created from google
private const string clientXmppJid = ""; // JID used to create Client id & Secret, eg for 'admin@gmail.com', this should be 'admin'.
private static string regKey = "HKEY_CURRENT_USER\\SOFTWARE\\GCPXMPPTest\\Testing";
private static string lastTokenKey = "LastAuthToken";
static void Main(string[] args)
{
try
{
var provider = new GoogleCloudPrintOAuth2Provider(clientId, clientSecret);
Console.WriteLine("Would you like to generate and use a new Auth Url? (y/n)");
var yes = Console.ReadLine().Trim().Contains("y");
Token token = null;
if(yes)
{
var url = provider.BuildAuthorizationUrl();
Console.WriteLine("Ready to open Auth link, press any key to open browser...");
Console.ReadKey();
var process = Process.Start(url);
Console.WriteLine("Please paste auth code, then hit enter...");
var authCode = Console.ReadLine().Trim();
Console.WriteLine("Accepted code: '{0}'", authCode);
token = provider.GenerateRefreshTokenAsync(authCode).Result;
Registry.SetValue(regKey, lastTokenKey, JsonConvert.SerializeObject(token));
}
else
{
Console.WriteLine("Reading previously saved token...");
var tokenJSON = (string)Registry.GetValue(regKey, lastTokenKey, "");
token = JsonConvert.DeserializeObject<Token>(tokenJSON);
}
var xmppConnection = new GoogleCloudPrintXMPPConnection();
xmppConnection.OnIncomingPrintJobs += (obj, evt) => Console.WriteLine("Recieved incoming job event for printer '{0}'", evt.PrinterId);
var tokenStr = token.RefreshToken; // .AccessToken Works, .RefreshToken fails.
xmppConnection.Initialize(tokenStr, clientXmppJid);
}
catch(Exception ex)
{
Console.WriteLine("Exception occured: {0}", ex.Message);
}
Console.WriteLine("Press Any key to stop service running in console.");
Console.ReadKey();
}
}
class GoogleCloudPrintXMPPConnection
{
// Consts
private const string xmppServerHost = "talk.google.com";
private const int xmppServerPort = 5223;
// Stream / Connection
private TcpClient _xmppTcpClient = null;
private SslStream _xmppSslStream = null;
private DateTime _startTime = DateTime.Now;
// Stream logging for initial connect
public string ConnectConversation { get; private set; }
// Handler for incoming job events
public event EventHandler<JobRecievedEventArgs> OnIncomingPrintJobs;
#region Constructor & Cleanup
public void Initialize(string xmppJid, string token)
{
if (_xmppSslStream == null && !string.IsNullOrEmpty(token))
{
EstablishConnectionAsync(token, xmppJid);
}
}
public void Cleanup()
{
Console.WriteLine("XMPP Connection - Closing & Cleaning up socket connection with google.");
// Cleanup stream & client
if (_xmppSslStream != null)
{
_xmppSslStream.Close();
_xmppSslStream = null;
}
if (_xmppTcpClient != null)
{
_xmppTcpClient.Close();
_xmppTcpClient = null;
}
}
#endregion
#region Main Connection Creation
public void EstablishConnectionAsync(string xmppJid, string accessToken)
{
ConnectConversation = "\n[ConversationBegin]\n";
try
{
Console.WriteLine("XMPP Connection - Opening new socket connection with google.");
// Setup socket connection
_xmppTcpClient = new TcpClient(xmppServerHost, xmppServerPort);
// Setup SSL Wrapper
var tcpStream = _xmppTcpClient.GetStream();
_xmppSslStream = new SslStream(tcpStream, false, new RemoteCertificateValidationCallback((s, c, ch, ss) => true), null);
// Authenticate
_xmppSslStream.AuthenticateAsClient(xmppServerHost);
// Begin conversation with google
// 1st initial stream request
Send("<stream:stream to=\"gmail.com\" xml:lang=\"en\" version=\"1.0\" xmlns:stream=\"http://etherx.jabber.org/streams\" xmlns=\"jabber:client\">");
var streamResp11 = Recieve();
var streamResp12 = Recieve();
Console.WriteLine("XMPP Connection - Authenticating with google.");
// Authenticate using Oauth2
var authB64 = Convert.ToBase64String(Encoding.UTF8.GetBytes(string.Format("\0{0}\0{1}", xmppJid, accessToken)));
Send("<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);
var authResp = RecieveXml();
if (authResp.GetElementsByTagName("success").Count < 1)
{
var exMsg = string.Format("XMPP Connection - Authentication failed, xmpp conversation: '{0}'.", ConnectConversation);
throw new GoogleCloudPrintXMPPConnectionException(exMsg);
}
// 2nd stream request (now authenticated)
Send("<stream:stream to=\"gmail.com\" xml:lang=\"en\" version=\"1.0\" xmlns:stream=\"http://etherx.jabber.org/streams\" xmlns=\"jabber:client\">");
var streamResp21 = Recieve();
var streamResp22 = Recieve();
Console.WriteLine("XMPP Connection - Subscribing to google cloud print events...");
// Bind Request (to retrieve jid)
Send("<iq type=\"set\" id=\"0\"><bind xmlns=\"urn:ietf:params:xml:ns:xmpp-bind\"><resource>cloud_print</resource></bind></iq>");
var bindResp = RecieveXml();
var fullJid = bindResp.GetElementsByTagName("jid")[0].InnerText;
var bareJid = fullJid.Substring(0, fullJid.IndexOf('/'));
// Establish session
Send("<iq type=\"set\" id=\"2\"><session xmlns=\"urn:ietf:params:xml:ns:xmpp-session\"/></iq>");
var sessionResp1 = Recieve();
var sessionResp2 = Recieve();
// Use jid to now subscribe to google cloud print events
Send("<iq type=\"set\" id=\"3\" to=\"{0}\"><subscribe xmlns=\"google:push\"><item channel=\"cloudprint.google.com\" from=\"cloudprint.google.com\"/></subscribe></iq>", bareJid);
var subscribeResp = Recieve();
Console.WriteLine("XMPP Connection - established connection to google and subscribed to events successfully.");
// Finally begin asynchronously listening for events
var listenerLoop = new Task(() => BeginListenerLoop());
listenerLoop.Start();
Console.WriteLine("XMPP Connection - Ready.");
}
catch (Exception ex)
{
Cleanup();
// If recognized, just log & throw
if (ex is GoogleCloudPrintXMPPConnectionException)
{
Console.WriteLine(ex.Message);
throw ex;
}
// else create new one.
else
{
var message = string.Format("XMPP Connection - Exception occured while attempting to establish secure stream with google exception: '{0}', conversation: '{1}'", ex.Message, ConnectConversation);
Console.WriteLine(message);
throw new GoogleCloudPrintXMPPConnectionException(message);
}
}
}
#endregion
#region Main Listen Loop
private async void BeginListenerLoop()
{
// Async task to listen to google stream for new additions to joblist
try
{
Console.WriteLine("XMPP Connection - Starting listen loop.");
// Only end when _xmppSslStream is cleaned up (set to null)
while (_xmppSslStream != null)
{
try
{
Console.WriteLine("XMPP Connection - Ping at '{0}' Runtime '{1}'", DateTime.Now.ToUniversalTime(), DateTime.Now.Subtract(_startTime).ToString());
// Asnynchronously wait for new xml incoming from google
var xmlDoc = await RecieveXmlAsync();
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));
Console.WriteLine("Xmpp Connection: Recieved job addition event from printer '{0}'", printerId);
OnIncomingPrintJobs(new object(), new JobRecievedEventArgs() { PrinterId = printerId });
}
}
}
catch(Exception ex)
{
Console.WriteLine("An Exception was thrown while listening to google, press any key to end program. Exception: '{0}'", ex.Message);
Cleanup();
}
}
Console.WriteLine("XMPP Connection - Ending listen loop.");
}
catch (Exception ex)
{
Console.WriteLine("Xmpp Connection: While listening for GCP events, an error occured, google connection has been disrupted: {0}", ex.Message);
Cleanup();
}
}
#endregion
#region connection read & write methods
private void Send(string message, params object[] values)
{
SendAsync(message, values).Wait();
}
private string Recieve()
{
return RecieveAsync().Result;
}
private XmlDocument RecieveXml()
{
return RecieveXmlAsync().Result;
}
private async Task SendAsync(string message, params object[] values)
{
// Asynchronously send message to google
if (values.Length > 0) message = string.Format(message, values);
ConnectConversation += string.Format("> {0}\n\n", message);
var streamWriter = new StreamWriter(_xmppSslStream, Encoding.UTF8);
await streamWriter.WriteLineAsync(message);
await streamWriter.FlushAsync();
}
private async Task<string> RecieveAsync()
{
// Asynchronously recieve message from google
var streamReader = new StreamReader(_xmppSslStream, Encoding.UTF8);
var bytesRead = 0;
var xmlString = string.Empty;
char[] buffer = new char[1024];
bytesRead = await streamReader.ReadAsync(buffer, 0, buffer.Length);
if (bytesRead == 1 && buffer[0] == ' ') bytesRead = 0;
xmlString += (new string(buffer)).Substring(0, bytesRead);
ConnectConversation += string.Format("< {0}\n\n", xmlString);
return xmlString;
}
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 (Exception ex)
{
Console.WriteLine("Xmpp Connection: Failed to parse xml '{0}' with error '{1}', instead returning null as recieved XML...", validXmlString, ex.Message);
return null;
}
return xmlDoc;
}
#endregion
}
public class JobRecievedEventArgs : EventArgs
{
public string PrinterId { get; set; }
}
public class GoogleCloudPrintXMPPConnectionException : Exception
{
public GoogleCloudPrintXMPPConnectionException(string message) : base(message) { }
}
}
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment