PageRenderTime 46ms CodeModel.GetById 17ms RepoModel.GetById 0ms app.codeStats 0ms

/PushSharp.Apple/ApplePushChannel.cs

https://github.com/mustafagenc/PushSharp
C# | 422 lines | 309 code | 71 blank | 42 comment | 42 complexity | fd80cbbac0806d7fff5ffcd10eb3fb3f MD5 | raw file
Possible License(s): Apache-2.0
  1. using System;
  2. using System.Collections.Generic;
  3. using System.Collections.Concurrent;
  4. using System.Security.Cryptography.X509Certificates;
  5. using System.Linq;
  6. using System.Net.Sockets;
  7. using System.Net.Security;
  8. using System.Text;
  9. using System.Threading;
  10. using System.Threading.Tasks;
  11. using System.Net;
  12. using PushSharp.Common;
  13. namespace PushSharp.Apple
  14. {
  15. public class ApplePushChannel : PushChannelBase
  16. {
  17. #region Constants
  18. private const string hostSandbox = "gateway.sandbox.push.apple.com";
  19. private const string hostProduction = "gateway.push.apple.com";
  20. private const int initialReconnectDelay = 3000;
  21. #endregion
  22. public delegate void ConnectingDelegate(string host, int port);
  23. public event ConnectingDelegate OnConnecting;
  24. public delegate void ConnectedDelegate(string host, int port);
  25. public event ConnectedDelegate OnConnected;
  26. public delegate void ConnectionFailureDelegate(ConnectionFailureException exception);
  27. public event ConnectionFailureDelegate OnConnectionFailure;
  28. public delegate void WaitBeforeReconnectDelegate(int millisecondsToWait);
  29. public event WaitBeforeReconnectDelegate OnWaitBeforeReconnect;
  30. ApplePushChannelSettings appleSettings = null;
  31. List<SentNotification> sentNotifications = new List<SentNotification>();
  32. public ApplePushChannel(ApplePushChannelSettings channelSettings, PushServiceSettings serviceSettings = null) : base(channelSettings, serviceSettings)
  33. {
  34. this.appleSettings = channelSettings;
  35. certificate = this.appleSettings.Certificate;
  36. certificates = new X509CertificateCollection();
  37. if (appleSettings.AddLocalAndMachineCertificateStores)
  38. {
  39. var store = new X509Store(StoreLocation.LocalMachine);
  40. certificates.AddRange(store.Certificates);
  41. store = new X509Store(StoreLocation.CurrentUser);
  42. certificates.AddRange(store.Certificates);
  43. }
  44. certificates.Add(certificate);
  45. if (this.appleSettings.AdditionalCertificates != null)
  46. foreach (var addlCert in this.appleSettings.AdditionalCertificates)
  47. certificates.Add(addlCert);
  48. //Start our cleanup task
  49. taskCleanup = new Task(() => Cleanup(), TaskCreationOptions.LongRunning);
  50. taskCleanup.ContinueWith((t) => { var ex = t.Exception; }, TaskContinuationOptions.OnlyOnFaulted);
  51. taskCleanup.Start();
  52. }
  53. public override PlatformType PlatformType
  54. {
  55. get { return Common.PlatformType.Apple; }
  56. }
  57. object sentLock = new object();
  58. object streamWriteLock = new object();
  59. int reconnectDelay = 3000;
  60. float reconnectBackoffMultiplier = 1.5f;
  61. byte[] readBuffer = new byte[6];
  62. bool connected = false;
  63. X509Certificate certificate;
  64. X509CertificateCollection certificates;
  65. TcpClient client;
  66. SslStream stream;
  67. System.IO.Stream networkStream;
  68. Task taskCleanup;
  69. protected override void SendNotification(Common.Notification notification)
  70. {
  71. var appleNotification = notification as AppleNotification;
  72. bool isOkToSend = true;
  73. byte[] notificationData = new byte[] {};
  74. try
  75. {
  76. notificationData = appleNotification.ToBytes();
  77. }
  78. catch (NotificationFailureException nfex)
  79. {
  80. //Bad notification format already
  81. isOkToSend = false;
  82. this.Events.RaiseNotificationSendFailure(notification, nfex);
  83. }
  84. if (isOkToSend)
  85. {
  86. Connect();
  87. try
  88. {
  89. lock (streamWriteLock)
  90. {
  91. lock (sentLock)
  92. {
  93. networkStream.Write(notificationData, 0, notificationData.Length);
  94. sentNotifications.Add(new SentNotification(appleNotification));
  95. }
  96. }
  97. }
  98. catch (Exception)
  99. {
  100. this.QueueNotification(notification);
  101. } //If this failed, we probably had a networking error, so let's requeue the notification
  102. }
  103. }
  104. public override void Stop(bool waitForQueueToDrain)
  105. {
  106. stopping = true;
  107. //See if we want to wait for the queue to drain before stopping
  108. if (waitForQueueToDrain)
  109. {
  110. while (QueuedNotificationCount > 0 || sentNotifications.Count > 0)
  111. Thread.Sleep(50);
  112. }
  113. //Sleep a bit to prevent any race conditions
  114. //especially since our cleanup method may need 3 seconds
  115. Thread.Sleep(5000);
  116. if (!CancelTokenSource.IsCancellationRequested)
  117. CancelTokenSource.Cancel();
  118. //Wait on our tasks for a maximum of 30 seconds
  119. Task.WaitAll(new Task[] { base.taskSender, taskCleanup }, 30000);
  120. }
  121. void Reader()
  122. {
  123. try
  124. {
  125. var result = networkStream.BeginRead(readBuffer, 0, 6, new AsyncCallback((asyncResult) =>
  126. {
  127. lock (sentLock)
  128. {
  129. try
  130. {
  131. var bytesRead = networkStream.EndRead(asyncResult);
  132. if (bytesRead > 0)
  133. {
  134. //We now expect apple to close the connection on us anyway, so let's try and close things
  135. // up here as well to get a head start
  136. //Hopefully this way we have less messages written to the stream that we have to requeue
  137. try { stream.Close(); stream.Dispose(); }
  138. catch { }
  139. try { client.Close(); stream.Dispose(); }
  140. catch { }
  141. //Get the enhanced format response
  142. // byte 0 is always '1', byte 1 is the status, bytes 2,3,4,5 are the identifier of the notification
  143. var status = readBuffer[1];
  144. var identifier = IPAddress.NetworkToHostOrder(BitConverter.ToInt32(readBuffer, 2));
  145. int failedNotificationIndex = -1;
  146. SentNotification failedNotification = null;
  147. //Try and find the failed notification in our sent list
  148. for (int i = 0; i < sentNotifications.Count; i++)
  149. {
  150. var n = sentNotifications[i];
  151. if (n.Identifier.Equals(identifier))
  152. {
  153. failedNotificationIndex = i;
  154. failedNotification = n;
  155. break;
  156. }
  157. }
  158. //Don't bother doing anything unless we know what failed
  159. if (failedNotification != null && failedNotificationIndex > -1)
  160. {
  161. //Anything before the failed message must have sent OK
  162. // so let's expedite the success status Success for all those before the failed one
  163. if (failedNotificationIndex > 0)
  164. {
  165. for (int i = 0; i < failedNotificationIndex; i++)
  166. this.Events.RaiseNotificationSent(sentNotifications[i].Notification);
  167. }
  168. //The notification that failed needs to have a failure event raised
  169. // we don't requeue it because apple told us it failed for real
  170. this.Events.RaiseNotificationSendFailure(failedNotification.Notification,
  171. new NotificationFailureException(status, failedNotification.Notification));
  172. // finally, raise failure for anything after the index of this failed one
  173. // in the sent list, since we may have sent them but apple will have disregarded
  174. // anything after the failed one and not told us about it
  175. if (failedNotificationIndex < sentNotifications.Count - 1)
  176. {
  177. //Requeue the failed notification since we're not sure it's a bad
  178. // notification, just that it was sent after a bad one was
  179. for (int i = failedNotificationIndex + 1; i <= sentNotifications.Count - 1; i++)
  180. this.QueueNotification(sentNotifications[i].Notification, false);
  181. }
  182. //Now clear out the sent list since we processed them all manually above
  183. sentNotifications.Clear();
  184. }
  185. //Start reading again
  186. Reader();
  187. }
  188. else
  189. {
  190. connected = false;
  191. }
  192. }
  193. catch
  194. {
  195. connected = false;
  196. }
  197. } // End Lock
  198. }), null);
  199. }
  200. catch
  201. {
  202. connected = false;
  203. }
  204. }
  205. void Cleanup()
  206. {
  207. while (true)
  208. {
  209. bool wasRemoved = false;
  210. lock (sentLock)
  211. {
  212. //See if anything is here to process
  213. if (sentNotifications.Count > 0)
  214. {
  215. //Don't expire any notifications while we are in a connecting state
  216. if (connected || CancelToken.IsCancellationRequested)
  217. {
  218. //Get the oldest sent message
  219. var n = sentNotifications[0];
  220. //If it was sent more than 3 seconds ago,
  221. // we have to assume it was sent successfully!
  222. if (n.SentAt < DateTime.UtcNow.AddMilliseconds(-1 * appleSettings.MillisecondsToWaitBeforeMessageDeclaredSuccess))
  223. {
  224. wasRemoved = true;
  225. this.Events.RaiseNotificationSent(n.Notification);
  226. sentNotifications.RemoveAt(0);
  227. }
  228. else
  229. wasRemoved = false;
  230. }
  231. else
  232. {
  233. //In fact, if we weren't connected, bump up the sentat timestamp
  234. // so that we wait awhile after reconnecting to expire this message
  235. try { sentNotifications[0].SentAt = DateTime.UtcNow; }
  236. catch { }
  237. }
  238. }
  239. }
  240. if (this.CancelToken.IsCancellationRequested)
  241. break;
  242. else if (!wasRemoved)
  243. Thread.Sleep(250);
  244. }
  245. }
  246. void Connect()
  247. {
  248. //Keep trying to connect
  249. while (!connected && !CancelToken.IsCancellationRequested)
  250. {
  251. try
  252. {
  253. connect();
  254. connected = true;
  255. }
  256. catch (ConnectionFailureException ex)
  257. {
  258. connected = false;
  259. //Report the error
  260. var cf = this.OnConnectionFailure;
  261. if (cf != null)
  262. cf(ex);
  263. //Raise a channel exception
  264. this.Events.RaiseChannelException(ex, PlatformType.Apple);
  265. }
  266. if (!connected)
  267. {
  268. //Notify we are waiting before reconnecting
  269. var eowbrd = this.OnWaitBeforeReconnect;
  270. if (eowbrd != null)
  271. eowbrd(reconnectDelay);
  272. //Sleep for a delay
  273. int slept = 0;
  274. while (slept <= reconnectDelay && !this.CancelToken.IsCancellationRequested)
  275. {
  276. Thread.Sleep(250);
  277. slept += 250;
  278. }
  279. //Increase the delay by the exponential backoff multiplier
  280. reconnectDelay = (int)(reconnectBackoffMultiplier * reconnectDelay);
  281. }
  282. else
  283. {
  284. //Reset connect delay
  285. reconnectDelay = initialReconnectDelay;
  286. //Notify we are connected
  287. var eoc = this.OnConnected;
  288. if (eoc != null)
  289. eoc(this.appleSettings.Host, this.appleSettings.Port);
  290. }
  291. }
  292. }
  293. void connect()
  294. {
  295. client = new TcpClient();
  296. //Notify we are connecting
  297. var eoc = this.OnConnecting;
  298. if (eoc != null)
  299. eoc(this.appleSettings.Host, this.appleSettings.Port);
  300. try
  301. {
  302. client.Connect(this.appleSettings.Host, this.appleSettings.Port);
  303. }
  304. catch (Exception ex)
  305. {
  306. throw new ConnectionFailureException("Connection to Host Failed", ex);
  307. }
  308. if (appleSettings.SkipSsl)
  309. {
  310. networkStream = client.GetStream();
  311. }
  312. else
  313. {
  314. stream = new SslStream(client.GetStream(), false,
  315. new RemoteCertificateValidationCallback((sender, cert, chain, sslPolicyErrors) => { return true; }),
  316. new LocalCertificateSelectionCallback((sender, targetHost, localCerts, remoteCert, acceptableIssuers) =>
  317. {
  318. return certificate;
  319. }));
  320. try
  321. {
  322. stream.AuthenticateAsClient(this.appleSettings.Host, this.certificates, System.Security.Authentication.SslProtocols.Ssl3, false);
  323. //stream.AuthenticateAsClient(this.appleSettings.Host);
  324. }
  325. catch (System.Security.Authentication.AuthenticationException ex)
  326. {
  327. throw new ConnectionFailureException("SSL Stream Failed to Authenticate as Client", ex);
  328. }
  329. if (!stream.IsMutuallyAuthenticated)
  330. throw new ConnectionFailureException("SSL Stream Failed to Authenticate", null);
  331. if (!stream.CanWrite)
  332. throw new ConnectionFailureException("SSL Stream is not Writable", null);
  333. networkStream = stream;
  334. }
  335. //Start reading from the stream asynchronously
  336. Reader();
  337. }
  338. }
  339. public class SentNotification
  340. {
  341. public SentNotification(AppleNotification notification)
  342. {
  343. this.Notification = notification;
  344. this.SentAt = DateTime.UtcNow;
  345. this.Identifier = notification.Identifier;
  346. }
  347. public AppleNotification Notification { get; set; }
  348. public DateTime SentAt { get; set; }
  349. public int Identifier { get; set; }
  350. }
  351. }