Websockets Protocol¶
We use standard websockets handshake with
Sec-WebSocket-Protocol: ciruela.v1
and no extensions.
Serialization¶
Payload is serialized using CBOR. There are three kinds of messages:
- Request
- Response
- Notification
All three types of messages can be sent at any time into any direction. Each request includes a numeric identifier that is used in corresponding response. Each side of the connection can create request identifiers independently. Each request has exactly one response. If more than one response is provided it’s built by some higher level construct.
Every message is contiguous, messages can’t interleaved. Protocol has no flow control besides what TCP provides. If more concurrency desired than multiple connections might be used.
We will use CDDL for describing message format. Here is the basic structure of a message:
message = $message .within message-structure
message-structure = [message-kind, message-type, *any] .and typed-message
message-kind = &( notification: 0, request: 1, response: 2 )
message-type = $notification-type / $request-type
typed-message = notification / request / response
notification = [0, $notification-type, *any]
request = [1, $request-type, request-id, *any]
response = [2, $request-type, request-id, *any]
request-id = uint
Signing Uploads¶
Signature of the upload consists of the following fields packed as the CBOR length-prefixed array in this specific order:
signature-data = [
path: text, ; destination path
image: bytes, ; binary hashsum of the image (bottom line of the
; index file but in binary form)
timestamp: uint, ; milliseconds since unix epoch when image was signed
]
Ciruela currently only supports ed25519 algorithm for signatures, but more alorithms (RSA in particular) can be used in future.
The signature
itself is an array of at least two arguments with type as
the first element and rest depends on the signature algorithm:
signature = ["ssh-ed25519", bytes .size 64]
Note: the ed25519 signature includes public key as a part of the signature as per standard. Other signatures might require different structure.
Commands¶
AppendDir¶
Schedule a an adding the new directory. This sends only a signed hash of the directory index and marks this directory as incoming.
Note
If different images have been scheduled for upload by different peers in the cluster cluster may end up with different images on different nodes
If upload for this path and image already exists at node another signature is added.
If there is no such index on the peer it asks this peer or any other available connection for the index data itself and subsequently asks for missing chunks (some chunks may be reused from different image).
Content of the message is a dictionary (CBOR object):
$message /= [1, "AppendDir", request-id, append-dir-params]
$message /= [2, "AppendDir", request-id, append-dir-response]
append-dir-params = {
path: text, ; path to put image to
image: bytes, ; binary hashsum of the image (bottom line
; of the index file but in binary form
timestamp: uint, ; milliseconds since the epoch
signatures: [+ signature], ; one or more signatures
}
append-dir-response = {
accepted: bool, ; whether directory accepted or not
? reject_reason: text, ; a machine-parseable reason for rejection
? hosts: {* bytes => text}, ; hosts that will probably accept the
; directory
}
Note: accepted response here doesn’t mean that this is new directory (i.e. same directory might already be in place or might still be downloaded). Also it doesn’t mean that download is already complete. Most probably it isn’t, and you should wait for a completion notification.
The hosts
field may or may be not sent both in case of accepted
is
true or not. In the latter case, it might be useful to reconnect to one of
these hosts. In the former case, we can track ReceiveImage
messages from
all these hosts. Note: we transmit machine ids (key in mapping) and host
names. Client should track notifications by machine_id, but may use name for
human-readable output. Note2: while in most cases hosts
will be exhaustive
list for all clusters it may be not so, if not is just restarted and has not
picked up all the data in gossip subsystem.
ReplaceDir¶
Schedule a replacing the directory with the new image. This sends only a signed hash of the directory index and marks this directory as incoming.
Note
If different images have been scheduled for upload by different peers in the cluster the one with latest accross the cluster timestamp in the signature will win
If there is no such index on the peer it asks this peer or any other available connection for the index data itself and subsequently asks for missing chunks (some chunks may be reused from different image).
$message /= [1, "ReplaceDir", request-id, replace-dir-params]
$message /= [2, "ReplaceDir", request-id, replace-dir-response]
replace-dir-params = {
path: text, ; path to put image to
image: bytes, ; binary hashsum of the image (bottom line
; of the index file but in binary form)
? old_image: bytes, ; hash olf the previous image
timestamp: uint, ; milliseconds since the epoch
signatures: [+ signature], ; one or more signatures
}
replace-dir-response = {
accepted: bool, ; whether directory accepted or not
? reject_reason: text, ; a machine-parseable reason for rejection
? hosts: {* bytes => text}, ; hosts that will probably accept the
; directory
}
Note: if no old_image
is specified the destination directory is not
checked. Use AppendDir
to atomically update first image.
See AppendDir for the explanation of hosts
usage.
PublishImage¶
Notifies peer that this host has data for the specified index. This is usually
executed before AppendDir
, so that when receiving latter command server
is already aware where to fetch data from.
$message /= [0, "PublishImage", publish-index-params]
publish-image-params = {
id: bytes, ; binary hashsum of the image (bottom line
; of the index file but in binary form)
}
This notification basically means that peer can issue GetIndex
in
backwards direction.
ReceivedImage¶
Notifies peer that some host (maybe this one, or other peer) received
and commited this image. The notification is usually sent after
PublishImage
for the specified id.
The notification can be used by cicuela command-line client to determine that at least one host (or at least N hosts) received the image and it’s safe to disconnect from the network and also to display progress.
$message /= [0, "ReceivedImage", received-image-params]
received-image-params = {
id: bytes, ; binary hashsum of the image (bottom line
; of the index file but in binary form)
path: text, ; path where image was stored
machine_id: bytes, ; machine-id of the receiver
hostname: text, ; hostname of the receiver
forwarded: bool, ; whether message originated from this host
; or forwarded
}
The forwarded
field might be used to skip check on hostname
field.
AbortedImage¶
Notifies peer that some host (maybe this one, or other peer) have aborted
receiving this image. The notification is usually sent after
PublishImage
for the specified id.
The notification can be used by cicuela command-line client to notify that
image can’t be written for some reason, or to determine when
it’s find to retry upload in case of already_uploading_different_version
(-x
flag of CLI).
$message /= [0, "AbortedImage", aborted-image-params]
aborted-image-params = {
id: bytes, ; binary hashsum of the image (bottom line
; of the index file but in binary form)
path: text, ; path where image was stored
machine_id: bytes, ; machine-id of the receiver
hostname: text, ; hostname of the receiver
forwarded: bool, ; whether message originated from this host
; or forwarded
reason: text, ; reason of why image was aborted
}
The forwarded
field might be used to skip check on hostname
field.
GetIndex¶
Fetch an index data by it’s hash. This method is usually called by server after AppendDir and ReplaceDir has been received. And it is sent to the original client (in backwards direction). But the call only takes place if no index already exists on this host or on one of the peers.
$message /= [1, "GetIndex", request-id, get-index-params]
$message /= [2, "GetIndex", request-id, get-index-response]
get-index-params = {
id: bytes, ; binary hashsum of the image (bottom line
; of the index file but in binary form)
? hint: text ; virtual_path where index can be found
}
get-index-response = {
? data: bytes, ; full original index file
}
Note: index file can potentially be in different formats, but in any case:
- Consistency of index file is verified by original id which is also a checksum
- Kind of index can be detected by inspecting data itself (i.e. first bytes of index file should contain a signature of some kind)
Note 2: server implementation can ignore or can use hint
value, client
implementation can supply or can skip hint
. Current state is:
ciruela upload
does not use hint, while ciruela-server
always sends
but never uses a hint value (still, the virtual path where index resides
is used internally, so it may become useful in future if we will ever forward
the GetIndex
requests)
GetIndexAt¶
Fetch an index data by it’s path. It’s usually used to download image by a client (perhaps to execute modify and update cycle).
Note: image id is a part of index data so is not provided separately.
$message /= [1, "GetIndexAt", request-id, get-index-at-params]
$message /= [2, "GetIndexAt", request-id, get-index-at-response]
get-index-at-params = {
path: text ; virtual_path to check image at
}
get-index-at-response = {
? data: bytes, ; full original index file
? hosts: {* bytes => text}, ; hosts that contain a directory
}
The index file returned is a similar way to GetIndex
. If there is no
such config response may include a list of hosts to search for a directory
at. Similarly to how it’s done in AppendDir
and ReplaceDir
.
GetBlock¶
Fetch a block with specified hash.
$message /= [1, "GetBlock", request-id, get-block-params]
$message /= [2, "GetBlock", request-id, get-block-response]
get-block-params = {
hash: bytes, ; binary hashsum of the block
? hint: [text, text, uint], ; virtual_path, path, and position where
; the blocks can be found found
}
get-block-response = {
? data: bytes, ; full original index file
}
Note: server implementation can ignore or can use hint
value, client
implementation can supply or can skip hint
. Current state is:
ciruela upload
does not use hint, while ciruela-server
always sends
and uses a hint value.