본문 바로가기
프로그래밍/iOS

푸시 메시지 포맷

by 백룡화검 2012. 2. 7.

 Provider와 APNS 간에 SSL(Secure Socket Layer)를 통해 통신을 하며, 서로 간에 올바른 통신을 위한 몇 가지
요구사항이 존재한다.   다음은  Local, Remote Notification Programming Guide 문서에서 일부 발췌한 내용이다.
자세한 내용은 실제 문서를 참고하자. 


1. Provider가 APNS와 통신하기 위한 요구사항
 〮 Provider는 Apple Push Notification Service와 바이너리 인터페이스를 사용해 통신함
 〮 TCP Socket을 사용하며, 안전한 통신채널을 위해 TLS(또는 SSL)을 사용함
 〮 APNS와의  잦은 연결과 종료는 DOS 공격으로 오인받을 수 있음 
 〮 에러가 발생하면 APNS는 해당연결을 종료함 
 〮 서버에 접속해서 반복적으로 푸시전송에 실패한 클라이언트 디바이스 리스트를 조회할 수 있음 => Feedback service



2. Binary Interface and Notification Formats
〮순수 TCP 소켓을 사용하며, 최적의 성능을 위해서는 단일 연결을 맺고, 다중 통지를 보내야함 
〮인터페이스는 simple 포맷, enhanced 포맷 2가지의 notification format을 지원함 
〮Simple 포맷에서는 payload 데이터가 제한크기를 초과하면 통지를 거부하지만, enhanced 포맷에서는
    notification 에 임의의 식별자를 할당하여, 에러가 발생하면 식별자와 연관된 에러코드를 확인할 수 있음


위에서 언급된 것처럼 2가지 종류의 메시지 형태가 존재한다. 대부분의 앱에서는 Simple 포맷으로도 충분하다. 하지만    
Simple 포맷은 실제로 푸시 메시지가 전송됐는지를 보장하지 않기 때문에,   푸시 메시지에 대한 전송 신뢰성이 보장되야 할경우엔는
Enhanced 포맷을 사용하자. Enhanced 포맷에서는 메시지 전송 실패시 에러를 반환하기 때문에,  Provider가 주기적으로 APNS에서 
이 값을 체크하여 실패시 다시 전송을 요청할 수 있다. 



3. Simple Format
APNS와  SSL 연결과, peer-exchage 인증을 가정한다. (SSL을 사용한 1:1 통신을 한다는 의미같다.)
메시지 포맷은 다음과 같다. 




Command : Simple 포맷인지 Enhanced  포맷인지 구별하기 위한 값. simple 포맷은 0을 갖는다.
Token length     : DeviceToken의 길이
Device Token    : 바이너리 포맷으로 인코딩 되야함
Payload length : 푸시로 전달할 메시지의 길이로 256 바이트를 초과할 수 없으며,
                           널(null)문자로 종료해서는 안됨
* token length와 payload length 는 네트워크 오더(big endian)이어야 함




3.1 SimpleFormat을 사용하는 예제코드 
Local, Remote Notification Programming Guide 문서에 나와있는 샘플 코드이다. 실제 메시지 전송을 구현해야할 경우 참고하자

static bool sendPayload(SSL *sslPtr, char *deviceTokenBinary, char *payloadBuff, size_t payloadLength)
{
        bool rtn = false;
        if (sslPtr && deviceTokenBinary && payloadBuff && payloadLength) {
        uint8_t command = 1; /* command number */ 
        char binaryMessageBuff[sizeof(uint8_t) + sizeof(uint32_t) + sizeof(uint32_t)
                + sizeof(uint16_t) + DEVICE_BINARY_SIZE + sizeof(uint16_t) + MAXPAYLOAD_SIZE];
        
        /* message format is, |COMMAND|ID|EXPIRY|TOKENLEN|TOKEN|PAYLOADLEN|PAYLOAD|  */
        char *binaryMessagePt = binaryMessageBuff; 
        uint32_t whicheverOrderIWantToGetBackInAErrorResponse_ID = 1234; 
        uint32_t networkOrderExpiryEpochUTC = htonl(time(NULL)+86400); 
        // expire message if not delivered in 1 day 

        uint16_t networkOrderTokenLength = htons(DEVICE_BINARY_SIZE); 
        uint16_t networkOrderPayloadLength = htons(payloadLength);
        
        /* command */ 
        *binaryMessagePt++ = command;
        
        /* provider preference ordered ID */
        memcpy(binaryMessagePt, &whicheverOrderIWantToGetBackInAErrorResponse_ID, sizeof
                                                                                                                (uint32_t));
        binaryMessagePt += sizeof(uint32_t);

        /* expiry date network order */ 
        memcpy(binaryMessagePt, &networkOrderExpiryEpochUTC, sizeof(uint32_t)); 
        binaryMessagePt += sizeof(uint32_t);
        
        /* token length network order */ 
        memcpy(binaryMessagePt, &networkOrderTokenLength, sizeof(uint16_t)); 
        binaryMessagePt += sizeof(uint16_t);

        /* device token */ 
        memcpy(binaryMessagePt, deviceTokenBinary, DEVICE_BINARY_SIZE); 
        binaryMessagePt += DEVICE_BINARY_SIZE;

        /* payload length network order */ 
        memcpy(binaryMessagePt, &networkOrderPayloadLength, sizeof(uint16_t)); 
        binaryMessagePt += sizeof(uint16_t);

        /* payload */ 
        memcpy(binaryMessagePt, payloadBuff, payloadLength); 
        binaryMessagePt += payloadLength; 
        
        if (SSL_write(sslPtr, binaryMessageBuff, (binaryMessagePt -binaryMessageBuff)) > 0) 
                rtn = true;
        } 
        return rtn;
}



