/* Serval DNA MeshMS Copyright (C) 2013 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. */ #define __MESHMS_INLINE #include #include "serval.h" #include "rhizome_types.h" #include "meshms.h" #include "log.h" #include "debug.h" #include "conf.h" #include "crypto.h" #include "strbuf.h" #include "keyring.h" #include "str.h" #include "dataformats.h" #include "overlay_buffer.h" 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) { while(conv){ struct meshms_conversations *n = conv; conv = conv->_next; free(n); } } static enum meshms_status get_my_conversation_bundle(const keyring_identity *id, rhizome_manifest *m) { /* Find our private key */ sign_keypair_t key; crypto_seed_keypair(&key, "incorrection%sconcentrativeness", alloca_tohex(id->box_sk, crypto_box_SECRETKEYBYTES)); struct rhizome_bundle_result result = rhizome_private_bundle(m, &key); switch (result.status) { case RHIZOME_BUNDLE_STATUS_NEW: case RHIZOME_BUNDLE_STATUS_SAME: case RHIZOME_BUNDLE_STATUS_DUPLICATE: // The 'meshms' automated test depends on this message; do not alter. DEBUGF(meshms, "MESHMS CONVERSATION BUNDLE bid=%s secret=%s", alloca_tohex_rhizome_bid_t(m->keypair.public_key), alloca_tohex(m->keypair.private_key.binary, RHIZOME_BUNDLE_KEY_BYTES) ); break; case RHIZOME_BUNDLE_STATUS_ERROR: case RHIZOME_BUNDLE_STATUS_INVALID: case RHIZOME_BUNDLE_STATUS_INCONSISTENT: WHYF("Error creating conversation manifest: %s", alloca_rhizome_bundle_result(result)); rhizome_bundle_result_free(&result); return MESHMS_STATUS_ERROR; case RHIZOME_BUNDLE_STATUS_BUSY: // TODO case RHIZOME_BUNDLE_STATUS_OLD: case RHIZOME_BUNDLE_STATUS_FAKE: case RHIZOME_BUNDLE_STATUS_NO_ROOM: case RHIZOME_BUNDLE_STATUS_MANIFEST_TOO_BIG: WARNF("Cannot create conversation manifest: %s", alloca_rhizome_bundle_result(result)); rhizome_bundle_result_free(&result); return MESHMS_STATUS_PROTOCOL_FAULT; case RHIZOME_BUNDLE_STATUS_READONLY: INFOF("Cannot create conversation manifest: %s", alloca_rhizome_bundle_result(result)); rhizome_bundle_result_free(&result); return MESHMS_STATUS_SID_LOCKED; } rhizome_bundle_result_free(&result); return MESHMS_STATUS_OK; } static struct meshms_conversations *add_conv(struct meshms_conversations **conv, const sid_t *them) { struct meshms_conversations **ptr = conv; while (*ptr) { int cmp = cmp_sid_t(&(*ptr)->them, them); if (cmp == 0) return *ptr; ptr=&(*ptr)->_next; } struct meshms_conversations *n = emalloc_zero(sizeof(struct meshms_conversations)); if (n){ n->them = *them; n->_next = *conv; *conv = n; } return n; } // find matching conversations // if their_sid == my_sid, return all conversations with any recipient static enum meshms_status get_database_conversations(const keyring_identity *id, struct meshms_conversations **conv) { sqlite_retry_state retry = SQLITE_RETRY_STATE_DEFAULT; sqlite3_stmt *statement = sqlite_prepare_bind(&retry, "SELECT id, version, filesize, tail, sender, recipient" " FROM manifests" " WHERE service = ?2" " AND (sender=?1 or recipient=?1)", SID_T, id->box_pk, STATIC_TEXT, RHIZOME_SERVICE_MESHMS2, END ); if (!statement) return MESHMS_STATUS_ERROR; DEBUGF(meshms, "Looking for conversations for %s", alloca_tohex_sid_t(*id->box_pk)); int r; while ((r=sqlite_step_retry(&retry, statement)) == SQLITE_ROW) { const char *id_hex = (const char *)sqlite3_column_text(statement, 0); uint64_t version = sqlite3_column_int64(statement, 1); int64_t size = sqlite3_column_int64(statement, 2); int64_t tail = sqlite3_column_int64(statement, 3); const char *sender = (const char *)sqlite3_column_text(statement, 4); const char *recipient = (const char *)sqlite3_column_text(statement, 5); DEBUGF(meshms, "found id %s, sender %s, recipient %s, size %"PRId64, id_hex, sender, recipient, size); rhizome_bid_t bid; if (str_to_rhizome_bid_t(&bid, id_hex) == -1) { WHYF("invalid Bundle ID hex: %s -- skipping", alloca_str_toprint(id_hex)); continue; } const char *them = sender; sid_t their_sid; if (str_to_sid_t(&their_sid, them) == -1) { WHYF("invalid SID hex: %s -- skipping", alloca_str_toprint(them)); continue; } if (cmp_sid_t(&their_sid, id->box_pk) == 0) { them = recipient; if (str_to_sid_t(&their_sid, them) == -1) { WHYF("invalid SID hex: %s -- skipping", alloca_str_toprint(them)); continue; } } struct meshms_conversations *ptr = add_conv(conv, &their_sid); if (!ptr) break; struct message_ply *p; if (them==sender){ p=&ptr->their_ply; }else{ p=&ptr->my_ply; } p->found = p->known_bid = 1; p->bundle_id = bid; p->version = version; p->tail = tail; p->size = size; } sqlite3_finalize(statement); if (!sqlite_code_ok(r)) return MESHMS_STATUS_ERROR; return MESHMS_STATUS_OK; } static enum meshms_status open_ply(struct message_ply *ply, struct message_ply_read *reader) { if (ply->found && !message_ply_is_open(reader) && message_ply_read_open(reader, &ply->bundle_id, NULL)!=0) return MESHMS_STATUS_ERROR; return MESHMS_STATUS_OK; } static enum meshms_status update_their_stats(struct meshms_metadata *metadata, struct message_ply *ply, struct message_ply_read *reader) { DEBUGF(meshms, "Update their stats? (theirsize=%"PRIu64", plysize=%"PRIu64", lastmessage=%"PRIu64", lastackoffset=%"PRIu64", lastack=%"PRIu64")", metadata->their_size, ply->size, metadata->their_last_message, metadata->their_last_ack_offset, metadata->their_last_ack ); if (metadata->their_size != ply->size){ enum meshms_status status; if (meshms_failed(status = open_ply(ply, reader))) return status; uint8_t found_their_msg=0; uint8_t found_their_ack=0; reader->read.offset = reader->read.length; while((!found_their_msg || !found_their_ack) && message_ply_read_prev(reader) == 0){ // stop if we've seen these records before if (reader->record_end_offset <= metadata->their_size) break; switch(reader->type){ case MESSAGE_BLOCK_TYPE_MESSAGE: if (!found_their_msg){ found_their_msg = 1; metadata->their_last_message = reader->record_end_offset; DEBUGF(meshms, "Found their last message @%"PRIu64, metadata->their_last_message); } break; case MESSAGE_BLOCK_TYPE_ACK: if (!found_their_ack){ found_their_ack = 1; metadata->their_last_ack_offset = reader->record_end_offset; struct message_ply_ack ack; message_ply_parse_ack(reader, &ack); metadata->their_last_ack = ack.end_offset; DEBUGF(meshms, "Found their last ack @%"PRIu64" = %"PRIu64, metadata->their_last_ack_offset, metadata->their_last_ack); } break; } } metadata->their_size = ply->size; message_ply_read_rewind(reader); return MESHMS_STATUS_UPDATED; } return MESHMS_STATUS_OK; } static enum meshms_status update_my_stats(struct meshms_metadata *metadata, struct message_ply *ply, struct message_ply_read *reader) { DEBUGF(meshms, "Update my stats? (mysize=%"PRIu64", plysize=%"PRIu64", lastack=%"PRIu64")", metadata->my_size, ply->size, metadata->my_last_ack); if (metadata->my_size != ply->size){ enum meshms_status status; if (meshms_failed(status = open_ply(ply, reader))) return status; reader->read.offset = reader->read.length; if (message_ply_find_prev(reader, MESSAGE_BLOCK_TYPE_ACK)==0){ struct message_ply_ack ack; message_ply_parse_ack(reader, &ack); metadata->my_last_ack = ack.end_offset; DEBUGF(meshms, "Found my last ack %"PRId64, metadata->my_last_ack); } metadata->my_size = ply->size; message_ply_read_rewind(reader); return MESHMS_STATUS_UPDATED; } return MESHMS_STATUS_OK; } static enum meshms_status update_stats(struct meshms_conversations *conv) { enum meshms_status status = MESHMS_STATUS_OK; struct message_ply_read reader; bzero(&reader, sizeof reader); enum meshms_status tmp_status = update_their_stats(&conv->metadata, &conv->their_ply, &reader); message_ply_read_close(&reader); if (meshms_failed(tmp_status)) return tmp_status; if (tmp_status == MESHMS_STATUS_UPDATED) status = tmp_status; // Nothing else to be done if they have never sent us anything if (!conv->metadata.their_last_message) return status; tmp_status = update_my_stats(&conv->metadata, &conv->my_ply, &reader); message_ply_read_close(&reader); if (meshms_failed(tmp_status)) return tmp_status; if (tmp_status == MESHMS_STATUS_UPDATED) status = tmp_status; return status; } // create an ack if required. // return MESHMS_STATUS_UPDATED if the conversation index needs to be saved. static enum meshms_status update_conversation(const keyring_identity *id, struct meshms_conversations *conv) { DEBUG(meshms, "Checking if conversation needs to be acked"); enum meshms_status status = update_stats(conv); if (meshms_failed(status)) return status; if (conv->metadata.my_last_ack >= conv->metadata.their_last_message) return status; // append an ack for their message DEBUGF(meshms, "Creating ACK for %"PRId64" - %"PRId64, conv->metadata.my_last_ack, conv->metadata.their_last_message); unsigned char buffer[30]; struct overlay_buffer *b = ob_static(buffer, sizeof buffer); struct message_ply_ack ack; bzero(&ack, sizeof ack); ack.end_offset = conv->metadata.their_last_message; ack.start_offset = conv->metadata.my_last_ack; message_ply_append_ack(b, &ack); message_ply_append_timestamp(b); assert(!ob_overrun(b)); if (message_ply_append(id, RHIZOME_SERVICE_MESHMS2, &conv->them, &conv->my_ply, b, NULL, 0, NULL)!=0){ status = MESHMS_STATUS_ERROR; }else{ conv->metadata.my_last_ack = conv->metadata.their_last_message; conv->metadata.my_size += ob_position(b); status = MESHMS_STATUS_UPDATED; } ob_free(b); return status; } // update conversations, and return MESHMS_STATUS_UPDATED if the conversation index should be saved static enum meshms_status update_conversations(const keyring_identity *id, struct meshms_conversations **conv) { enum meshms_status rstatus = MESHMS_STATUS_OK; struct meshms_conversations **ptr = conv; while (*ptr) { struct meshms_conversations *n = *ptr; enum meshms_status status; if (meshms_failed(status = update_conversation(id, n))) return status; if (status == MESHMS_STATUS_UPDATED){ rstatus = MESHMS_STATUS_UPDATED; if (n != *conv){ DEBUGF(meshms, "Bumping conversation from %s", alloca_tohex_sid_t(n->them)); *ptr = n->_next; n->_next = *conv; *conv = n; continue; } } ptr = &(*ptr)->_next; } return rstatus; } // read our cached conversation list from our rhizome payload // if we can't load the existing data correctly, just ignore it. static enum meshms_status read_known_conversations(rhizome_manifest *m, struct meshms_conversations **conv) { if (m->haveSecret==NEW_BUNDLE_ID) return MESHMS_STATUS_OK; struct meshms_conversations **ptr = conv; struct rhizome_read read; bzero(&read, sizeof(read)); struct rhizome_read_buffer buff; bzero(&buff, sizeof(buff)); enum meshms_status status = MESHMS_STATUS_OK; enum rhizome_payload_status pstatus = rhizome_open_decrypt_read(m, &read); if (pstatus == RHIZOME_PAYLOAD_STATUS_NEW) { WARNF("Payload was not found for manifest %s, %"PRIu64, alloca_tohex_rhizome_bid_t(m->keypair.public_key), m->version); goto end; } if (pstatus != RHIZOME_PAYLOAD_STATUS_STORED && pstatus != RHIZOME_PAYLOAD_STATUS_EMPTY) goto end; uint8_t version=0xFF; ssize_t r = rhizome_read_buffered(&read, &buff, &version, 1); if (r == -1) goto end; if (version < 1 || version > 2) { WARNF("Unknown file format version (got 0x%02x)", version); goto end; } while (1) { uint8_t buffer[SID_SIZE + 12*3 + 1]; ssize_t bytes = rhizome_read_buffered(&read, &buff, buffer, sizeof buffer); if (bytes == 0) { status = MESHMS_STATUS_OK; goto end; } if (bytes < (ssize_t)SID_SIZE+1) break; const sid_t *sid = (sid_t *)&buffer[0]; int ofs = SID_SIZE; //TODO flags byte uint8_t flags = 0; struct meshms_metadata metadata; bzero(&metadata, sizeof metadata); int unpacked; if (version==1){ // force re-reading ply details uint64_t ignored=0; if ((unpacked = unpack_uint(buffer+ofs, bytes-ofs, &ignored)) == -1) break; ofs += unpacked; if ((unpacked = unpack_uint(buffer+ofs, bytes-ofs, &ignored)) == -1) break; ofs += unpacked; if ((unpacked = unpack_uint(buffer+ofs, bytes-ofs, &ignored)) == -1) break; ofs += unpacked; }else if(version>=2){ uint64_t delta=0; ofs ++; // flags = buffer[ofs++]; if ((unpacked = unpack_uint(buffer+ofs, bytes-ofs, &metadata.their_size)) == -1) break; ofs += unpacked; if ((unpacked = unpack_uint(buffer+ofs, bytes-ofs, &metadata.my_size)) == -1) break; ofs += unpacked; if ((unpacked = unpack_uint(buffer+ofs, bytes-ofs, &delta)) == -1) break; ofs += unpacked; metadata.their_last_message = metadata.their_size - delta; if ((unpacked = unpack_uint(buffer+ofs, bytes-ofs, &delta)) == -1) break; ofs += unpacked; metadata.read_offset = metadata.their_size - delta; if ((unpacked = unpack_uint(buffer+ofs, bytes-ofs, &delta)) == -1) break; ofs += unpacked; metadata.their_last_ack_offset = metadata.their_size - delta; if ((unpacked = unpack_uint(buffer+ofs, bytes-ofs, &delta)) == -1) break; ofs += unpacked; metadata.my_last_ack = metadata.their_size - delta; if ((unpacked = unpack_uint(buffer+ofs, bytes-ofs, &delta)) == -1) break; ofs += unpacked; metadata.their_last_ack = metadata.my_size - delta; } read.offset += ofs - bytes; struct meshms_conversations *n = emalloc_zero(sizeof(struct meshms_conversations)); if (!n) goto end; *ptr = n; ptr = &n->_next; n->them = *sid; n->metadata = metadata; DEBUGF(meshms, "Unpacked existing conversation for %s (their_size=%"PRIu64", my_size=%"PRIu64", last_message=%"PRIu64", read_offset=%"PRIu64", my_ack=%"PRIu64", their_ack=%"PRIu64")", alloca_tohex_sid_t(*sid), metadata.their_size, metadata.my_size, metadata.their_last_message, metadata.read_offset, metadata.my_last_ack, metadata.their_last_ack ); } end: rhizome_read_close(&read); return status; } static ssize_t write_conversation(struct rhizome_write *write, struct meshms_conversations *conv) { size_t len=0; unsigned char buffer[sizeof(conv->them) + (12*3) + 1]; bcopy(conv->them.binary, buffer, sizeof(conv->them)); len+=sizeof(conv->them); buffer[len++] = 0; // TODO reserved for flags assert(conv->metadata.their_size >= conv->metadata.their_last_message); assert(conv->metadata.their_size >= conv->metadata.read_offset); assert(conv->metadata.their_size >= conv->metadata.my_last_ack); assert(conv->metadata.their_size >= conv->metadata.their_last_ack_offset); assert(conv->metadata.my_size >= conv->metadata.their_last_ack); // assume that most ack & read offsets are going to be near the ply length // so store them as delta's. len+=pack_uint(&buffer[len], conv->metadata.their_size); len+=pack_uint(&buffer[len], conv->metadata.my_size); len+=pack_uint(&buffer[len], conv->metadata.their_size - conv->metadata.their_last_message); len+=pack_uint(&buffer[len], conv->metadata.their_size - conv->metadata.read_offset); len+=pack_uint(&buffer[len], conv->metadata.their_size - conv->metadata.their_last_ack_offset); len+=pack_uint(&buffer[len], conv->metadata.their_size - conv->metadata.my_last_ack); len+=pack_uint(&buffer[len], conv->metadata.my_size - conv->metadata.their_last_ack); assert(len <= sizeof buffer); if (write){ int ret=rhizome_write_buffer(write, buffer, len); if (ret == -1) return ret; } DEBUGF(meshms, "len %s, %"PRId64", %"PRId64", %"PRId64" = %zu", alloca_tohex_sid_t(conv->them), conv->metadata.their_last_message, conv->metadata.read_offset, conv->metadata.their_size, len ); return len; } static ssize_t write_conversations(struct rhizome_write *write, struct meshms_conversations *conv) { ssize_t len=0; while(conv){ ssize_t this_len = write_conversation(write, conv); if (this_len==-1) return this_len; len+=this_len; conv = conv->_next; } return len; } static enum meshms_status write_known_conversations(rhizome_manifest *m, struct meshms_conversations *conv) { rhizome_manifest *mout=NULL; struct rhizome_write write; bzero(&write, sizeof(write)); enum meshms_status status = MESHMS_STATUS_ERROR; // TODO rebalance tree...? rhizome_manifest_set_version(m, m->version + 1); rhizome_manifest_set_filesize(m, RHIZOME_SIZE_UNSET); rhizome_manifest_set_filehash(m, NULL); enum rhizome_payload_status pstatus = rhizome_write_open_manifest(&write, m); if (pstatus!=RHIZOME_PAYLOAD_STATUS_NEW) goto end; uint8_t version=2; if (rhizome_write_buffer(&write, &version, 1) == -1) goto end; if (write_conversations(&write, conv) == -1) goto end; if (write.file_offset == 1){ // Don't bother if we don't know anyone. status = MESHMS_STATUS_OK; goto end; } pstatus = rhizome_finish_write(&write); if (pstatus != RHIZOME_PAYLOAD_STATUS_NEW) goto end; rhizome_manifest_set_filehash(m, &write.id); rhizome_manifest_set_filesize(m, write.file_length); struct rhizome_bundle_result result = rhizome_manifest_finalise(m, &mout, 0); switch (result.status) { case RHIZOME_BUNDLE_STATUS_ERROR: // error is already logged break; case RHIZOME_BUNDLE_STATUS_NEW: status = MESHMS_STATUS_UPDATED; break; case RHIZOME_BUNDLE_STATUS_SAME: case RHIZOME_BUNDLE_STATUS_DUPLICATE: case RHIZOME_BUNDLE_STATUS_OLD: status = MESHMS_STATUS_PROTOCOL_FAULT; WARNF("MeshMS conversation manifest (version=%"PRIu64") gazumped by Rhizome store (version=%"PRIu64")", m->version, mout->version); break; case RHIZOME_BUNDLE_STATUS_NO_ROOM: status = MESHMS_STATUS_PROTOCOL_FAULT; WARNF("MeshMS ply manifest evicted from store"); break; case RHIZOME_BUNDLE_STATUS_INCONSISTENT: status = MESHMS_STATUS_PROTOCOL_FAULT; WARN("MeshMS conversation manifest not consistent with payload"); break; case RHIZOME_BUNDLE_STATUS_FAKE: case RHIZOME_BUNDLE_STATUS_READONLY: status = MESHMS_STATUS_PROTOCOL_FAULT; WARN("MeshMS conversation manifest is not signed"); break; case RHIZOME_BUNDLE_STATUS_INVALID: case RHIZOME_BUNDLE_STATUS_MANIFEST_TOO_BIG: status = MESHMS_STATUS_PROTOCOL_FAULT; WARN("MeshMS conversation manifest is invalid"); break; case RHIZOME_BUNDLE_STATUS_BUSY: status = MESHMS_STATUS_PROTOCOL_FAULT; WARNF("MeshMS conversation manifest not stored due to database locking"); break; } rhizome_bundle_result_free(&result); end: if (meshms_failed(status)) rhizome_fail_write(&write); if (mout && m!=mout) rhizome_manifest_free(mout); return status; } static enum meshms_status meshms_open_list(const keyring_identity *id, rhizome_manifest *m, struct meshms_conversations **conv) { enum meshms_status status; if (meshms_failed(status = get_my_conversation_bundle(id, m))) goto end; // read conversations payload if (meshms_failed(status = read_known_conversations(m, conv))) goto end; status = get_database_conversations(id, conv); end: return status; } static enum meshms_status meshms_save_list(const keyring_identity *id, rhizome_manifest *m, struct meshms_conversations **conv) { enum meshms_status status; if ((status = update_conversations(id, conv)) == MESHMS_STATUS_UPDATED) status = write_known_conversations(m, *conv); return status; } // read information about existing conversations from a rhizome payload enum meshms_status meshms_conversations_list(const keyring_identity *id, const sid_t *my_sid, struct meshms_conversations **conv) { enum meshms_status status = MESHMS_STATUS_ERROR; rhizome_manifest *m=NULL; assert(keyring != NULL); assert(id || my_sid); if (!my_sid){ my_sid = id->box_pk; }else if(!id){ id = keyring_find_identity_sid(keyring, my_sid); if (!id){ status = MESHMS_STATUS_SID_LOCKED; goto end; } } m = rhizome_new_manifest(); if (!m) goto end; if (meshms_failed(status = meshms_open_list(id, m, conv))) goto end; if (meshms_failed(status = meshms_save_list(id, m, conv))) goto end; end: rhizome_manifest_free(m); DEBUGF(meshms, "status=%d", status); return status; } /* Start traversing the given conversation binary tree in infix order. * * @author Andrew Bettison */ void meshms_conversation_iterator_start(struct meshms_conversation_iterator *it, struct meshms_conversations *conv) { it->current = conv; } /* Advance to the next conversation in the tree. * * @author Andrew Bettison */ void meshms_conversation_iterator_advance(struct meshms_conversation_iterator *it) { assert(it->current != NULL); // do not call on a finished iterator it->current = it->current->_next; } enum meshms_status meshms_message_iterator_open(struct meshms_message_iterator *iter, const sid_t *me, const sid_t *them) { assert(keyring != NULL); bzero(iter, sizeof *iter); DEBUGF(meshms, "iter=%p me=%s them=%s", iter, me ? alloca_tohex_sid_t(*me) : "NULL", them ? alloca_tohex_sid_t(*them) : "NULL" ); enum meshms_status status = MESHMS_STATUS_ERROR; struct meshms_conversations *conv = NULL; rhizome_manifest *m = NULL; keyring_identity *id = keyring_find_identity_sid(keyring, me); if (!id){ status = MESHMS_STATUS_SID_LOCKED; WHY("Identity not found"); goto fail; } if (!(m = rhizome_new_manifest())) goto error; if (meshms_failed(status = meshms_open_list(id, m, &conv))) goto fail; iter->identity = id; iter->timestamp = 0; iter->_in_ack = 0; iter->my_sid = *me; iter->their_sid = *them; struct meshms_conversations *c = conv; while(c){ if (cmp_sid_t(them, &c->them)==0){ DEBUGF(meshms, "Found matching conversation, found_mine=%d, found_theirs=%d, read_offset=%"PRId64, c->my_ply.found, c->their_ply.found, c->metadata.read_offset); if (meshms_failed(status = update_conversation(id, c))) goto fail; if (status == MESHMS_STATUS_UPDATED) // ignore failures, we can retry later anyway. write_known_conversations(m, conv); if (meshms_failed(status = open_ply(&c->my_ply, &iter->_my_reader))) goto fail; if (meshms_failed(status = open_ply(&c->their_ply, &iter->_their_reader))) goto fail; iter->metadata = c->metadata; iter->my_ply = c->my_ply; iter->their_ply = c->their_ply; if (c->their_ply.found && c->metadata.their_last_message > c->metadata.my_last_ack){ iter->_in_ack = 1; iter->_their_reader.read.offset = c->metadata.their_last_message; iter->_end_range = c->metadata.my_last_ack; } break; } c = c->_next; } rhizome_manifest_free(m); meshms_free_conversations(conv); return MESHMS_STATUS_OK; error: status = MESHMS_STATUS_ERROR; fail: rhizome_manifest_free(m); meshms_message_iterator_close(iter); meshms_free_conversations(conv); return status; } void meshms_message_iterator_close(struct meshms_message_iterator *iter) { DEBUGF(meshms, "iter=%p", iter); message_ply_read_close(&iter->_my_reader); message_ply_read_close(&iter->_their_reader); } enum meshms_status meshms_message_iterator_prev(struct meshms_message_iterator *iter) { DEBUGF(meshms, "iter=%p, in_ack=%d, found_mine=%d, my_offset=%"PRIu64", their_offset=%"PRIu64, iter, iter->_in_ack, iter->my_ply.found, iter->_my_reader.read.offset, iter->_their_reader.read.offset); while (1) { if (iter->their_ply.found && iter->_in_ack) { DEBUGF(meshms, "Reading other log from %"PRIu64", to %"PRIu64, iter->_their_reader.read.offset, iter->_end_range); // just in case we don't have the full bundle in this rhizome store if (iter->_their_reader.read.offset > iter->_their_reader.read.length) iter->_their_reader.read.offset = iter->_their_reader.read.length; // eof or other read errors, skip over messages (the tail is allowed to advance) if (message_ply_read_prev(&iter->_their_reader)==0){ iter->which_ply = THEIR_PLY; if (iter->_their_reader.read.offset >= iter->_end_range) { switch (iter->_their_reader.type) { case MESSAGE_BLOCK_TYPE_ACK:{ iter->type = ACK_RECEIVED; iter->their_offset = iter->_their_reader.record_end_offset; iter->text = NULL; iter->text_length = 0; struct message_ply_ack ack; message_ply_parse_ack(&iter->_their_reader, &ack); iter->ack_offset = ack.end_offset; iter->read = 0; return MESHMS_STATUS_UPDATED; }case MESSAGE_BLOCK_TYPE_MESSAGE: iter->type = MESSAGE_RECEIVED; iter->their_offset = iter->_their_reader.record_end_offset; iter->text = (const char *)iter->_their_reader.record; iter->text_length = iter->_their_reader.record_length; if ( iter->_their_reader.record_length != 0 && iter->_their_reader.record[iter->_their_reader.record_length - 1] == '\0' ) { iter->read = iter->_their_reader.record_end_offset <= iter->metadata.read_offset; return MESHMS_STATUS_UPDATED; } WARN("Malformed MeshMS2 ply journal, missing NUL terminator"); return MESHMS_STATUS_PROTOCOL_FAULT; } continue; } } iter->_in_ack = 0; }else if(iter->my_ply.found){ if (message_ply_read_prev(&iter->_my_reader) != 0) return MESHMS_STATUS_OK; DEBUGF(meshms, "Offset %"PRId64", type %d, read_offset %"PRId64, iter->_my_reader.read.offset, iter->_my_reader.type, iter->metadata.read_offset); iter->which_ply = MY_PLY; switch (iter->_my_reader.type) { case MESSAGE_BLOCK_TYPE_TIME: if (message_ply_parse_timestamp(&iter->_my_reader, &iter->timestamp)!=0){ WARN("Malformed MeshMS2 ply journal, malformed timestamp"); return MESHMS_STATUS_PROTOCOL_FAULT; } DEBUGF(meshms, "Parsed timestamp %ds old", gettime() - iter->timestamp); break; case MESSAGE_BLOCK_TYPE_ACK: // Read the received messages up to the ack'ed offset if (iter->their_ply.found) { iter->my_offset = iter->_my_reader.record_end_offset; struct message_ply_ack ack; if (message_ply_parse_ack(&iter->_my_reader, &ack) == -1){ WHYF("Malformed ACK"); return MESHMS_STATUS_PROTOCOL_FAULT; } iter->_their_reader.read.offset = ack.end_offset; iter->_end_range = ack.start_offset; // TODO tail iter->_in_ack = 1; } break; case MESSAGE_BLOCK_TYPE_MESSAGE: iter->type = MESSAGE_SENT; iter->my_offset = iter->_my_reader.record_end_offset; iter->their_offset = 0; iter->text = (const char *)iter->_my_reader.record; iter->text_length = iter->_my_reader.record_length; iter->delivered = iter->_my_reader.record_end_offset <= iter->metadata.their_last_ack; return MESHMS_STATUS_UPDATED; } }else{ return MESHMS_STATUS_OK; } } } enum meshms_status meshms_send_message(const sid_t *sender, const sid_t *recipient, const char *message, size_t message_len) { assert(keyring != NULL); assert(message_len != 0); if (message_len > MESSAGE_PLY_MAX_LEN) { WHY("message too long"); return MESHMS_STATUS_ERROR; } struct meshms_conversations *conv = NULL; enum meshms_status status = MESHMS_STATUS_ERROR; rhizome_manifest *m=NULL; keyring_identity *id = keyring_find_identity_sid(keyring, sender); if (!id) return MESHMS_STATUS_SID_LOCKED; m = rhizome_new_manifest(); if (!m) goto end; if (meshms_failed(status = meshms_open_list(id, m, &conv))) goto end; struct meshms_conversations *c = conv; while(c && cmp_sid_t(recipient, &c->them)!=0) c = c->_next; if (!c){ c = (struct meshms_conversations *) emalloc_zero(sizeof(struct meshms_conversations)); if (!c) goto end; c->them = *recipient; c->_next = conv; conv = c; status = MESHMS_STATUS_UPDATED; } enum meshms_status tmp_status = update_stats(c); if (meshms_failed(tmp_status)) return tmp_status; if (tmp_status == MESHMS_STATUS_UPDATED) status = tmp_status; // construct a message payload struct overlay_buffer *b = ob_new(); // if we didn't "know" them, or we just received a new message, we may need to add an ack now. // lets do that in one hit uint8_t ack = (c->metadata.my_last_ack < c->metadata.their_last_message) ? 1:0; DEBUGF(meshms,"Our ack %"PRIu64", their message %"PRIu64, c->metadata.my_last_ack, c->metadata.their_last_message); if (ack){ struct message_ply_ack ack; bzero(&ack, sizeof ack); ack.end_offset = c->metadata.their_last_message; ack.start_offset = c->metadata.my_last_ack; message_ply_append_ack(b, &ack); } message_ply_append_message(b, message, message_len); message_ply_append_timestamp(b); assert(!ob_overrun(b)); if (message_ply_append(id, RHIZOME_SERVICE_MESHMS2, recipient, &c->my_ply, b, NULL, 0, NULL)==0){ if (ack) c->metadata.my_last_ack = c->metadata.their_last_message; c->metadata.my_size += ob_position(b); // save known conversations since our stats will always change. write_known_conversations(m, conv); status = MESHMS_STATUS_UPDATED; }else{ status = MESHMS_STATUS_ERROR; } ob_free(b); end: if (m) rhizome_manifest_free(m); meshms_free_conversations(conv); return status; } enum meshms_status meshms_mark_read(const sid_t *sender, const sid_t *recipient, uint64_t offset) { assert(keyring != NULL); rhizome_manifest *m=NULL; enum meshms_status status = MESHMS_STATUS_ERROR; struct meshms_conversations *conv = NULL; keyring_identity *id = keyring_find_identity_sid(keyring, sender); if (!id){ status = MESHMS_STATUS_SID_LOCKED; goto end; } DEBUGF(meshms, "sender=%s recipient=%s offset=%"PRIu64, alloca_tohex_sid_t(*sender), recipient ? alloca_tohex_sid_t(*recipient) : "(all)", offset ); m = rhizome_new_manifest(); if (!m) goto end; if (meshms_failed(status = meshms_open_list(id, m, &conv))) goto end; unsigned changed = 0; // check if any incoming conversations need to be acked or have new messages if (meshms_failed(status = update_conversations(id, &conv))) goto end; if (status == MESHMS_STATUS_UPDATED) changed ++; // update the read offset changed += mark_read(conv, recipient, offset); DEBUGF(meshms, "changed=%u", changed); if (changed) status = write_known_conversations(m, conv); end: if (m) rhizome_manifest_free(m); meshms_free_conversations(conv); return status; } // Returns the number of read markers moved. static unsigned mark_read(struct meshms_conversations *conv, const sid_t *their_sid, const uint64_t offset) { unsigned ret=0; while (conv){ if (!their_sid || cmp_sid_t(&conv->them, their_sid)==0){ // update read offset // - never past their last message // - never rewind, only advance uint64_t new_offset = offset; if (new_offset > conv->metadata.their_last_message) new_offset = conv->metadata.their_last_message; DEBUGF(meshms, "Read marker for %s, to %"PRIu64" (asked for %"PRIu64", was %"PRIu64")", alloca_tohex_sid_t(conv->them), new_offset, offset, conv->metadata.read_offset); if (new_offset > conv->metadata.read_offset) { conv->metadata.read_offset = new_offset; ret++; } if (their_sid) break; } conv = conv->_next; } return ret; } const char *meshms_status_message(enum meshms_status status) { switch (status) { case MESHMS_STATUS_OK: return "OK"; case MESHMS_STATUS_UPDATED: return "Updated"; case MESHMS_STATUS_SID_LOCKED: return "Identity unknown"; case MESHMS_STATUS_PROTOCOL_FAULT: return "MeshMS protocol fault"; case MESHMS_STATUS_ERROR: return "Internal error"; } return NULL; }