13

Apple really had bad documentation about how the provider connects and communicates to their service (at the time of writing - 2009). I am confused about the protocol. How is this done in C#?

Cœur
  • 37,241
  • 25
  • 195
  • 267
Phobis
  • 7,524
  • 10
  • 47
  • 76
  • 1
    In my opinion, Apple's documentation is pretty clear: http://developer.apple.com/iphone/library/documentation/NetworkingInternet/Conceptual/RemoteNotificationsPG/CommunicatingWIthAPS/CommunicatingWIthAPS.html#//apple_ref/doc/uid/TP40008194-CH101-SW1 – Linulin Aug 17 '09 at 20:19
  • This answer was chosen a long time ago. Also look at the answer from shay - http://stackoverflow.com/a/36963561/19854 If you are looking for a manual way to write this code then look at the originally chosen answer: http://stackoverflow.com/a/1077664/19854 – Phobis Jun 06 '16 at 04:21

5 Answers5

24

Working code example:

int port = 2195;
String hostname = "gateway.sandbox.push.apple.com";

//load certificate
string certificatePath = @"cert.p12";
string certificatePassword = "";
X509Certificate2 clientCertificate = new X509Certificate2(certificatePath, certificatePassword);
X509Certificate2Collection certificatesCollection = new X509Certificate2Collection(clientCertificate);

TcpClient client = new TcpClient(hostname, port);
SslStream sslStream = new SslStream(
        client.GetStream(),
        false,
        new RemoteCertificateValidationCallback(ValidateServerCertificate),
        null
);

try
{
    sslStream.AuthenticateAsClient(hostname, certificatesCollection, SslProtocols.Tls, true);
}
catch (AuthenticationException ex)
{
    client.Close();
    return;
}

// Encode a test message into a byte array.
MemoryStream memoryStream = new MemoryStream();
BinaryWriter writer = new BinaryWriter(memoryStream);

writer.Write((byte)0);  //The command
writer.Write((byte)0);  //The first byte of the deviceId length (big-endian first byte)
writer.Write((byte)32); //The deviceId length (big-endian second byte)

String deviceId = "DEVICEIDGOESHERE";
writer.Write(ToByteArray(deviceId.ToUpper()));

String payload = "{\"aps\":{\"alert\":\"I like spoons also\",\"badge\":14}}";

writer.Write((byte)0); //First byte of payload length; (big-endian first byte)
writer.Write((byte)payload.Length); //payload length (big-endian second byte)

byte[] b1 = System.Text.Encoding.UTF8.GetBytes(payload);
writer.Write(b1);
writer.Flush();

byte[] array = memoryStream.ToArray();
sslStream.Write(array);
sslStream.Flush();

// Close the client connection.
client.Close();
Nate
  • 31,017
  • 13
  • 83
  • 207
shader
  • 2,121
  • 1
  • 13
  • 20
  • As another option is a free push service (for those ones who don't want to write the code): [http://www.pushwoosh.com/](http://www.pushwoosh.com/) – shader Aug 30 '11 at 11:23
  • The original x-cake link is dead... here's the archive version. https://web.archive.org/web/20130719213517/http://x-cake.ning.com/profiles/blogs/setting-up-a-push-notification – BIBD Mar 01 '16 at 20:53
  • How to get response from APNS ? does message sent or fail? – Hina Khuman Aug 01 '17 at 07:03
6

I hope this is relevant (slightly), but I have just successfully created one for Java, so conceptually quite similar to C# (except perhaps the SSL stuff, but that shouldn't be too hard to modify. Below is a sample message payload and crypto setup:

    int port = 2195;
    String hostname = "gateway.sandbox.push.apple.com";


    char []passwKey = "<keystorePassword>".toCharArray();
    KeyStore ts = KeyStore.getInstance("PKCS12");
    ts.load(new FileInputStream("/path/to/apn_keystore/cert.p12"), passwKey);

    KeyManagerFactory tmf = KeyManagerFactory.getInstance("SunX509");
    tmf.init(ts,passwKey);
    SSLContext sslContext = SSLContext.getInstance("TLS");
    sslContext.init(tmf.getKeyManagers(), null, null);
    SSLSocketFactory factory =sslContext.getSocketFactory();
    SSLSocket socket = (SSLSocket) factory.createSocket(hostname,port); // Create the ServerSocket
    String[] suites = socket.getSupportedCipherSuites();
    socket.setEnabledCipherSuites(suites);
    //start handshake

    socket.startHandshake();


    // Create streams to securely send and receive data to the server
    InputStream in = socket.getInputStream();
    OutputStream out = socket.getOutputStream();



    // Read from in and write to out...
    ByteArrayOutputStream baos = new ByteArrayOutputStream();
    baos.write(0); //The command
    System.out.println("First byte Current size: " + baos.size());

    baos.write(0); //The first byte of the deviceId length    
    baos.write(32); //The deviceId length

    System.out.println("Second byte Current size: " + baos.size());

    String deviceId = "<heaxdecimal representation of deviceId";
    baos.write(hexStringToByteArray(deviceId.toUpperCase()));
    System.out.println("Device ID: Current size: " + baos.size());


    String payload = "{\"aps\":{\"alert\":\"I like spoons also\",\"badge\":14}}";
    System.out.println("Sending payload: " + payload);
    baos.write(0); //First byte of payload length;
    baos.write(payload.length());
    baos.write(payload.getBytes());

    out.write(baos.toByteArray());
    out.flush();

    System.out.println("Closing socket..");
    // Close the socket
    in.close();
    out.close();

}

