From 7b5752a1116f77342a7a08e0ce1832908b1bb619 Mon Sep 17 00:00:00 2001 From: Andrew Bettison Date: Fri, 24 Jan 2014 17:26:19 +1030 Subject: [PATCH] Implement HTTP GET /restful/meshms///messagelist.json --- rhizome_http.c | 159 ++++++++++++++++++++++++++++++++++++++++++---- rhizome_http.h | 17 ++++- tests/rhizomehttp | 56 +++++++++++++++- 3 files changed, 215 insertions(+), 17 deletions(-) diff --git a/rhizome_http.c b/rhizome_http.c index 478fc82d..f331d7ec 100644 --- a/rhizome_http.c +++ b/rhizome_http.c @@ -263,6 +263,11 @@ static void finalise_union_meshms_conversationlist(rhizome_http_request *r) r->u.mclist.conv = NULL; } +static void finalise_union_meshms_messagelist(rhizome_http_request *r) +{ + meshms_message_iterator_close(&r->u.msglist.iter); +} + static void rhizome_server_finalise_http_request(struct http_request *hr) { rhizome_http_request *r = (rhizome_http_request *) hr; @@ -515,10 +520,8 @@ static int restful_rhizome_bundlelist_json_content_chunk(struct http_request *hr if (ret == 0) { time_ms_t now; if (r->u.rhlist.cursor.rowid_since == 0 || (now = gettime_ms()) >= r->u.rhlist.end_time) { - strbuf_puts(b, "\n]\n}\n"); - if (!strbuf_overrun(b)) - r->u.rhlist.phase = LIST_DONE; - return 0; + r->u.rhlist.phase = LIST_END; + return 1; } time_ms_t wake_at = now + config.rhizome.api.restful.newsince_poll_ms; if (wake_at > r->u.rhlist.end_time) @@ -577,8 +580,13 @@ static int restful_rhizome_bundlelist_json_content_chunk(struct http_request *hr rhizome_list_commit(&r->u.rhlist.cursor); ++r->u.rhlist.rowcount; } - return 1; } + return 1; + case LIST_END: + strbuf_puts(b, "\n]\n}\n"); + if (!strbuf_overrun(b)) + r->u.rhlist.phase = LIST_DONE; + // fall through... case LIST_DONE: return 0; } @@ -1049,6 +1057,7 @@ static int restful_rhizome_bid_decrypted_bin(rhizome_http_request *r, const char } static HTTP_HANDLER restful_meshms_conversationlist_json; +static HTTP_HANDLER restful_meshms_messagelist_json; static int restful_meshms_(rhizome_http_request *r, const char *remainder) { @@ -1057,10 +1066,17 @@ static int restful_meshms_(rhizome_http_request *r, const char *remainder) return 403; HTTP_HANDLER *handler = NULL; const char *end; - if (strn_to_sid_t(&r->sid, remainder, &end) != -1) { - if (strcmp(end, "/conversationlist.json") == 0) { + if (strn_to_sid_t(&r->sid1, remainder, &end) != -1) { + remainder = end; + if (strcmp(remainder, "/conversationlist.json") == 0) { handler = restful_meshms_conversationlist_json; remainder = ""; + } else if (*remainder == '/' && strn_to_sid_t(&r->sid2, remainder + 1, &end) != -1) { + remainder = end; + if (strcmp(remainder, "/messagelist.json") == 0) { + handler = restful_meshms_messagelist_json; + remainder = ""; + } } } if (handler == NULL) @@ -1085,7 +1101,7 @@ static int restful_meshms_conversationlist_json(rhizome_http_request *r, const c r->u.mclist.phase = LIST_HEADER; r->u.mclist.rowcount = 0; r->u.mclist.conv = NULL; - if (meshms_conversations_list(&r->sid, NULL, &r->u.mclist.conv)) + if (meshms_conversations_list(&r->sid1, NULL, &r->u.mclist.conv)) return -1; meshms_conversation_iterator_start(&r->u.mclist.iter, r->u.mclist.conv); http_request_response_generated(&r->http, 200, "application/json", restful_meshms_conversationlist_json_content); @@ -1124,10 +1140,8 @@ static int restful_meshms_conversationlist_json_content_chunk(struct http_reques return 1; case LIST_ROWS: if (r->u.mclist.iter.current == NULL) { - strbuf_puts(b, "\n]\n}\n"); - if (!strbuf_overrun(b)) - r->u.mclist.phase = LIST_DONE; - return 0; + r->u.mclist.phase = LIST_END; + // fall through... } else { if (r->u.mclist.rowcount != 0) strbuf_putc(b, ','); @@ -1148,6 +1162,127 @@ static int restful_meshms_conversationlist_json_content_chunk(struct http_reques } return 1; } + // fall through... + case LIST_END: + strbuf_puts(b, "\n]\n}\n"); + if (!strbuf_overrun(b)) + r->u.mclist.phase = LIST_DONE; + // fall through... + case LIST_DONE: + return 0; + } + abort(); + return 0; +} + +#define MESHMS_TOKEN_STRLEN (BASE64_ENCODED_LEN(sizeof(rhizome_bid_t) + sizeof(uint64_t))) +#define alloca_meshms_token(bid, offset) meshms_token_to_str(alloca(MESHMS_TOKEN_STRLEN + 1), (bid), (offset)) + +static char *meshms_token_to_str(char *buf, const rhizome_bid_t *bid, uint64_t offset) +{ + struct iovec iov[2]; + iov[0].iov_base = (void *) bid->binary; + iov[0].iov_len = sizeof bid->binary; + iov[1].iov_base = &offset; + iov[1].iov_len = sizeof offset; + size_t n = base64url_encodev(buf, iov, 2); + assert(n == MESHMS_TOKEN_STRLEN); + buf[n] = '\0'; + return buf; +} + +static HTTP_CONTENT_GENERATOR restful_meshms_messagelist_json_content; + +static int restful_meshms_messagelist_json(rhizome_http_request *r, const char *remainder) +{ + if (*remainder) + return 404; + assert(r->finalise_union == NULL); + r->finalise_union = finalise_union_meshms_messagelist; + r->u.msglist.rowcount = 0; + meshms_message_iterator_open(&r->u.msglist.iter, &r->sid1, &r->sid2); + if ((r->u.msglist.finished = meshms_message_iterator_next(&r->u.msglist.iter)) == -1) + return -1; + r->u.msglist.phase = LIST_HEADER; + http_request_response_generated(&r->http, 200, "application/json", restful_meshms_messagelist_json_content); + return 1; +} + +static HTTP_CONTENT_GENERATOR_STRBUF_CHUNKER restful_meshms_messagelist_json_content_chunk; + +static int restful_meshms_messagelist_json_content(struct http_request *hr, unsigned char *buf, size_t bufsz, struct http_content_generator_result *result) +{ + return generate_http_content_from_strbuf_chunks(hr, (char *)buf, bufsz, result, restful_meshms_messagelist_json_content_chunk); +} + +static int restful_meshms_messagelist_json_content_chunk(struct http_request *hr, strbuf b) +{ + rhizome_http_request *r = (rhizome_http_request *) hr; + const char *headers[] = { + "token", + "text", + "offset", + "direction", + "delivered", + "read" + }; + switch (r->u.msglist.phase) { + case LIST_HEADER: + strbuf_sprintf(b, "{\n\"received_read_offset\":%"PRIu64",\n\"sent_ack_offset\":%"PRIu64",\n\"header\":[", + r->u.msglist.iter.received_read_offset, + r->u.msglist.iter.sent_ack_offset + ); + unsigned i; + for (i = 0; i != NELS(headers); ++i) { + if (i) + strbuf_putc(b, ','); + strbuf_json_string(b, headers[i]); + } + strbuf_puts(b, "],\n\"rows\":["); + if (!strbuf_overrun(b)) + r->u.msglist.phase = r->u.msglist.finished ? LIST_END : LIST_ROWS; + return 1; + case LIST_ROWS: + if (!r->u.msglist.finished) { + if (r->u.msglist.rowcount != 0) + strbuf_putc(b, ','); + strbuf_puts(b, "\n["); + const rhizome_bid_t *ply_bid = r->u.msglist.iter.direction == SENT ? &r->u.msglist.iter._conv->my_ply.bundle_id : &r->u.msglist.iter._conv->their_ply.bundle_id; + strbuf_json_string(b, alloca_meshms_token(ply_bid, r->u.msglist.iter.offset)); + strbuf_putc(b, ','); + strbuf_json_string(b, r->u.msglist.iter.text); + strbuf_sprintf(b, ",%"PRIu64",", r->u.msglist.iter.offset); + switch (r->u.msglist.iter.direction) { + case SENT: + strbuf_json_string(b, ">"); + strbuf_putc(b, ','); + strbuf_json_boolean(b, r->u.msglist.iter.delivered); + strbuf_putc(b, ','); + strbuf_json_boolean(b, 0); + break; + case RECEIVED: + strbuf_json_string(b, "<"); + strbuf_putc(b, ','); + strbuf_json_boolean(b, 1); + strbuf_putc(b, ','); + strbuf_json_boolean(b, r->u.msglist.iter.read); + break; + } + strbuf_puts(b, "]"); + if (!strbuf_overrun(b)) { + ++r->u.msglist.rowcount; + if ((r->u.msglist.finished = meshms_message_iterator_next(&r->u.msglist.iter)) == -1) + return -1; + } + return 1; + } + r->u.msglist.phase = LIST_END; + // fall through... + case LIST_END: + strbuf_puts(b, "\n]\n}\n"); + if (!strbuf_overrun(b)) + r->u.msglist.phase = LIST_DONE; + // fall through... case LIST_DONE: return 0; } diff --git a/rhizome_http.h b/rhizome_http.h index 970c5f98..2684e159 100644 --- a/rhizome_http.h +++ b/rhizome_http.h @@ -26,7 +26,7 @@ Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. int is_rhizome_http_server_running(); -enum list_phase { LIST_HEADER = 0, LIST_ROWS, LIST_DONE }; +enum list_phase { LIST_HEADER = 0, LIST_ROWS, LIST_END, LIST_DONE }; typedef struct rhizome_http_request { @@ -42,9 +42,10 @@ typedef struct rhizome_http_request */ rhizome_manifest *manifest; - /* For requests/responses that pertain to a single identity. + /* For requests/responses that contain one or two SIDs. */ - sid_t sid; + sid_t sid1; + sid_t sid2; /* Finaliser for union contents (below). */ @@ -128,6 +129,16 @@ typedef struct rhizome_http_request } mclist; + /* For responses that list MeshMS messages in a single conversation. + */ + struct { + enum list_phase phase; + size_t rowcount; + struct meshms_message_iterator iter; + int finished; + } + msglist; + } u; } rhizome_http_request; diff --git a/tests/rhizomehttp b/tests/rhizomehttp index fdd01af4..d015f0a8 100755 --- a/tests/rhizomehttp +++ b/tests/rhizomehttp @@ -963,8 +963,60 @@ test_MeshmsListConversations() { } doc_MeshmsListMessages="HTTP RESTful list MeshMS messages in one conversation as JSON" -Xtest_MeshmsListMessages() { - : +setup_MeshmsListMessages() { + IDENTITY_COUNT=2 + setup + text=( + "One banana" + "Two apples" + "Three watermelons" + "Four oranges" + "Five grapes" + "Six lychees" + "Seven raspberries" + "Eight lemons" + ) + direction=('>' '>' '<' '>' '>' '<' '<' '>') + for ((i = 0; i < ${#text[*]}; ++i)); do + case ${direction[$i]} in + '>') executeOk_servald meshms send message $SIDA1 $SIDA2 "${text[$i]}";; + '<') executeOk_servald meshms send message $SIDA2 $SIDA1 "${text[$i]}";; + *) error "direction[$i]=${direction[$i]}";; + esac + done + executeOk_servald meshms list messages $SIDA1 $SIDA2 + tfw_cat --stdout + delivered_offset=$(sed -n -e '/^[0-9]\+:[0-9]\+:ACK:delivered$/{n;s/^[0-9]\+:\([0-9]\+\):>:.*/\1/p;q}' "$TFWSTDOUT") + [ -z "$delivered_offset" ] && delivered_offset=0 + read_offset=$(sed -n -e 's/^[0-9]\+:\([0-9]\+\):MARK:read$/\1/p' "$TFWSTDOUT") + [ -z "$read_offset" ] && read_offset=0 +} +test_MeshmsListMessages() { + executeOk curl \ + --silent --fail --show-error \ + --output messagelist.json \ + --dump-header http.headers \ + --basic --user harry:potter \ + "http://$addr_localhost:$PORTA/restful/meshms/$SIDA1/$SIDA2/messagelist.json" + tfw_cat http.headers messagelist.json + tfw_preserve messagelist.json + assert [ "$(jq '.rows | length' messagelist.json)" = 8 ] + transform_list_json messagelist.json messages.json + tfw_preserve messages.json + for ((i = 0; i < ${#text[*]}; ++i)); do + let j=${#text[*]}-$i-1 + assertJq messages.json '(.['$i'].token | length) > 0' + assertJq messages.json '.['$i'].text == "'"${text[$j]}"'"' + assertJq messages.json '.['$i'].direction == "'"${direction[$j]}"'"' + case ${direction[$j]} in + '>') + assertJq messages.json '.['$i'].delivered == (.['$i'].offset <= '$delivered_offset')' + ;; + '<') + assertJq messages.json '.['$i'].read == (.['$i'].offset <= '$read_offset')' + ;; + esac + done } doc_MeshmsListMessagesSince="HTTP RESTful list MeshMS messages in one conversation since token as JSON"