Table of Contents
New America Foundation - Contractor Agreement #31A1D0T1CY14 “NAF4”
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.
Section 1: Scope of Work
1. Extend the Rhizome C API within Serval DNA to support additional interconnected applications, implementing the deliverables listed below:
- R1(a). Implement an access control scheme, using HTTP Basic authentication in the first instance, that controls access to the following APIs, identified in subsequent deliverable items by their URL. A password and permissions file will be used to record the authorities given to each password. Requests will only be accepted on the loopback interface.
- R2. Implement URL
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 toGET /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). - R3. Implement URL
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). - R4. Implement URL
localhost:4110/restful/rhizome/<bundleID>/manifest.bin
(see V2) in servald that returns the manifest of the specified bundle. - R6. Implement URL
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. - R7. Implement URL
localhost:4110/restful/rhizome/insert
in servald that inserts the bundle manifest and file provided through the POST request (see N7). - R10. Implement URL
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). - R11. Implement URL
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). - R12. Implement URL
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). - R13. Create a cross-platform Java library that provides bindings to each of the APIs introduced above (see N16).
- R15. Reimplement the MeshMS interface in the Serval Mesh Android App to use the Java library (see N16).
Technical variations
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.
- V2. In R4, the URL was changed to
/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. - V4. In R12, the DID fields were removed from the URL, since they are not carried in MeshMS2 messages (they were a legacy from the MeshMS1 design)
Technical notes
The following implementation decisions were made during the course of the contract.
- N1. In R2 and R3, the JSON output of
GET /restful/rhizome/bundlelist.json
andGET /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.
- N2. The token implemented in R2 and R3 is encoded using Base64 for URL, and comprises the unique UUID of the Rhizome database plus the ROWID of the SQLite MANIFESTS table row, which increases monotonically with every insert or alteration.
- N3. In R2,
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. - N4. In R2 and R3, the token is embedded in the JSON output of
GET /restful/rhizome/bundlelist.json
andGET /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 theGET /restful/rhizome/newsince/<token>/bundlelist.json
URL. - N5. In R3,
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. - N6. In R4, R5, R6 and R7, the HTTP response contains the following extra headers that describe the outcome of the Rhizome payload operation and the bundle operation:
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>
- The
Serval-Rhizome-Result-Payload-Status-Code
values are encapsulated in the new Java class org.servalproject.servaldna.rhizome.RhizomePayloadStatus - The
Serval-Rhizome-Result-Bundle-Status-Code
values are encapsulated in the new Java class org.servalproject.servaldna.rhizome.RhizomeBundleStatus - The
Serval-Rhizome-Bundle-Filehash
header is only present if the bundle filesize is nonzero. - The
Serval-Rhizome-Bundle-BK
header is only present if the manifest contains a Bundle Key (BK
) field - The
Serval-Rhizome-Bundle-Crypt
header is only present if the manifest contains acrypt
field - The
Serval-Rhizome-Bundle-Tail
header is only present if the manifest contains atail
field, ie, is a journal - The
Serval-Rhizome-Bundle-Date
header is only present if the manifest contains adate
field - The
Serval-Rhizome-Bundle-Name
header is only present if the manifest contains aname
field - The
Serval-Rhizome-Bundle-Service
header is only present if the manifest contains aservice
field - The
Serval-Rhizome-Bundle-Author
header is only present if the identity that created the bundle is present and currently unlocked in the keyring. - The
Serval-Rhizome-Bundle-Secret
header ditto. - The
Serval-Rhizome-Bundle-Rowid
header exposes an implementation detail of the Rhizome storage database, and as such, is not guaranteed to be present.
- N7. In R7 the
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. - N8. The HTTP status code 403 “Forbidden” is returned if a request is well-formed by the rules of HTTP and obeys the RESTful API, but provokes a failure response that is not the fault of the caller nor a defect in the interface, for example running out of space in the Rhizome store, or missing a necessary bundle
- N9(a). The JSON object returned from Rhizome operations R4, R5, R6 and R7 contains four extra fields describing the outcome of the Rhizome payload operation and the bundle operation:
{ "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" }
- N10. In R10 and R11, the JSON output of
GET /restful/meshms/<SID>/<SID>/messagelist.json
andGET /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 alwaystrue
for received messages and ACKsread
– equals(offset <= read_offset)
for received messages and ACKs, and is alwaysfalse
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-ACKs
- N11. The token implemented in R10 and R11 is encoded using Base64 for URL, and comprises the Bundle ID of the respective ply (sender or recipient) and the message's byte position (offset) within that bundle
- N12. The JSON described in N10 contains
my_sid
andtheir_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 conversation - N13. In R10, the
GET /restful/meshms/<SID>/<SID>/messagelist.json
response contains at most one ACK row, which is the latest (most recent) ACK from the recipient. Itsack_offset
field will be equal to the"latest_ack_offset"
field in the top-level object. - N14. In R11, the
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. - N15. In R11, the
GET /restful/meshms/<SID>/<SID>/newsince/<TOKEN>/messagelist.json
response may return sent messages whoseread
field istrue
even though prior (lower offset) sent messages have afalse
value. This occurs because theread_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 read
COMPLETION REPORT
The work was completed on 14 July 2014, having been performed concurrently with three other contracts:
Preliminary work
This contract drove several significant architectural improvements to Serval DNA and Serval Mesh.
Security: SQL injections
One issue that must be addressed when building any external interface is security.
- A class of potential SQL injection vulnerabilities in the existing HTTP server was fixed (see Serval DNA issue #69).
- This prompted a re-factor of the Serval DNA source code to regularise the use of internal types for representing fundamental types like SID, Bundle ID, MDP port number, and so forth (see Serval DNA issue #11 and Serval DNA Git commits 3758b03, and a95ef79 and 221fc4a).
- As a result, many poor coding patterns that were susceptible to classic vulnerability bugs (eg, interpreted code injection and buffer overruns) were replaced with safe and far more rigorous coding patterns.
- This provided a sound code base upon which to embark on implementation of a new HTTP REST API.
New HTTP server
The next issue to be addressed was the existing Rhizome HTTP server code.
- The only HTTP clients to date had been other Android apps by the Serval Project and Serval DNA itself, which sent small, known header blocks.
- HTTP requests were being parsed by reading the entire header block into memory and parsing it using a combination of pointer work and sscanf(3). Much of this code was duplicated between rhizome_http.c and rhizome_direct_http.c (from commit 00cf617).
- The HTTP request parsing code needed improvement in order to support all potential HTTP clients, and to provide a sound basis for parsing more HTTP headers such as Authorization and Content-Type.
- A completely new HTTP request parser was written (http_server.c) which:
- conserves memory by using a single buffer for reading and disassembling an HTTP request header and
multipart/form-data
body; - adheres more closely to published standards (eg, now parses tokens using the proper lexical character set instead of any non-space sequence, and corrects an off-by-one mistake in the existing interpretation of the Range header);
- uses progressive parsing that does not require the entire header block to fit within the buffer, just each header line;
- makes extensive use of assert(3) and abort(3) to ensure that buffer overruns and other logical errors do not go undetected (if there is any buffer overrun vulnerability, it is likely to cause Serval DNA to terminate, so the only potential attack is likely to be denial of service rather than information disclosure or remote code execution;
- formats and sends an HTTP response using the same buffer used for receiving the request, allowing the content (body) to either be provided statically (already rendered into a temporary buffer) or generated dynamically (rendered progressively into a single buffer as the response is sent);
- The existing Rhizome HTTP interfaces (Rhizome HTTP transport and Rhizome Direct) were rewritten to use the new HTTP request parser.
- The new HTTP request parser is modular and is not tied to the existing Rhizome implementation, so it may easily be re-used to implement other HTTP interfaces in future.
Re-organisation around RESTful API
Henceforward the RESTful API will be the preferred (and eventually only) method for applications to use Rhizome and MeshMS.
- The existing JNI API for Rhizome and MeshMS was inferior:
- the JNI API provided function-call entry points into the Serval DNA shared library
- application processes opened and accessed the Rhizome database themselves, not via requests to the daemon
- this sometimes caused database locking to block the daemon, which severely degraded responsiveness when starting a freshly-installed Batphone app in the presence of other devices holding lots of Rhizome content – see Serval DNA issue #1
- all Android applications using MeshMS and Rhizome had to be bundled together in a single APK, so that Android would run them all with the same User ID, so they could all access and modify the Rhizome database files
- Commit 68dbaef refactored the Java API to add a stateful object that holds an open connection to the daemon, and keep the Serval DNA RESTful interface independent from the JNI command interface
- Commit 82b13ca deprecated the existing MeshMS JNI API
- Commit 11e9d38 renamed the Serval DNA configuration
rhizome.api.restful.
options toapi.restful.
because the RESTful interface will be used for services other than Rhizome (eg, MeshMS)
Serval DNA power consumption
The final issue to be addressed was the CPU activity of the Serval DNA daemon whilst idle.
- The previous implementation of the Serval Mesh app for Android did not have a continuously running daemon:
- the daemon only ran while the “Enable Services” checkbox was ticked on the “Connect” menu
- the daemon awoke periodically on *alarm* events (once per second) while running, even while the network was idle or no network interfaces were up
- this caused rapid battery drain while Serval Mesh networking was active, which was advised in the Release Notes for 0.91
- the JNI API (see above) accessed the Rhizome database directly in-process; it did not use the daemon, so the Rhizome and MeshMS screens functioned at all times
- The new implementation (R15) implements MeshMS operations using the RESTful HTTP interface:
- this requires a continuously running daemon, or at least a daemon running while any Rhizome or MeshMS screen are active
- this could quickly drain the battery simply by opening the app
- This necessitated improvements to the event scheduling sub-system within Serval DNA:
- only use CPU in response to real external events (user and network)
- only schedule timed wake-ups for externally imposed timing requirements such as network protocol time-outs
-
- a new kind of “lazy” alarm does not force the daemon process to wake
- whenever the daemon does wake due to network or user activity, overdue lazy alarms are run in time order
- existing periodic events, such as the configuration file poll and shutdown file poll, were re-implemented to use lazy alarms
- this allowed R15 to be implemented without any impact on battery life
- Commit f0948b2 improved the Serval Mesh app:
- starts the daemon as needed and keeps it running
- changing the “Enable Services” checkbox simply toggles a Serval DNA configuration option
- This improvement has significant positive repercussions for other Serval products like Serval Mesh Extender, by extending battery life towards the maximum practical limit
Implementation
R1(a) – HTTP Basic Authorization and loopback interface restriction
- commit 3d3e900 expanded the Serval DNA configuration schema to include Rhizome Restful API user names and passwords (in the clear)
-
- the new HTTP server was extended to parse the Authorization header
- the new HTTP server was extended to support the WWW-Authenticate response header
- two test cases were added for Basic Authentication
- Basic Authentication was implemented in an empty
/restful/rhizome/bundlelist.json
resource to return a 401 Unauthorized response unless a correct Basic Authentication user/password pair are supplied as per RFC2617
- commit b44046d improved the empty
/restful/rhizome/bundlelist.json
resource to return a 403 Forbidden response unless the request arrives over the local loopback interface - all the checks in the
/restful/rhizome/bundlelist.json
resource were factored into a function that was re-used to implement the other resources - commit 067340b renamed the HTTP Authorisation realm from “Serval Rhizome” to “Serval RESTful API”
R2 – GET /restful/rhizome/bundlelist.json
request
-
- added JSON content (see N1) to the empty
/restful/rhizome/bundlelist.json
that had already been implemented by the Basic Authentication work (see above) - did not implement the token (yet)
- added a new test case that:
- commit 64db53a upgraded the Rhizome database schema to include a single, unique UUID per database
- commit 4380fdc altered the JSON returned to include the specified token. The token includes the Rhizome database's UUID to ensure that it cannot be used erroneously in a
/restful/rhizome/newsince/<token>/bundlelist.json
request to another database.
R3 – GET /restful/rhizome/newsince/<token>/bundlelist.json
request
- the
GET /restful/rhizome/newsince/<token>/bundlelist.json
request was implemented using the same JSON content generation code asGET /restful/rhizome/bundlelist.json
modified as follows:- commit c1f0c0c adds a new dispatched path function which decodes the
<token>
and invokes the same JSON content generator with different parameters - commit 1acfff6 adds support for pausing HTTP response transmission for a specified time interval
- commit fce0893 deals with iterator end by pausing the transmission and polling (re-querying) every 2 seconds until a configurable timeout (default 60 seconds) is reached, instead of terminating the response immediately – V1 for the variation that replaces a 204 No Data response with an empty JSON response
- commit 29fab6d makes the poll interval configurable (default 2 seconds)
- commit 6e4acb6 ensures that the token matches the Rhizome database UUID, otherwise the ROWID embedded in the token is meaningless and the request fails
- commit 513aa15 put the finishing touches on a new test case that runs three HTTP requests simultaneously while adding new bundles
R4 – GET /restful/rhizome/<BundleID>.rhm
request (see V2) (completed 13 December)
- commit 40698b1 fixed a minor bug in the HTTP request parser, that incorrectly detected a Content-Type header present in a GET request
- commit 26e0120 implemented the request and a test case that fetches three manifests in succession
R5 – GET /restful/rhizome/<BundleID>/raw.bin
request (completed 13 December)
- commit 8c9ac6c improved the test case to use a mixture of encrypted and plain payloads
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)
- commit 8a1c0a3 implemented the MeshMS2 service in ServalDNA, based on a student implementation from earlier in 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)
- commit 8897563 implemented the request and a test case, based on the existing MeshMS CLI test, that creates three conversations, runs the HTTP request, then marks the conversations as read and runs the HTTP request again
- commit 6c16b95 fixed a SEGV bug when requesting an empty conversation list
R10 – GET /restful/meshms/<fromSID>/<toSID>/messagelist.json
request (see V3) (completed 24 January 2014)
- commit 879395b refactored the existing
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)
- commit b1992b3 refactored some MeshMS source code to make it a bit clearer
- commit ebe444f refactored the RESTful MeshMS test script for re-usability in more RESTful MeshMS test cases
R12 – POST /restful/meshms/<fromSID>/<toSID>/sendmessage
request (see V4 and V5) (completed 7 February 2014)
- commit fd86a3d implemented the request with one test case
- commit 5dd9ea7 refactored MeshMS code to improve handling and reporting of failures
Merge into mainline development branch (completed 7 February 2014)
R13 – Java API (completed 14 July 2014)
- commit 99fb8b6 moved the Java API foundation classes from the Batphone source repository to the Serval DNA repository
- commit 9cbd7c3 implemented a Java API to list MeshMS conversations:
- uses the RESTful HTTP interface developed in R9
- makes an HTTP connection using the standard java.net.HttpURLConnection
- new class org.servalproject.servaldna.ServalDClient establishes an HTTP connection to a running Serval DNA daemon
- new class org.servalproject.servaldna.meshmes.MeshMSConversationList decodes the JSON response
- commit 0a54414 refactored the JSON parsing code (developed in the previous commit):
- new class org.servalproject.json.JSONTokeniser implements a simple, stateful JSON tokeniser wrapped around an java.io.InputStream object
- commit e9437e9 implemented a Java API to list MeshMS messages from a single conversation:
- uses the RESTful HTTP interface developed in R10
- commit 819b8dc improved the Serval DNA HTTP server to be strict about the Content-Disposition and Content-Type form-part headers supplied in POST requests
- commit eba7f65 implemented the Java API to send a MeshMS message:
- uses the RESTful HTTP interface developed in R12
- new class org.servalproject.servaldna.meshms.MeshMSCommon contains methods for parsing a MeshMS status report JSON (N9b)
- commit c79a382 implemented the MeshMS Java API to mark messages as read
- commit 1ac67de fixed a bug in Serval DNA's HTTP form-data MIME part parsing that was revealed by the new Java API
- commit 6102328 improved Serval DNA's Rhizome manifest validation code to provide a textual reason for failure, which can be passed back to API clients to assist diagnosis
- commit db8ee79 implemented a Java API to list Rhizome bundles:
- uses the RESTful HTTP interface developed in R2
- new class org.servalproject.servaldna.rhizome.RhizomeCommon contains methods for parsing a Rhizome status report JSON (N9a)
- new class org.servalproject.json.JSONTableScanner generalises the parsing of the JSON table structure returned by Rhizome and MeshMS lists (N1 and N10)
- commit 93e67ed implemented a Java API to fetch a Rhizome manifest given its Bundle ID:
- uses the RESTful HTTP interface developed in R4
- commit 34b6ff4 implemented a Java API to fetch a “raw” Rhizome payload (not decrypted) given its Bundle ID
- uses the RESTful HTTP interface developed in R5
- commit 2aec8f3 implemented a Java API to fetch a decrypted Rhizome payload given its Bundle ID
- uses the RESTful HTTP interface developed in R6
- commit a81d05b implemented a Java API to insert a new Rhizome bundle
- uses the RESTful HTTP interface developed in R7
- commit 57cce64 narrowed the classes of exceptions thrown by Rhizome Java API methods, to make the API more self-documenting
- commit 04b2a20 improved the Content-Type header and added Content-Transfer-Encoding headers sent by the Java API *insert bundle* operation, to help future-proof the protocol
- commit 4c6612a implemented a Java API to list Rhizome bundles that have arrived since a given token:
- uses the RESTful HTTP interface developed in R3
- commit 8962301 implemented a Java API to list MeshMS messages that have arrived since a given token from a single conversation:
- uses the RESTful HTTP interface developed in R11
R14 – Automated tests (completed 11 July 2014)
- The tests/meshms script contains all the tests that were created when the MeshMS protocol was moved from Serval Mesh (Java) into Serval DNA (C) (R8).
- Created the new rhizomehttp test script containing tests for the new RESTful HTTP API (later split into rhizomerestful and meshmsrestful):
- commit 39b2f3a improved the test case titles (descriptions)
-
- insert four bundles with different authors (bundle-author form parameter) and name, service and crypt manifest fields, then update them all and check that only the anonymous bundle cannot be updated because the Bundle secret is unknown
- update an anonymous (authorless) bundle by passing the bundle-secret form parameter
- insert an empty bundle (nil payload)
- insert a large bundle (50 MiB payload)
- omit the manifest form parameter
- pass incorrect manifest form parameter Content-Type
- pass duplicate manifest form parameters
- insert a Journal bundle, fails
- omit payload form parameter
- pass duplicate payload form parameter
- pass payload form parameter before manifest parameter
- pass unsupported form parameter
- manifest filesize contradicts supplied payload size
- manifest filehash contradicts supplied payload hash
- commit 6a1c8bc made all RESTful HTTP responses have a Content-Type of JSON instead of HTML (specifically, failure results)
- commit ebe444f refactored the MeshMS “add message” test fixture into a function for re-use in other tests
-
- missing message form parameter
- duplicate message form parameters
- send from an unknown (locked) identity
-
- list messages from an unknown (locked) identity
- commit f2772b0 refactored some test fixture code to be shared with the new meshmsjava test script
-
- missing Content-Type header from message form parameter
- unsupported Content-Type in message form parameter
- missing Content-Type charset from message form parameter
- unsupported Content-Type charset in message form parameter
- added proper Content-Type headers to form parts (
plain/text; charset=utf-8
) in other tests
-
- mark all conversations as read
- mark all messages as read in a single conversation
- mark a single message as read in a single conversation
- supply spurious content body to a POST request
- Created the new meshmsjava test script containing tests for the new MeshMS Java API:
- Created the new rhizomejava test script containing tests for the new Rhizome Java API:
- commit a9ec5dc refactored some shell functions from the existing rhizomerestful script into testdefs_rhizome.sh ready for re-use
- The Java tests were added to the tests/all script:
The 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)
- The Serval Mesh app for Android was improved to use the Java APIs:
Merge into mainline development branch (completed 11 July 2014)
- commit 606f087 merged all work into the mainline development branch