Created
January 12, 2017 04:00
-
-
Save Jezternz/fb4ad1f46eb38b0324cc24786f8af556 to your computer and use it in GitHub Desktop.
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
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 '[email protected]', 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