mirror of
https://github.com/servalproject/serval-dna.git
synced 2024-12-19 05:07:56 +00:00
fb2709d10c
Move code from rhizome_http.c into rhizome_restful.c and meshms_restful.c
410 lines
14 KiB
C
410 lines
14 KiB
C
/*
|
|
Serval DNA MeshMS HTTP RESTful interface
|
|
Copyright (C) 2013,2014 Serval Project Inc.
|
|
|
|
This program is free software; you can redistribute it and/or
|
|
modify it under the terms of the GNU General Public License
|
|
as published by the Free Software Foundation; either version 2
|
|
of the License, or (at your option) any later version.
|
|
|
|
This program is distributed in the hope that it will be useful,
|
|
but WITHOUT ANY WARRANTY; without even the implied warranty of
|
|
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
|
GNU General Public License for more details.
|
|
|
|
You should have received a copy of the GNU General Public License
|
|
along with this program; if not, write to the Free Software
|
|
Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
|
|
*/
|
|
|
|
#include "conf.h"
|
|
#include "serval.h"
|
|
#include "httpd.h"
|
|
#include "strbuf_helpers.h"
|
|
|
|
static void finalise_union_meshms_conversationlist(httpd_request *r)
|
|
{
|
|
meshms_free_conversations(r->u.mclist.conv);
|
|
r->u.mclist.conv = NULL;
|
|
}
|
|
|
|
static void finalise_union_meshms_messagelist(httpd_request *r)
|
|
{
|
|
meshms_message_iterator_close(&r->u.msglist.iter);
|
|
}
|
|
|
|
#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 int strn_to_meshms_token(const char *str, rhizome_bid_t *bidp, uint64_t *offsetp, const char **afterp)
|
|
{
|
|
unsigned char token[sizeof bidp->binary + sizeof *offsetp];
|
|
if (base64url_decode(token, sizeof token, str, 0, afterp, 0, NULL) != sizeof token)
|
|
return 0;
|
|
memcpy(bidp->binary, token, sizeof bidp->binary);
|
|
memcpy(offsetp, token + sizeof bidp->binary, sizeof *offsetp);
|
|
return 1;
|
|
}
|
|
|
|
static HTTP_HANDLER restful_meshms_conversationlist_json;
|
|
static HTTP_HANDLER restful_meshms_messagelist_json;
|
|
static HTTP_HANDLER restful_meshms_newsince_messagelist_json;
|
|
|
|
int restful_meshms_(httpd_request *r, const char *remainder)
|
|
{
|
|
r->http.response.header.content_type = "application/json";
|
|
if (!is_rhizome_http_enabled())
|
|
return 403;
|
|
HTTP_HANDLER *handler = NULL;
|
|
const char *end;
|
|
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 = "";
|
|
}
|
|
else if ( str_startswith(remainder, "/newsince/", &end)
|
|
&& strn_to_meshms_token(end, &r->bid, &r->ui64, &end)
|
|
&& strcmp(end, "/messagelist.json") == 0
|
|
) {
|
|
handler = restful_meshms_newsince_messagelist_json;
|
|
remainder = "";
|
|
}
|
|
}
|
|
}
|
|
if (handler == NULL)
|
|
return 404;
|
|
if (r->http.verb != HTTP_VERB_GET)
|
|
return 405;
|
|
int ret = authorize(&r->http);
|
|
if (ret)
|
|
return ret;
|
|
ret = handler(r, remainder);
|
|
return ret;
|
|
}
|
|
|
|
static HTTP_CONTENT_GENERATOR restful_meshms_conversationlist_json_content;
|
|
|
|
static int restful_meshms_conversationlist_json(httpd_request *r, const char *remainder)
|
|
{
|
|
if (*remainder)
|
|
return 404;
|
|
assert(r->finalise_union == NULL);
|
|
r->finalise_union = finalise_union_meshms_conversationlist;
|
|
r->u.mclist.phase = LIST_HEADER;
|
|
r->u.mclist.rowcount = 0;
|
|
r->u.mclist.conv = NULL;
|
|
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);
|
|
return 1;
|
|
}
|
|
|
|
static HTTP_CONTENT_GENERATOR_STRBUF_CHUNKER restful_meshms_conversationlist_json_content_chunk;
|
|
|
|
static int restful_meshms_conversationlist_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_conversationlist_json_content_chunk);
|
|
}
|
|
|
|
static int restful_meshms_conversationlist_json_content_chunk(struct http_request *hr, strbuf b)
|
|
{
|
|
httpd_request *r = (httpd_request *) hr;
|
|
// The "my_sid" and "their_sid" per-conversation fields allow the same JSON structure to be used
|
|
// in a future, non-SID-specific request, eg, to list all conversations for all currently open
|
|
// identities.
|
|
const char *headers[] = {
|
|
"_id",
|
|
"my_sid",
|
|
"their_sid",
|
|
"read",
|
|
"last_message",
|
|
"read_offset"
|
|
};
|
|
switch (r->u.mclist.phase) {
|
|
case LIST_HEADER:
|
|
strbuf_puts(b, "{\n\"header\":[");
|
|
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.mclist.phase = LIST_ROWS;
|
|
return 1;
|
|
case LIST_ROWS:
|
|
if (r->u.mclist.iter.current == NULL) {
|
|
r->u.mclist.phase = LIST_END;
|
|
// fall through...
|
|
} else {
|
|
if (r->u.mclist.rowcount != 0)
|
|
strbuf_putc(b, ',');
|
|
strbuf_puts(b, "\n[");
|
|
strbuf_sprintf(b, "%u", r->u.mclist.rowcount);
|
|
strbuf_putc(b, ',');
|
|
strbuf_json_hex(b, r->sid1.binary, sizeof r->sid1.binary);
|
|
strbuf_putc(b, ',');
|
|
strbuf_json_hex(b, r->u.mclist.iter.current->them.binary, sizeof r->u.mclist.iter.current->them.binary);
|
|
strbuf_putc(b, ',');
|
|
strbuf_json_boolean(b, r->u.mclist.iter.current->read_offset >= r->u.mclist.iter.current->their_last_message);
|
|
strbuf_putc(b, ',');
|
|
strbuf_sprintf(b, "%"PRIu64, r->u.mclist.iter.current->their_last_message);
|
|
strbuf_putc(b, ',');
|
|
strbuf_sprintf(b, "%"PRIu64, r->u.mclist.iter.current->read_offset);
|
|
strbuf_puts(b, "]");
|
|
if (!strbuf_overrun(b)) {
|
|
meshms_conversation_iterator_advance(&r->u.mclist.iter);
|
|
++r->u.mclist.rowcount;
|
|
}
|
|
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;
|
|
}
|
|
|
|
static HTTP_CONTENT_GENERATOR restful_meshms_messagelist_json_content;
|
|
|
|
static int reopen_meshms_message_iterator(httpd_request *r)
|
|
{
|
|
if (!meshms_message_iterator_is_open(&r->u.msglist.iter)) {
|
|
if ( meshms_message_iterator_open(&r->u.msglist.iter, &r->sid1, &r->sid2) == -1
|
|
|| (r->u.msglist.finished = meshms_message_iterator_prev(&r->u.msglist.iter)) == -1
|
|
)
|
|
return -1;
|
|
if (!r->u.msglist.finished) {
|
|
r->u.msglist.latest_which_ply = r->u.msglist.iter.which_ply;
|
|
r->u.msglist.latest_offset = r->u.msglist.iter.offset;
|
|
}
|
|
}
|
|
return 0;
|
|
}
|
|
|
|
static int restful_meshms_messagelist_json(httpd_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;
|
|
r->u.msglist.phase = LIST_HEADER;
|
|
r->u.msglist.token_offset = 0;
|
|
r->u.msglist.end_time = 0;
|
|
http_request_response_generated(&r->http, 200, "application/json", restful_meshms_messagelist_json_content);
|
|
return 1;
|
|
}
|
|
|
|
static int restful_meshms_newsince_messagelist_json(httpd_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;
|
|
r->u.msglist.phase = LIST_HEADER;
|
|
if (reopen_meshms_message_iterator(r) == -1)
|
|
return -1;
|
|
if (cmp_rhizome_bid_t(&r->bid, r->u.msglist.iter.my_ply_bid) == 0)
|
|
r->u.msglist.token_which_ply = MY_PLY;
|
|
else if (cmp_rhizome_bid_t(&r->bid, r->u.msglist.iter.their_ply_bid) == 0)
|
|
r->u.msglist.token_which_ply = THEIR_PLY;
|
|
else {
|
|
http_request_simple_response(&r->http, 404, "Invalid token");
|
|
return 404;
|
|
}
|
|
r->u.msglist.token_offset = r->ui64;
|
|
r->u.msglist.end_time = gettime_ms() + config.rhizome.api.restful.newsince_timeout * 1000;
|
|
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)
|
|
{
|
|
httpd_request *r = (httpd_request *) hr;
|
|
if (reopen_meshms_message_iterator(r) == -1)
|
|
return -1;
|
|
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)
|
|
{
|
|
httpd_request *r = (httpd_request *) hr;
|
|
// Include "my_sid" and "their_sid" per-message, so that the same JSON structure can be used by a
|
|
// future, non-SID-specific request (eg, to get all messages for all currently open identities).
|
|
const char *headers[] = {
|
|
"type",
|
|
"my_sid",
|
|
"their_sid",
|
|
"offset",
|
|
"token",
|
|
"text",
|
|
"delivered",
|
|
"read",
|
|
"ack_offset"
|
|
};
|
|
switch (r->u.msglist.phase) {
|
|
case LIST_HEADER:
|
|
strbuf_puts(b, "{\n");
|
|
if (!r->u.msglist.end_time) {
|
|
strbuf_sprintf(b, "\"read_offset\":%"PRIu64",\n\"latest_ack_offset\":%"PRIu64",\n",
|
|
r->u.msglist.iter.read_offset,
|
|
r->u.msglist.iter.latest_ack_my_offset
|
|
);
|
|
}
|
|
strbuf_puts(b, "\"header\":[");
|
|
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
|
|
|| (r->u.msglist.token_which_ply == r->u.msglist.iter.which_ply && r->u.msglist.iter.offset <= r->u.msglist.token_offset)
|
|
) {
|
|
time_ms_t now;
|
|
if (r->u.msglist.end_time && (now = gettime_ms()) < r->u.msglist.end_time) {
|
|
r->u.msglist.token_which_ply = r->u.msglist.latest_which_ply;
|
|
r->u.msglist.token_offset = r->u.msglist.latest_offset;
|
|
meshms_message_iterator_close(&r->u.msglist.iter);
|
|
time_ms_t wake_at = now + config.rhizome.api.restful.newsince_poll_ms;
|
|
if (wake_at > r->u.msglist.end_time)
|
|
wake_at = r->u.msglist.end_time;
|
|
http_request_pause_response(&r->http, wake_at);
|
|
return 0;
|
|
}
|
|
} else {
|
|
switch (r->u.msglist.iter.type) {
|
|
case MESSAGE_SENT:
|
|
if (r->u.msglist.rowcount != 0)
|
|
strbuf_putc(b, ',');
|
|
strbuf_puts(b, "\n[");
|
|
strbuf_json_string(b, ">");
|
|
strbuf_putc(b, ',');
|
|
strbuf_json_hex(b, r->u.msglist.iter.my_sid->binary, sizeof r->u.msglist.iter.my_sid->binary);
|
|
strbuf_putc(b, ',');
|
|
strbuf_json_hex(b, r->u.msglist.iter.their_sid->binary, sizeof r->u.msglist.iter.their_sid->binary);
|
|
strbuf_putc(b, ',');
|
|
strbuf_sprintf(b, "%"PRIu64, r->u.msglist.iter.offset);
|
|
strbuf_putc(b, ',');
|
|
strbuf_json_string(b, alloca_meshms_token(&r->u.msglist.iter._conv->my_ply.bundle_id, r->u.msglist.iter.offset));
|
|
strbuf_putc(b, ',');
|
|
strbuf_json_string(b, r->u.msglist.iter.text);
|
|
strbuf_putc(b, ',');
|
|
strbuf_json_boolean(b, r->u.msglist.iter.delivered);
|
|
strbuf_putc(b, ',');
|
|
strbuf_json_boolean(b, 0);
|
|
strbuf_putc(b, ',');
|
|
strbuf_json_null(b);
|
|
strbuf_puts(b, "]");
|
|
break;
|
|
case MESSAGE_RECEIVED:
|
|
if (r->u.msglist.rowcount != 0)
|
|
strbuf_putc(b, ',');
|
|
strbuf_puts(b, "\n[");
|
|
strbuf_json_string(b, "<");
|
|
strbuf_putc(b, ',');
|
|
strbuf_json_hex(b, r->u.msglist.iter.my_sid->binary, sizeof r->u.msglist.iter.my_sid->binary);
|
|
strbuf_putc(b, ',');
|
|
strbuf_json_hex(b, r->u.msglist.iter.their_sid->binary, sizeof r->u.msglist.iter.their_sid->binary);
|
|
strbuf_putc(b, ',');
|
|
strbuf_sprintf(b, "%"PRIu64, r->u.msglist.iter.offset);
|
|
strbuf_putc(b, ',');
|
|
strbuf_json_string(b, alloca_meshms_token(&r->u.msglist.iter._conv->their_ply.bundle_id, r->u.msglist.iter.offset));
|
|
strbuf_putc(b, ',');
|
|
strbuf_json_string(b, r->u.msglist.iter.text);
|
|
strbuf_putc(b, ',');
|
|
strbuf_json_boolean(b, 1);
|
|
strbuf_putc(b, ',');
|
|
strbuf_json_boolean(b, r->u.msglist.iter.read);
|
|
strbuf_putc(b, ',');
|
|
strbuf_json_null(b);
|
|
strbuf_puts(b, "]");
|
|
break;
|
|
case ACK_RECEIVED:
|
|
// Don't send old (irrelevant) ACKs.
|
|
if (r->u.msglist.iter.ack_offset > r->u.msglist.highest_ack_offset) {
|
|
if (r->u.msglist.rowcount != 0)
|
|
strbuf_putc(b, ',');
|
|
strbuf_puts(b, "\n[");
|
|
strbuf_json_string(b, "ACK");
|
|
strbuf_putc(b, ',');
|
|
strbuf_json_hex(b, r->u.msglist.iter.my_sid->binary, sizeof r->u.msglist.iter.my_sid->binary);
|
|
strbuf_putc(b, ',');
|
|
strbuf_json_hex(b, r->u.msglist.iter.their_sid->binary, sizeof r->u.msglist.iter.their_sid->binary);
|
|
strbuf_putc(b, ',');
|
|
strbuf_sprintf(b, "%"PRIu64, r->u.msglist.iter.offset);
|
|
strbuf_putc(b, ',');
|
|
strbuf_json_string(b, alloca_meshms_token(&r->u.msglist.iter._conv->their_ply.bundle_id, r->u.msglist.iter.offset));
|
|
strbuf_putc(b, ',');
|
|
strbuf_json_string(b, r->u.msglist.iter.text);
|
|
strbuf_putc(b, ',');
|
|
strbuf_json_boolean(b, 1);
|
|
strbuf_putc(b, ',');
|
|
strbuf_json_boolean(b, r->u.msglist.iter.read);
|
|
strbuf_putc(b, ',');
|
|
strbuf_sprintf(b, "%"PRIu64, r->u.msglist.iter.ack_offset);
|
|
strbuf_puts(b, "]");
|
|
r->u.msglist.highest_ack_offset = r->u.msglist.iter.ack_offset;
|
|
}
|
|
break;
|
|
}
|
|
if (!strbuf_overrun(b)) {
|
|
++r->u.msglist.rowcount;
|
|
if ((r->u.msglist.finished = meshms_message_iterator_prev(&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;
|
|
}
|
|
abort();
|
|
return 0;
|
|
}
|
|
|