Contributors: Jens Alfke, Traun Leyden, Ben Brooks, Jim Borden
Protocol version 3
This document specifies the replication protocol in use by Couchbase Mobile 2.0 and later. It supersedes the REST-based protocol inherited from CouchDB.
Benefits of the new protocol are:
-
Faster and uses less bandwidth
-
Consumes fewer TCP ports on the server (a limited resource)
-
Opens a single TCP connection on the client instead of 4+ — this prevents problems with the limited number of sockets the client HTTP library will open, which has led to deadlocks and performance problems
-
Cleaner error handling, because socket-level connectivity errors only happen on one socket instead of intermittently affecting some fraction of requests
-
Fully IANA compliant sparkly pony hooves (just checking to see if anyone is reading this)
-
Can be adapted to run over alternate transports like Bluetooth, or anything else we can send framed messages over
-
Less code (about 40% less in the iOS implementation)
-
Cleaner implementation, with the generic messaging layer separated from the replication-specific logic
-
Protocol is inherently symmetric between client/server, which means the two roles share a lot of common code
-
Supports “conflict-free” servers, which require clients to resolve conflicts before pushing changes.
There is also a related blog post that is targeted towards end users.
The new replication protocol is built on the multiplexed BLIP messaging protocol, which itself is layered on WebSockets. The relevant aspects of the transport are:
-
Communication runs over a single socket.
-
Both client and server can send messages. The protocol is symmetrical.
-
Message delivery is reliable and ordered.
-
Messages are multiplexed — any number can be in flight at once, and a large message does not block the ones behind it.
-
A message is similar to an HTTP request in that it contains an unlimited-size binary body plus a set of key/value properties.
-
An unsolicited message is called a request.
-
A request can be responded to (unless marked as no-reply) and the response message can contain either properties and a body, or an error.
Other transport protocols could be used under the hood in some circumstances, for example Bluetooth, as long as the message semantics are preserved.
The client opens a WebSocket connection to the server at path
/`dbname
/blipsync` (where _dbname is the name of the database.)
This begins as an HTTP GET request, and goes through authentication as
usual, then upgrades to WebSocket protocol.
The WebSocket sub-protocol name BLIP_3+CBMobile_3
, sent in the
Sec-WebSocket-Protocol
header, is used to ensure that both client and
server understand BLIP and the replicator protocol.
A request’s type is identified by its Profile
property. The following
subsections are named after the Profile
value of the request. Each
section begins by listing other defined properties and any meaning
assigned to the message body.
Any response properties and/or body data are listed too. However, many messages don’t require any data in the response, just a success/failure indication.
Most of these messages are sent by both client and server. Their usage in the replication algorithm is described in the Algorithm section below.
collection
[3.1+]: Indicates the index of the collection to operate on for this message, indexed by the list resolved in getCollections
. If legacy getCheckpoint
is used, this property must be omitted
client
: Unique ID of client checkpoint to retrieve
Retrieves a checkpoint stored on the receiver. The checkpoint is a JSON
object that’s stored as the value of the key given by the client
property. This property should be derived in a way that is collection
aware (i.e. each collection can have its own checkpoint). If this message is sent before getCollections
then the replication will operate in legacy mode and only the default collection can be replicated.
Response:
rev
: The MVCC revision ID of the checkpoint (not the same as a document revID!)
Body: JSON data of the checkpoint
Body: JSON dictionary
A collection aware parallelized version of getCheckpoint
. If this message is sent before any getCheckpoint
calls then replication will operate in 3.1+ mode, which can fully handle collections. The body of this message will contain two arrays (collections
and checkpoint_ids
) each of the same length. The combination of the two at a given index provide information about a checkpoint to retrieve (i.e. request checkpoint X for collection Y). The format of each entry in the collections
array will be of the format [scope.]collection
where if scope is omitted the collection will be considered to be from the default scope.
Response:
An array of items each identical in structure to the result of getCheckpoint
. The entries will be ordered according to the order in the request message. There are two special entries in this case, however. An entry which is an empty dictionary ({}
) means that there is no existing checkpoint for the given checkpoint ID. An entry which is null
means that the collection does not exist in the remote, and the replication should not proceed.
collection
[3.1+]: Indicates the index of the collection to operate on for this message, indexed by the list resolved in getCollections
. If legacy getCheckpoint
is used, this property must be omitted
client
: Unique ID of client checkpoint to store
rev
: Last known MVCC revision ID of the checkpoint (omitted if this
is a new checkpoint)
Body: JSON data of checkpoint
Stores a checkpoint on the receiver. The JSON object in the request body
is associated with the key given in the client
property. If the rev
value does not match the checkpoint’s current MVCC revision ID, the
request fails. On success, a new revision ID is generated and returned
in the response for use in the next request.
Response:
rev
: New MVCC revision ID of the checkpoint
collection
[3.1+]: Indicates the index of the collection to operate on for this message, indexed by the list resolved in getCollections
. If legacy getCheckpoint
is used, this property must be omitted
since
: Latest sequence ID already known to the requestor, JSON-encoded
(optional)
continuous
: Set to true
if the requestor wants change notifications
to be sent indefinitely (optional)
filter
: The name of a filter function known to the recipient
(optional)
batch
: Maximum number of changes to send in a single change
message
(optional)
activeOnly
: Set to true
if the requestor doesn’t want to be sent tombstones.
(optional)
versioning
: rev-trees
(default) or version-vectors
— see the 4. Revision IDs and Histories section.
other properties: Named parameters for the filter function
(optional)
Body: JSON dictionary (optional)
sendReplacementRevs
: Boolean indicating whether the passive peer (pusher) should send replacement revs rather than NoRev when the body of a requested rev is unavailable and a newer revision is available. (optional)
Asks the recipient to begin sending change messages starting from the
sequence just after the one given by the since
property, or from the
beginning if no since
is given. If a collection index is provided, that collections changes are used. Otherwise, the default collection will be used if possible.
The recipient MUST agree to the requested versioning type and use the corresponding syntax for revIDs and revision histories in its messages. (See section 4.) If it cannot, it MUST respond with an error and close the connection.
Note: A sequence ID can be any type of JSON value, so the since
property MUST be JSON-encoded. In particular, if the sequence ID is a
string, it MUST have quotes and any necessary escape characters added.
The changes are not sent as a response to this request, rather as a
series of changes
messages, each containing information about zero or
more changes. These are sent in chronological order.
Once all the existing changes have been sent, the end is signaled via an
empty changes
message. Ordinarily, that will be the last message sent.
However, if the continuous
property was set in the subChanges
request, the recipient will continue to send changes
messages as new
changes are made to its database, until the connection is closed.
The optional filter
parameter names a filter function known to the
recipient that limits which changes are sent. If this is present, any
other properties to the request will be passed as parameters to the
filter function. The Sync Gateway only recognizes the filter
sync_gateway/bychannel
, which requires the parameter channels
whose
value is a comma-delimited set of channel names.
If a request body is present, it MUST be a JSON dictionary/object. In
this dictionary the key docIDs
MAY appear; its value MUST be an array
of strings. If present, the recipient MUST only send changes to
documents with IDs appearing in that array. Other unrecognized keys in
the dictionary MUST be ignored.
collection
[3.1+]: Indicates the index of the collection to operate on for this message, indexed by the list resolved in getCollections
. If legacy getCheckpoint
is used, this property must be omitted
Body: JSON array
Notifies the recipient of a series of changes made to either the sender’s default collection, or the collection specified by the provided index. A passive replicator (like Sync Gateway) is triggered to send
these by a prior subChanges
request sent by the client. An active
replicator (Couchbase Lite) will send them spontaneously as part of a
push replication.
The changes are encoded in the message body as a JSON array with one item per change. There can be zero or more changes; a messages with zero changes signifies that delivery has "caught up" and all existing sequences have been sent. This may be followed by more changes as they occur, if the replication is continuous.
Each change in the array is encoded as a nested array of the form
[sequence, docID, revID, deleted]
, i.e. sequence ID followed by
document ID followed by revision ID followed by the deletion state
(which can be omitted if it’s false
.)
The sequence IDs MUST be in forward chronological order but are otherwise opaque (and may be any JSON data type.)
The document body size (in bytes) MAY be appended to the array as a fifth item if it’s known. This is understood to be approximate, since the sender’s database may not store the body in exactly the same form that will be transmitted.
The sender SHOULD break up its change history into multiple changes
messages instead of sending them in one big message. (It SHOULD honor
the optional batch
parameter in the subChanges
request it received
from the peer.) It SHOULD use flow control by limiting the number of
changes
messages that it’s sent but not received replies to yet.
A peer in conflict-free mode SHOULD reject a received changes
message
by returning a BLIP/409 error. This informs the sender that it should
use proposeChanges
instead.
LiteCore always uses the proposeChanges
endpoint rather than changes
;
If LiteCore pushed a conflict via the changes
endpoint, it would end up
pulling in the other branch of the conflict soon thereafter, and CBL
would resolve it and push the merge.
Response:
maxHistory
: Max length of revision history to send (optional; not used with version vectors)
Body: JSON array (see below)
The response message indicates which revisions the recipient wants to
receive (as rev
messages). Its body is also a JSON array; each item
corresponds to the revision at the same index in the request. The item
is either:
-
an array of strings, where each string is the revision ID of an already-known ancestor. (This may be empty if no ancestors are known.) This is used to shorten the revision history to be sent with the document, and may in the future be used to enable delta compression.
-
or a
0
(zero) ornull
value, indicating that the corresponding revision isn’t of interest.
Trailing zeros or nulls can be omitted from the response array, so in
the simplest case the response can be an empty array []
if the
recipient isn’t interested in any of the revisions.
The maxHistory
response property, if present, indicates the maximum
length of the history
array to be sent in rev
messages (see below.)
It should be set to the maximum revision-tree depth of the database. If
it’s missing, the history length is unlimited.
collection
[3.1+]: Indicates the index of the collection to operate on for this message, indexed by the list resolved in getCollections
. If legacy getCheckpoint
is used, this property must be omitted
Body: JSON array
Sends proposed changes to a server that’s in conflict-free mode. This is
much like changes
except that the items in the body array are
different; they look like [docID, revID, serverRevID]
. Each still
represents an updated document, but the information sent is the
documentID, the current revisionID, and the revisionID of the last known
server revision (if any). If there is no known server revision, the
serverRevID
SHOULD be omitted, or otherwise MUST be an empty string.
(As with changes
, the estimated body size MAY be appended, if the
serverRevID
is present.)
The recipient SHOULD then look through each document in its relevant collection. If the document exists, but the given serverRevID is not known or not current, the proposed document SHOULD be rejected with a 409 status (see below.) Or if the document exists and the revID is current, the server already has the document and SHOULD reject it with a 304 status. The recipient MAY also detect other problems, such as an illegal document ID, or a lack of write access to the document, and send back an appropriate status code as described below.
Response:
Body: JSON array
The response message indicates which of the proposed changes are allowed and which are out of date. It consists of an array of numbers, generally with the same meanings as HTTP status codes, with the following specific meanings:
-
0: The change is allowed and the peer should send the revision
-
304: The server already has this revision, so the peer doesn’t need to send it
-
409: This change would cause a conflict, so the server needs to resolve it and retry later
As with changes
, trailing zeros can be omitted, but the interpretation
is different since a zero means "send it" instead of "don’t send it". So
the common case of an empty array response tells the sender to send
all of the proposed revisions.
collection
[3.1+]: Indicates the index of the collection to operate on for this message, indexed by the list resolved in getCollections
. If legacy getCheckpoint
is used, this property must be omitted
id
: Document ID (optional)
rev
: Revision ID (optional) — see the 4. Revision IDs and Histories section.
replacedRev
: If the revision sent is not the revision originally requested, but is a
replacementRev, this is the originally requested Revision ID. (optional)
deleted
: true if the revision is a tombstone (optional)
sequence
: Sequence ID, JSON-encoded (optional unless unsolicited,
q.v.). If this is a replacementRev, this will be the sequence of the original requested rev.
history
: Revision history (list of revision IDs) — see the 4. Revision IDs and Histories section.
Body: Document JSON
noconflicts
: true if the revision may not create a conflict (optional; default is false)
Sends one document revision, either meant for the specified collection or the default collection if one is not specified. The id
, rev
, deleted
properties are
optional if corresponding _id
, _rev
, _deleted
properties exist in
the JSON body (and vice versa.) The sequence
property is optional
unless this message was unsolicited.
If the noconflicts
flag is set, or if the recipient is in conflict-free mode,
it MUST check whether the history
array contains the current local revision ID,
or if the history
array is empty and the document does not exist locally.
If not, it MUST reject the revision by returning a 409 status.
Ordinarily a rev
message is triggered by a prior response to a
changes
message. However, it MAY be sent unsolicited, instead of in
a changes
message, if all of the following are true:
-
This revision’s metadata hasn’t yet been sent in a
changes
message; -
this revision’s sequence is the first one that hasn’t yet been sent in a
changes
message; -
the revision’s JSON body is small;
-
and the sender believes it’s very likely that the recipient will want this revision (doesn’t have it yet and is not filtering it out.)
In practice this is most likely to occur for brand new changes being sent in a continuous replication in response to a local database update notification.
The recipient MUST send a response unless the request was sent 'noreply'. It MUST not send a success response until it has durably added the revision to its database, or has failed to add it. On success the response can be empty; on failure it MUST be an error.
Note: The recipient may need to send one or more getattach
messages
while processing the rev
message, in which case it MUST NOT send the
rev’s response until it’s received responses to the `getattach
message(s) and durably added the attachments, as well as the document,
to its database.
collection
[3.1+]: Indicates the index of the collection to operate on for this message, indexed by the list resolved in getCollections
. If legacy getCheckpoint
is used, this property must be omitted
id
: Document ID (optional)
rev
: Revision ID (optional)
sequence
: Sequence ID, JSON-encoded (optional)
error
: The error number, which should correspond to HTTP Response status codes
reason
: A more detailed description of the cause of the error (optional)
Body: None
In the case a rev is requested from a peer via a changes
or subChanges
response,
but that revision is not available, and the peer’s message did not include the sendReplacementRevs
property (set to true), the norev
message should be sent
as a placeholder to inform the peer
that there will be no corresponding
rev
message sent for the requested revision. This prevents the peer
from waiting for a rev
message that will never come, which could cause
the replication to get stuck.
collection
[3.1+]: Indicates the index of the collection to operate on for this message, indexed by the list resolved in getCollections
. If legacy getCheckpoint
is used, this property must be omitted
digest
: Attachment digest (as found in document _attachments
metadata.)
Requests the body of an attachment, given its digest. This is called by
the recipient of a rev
message if it determines that the revision
contains an attachment whose contents it does not know.
If the server’s database has per-document access control, where
documents may be readable by some but not all users, it MUST check that
an attachment with this digest appears in at least one document that the
client has access to. Otherwise a client could violate access control by
getting the body of any attachment it can learn the digest of (probably
"leaked" by another user who does have access to it.) The simplest way
to enforce this is for the server to keep track of which rev
messages
it’s sent to the client but not yet received responses to; these are the
ones that the client will be requesting attachments of, to complete its
downloads.
(This request is problematic — it assumes that the recipient indexes attachments by digest, which is true of Couchbase Mobile but not necessarily of other implementations. Adding the document and revision ID to the properties would help.)
Response:
Body: raw contents of attachment
collection
[3.1+]: Indicates the index of the collection to operate on for this message, indexed by the list resolved in getCollections
. If legacy getCheckpoint
is used, this property must be omitted
digest
: Attachment digest (as found in document attachments
metadata.)
Body: A _nonce: 16 to 255 bytes of random binary data
Asks the recipient to prove that it has the body of the attachment with the given digest, without making it actually send the data. This is another security precaution that SHOULD used by servers with per-document access control, i.e. where documents may be readable by some but not all users. If this weren’t in place, a user who knew the digest (but not the contents) of an an attachment could upload a document containing the metadata of an attachment with the same digest, and then immediately download the document and the attachment.
Such a server SHOULD send this request when it receives a rev
message
containing an attachment digest that matches an attachment it already
has. The server first generates some cryptographically-random bytes (20
is a reasonable number) as a nonce
, and sends the nonce along with the
attachment’s digest in a proveattach
request to the client.
The recipient (the client, the one trying to push the revision) computes a SHA-1 digest of the concatenation of the following:
-
The length of the nonce (a single byte)
-
The nonce itself
-
The entire body of the attachment
It then sends a response containing the resulting digest, in the same encoding used for attachment digests: "sha1-" followed by lowercase hex digits.
(Meanwhile, the paranoid server performs the same computation using its
own copy of the attachment. It then verifies that the digest received
from the client matches the digest it computed. If it doesn’t match, the
server can assume the client doesn’t really have the attachment, and can
reject the rev
message with the revision containing it.)
Several of the messages contain document revision IDs, and the rev
message includes a document’s revision history. This section defines their syntax.
A database uses one of two types of versioning: revision trees (older) or version vectors (new). They have different forms of revision IDs and histories. The active peer communicates which type is in use via the versioning
property (or lack thereof) in its subChanges
request.
A revision ID consists of a positive decimal integer, followed by a hyphen (-
), followed by 16-20 hexadecimal-encoded bytes (32-40 digits.) The first component is the generation count, the second the encoded digest.
-
The generation count is expected to be fairly small. It starts at 1 and might go into the millions.
-
The digest is generated by MD5 or SHA-1. It SHOULD not be larger than 20 bytes (40 digits.)
A revision history consists of a series of one or more revision IDs delimited by commas, with optional whitespace after the comma. The generation number MUST decrease by one in successive revIDs.
In the rev
message the current revision is given its own property (rev
), so to avoid redundancy the history
property contains a history that omits the current revision.
A revision ID, also called a version, consists of a hexadecimal number of up to 16 digits, followed by an @
sign, followed by 22 characters in the base64 alphabet (upper and lower case, digits, +
and /
.) The first component is the timestamp, the second the (base64-encoded) source ID.
-
The timestamp is usually a count of nanoseconds since the Unix epoch, currently around 2^60. It fits in a 64-bit integer. It SHOULD be interpreted as unsigned (although it won’t overflow a signed integer for a century or more.) It MUST NOT be stored in an IEEE double or it will lose precision; for the same reason it SHOULD NOT be encoded in JSON as a number.
-
The source ID is a 128-bit (16-byte) number, most likely a UUID.
A revision history is a version vector. It’s a series of one or more versions delimited by either a comma or semicolon, with optional whitespace after. Most versions are separated by commas, but a semicolon is used to separate the current and merge version(s) from the historical ones.
The current version must come first, then zero or two merge versions, then zero or more historical versions. The current version is causally later than the rest, and the merge versions are causally later than the historical versions.
In the rev
message the current version is given its own property (rev
), so to avoid redundancy the history
property contains a version vector that omits the current version.
Here are informal descriptions of the flow of control of both push and pull replication. Note the symmetry: a lot of the steps are the same in both lists but with "client" and "server" swapped.
[3.1+] With the introduction of collections, and in order to maintain maximum compatibility, the algorithm now has an overall choice of two flavors: legacy and collection-aware. The mode that is entered into depends on the presence or lack of the newly introduced getCollections
message. If this message is sent as the first message, the connection is thereafter a collection-aware connection and every message that is capable of including a collection
property must do so, or the server side should return a 400 error. Conversely, if any other message is received (often getCheckpoint
) then the connection is thereafter a legacy connection and the inverse is true: Any message that contains a collection
property is incorrect and the server side should return a 400 error.
-
Client opens connection to server and authenticates
-
Client sends
getCheckpoint
to verify checkpoint status -
Client sends one or more
changes
messages containing revisions added since the checkpointed local sequence-
If response is a BLIP/409 or HTTP/409 error, client infers that the server is in "no conflicts" mode, and switches to sending
proposeChanges
messages, including resending the failed one. -
Client keeps track of how many
changes
messages have been sent but not yet responded to -
If that count exceeds a reasonable limit, the client waits to send the next message until a response is received.
-
-
Server replies to each
changes
message indicating which revisions it wants and which ancestors it already has-
If server is in "no conflicts" mode, it will reject
changes
messages with a BLIP/409 or HTTP/409 error, and instead acceptproposeChanges
messages.
-
-
For each requested revision:
-
Client sends document body in a
rev
message -
Server looks at each newly-added attachment digest in each revision and
-
sends a
getAttachment
for each attachment it doesn’t have; client sends data -
sends a
proveAttachment
for each attachment it already has; client sends proof
-
-
Server adds revision & attachments to database, and sends success response to the client’s
rev
message. -
Client periodically sends
setCheckpoint
as progress updates -
When all revisions and attachments have been sent, client either disconnects (non-continuous mode) or stays connected and watches for local doc changes, returning to step 3 when changes occur
Push interaction diagram
┌────────────┐ ┌────────────────┐
│ Pusher │ │ Peer │
└────────────┘ └────────────────┘
│ │
├────────────────────────────getCheckpoint RQ [clientID]─────────────────────────────────▶
│ │
│ │
◀───────────────────────────getCheckpoint RSP: [checkpoint]──────────────────────────────┤
│ │
│ │
├─────────────────────────changes RQ [{docId, revId, ..}, {..}]──────────────────────────▶
│ │
│ │
◀─────────────────────────────changes RSP [rev1, rev2, ..]───────────────────────────────┤
│ │
│ │
├─────────────────────────changes RQ [{docId, revId, ..}, {..}]──────────────────────────▶
│ │
│ │
◀─────────────────────────────changes RSP [rev5, rev6, ..]───────────────────────────────┤
│ │
│ │
├─────────────────────────changes RQ [] (empty indicates finished)───────────────────────▶
│ │
│ │
◀ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ changes RSP: NoReply─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─│
│ │
│ │
├──────────────────────────────rev RQ [{docId, rev1, body}]──────────────────────────────▶
│ │
│ getAttach RQ │
◀───────────────────────────────────────[digest]─────────────────────────────────────────┤
│ │
│ │
├──────────────────────────────────getAttach RSP [body]──────────────────────────────────▶
│ │
│ │
◀──────────────────────────────────getAttach RQ [digest]─────────────────────────────────┤
│ │
│ │
├───────────────────────────────────getAttach RSP [body]─────────────────────────────────▶
│ │
│ │
◀───────────────────────────────────────rev RSP []───────────────────────────────────────┤
│ │
│ │
│ setCheckpoint RQ [clientID, │
├────────────────────────────────────────checkpoint]─────────────────────────────────────▶
│ │
│ │
◀───────────────────────────setCheckpoint RSP: [checkpoint]──────────────────────────────┤
│ │
│ │
▣ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ Close Socket─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─▶
│ │
│ │
▼ ▼
-
Client opens connection to server and authenticates
-
Client sends
getCheckpoint
to verify checkpoint status -
Client sends a
subChanges
message with the latest remote sequence ID it’s received in the past, and acontinuous
property if it wants to pull continuously -
Server sends one or more
changes
messages containing revisions added since the checkpointed remote sequence-
Server keeps track of how many
changes
messages have been sent but not yet responded to -
If that count exceeds a reasonable limit, the server waits to send the next message until a response is received.
-
-
Client replies to each
changes
message indicating which revisions it wants and which ancestors it already has -
For each requested revision:
-
Server sends document body in a
rev
message -
Client looks at each newly-added attachment digest in each revision and sends a
getAttachment
for each attachment it doesn’t have; server sends data -
Client adds revision & attachments to database, and sends success response to the server’s
rev
message. -
Client periodically sends
setCheckpoint
as progress updates -
When there are no more changes, server sends a
changes
message with an empty list -
Client in non-continuous mode disconnects now that it’s caught up; client in continuous mode keeps listening
-
Server in continuous mode watches for local doc changes, returning to step 4 when changes occur
Pull interaction digram
┌────────────┐ ┌────────────────┐
│ Puller │ │ Peer │
└────────────┘ └────────────────┘
│ │
│ │
├────────────────────────────getCheckpoint RQ [clientID]─────────────────────────────────▶
│ │
│ │
◀───────────────────────────getCheckpoint RSP: [checkpoint]──────────────────────────────┤
│ │
│ │
├─────────────────────────subChanges RQ [since, continuous]──────────────────────────────▶
│ │
│ │
◀ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ subChanges RSP: NoReply ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─│
│ │
│ │
◀─────────────────────────changes RQ [{docId, revId, ..}, {..}]──────────────────────────┤
│ │
│ │
├─────────────────────────────changes RSP [rev1, rev2, ..]───────────────────────────────▶
│ │
│ │
◀─────────────────────────changes RQ [{docId, revId, ..}, {..}]──────────────────────────┤
│ │
│ │
├─────────────────────────────changes RSP [rev5, rev6, ..]───────────────────────────────▶
│ │
│ │
◀─────────────────────────changes RQ [] (empty indicates finished)───────────────────────┤
│ │
│ │
├ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ changes RSP: NoReply─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─▶
│ │
│ │
◀──────────────────────────────rev RQ [{docId, rev1, body}]──────────────────────────────┤
│ │
│ getAttach RQ │
├───────────────────────────────────────[digest]─────────────────────────────────────────▶
│ │
│ │
◀──────────────────────────────────getAttach RSP [body]──────────────────────────────────┤
│ │
│ │
├──────────────────────────────────getAttach RQ [digest]─────────────────────────────────▶
│ │
│ │
◀───────────────────────────────────getAttach RSP [body]─────────────────────────────────┤
│ │
│ │
├───────────────────────────────────────rev RSP []───────────────────────────────────────▶
│ │
│ │
│ setCheckpoint RQ [clientID, │
├────────────────────────────────────────checkpoint]─────────────────────────────────────▶
│ │
│ │
◀───────────────────────────setCheckpoint RSP: [checkpoint]──────────────────────────────┤
│ │
│ │
│ Close │
▣ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─Socket ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─▶
│ │
│ │
▼ ▼
The replication protocol can be extended with additional message types and properties.
The Connected Client protocol extension allows mobile clients to access a server-side database (bucket) directly, without needing a local database replica.