PageRenderTime 25ms CodeModel.GetById 21ms RepoModel.GetById 0ms app.codeStats 0ms

/Application/MoonApns/PushNotification.cs

http://github.com/arashnorouzi/Moon-APNS
C# | 409 lines | 305 code | 63 blank | 41 comment | 22 complexity | a2740a4eec6b1dc5541669cec85a18f8 MD5 | raw file
  1. /*Copyright 2011 Arash Norouzi
  2. Licensed under the Apache License, Version 2.0 (the "License");
  3. you may not use this file except in compliance with the License.
  4. You may obtain a copy of the License at
  5. http://www.apache.org/licenses/LICENSE-2.0
  6. Unless required by applicable law or agreed to in writing, software
  7. distributed under the License is distributed on an "AS IS" BASIS,
  8. WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
  9. See the License for the specific language governing permissions and
  10. limitations under the License.
  11. */
  12. using System;
  13. using System.Collections.Generic;
  14. using System.IO;
  15. using System.Linq;
  16. using System.Net.Security;
  17. using System.Net.Sockets;
  18. using System.Security.Cryptography.X509Certificates;
  19. using System.Text;
  20. using System.Threading;
  21. using NLog;
  22. namespace MoonAPNS
  23. {
  24. public class PushNotification
  25. {
  26. private static readonly Logger Logger = LogManager.GetCurrentClassLogger();
  27. private TcpClient _apnsClient;
  28. private SslStream _apnsStream;
  29. private X509Certificate _certificate;
  30. private X509CertificateCollection _certificates;
  31. public string P12File { get; set; }
  32. public string P12FilePassword { get; set; }
  33. // Default configurations for APNS
  34. private const string ProductionHost = "gateway.push.apple.com";
  35. private const string SandboxHost = "gateway.sandbox.push.apple.com";
  36. private const int NotificationPort = 2195;
  37. // Default configurations for Feedback Service
  38. private const string ProductionFeedbackHost = "feedback.push.apple.com";
  39. private const string SandboxFeedbackHost = "feedback.sandbox.push.apple.com";
  40. private const int FeedbackPort = 2196;
  41. private bool _conected = false;
  42. private readonly string _host;
  43. private readonly string _feedbackHost;
  44. private List<NotificationPayload> _notifications = new List<NotificationPayload>();
  45. private List<string> _rejected = new List<string>();
  46. private Dictionary<int, string> _errorList = new Dictionary<int, string>();
  47. public PushNotification(bool useSandbox, string p12File, string p12FilePassword)
  48. {
  49. if (useSandbox)
  50. {
  51. _host = SandboxHost;
  52. _feedbackHost = SandboxFeedbackHost;
  53. }
  54. else
  55. {
  56. _host = ProductionHost;
  57. _feedbackHost = ProductionFeedbackHost;
  58. }
  59. //Load Certificates in to collection.
  60. _certificate = string.IsNullOrEmpty(p12FilePassword)? new X509Certificate2(File.ReadAllBytes(p12File)): new X509Certificate2(File.ReadAllBytes(p12File), p12FilePassword);
  61. _certificates = new X509CertificateCollection {_certificate};
  62. // Loading Apple error response list.
  63. _errorList.Add(0, "No errors encountered");
  64. _errorList.Add(1, "Processing error");
  65. _errorList.Add(2, "Missing device token");
  66. _errorList.Add(3, "Missing topic");
  67. _errorList.Add(4, "Missing payload");
  68. _errorList.Add(5, "Invalid token size");
  69. _errorList.Add(6, "Invalid topic size");
  70. _errorList.Add(7, "Invalid payload size");
  71. _errorList.Add(8, "Invalid token");
  72. _errorList.Add(255, "None (unknown)");
  73. }
  74. public List<string> SendToApple(List<NotificationPayload> queue)
  75. {
  76. Logger.Info("Payload queue received.");
  77. _notifications = queue;
  78. if (queue.Count < 8999)
  79. {
  80. SendQueueToapple(_notifications);
  81. }
  82. else
  83. {
  84. const int pageSize = 8999;
  85. int numberOfPages = (queue.Count / pageSize) + (queue.Count % pageSize == 0 ? 0 : 1);
  86. int currentPage = 0;
  87. while(currentPage < numberOfPages)
  88. {
  89. _notifications = (queue.Skip(currentPage * pageSize).Take(pageSize)).ToList();
  90. SendQueueToapple(_notifications);
  91. currentPage++;
  92. }
  93. }
  94. //Close the connection
  95. Disconnect();
  96. return _rejected;
  97. }
  98. private void SendQueueToapple(IEnumerable<NotificationPayload> queue)
  99. {
  100. int i = 1000;
  101. foreach (var item in queue)
  102. {
  103. if (!_conected)
  104. {
  105. Connect(_host, NotificationPort, _certificates);
  106. var response = new byte[6];
  107. _apnsStream.BeginRead(response, 0, 6, ReadResponse, new MyAsyncInfo(response, _apnsStream));
  108. }
  109. try
  110. {
  111. if (item.DeviceToken.Length == 64) //check lenght of device token, if its shorter or longer stop generating Payload.
  112. {
  113. item.PayloadId = i;
  114. byte[] payload = GeneratePayload(item);
  115. _apnsStream.Write(payload);
  116. Logger.Info("Notification successfully sent to APNS server for Device Toekn : " + item.DeviceToken);
  117. Thread.Sleep(1000); //Wait to get the response from apple.
  118. }
  119. else
  120. Logger.Error("Invalid device token length, possible simulator entry: " + item.DeviceToken);
  121. }
  122. catch (Exception ex)
  123. {
  124. Logger.Error("An error occurred on sending payload for device token {0} - {1}", item.DeviceToken, ex.Message);
  125. _conected = false;
  126. }
  127. i++;
  128. }
  129. }
  130. private void ReadResponse(IAsyncResult ar)
  131. {
  132. if (!_conected)
  133. return;
  134. string payLoadId = "";
  135. int payLoadIndex = 0;
  136. try
  137. {
  138. var info = ar.AsyncState as MyAsyncInfo;
  139. info.MyStream.ReadTimeout = 100;
  140. if (_apnsStream.CanRead)
  141. {
  142. var command = Convert.ToInt16(info.ByteArray[0]);
  143. var status = Convert.ToInt16(info.ByteArray[1]);
  144. var ID = new byte[4];
  145. Array.Copy(info.ByteArray, 2, ID, 0, 4);
  146. payLoadId = Encoding.Default.GetString(ID);
  147. payLoadIndex = ((int.Parse(payLoadId)) - 1000);
  148. Logger.Error("Apple rejected palyload for device token : " + _notifications[payLoadIndex].DeviceToken);
  149. Logger.Error("Apple Error code : " + _errorList[status]);
  150. Logger.Error("Connection terminated by Apple.");
  151. _rejected.Add(_notifications[payLoadIndex].DeviceToken);
  152. _conected = false;
  153. }
  154. }
  155. catch (Exception ex)
  156. {
  157. Logger.Error("An error occurred while reading Apple response for token {0} - {1}", _notifications[payLoadIndex].DeviceToken, ex.Message);
  158. }
  159. }
  160. private void Connect(string host, int port, X509CertificateCollection certificates)
  161. {
  162. Logger.Info("Connecting to apple server.");
  163. try
  164. {
  165. _apnsClient = new TcpClient();
  166. _apnsClient.Connect(host, port);
  167. }
  168. catch (SocketException ex)
  169. {
  170. Logger.Error("An error occurred while connecting to APNS servers - " + ex.Message);
  171. }
  172. var sslOpened = OpenSslStream(host, certificates);
  173. if (sslOpened)
  174. {
  175. _conected = true;
  176. Logger.Info("Conected.");
  177. }
  178. }
  179. private void Disconnect()
  180. {
  181. try
  182. {
  183. Thread.Sleep(500);
  184. _apnsClient.Close();
  185. _apnsStream.Close();
  186. _apnsStream.Dispose();
  187. _apnsStream = null;
  188. _conected = false;
  189. Logger.Info("Disconnected.");
  190. }
  191. catch (Exception ex)
  192. {
  193. Logger.Error("An error occurred while disconnecting. - " + ex.Message);
  194. }
  195. }
  196. private bool OpenSslStream(string host, X509CertificateCollection certificates)
  197. {
  198. Logger.Info("Creating SSL connection.");
  199. _apnsStream = new SslStream(_apnsClient.GetStream(), false, validateServerCertificate, SelectLocalCertificate);
  200. try
  201. {
  202. _apnsStream.AuthenticateAsClient(host, certificates, System.Security.Authentication.SslProtocols.Ssl3, false);
  203. }
  204. catch (System.Security.Authentication.AuthenticationException ex)
  205. {
  206. Logger.Error(ex.Message);
  207. return false;
  208. }
  209. if (!_apnsStream.IsMutuallyAuthenticated)
  210. {
  211. Logger.Error("SSL Stream Failed to Authenticate");
  212. return false;
  213. }
  214. if (!_apnsStream.CanWrite)
  215. {
  216. Logger.Error("SSL Stream is not Writable");
  217. return false;
  218. }
  219. return true;
  220. }
  221. private bool validateServerCertificate(object sender, X509Certificate certificate, X509Chain chain, SslPolicyErrors sslPolicyErrors)
  222. {
  223. return true; // Dont care about server's cert
  224. }
  225. private X509Certificate SelectLocalCertificate(object sender, string targetHost, X509CertificateCollection localCertificates, X509Certificate remoteCertificate, string[] acceptableIssuers)
  226. {
  227. return _certificate;
  228. }
  229. private static byte[] GeneratePayload(NotificationPayload payload)
  230. {
  231. try
  232. {
  233. //convert Devide token to HEX value.
  234. byte[] deviceToken = new byte[payload.DeviceToken.Length / 2];
  235. for (int i = 0; i < deviceToken.Length; i++)
  236. deviceToken[i] = byte.Parse(payload.DeviceToken.Substring(i * 2, 2), System.Globalization.NumberStyles.HexNumber);
  237. var memoryStream = new MemoryStream();
  238. // Command
  239. memoryStream.WriteByte(1); // Changed command Type
  240. //Adding ID to Payload
  241. memoryStream.Write(Encoding.ASCII.GetBytes(payload.PayloadId.ToString()), 0, payload.PayloadId.ToString().Length);
  242. //Adding ExpiryDate to Payload
  243. int epoch = (int) (DateTime.UtcNow.AddMinutes(300) - new DateTime(1970, 1, 1)).TotalSeconds;
  244. byte[] timeStamp = BitConverter.GetBytes(epoch);
  245. memoryStream.Write(timeStamp, 0, timeStamp.Length);
  246. byte[] tokenLength = BitConverter.GetBytes((Int16) 32);
  247. Array.Reverse(tokenLength);
  248. // device token length
  249. memoryStream.Write(tokenLength, 0, 2);
  250. // Token
  251. memoryStream.Write(deviceToken, 0, 32);
  252. // String length
  253. string apnMessage = payload.ToJson();
  254. Logger.Info("Payload generated for " + payload.DeviceToken + " : " + apnMessage);
  255. byte[] apnMessageLength = BitConverter.GetBytes((Int16) apnMessage.Length);
  256. Array.Reverse(apnMessageLength);
  257. // message length
  258. memoryStream.Write(apnMessageLength, 0, 2);
  259. // Write the message
  260. memoryStream.Write(Encoding.ASCII.GetBytes(apnMessage), 0, apnMessage.Length);
  261. return memoryStream.ToArray();
  262. }
  263. catch (Exception ex)
  264. {
  265. Logger.Error("Unable to generate payload - " + ex.Message);
  266. return null;
  267. }
  268. }
  269. public List<Feedback> GetFeedBack()
  270. {
  271. try
  272. {
  273. var feedbacks = new List<Feedback>();
  274. Logger.Info("Connecting to feedback service.");
  275. if (!_conected)
  276. Connect(_feedbackHost, FeedbackPort, _certificates);
  277. if (_conected)
  278. {
  279. //Set up
  280. byte[] buffer = new byte[38];
  281. int recd = 0;
  282. DateTime minTimestamp = DateTime.Now.AddYears(-1);
  283. //Get the first feedback
  284. recd = _apnsStream.Read(buffer, 0, buffer.Length);
  285. Logger.Info("Feedback response received.");
  286. if (recd == 0)
  287. Logger.Info("Feedback response is empty.");
  288. //Continue while we have results and are not disposing
  289. while (recd > 0)
  290. {
  291. Logger.Info("processing feedback response");
  292. var fb = new Feedback();
  293. //Get our seconds since 1970 ?
  294. byte[] bSeconds = new byte[4];
  295. byte[] bDeviceToken = new byte[32];
  296. Array.Copy(buffer, 0, bSeconds, 0, 4);
  297. //Check endianness
  298. if (BitConverter.IsLittleEndian)
  299. Array.Reverse(bSeconds);
  300. int tSeconds = BitConverter.ToInt32(bSeconds, 0);
  301. //Add seconds since 1970 to that date, in UTC and then get it locally
  302. fb.Timestamp = new DateTime(1970, 1, 1, 0, 0, 0, DateTimeKind.Utc).AddSeconds(tSeconds).ToLocalTime();
  303. //Now copy out the device token
  304. Array.Copy(buffer, 6, bDeviceToken, 0, 32);
  305. fb.DeviceToken = BitConverter.ToString(bDeviceToken).Replace("-", "").ToLower().Trim();
  306. //Make sure we have a good feedback tuple
  307. if (fb.DeviceToken.Length == 64 && fb.Timestamp > minTimestamp)
  308. {
  309. //Raise event
  310. //this.Feedback(this, fb);
  311. feedbacks.Add(fb);
  312. }
  313. //Clear our array to reuse it
  314. Array.Clear(buffer, 0, buffer.Length);
  315. //Read the next feedback
  316. recd = _apnsStream.Read(buffer, 0, buffer.Length);
  317. }
  318. //clode the connection here !
  319. Disconnect();
  320. if (feedbacks.Count > 0)
  321. Logger.Info("Total {0} feedbacks received.", feedbacks.Count);
  322. return feedbacks;
  323. }
  324. }
  325. catch (Exception ex)
  326. {
  327. Logger.Error("Error occurred on receiving feed back. - " + ex.Message);
  328. return null;
  329. }
  330. return null;
  331. }
  332. }
  333. public class MyAsyncInfo
  334. {
  335. public Byte[] ByteArray { get; set; }
  336. public SslStream MyStream { get; set; }
  337. public MyAsyncInfo(Byte[] array, SslStream stream)
  338. {
  339. ByteArray = array;
  340. MyStream = stream;
  341. }
  342. }
  343. }