Once again, not C#, but at least closer than the poor ObjC sample that Apple provides.

Alex Taylor
  • 7,128
  • 2
  • 26
  • 22
  • I am still trying to get this to work. I have duplicated your code in C#, but since .NET uses a different kind of object to connect via SSL "SSLStream" it doesn't have a "handshake" method. I can't seem to figure out how to get the proper handshake to occur. – Phobis Jun 21 '09 at 00:10
  • Look here: http://msdn.microsoft.com/en-us/library/system.net.security.sslstream.aspx (about three quarters of the page down, there is a C# SSL client handshake example, using the SSLStream object. Seems to be the way to do it, by the look of it (a callback) – Alex Taylor Jun 21 '09 at 00:43
  • This is wrong. It's not creating a server socket. It's creating a client socket. ts and tmf are sometimes used to refer to "trust store" and "trust manager factory," but here they refer to the client key material... really strange. – erickson Jun 21 '09 at 04:26
  • OK, I think I got it to work. I will post the code, once I can verify it working. One issue though. I connect and send the payload and then read the server response (which is - no response) And I get no push to my phone. Is there something I am missing (my app already registered the push service) I am using the sandbox, is this the issue? – Phobis Jun 21 '09 at 05:38
  • 1
    Does your app show up under 'notifications' on the device? If not, you probably need to regenerate your certificates and redeploy your app - that one got me for a while.. Also, make sure that you are *absolutely* right, byte-for-byte, in what you are transmitting (see above) . If you have it wrong, it will not work at all. Also - in that MS document on SSLStream, about 1/2 to 3/4 of the way down, it deals with Client->Server connections. – Alex Taylor Jun 21 '09 at 06:49
  • My app does show in the 'notifications' section of my phone. I have verified it about 5 times (byte for byte)... but as we all know it never hurts to look again (I am hoping I missed something) – Phobis Jun 21 '09 at 15:45
  • Hi Phobis, I am having same problem as yours. I have also registered my app and using sandbox. Using sandbox environment, there will be a virtual device. then how can we connect a phone and check? As Chaos said, how to check the notification which i am is correct? - dsk – saikamesh Sep 04 '09 at 07:36
2

The best APNSSharp project available on Github. It worked for me absolutely fine in just couple of minutes!

Paresh Masani
  • 7,474
  • 12
  • 73
  • 139
1

You can use the PushSharp library in GitHub.

I am using it in all of my projects

shay
  • 2,021
  • 1
  • 15
  • 17
0
  public ActionResult ios()
    {
        string message = string.Empty;

        var certi = Path.Combine(AppDomain.CurrentDomain.BaseDirectory, "Certificates2.p12");
        var appleCert = System.IO.File.ReadAllBytes(Path.Combine(AppDomain.CurrentDomain.BaseDirectory, "Certificates2.p12"));

        ApnsConfiguration apnsConfig = new ApnsConfiguration(ApnsConfiguration.ApnsServerEnvironment.Production, appleCert, "Password");

        var apnsBroker = new ApnsServiceBroker(apnsConfig);
        apnsBroker.OnNotificationFailed += (notification, aggregateEx) =>
        {
            aggregateEx.Handle(ex =>
            {
                if (ex is ApnsNotificationException)
                {
                    var notificationException = (ApnsNotificationException)ex;
                    var apnsNotification = notificationException.Notification;
                    var statusCode = notificationException.ErrorStatusCode;
                    var inner = notificationException.InnerException;
                    message = "IOS Push Notifications: Apple Notification Failed: ID=" + apnsNotification.Identifier + ", Code=" + statusCode + ", Inner Exception" + inner;
                }
                else
                {
                    message = "IOS Push Notifications: Apple Notification Failed for some unknown reason : " + ex.InnerException;
                }
                return true;
            });
        };

        apnsBroker.OnNotificationSucceeded += (notification) =>
        {
            message = "IOS Push Notifications: Apple Notification Sent!";
        };

        apnsBroker.Start();
        try
        {
            string deviceToken = "33c2f3a13c90dc62660281913377c22066c1567e23c2ee2c728e0f936ff3ee9b";

            apnsBroker.QueueNotification(new ApnsNotification
            {
                DeviceToken = deviceToken,
                Payload = JObject.Parse("{\"aps\":{\"alert\":\" Test Message\", \"badge\":1, \"sound\":\" default\",}}")
            });
        }
        catch (Exception ex)
        {
            Console.Write(ex);
        }
        apnsBroker.Stop();

        return View(message);
    }
  • While this code may answer the question, it would be better to explain how it solves the problem without introducing others and why to use it. Code-only answers are not useful in the long run. – Mateus Jul 29 '17 at 11:33