jessn jessn - 6 months ago 24
iOS Question

APNs messages are delivered but not received on iOS device

I have set up a forwarding / dispatch service which holds an open connection to the APNs as stated / recommended by Apple to prevent DoS detection and the certificate is working...

I am connected successfully and messages are sent successfully, but messages are not received on the iOS device (iPhone)... (?!?) The number of bytes sent are equal to the number (of bytes) fwrite returns.

Finally, the messages might contain country specific characters. How the the APNs handle that? and how should this characters be sent to the APNs server?

I have tried both with and without the forwarding service, but without luck. :-(

All calls to the log are removed in the code snippets to avoid confusing. This means that the log messages pasted cannot be mapped directly into the code snippets, but it should be possible to "guess" from where log messages come.

Please help me regarding these issues.
Thanks in advance

The connection is made in this way:

$ctx = stream_context_create();
stream_context_set_option($ctx, 'ssl', 'local_cert', $certPath);
stream_context_set_option($ctx, 'ssl', 'passphrase', $passPhrase);
$this->apn_link = stream_socket_client($url, $err, $errstr, 60, STREAM_CLIENT_CONNECT | STREAM_CLIENT_PERSISTENT, $ctx);
/* ... */


Inside a loop that listens on the socket on the forwarding service:

foreach ($changed as $changed_socket) {
while(isset($changed_socket) && socket_recv($changed_socket, $buf, 1024, 0) >= 1)
{
$buf = trim($buf);
if ($buf != '') {
$this->msg2apn($buf);
}
}
}
/* ... */


Further I send messages in this way:

public function msg2apn($msg) {
if (isset($this->apn_link)) {
while(!$result = fwrite($this->apn_link, $msg, strlen($msg))) {
usleep(400000); //400 msec
}

if ($result) {
fflush($this->apn_link);
return True;
}
}
}


Messages sent to the forwarding service:

private function send_pn_iosproxy($msg) {
$address = IOS_APNPROXY_HOST;
$port = IOS_APNPROXY_PORT;
$fp = fsockopen($address,$port);
if (!$fp) {
elog("Connection to [".$address.":".$port."] could not be established");
return False;
}
$result = fwrite($fp, $msg, strlen($msg));

if (isset($fp)) fflush($fp);
if (isset($fp)) fclose($fp);
return True;
}


For each device token a message is generated in the following way:

$payload = '{"aps":{"alert":"'.$message['message'].'","badge" : "1","sound":"default"}}';

foreach($deviceTokens as $deviceToken) {
// Build the binary notification
$msg = chr(0) . pack('n', 32) . base64_decode($deviceToken) . pack('n', strlen($payload)) . $payload;

// see function above
$result = $this->send_pn_iosproxy($msg);

if ($result) {
ilog("Message delivered");
dlog("DeviceToken[$deviceToken], Payload[$payload]");
} else {
wlog("Message not properly delivered");
dlog("Msg[$msg]");
dlog("DeviceToken[$deviceToken], Payload[$payload]");
}
}


Log messages from logdisp are from the forwarding service, while log messages from the log are from the app-/web server. The web server connects to the forwarding service using 127.0.0.1/localhost hence the web server and the forwarding server is hosted on the same physical computer.

[2015-08-27 11:19:03] log.DEBUG: pn::send_pn_ios() [] []
[2015-08-27 11:19:03] log.DEBUG: pn::send_pn_iosproxy() [] []
[2015-08-27 11:19:03] log.INFO: Message delivered [] []
[2015-08-27 11:19:03] logdisp.DEBUG: Client [127.0.0.1] connected [] []
[2015-08-27 11:19:03] log.DEBUG: DeviceToken[djYBbJepT8NOPKgLd5FoGOog9jxY8LcpQhCGaXAkqy0=], Payload[{"aps":{"alert":"message","badge" : "1","sound":"default"}}] [] []
[2015-08-27 11:19:03] logdisp.DEBUG: Message buf[...{"aps":{"alert":"message","badge" : "1","sound":"default"}}] from ip[127.0.0.1] [] []
[2015-08-27 11:19:03] log.DEBUG: pn::send_pn_iosproxy() [] []
[2015-08-27 11:19:03] logdisp.DEBUG: dispatcher::msg2apn() [] []
[2015-08-27 11:19:03] log.INFO: Message delivered [] []
[2015-08-27 11:19:03] logdisp.DEBUG: SUCCESS msg2apn, written[107], sent[107] [] []
[2015-08-27 11:19:03] log.DEBUG: DeviceToken[7FH3vRMfmb3qRBOaKY30GT/+82jU/6kx7RhBJq2Ihw8=], Payload[{"aps":{"alert":"message","badge" : "1","sound":"default"}}] [] []
[2015-08-27 11:19:03] log.DEBUG: pn::send_pn_iosproxy() [] []
[2015-08-27 11:19:03] log.INFO: Message delivered [] []
[2015-08-27 11:19:03] log.DEBUG: DeviceToken[b+20wASQBp9lD08l1+EY3CHJjcCl1UBWNmSeI1c9ouQ=], Payload[{"aps":{"alert":"message","badge" : "1","sound":"default"}}] [] []
[2015-08-27 11:19:03] log.DEBUG: pn::send_pn_iosproxy() [] []
[2015-08-27 11:19:03] log.INFO: Message delivered [] []
[2015-08-27 11:19:03] log.DEBUG: DeviceToken[Yya0lH080lz6fL2B/1UexOenBLyAJIp3zgSGgV9F5ms=], Payload[{"aps":{"alert":"message","badge" : "1","sound":"default"}}] [] []
[2015-08-27 11:19:04] logdisp.DEBUG: Client [127.0.0.1] connected [] []
[2015-08-27 11:19:04] logdisp.DEBUG: Message buf[...{"aps":{"alert":"message","badge" : "1","sound":"default"}}] from ip[127.0.0.1] [] []
[2015-08-27 11:19:04] logdisp.DEBUG: dispatcher::msg2apn() [] []
[2015-08-27 11:19:04] logdisp.DEBUG: SUCCESS msg2apn, written[107], sent[107] [] []
[2015-08-27 11:19:05] logdisp.DEBUG: Client [127.0.0.1] connected [] []
[2015-08-27 11:19:05] logdisp.DEBUG: Message buf[...{"aps":{"alert":"message","badge" : "1","sound":"default"}}] from ip[127.0.0.1] [] []
[2015-08-27 11:19:05] logdisp.DEBUG: dispatcher::msg2apn() [] []
[2015-08-27 11:19:05] logdisp.DEBUG: SUCCESS msg2apn, written[107], sent[107] [] []
[2015-08-27 11:19:07] logdisp.DEBUG: Client [127.0.0.1] connected [] []
[2015-08-27 11:19:07] logdisp.DEBUG: Message buf[...{"aps":{"alert":"message","badge" : "1","sound":"default"}}] from ip[127.0.0.1] [] []
[2015-08-27 11:19:07] logdisp.DEBUG: dispatcher::msg2apn() [] []
[2015-08-27 11:19:07] logdisp.DEBUG: SUCCESS msg2apn, written[107], sent[107] [] []


I tried to sent two kinds of payload:

{"aps" : {"alert" : "message","badge" : "1","sound" : "default"}}
{"aps" : { "alert" : { "title" : "My Title", "body" : "My Message" }, "badge" : 2, "sound" : "default" } }


After regenerating the certificates, I do now get the following errors when connecting:

[vagrant@server backend-src]$ php -f dispatcher.php
Log path: [/srv/backend/log/log.txt], Log level: [0],
Binding info: address[127.0.0.1], port[6060]
Connecting to [ssl://gateway.push.apple.com:2195], using certificate [/srv/backend/htdocs/ios_distribution.pem] and passphrase[?]
PHP Warning: stream_socket_client(): Failed to enable crypto in /vagrant/backend-src/dispatcher.php on line 182
PHP Warning: stream_socket_client(): unable to connect to ssl://gateway.push.apple.com:2195 (Unknown error) in /vagrant/backend-src/dispatcher.php on line 182
Warning: stream_socket_client(): unable to connect to ssl://gateway.push.apple.com:2195 (Unknown error) in /vagrant/backend-src/dispatcher.php on line 182
Connect failed error[0 ] to url [ssl://gateway.push.apple.com:2195] with certicate [/srv/backend/htdocs/ios_distribution.pem]


I am now able to connect! The reason here was simple. The path to the certificates was incorrect. However push notifications are still not received on the devices with the new certificate.

Does the App need to be published / release with the new certificate before it will work??? Neither on development nor on production are messages received on the device???

Finally, it succeeded receiving messages for both sandbox and production / ad-hoc tokens.

// 64 chars token
// Sandbox and productions tokens have the same length and
// they cannot be distinguished
$deviceToken='1234567890123456789012345678901234567890123456789012345678901234';

$body['aps'] = array('alert' => 'Sample message', 'sound' => 'default');
$payload = json_encode($body);

$deviceToken = str_replace(' ', '', $deviceToken);
$msg = chr(0) . pack('n', 32) . pack('H*', $deviceToken) . pack('n', strlen($payload)) . $payload;

fwrite($apn_link, $msg, strlen($msg));
fflush($apn_link);


Do NOT mix sandbox and production device tokens on production!

The APN production environment does not allow sandbox tokens
and it silently stops delivering messages to production tokens when
a sandbox token has been used.

Answer
// 64 chars token
// Sandbox and productions tokens have the same length and 
// they cannot be distinguished    
$deviceToken='1234567890123456789012345678901234567890123456789012345678901234';

$body['aps'] = array('alert' => 'Sample message', 'sound' => 'default');
$payload = json_encode($body);

$deviceToken = str_replace(' ', '', $deviceToken);
$msg = chr(0) . pack('n', 32) . pack('H*', $deviceToken) . pack('n', strlen($payload)) . $payload;

fwrite($apn_link, $msg, strlen($msg));
fflush($apn_link);

Do NOT mix sandbox and production device tokens on production!

The APN production environment does not allow sandbox tokens and it silently stops delivering messages to production tokens when a sandbox token has been used.

Comments