In November 2013, The Serval Project commenced a fourth round of work for the New America Foundation's Open Technology Institute to develop a RESTful API for Rhizome. The aim is to allow Commotion Wireless and other third party Android apps to use Serval's secure, authenticated and non-centralised file distribution.
THE FOLLOWING DESCRIPTION OF WORK DOES NOT SPECIFY THE CURRENT RHIZOME OR MESHMS REST APIS. SEE RHIZOME API AND MESHMS API FOR THE UP-TO-DATE DEFINITIONS.
1. Extend the Rhizome C API within Serval DNA to support additional interconnected applications, implementing the deliverables listed below:
localhost:4110/restful/rhizome/bundlelist.json
in servald that returns the list of all Rhizome bundles as JSON formatted text. The list will include a token that can be supplied to GET /restful/rhizome/newsince/<token>/bundlelist.json
. The token should be considered an opaque symbol, whose format may vary between implementations and instances of servald (see technical notes N1, N2, N3 and N4).localhost:4110/restful/rhizome/newsince/<token>/bundlelist.json
in servald that returns the list of all Rhizome bundles added or updated after the Rhizome database state indicated by <token> as JSON formatted text. The token should be considered an opaque symbol, whose format may vary between implementations and instances of servald (see N2). The request will block for up to 60 seconds until results are available, making this request suitable for efficient polling for new bundles (see N5). If no results are available the request will eventually timeout and complete with HTTP response code 204 and no data (see technical variation V1).localhost:4110/restful/rhizome/<bundleID>/manifest.bin
(see V2) in servald that returns the manifest of the specified bundle.localhost:4110/restful/rhizome/<bundleID>/raw.bin
in servald that returns the raw associated file of the specified bundle. If the bundle is encrypted, it is the encrypted file that will be returned.localhost:4110/restful/rhizome/<bundleID>/decrypted.bin
in servald that returns the associated file of the specified bundle, if possible. If the bundle is encrypted, it is the decrypted file that will be returned. If it is not possible to decrypt the bundle, respond with HTTP response code 412.localhost:4110/restful/rhizome/insert
in servald that inserts the bundle manifest and file provided through the POST request (see N7).localhost:4110/restful/meshms/<SID>/conversationlist.json
in servald that returns the list of all MeshMS conversations as JSON formatted text.localhost:4110/restful/meshms/<toSID>/<fromSID>/messagelist.json
that returns the list of messages between the two parties as JSON formatted text (see N10, N12 and N13). Each message will be identified by a unique token. The tokens should be considered opaque symbols, whose format may vary between implementations and instances of servald (see N11). Each message will also be accompanied by a delivery status of either unacknowledged, delivered (for messages sent by this node), or received (for messages sent by the remote party) (see technical variation V3).localhost:4110/restful/meshms/<toSID>/<fromSID>/newsince/<token>/messagelist.json
that returns the list of messages between the two parties as JSON formatted text (see N10, N12 and N13). Only messages newer than the message corresponding to the supplied token (see N11) will be returned. The request will block for up to 60 seconds until results are available (see N14 and N15), making this request suitable for efficient polling for new bundles. If no results are available the request will eventually timeout and complete with HTTP response code 204 and no data (see technical variations V1 and V3).localhost:4110/restful/meshms/<toSID>/<fromSID>/sendmessage/<toDID>/<fromDID/<URL-coded-message-text>
that attempts to send a MeshMS message. Total URL length is limited to 1000 characters. Message body must be representable as a UTF-8 string (see technical variations V3, V4 and V5).The following variations to requirements were made during the course of the contract without prior consultation because they did not alter the intent or functionality of the original requirements.
/restful/rhizome/<bundleID>.rhm
so that applications which store the downloaded manifest will use <BundleID>.rhm
as the default file name. The RHM file extension was chosen because a manifest file is not strictly a text file (TXT), since the text portion is followed by a NUL byte and the binary signature block.The following implementation decisions were made during the course of the contract.
GET /restful/rhizome/bundlelist.json
and GET /restful/rhizome/newsince/<token>/bundlelist.json
has the following structure:{ "header":[".token","_id","service","id","version","date",".inserttime",".author",".fromhere","filesize","filehash","sender","recipient","name"], "rows":[ ["-luCgeURRKqvbRKQilANXwcAAAAAAAAA",7,"file","F81645C6693A98EB4D2727A7A3D9F478FF77CC60F68949679BFD2A7C65A29E89",1384921558580,1384921558581,1384921558648,"A8BFD18D5BF6E005E96E62188F553F0149B10AD03B47B7A35181457B2A1ABF69",1,1006,"A49C795AE4687CB14F6F1F01265C1DC6472935B125F52ADE4F166A31770E508AF2E0AF9F48D9F6826D60C7B5B820C63028EFD78AD32B9EBA5E43A2B60D74C9E8",null,null,"file6"], ... ] }
This is more compact than the most convenient JSON representation, which would be an array of JSON objects. An array of JSON objects would redundantly repeat all the header labels within every single object. The chosen representation can easily be transformed into an array of JSON objects. The test script depends on version 1.3 of the jq(1) utility to perform this transformation.
GET /restful/rhizome/bundlelist.json
returns bundles in reverse chronological order (most recent first). This was chosen because, in general, applications are likely to be interested in the most recent bundles (even were filtering functions added in future), and it allows applications to cut off the response once they have received enough rows simply by closing the HTTP connection prematurely.GET /restful/rhizome/bundlelist.json
and GET /restful/rhizome/newsince/<token>/bundlelist.json
as an optional per-row attribute which is only present in the first row and in any row thereafter which is more recent than the row containing the previous token. This means that any future changes which alter the row order will not introduce incompatibilities in any client which follows the correct semantic of always using the last token received when forming the GET /restful/rhizome/newsince/<token>/bundlelist.json
URL.GET /restful/rhizome/newsince/<token>/bundlelist.json
returns bundles in chronological order (oldest first). This is because the temporally incremental nature of the request makes it impossible to return bundles in reverse chronological order (as in N3) except by waiting for the end of the 60-second timeout and sending them all in a single burst, which would not yield the kind of immediate interactive functionality that is desired. One consequence of this is that every row will contain a new token (see N4), which has the benefit that the HTTP client can close the connection at any point and initiate a new request using the latest token it received, without missing any bundles.Serval-Rhizome-Result-Payload-Status-Code: -1|0|1|2|3|4|5|6|7 Serval-Rhizome-Result-Payload-Status-Message: <text> Serval-Rhizome-Result-Bundle-Status-Code: -1|0|1|2|3|4|5|6|7|8 Serval-Rhizome-Result-Bundle-Status-Code: <text> Serval-Rhizome-Bundle-Id: <BundleIDHex> Serval-Rhizome-Bundle-Version: <ASCIIDecimal> Serval-Rhizome-Bundle-Filesize: <ASCIIDecimal> Serval-Rhizome-Bundle-Filehash: <FilehashHex> Serval-Rhizome-Bundle-BK: <BundleKeyHex> Serval-Rhizome-Bundle-Crypt: 0|1 Serval-Rhizome-Bundle-Tail: <ASCIIDecimal> Serval-Rhizome-Bundle-Date: <ASCIIDecimal> Serval-Rhizome-Bundle-Name: <NameQuotedString> Serval-Rhizome-Bundle-Service: <token> Serval-Rhizome-Bundle-Author: <AuthorSIDHex> Serval-Rhizome-Bundle-Secret: <BundleSecretHex> Serval-Rhizome-Bundle-Rowid: <ASCIIDecimal> Serval-Rhizome-Bundle-Inserttime: <ASCIIDecimal>
Serval-Rhizome-Result-Payload-Status-Code
values are encapsulated in the new Java class org.servalproject.servaldna.rhizome.RhizomePayloadStatusServal-Rhizome-Result-Bundle-Status-Code
values are encapsulated in the new Java class org.servalproject.servaldna.rhizome.RhizomeBundleStatusServal-Rhizome-Bundle-Filehash
header is only present if the bundle filesize is nonzero.Serval-Rhizome-Bundle-BK
header is only present if the manifest contains a Bundle Key (BK
) fieldServal-Rhizome-Bundle-Crypt
header is only present if the manifest contains a crypt
fieldServal-Rhizome-Bundle-Tail
header is only present if the manifest contains a tail
field, ie, is a journalServal-Rhizome-Bundle-Date
header is only present if the manifest contains a date
fieldServal-Rhizome-Bundle-Name
header is only present if the manifest contains a name
fieldServal-Rhizome-Bundle-Service
header is only present if the manifest contains a service
fieldServal-Rhizome-Bundle-Author
header is only present if the identity that created the bundle is present and currently unlocked in the keyring.Serval-Rhizome-Bundle-Secret
header ditto.Serval-Rhizome-Bundle-Rowid
header exposes an implementation detail of the Rhizome storage database, and as such, is not guaranteed to be present.POST /restful/rhizome/insert
request cannot be used to inject a Journal Bundle, and produces a 501 “Not Implemented” HTTP response. A future “journal append” operation will have to be implemented to support Rhizome journals.Content-Type: application/json
. Error results return a single JSON object that contains the HTTP status code and a textual message:{ "http_status_code": 403, "http_status_message": "Missing form parameter" }
{ "http_status_code": 200, "http_status_message": "OK", "rhizome_payload_status_code": 2, "rhizome_payload_status_message": "Payload already in store" "rhizome_bundle_status_code": 3, "rhizome_bundle_status_message": "Newer bundle already in store" }
GET /restful/meshms/<SID>/<SID>/messagelist.json
and GET /restful/meshms/<SID>/<SID>/newsince/token/messagelist.json
has the following structure:{ "read_offset":29, "latest_ack_my_offset":76, "header":["type","my_sid","their_sid","offset","token","text","delivered","read","ack_offset"], "rows":[ [">","EEBF3AC19E7EE58722A0F6D4A4D5894A72F5C71030C3399FE75808DCF6C6254B","C10C91D24BF210DD6733ED2424B4509E6CC4402D34055B6D29F7A778701AA542",105,"yytLhh7a4lraha6uj5E1e3c3itq1yIoxwPpWP6c3JYJpAAAAAAAAAA==","Text of fourth message",false,false,null], ["<","EEBF3AC19E7EE58722A0F6D4A4D5894A72F5C71030C3399FE75808DCF6C6254B","C10C91D24BF210DD6733ED2424B4509E6CC4402D34055B6D29F7A778701AA542",56,"Wn7oquN__5k0tI317ZcH9DszrdpEqnzjn3nZbvaK3fY4AAAAAAAAAA==","Text of second reply",true,false,null], ["ACK","EEBF3AC19E7EE58722A0F6D4A4D5894A72F5C71030C3399FE75808DCF6C6254B","C10C91D24BF210DD6733ED2424B4509E6CC4402D34055B6D29F7A778701AA542",33,"Wn7oquN__5k0tI317ZcH9DszrdpEqnzjn3nZbvaK3fYhAAAAAAAAAA==",null,true,false,76], [">","EEBF3AC19E7EE58722A0F6D4A4D5894A72F5C71030C3399FE75808DCF6C6254B","C10C91D24BF210DD6733ED2424B4509E6CC4402D34055B6D29F7A778701AA542",76,"yytLhh7a4lraha6uj5E1e3c3itq1yIoxwPpWP6c3JYJMAAAAAAAAAA==","Text of third message",true,false,null], ["<","EEBF3AC19E7EE58722A0F6D4A4D5894A72F5C71030C3399FE75808DCF6C6254B","C10C91D24BF210DD6733ED2424B4509E6CC4402D34055B6D29F7A778701AA542",29,"Wn7oquN__5k0tI317ZcH9DszrdpEqnzjn3nZbvaK3fYdAAAAAAAAAA==","Text of first reply",true,true,null], [">","EEBF3AC19E7EE58722A0F6D4A4D5894A72F5C71030C3399FE75808DCF6C6254B","C10C91D24BF210DD6733ED2424B4509E6CC4402D34055B6D29F7A778701AA542",49,"yytLhh7a4lraha6uj5E1e3c3itq1yIoxwPpWP6c3JYIxAAAAAAAAAA==","Text of second message",true,false,null], [">","EEBF3AC19E7EE58722A0F6D4A4D5894A72F5C71030C3399FE75808DCF6C6254B","C10C91D24BF210DD6733ED2424B4509E6CC4402D34055B6D29F7A778701AA542",24,"yytLhh7a4lraha6uj5E1e3c3itq1yIoxwPpWP6c3JYIYAAAAAAAAAA==","Text of first message",true,false,null] ] }
N2 describes why this structure is used instead of an array of JSON objects. The per-message fields are:
type
– ">"
for a sent message, "<"
for a received message, "ACK"
for a received ACKmy_sid
– the Serval ID (identity) of the sender (the first SID in the request URL)their_sid
– the Serval ID (identity) of the recipient (the second SID in the request URL)offset
– the byte position within the respective Journal Bundle (ply) where the message is stored – there are two plys: one for sent messages, the other for received, and it is only meaningful to compare offsets within the same plytoken
– described in N11text
– the content of the message (null
for ACKs)delivered
– equals (offset <= latest_ack_offset)
for sent messages, and is always true
for received messages and ACKsread
– equals (offset <= read_offset)
for received messages and ACKs, and is always false
for sent messagesack_offset
– the byte offset within the sender's ply of the sent message that the recipient is acknowledging as delivered – equal to (latest_ack_offset)
– null
for non-ACKsmy_sid
and their_sid
fields per-row so that the same JSON structure can be used in future for new HTTP RESTful requests that could return messages from more than one conversationGET /restful/meshms/<SID>/<SID>/messagelist.json
response contains at most one ACK row, which is the latest (most recent) ACK from the recipient. Its ack_offset
field will be equal to the "latest_ack_offset"
field in the top-level object.GET /restful/meshms/<SID>/<SID>/newsince/<TOKEN>/messagelist.json
response can contain more than one ACK row, as ACKs are output as they are received. The "latest_ack_offset"
top-level object field is therefore omitted from this response.GET /restful/meshms/<SID>/<SID>/newsince/<TOKEN>/messagelist.json
response may return sent messages whose read
field is true
even though prior (lower offset) sent messages have a false
value. This occurs because the read_offset
marker can advance during the course of the request. Once a sent message is marked as read, all prior sent messages are also implicitly read (there is no per-message read/unread flag). The "read_offset"
top-level object field is therefore omitted from this response.POST /restful/meshms/<SID>/readall
marks all the messages in all conversations as readPOST /restful/meshms/<SID>/<SID>/readall
marks all the messages in a single conversation as readPOST /restful/meshms/<SID>/<SID>/recv/<offset>/read
advances the read-offset in a single conversation, to mark a single message (and all its predecessors) as readThe work was completed on 14 July 2014, having been performed concurrently with three other contracts:
This contract drove several significant architectural improvements to Serval DNA and Serval Mesh.
One issue that must be addressed when building any external interface is security.
The next issue to be addressed was the existing Rhizome HTTP server code.
multipart/form-data
body;Henceforward the RESTful API will be the preferred (and eventually only) method for applications to use Rhizome and MeshMS.
rhizome.api.restful.
options to api.restful.
because the RESTful interface will be used for services other than Rhizome (eg, MeshMS)The final issue to be addressed was the CPU activity of the Serval DNA daemon whilst idle.
R1(a) – HTTP Basic Authorization and loopback interface restriction
/restful/rhizome/bundlelist.json
resource to return a 401 Unauthorized response unless a correct Basic Authentication user/password pair are supplied as per RFC2617/restful/rhizome/bundlelist.json
resource to return a 403 Forbidden response unless the request arrives over the local loopback interface/restful/rhizome/bundlelist.json
resource were factored into a function that was re-used to implement the other resources
R2 – GET /restful/rhizome/bundlelist.json
request
/restful/rhizome/bundlelist.json
that had already been implemented by the Basic Authentication work (see above)/restful/rhizome/newsince/<token>/bundlelist.json
request to another database.
R3 – GET /restful/rhizome/newsince/<token>/bundlelist.json
request
GET /restful/rhizome/newsince/<token>/bundlelist.json
request was implemented using the same JSON content generation code as GET /restful/rhizome/bundlelist.json
modified as follows:<token>
and invokes the same JSON content generator with different parameters
R4 – GET /restful/rhizome/<BundleID>.rhm
request (see V2) (completed 13 December)
R5 – GET /restful/rhizome/<BundleID>/raw.bin
request (completed 13 December)
R6 – GET /restful/rhizome/<BundleID>/decrypted.bin
request (completed 16 December)
R7 – POST /restful/rhizome/insert
request (completed 30 December)
R8 – MeshMS in Serval DNA (initial work completed 5 August, finalised on 30 December 2013)
Merge into mainline development branch (completed 30 December 2013)
JSON responses (completed 20 January 2014)
R9 – GET /restful/meshms/<SID>/conversationlist.json
request (completed 22 January 2014, fixed 26 June 2014)
R10 – GET /restful/meshms/<fromSID>/<toSID>/messagelist.json
request (see V3) (completed 24 January 2014)
meshms list messages
code to use an iterator
R11 – GET /restful/meshms/<fromSID>/<toSID>/newsince/<TOKEN>/messagelist.json
request (see V3) (completed 31 January 2014)
R12 – POST /restful/meshms/<fromSID>/<toSID>/sendmessage
request (see V4 and V5) (completed 7 February 2014)
Merge into mainline development branch (completed 7 February 2014)
R13 – Java API (completed 14 July 2014)
R14 – Automated tests (completed 11 July 2014)
plain/text; charset=utf-8
) in other testsThe test code coverage for the new C source code created by this contract exceeds the average:
Lines | Functions | |||
---|---|---|---|---|
http_server.c | 70% | 927/1325 | 95.5% | 64/67 |
meshms_restful.c | 89% | 316/355 | 100% | 21/21 |
rhizome_restful.c | 82% | 410/500 | 100% | 24/24 |
Average for all Serval DNA source code | ||||
*.c *.h | 72.2% | 17904/24796 | 81.9% | 1516/1851 |
There are no code coverage statistics for the Java source code.
R15 – upgrade Serval Mesh app for Android (completed 25 June 2014)
Merge into mainline development branch (completed 11 July 2014)