PushNotification in C#,Objective-c/C
PushNotification
A simplest way to push notification on your own server in C# and C/Objective-C.
APNs Overview
Apple Push Notification service (APNs) is the centerpiece of the remote notifications feature. It is a robust and highly efficient service for propagating information to iOS (and, indirectly, WatchOS), tvOS, and macOS devices. On initial launch, your app establishes an accredited and encrypted IP connection with APNs from the user’s device. Over time, APNs delivers notifications using this persistent connection. If a notification arrives when your app is not running. the device receives the notification and handles its delivery to your app at an appropriate time.
In addition to APNs and your app, another piece is required for the delivery of remote notifications. You must configure your own server to originate those notifications. Your server, known as the provider, has the following responsibilities:
- It receives device tokens and relevant data from your app.
- It determines when remote notifications need to be sent to a device.
- It communicates the notification data to APNs, which then handles the delivery of the notifications to that device.
For each remote notification, your provider:
- Constructs a JSON dictionary with the notification’s payload; described in the Remote Notification Payload.
- Attaches the payload and an appropriate device token to an HTTP/2 request.
- Sends the request to APNs over a persistent and secure channel that uses the HTTP/2 network protocol.
What can this repository do?
- Provide the simplest way to send remote notification to user’s device in C#
- Using the most simple code in C#/iOS , a newcomer can be able to understood
- In order to test your iOS app when you’re an iOS developer
- In order to write the code of server end when you’re an ASP.NET/C# developer
For more details, linked to here.
Introduction
Let me introduce the server end(C#) at first.
See the flow briefly firstly.
- 1.Using
TcpClient
andSslStream
classes to connect with Apple Server - 2.Setting certificate through
AuthenticateAsClient()
method within a passphrase, this method involves setting basic parameters and handshaking - 3.
Write()
for writing data bytes to APNs before splicing a whole string of payload
- Here is the simplest
payload
of a remote notification1
{"aps":{"alert":"This is a message for testing APNs","badge":123,"sound":"default"}}
These header files should be referenced1
2
3
4
5
6
7
8using System;
using System.IO;
using System.Net.Security;
using System.Net.Sockets;
using System.Security.Authentication;
using System.Security.Cryptography.X509Certificates;
using System.Text;
using System.Threading;
Reading the p12 file which is downloaded from Apple Developer Website, the varieble certFilePath is your p12 certificate whole path and the varieble certPwd is the passphrase of certificate, code snippet below
1
2
3X509Certificate2 cert = new X509Certificate2(certFilePath, certPwd);
X509CertificateCollection certificate = new X509CertificateCollection();
certificate.Add(cert);Then, create an instance of SslStream and handshake before passing host address and port, code snippet below
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31//For distribution mode, the host is gateway.push.apple.com
//For development mode, the host is gateway.sandbox.push.apple.com
TcpClient client = new TcpClient("gateway.push.apple.com", 2195);
SslStream sslStream = new SslStream(client.GetStream(), false, new RemoteCertificateValidationCallback(ServerCertificateValidationCallback), null);
//The method AuthenticateAsClient() may cause an exception, so we need to try..catch.. it
try
{
//Reference of SslStream
//https://msdn.microsoft.com/en-us/library/system.net.security.sslstream(v=vs.110).aspx?cs-save-lang=1&cs-lang=csharp#code-snippet-2
sslStream.AuthenticateAsClient(_host, certificate, SslProtocols.Default, false);
}
catch (Exception e)
{
Console.WriteLine("Exception Message: {0} ", e.Message);
sslStream.Close();
}
//Obviously , this is a method of callback when handshaking
bool ServerCertificateValidationCallback(object sender, X509Certificate certificate, X509Chain chain, SslPolicyErrors sslPolicyErrors)
{
if (sslPolicyErrors == SslPolicyErrors.None)
{
Console.WriteLine("Specified Certificate is accepted.");
return true;
}
Console.WriteLine("Certificate error : {0} ", sslPolicyErrors);
return false;
}Push a remote notification before consisting of the Payload string
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56//This is definition of PushNotificationPayload of struct
public struct PushNotificationPayload
{
public string deviceToken;
public string message;
public string sound;
public int badge;
public string PushPayload()
{
return "{\"aps\":{\"alert\":\"" + message + "\",\"badge\":" + badge + ",\"sound\":\"" + sound + "\"}}";
}
}
//We gave values to it to consisting of the payload content
PushNotificationPayload payload = new PushNotificationPayload();
payload.deviceToken = "dc67b56c eb5dd9f9 782c37fd cfdcca87 3b7bc77c 3b090ac4 c538e007 a2f23a24";
payload.badge = 56789;
payload.sound = "default";
payload.message = "This message was pushed by C# platform.";
//And then, calling Push() method to invoke it
public void Push(PushNotificationPayload payload)
{
string payloadStr = payload.PushPayload();
string deviceToken = payload.deviceToken;
MemoryStream memoryStream = new MemoryStream();
BinaryWriter writer = new BinaryWriter(memoryStream);
writer.Write((byte)0); //The command
writer.Write((byte)0); //The first byte of deviceId length (Big-endian first byte)
writer.Write((byte)32); //The deviceId length (Big-endian second type)
//Method of DataWithDeviceToken() , see source code in this repo.
byte[] deviceTokenBytes = DataWithDeviceToken(deviceToken.ToUpper());
writer.Write(deviceTokenBytes);
writer.Write((byte)0); //The first byte of payload length (Big-endian first byte)
writer.Write((byte)payloadStr.Length); //payload length (Big-endian second byte)
byte[] bytes = Encoding.UTF8.GetBytes(payloadStr);
writer.Write(bytes);
writer.Flush();
_sslStream.Write(memoryStream.ToArray());
_sslStream.Flush();
Thread.Sleep(3000);
//Method of ReadMessage() , see source code in this repo.
string result = ReadMessage(_sslStream);
Console.WriteLine("server said: " + result);
_sslStream.Close();
}
Secondly, let me introduce the client side written by C/Objective-C.
See the flow briefly firstly.
- 1.Using
socket()
to connect with Apple Server - 2.Setting certificate through
SSLSetCertificate()
function within a passphrase - 3.
SSLHandshake()
with Apple Push Notification server - 4.
SSLWrite()
for writing data bytes to APNs before splicing a whole string of payload
- Here is the simplest
payload
of a remote notification1
{"aps":{"alert":"This is a message for testing APNs","badge":123,"sound":"default"}}
These header files should be referenced1
2
3
4#include <Security/SecureTransport.h>
#include <unistd.h>
#include <netdb.h>
#include <fcntl.h>
Step 1
1 | bool connectSocket() |
Step 2
Setting parameters, passphrase and certificate to SSLContextRef
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32bool connectSSLWithCertificate(NSString * certificateFilePath, NSString * certificatePasswords)
{
SSLContextRef context = SSLCreateContext(NULL, kSSLClientSide, kSSLStreamType);
if (!context) {
printf("SSLContextRef creation failed! \n");
return false;
}
OSStatus setio = SSLSetIOFuncs(context, VZSSLRead, VZSSLWrite);
if (setio != errSecSuccess) {
printf("OSStatus set failed! \n");
return false;
}
OSStatus setconn = SSLSetConnection(context, (SSLConnectionRef)(unsigned long)_socket);
if (setconn != errSecSuccess) {
printf("SSLSetConnection() function failed! \n");
return false;
}
OSStatus setpeer = SSLSetPeerDomainName(context, _host, strlen(_host));
if (setpeer != errSecSuccess) {
printf("SSLSetPeerDomainName() function failed! \n");
return false;
}
id certificate = importPKCS12Data(certificateFilePath, certificatePasswords);
OSStatus setcert = SSLSetCertificate(context, (__bridge CFArrayRef)@[certificate]);
if (setcert != errSecSuccess) {
printf("SSLSetCertificate() function failed! \n");
return false;
}
_context = context;
return true;
}
Step 3
Handshaking1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35bool handshakeSSL()
{
OSStatus status = errSSLWouldBlock;
for (int i = 0; i < NWSSL_HANDSHAKE_TRY_COUNT && status == errSSLWouldBlock; i++) {
status = SSLHandshake(_context);
}
bool result = false;
switch (status) {
case errSecSuccess: {
printf("SSLHandshake() success! \n");
result = true;
}
break;
case errSSLWouldBlock:
case errSecIO:
case errSecAuthFailed:
case errSSLUnknownRootCert:
case errSSLNoRootCert:
case errSSLCertExpired:
case errSSLXCertChainInvalid:
case errSSLClientCertRequested:
case errSSLServerAuthCompleted:
case errSSLPeerCertExpired:
case errSSLPeerCertRevoked:
case errSSLPeerCertUnknown:
case errSecInDarkWake:
case errSSLClosedAbort: {
printf("SSLHandshake failed! Failure code = %d \n", status);
result = false;
}
break;
}
return result;
}
Step 4
Writting data to APNs server1
2
3
4
5
6
7
8
9
10
11
12int writePushData(NSData *data, NSUInteger *length)
{
*length = 0;
size_t processed = 0;
OSStatus status = SSLWrite(_context, data.bytes, data.length, &processed);
*length = processed;
if (status == errSecSuccess) {
NSLog(@"%@", securityErrorMessageString(status));
return errSecSuccess;
}
return status;
}