/* This file is part of GNUnet. Copyright (C) 2020--2021 GNUnet e.V. GNUnet is free software: you can redistribute it and/or modify it under the terms of the GNU Affero General Public License as published by the Free Software Foundation, either version 3 of the License, or (at your option) any later version. GNUnet 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 Affero General Public License for more details. You should have received a copy of the GNU Affero General Public License along with this program. If not, see . SPDX-License-Identifier: AGPL3.0-or-later */ /** * @author Tobias Frisch * @file src/messenger/gnunet-service-messenger_message_store.c * @brief GNUnet MESSENGER service */ #include "gnunet-service-messenger_message_store.h" #include "messenger_api_message.h" void init_message_store (struct GNUNET_MESSENGER_MessageStore *store) { GNUNET_assert(store); store->storage_messages = NULL; store->entries = GNUNET_CONTAINER_multihashmap_create (8, GNUNET_NO); store->messages = GNUNET_CONTAINER_multihashmap_create (8, GNUNET_NO); store->links = GNUNET_CONTAINER_multihashmap_create (8, GNUNET_NO); store->rewrite_entries = GNUNET_NO; store->write_links = GNUNET_NO; } static int iterate_destroy_entries (void *cls, const struct GNUNET_HashCode *key, void *value) { struct GNUNET_MESSENGER_MessageEntry *entry = value; GNUNET_free(entry); return GNUNET_YES; } static int iterate_destroy_messages (void *cls, const struct GNUNET_HashCode *key, void *value) { struct GNUNET_MESSENGER_Message *message = value; destroy_message (message); return GNUNET_YES; } static int iterate_destroy_links (void *cls, const struct GNUNET_HashCode *key, void *value) { struct GNUNET_HashCode *previous = value; GNUNET_free(previous); return GNUNET_YES; } void clear_message_store (struct GNUNET_MESSENGER_MessageStore *store) { GNUNET_assert(store); if (store->storage_messages) { GNUNET_DISK_file_close (store->storage_messages); store->storage_messages = NULL; } GNUNET_CONTAINER_multihashmap_iterate (store->entries, iterate_destroy_entries, NULL); GNUNET_CONTAINER_multihashmap_iterate (store->messages, iterate_destroy_messages, NULL); GNUNET_CONTAINER_multihashmap_iterate (store->links, iterate_destroy_links, NULL); GNUNET_CONTAINER_multihashmap_destroy (store->entries); GNUNET_CONTAINER_multihashmap_destroy (store->messages); GNUNET_CONTAINER_multihashmap_destroy (store->links); } struct GNUNET_MESSENGER_MessageEntryStorage { struct GNUNET_HashCode hash; struct GNUNET_MESSENGER_MessageEntry entry; }; static void load_message_store_entries (struct GNUNET_MESSENGER_MessageStore *store, const char *filename) { enum GNUNET_DISK_AccessPermissions permission = (GNUNET_DISK_PERM_USER_READ); struct GNUNET_DISK_FileHandle *entries = GNUNET_DISK_file_open (filename, GNUNET_DISK_OPEN_READ, permission); if (!entries) return; struct GNUNET_MESSENGER_MessageEntryStorage storage; struct GNUNET_MESSENGER_MessageEntry *entry; do { entry = GNUNET_new(struct GNUNET_MESSENGER_MessageEntry); if (GNUNET_DISK_file_read (entries, &storage, sizeof(storage)) == sizeof(storage)) { GNUNET_memcpy(entry, &(storage.entry), sizeof(*entry)); if ((GNUNET_YES == GNUNET_CONTAINER_multihashmap_contains (store->entries, &(storage.hash))) || (GNUNET_OK != GNUNET_CONTAINER_multihashmap_put (store->entries, &(storage.hash), entry, GNUNET_CONTAINER_MULTIHASHMAPOPTION_UNIQUE_FAST))) { store->rewrite_entries = GNUNET_YES; GNUNET_free(entry); } } else { GNUNET_free(entry); entry = NULL; } } while (entry); GNUNET_DISK_file_close (entries); } struct GNUNET_MESSENGER_MessageLinkStorage { struct GNUNET_HashCode hash; struct GNUNET_MESSENGER_MessageLink link; }; static void load_message_store_links (struct GNUNET_MESSENGER_MessageStore *store, const char *filename) { enum GNUNET_DISK_AccessPermissions permission = (GNUNET_DISK_PERM_USER_READ); struct GNUNET_DISK_FileHandle *entries = GNUNET_DISK_file_open (filename, GNUNET_DISK_OPEN_READ, permission); if (!entries) return; struct GNUNET_MESSENGER_MessageLinkStorage storage; struct GNUNET_MESSENGER_MessageLink *link = NULL; memset(&storage, 0, sizeof(storage)); do { if ((sizeof(storage.hash) != GNUNET_DISK_file_read (entries, &(storage.hash), sizeof(storage.hash))) || (sizeof(storage.link.multiple) != GNUNET_DISK_file_read (entries, &(storage.link.multiple), sizeof(storage.link.multiple))) || (sizeof(storage.link.first) != GNUNET_DISK_file_read (entries, &(storage.link.first), sizeof(storage.link.first))) || ((GNUNET_YES == storage.link.multiple) && (sizeof(storage.link.second) != GNUNET_DISK_file_read (entries, &(storage.link.second), sizeof(storage.link.second))))) break; link = GNUNET_new(struct GNUNET_MESSENGER_MessageLink); GNUNET_memcpy(link, &(storage.link), sizeof(*link)); if ((GNUNET_YES == GNUNET_CONTAINER_multihashmap_contains (store->links, &(storage.hash))) || (GNUNET_OK != GNUNET_CONTAINER_multihashmap_put (store->links, &(storage.hash), link, GNUNET_CONTAINER_MULTIHASHMAPOPTION_UNIQUE_FAST))) break; } while (link); if (link) GNUNET_free(link); GNUNET_DISK_file_close (entries); } void load_message_store (struct GNUNET_MESSENGER_MessageStore *store, const char *directory) { GNUNET_assert((store) && (directory)); enum GNUNET_DISK_AccessPermissions permission = (GNUNET_DISK_PERM_USER_READ | GNUNET_DISK_PERM_USER_WRITE); if (store->storage_messages) GNUNET_DISK_file_close (store->storage_messages); char *filename; GNUNET_asprintf (&filename, "%s%s", directory, "messages.store"); if (GNUNET_YES == GNUNET_DISK_file_test (filename)) store->storage_messages = GNUNET_DISK_file_open (filename, GNUNET_DISK_OPEN_READWRITE, permission); else store->storage_messages = NULL; GNUNET_free(filename); if (!store->storage_messages) return; GNUNET_asprintf (&filename, "%s%s", directory, "entries.store"); if (GNUNET_YES == GNUNET_DISK_file_test (filename)) load_message_store_entries(store, filename); GNUNET_free(filename); GNUNET_asprintf (&filename, "%s%s", directory, "links.store"); if (GNUNET_YES == GNUNET_DISK_file_test (filename)) load_message_store_links(store, filename); GNUNET_free(filename); } struct GNUNET_MESSENGER_ClosureMessageSave { struct GNUNET_MESSENGER_MessageStore *store; struct GNUNET_DISK_FileHandle *storage; }; static int iterate_save_entries (void *cls, const struct GNUNET_HashCode *key, void *value) { struct GNUNET_MESSENGER_ClosureMessageSave *save = cls; struct GNUNET_MESSENGER_MessageEntry *entry = value; struct GNUNET_MESSENGER_MessageEntryStorage storage; GNUNET_memcpy(&(storage.hash), key, sizeof(storage.hash)); GNUNET_memcpy(&(storage.entry), entry, sizeof(*entry)); GNUNET_DISK_file_write (save->storage, &storage, sizeof(storage)); return GNUNET_YES; } static int iterate_save_messages (void *cls, const struct GNUNET_HashCode *key, void *value) { struct GNUNET_MESSENGER_ClosureMessageSave *save = cls; if (GNUNET_NO != GNUNET_CONTAINER_multihashmap_contains (save->store->entries, key)) return GNUNET_YES; struct GNUNET_MESSENGER_Message *message = value; struct GNUNET_MESSENGER_MessageEntryStorage storage; GNUNET_memcpy(&(storage.hash), key, sizeof(storage.hash)); storage.entry.length = get_message_size (message, GNUNET_YES); storage.entry.offset = GNUNET_DISK_file_seek (save->store->storage_messages, 0, GNUNET_DISK_SEEK_END); if ((GNUNET_SYSERR == storage.entry.offset) || (sizeof(storage) != GNUNET_DISK_file_write (save->storage, &storage, sizeof(storage)))) return GNUNET_YES; char *buffer = GNUNET_malloc(storage.entry.length); encode_message (message, storage.entry.length, buffer, GNUNET_YES); GNUNET_DISK_file_write (save->store->storage_messages, buffer, storage.entry.length); GNUNET_free(buffer); return GNUNET_YES; } static int iterate_save_links (void *cls, const struct GNUNET_HashCode *key, void *value) { struct GNUNET_MESSENGER_ClosureMessageSave *save = cls; struct GNUNET_MESSENGER_MessageLink *link = value; GNUNET_DISK_file_write (save->storage, key, sizeof(*key)); GNUNET_DISK_file_write (save->storage, &(link->multiple), sizeof(link->multiple)); GNUNET_DISK_file_write (save->storage, &(link->first), sizeof(link->first)); if (GNUNET_YES == link->multiple) GNUNET_DISK_file_write (save->storage, &(link->second), sizeof(link->second)); return GNUNET_YES; } void save_message_store (struct GNUNET_MESSENGER_MessageStore *store, const char *directory) { GNUNET_assert((store) && (directory)); struct GNUNET_MESSENGER_ClosureMessageSave save; enum GNUNET_DISK_AccessPermissions permission = (GNUNET_DISK_PERM_USER_READ | GNUNET_DISK_PERM_USER_WRITE); char *filename; if (GNUNET_YES != store->write_links) goto save_entries; GNUNET_asprintf (&filename, "%s%s", directory, "links.store"); save.store = store; save.storage = GNUNET_DISK_file_open (filename, GNUNET_DISK_OPEN_WRITE | GNUNET_DISK_OPEN_CREATE, permission); GNUNET_free(filename); if (!save.storage) goto save_entries; if (GNUNET_SYSERR == GNUNET_DISK_file_seek (save.storage, 0, GNUNET_DISK_SEEK_SET)) goto close_links; GNUNET_CONTAINER_multihashmap_iterate (store->links, iterate_save_links, &save); store->write_links = GNUNET_NO; close_links: GNUNET_DISK_file_close (save.storage); save_entries: GNUNET_asprintf (&filename, "%s%s", directory, "entries.store"); save.store = store; save.storage = GNUNET_DISK_file_open (filename, GNUNET_DISK_OPEN_WRITE | GNUNET_DISK_OPEN_CREATE, permission); GNUNET_free(filename); if (!save.storage) return; if (GNUNET_YES == store->rewrite_entries) { if (GNUNET_SYSERR == GNUNET_DISK_file_seek (save.storage, 0, GNUNET_DISK_SEEK_SET)) goto close_entries; GNUNET_CONTAINER_multihashmap_iterate (store->entries, iterate_save_entries, &save); store->rewrite_entries = GNUNET_NO; } else if (GNUNET_SYSERR == GNUNET_DISK_file_seek (save.storage, 0, GNUNET_DISK_SEEK_END)) goto close_entries; if (store->storage_messages) GNUNET_DISK_file_close (store->storage_messages); GNUNET_asprintf (&filename, "%s%s", directory, "messages.store"); store->storage_messages = GNUNET_DISK_file_open (filename, GNUNET_DISK_OPEN_READWRITE | GNUNET_DISK_OPEN_CREATE, permission); GNUNET_free(filename); if (store->storage_messages) { GNUNET_CONTAINER_multihashmap_iterate (store->messages, iterate_save_messages, &save); GNUNET_DISK_file_sync (store->storage_messages); GNUNET_DISK_file_sync (save.storage); } close_entries: GNUNET_DISK_file_close (save.storage); } int contains_store_message (const struct GNUNET_MESSENGER_MessageStore *store, const struct GNUNET_HashCode *hash) { GNUNET_assert((store) && (hash)); if (GNUNET_YES == GNUNET_CONTAINER_multihashmap_contains (store->messages, hash)) return GNUNET_YES; return GNUNET_CONTAINER_multihashmap_contains (store->entries, hash); } const struct GNUNET_MESSENGER_Message* get_store_message (struct GNUNET_MESSENGER_MessageStore *store, const struct GNUNET_HashCode *hash) { GNUNET_assert((store) && (hash)); struct GNUNET_MESSENGER_Message *message = GNUNET_CONTAINER_multihashmap_get (store->messages, hash); if (message) return message; if (!store->storage_messages) return NULL; const struct GNUNET_MESSENGER_MessageEntry *entry = GNUNET_CONTAINER_multihashmap_get (store->entries, hash); if (!entry) return NULL; if (entry->offset != GNUNET_DISK_file_seek (store->storage_messages, entry->offset, GNUNET_DISK_SEEK_SET)) return message; char *buffer = GNUNET_malloc(entry->length); if (!buffer) return NULL; if ((GNUNET_DISK_file_read (store->storage_messages, buffer, entry->length) != entry->length) || (entry->length < get_message_kind_size(GNUNET_MESSENGER_KIND_UNKNOWN))) goto free_buffer; message = create_message (GNUNET_MESSENGER_KIND_UNKNOWN); const int decoding = decode_message (message, entry->length, buffer, GNUNET_YES, NULL); struct GNUNET_HashCode check; hash_message (message, entry->length, buffer, &check); if ((GNUNET_YES != decoding) || (GNUNET_CRYPTO_hash_cmp (hash, &check) != 0)) { if (GNUNET_YES != GNUNET_CONTAINER_multihashmap_remove (store->entries, hash, entry)) GNUNET_log(GNUNET_ERROR_TYPE_WARNING, "Corrupted entry could not be removed from store: %s\n", GNUNET_h2s(hash)); store->rewrite_entries = GNUNET_YES; goto free_message; } if (GNUNET_OK == GNUNET_CONTAINER_multihashmap_put (store->messages, hash, message, GNUNET_CONTAINER_MULTIHASHMAPOPTION_UNIQUE_FAST)) goto free_buffer; free_message: destroy_message (message); message = NULL; free_buffer: GNUNET_free(buffer); return message; } const struct GNUNET_MESSENGER_MessageLink* get_store_message_link (struct GNUNET_MESSENGER_MessageStore *store, const struct GNUNET_HashCode *hash, int deleted_only) { if (deleted_only) goto get_link; const struct GNUNET_MESSENGER_Message *message = get_store_message(store, hash); if (!message) goto get_link; static struct GNUNET_MESSENGER_MessageLink link; GNUNET_memcpy(&(link.first), &(message->header.previous), sizeof(link.first)); link.multiple = GNUNET_MESSENGER_KIND_MERGE == message->header.kind? GNUNET_YES : GNUNET_NO; if (GNUNET_YES == link.multiple) GNUNET_memcpy(&(link.second), &(message->body.merge.previous), sizeof(link.second)); else GNUNET_memcpy(&(link.second), &(message->header.previous), sizeof(link.second)); return &link; get_link: return GNUNET_CONTAINER_multihashmap_get (store->links, hash); } int put_store_message (struct GNUNET_MESSENGER_MessageStore *store, const struct GNUNET_HashCode *hash, struct GNUNET_MESSENGER_Message *message) { GNUNET_assert((store) && (hash) && (message)); return GNUNET_CONTAINER_multihashmap_put (store->messages, hash, message, GNUNET_CONTAINER_MULTIHASHMAPOPTION_UNIQUE_FAST); } static void add_link (struct GNUNET_MESSENGER_MessageStore *store, const struct GNUNET_HashCode *hash, const struct GNUNET_MESSENGER_Message *message) { struct GNUNET_MESSENGER_MessageLink *link = GNUNET_new(struct GNUNET_MESSENGER_MessageLink); GNUNET_memcpy(&(link->first), &(message->header.previous), sizeof(link->first)); link->multiple = GNUNET_MESSENGER_KIND_MERGE == message->header.kind? GNUNET_YES : GNUNET_NO; if (GNUNET_YES == link->multiple) GNUNET_memcpy(&(link->second), &(message->body.merge.previous), sizeof(link->second)); else GNUNET_memcpy(&(link->second), &(message->header.previous), sizeof(link->second)); if (GNUNET_OK != GNUNET_CONTAINER_multihashmap_put(store->links, hash, link, GNUNET_CONTAINER_MULTIHASHMAPOPTION_UNIQUE_FAST)) GNUNET_free(link); } int delete_store_message (struct GNUNET_MESSENGER_MessageStore *store, const struct GNUNET_HashCode *hash) { GNUNET_assert((store) && (hash)); const struct GNUNET_MESSENGER_MessageEntry *entry = GNUNET_CONTAINER_multihashmap_get (store->entries, hash); if (!entry) goto clear_memory; const struct GNUNET_MESSENGER_Message *message = get_store_message(store, hash); if (message) add_link (store, hash, message); if (!store->storage_messages) goto clear_entry; if (entry->offset != GNUNET_DISK_file_seek (store->storage_messages, entry->offset, GNUNET_DISK_SEEK_SET)) return GNUNET_SYSERR; char *clear_buffer = GNUNET_malloc(entry->length); if (!clear_buffer) return GNUNET_SYSERR; GNUNET_CRYPTO_zero_keys (clear_buffer, entry->length); if ((entry->length != GNUNET_DISK_file_write (store->storage_messages, clear_buffer, entry->length)) || (GNUNET_OK != GNUNET_DISK_file_sync (store->storage_messages))) { GNUNET_free(clear_buffer); return GNUNET_SYSERR; } GNUNET_free(clear_buffer); clear_entry: if (GNUNET_YES == GNUNET_CONTAINER_multihashmap_remove (store->entries, hash, entry)) store->rewrite_entries = GNUNET_YES; clear_memory: GNUNET_CONTAINER_multihashmap_remove_all (store->messages, hash); return GNUNET_OK; }