Recipe: Sending Notifications
The notification process involves several steps (see Figure 16-12). First, you build your JSON payload, which you just read about in the previous section. Next, you retrieve the SSL certificate and the device token for the unit you want to send to. How you store these is left up to you, but you must remember that these are sensitive pieces of information. Open a secure connection to the APNS server. Finally, you handshake with the server, send the notification package, and close the connection.
Figure 16-12 The steps for sending remote notifications.
This is the most basic way of communicating and assumes you have just one payload to send. In fact, you can establish a session and send many packets at a time; however, that is left as an exercise for the reader as is creating services in languages other than Objective-C. The Apple Developer Forums (devforums.apple.com) host ongoing discussions about push providers and offer an excellent jumping off point for finding sample code for PHP, Perl, and other languages.
Be aware that APNS may react badly to a rapid series of connections that are repeatedly established and torn down. If you have multiple notifications to send at once, go ahead and send them during a single session. Otherwise, APNS might confuse your push deliveries with a denial of service attack.
Recipe 16-2 demonstrates how to send a single payload to APNS, showing the steps needed to implement the fourth and final box in Figure 16-12. The recipe is built around code developed by Stefan Hafeneger and uses Apple's ioSock sample source code.
Recipe 16-2. Pushing Payloads to the APNS Server
// Adapted from code by Stefan Hafeneger - (BOOL) push: (NSString *) payload { otSocket socket; SSLContextRef context; SecKeychainRef keychain; SecIdentityRef identity; SecCertificateRef certificate; OSStatus result; // Ensure device token if (!self.deviceTokenID) { printf("Error: Device Token is nil\n"); return NO; } // Ensure certificate if (!self.certificateData) { printf("Error: Certificate Data is nil\n"); return NO; } // Establish connection to server. PeerSpec peer; result = MakeServerConnection("gateway.sandbox.push.apple.com", 2195, &socket, &peer); if (result) { printf("Error creating server connection\n"); return NO; } // Create new SSL context. result = SSLNewContext(false, &context); if (result) { printf("Error creating SSL context\n"); return NO; } // Set callback functions for SSL context. result = SSLSetIOFuncs(context, SocketRead, SocketWrite); if (result) { printf("Error setting SSL context callback functions\n"); return NO; } // Set SSL context connection. result = SSLSetConnection(context, socket); if (result) { printf("Error setting the SSL context connection\n"); return NO; } // Set server domain name. result = SSLSetPeerDomainName(context, "gateway.sandbox.push.apple.com", 30); if (result) { printf("Error setting the server domain name\n"); return NO; } // Open keychain. result = SecKeychainCopyDefault(&keychain); if (result) { printf("Error accessing keychain\n"); return NO; } // Create certificate from data CSSM_DATA data; data.Data = (uint8 *)[self.certificateData bytes]; data.Length = [self.certificateData length]; result = SecCertificateCreateFromData(&data, CSSM_CERT_X_509v3, CSSM_CERT_ENCODING_BER, &certificate); if (result) { printf("Error creating certificate from data\n"); return NO; } // Create identity. result = SecIdentityCreateWithCertificate(keychain, certificate, &identity); if (result) { printf("Error creating identity from certificate\n"); return NO; } // Set client certificate. CFArrayRef certificates = CFArrayCreate(NULL, (const void **)&identity, 1, NULL); result = SSLSetCertificate(context, certificates); if (result) { printf("Error setting the client certificate\n"); return NO; } CFRelease(certificates); // Perform SSL handshake. do {result = SSLHandshake(context);} while(result == errSSLWouldBlock); // Convert string into device token data. NSMutableData *deviceToken = [NSMutableData data]; unsigned value; NSScanner *scanner = [NSScanner scannerWithString:self.deviceTokenID]; while(![scanner isAtEnd]) { [scanner scanHexInt:&value]; value = htonl(value); [deviceToken appendBytes:&value length:sizeof(value)]; } // Create C input variables. char *deviceTokenBinary = (char *)[deviceToken bytes]; char *payloadBinary = (char *)[payload UTF8String]; size_t payloadLength = strlen(payloadBinary); // Prepare message uint8_t command = 0; char message[293]; char *pointer = message; uint16_t networkTokenLength = htons(32); uint16_t networkPayloadLength = htons(payloadLength); // Compose message. memcpy(pointer, &command, sizeof(uint8_t)); pointer += sizeof(uint8_t); memcpy(pointer, &networkTokenLength, sizeof(uint16_t)); pointer += sizeof(uint16_t); memcpy(pointer, deviceTokenBinary, 32); pointer += 32; memcpy(pointer, &networkPayloadLength, sizeof(uint16_t)); pointer += sizeof(uint16_t); memcpy(pointer, payloadBinary, payloadLength); pointer += payloadLength; // Send message over SSL. size_t processed = 0; result = SSLWrite(context, &message, (pointer - message), &processed); if (result) { printf("Error sending message via SSL.\n"); return NO; } else { printf("Message sent.\n"); return YES; } }
The individual server setups vary greatly depending on your security, databases, organization, and programming language. Recipe 16-2 demonstrates a minimum of what is required to implement this functionality and serves as a template for your own server implementation in whatever form this might take.
Sandbox and Production
Apple provides both sandbox (development) and production (distribution) environments for push notification. You must create separate SSL certificates for each. The sandbox helps you develop and test your application before submitting to App Store. It works with a smaller set of servers and is not meant for large-scale testing. The production system is reserved for deployed applications that have been accepted to App Store.
- The Sandbox servers are located at gateway.sandbox.push.apple.com, port 2195.
- The Production servers are located at gateway.push.apple.com, port 2195.