WebAPI/SimplePush/Protocol

This page describes the protocol used for communication by the PushServer and the UserAgent.

Status: Draft

Everything here applies to Version 1 of the protocol.

TODO: Possible pain points:

  • Versioning
  • Sec-WebSocket-Protocol - set to push-notification?

Purpose

The SimplePush protocol is closely based on Thialfi: A Client Notification Service for Internet-Scale Applications and makes the same delivery guarantees, soft server state and client driven recovery. It is a signaling and not a data carrying system.

The goal: To notify clients of changes to application server state in a reliable manner by ensuring that the client will always eventually learn of the latest version of an object for which it has expressed interest.

Definitions

PushServer

A publicly accessible server that implements the server side of the Push Protocol and exposes an HTTP API for AppServer's to notify it.

UserAgent

A device or program that implements the client side of the Push Protocol.

UAID
A globally unique UserAgent ID. Used by the PushServer to associate channelIDs with a client. Stored by the UserAgent, but opaque to it.
Channel
The flow of information from AppServer through PushServer to UserAgent.
ChannelID
Unique identifier for a Channel. Generated by UserAgent for a particular application. Opaque identifier for both UserAgent and PushServer. This MUST NOT be exposed to an application.
Endpoint
A REST-ful HTTP URL uniquely associated to a channel. Requests to this URL should update the PushServer state for the channel. MUST be exposed to applications.
Version

Monotonically increasing 64-bit integer describing the application state. This holds meaning as a primary key or similar only to the AppServer. The PushServer and UserAgent and App should use this only for detecting changes. MUST be exposed to applications.

Application

A program which requires access to push notifications. The UserAgent acts on behalf of applications.

Protocol Overview

The SimplePush protocol defines how UserAgents and PushServers communicate to ensure reliable delivery of the latest version of a channel from the PushServer to the UserAgent.

