serval-dna/doc/Servald-REST-API.md
Andrew Bettison bd45186a6a Document REST POST /restful/rhizome/insert
Adds definitions for Rhizome concepts such as Bundle ID, Bundle Secret,
payload, etc. and a detailed step-by-step definition of the insertion
logic.
2015-12-07 22:39:19 +10:30

67 KiB
Raw Blame History

Serval DNA REST API

Serval Project, September 2015

Introduction

The Serval DNA daemon that runs on every node in a Serval Mesh network gives applications access to the network through two main APIs:

  • the MDP API and MSP API provide "traditional" packet and stream transport, allowing applications to send and receive Serval network packets to and from nearby nodes with latencies of up to several seconds;

  • the HTTP REST API provides applications with access to the following Serval services:

    • Keyring -- local identity management
    • Rhizome -- store-and-forward (high latency) content distribution
    • MeshMS -- secure one-to-one messaging using Rhizome as transport

This document describes the second of these, the HTTP REST API.

Protocol and port

The Serval DNA HTTP REST API is an HTTP 1.0 server that only accepts requests on the loopback interface (IPv4 address 127.0.0.1), TCP port 4110. It rejects requests that do not originate on the local host, by replying 403.

Security

The REST API uses plain HTTP without encryption. REST requests and responses are not carried over any physical network link, only local (“logical”) links between processes, so there is no risk of remote eavesdropping. The only potential threat comes from hostile local processes.

Operating system kernels such as Linux (Android, Ubuntu) and Darwin (Apple) prevent normal processes from accessing the traffic on local sockets between other processes. To attack Serval DNA and its clients, a local process on the local host would have to gain super-user privilege (eg, through a privilege escalation vulnerability). A super-user process would have many ways to attack Serval DNA and its clients, much more effective than intercepting their communications, so encrypting client-server communications would offer no protection whatsoever.

Authentication

Clients of the HTTP REST API must authenticate themselves using Basic Authentication. This narrows the window for opportunistic attacks on the server's HTTP port by malicious applications that scan for open local ports to exploit. Any process wishing to use the REST API must supply valid authentication credentials (name/password), or will receive a 401 Unauthorized response.

Client applications obtain their REST API credentials via a back channel specific to their particular platform. This delegates the exercise of handing out credentials to the application layer, where users can (usually) exercise their own discretion. For example, on Android, a client app sends an Intent to the Serval Mesh app requesting a Serval REST credential, and will receive a reply only if it possesses the right Android Permission. When users install or run the client app, Android informs them that the app requests the "Serval Network" permission, and users may allow or deny it.

As a fall-back mechanism, created primarily to facilitate testing, HTTP REST API credentials can be configured using configuration options of the form:

api.restful.users.USERNAME.password=PASSWORD

PASSWORD is a cleartext secret, so the Serval DNA configuration file must be protected from unauthorised access or modification by other apps. That makes this mechanism unsuitable for general use.

Request

An HTTP REST request is a normal HTTP 1.0 GET or POST:

GET

A GET request consists of an initial "GET" line containing the path and HTTP version, followed by zero or more header lines, followed by a blank line. As usual for HTTP, all lines are terminated by an ASCII CR-LF sequence.

For example:

