Server-Replica Protocol

The server-replica protocol is defined abstractly in terms of request/response transactions.

The protocol builds on the model presented in the previous chapters, and in particular on the synchronization process.

Clients

From the protocol's perspective, replicas accessing the same task history are indistinguishable, so this protocol uses the term "client" to refer generically to all replicas replicating a single task history.

Server

A server implements the requests and responses described below. Where the logic is implemented depends on the specific implementation of the protocol.

For each client, the server is responsible for storing the task history, in the form of a branch-free sequence of versions. It also stores the latest snapshot, if any exists. From the server's perspective, snapshots and versions are opaque byte sequences.

Version Invariant

The following invariant must always hold:

All versions are linked by parent-child relationships to form a single chain. That is, each version must have no more than one parent and one child, and no more than one version may have zero parents or zero children.

Data Formats

Task data sent to the server is encrypted by the client, using the scheme described in the "Encryption" chapter.

Version

The decrypted form of a version is a JSON array containing operations in the order they should be applied. Each operation has the form {TYPE: DATA}, for example:

  • [{"Create":{"uuid":"56e0be07-c61f-494c-a54c-bdcfdd52d2a7"}}]
  • [{"Delete":{"uuid":"56e0be07-c61f-494c-a54c-bdcfdd52d2a7"}}]
  • [{"Update":{"uuid":"56e0be07-c61f-494c-a54c-bdcfdd52d2a7","property":"prop","value":"v","timestamp":"2021-10-11T12:47:07.188090948Z"}}]
  • [{"Update":{"uuid":"56e0be07-c61f-494c-a54c-bdcfdd52d2a7","property":"prop","value":null,"timestamp":"2021-10-11T12:47:07.188090948Z"}}] (to delete a property)

Timestamps are in RFC3339 format with a Z suffix.

Snapshot

The decrypted form of a snapshot is a JSON object mapping task UUIDs to task properties. For example (pretty-printed for clarity):

{
 "56e0be07-c61f-494c-a54c-bdcfdd52d2a7": {
   "description": "a task",
   "priority": "H"
 },
 "4b7ed904-f7b0-4293-8a10-ad452422c7b3": {
   "description": "another task"
 }
}

Transactions

All interactions between the client and server are defined in terms of request/response transactions, as described here.

AddVersion

The AddVersion transaction requests that the server add a new version to the client's task history. The request contains the following;

  • parent version ID, and
  • encrypted version data.

The server determines whether the new version is acceptable, atomically with respect to other requests for the same client. If it has no versions for the client, it accepts the version. If it already has one or more versions for the client, then it accepts the version only if the given parent version has no children, thereby maintaining the version invariant.

If the version is accepted, the server generates a new version ID for it. The version is added to the chain of versions for the client, and the new version ID is returned in the response to the client. The response may also include a request for a snapshot, with associated urgency.

If the version is not accepted, the server makes no changes, but responds to the client with a conflict indication containing the ID of the version which has no children. The client may then "rebase" its operations and try again. Note that if a client receives two conflict responses with the same parent version ID, it is an indication that the client's version history has diverged from that on the server.

GetChildVersion

The GetChildVersion transaction is a read-only request for a version. The request consists of a parent version ID. The server searches its set of versions for a version with the given parent ID. If found, it returns the version's

  • version ID,
  • parent version ID (matching that in the request), and
  • encrypted version data.

If found, it returns a not-found indication. Note that this circumstance is not an error, and occurs during every successful sync process.

Implementations may return a different error code if the version is "gone" (not found and would not be accepted by AddVersion) in order to provide better error messages to the user. This may occur if a client has not been synced for a long time and its base version has expired, or if the server storage has been corrupted.

Note that GetChildVersion should return the not-found when no versions exist. This allows a client to switch to a new server and continue uploading the same sequence of versions.

AddSnapshot

The AddSnapshot transaction requests that the server store a new snapshot, generated by the client. The request contains the following:

  • version ID at which the snapshot was made, and
  • encrypted snapshot data.

The server may validate that the snapshot is for an existing version and is newer than any existing snapshot. It may also validate that the snapshot is for a "recent" version (e.g., one of the last 5 versions). If a snapshot already exists for the given version, the server may keep or discard the new snapshot but should return a success indication to the client.

The server response is empty.

GetSnapshot

The GetSnapshot transaction requests that the server provide the latest snapshot. The response contains the snapshot version ID and the snapshot data, if those exist.