The SimplePush communication channel is WebSockets, and the wire protocol is JSON, with messages defined below. All messages MUST use TLS (wss:// protocol for WebSockets). In addition, Endpoints generated by PushServer's MUST use HTTPS URLs.

The PushServer should maintain a mapping of channelIDs and their versions and pushEndpoints. SimplePush is backed by a best effort delivery mechanism over WebSocket

The protocol has some request-response driven actions, where the UserAgent MUST make requests, and the PushServer MAY respond. One message, notification, MAY be sent at any time by the PushServer.

ChannelID, UAIDs and Endpoints

Since the UAID is the only unique identifier of a UserAgent, with no authentication information, it is important that UAIDs are not compromised. Only the UserAgent to which the UAID was assigned, and the PushServer, should know the UAID. An attacker can use a leaked UAID to pretend to be the UserAgent with that UAID. Although SimplePush does not transfer any private information, the attacker would still receive the notifications meant for the victim. If any party detects a compromise, they SHOULD reset the UAID. For this, the UserAgent may send a blank UAID and get a new one from the PushServer. Similarly the PushServer may send a new UAID during a handshake.

ChannelIDs are also unique. It is RECOMMENDED that channelIDs be associated with a UAID, so that an attacker cannot receive notifications for a compromised channelID without also having accessed the UAID.

It is RECOMMENDED that both UAID and channelIDs be UUIDv4 tokens.

The format of the Endpoint is not specified by this protocol. There are two requirements that MUST be satisfied:

  • PushServers MUST use HTTPS, so the Endpoint URL will begin with "https://".
  • ChannelIDs and Endpoints MUST have a one-one mapping and Endpoints may not 'expire' while their channelID is still active.

It is RECOMMENDED that Endpoints simply be a prefix followed by the channelID or some transformation of the channelID which is referentially transparent. PushServers SHOULD avoid exposing UAIDs in the Endpoints since they are sent to AppServers.

Messages

All messages are encoded as JSON. All messages MUST have the following fields:

messageType string
Defines the message type

Handshake

After the WebSocket is established, the UserAgent begins communication by sending a hello message. The hello message contains the UAID if the UserAgent has one. The UserAgent also transmits the channelIDs it knows so the server may synchronize its state.

The server is MAY respect this UAID, but it is at liberty to ask the UserAgent to change its UAID in the response.

If the UserAgent receives a new UAID, it MUST delete all existing channelIDs and their associated versions. It MAY then wake up all registered applications immediately or at a later date by sending them a push-register message.

The handshake is considered complete, once the UserAgent has received a reply.

An UserAgent MUST transmit a hello message only once on its WebSocket. If the handshake is not completed in the first try, it MUST disconnect the WebSocket and begin a new connection.

NOTE: Applications may request registrations or unregistrations from the UserAgent, before or when the handshake is in progress. The UserAgent MAY buffer these or report errors to the application. But it MUST NOT send these requests to the PushServer until the handshake is completed.

UserAgent -> PushServer

messageType = "hello"
Begin handshake
uaid string REQUIRED
If the UserAgent has a previously assigned UAID, it should send it. Otherwise send an empty string.
channelIDs list of strings REQUIRED
If the UserAgent has a list of channelIDs it wants to be notified of, it must pass these, otherwise an empty list.

Extra fields: The UserAgent MAY pass any extra JSON data to the PushServer. This data may include information required to wake up the UserAgent out-of-band. The PushServer MAY ignore this data.

Example
 {
   "messageType": "hello",
   "uaid": "1234567890",
   "channelIDs": ["r2d2", "c3po"]
 }

PushServer -> UserAgent

PushServers MUST send only respond to a hello once. UserAgents MUST ignore multiple hello replies.

messageType = "hello"
Responses generally have the same messageType as the request
uaid string REQUIRED
If the UserAgent sent no UAID, generate a new one. If the UserAgent send a valid UAID and the PushServer is in sync with the UserAgent, send back the same UAID, otherwise send a new UAID.
Example
 {
   messageType: “hello”,
   uaid: <if not provided by previous call, a new valid one will be generated>
 }

Register

The Register message is used by the UserAgent to request that the PushServer notify it when a channel changes. Since channelIDs are associated with only one UAID, this effectively creates the channel, while unregister destroys the channel.

The channelID is chosen by the UserAgent because it also acts like a nonce for the Register message itself. Because of this PushServers MAY respond out of order to multiple register messages or messages may be lost without compromising correctness of the protocol.

The request is considered successful only after a response is received with a status code of 200. On success the UserAgent MUST:

  • Update it's persistent storage based on the response
  • Notify the application of a successful registration.

On error, the UserAgent MUST notify the application as soon as possible.

NOTE: The register call is made by the UserAgent on behalf of an application. The UserAgent SHOULD have reasonable timeouts in place so that the application is not kept waiting for too long if the server does not respond or the UserAgent has to retry the connection.

UserAgent -> PushServer

messageType = "register"
channelID string REQUIRED


PushServer -> UserAgent

messageType = "register"
channelID string REQUIRED
This MUST be the same as the channelID sent by the UserAgent in the register request that this message is a response to.
status number REQUIRED
Used to indicate success/failure. MUST be one of:
  • 200 - OK. Success
  • 409 - Conflict. The chosen ChannelID is already in use and not associated with this UserAgent. UserAgent SHOULD retry with a new ChannelID as soon as possible.
  • 500 - Internal server error. Database out of space or offline. Disk space full or whatever other reason due to which the PushServer could not grant this registration. UserAgent SHOULD avoid retrying immediately.
pushEndpoint string REQUIRED
Should be the URL sent to the application by the UserAgent. AppServer's will contact the PushServer at this URL to update the version of the channel identified by channelID.

Unregister

messageType = "unregister"
channelID string REQUIRED

Notification

Synchronization of server and client state

Acheiving reliable delivery

At any time, if the PushServer reaches an inconsistent condition, due to database failure, network failure, or any other reason, it MAY drop all state. It MUST then disconnect all active clients, forcing them to reconnect and begin a handshake to get synchronized.

Alternative communication

In environments or devices where maintaining an always alive WebSocket is difficult, UserAgents and PushServer's MAY implement alternative means to notify UserAgents about updates. This out of band notification SHOULD be restricted to request the UserAgent to establish the WebSocket. SimplePush state SHOULD NOT be transmitted out of band, since reliable delivery may not be guaranteed.

API Push Client - Push Server

This should be considered a translation to WebSockets of the API defined here - https://wiki.mozilla.org/WebAPI/SimplePush/ServerAPI Status/Error codes documented in that document apply here unless explicitly marked otherwise.

Push API endpoint for WebSocket connection is : wss://push.server.com/v1/

C->S:

           {
     		messageType: "hello",
       		uaid: "<a valid UAID>",

channelIDs: [channelID1, channelID2, …],

       		interface: {

ip: "<current device IP address>",

         			port: "<TCP or UDP port in which the device is waiting for wake up notifications>"
},
mobilenetwork: {

mcc: "<Mobile Country Code>", mnc: "<Mobile Network Code>"

},

protocol: <wakeup protocol. OPTIONAL. By default: UDP>

   	}

uaid can be null, then, a new uaid is created (see next)

S->C:

{ messageType: “hello”, status: xxx, <200, ...>, uaid: <if not provided by previous call, a new valid one will be generated> }

NOTE: All pending registration/unregistration requests stay on ‘hold’ until the client has transmitted new state and the server has concurred.

Recovery protocol


The client “hello” contains the “uaid” field. If the “uaid” field is known by the server, the server should check that the list of channelIDs sent by the client matches what it has. If there is even a single channelID that the server does not know about, it should generate a new UAID for the client, and drop all state about the current UAID. This will cause the client to generate new channelIDs by waking up all apps and having them register. This way apps get latest state since they contact their server.

If the server has ‘extra’ channelIDs associated with the UAID, it can simply delete them. Channel Registration:

A client channel registration request:

  	{
     		messageType: "register",

channelID: "<ChannelID>

   	}
   	

The server response can be:

{ messageType: “register”, status: xxx, pushEndpoint: <pushendpoint>, channelID: <channelId> }



Channel Deletion:

{

     		messageType: "unregister",
     		channelID: <channelId>
   	}

The server response can be:

{

     		messageType: "unregister",

status: xxx,

     		channelID: <channelId>
   	}

Channel Update:

Server -> Client { messageType: “notification”, updates: [{"channelID": "id", "version": "XXX"}, ...] }

Client -> Server { messageType: “ack”, updates: [{"channelID": channelID, “version”: xxx}, ...] } API Application Server - Notification Server


NOTIFICATION


https://server:port/notify/<hashed channelID> [Actually Opaque as far as anyone other than the push server is concerned]

Method PUT Payload:

version=<version>




April Discussion Below:






DESKTOP NOTIFICATION


https://server:port/notify/<hashed channelID>

Method PUT Payload:

body=<any text>[&ttl=<ttl>]

GROUPS MANAGEMENT [WIP]


https://server:port/groups

CRUD API: C: POST https://server:port/groups Returns URL (pushEndpoint)

R: GET https://pushEndpoint Returns: [endPointURL1, endPointURL2, …]

U: PUT https://pushEndpoint url=endPointURLN&op=[ADD|DELETE] Returns: 200 OK or 404 No group found

D: DELETE https://pushEndpoint Removes the group Returns: 200 OK or 404 No group found



WAKEUP API [WIP]

This (WIP) API will be offered by the carriers in order to be able to wakeup handsets inside their own mobile networks.

Connection to this API will be protected with client certificates signed by the carrier so only trusted 3rd. party notification servers will be able to send datagrams to the handsets.

https://server:port/wakeup


GET mcc=<mcc>&mnc=<mnc> RETURNS 200 OK (We’ve a wake up server in that network !) 404 Error (We don’t have a wake up server)


PUT ip=<ip>&port=<port>&mcc=<mcc>&mnc=<mnc> RETURNS 200 OK


Channel Update:

Server -> Client { messageType: “notification”, updates: [{"channelID": "id", "version": "XXX"}, ...] }

{

     		messageType: "desktopNotification",

updates: [{"channelID": "version", _internal_id: ..., "body": "body"}, ...]

   	}

Return Status codes

WIP: https://github.com/telefonicaid/notification_server/blob/mozAPI/src/common/constants.js nikhil: I think you’ll just want to use the ones from the mozilla spec that are required, like 404 and so on. Most of the other status codes are not required in my opinion.