GET /restful/keyring/identities.json?pin=1234 HTTP/1.0
Authorization: Basic aGFycnk6cG90dGVy
Accept: */*

GET requests only accept parameters as query parameters in the path.

POST

A POST request is the same as a GET request except that the first word of the first line is "POST", the blank line is followed by a request body, and the following request headers are mandatory:

POST requests accept parameters as query parameters in the path and also as a request body with Content-Type: multipart/form-data. These two kinds of parameters are not exclusive; a request may contain a mixture of both.

Request Content-Length

In a request, the Content-Length header gives the exact number of bytes (octets) in the request's body, which must be correct. Serval DNA will not process a request until it receives Content-Length bytes, so if Content-Length is too large, the request will suspend and eventually time out. Serval DNA will ignore any bytes received after it has read Content-Length bytes, so if Content-Length is too small, the request body will be malformed.

Request Content-Type

In a request, the Content-Type header gives the Internet Media Type of the body. Serval DNA currently supports the following media types in requests:

  • multipart/form-data is used to send parameters in POST requests. The boundary parameter must specify a string that does not occur anywhere within the content of any form part.

  • text/plain; charset=utf-8 is used for MeshMS message form parts. The only supported charset is utf-8; a missing or different charset will cause a 415 response.

  • rhizome/manifest; format=text+binarysig is used for Rhizome manifests in text+binarysig format.

A missing Content-Type header in a POST request will cause a 400 response. An unsupported content type will cause a 415 response.

Request Range

HTTP 1.1 Range retrieval is partially supported. In a request, the Range header gives the start and end, in byte offsets, of the resource to be returned. The server may respond with exactly the range requested, in which case the response status code will be 206, or it may ignore the Range header and respond with the entire requested resource.

For example, the following header asks that the server omit the first 64 bytes and send only the next 64 bytes (note that ranges are inclusive of their end byte number):

Range: bytes=64-127

The specification allows for more than one start-end range to be supplied, separated by commas, however not all REST API operations support multi ranges. If a multi-range header is used in such a request, then the response may be the entire content or 501 Not Implemented.

Responses

An HTTP REST response is a normal HTTP 1.0 response consisting of a header block, a blank line, and an optional body, for example: As usual, all lines are terminated by an ASCII CR-LF sequence. For example:

HTTP/1.0 200 OK
Content-Type: application/json
Content-Length: 78

{
 "http_status_code": 200,
 "http_status_message": "OK"
}

The lingua franca of the HTTP REST API is JSON in UTF-8 encoding. All Serval DNA HTTP REST responses have a Content-Type of application/json unless otherwise documented.

Some responses contain non-standard HTTP headers as part of the result they return to the client; for example, Rhizome response headers.

Response status code

The HTTP REST API response uses the HTTP status code to indicate the outcome of the request as follows:

200 OK

The operation was successful and no new entity was created. Most requests return this code to indicate success. Requests that create a new entity only return this code if the entity already existed, meaning that the creation was not performed but the request can be considered a success since the desired outcome was achieved: namely, the existence of the entity. (If the entity was created, then these requests return 201 Created instead.)

(Serval APIs are all idempotent with respect to creation: creating the same entity twice yields the same state as creating it once. This is an important property for a purely distributed network that has no central arbiter to enforce sequencing of operations.)

201 Created

The operation was successful and the entity was created. This code is only returned by requests that create new entities, in the case that the entity did not exist beforehand and has been created successfully.

202 Accepted

The operation was successful but the entity was not created. This code is only returned by requests that create new entities, in the case that the request was valid but the entity was not created because other existing entities take precedence. For example, the Rhizome REST API returns this code when inserting a bundle to a full Rhizome store if the new bundle's rank falls below all other bundles, so the new bundle itself would be evicted to make room.

206 Partial Content

The operation was successful and the response contains part of the requested content. This code is only returned by requests that fetch an entity (the fetched entity forms the body of the response) if the request supplied a Range header that specified less than the entire entity.

400 Bad Request

The HTTP request was malformed, and should not be repeated without modifications. This could be for several reasons:

  • invalid syntax in the request header block
  • a POST request MIME part is missing, duplicated or out of order
  • a POST request was given an unsupported MIME part
  • a POST request MIME part has missing or malformed content

401 Unauthorized

The request did not supply an "Authorization" header with a recognised credential. This response contains a "WWW-Authenticate" header that describes the missing credential:

HTTP/1.0 401 Unauthorized
Content-Type: application/json
Content-Length: 88
WWW-Authenticate: Basic "Serval RESTful API"

{
 "http_status_code": 401
 "http_status_message": "Unauthorized"
}

403 Forbidden

The request failed because the server does not accept requests from the originating host.

404 Not Found

The request failed because the HTTP request URI does not exist. This could be for several reasons:

  • the request specified an incorrect path (typographic mistake)
  • the path is unavailable because the API in question is unavailable (eg, the Rhizome REST API) is currently configured as disabled
  • the path contains a reference to an entity (eg, SID, Bundle ID) that does not exist

405 Method Not Allowed

The request failed because the HTTP request method is not supported for the given path. Usually this means that a GET request was attempted on a path that only supports POST, or vice versa.

411 Length Required

A POST request did not supply a Content-Length header.

414 Request-URI Too Long

The request failed because the HTTP request URI was too long. The server persists the path and a few other pieces of the request in a fixed size request buffer, and this response is triggered if the collective size of these does not leave enough buffer for receiving the remainder of the request.

415 Unsupported Media Type

A POST request failed because of an unsupported content type, which could be for several reasons:

  • the request's Content-Type header specified an unsupported media type
  • a MIME part Content-Disposition was not “form-data”
  • a MIME part Content-Type was unsupported
  • a MIME part Content-Type specified an unsupported charset

416 Requested Range Not Satisfiable

The Range header specified a range whose start position falls outside the size of the requested entity.

419 Authentication Timeout

The request failed because the server does not possess and cannot derive the necessary cryptographic secret or credential. For example, updating a Rhizome bundle without providing the bundle secret. This code is not part of the HTTP standard.

422 Unprocessable Entity

A POST request supplied data that was inconsistent or violates semantic constraints, so cannot be processed. For example, the Rhizome insert operation responds with 422 if the manifest filesize and filehash fields do not match the supplied payload.

423 Locked

The request cannot be performed because a necessary resource is busy for reasons outside the control of the requester and server.

This code is returned by Rhizome requests if the Rhizome store database is currently locked by another process. The architecture of Serval DNA is being improved to prevent any process other than the Serval DNA daemon itself from directly accessing the Rhizome database. Once these improvements are done, this code should no longer occur except during unusual testing and development situations.

429 Too Many Requests

The request cannot be performed because a necessary resource is temporarily unavailable due to a high volume of concurrent requests.

The original use of this code was for Rhizome operations if the server's manifest table ran out of free manifests, which would only happen if there were many concurrent Rhizome requests holding manifest structures open in server memory.

This code may also be used to indicate temporary exhaustion of other finite resources. For example, if Serval DNA is ever limited to service only a few HTTP requests at a time, then this code will be returned to new requests that would exceed the limit.

431 Request Header Fields Too Large

The request header block was too long.

Initial implementations of Serval DNA allocated approximately 8 KiB of buffer memory for each request, and the HTTP server read each header line entirely into that buffer before parsing it. If a single header exceeded the size of this buffer, then the 431 response was returned.

500 Internal Server Error

The request failed because of an internal error in Serval DNA, not an error in the request itself. This could be for several reasons:

  • software defect (bug)
  • unavailable system resource (eg, memory, disk space)
  • corrupted environment (eg, bad configuration, database inconsistency)

Internal errors of this kind may persist or may resolve if the request is re-tried, but in general they will persist because the cause is not transient. Temporary failures that can be resolved by re-trying the request are generally indicated by other status codes, such as 423.

501 Not Implemented

The requested operation is valid but not yet implemented. This is used for the following cases:

  • a request Range header specifies a multi range

Cross-Origin Resource Sharing (CORS)

To support client-side JavaScript applications, Serval DNA has a limited implementation of Cross-Origin Resource Sharing. If a request contains an Origin header with either “null” or a single URI with scheme “http” or “https” or “file”, hostname “localhost” or “127.0.0.1” (or empty in the case of a “file” scheme), and optionally any port number, then the response will contain three Access-Control headers granting permission for other pages on the same site to access resources in the returned response.

For example, given the request:

GET /restful/keyring/identities.json HTTP/1.0
Origin: http://localhost:8080/
...

Serval DNA will respond:

HTTP/1.0 200 OK
Access-Control-Allow-Origin: http://localhost:8080
Access-Control-Allow-Methods: GET, POST, OPTIONS
Access-Control-Allow-Headers: Authorization
...

JSON result

All responses that convey no special content return the following JSON result object:

{
 "http_status_code": ...,
 "http_status_message": "..."
}

The http_status_code field is an integer equal to the status code that follows the HTTP/1.0 token in the first line of the response.

The http_status_message field is usually the same as the reason phrase text that follows the code in the first line of the HTTP response. This reason phrase may be a [standard phrase][status code], or it may be more explanatory; for example, some 404 responses from Rhizome have phrases like, “Bundle not found”, “Payload not found”, etc.

Some responses augment the JSON result object with extra fields; for example, Rhizome JSON result.

JSON table

Many HTTP REST responses that return a list of regular objects (eg, GET /restful/rhizome/bundlelist.json) use the following JSON table format:

{
    "header":["fieldname1","fieldname2","fieldname3", ... ],
    "rows":[
        [field1, field2, field3, ... ],
        [field1, field2, field3, ... ],
        ...
    ]
}

The JSON table format is more compact than the most straightforward JSON representation, an array of JSON objects, which has the overhead of redundantly repeating all field labels in every single object:

[
    {
        "fieldname1: field1,
        "fieldname2: field2,
        "fieldname3: field3,
        ...
    },
    {
        "fieldname1: field1,
        "fieldname2: field2,
        "fieldname3: field3,
        ...
    },
    ...
]

A JSON table can easily be transformed into its equivalent array of JSON objects. The test scripts use the following jq(1) expression to perform the transformation:

[
    .header as $header |
    .rows as $rows |
    $rows | keys | .[] as $index |
    [ $rows[$index] as $d | $d | keys | .[] as $i | {key:$header[$i], value:$d[$i]} ] |
    from_entries |
    .["__index"] = $index
]

Keyring REST API

The Keyring REST API allows client applications to query, unlock, lock, create, and modify Serval Identities in the keyring.

Basic concepts

Serval ID

The Serval ID (a.k.a. SID, a.k.a. “Subscriber ID”) is a unique 256-bit public key in the Curve25519 key space, generated from the random Serval ID secret when the identity is created. The SID is used:

Rhizome Secret

The Rhizome Secret is a secret key, separate from the SID secret, that is generated randomly for each new identity, and stored in the keyring as part of the identity. The Rhizome Secret is used to securely encode the Bundle Secrets of bundles into the bundles themselves, in the form of the Bundle Key. This relieves Rhizome applications of the burden of having to store and protect Bundle Secrets themselves.

PIN

When an identity is created, it can optionally be given a PIN (passphrase). If the PIN is empty then the identity is permanently unlocked (visible).

Identities with a non-empty PIN are stored encrypted in the keyring file. Inspection of the keyring file will not reveal their presence unless the correct PIN is supplied, because all unused entries in the keyring file are filled with pseudo-random content that is indistinguishable from encrypted identities.

If a PIN is lost and forgotten, then the identity (identities) it unlocks will remain locked and unusable forever. There is no “master PIN” or back-door.

Identity unlocking

All Keyring API requests can supply a passphrase using the optional pin parameter, which unlocks all keyring identities protected by that password, prior to performing the request. Serval DNA caches every password it receives until the password is revoked using the lock request, so once an identity is unlocked, it remains visible until explicitly locked.

GET /restful/keyring/identities.json

Returns a list of all currently unlocked identities, in JSON table format. The table columns are:

  • sid: the SID of the identity, a string of 64 uppercase hex digits
  • did: the optional DID (telephone number) of the identity, either null or a string of five or more digits from the set 123456789#0*
  • name: the optional name of the identity, either null or a non-empty string of UTF-8 characters

GET /restful/keyring/add

Creates a new identity with a random SID. If the pin parameter is supplied, then the new identity will be protected by that password, and the password will be cached by Serval DNA so that the new identity is unlocked.

GET /restful/keyring/SID/set

Sets the DID and/or name of the unlocked identity that has the given SID. The following parameters are recognised:

  • did: sets the DID (phone number); must be a string of five or more digits from the set 123456789#0*
  • name: sets the name; must be non-empty

If there is no unlocked identity with the given SID, this request returns 404 Not Found.

Rhizome REST API

Basic concepts

Rhizome storage and synchronisation

Rhizome is a store and forward content distribution system that has no central storage and relies on intermittent and ad-hoc network links between nodes to disseminate copies of its content.

Whenever two Rhizome nodes are in direct network contact with each other (eg, as immediate peers, or neighbours, in an ad hoc wireless network), they spontaneously perform Rhizome synchronisation, during which each provides a list of its own content to the other, and then chooses which of the other's content to fetch.

Every Rhizome node has a Rhizome store, sometimes called the Rhizome database, which keeps a copy of its recently received and inserted content. Every store is limited in size, so during synchronisation, Rhizome expires older items of content to make way for newer items. Rhizome also gives priority to smaller items, and can be made to prioritise on other criteria such as geographical proximity to a location, sender, recipient, or content type.

The Rhizome REST API simply provides access to the contents of the local Rhizome store. The physical location of the store, expiry of bundles from the store, and synchronisation with other Rhizome nodes are outside the scope of the API. An application wishing to share a file via Rhizome simply inserts the file into Rhizome using this API, and lets Rhizome take care of the rest.

Bundle

Rhizome content is organised into bundles. Every bundle is an indivisible item of content, analogous to a single file on a hard disk. A bundle may have any size.

A Rhizome bundle consists of a single manifest and an optional payload.

Every Rhizome bundle is identified by its Bundle ID and version.

Bundle ID

A Bundle ID (a.k.a. BID, a.k.a. “Manifest ID”) is a unique 256-bit public key in the Curve25519 key space, generated from the random Bundle Secret when the the bundle is first created.

Bundle version

A Bundle's version is a 64-bit unsigned integer chosen by the bundle's author.

When presented with two or more bundles that have the same Bundle ID, Rhizome always prefers the one with the highest version number and discards the others. This allows bundles to be updated by publishing a new one with a larger version number than before. As an updated bundle spreads through the Rhizome network, it replaces all prior versions of itself.

Bundle Secret

A Bundle Secret is the Curve25519 cryptographic secret key that produces a Bundle ID public key, and is generated randomly when the bundle is first created.

Every bundle is cryptographically signed by its own Bundle Secret, and the signature is distributed along with the bundle's content. This allows all recipients to verify whether the bundle was in fact produced by the owner of the Bundle Secret. Bundles that do not verify are not stored or synchronised.

There is no restriction on the random generation of Bundle Secrets, so any party may create, sign and publish as many bundles as desired. However, only the possessor of a Bundle Secret may publish an update to a bundle (same Bundle ID, higher version). The signature therefore prevents forgery of updates to existing bundles.

Rhizome API operations that operate on a single bundle accept the Bundle Secret as an optional parameter. This allows applications to store the secrets for the bundles they create, if desired. However, a far easier way to remember of a bundle's secret is to add a Bundle Key to each bundle by specifying a bundle author.

Bundle Key

The Bundle Key is an optional item of meta-data that may be included in a bundle (as the manifest BK field). It encodes the Bundle Secret in a form that only the possessor of the original Rhizome Secret can decode. This avoids every application having to store the secret of every bundle it may wish to update in future; instead, it can add a BK field to each manifest it creates, and only the Rhizome Secret need be stored (in the keyring).

If a bundle contains a Bundle Key, then the Bundle Secret can be recovered as long as an unlocked keyring identity contains the originating Rhizome Secret. See bundle author for further information.

A bundle with no Bundle Key is truly anonymous. If an application stores the Bundle Secret itself (eg, in a local database indexed by Bundle ID), then it may use that secret to update (modify) the bundle, but if the Bundle Secret is lost, then the bundle becomes immutable.

Manifest

A Rhizome bundle's manifest consists of two parts: a meta-data section and a signature section.

The meta-data section is a set of key-value fields. A field key consists of up to 80 alphanumeric ASCII characters, and the first character must be alphabetic. A field's value consists of zero or more bytes that may have any value except ASCII NUL (0), CR (13) and NL (10). Conventionally, numeric values are represented using their decimal ASCII representation.

Every manifest must contain the following core fields, or it is partial:

  • id - the Bundle ID; 64 uppercase hexadecimal digits.

  • version - the version; ASCII decimal.

  • filesize - the number of bytes in the payload; ASCII decimal.

  • service - the name of the service (application) that created the bundle.

  • date - the date the bundle was created; an integral number of milliseconds since the Unix time epoch, in ASCII decimal. This field is set by the bundle's creator and could have any value, due either to inaccuracies in the system clock used to make the time stamp, or deliberate falsification. This field can have values up to 2^64 1, so it is immune to the Y2038 problem.

If the filesize is non-zero, then the following field must also be present:

  • filehash - the 512-bit cryptographic SHA-512 digest of the payload's content; 128 uppercase hexadecimal digits.

The presence of the following field indicates that the bundle is a journal:

  • tail - the byte offset within the journal at which the payload starts; ASCII decimal. The bundle's creator can advance the tail whenever it updates the bundle, to indicate that the preceding bytes are no longer needed, so they can be deleted from Rhizome stores to reclaim space and need not be synchronised, to save network load.

The following fields are all optional:

  • sender - the SID of the bundle's sender; 64 uppercase hexadecimal digits. Used mainly with the MeshMS service, for which it is mandatory, but can also be used by any application to suggest the bundle's author.

  • recipient - the SID of the bundle's recipient; 64 uppercase hexadecimal digits. Used mainly with the MeshMS service, for which it is mandatory, but can also be used by any application to identify the bundle's intended destination.

  • name - a label that identifies the bundle to human users, and also serves as a file name for the file service.

  • crypt - if 1 then the payload is encrypted, so only its intended recipient (who may or may not be identified by the recipient field) can decrypt and read it. If 0 or absent then the payload is clear text.

  • BK - the Bundle Key; 64 uppercase hexadecimal digits.

Any other field may be included in any manifest, but only those mentioned above are given special meaning by Rhizome.

The manifest's signature section is a sequence of one or more concatenated Curve25519 signature blocks. At present, every bundle carries exactly one signature, made using its Bundle Secret, although the manifest format allows for the possibility of multi-signed bundles in future.

Bundle author

A bundle's author is the identity whose Rhizome Secret was used to set the manifest's BK field (Bundle Key).

Manifests do not store the author SID explicitly. Rhizome does not support a manifest field called author. Instead, the bundle author is deduced from the BK field, if present. The BK field relieves authors from having to retain and protect all their Bundle Secrets, and it does so without revealing the identity of the author.

If a bundle contains a BK (Bundle Key) field, then the author's identity can only be deduced if it is an unlocked identity in the local keyring. Serval DNA tries the Rhizome Secret of every unlocked identity until it finds one that, when used to decode the Bundle Key, yields a [Bundle Secret](#bundle secret) that correctly generates the manifest's signature. If no unlocked identity is found, then the author is unknown.

Rhizome nodes that do not possess the unlocked author identity cannot derive the SID of the author, even if the SID is already known to them through other means, since they do not possess the author's Rhizome Secret. Thus, the identity of the author is hidden even if a BK field is present.

If a bundle has no BK field, then its author can never be deduced, so the bundle is anonymous. An anonymous bundle is immutable if the Bundle Secret is lost; without the Bundle Secret, it is impossible to sign any change to the manifest, so no updates can be made.

The nearest thing to an “author” field is the optional sender field, to which a bundle's creator can assign any SID it wishes, so it carries no guarantee of validity. As an optimisation, when deducing the author, Serval DNA tries the sender identity first (if present and unlocked) before trying any others. In many cases (eg, MeshMS conversations), the sender turns out to be the author.

Payload

A Rhizome bundle's payload is a contiguous sequence of one or more bytes.

A zero-length payload is represented as "no payload" (filesize=0).

The interpretation of a payload's contents depends on the service that created the bundle (and can therefore also deal with it), whether or not the payload is encrypted (crypt=1), and other optional fields (eg, name) that the creator added to the manifest.

Journal

A journal is a special kind of bundle whose payload can only be altered by appending new content to the end or discarding old content from the beginning, never by changing existing content.

The presence of a tail field in the manifest indicates that a bundle is a journal. The tail of a journal is set to zero (0) when the journal is first created, and advanced in subsequent updates to indicate how much of the payload has been discarded since the beginning.

The filesize field of a journal gives the number of bytes currently in the payload, not counting those that have been discarded. In other words, the “logical length” of a journal's payload is tail + filesize, of which only the most recently appended filesize bytes are actually stored and transported.

The filehash field of a journal is the digest of the filesize bytes currently being stored and transported. This allows Rhizome synchronisation and storage to apply exactly the same manifest-payload consistency checks to journals and non-journals alike.

Journal updates obey the following rules:

  • must alter one or both of the tail and filesize fields
  • do not decrease the value of the tail field
  • do not decrease the sum of the tail and filesize fields
  • do not modify any bytes of an existing payload except to remove bytes from the start when increasing the tail field or add bytes to the end when increasing the filesize field

The Rhizome insert operation enforces these rules if it has access to a prior version of the journal, but this cannot provide a guarantee, since an update could be performed in the absence of a prior version, in which case the rules cannot be checked.

The Rhizome transport takes advantage of the append-only property of journals by only transferring the newly-appended end of a payload (the “head”) during synchronisation. The Rhizome store reclaims space from the stored payloads of journals by discarding bytes over which the tail has advanced.

Rhizome-specific REST API

text+binarysig manifest format

The Rhizome REST API accepts and returns manifests in only one format, denoted text+binarysig. The Content-Type for this format is rhizome/manifest; format=text+binarysig.

In future, other formats may be supported, for example, all-binary or all-text.

The TEXT part of this format lists key-value fields in arbitrary order, using the following grammar:

TEXT = ( KEY "=" VALUE "\n" ){0..*}
KEY = ALPHA ( ALPHANUM ){0..79}
VALUE = ( VALUECHAR ){0..*}
VALUECHAR = any ASCII except NUL "\r" "\n"

Following the text is a single NUL byte, followed by the signature section in a binary format. If the NUL byte is missing, then the manifest is unsigned.

The signature section consists of one or more concatenated signature blocks. Each block begins with a single type byte, followed by the bytes of the signature itself. The length of the signature is computed as type × 4 + 4.

The only supported signature type is 23 (hex 17), which is a 96-byte signature that is verified using Curve25519.

Rhizome HTTP response headers

All Rhizome requests that fetch or insert a single bundle, whatever the outcome, contain the following HTTP headers in the response:

Serval-Rhizome-Result-Bundle-Status-Code: <integer>
Serval-Rhizome-Result-Bundle-Status-Message: <text>
Serval-Rhizome-Result-Payload-Status-Code: <integer>
Serval-Rhizome-Result-Payload-Status-Message: <text>

Rhizome HTTP response bundle headers

All Rhizome requests that successfully fetch or insert a single bundle contain the following HTTP headers in the response, which convey the core manifest fields:

Serval-Rhizome-Bundle-Id: <hex64bid>
Serval-Rhizome-Bundle-Version: <integer>
Serval-Rhizome-Bundle-Filesize: <integer>

If filesize is not zero, then the following HTTP header is present:

Serval-Rhizome-Bundle-Filehash: <hex128>

If the bundle is a journal, then the following HTTP header is present:

Serval-Rhizome-Bundle-Tail: <integer>

In addition, none, some or all of the following HTTP headers may be present, to convey optional fields that are present in the bundle's manifest:

Serval-Rhizome-Bundle-Sender: <hex64sid>
Serval-Rhizome-Bundle-Recipient: <hex64sid>
Serval-Rhizome-Bundle-BK: <hex64>
Serval-Rhizome-Bundle-Crypt: 0 or 1
Serval-Rhizome-Bundle-Service: <token>
Serval-Rhizome-Bundle-Name: <quotedstring>
Serval-Rhizome-Bundle-Date: <integer>

All single-bundle operations, unless otherwise specified, attempt to deduce the bundle's author by finding whether the manifest's signature could be re-created using a Rhizome Secret from a currently unlocked identity in the keyring. If the manifest sender field is present or the author has been cached in the Rhizome database, then only that identity is tried, otherwise every single identity in the keyring is tested. If a signing identity is found, then the following HTTP header is present:

Serval-Rhizome-Bundle-Author: <hex64sid>

(In future, Serval DNA may cache the authors it discovers, to avoid redundant re-testing of all keyring identities, but cached authors will not be automatically treated as verified when read from the Rhizome database, because the database could be altered by external means.)

If the bundle's secret is known, either because it was supplied in the request or was deduced from the manifest's Bundle Key field and the author's Rhizome Secret, then the following HTTP header is present:

Serval-Rhizome-Bundle-Secret: <hex64>

The following HTTP headers might be present at the sole discretion of the server, but they are not guaranteed, and future upgrades of Serval DNA may remove them. They reveal internal details of the storage of the bundle:

Serval-Rhizome-Bundle-Rowid: <integer>
Serval-Rhizome-Bundle-Inserttime: <integer>

Rhizome JSON result

All Rhizome requests to fetch or insert a single bundle that do not produce a special response content for the outcome, return the following augmented JSON result object as the HTTP response content:

{
 "http_status_code": ...,
 "http_status_message": "...",
 "rhizome_bundle_status_code": ...,
 "rhizome_bundle_status_message": "...",
 "rhizome_payload_status_code": ...,
 "rhizome_payload_status_message": "..."
}

Bundle status code

All Rhizome operations that involve fetching and/or inserting a single manifest into the Rhizome store return a bundle status code, which describes the outcome of the operation. Some codes have different meanings in the context of a fetch or an insertion, and some codes can only be produced by insertions. The bundle status code determines the HTTP response code.

code HTTP meaning
-1 500 internal error
0 201 “new”; (fetch) bundle not found; (insert) bundle added to store
1 200 “same”; (fetch) bundle found; (insert) bundle already in store
2 200 “duplicate”; (insert only) duplicate bundle already in store
3 202 “old”; (insert only) newer version of bundle already in store
4 422 “invalid”; (insert only) manifest is malformed or invalid
5 419 “fake”; (insert only) manifest signature is invalid
6 422 “inconsistent”; (insert only) manifest filesize/filehash does not match payload
7 202 “no room”; (insert only) doesn't fit; store may contain more important bundles
8 419 “readonly”; (insert only) cannot modify manifest because secret is unknown
9 423 “busy”; Rhizome store database is currently busy (re-try)
10 422 “manifest too big”; (insert only) manifest size exceeds limit

Bundle status message

The bundle status message is a short English text that explains the meaning of its accompanying bundle status code, to assist with diagnosis. The message for a code may differ across requests and may change when Serval DNA is upgraded, so it cannot be relied upon as a means to programmatically detect the outcome of an operation.

Payload status code

All Rhizome operations that involve fetching and/or inserting a single payload into the Rhizome store return a payload status code, which describes the outcome of the payload operation, and elaborates on the the reason for the accompanying bundle status code. Some codes have different meanings in the context of a fetch or an insertion, and some codes can only be produced by insertions. The payload status code overrides the HTTP response code derived from the bundle status code if it is numerically higher.

code HTTP meaning
-1 500 internal error
0 201 empty payload (zero length)
1 201 (fetch) payload not found; (insert) payload added to store
2 200 (fetch) payload found; (insert) payload already in store
3 422 payload size does not match manifest filesize field
4 422 payload hash does not match manifest filehash field
5 419 payload key unknown: (fetch) cannot decrypt; (insert) cannot encrypt
6 202 (insert only) payload is too big to fit in store
7 202 (insert only) payload evicted; other payloads are ranked higher

Payload status message

The payload status message is short English text that explains the meaning of its accompanying payload status code, to assist diagnosis. The message for a code may differ across requests and may change when Serval DNA is upgraded, so it cannot be relied upon as a means to programmatically detect the outcome of an operation.

GET /restful/rhizome/bundlelist.json

This request allows a client to discover all the bundles currently held in the local Rhizome store.

Fetches a list of all bundles currently in Serval DNA's Rhizome store, in order of descending insertion time starting with the most recently inserted. The list is returned in the body of the response in JSON table format with the following columns:

  • .token - either null or a string value that can be used as the token in a newsince request.

  • _id - the Rhizome database row identifier; a unique integer per bundle with no guarantees of sequence or re-use after deletion.

  • service - the string value of the manifest's service field, or null if the manifest has no service field.

  • id - the Bundle ID; a string containing 64 hexadecimal digits.

  • version - the bundle version; a positive integer with a maximum value of 2^64 1.

  • date - the bundle publication time; an integral number of milliseconds since the Unix time epoch, or null if the manifest has no date field.

  • .inserttime - the time that the bundle was inserted into the local Rhizome store. This field is created using the local system clock, so comparisons with the date field cannot be relied upon as having any meaning.

  • .author - the SID of the local (unlocked) identity that created the bundle; either a string containing 64 hexadecimal digits, or null if the bundle author cannot be deduced. In the case of null, the .fromhere field will be 0 (“not authored here”). In the case of a SID, the .fromhere indicates whether authorship was absent, likely or certain.

  • .fromhere - an integer flag that indicates whether the bundle was authored on the local device:

    • 0 (“absent”) means that the bundle was not authored by any unlocked identity on this device.

    • 1 (“likely”) means that the author whose SID is given in the .author field is present in the local keyring but authorship (the manifest's signature) has not been cryptographically verified, so attempting to update this bundle may yet fail. This is the usual value because cryptographic verification is not performed while listing bundles, since it is slow and costly in CPU and battery.

    • 2 (“certain”) means that the author whose SID is given in the .author field is present in the local keyring and has been cryptographically verified as the true author of the bundle, ie, yields a correct Bundle Secret. This value will usually only be returned for locally-authored bundles that have recently been examined individually (eg, GET /restful/rhizome/BID.rhm), if Serval DNA has cached the result of the verification in memory.

  • filesize - the number of bytes in the bundle's payload; an integer zero or positive with a maximum value of 2^64 1.

  • filehash - if the bundle has a non-empty payload, then the SHA-512 hash of the payload content; a string containing 128 hexadecimal digits, otherwise null if the payload is empty (filesize = 0).

  • sender - the SID of the bundle's sender; either a string containing 64 hexadecimal digits, or null if the manifest has no sender field.

  • recipient - the SID of the bundle's recipient; either a string containing 64 hexadecimal digits, or null if the manifest has no recipient field.

  • name - the string value of the manifest's name field, or null if the manifest has no name field.

GET /restful/rhizome/newsince/TOKEN/bundlelist.json

This request allows a client to receive near-real-time notification of newly-arriving Rhizome bundles.

Fetches a list of all bundles currently in Serval DNA's Rhizome store, in order of ascending insertion time, since (but not including) the bundle identified by TOKEN. TOKEN must be a value taken from the non-null .token field of any previous bundlelist.json request.

The list is returned in the body of the response in JSON table format, exactly the same as GET /restful/rhizome/bundlelist.json, but with the following differences:

  • Bundles are listed in order of ascending, not descending, insertion time, ie, the most recent last.

  • Once all bundles have been listed, the response does not finish immediately, but blocks for approximately 60 seconds while waiting for new bundles to appear (get added to the Rhizome store).

  • The final “]}” of the JSON table “rows” array and top-level object are not sent until the response finishes, so in order to make proper use of this request, the client must be able to incrementally parse partial JSON as it arrives.

GET /restful/rhizome/BID.rhm

Fetches the manifest for the bundle whose id is BID (64 hex digits), eg:

/restful/rhizome/1702BD647D614DB72C36BD634B6870CA31040C2EEC5069AEC0C0841D0CC671BE.rhm

If the manifest is found in the local Rhizome store, then the response will be 200 OK and:

  • the bundle status code will be 1
  • the payload status code, if present in the response, is not relevant, so must be ignored
  • the Rhizome response bundle headers give information about the found bundle, some of which is duplicated from the manifest
  • the response's Content-Type is rhizome/manifest; format=text+binarysig
  • the response's Content-Length is the size, in bytes, of the manifest with its binary signature appended
  • the response's content is the Rhizome manifest in text+binarysig format

If the manifest is not found in the local Rhizome store, then the response will be 404 Bundle not found and:

GET /restful/rhizome/BID/raw.bin

Fetches the "raw" (encrypted) payload for the bundle whose id is BID (64 hex digits), eg:

/restful/rhizome/1702BD647D614DB72C36BD634B6870CA31040C2EEC5069AEC0C0841D0CC671BE/raw.bin

If the manifest and the payload are both found in the local Rhizome store, then the response will be 200 OK and:

  • the bundle status code will be 1
  • the payload status code will be 0 if the payload has zero length, otherwise 2
  • the Rhizome response bundle headers give information about the found bundle, some of which is duplicated from the manifest
  • the response's Content-Type is application/octet-stream
  • the response's Content-Length is the size, in bytes, of the raw payload
  • the response's content is the bundle's payload exactly as stored in Rhizome; if the payload is encrypted (the manifest's crypt field is 1) then the payload is not decrypted

If the manifest is not found in the local Rhizome store, then the response will be 404 Bundle not found and:

If the manifest is found in the local Rhizome store but the payload is not found, then the response will be 404 Payload not found and:

GET /restful/rhizome/BID/decrypted.bin

Fetches the decrypted payload for the bundle whose id is BID (64 hex digits), eg:

/restful/rhizome/1702BD647D614DB72C36BD634B6870CA31040C2EEC5069AEC0C0841D0CC671BE/decrypted.bin

The responses are identical to those for GET /restful/rhizome/BID/raw.bin, with the following additional case:

If the manifest and payload are both found and the payload is encrypted (the manifest's crypt field is 1), but the payload secret is not known, then:

For a bundle that has a sender and a recipient, the payload secret is determined as follows:

  • if the recipient's identity is found (unlocked) in the keyring, then the secret is derived from the recipient's Serval ID secret; otherwise
  • if the recipient's identity is not found in the keyring (locked or missing) but the sender's identity is found (unlocked) in the keyring, then the secret is derived from the sender's Serval ID secret; otherwise
  • neither identity is found in the keyring (both are locked or missing), so the payload secret is unknown.

For all other bundles, the payload secret is derived directly from the Bundle Secret, whether supplied as a query parameter or deduced from the bundle's Bundle Key. If the Bundle Secret is unknown, then the payload secret is unknown.

POST /restful/rhizome/insert

This request allows a client to add a new bundle to the Rhizome store, or update an existing bundle in the store. This request cannot be used to create or update journals; use the append request instead.

Takes the following parameters, all optional under various conditions:

  • bundle-id The Bundle ID of an existing bundle to update; 64 hexadecimal digits. If the bundle currently exists in the Rhizome store then a copy of its manifest is used as the basis of the new bundle, omitting its version, filesize, filehash fields (which must be supplied or inferred anew).

  • bundle-author The SID of the bundle's author:

    • 64 hexadecimal digits;
    • the bundle author sets (or removes) the bundle's BK field, overriding any BK field in the partial manifest supplied in the manifest parameter or in the existing bundle nominated by the bundle-id parameter;
    • if there is no unlocked identity in the keyring with the given SID, then the new bundle will be an anonymous bundle with no BK field;
    • this parameter must come before the manifest parameter, otherwise the request fails with status 400 and the message Spurious "bundle-id" form part.
  • bundle-secret The Bundle Secret; 64 hexadecimal digits. This is needed in order to create a bundle with a specific Bundle ID (supplied in the manifest parameter), or to update an existing bundle that is anonymous or the author is not a currently unlocked identity in the keyring.

  • manifest A partial, unsigned manifest in text+binarysig format, with a correct Content-Type header. The fields in this manifest are used to form the new bundle's manifest, overwriting the fields of any existing manifest specified by the bundle-id parameter, if given.

  • payload The content of the new bundle's payload:

    • the form part's Content-Type header is currently ignored, but in future it may be used to determine the default values of some manifest fields;
    • this parameter must occur after the manifest parameter, otherwise the request fails with status 400 and the message Missing "manifest" form part;
    • the payload parameter must not be supplied if the filesize field in the manifest parameter is zero.

The insertion logic proceeds in the following steps:

  1. If the partial manifest supplied in the manifest parameter is malformed (syntax error) or contains a core field with an invalid value, then the request fails with status 422 and the bundle status code for “invalid”.

  2. If a bundle-id parameter was supplied and the given bundle exists in the Rhizome store, then the new bundle's manifest is initialised by copying all the fields from the existing manifest.

  3. If a partial manifest was supplied in the manifest parameter, then its fields are copied into the new manifest, overwriting any that were copied in step 2.

  4. If the tail field is present in the new manifest then the new bundle is a journal, so the request fails with status 422 and the bundle status code for “invalid”. Journals can only be created and updated using the append request.

  5. If the bundle-secret parameter was supplied, then a public key (Bundle ID) is derived from the Bundle Secret, and:

    • if the new manifest has no id field, then the id field is set to the derived public key;

    • otherwise, if the new manifest's id field is not equal to the derived public key, then the supplied secret is wrong, so the request fails with status 419 and the bundle status code for “readonly”;

    Otherwise, if no bundle-secret parameter was supplied:

    • if the new manifest has no id field, then a new Bundle Secret is generated randomly, the Bundle ID is derived from the new Bundle Secret, and the id field set to that Bundle ID;

    • if the new manifest already has an id field but no BK field (Bundle Key) (ie, the bundle is anonymous), then the Bundle Secret cannot be discovered, so the request fails with status 419 and the bundle status code for “readonly”.

    • otherwise, if the bundle-author parameter was given, then that SID is looked up in the keyring. If the identity is found, then the Bundle Secret is derived from the combination of the BK field (Bundle Key) with the identity's Rhizome Secret, and the Bundle ID is derived from the Bundle Secret. If the identity was not found or the derived Bundle ID does not equal the id field then the request fails with status 419 and the bundle status code for “readonly”.

    • otherwise, if no bundle-author parameter was given, then the keyring is searched for an identity whose Rhizome Secret combined with the BK field (Bundle Key) produces a Bundle Secret whose derived Bundle ID matches the id field. The search starts with the identity given by the sender field, if present. If none is found, then the request fails with status 419 and the bundle status code for “readonly”, otherwise the author is deduced to be the found identity.

  6. If the bundle-author parameter was given and step 5 set the id field (either derived from the bundle-secret parameter or randomly generated), then the bundle-author SID is looked up in the keyring. If not found, then the request fails with status 419 and the bundle status code for “readonly”. If found, then the author's Rhizome Secret is used to calculate the Bundle Key and set the BK field.

  7. The following fields are initialised if they are missing:

    • service to the value file
    • version to the current Unix time in milliseconds since the epoch
    • date to the current Unix time in milliseconds since the epoch
    • crypt to 1 if the sender and recipient fields are both set
  8. If the payload parameter is given and is non-empty, then its value is stored in the store, and its size and SHA-512 digest computed. If the manifest is missing either or both of the filesize and filehash fields, then the missing ones are filled in from the computed values. If the manifest had a filesize or filehash field that does not match the computed value, then the request fails with status 422 and the bundle status code for “inconsistent”.

  9. The manifest is validated to ensure that:

    • the id field is present
    • the version field is present
    • the filesize field is present
    • if filesize is zero then there is no filehash field
    • if filesize is non-zero then the filehash field is present
    • if service is file then a name field is present
    • if service is MeshMS1 or MeshMS2 then the sender and recipient fields are both present
    • the service field contains no invalid characters
    • the date field is present

    If validation fails, the request fails with status 422 and the bundle status code for “invalid”.

  10. If step 5 set the id field (either derived from the bundle-secret parameter or randomly generated) and the bundle is a duplicate of a bundle that is already in the store, then the request finishes with status 200 and the bundle status code for “duplicate”. Bundles are considered duplicates if they have:

    • an identical payload (identical filesize and filehash fields), and
    • the same service field, and
    • the same name field, and
    • the same sender field, and
    • the same recipient field.
  11. The manifest is signed using the Bundle Secret, and the signature appended to the manifest after a single ASCII NUL (0) separator byte. If the result exceeds the maximum manifest size (8 KiB) then the request fails with status 422 and the bundle status code for “manifest too big”.

  12. If the Rhizome store already contains a manifest with the same Bundle ID, then its version is compared with the new manifest's version.

    • If they have the same version, then the new manifest is not stored, and the request returns status 200 and the bundle status code for “same”.

    • If the new manifest's version is less than the stored manifest's, then the new manifest is not stored, and the request returns status 202 and the bundle status code for “old”.

  13. The new manifest is stored in the Rhizome store, replacing any existing manifest with the same Bundle ID. The request returns status 201 and the bundle status code for “new”.

POST /restful/rhizome/append

This request allows a client to add a new journal bundle to the Rhizome store, or update an existing one. It takes exactly the same parameters as the insert operation, to which it is identical in all respects except as follows:

The steps of the insertion logic have these variations:

  1. The validity checks on any partial manifest given in the manifest parameter will also fail if the partial manifest contains a version, filesize or filehash field.

  2. If the bundle-id parameter specifies an existing manifest, then the version, filesize and filehash fields are not copied from the existing manifest to the new manifest.

  3. After the partial manifest has been copied into the new manifest, if the bundle-id parameter was not given or specified a bundle that was not found in the store (step 2), then the filesize and tail fields are initialised to zero (0) if they are missing.

  4. If the tail field is missing from the new manifest then the bundle is not a journal, so the request fails with status 422 and the bundle status code for “invalid”.

  5. No change.

  6. No change.

  7. No change.

  8. After the payload has been stored, the filesize and filehash fields are always set, overriding any that were already present. Also, the version is always set to tail + filesize.

  9. No change.

  10. No change.

  11. No change.

  12. No change.

  13. No change.

MeshMS REST API

Basic concepts

Conversation

TBC

Ply

GET /restful/meshms/RECIPIENTSID/conversationlist.json

TBC

GET /restful/meshms/SENDERSID/RECIPIENTSID/messagelist.json

TBC

GET /restful/meshms/SENDERSID/RECIPIENTSID/newsince/TOKEN/messagelist.json

TBC

POST /restful/meshms/SENDERSID/RECIPIENTSID/sendmessage

TBC


Copyright 2015 Serval Project Inc.
CC-BY-4.0 Available under the Creative Commons Attribution 4.0 International licence.