Permalink
Browse files

Merge remote-tracking branch 'neilrahilly/develop' into develop

Conflicts:
	apns.py
	tests.py
  • Loading branch information...
2 parents 007bcbd + 7be4670 commit 0f1ce70cd7fa6f293d03908998e4fb3e9aec20a9 Josh Chung committed Jun 3, 2012
Showing with 79 additions and 77 deletions.
  1. +75 −73 apns.py
  2. +4 −4 tests.py
View
148 apns.py
@@ -32,9 +32,57 @@
import ssl
import json
-MAX_PAYLOAD_LENGTH = 256
+GATEWAY_PORT = 2195
+GATEWAY_HOST = 'gateway.push.apple.com'
+GATEWAY_SANDBOX_HOST = 'gateway.sandbox.push.apple.com'
+
+FEEDBACK_PORT = 2196
+FEEDBACK_HOST = 'feedback.push.apple.com'
+FEEDBACK_SANDBOX_HOST = 'feedback.sandbox.push.apple.com'
+
+NOTIFICATION_COMMAND = 0
+ENHANCED_NOTIFICATION_COMMAND = 1
+
+NOTIFICATION_FORMAT = (
+ '!' # network big-endian
+ 'B' # command
+ 'H' # token length
+ '32s' # token
+ 'H' # payload length
+ '%ds' # payload
+)
+
+ENHANCED_NOTIFICATION_FORMAT = (
+ '!' # network big-endian
+ 'B' # command
+ 'I' # identifier
+ 'I' # expiry
+ 'H' # token length
+ '32s' # token
+ 'H' # payload length
+ '%ds' # payload
+)
+
+ERROR_RESPONSE_FORMAT = (
+ '!' # network big-endian
+ 'B' # command
+ 'B' # status
+ 'I' # identifier
+)
+
ERROR_RESPONSE_LENGTH = 6
+FEEDBACK_FORMAT = (
+ '!' # network big-endian
+ 'I' # time
+ 'H' # token length
+ '32s' # token
+)
+
+FEEDBACK_FORMAT_LENGTH = 38 # struct.calcsize(FEEDBACK_FORMAT)
+TOKEN_LENGTH = 32
+MAX_PAYLOAD_LENGTH = 256
+
class APNs(object):
"""A class representing an Apple Push Notification service connection"""
@@ -51,42 +99,6 @@ def __init__(self, use_sandbox=False, cert_file=None, key_file=None, enhanced=Fa
self._feedback_connection = None
self._gateway_connection = None
- @staticmethod
- def unpacked_byte_bigendian(byte):
- """
- Returns a byte from a packed big-endian (network) form
- """
- return unpack('>b', byte)[0]
-
- @staticmethod
- def packed_ushort_big_endian(num):
- """
- Returns an unsigned short in packed big-endian (network) form
- """
- return pack('>H', num)
-
- @staticmethod
- def unpacked_ushort_big_endian(bytes):
- """
- Returns an unsigned short from a packed big-endian (network) byte
- array
- """
- return unpack('>H', bytes)[0]
-
- @staticmethod
- def packed_uint_big_endian(num):
- """
- Returns an unsigned int in packed big-endian (network) form
- """
- return pack('>I', num)
-
- @staticmethod
- def unpacked_uint_big_endian(bytes):
- """
- Returns an unsigned int from a packed big-endian (network) byte array
- """
- return unpack('>I', bytes)[0]
-
@property
def feedback_server(self):
if not self._feedback_connection:
@@ -166,12 +178,11 @@ def write(self, string):
rlist, wlist, _ = select.select([self._connection()], [self._connection()], [])
if len(rlist) > 0: # there's error response from APNs
buff = self.read(ERROR_RESPONSE_LENGTH)
- command = APNs.unpacked_byte_bigendian(buff[0])
+ if len(buff) != ERROR_RESPONSE_LENGTH:
+ return None
+ command, status, identifier = unpack(ERROR_RESPONSE_FORMAT, buff)
if 8 != command: # not error response
return None
- status = APNs.unpacked_byte_bigendian(buff[1])
- identifier = APNs.unpacked_uint_big_endian(buff[2:])
-
return (status, identifier)
if len(wlist) > 0:
self._connection().write(string)
@@ -253,10 +264,8 @@ class FeedbackConnection(APNsConnection):
"""
def __init__(self, use_sandbox=False, **kwargs):
super(FeedbackConnection, self).__init__(**kwargs)
- self.server = (
- 'feedback.push.apple.com',
- 'feedback.sandbox.push.apple.com')[use_sandbox]
- self.port = 2196
+ self.server = FEEDBACK_SANDBOX_HOST if use_sandbox else FEEDBACK_HOST
+ self.port = FEEDBACK_PORT
def _chunks(self):
BUF_SIZE = 4096
@@ -285,17 +294,19 @@ def items(self):
break
while len(buff) > 6:
- token_length = APNs.unpacked_ushort_big_endian(buff[4:6])
- bytes_to_read = 6 + token_length
- if len(buff) >= bytes_to_read:
- fail_time_unix = APNs.unpacked_uint_big_endian(buff[0:4])
+
+ if len(buff) >= FEEDBACK_FORMAT_LENGTH:
+
+ fail_time_unix, token_len, token = unpack(
+ FEEDBACK_FORMAT, buff[:FEEDBACK_FORMAT_LENGTH])
+
+ token_hex = b2a_hex(token)
fail_time = datetime.utcfromtimestamp(fail_time_unix)
- token = b2a_hex(buff[6:bytes_to_read])
- yield (token, fail_time)
+ yield (token_hex, fail_time)
# Remove data for current token from buffer
- buff = buff[bytes_to_read:]
+ buff = buff[FEEDBACK_FORMAT_LENGTH:]
else:
# break out of inner while loop - i.e. go and fetch
# some more data and append to buffer
@@ -307,39 +318,30 @@ class GatewayConnection(APNsConnection):
"""
def __init__(self, use_sandbox=False, **kwargs):
super(GatewayConnection, self).__init__(**kwargs)
- self.server = (
- 'gateway.push.apple.com',
- 'gateway.sandbox.push.apple.com')[use_sandbox]
- self.port = 2195
+ self.server = GATEWAY_SANDBOX_HOST if use_sandbox else GATEWAY_HOST
+ self.port = GATEWAY_PORT
def _get_notification(self, token_hex, payload):
"""
Takes a token as a hex string and a payload as a Python dict and sends
the notification
"""
- token_bin = a2b_hex(token_hex)
- token_length_bin = APNs.packed_ushort_big_endian(len(token_bin))
- payload_json = payload.json()
- payload_length_bin = APNs.packed_ushort_big_endian(len(payload_json))
-
- notification = ('\0' + token_length_bin + token_bin
- + payload_length_bin + payload_json)
-
+ token = a2b_hex(token_hex)
+ payload = payload.json()
+ fmt = NOTIFICATION_FORMAT % len(payload)
+ notification = pack(fmt, NOTIFICATION_COMMAND, TOKEN_LENGTH, token,
+ len(payload), payload)
return notification
def _get_enhanced_notification(self, token_hex, payload, identifier, expiry):
"""
form notification data in an enhanced format
"""
- token_bin = a2b_hex(token_hex)
- token_length_bin = APNs.packed_ushort_big_endian(len(token_bin))
- payload_json = payload.json()
- payload_length_bin = APNs.packed_ushort_big_endian(len(payload_json))
- identifier_bin = APNs.packed_uint_big_endian(identifier)
- expiry_bin = APNs.packed_uint_big_endian(expiry)
-
- notification = ('\1' + identifier_bin + expiry_bin + token_length_bin + token_bin + payload_length_bin +
- payload_json)
+ token = a2b_hex(token_hex)
+ payload = payload.json()
+ fmt = ENHANCED_NOTIFICATION_FORMAT % len(payload)
+ notification = pack(fmt, ENHANCED_NOTIFICATION_COMMAND, identifier, expiry,
+ TOKEN_LENGTH, token, len(payload), payload)
return notification
def send_notification(self, token_hex, payload, identifier=0, expiry=0):
View
@@ -3,6 +3,7 @@
from apns import *
from binascii import a2b_hex
from random import random
+from struct import pack, unpack
import hashlib
import os
@@ -25,10 +26,9 @@ def mock_chunks_generator():
token_bin = a2b_hex(t)
token_length = len(token_bin)
- data += APNs.packed_uint_big_endian(int(time.time()))
- data += APNs.packed_ushort_big_endian(token_length)
- data += token_bin
-
+ data += pack(FEEDBACK_FORMAT, int(time.time()), TOKEN_LENGTH,
+ token_bin)
+
while data:
yield data[0:BUF_SIZE]
data = data[BUF_SIZE:]

0 comments on commit 0f1ce70

Please sign in to comment.