mirror of
https://github.com/servalproject/serval-dna.git
synced 2024-12-20 21:53:12 +00:00
New HTTP RESTful requests: MeshMS read message(s)
This commit is contained in:
parent
eba7f6555f
commit
7736a4ceb1
@ -97,6 +97,7 @@ static int http_request_parse_http_version(struct http_request *r);
|
|||||||
static int http_request_start_parsing_headers(struct http_request *r);
|
static int http_request_start_parsing_headers(struct http_request *r);
|
||||||
static int http_request_parse_header(struct http_request *r);
|
static int http_request_parse_header(struct http_request *r);
|
||||||
static int http_request_start_body(struct http_request *r);
|
static int http_request_start_body(struct http_request *r);
|
||||||
|
static int http_request_reject_content(struct http_request *r);
|
||||||
static int http_request_parse_body_form_data(struct http_request *r);
|
static int http_request_parse_body_form_data(struct http_request *r);
|
||||||
static void http_request_start_response(struct http_request *r);
|
static void http_request_start_response(struct http_request *r);
|
||||||
|
|
||||||
@ -1025,6 +1026,9 @@ static int http_request_start_body(struct http_request *r)
|
|||||||
DEBUGF("Malformed HTTP %s request: missing Content-Length header", r->verb);
|
DEBUGF("Malformed HTTP %s request: missing Content-Length header", r->verb);
|
||||||
return 411;
|
return 411;
|
||||||
}
|
}
|
||||||
|
if (r->request_header.content_length == 0) {
|
||||||
|
r->parser = http_request_reject_content;
|
||||||
|
} else {
|
||||||
if (r->request_header.content_type.type[0] == '\0') {
|
if (r->request_header.content_type.type[0] == '\0') {
|
||||||
if (r->debug_flag && *r->debug_flag)
|
if (r->debug_flag && *r->debug_flag)
|
||||||
DEBUGF("Malformed HTTP %s request: missing Content-Type header", r->verb);
|
DEBUGF("Malformed HTTP %s request: missing Content-Type header", r->verb);
|
||||||
@ -1050,6 +1054,7 @@ static int http_request_start_body(struct http_request *r)
|
|||||||
return 415;
|
return 415;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
}
|
||||||
else {
|
else {
|
||||||
if (r->debug_flag && *r->debug_flag)
|
if (r->debug_flag && *r->debug_flag)
|
||||||
DEBUGF("Unsupported HTTP %s request", r->verb);
|
DEBUGF("Unsupported HTTP %s request", r->verb);
|
||||||
@ -1061,6 +1066,22 @@ static int http_request_start_body(struct http_request *r)
|
|||||||
return 0;
|
return 0;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/* A special content parser that rejects any content, used when a Content-Type: 0 header was
|
||||||
|
* received.
|
||||||
|
*
|
||||||
|
* @author Andrew Bettison <andrew@servalproject.com>
|
||||||
|
*/
|
||||||
|
static int http_request_reject_content(struct http_request *r)
|
||||||
|
{
|
||||||
|
if (r->debug_flag && *r->debug_flag) {
|
||||||
|
if (r->request_header.content_length != CONTENT_LENGTH_UNKNOWN)
|
||||||
|
DEBUGF("Malformed HTTP %s request (Content-Length %"PRIhttp_size_t"): spurious content", r->verb, r->request_header.content_length);
|
||||||
|
else
|
||||||
|
DEBUGF("Malformed HTTP %s request: spurious content", r->verb);
|
||||||
|
}
|
||||||
|
return 400;
|
||||||
|
}
|
||||||
|
|
||||||
/* Returns 1 if a MIME delimiter is skipped, 2 if a MIME close-delimiter is skipped.
|
/* Returns 1 if a MIME delimiter is skipped, 2 if a MIME close-delimiter is skipped.
|
||||||
*/
|
*/
|
||||||
static int _skip_mime_boundary(struct http_request *r)
|
static int _skip_mime_boundary(struct http_request *r)
|
||||||
@ -1208,7 +1229,7 @@ static int http_request_form_data_start_part(struct http_request *r, int b)
|
|||||||
*
|
*
|
||||||
* NOTE: No support for nested/mixed parts, as that would considerably complicate the parser. If
|
* NOTE: No support for nested/mixed parts, as that would considerably complicate the parser. If
|
||||||
* the need arises in future, we will deal with it then. In the meantime, we will have something
|
* the need arises in future, we will deal with it then. In the meantime, we will have something
|
||||||
* that meets our immediate needs for Rhizome Direct and a variety of use cases.
|
* that meets our immediate needs for Rhizome Direct and the RESTful API.
|
||||||
*
|
*
|
||||||
* @author Andrew Bettison <andrew@servalproject.com>
|
* @author Andrew Bettison <andrew@servalproject.com>
|
||||||
*/
|
*/
|
||||||
|
143
meshms.c
143
meshms.c
@ -32,6 +32,8 @@ Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
|
|||||||
#define MESHMS_BLOCK_TYPE_ACK 0x01
|
#define MESHMS_BLOCK_TYPE_ACK 0x01
|
||||||
#define MESHMS_BLOCK_TYPE_MESSAGE 0x02 // NUL-terminated UTF8 string
|
#define MESHMS_BLOCK_TYPE_MESSAGE 0x02 // NUL-terminated UTF8 string
|
||||||
|
|
||||||
|
static unsigned mark_read(struct meshms_conversations *conv, const sid_t *their_sid, const uint64_t offset);
|
||||||
|
|
||||||
void meshms_free_conversations(struct meshms_conversations *conv)
|
void meshms_free_conversations(struct meshms_conversations *conv)
|
||||||
{
|
{
|
||||||
if (conv) {
|
if (conv) {
|
||||||
@ -681,7 +683,7 @@ static enum meshms_status write_known_conversations(rhizome_manifest *m, struct
|
|||||||
// error is already logged
|
// error is already logged
|
||||||
break;
|
break;
|
||||||
case RHIZOME_BUNDLE_STATUS_NEW:
|
case RHIZOME_BUNDLE_STATUS_NEW:
|
||||||
status = MESHMS_STATUS_OK;
|
status = MESHMS_STATUS_UPDATED;
|
||||||
break;
|
break;
|
||||||
case RHIZOME_BUNDLE_STATUS_SAME:
|
case RHIZOME_BUNDLE_STATUS_SAME:
|
||||||
case RHIZOME_BUNDLE_STATUS_DUPLICATE:
|
case RHIZOME_BUNDLE_STATUS_DUPLICATE:
|
||||||
@ -953,6 +955,51 @@ enum meshms_status meshms_send_message(const sid_t *sender, const sid_t *recipie
|
|||||||
return status;
|
return status;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
enum meshms_status meshms_mark_read(const sid_t *sender, const sid_t *recipient, uint64_t offset)
|
||||||
|
{
|
||||||
|
if (config.debug.meshms)
|
||||||
|
DEBUGF("sender=%s recipient=%s offset=%"PRIu64,
|
||||||
|
alloca_tohex_sid_t(*sender),
|
||||||
|
recipient ? alloca_tohex_sid_t(*recipient) : "NULL",
|
||||||
|
offset
|
||||||
|
);
|
||||||
|
enum meshms_status status = MESHMS_STATUS_ERROR;
|
||||||
|
struct meshms_conversations *conv = NULL;
|
||||||
|
rhizome_manifest *m = rhizome_new_manifest();
|
||||||
|
if (!m)
|
||||||
|
goto end;
|
||||||
|
if (meshms_failed(status = get_my_conversation_bundle(sender, m)))
|
||||||
|
goto end;
|
||||||
|
// read all conversations, so we can write them again
|
||||||
|
if (meshms_failed(status = read_known_conversations(m, NULL, &conv)))
|
||||||
|
goto end;
|
||||||
|
// read the full list of conversations from the database too
|
||||||
|
if (meshms_failed(status = get_database_conversations(sender, NULL, &conv)))
|
||||||
|
goto end;
|
||||||
|
// check if any incoming conversations need to be acked or have new messages and update the read offset
|
||||||
|
unsigned changed = 0;
|
||||||
|
if (meshms_failed(status = update_conversations(sender, conv)))
|
||||||
|
goto end;
|
||||||
|
if (status == MESHMS_STATUS_UPDATED)
|
||||||
|
changed = 1;
|
||||||
|
changed += mark_read(conv, recipient, offset);
|
||||||
|
if (config.debug.meshms)
|
||||||
|
DEBUGF("changed=%u", changed);
|
||||||
|
if (changed) {
|
||||||
|
if (meshms_failed(status = write_known_conversations(m, conv)))
|
||||||
|
goto end;
|
||||||
|
if (status != MESHMS_STATUS_UPDATED) {
|
||||||
|
WHYF("expecting %d (MESHMS_STATUS_UPDATED), got %s", MESHMS_STATUS_UPDATED, status);
|
||||||
|
status = MESHMS_STATUS_ERROR;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
end:
|
||||||
|
if (m)
|
||||||
|
rhizome_manifest_free(m);
|
||||||
|
meshms_free_conversations(conv);
|
||||||
|
return status;
|
||||||
|
}
|
||||||
|
|
||||||
// output the list of existing conversations for a given local identity
|
// output the list of existing conversations for a given local identity
|
||||||
int app_meshms_conversations(const struct cli_parsed *parsed, struct cli_context *context)
|
int app_meshms_conversations(const struct cli_parsed *parsed, struct cli_context *context)
|
||||||
{
|
{
|
||||||
@ -1127,35 +1174,30 @@ int app_meshms_list_messages(const struct cli_parsed *parsed, struct cli_context
|
|||||||
}
|
}
|
||||||
|
|
||||||
// Returns the number of read markers moved.
|
// Returns the number of read markers moved.
|
||||||
static unsigned mark_read(struct meshms_conversations *conv, const sid_t *their_sid, const char *offset_str)
|
static unsigned mark_read(struct meshms_conversations *conv, const sid_t *their_sid, const uint64_t offset)
|
||||||
{
|
{
|
||||||
unsigned ret=0;
|
unsigned ret=0;
|
||||||
if (conv){
|
if (conv){
|
||||||
int cmp = their_sid ? cmp_sid_t(&conv->them, their_sid) : 0;
|
int cmp = their_sid ? cmp_sid_t(&conv->them, their_sid) : 0;
|
||||||
if (!their_sid || cmp<0){
|
if (!their_sid || cmp<0)
|
||||||
ret+=mark_read(conv->_left, their_sid, offset_str);
|
ret += mark_read(conv->_left, their_sid, offset);
|
||||||
}
|
|
||||||
if (!their_sid || cmp==0){
|
if (!their_sid || cmp==0){
|
||||||
// update read offset
|
// update read offset
|
||||||
// - never rewind
|
|
||||||
// - never past their last message
|
// - never past their last message
|
||||||
uint64_t offset = conv->their_last_message;
|
// - never rewind, only advance
|
||||||
if (offset_str){
|
uint64_t new_offset = offset;
|
||||||
uint64_t x = atol(offset_str);
|
if (new_offset > conv->their_last_message)
|
||||||
if (x<offset)
|
new_offset = conv->their_last_message;
|
||||||
offset=x;
|
if (new_offset > conv->read_offset) {
|
||||||
}
|
|
||||||
if (offset > conv->read_offset){
|
|
||||||
if (config.debug.meshms)
|
if (config.debug.meshms)
|
||||||
DEBUGF("Moving read marker for %s, from %"PRId64" to %"PRId64,
|
DEBUGF("Moving read marker for %s, from %"PRId64" to %"PRId64,
|
||||||
alloca_tohex_sid_t(conv->them), conv->read_offset, offset);
|
alloca_tohex_sid_t(conv->them), conv->read_offset, new_offset);
|
||||||
conv->read_offset = offset;
|
conv->read_offset = new_offset;
|
||||||
ret++;
|
ret++;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
if (!their_sid || cmp>0){
|
if (!their_sid || cmp>0)
|
||||||
ret+=mark_read(conv->_right, their_sid, offset_str);
|
ret += mark_read(conv->_right, their_sid, offset);
|
||||||
}
|
|
||||||
}
|
}
|
||||||
return ret;
|
return ret;
|
||||||
}
|
}
|
||||||
@ -1165,51 +1207,40 @@ int app_meshms_mark_read(const struct cli_parsed *parsed, struct cli_context *UN
|
|||||||
const char *my_sidhex, *their_sidhex, *offset_str;
|
const char *my_sidhex, *their_sidhex, *offset_str;
|
||||||
if (cli_arg(parsed, "sender_sid", &my_sidhex, str_is_subscriber_id, "") == -1
|
if (cli_arg(parsed, "sender_sid", &my_sidhex, str_is_subscriber_id, "") == -1
|
||||||
|| cli_arg(parsed, "recipient_sid", &their_sidhex, str_is_subscriber_id, NULL) == -1
|
|| cli_arg(parsed, "recipient_sid", &their_sidhex, str_is_subscriber_id, NULL) == -1
|
||||||
|| cli_arg(parsed, "offset", &offset_str, NULL, NULL)==-1)
|
|| cli_arg(parsed, "offset", &offset_str, str_is_uint64_decimal, NULL)==-1)
|
||||||
return -1;
|
return -1;
|
||||||
|
|
||||||
if (create_serval_instance_dir() == -1)
|
if (create_serval_instance_dir() == -1)
|
||||||
return -1;
|
return -1;
|
||||||
if (!(keyring = keyring_open_instance_cli(parsed)))
|
if (!(keyring = keyring_open_instance_cli(parsed)))
|
||||||
return -1;
|
return -1;
|
||||||
if (rhizome_opendb() == -1){
|
int ret = -1;
|
||||||
keyring_free(keyring);
|
if (rhizome_opendb() == -1)
|
||||||
keyring = NULL;
|
goto done;
|
||||||
return -1;
|
|
||||||
}
|
|
||||||
sid_t my_sid, their_sid;
|
sid_t my_sid, their_sid;
|
||||||
fromhex(my_sid.binary, my_sidhex, sizeof(my_sid.binary));
|
if (str_to_sid_t(&my_sid, my_sidhex) == -1) {
|
||||||
if (their_sidhex)
|
ret = WHYF("my_sidhex=%s", my_sidhex);
|
||||||
fromhex(their_sid.binary, their_sidhex, sizeof(their_sid.binary));
|
goto done;
|
||||||
|
}
|
||||||
enum meshms_status status = MESHMS_STATUS_ERROR;
|
if (their_sidhex && str_to_sid_t(&their_sid, their_sidhex) == -1) {
|
||||||
struct meshms_conversations *conv = NULL;
|
ret = WHYF("their_sidhex=%s", their_sidhex);
|
||||||
rhizome_manifest *m = rhizome_new_manifest();
|
goto done;
|
||||||
if (!m)
|
}
|
||||||
goto end;
|
uint64_t offset = UINT64_MAX;
|
||||||
if (meshms_failed(status = get_my_conversation_bundle(&my_sid, m)))
|
if (offset_str) {
|
||||||
goto end;
|
if (!their_sidhex) {
|
||||||
// read all conversations, so we can write them again
|
ret = WHY("missing recipient_sid");
|
||||||
if (meshms_failed(status = read_known_conversations(m, NULL, &conv)))
|
goto done;
|
||||||
goto end;
|
}
|
||||||
// read the full list of conversations from the database too
|
if (!str_to_uint64(offset_str, 10, &offset, NULL)) {
|
||||||
if (meshms_failed(status = get_database_conversations(&my_sid, NULL, &conv)))
|
ret = WHYF("offset_str=%s", offset_str);
|
||||||
goto end;
|
goto done;
|
||||||
// check if any incoming conversations need to be acked or have new messages and update the read offset
|
}
|
||||||
int changed = 0;
|
}
|
||||||
if (meshms_failed(status = update_conversations(&my_sid, conv)))
|
enum meshms_status status = meshms_mark_read(&my_sid, their_sidhex ? &their_sid : NULL, offset);
|
||||||
goto end;
|
ret = (status == MESHMS_STATUS_UPDATED) ? MESHMS_STATUS_OK : status;
|
||||||
if (status == MESHMS_STATUS_UPDATED)
|
done:
|
||||||
changed = 1;
|
|
||||||
if (mark_read(conv, their_sidhex?&their_sid:NULL, offset_str))
|
|
||||||
changed =1;
|
|
||||||
if (changed)
|
|
||||||
status = write_known_conversations(m, conv);
|
|
||||||
end:
|
|
||||||
if (m)
|
|
||||||
rhizome_manifest_free(m);
|
|
||||||
meshms_free_conversations(conv);
|
|
||||||
keyring_free(keyring);
|
keyring_free(keyring);
|
||||||
keyring = NULL;
|
keyring = NULL;
|
||||||
return status;
|
return ret;
|
||||||
}
|
}
|
||||||
|
20
meshms.h
20
meshms.h
@ -175,9 +175,25 @@ enum meshms_status meshms_message_iterator_prev(struct meshms_message_iterator *
|
|||||||
|
|
||||||
/* Append a message ('message_len' bytes of UTF8 at 'message') to the sender's
|
/* Append a message ('message_len' bytes of UTF8 at 'message') to the sender's
|
||||||
* ply in the conversation between 'sender' and 'recipient'. If no
|
* ply in the conversation between 'sender' and 'recipient'. If no
|
||||||
* conversation (ply bundle) exists, then create it. Returns 0 on success, -1
|
* conversation (ply bundle) exists, then create it. Returns
|
||||||
* on error (already logged).
|
* MESHMS_STATUS_UPDATED on success, any other value indicates a failure or
|
||||||
|
* error (which is already logged).
|
||||||
*/
|
*/
|
||||||
enum meshms_status meshms_send_message(const sid_t *sender, const sid_t *recipient, const char *message, size_t message_len);
|
enum meshms_status meshms_send_message(const sid_t *sender, const sid_t *recipient, const char *message, size_t message_len);
|
||||||
|
|
||||||
|
/* Update the read offset for one or more conversations. Returns
|
||||||
|
* MESHMS_STATUS_UPDATED on success, any other value indicates a failure or
|
||||||
|
* error (which is already logged).
|
||||||
|
*
|
||||||
|
* If 'offset' is greater than a conversation's last-received offset, then it
|
||||||
|
* is clamped to the last-received offset. This means that passing an offset
|
||||||
|
* of UINT64_MAX will mark the conversation as fully read, and an offset of
|
||||||
|
* zero will have no effect.
|
||||||
|
*
|
||||||
|
* If 'recipient' is NULL then all of the sender's conversations are marked
|
||||||
|
* with the given read offset. In this case it only makes sense to pass an
|
||||||
|
* offest of UINT64_MAX.
|
||||||
|
*/
|
||||||
|
enum meshms_status meshms_mark_read(const sid_t *sender, const sid_t *recipient, uint64_t offset);
|
||||||
|
|
||||||
#endif // __SERVAL_DNA__MESHMS_H
|
#endif // __SERVAL_DNA__MESHMS_H
|
||||||
|
@ -112,6 +112,9 @@ static HTTP_HANDLER restful_meshms_conversationlist_json;
|
|||||||
static HTTP_HANDLER restful_meshms_messagelist_json;
|
static HTTP_HANDLER restful_meshms_messagelist_json;
|
||||||
static HTTP_HANDLER restful_meshms_newsince_messagelist_json;
|
static HTTP_HANDLER restful_meshms_newsince_messagelist_json;
|
||||||
static HTTP_HANDLER restful_meshms_sendmessage;
|
static HTTP_HANDLER restful_meshms_sendmessage;
|
||||||
|
static HTTP_HANDLER restful_meshms_read_all_conversations;
|
||||||
|
static HTTP_HANDLER restful_meshms_read_all_messages;
|
||||||
|
static HTTP_HANDLER restful_meshms_read_to_offset;
|
||||||
|
|
||||||
int restful_meshms_(httpd_request *r, const char *remainder)
|
int restful_meshms_(httpd_request *r, const char *remainder)
|
||||||
{
|
{
|
||||||
@ -119,6 +122,7 @@ int restful_meshms_(httpd_request *r, const char *remainder)
|
|||||||
if (!is_rhizome_http_enabled())
|
if (!is_rhizome_http_enabled())
|
||||||
return 403;
|
return 403;
|
||||||
const char *verb = HTTP_VERB_GET;
|
const char *verb = HTTP_VERB_GET;
|
||||||
|
http_size_t content_length = CONTENT_LENGTH_UNKNOWN;
|
||||||
HTTP_HANDLER *handler = NULL;
|
HTTP_HANDLER *handler = NULL;
|
||||||
const char *end;
|
const char *end;
|
||||||
if (strn_to_sid_t(&r->sid1, remainder, SIZE_MAX, &end) != -1) {
|
if (strn_to_sid_t(&r->sid1, remainder, SIZE_MAX, &end) != -1) {
|
||||||
@ -126,7 +130,14 @@ int restful_meshms_(httpd_request *r, const char *remainder)
|
|||||||
if (strcmp(remainder, "/conversationlist.json") == 0) {
|
if (strcmp(remainder, "/conversationlist.json") == 0) {
|
||||||
handler = restful_meshms_conversationlist_json;
|
handler = restful_meshms_conversationlist_json;
|
||||||
remainder = "";
|
remainder = "";
|
||||||
} else if (*remainder == '/' && strn_to_sid_t(&r->sid2, remainder + 1, SIZE_MAX, &end) != -1) {
|
}
|
||||||
|
else if (strcmp(remainder, "/readall") == 0) {
|
||||||
|
handler = restful_meshms_read_all_conversations;
|
||||||
|
verb = HTTP_VERB_POST;
|
||||||
|
content_length = 0;
|
||||||
|
remainder = "";
|
||||||
|
}
|
||||||
|
else if (*remainder == '/' && strn_to_sid_t(&r->sid2, remainder + 1, SIZE_MAX, &end) != -1) {
|
||||||
remainder = end;
|
remainder = end;
|
||||||
if (strcmp(remainder, "/messagelist.json") == 0) {
|
if (strcmp(remainder, "/messagelist.json") == 0) {
|
||||||
handler = restful_meshms_messagelist_json;
|
handler = restful_meshms_messagelist_json;
|
||||||
@ -144,12 +155,36 @@ int restful_meshms_(httpd_request *r, const char *remainder)
|
|||||||
verb = HTTP_VERB_POST;
|
verb = HTTP_VERB_POST;
|
||||||
remainder = "";
|
remainder = "";
|
||||||
}
|
}
|
||||||
|
else if (strcmp(remainder, "/readall") == 0) {
|
||||||
|
handler = restful_meshms_read_all_messages;
|
||||||
|
verb = HTTP_VERB_POST;
|
||||||
|
content_length = 0;
|
||||||
|
remainder = "";
|
||||||
|
}
|
||||||
|
else if (str_startswith(remainder, "/recv/", &end)) {
|
||||||
|
remainder = end;
|
||||||
|
if (str_to_uint64(remainder, 10, &r->ui64, &end)) {
|
||||||
|
remainder = end;
|
||||||
|
if (strcmp(remainder, "/read") == 0) {
|
||||||
|
handler = restful_meshms_read_to_offset;
|
||||||
|
verb = HTTP_VERB_POST;
|
||||||
|
content_length = 0;
|
||||||
|
remainder = "";
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
if (handler == NULL)
|
if (handler == NULL)
|
||||||
return 404;
|
return 404;
|
||||||
if (r->http.verb != verb)
|
if (r->http.verb != verb)
|
||||||
return 405;
|
return 405;
|
||||||
|
if ( content_length != CONTENT_LENGTH_UNKNOWN
|
||||||
|
&& r->http.request_header.content_length != CONTENT_LENGTH_UNKNOWN
|
||||||
|
&& r->http.request_header.content_length != content_length) {
|
||||||
|
http_request_simple_response(&r->http, 400, "Bad content length");
|
||||||
|
return 400;
|
||||||
|
}
|
||||||
int ret = authorize(&r->http);
|
int ret = authorize(&r->http);
|
||||||
if (ret)
|
if (ret)
|
||||||
return ret;
|
return ret;
|
||||||
@ -566,3 +601,42 @@ static int restful_meshms_sendmessage_end(struct http_request *hr)
|
|||||||
return http_request_meshms_response(r, 0, NULL, status);
|
return http_request_meshms_response(r, 0, NULL, status);
|
||||||
return http_request_meshms_response(r, 201, "Message sent", status);
|
return http_request_meshms_response(r, 201, "Message sent", status);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
static int restful_meshms_read_all_conversations(httpd_request *r, const char *remainder)
|
||||||
|
{
|
||||||
|
if (*remainder)
|
||||||
|
return 404;
|
||||||
|
assert(r->finalise_union == NULL);
|
||||||
|
enum meshms_status status;
|
||||||
|
if (meshms_failed(status = meshms_mark_read(&r->sid1, NULL, UINT64_MAX)))
|
||||||
|
return http_request_meshms_response(r, 0, NULL, status);
|
||||||
|
if (status == MESHMS_STATUS_UPDATED)
|
||||||
|
return http_request_meshms_response(r, 201, "Read offsets updated", status);
|
||||||
|
return http_request_meshms_response(r, 200, "Read offsets unchanged", status);
|
||||||
|
}
|
||||||
|
|
||||||
|
static int restful_meshms_read_all_messages(httpd_request *r, const char *remainder)
|
||||||
|
{
|
||||||
|
if (*remainder)
|
||||||
|
return 404;
|
||||||
|
assert(r->finalise_union == NULL);
|
||||||
|
enum meshms_status status;
|
||||||
|
if (meshms_failed(status = meshms_mark_read(&r->sid1, &r->sid2, UINT64_MAX)))
|
||||||
|
return http_request_meshms_response(r, 0, NULL, status);
|
||||||
|
if (status == MESHMS_STATUS_UPDATED)
|
||||||
|
return http_request_meshms_response(r, 201, "Read offset updated", status);
|
||||||
|
return http_request_meshms_response(r, 200, "Read offset unchanged", status);
|
||||||
|
}
|
||||||
|
|
||||||
|
static int restful_meshms_read_to_offset(httpd_request *r, const char *remainder)
|
||||||
|
{
|
||||||
|
if (*remainder)
|
||||||
|
return 404;
|
||||||
|
assert(r->finalise_union == NULL);
|
||||||
|
enum meshms_status status;
|
||||||
|
if (meshms_failed(status = meshms_mark_read(&r->sid1, &r->sid2, r->ui64)))
|
||||||
|
return http_request_meshms_response(r, 0, NULL, status);
|
||||||
|
if (status == MESHMS_STATUS_UPDATED)
|
||||||
|
return http_request_meshms_response(r, 201, "Read offset updated", status);
|
||||||
|
return http_request_meshms_response(r, 200, "Read offset unchanged", status);
|
||||||
|
}
|
||||||
|
34
str.c
34
str.c
@ -645,6 +645,11 @@ char *str_str(char *haystack, const char *needle, size_t haystack_len)
|
|||||||
return NULL;
|
return NULL;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
int str_is_uint64_decimal(const char *str)
|
||||||
|
{
|
||||||
|
return str_to_uint64(str, 10, NULL, NULL);
|
||||||
|
}
|
||||||
|
|
||||||
int str_to_int32(const char *str, unsigned base, int32_t *result, const char **afterp)
|
int str_to_int32(const char *str, unsigned base, int32_t *result, const char **afterp)
|
||||||
{
|
{
|
||||||
if (isspace(*str))
|
if (isspace(*str))
|
||||||
@ -710,14 +715,29 @@ int str_to_int64(const char *str, unsigned base, int64_t *result, const char **a
|
|||||||
|
|
||||||
int str_to_uint64(const char *str, unsigned base, uint64_t *result, const char **afterp)
|
int str_to_uint64(const char *str, unsigned base, uint64_t *result, const char **afterp)
|
||||||
{
|
{
|
||||||
if (isspace(*str))
|
return strn_to_uint64(str, 0, base, result, afterp);
|
||||||
return 0;
|
}
|
||||||
const char *end = str;
|
|
||||||
errno = 0;
|
int strn_to_uint64(const char *str, size_t strlen, unsigned base, uint64_t *result, const char **afterp)
|
||||||
unsigned long long value = strtoull(str, (char**)&end, base);
|
{
|
||||||
|
assert(base > 0);
|
||||||
|
assert(base <= 16);
|
||||||
|
uint64_t value = 0;
|
||||||
|
uint64_t newvalue = 0;
|
||||||
|
const char *const end = str + strlen;
|
||||||
|
const char *s;
|
||||||
|
for (s = str; strlen ? s < end : *s; ++s) {
|
||||||
|
int digit = hexvalue(*s);
|
||||||
|
if (digit < 0 || (unsigned)digit >= base)
|
||||||
|
break;
|
||||||
|
newvalue = value * base + digit;
|
||||||
|
if (newvalue < value) // overflow
|
||||||
|
break;
|
||||||
|
value = newvalue;
|
||||||
|
}
|
||||||
if (afterp)
|
if (afterp)
|
||||||
*afterp = end;
|
*afterp = s;
|
||||||
if (errno == ERANGE || end == str || isdigit(*end) || (!afterp && *end))
|
if (s == str || value > UINT64_MAX || value != newvalue || (!afterp && (strlen ? s != end : *s)))
|
||||||
return 0;
|
return 0;
|
||||||
if (result)
|
if (result)
|
||||||
*result = value;
|
*result = value;
|
||||||
|
9
str.h
9
str.h
@ -380,6 +380,14 @@ int strn_str_casecmp(const char *str1, size_t len1, const char *str2);
|
|||||||
*/
|
*/
|
||||||
char *str_str(char *haystack, const char *needle, size_t haystack_len);
|
char *str_str(char *haystack, const char *needle, size_t haystack_len);
|
||||||
|
|
||||||
|
/* Returns 1 if the given nul-terminated string parses successfully as an unsigned 64-bit integer.
|
||||||
|
* Returns 0 if not. This is simply a shortcut for str_to_uint32(str, 10, NULL, NULL), which is
|
||||||
|
* convenient for when a pointer to a predicate function is needed.
|
||||||
|
*
|
||||||
|
* @author Andrew Bettison <andrew@servalproject.com>
|
||||||
|
*/
|
||||||
|
int str_is_uint64_decimal(const char *str);
|
||||||
|
|
||||||
/* Parse a NUL-terminated string as an integer in ASCII radix notation in the given 'base' (eg,
|
/* Parse a NUL-terminated string as an integer in ASCII radix notation in the given 'base' (eg,
|
||||||
* base=10 means decimal).
|
* base=10 means decimal).
|
||||||
*
|
*
|
||||||
@ -408,6 +416,7 @@ int str_to_uint64(const char *str, unsigned base, uint64_t *result, const char *
|
|||||||
* @author Andrew Bettison <andrew@servalproject.com>
|
* @author Andrew Bettison <andrew@servalproject.com>
|
||||||
*/
|
*/
|
||||||
int strn_to_uint32(const char *str, size_t strlen, unsigned base, uint32_t *result, const char **afterp);
|
int strn_to_uint32(const char *str, size_t strlen, unsigned base, uint32_t *result, const char **afterp);
|
||||||
|
int strn_to_uint64(const char *str, size_t strlen, unsigned base, uint64_t *result, const char **afterp);
|
||||||
|
|
||||||
/* Parse a string as an integer in ASCII radix notation in the given 'base' (eg, base=10 means
|
/* Parse a string as an integer in ASCII radix notation in the given 'base' (eg, base=10 means
|
||||||
* decimal) and scale the result by a factor given by an optional suffix "scaling" character in the
|
* decimal) and scale the result by a factor given by an optional suffix "scaling" character in the
|
||||||
|
@ -126,6 +126,7 @@ set_rhizome_config() {
|
|||||||
set debug.rhizome_manifest on \
|
set debug.rhizome_manifest on \
|
||||||
set debug.rhizome_store on \
|
set debug.rhizome_store on \
|
||||||
set debug.rhizome on \
|
set debug.rhizome on \
|
||||||
|
set debug.meshms on \
|
||||||
set debug.verbose on \
|
set debug.verbose on \
|
||||||
set log.console.level debug
|
set log.console.level debug
|
||||||
}
|
}
|
||||||
@ -1359,4 +1360,130 @@ test_MeshmsSendNoIdentity() {
|
|||||||
assertJqGrep --ignore-case http.body '.http_status_message' 'identity.*unknown'
|
assertJqGrep --ignore-case http.body '.http_status_message' 'identity.*unknown'
|
||||||
}
|
}
|
||||||
|
|
||||||
|
doc_MeshmsReadAllConversations="HTTP RESTful MeshMS mark all conversations read"
|
||||||
|
setup_MeshmsReadAllConversations() {
|
||||||
|
IDENTITY_COUNT=5
|
||||||
|
setup
|
||||||
|
# create 3 threads, with all permutations of incoming and outgoing messages
|
||||||
|
executeOk_servald meshms send message $SIDA1 $SIDA2 "Message1"
|
||||||
|
executeOk_servald meshms send message $SIDA3 $SIDA1 "Message2"
|
||||||
|
executeOk_servald meshms send message $SIDA1 $SIDA4 "Message3"
|
||||||
|
executeOk_servald meshms send message $SIDA4 $SIDA1 "Message4"
|
||||||
|
executeOk_servald meshms list conversations $SIDA1
|
||||||
|
assertStdoutGrep --stderr --matches=1 ":$SIDA2::0:0\$"
|
||||||
|
assertStdoutGrep --stderr --matches=1 ":$SIDA3:unread:11:0\$"
|
||||||
|
assertStdoutGrep --stderr --matches=1 ":$SIDA4:unread:14:0\$"
|
||||||
|
}
|
||||||
|
test_MeshmsReadAllConversations() {
|
||||||
|
executeOk curl \
|
||||||
|
--silent --show-error --write-out '%{http_code}' \
|
||||||
|
--output http_body \
|
||||||
|
--basic --user harry:potter \
|
||||||
|
--request POST \
|
||||||
|
"http://$addr_localhost:$PORTA/restful/meshms/$SIDA1/readall"
|
||||||
|
tfw_cat http_body
|
||||||
|
assertExitStatus == 0
|
||||||
|
assertStdoutIs 201
|
||||||
|
executeOk_servald meshms list conversations $SIDA1
|
||||||
|
assertStdoutGrep --stderr --matches=1 ":$SIDA2::0:0\$"
|
||||||
|
assertStdoutGrep --stderr --matches=1 ":$SIDA3::11:11\$"
|
||||||
|
assertStdoutGrep --stderr --matches=1 ":$SIDA4::14:14\$"
|
||||||
|
}
|
||||||
|
|
||||||
|
doc_MeshmsPostSpuriousContent="HTTP RESTful MeshMS rejects unwanted content in POST request"
|
||||||
|
setup_MeshmsPostSpuriousContent() {
|
||||||
|
IDENTITY_COUNT=2
|
||||||
|
setup
|
||||||
|
# create 3 threads, with all permutations of incoming and outgoing messages
|
||||||
|
executeOk_servald meshms send message $SIDA1 $SIDA2 "Message1"
|
||||||
|
executeOk_servald meshms send message $SIDA2 $SIDA1 "Message2"
|
||||||
|
executeOk_servald meshms send message $SIDA1 $SIDA2 "Message3"
|
||||||
|
executeOk_servald meshms send message $SIDA2 $SIDA1 "Message4"
|
||||||
|
executeOk_servald meshms list conversations $SIDA1
|
||||||
|
assertStdoutGrep --stderr --matches=1 ":$SIDA2:unread:29:0\$"
|
||||||
|
}
|
||||||
|
test_MeshmsPostSpuriousContent() {
|
||||||
|
executeOk curl \
|
||||||
|
--silent --show-error --write-out '%{http_code}' \
|
||||||
|
--output http_body \
|
||||||
|
--basic --user harry:potter \
|
||||||
|
--request POST \
|
||||||
|
--form "offset=0" \
|
||||||
|
"http://$addr_localhost:$PORTA/restful/meshms/$SIDA1/readall"
|
||||||
|
tfw_cat http_body
|
||||||
|
assertExitStatus == 0
|
||||||
|
assertStdoutIs 400
|
||||||
|
assertJq http_body 'contains({"http_status_code": 400})'
|
||||||
|
assertJqGrep --ignore-case http_body '.http_status_message' 'content length'
|
||||||
|
executeOk_servald meshms list conversations $SIDA1
|
||||||
|
assertStdoutGrep --stderr --matches=1 ":$SIDA2:unread:29:0\$"
|
||||||
|
}
|
||||||
|
|
||||||
|
doc_MeshmsReadAllMessages="HTTP RESTful MeshMS mark all conversations read"
|
||||||
|
setup_MeshmsReadAllMessages() {
|
||||||
|
IDENTITY_COUNT=5
|
||||||
|
setup
|
||||||
|
# create 3 threads, with all permutations of incoming and outgoing messages
|
||||||
|
executeOk_servald meshms send message $SIDA1 $SIDA2 "Message1"
|
||||||
|
executeOk_servald meshms send message $SIDA3 $SIDA1 "Message2"
|
||||||
|
executeOk_servald meshms send message $SIDA1 $SIDA2 "Message3"
|
||||||
|
executeOk_servald meshms send message $SIDA1 $SIDA4 "Message4"
|
||||||
|
executeOk_servald meshms send message $SIDA4 $SIDA1 "Message5"
|
||||||
|
executeOk_servald meshms send message $SIDA1 $SIDA2 "Message6"
|
||||||
|
executeOk_servald meshms list conversations $SIDA2
|
||||||
|
assertStdoutGrep --stderr --matches=1 ":$SIDA1:unread:33:0\$"
|
||||||
|
}
|
||||||
|
test_MeshmsReadAllMessages() {
|
||||||
|
executeOk curl \
|
||||||
|
--silent --show-error --write-out '%{http_code}' \
|
||||||
|
--output http_body \
|
||||||
|
--basic --user harry:potter \
|
||||||
|
--request POST \
|
||||||
|
"http://$addr_localhost:$PORTA/restful/meshms/$SIDA2/$SIDA1/readall"
|
||||||
|
tfw_cat http_body
|
||||||
|
assertExitStatus == 0
|
||||||
|
assertStdoutIs 201
|
||||||
|
executeOk_servald meshms list conversations $SIDA2
|
||||||
|
assertStdoutGrep --stderr --matches=1 ":$SIDA1::33:33\$"
|
||||||
|
}
|
||||||
|
|
||||||
|
doc_MeshmsReadMessage="HTTP RESTful MeshMS mark a message as read"
|
||||||
|
setup_MeshmsReadMessage() {
|
||||||
|
IDENTITY_COUNT=5
|
||||||
|
setup
|
||||||
|
# create 3 threads, with all permutations of incoming and outgoing messages
|
||||||
|
executeOk_servald meshms send message $SIDA1 $SIDA2 "Message1"
|
||||||
|
executeOk_servald meshms send message $SIDA3 $SIDA1 "Message2"
|
||||||
|
executeOk_servald meshms send message $SIDA1 $SIDA2 "Message3"
|
||||||
|
executeOk_servald meshms send message $SIDA1 $SIDA4 "Message4"
|
||||||
|
executeOk_servald meshms send message $SIDA4 $SIDA1 "Message5"
|
||||||
|
executeOk_servald meshms send message $SIDA1 $SIDA2 "Message6"
|
||||||
|
executeOk_servald meshms list conversations $SIDA2
|
||||||
|
assertStdoutGrep --stderr --matches=1 ":$SIDA1:unread:33:0\$"
|
||||||
|
}
|
||||||
|
test_MeshmsReadMessage() {
|
||||||
|
executeOk curl \
|
||||||
|
--silent --show-error --write-out '%{http_code}' \
|
||||||
|
--output http_body \
|
||||||
|
--basic --user harry:potter \
|
||||||
|
--request POST \
|
||||||
|
"http://$addr_localhost:$PORTA/restful/meshms/$SIDA2/$SIDA1/recv/22/read"
|
||||||
|
tfw_cat http_body
|
||||||
|
assertExitStatus == 0
|
||||||
|
assertStdoutIs 201
|
||||||
|
executeOk_servald meshms list conversations $SIDA2
|
||||||
|
assertStdoutGrep --stderr --matches=1 ":$SIDA1:unread:33:22\$"
|
||||||
|
executeOk curl \
|
||||||
|
--silent --show-error --write-out '%{http_code}' \
|
||||||
|
--output read.json \
|
||||||
|
--basic --user harry:potter \
|
||||||
|
--request POST \
|
||||||
|
"http://$addr_localhost:$PORTA/restful/meshms/$SIDA2/$SIDA1/recv/11/read"
|
||||||
|
tfw_cat read.json
|
||||||
|
assertExitStatus == 0
|
||||||
|
assertStdoutIs 200
|
||||||
|
executeOk_servald meshms list conversations $SIDA2
|
||||||
|
assertStdoutGrep --stderr --matches=1 ":$SIDA1:unread:33:22\$"
|
||||||
|
}
|
||||||
|
|
||||||
runTests "$@"
|
runTests "$@"
|
||||||
|
Loading…
Reference in New Issue
Block a user