4. Enhanced Format

Enhanced Format 를 사용하여 메시지를 전송하면, APNS가 인지 불가능한 명령을 만났을 경우 연결을 종료하기 전에 에러 응답을
반환해주어 에러의 원인을 확인할 수 있다. Provider 에서는 주기적으로 APNS의 에러응답에 접근하여 결과를 확인할 수 있다. 



first byte: 1
identifier: notification을 식별하기 위한 임의의 값. 에러가 발생하면 APNS는 이값을 반환함
expiry : notification이 더이상 유효하지 않는 때를 나타내는 초로 표현된 시간.  fixed UNIX epoch  date(UTC)
...나머지 필드는 Simple Format과 동일하다.




4.1 Enhanced Format을 사용하는 예제코드 
역시 실제 구현시에 참고하자. 
 
static bool sendPayload(SSL *sslPtr, char *deviceTokenBinary, char *payloadBuff, size_t payloadLength)
{
        bool rtn = false;
        if (sslPtr && deviceTokenBinary && payloadBuff && payloadLength) {
        uint8_t command = 1; /* command number */ 
        char binaryMessageBuff[sizeof(uint8_t) + sizeof(uint32_t) + sizeof(uint32_t)
                + sizeof(uint16_t) + DEVICE_BINARY_SIZE + sizeof(uint16_t) + MAXPAYLOAD_SIZE];
        
        /* message format is, |COMMAND|ID|EXPIRY|TOKENLEN|TOKEN|PAYLOADLEN|PAYLOAD|  */
        char *binaryMessagePt = binaryMessageBuff; 
        uint32_t whicheverOrderIWantToGetBackInAErrorResponse_ID = 1234; 
        uint32_t networkOrderExpiryEpochUTC = htonl(time(NULL)+86400); 
        // expire message if not delivered in 1 day 

        uint16_t networkOrderTokenLength = htons(DEVICE_BINARY_SIZE); 
        uint16_t networkOrderPayloadLength = htons(payloadLength);
        
        /* command */ 
        *binaryMessagePt++ = command;
        
        /* provider preference ordered ID */
        memcpy(binaryMessagePt, &whicheverOrderIWantToGetBackInAErrorResponse_ID, sizeof(uint32_t));
        binaryMessagePt += sizeof(uint32_t);

        /* expiry date network order */ 
        memcpy(binaryMessagePt, &networkOrderExpiryEpochUTC, sizeof(uint32_t)); 
        binaryMessagePt += sizeof(uint32_t);
        
        /* token length network order */ 
        memcpy(binaryMessagePt, &networkOrderTokenLength, sizeof(uint16_t)); 
        binaryMessagePt += sizeof(uint16_t);

        /* device token */ 
        memcpy(binaryMessagePt, deviceTokenBinary, DEVICE_BINARY_SIZE); 
        binaryMessagePt += DEVICE_BINARY_SIZE;

        /* payload length network order */ 
        memcpy(binaryMessagePt, &networkOrderPayloadLength, sizeof(uint16_t)); 
        binaryMessagePt += sizeof(uint16_t);

        /* payload */ 
        memcpy(binaryMessagePt, payloadBuff, payloadLength); 
        binaryMessagePt += payloadLength; 
        
        if (SSL_write(sslPtr, binaryMessageBuff, (binaryMessagePt -binaryMessageBuff)) > 0) 
                rtn = true;
        } 
        return rtn;
}



5. Error 응답 포맷
notification 포맷을 인식할 수 없을 경우 APNS는 연결을 종료하기 전에 error 응답을 전송해준다.  error가 없다면 어떠한 값도 
반환하지 않는다.  에러 응답의 포맷은 다음과 같다. 



first byte: 8
status : 상태코드
identifier : notification을 구성할 때 사용한 식별자 

상태코드
0 : 에러없음
1 :  에러 처리중
2 : device token 이 없음
3 : topic이 없음
4 : payload 없음
5 : 잘못된 token 크기
6 : 잘못된 topic 크기
7 : 잘못된 payload 크기
8 : 잘못된 token
255 : 알수없음