[個人筆記] 發送推播請求到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;
}