[個人筆記] 發送推播請求到iOS APNS

  • 880
  • 0
  • Etc
  • 2015-11-23

[個人筆記] 發送推播請求到iOS APNS

        public static DateTime? gExpiration { get; set; }
        public static readonly DateTime gDoNotStore = DateTime.MinValue;
        private static readonly DateTime gUNIX_EPOCH = new DateTime(1970, 1, 1, 0, 0, 0, DateTimeKind.Utc);
        private static string gDeviceToken = "22798c9ea917d5eae1446a6c8efa34d72c70eedfcbf035a7e0cbf3f23e0ded5c";
        private static string gMessage = string.Empty;
        public const int DEVICE_TOKEN_BINARY_SIZE = 32;
        public const int DEVICE_TOKEN_STRING_SIZE = 64;
        public const int MAX_PAYLOAD_SIZE = 256;
        private static X509Certificate gCertificate;
        private static X509CertificateCollection gCertificates;

        /// <summary>
        /// 發出iOS推播要求
        /// </summary>
        /// <param name="pDeviceToken"></param>
        /// <param name="pMessage"></param>
        /// <returns></returns>
        public static string Send_iOS_APNS_Request(string pDeviceToken,string pMessage)
        {
            if (string.IsNullOrWhiteSpace(pDeviceToken) || string.IsNullOrWhiteSpace(pMessage))
                return "必要參數不足";
            else
            {
                gDeviceToken = pDeviceToken.Trim();
                gMessage = pMessage.Trim();
            }
            bool tDevMode = true;
            if (System.Configuration.ConfigurationManager.AppSettings.AllKeys.Contains("DevMode"))
                bool.TryParse(System.Configuration.ConfigurationManager.AppSettings["DevMode"].ToString(), out tDevMode);

            string tResult = string.Empty;
            //APNS主機IP
            string tHostIP = string.Empty;
            //驗證檔位置
            string tCertificatePath = string.Empty;
            //連線Port
            int tPort = 2195;
            //P12加密密碼
            string tPassword = "";

            if (tDevMode)
            {
                tHostIP = "gateway.sandbox.push.apple.com";
                tCertificatePath = System.Configuration.ConfigurationManager.AppSettings["DevP12Path"].ToString();
            }
            else
            {
                tHostIP = "gateway.push.apple.com";
                tCertificatePath = System.Configuration.ConfigurationManager.AppSettings["ProdP12Path"].ToString();
            }

            #region 建立憑證
            string tP12Filename = System.IO.Path.Combine(AppDomain.CurrentDomain.BaseDirectory, tCertificatePath);
            gCertificate = new X509Certificate2(System.IO.File.ReadAllBytes(tP12Filename), tPassword, X509KeyStorageFlags.MachineKeySet | X509KeyStorageFlags.PersistKeySet | X509KeyStorageFlags.Exportable);
            gCertificates = new X509CertificateCollection();
            gCertificates.Add(gCertificate);
            #endregion

            TcpClient tAPNS_Client = new TcpClient();
            try
            {
                tAPNS_Client.Connect(tHostIP, tPort);
            }
            catch(Exception ex)
            {
                return "APNS Connected failed: " + ex.Message;
            }
            SslStream tAPNS_Stream = new SslStream(tAPNS_Client.GetStream(), false, new RemoteCertificateValidationCallback(ValidateServerCertificate), new LocalCertificateSelectionCallback(SelectLocalCertificate));

            try
            {
                //APNs已不支持SSL 3.0 
                tAPNS_Stream.AuthenticateAsClient(tHostIP, gCertificates, System.Security.Authentication.SslProtocols.Tls, false);
            }
            catch (System.Security.Authentication.AuthenticationException ex)
            {
                return "Error: " + ex.Message;
            }
            if (!tAPNS_Stream.IsMutuallyAuthenticated)
            {
                return "Error: SSL Stream Failed to Authenticate!";
            }
            if (!tAPNS_Stream.CanWrite)
            {
                return "Error: SSL Stream is not Writable!";
            }
            try
            {
                //組合封包
                Byte[] tMessage = ToBytes();
                //送出推播封包
                tAPNS_Stream.Write(tMessage);
            }
            catch(Exception ex)
            {
                return "Errow occured when message sending: " + ex.Message;
            }
            return tResult;
        }

        private static byte[] ToBytes()
        {
            // Without reading the response which would make any identifier useful, it seems silly to
            // expose the value in the object model, although that would be easy enough to do. For
            // now we'll just use zero.
            int tIdentifier = 0;
            byte[] tIdentifierBytes = BitConverter.GetBytes(IPAddress.HostToNetworkOrder(tIdentifier));
            // APNS will not store-and-forward a notification with no expiry, so set it one year in the future
            // if the client does not provide it.
            int tExpiryTimeStamp = -1;//過期時間戳記
            if (gExpiration != gDoNotStore)
            {
                //DateTime concreteExpireDateUtc = (Expiration ?? DateTime.UtcNow.AddMonths(1)).ToUniversalTime();
                DateTime concreteExpireDateUtc = (gExpiration ?? DateTime.UtcNow.AddSeconds(20)).ToUniversalTime();
                TimeSpan epochTimeSpan = concreteExpireDateUtc - gUNIX_EPOCH;
                tExpiryTimeStamp = (int)epochTimeSpan.TotalSeconds;
            }

            byte[] tExpiry = BitConverter.GetBytes(IPAddress.HostToNetworkOrder(tExpiryTimeStamp));
            byte[] tDeviceToken = new byte[gDeviceToken.Length / 2];
            for (int i = 0; i < tDeviceToken.Length; i++)
                tDeviceToken[i] = byte.Parse(gDeviceToken.Substring(i * 2, 2), System.Globalization.NumberStyles.HexNumber);
            if (tDeviceToken.Length != DEVICE_TOKEN_BINARY_SIZE)
            {
                throw new Exception("Device token length error!");
            }

            byte[] tDeviceTokenSize = BitConverter.GetBytes(IPAddress.HostToNetworkOrder(Convert.ToInt16(tDeviceToken.Length)));
            string tMessage = "{\"aps\":{\"alert\":\"" + gMessage + "\",\"badge\":1,\"sound\":\"anke.mp3\"}}";
            byte[] tPayload = Encoding.UTF8.GetBytes(tMessage);
            byte[] tPayloadSize = BitConverter.GetBytes(IPAddress.HostToNetworkOrder(Convert.ToInt16(tPayload.Length)));
            List<byte[]> tNotificationParts = new List<byte[]>();
            //1 Command
            tNotificationParts.Add(new byte[] { 0x01 }); // Enhanced notification format command
            tNotificationParts.Add(tIdentifierBytes);
            tNotificationParts.Add(tExpiry);
            tNotificationParts.Add(tDeviceTokenSize);
            tNotificationParts.Add(tDeviceToken);
            tNotificationParts.Add(tPayloadSize);
            tNotificationParts.Add(tPayload);
            return BuildBufferFrom(tNotificationParts);
        }

        private static byte[] BuildBufferFrom(IList<byte[]> pBufferParts)
        {
            int tBufferSize = 0;
            for (int i = 0; i < pBufferParts.Count; i++)
                tBufferSize += pBufferParts[i].Length;
            byte[] tBuffer = new byte[tBufferSize];
            int tPosition = 0;
            for (int i = 0; i < pBufferParts.Count; i++)
            {
                byte[] tPart = pBufferParts[i];
                System.Buffer.BlockCopy(pBufferParts[i], 0, tBuffer, tPosition, tPart.Length);
                tPosition += tPart.Length;
            }
            return tBuffer;
        }

        private static bool ValidateServerCertificate(object sender, X509Certificate certificate, X509Chain chain, SslPolicyErrors sslPolicyErrors)
        {
            return true; // Dont care about server's cert
        }

        private static X509Certificate SelectLocalCertificate(object sender, string targetHost, X509CertificateCollection localCertificates,
         X509Certificate remoteCertificate, string[] acceptableIssuers)
        {
            return gCertificate;
        }