/* This file is part of GNUnet Copyright (C) 2012, 2015, 2022 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 */ /** * @file datacache/plugin_datacache_heap.c * @brief heap-only implementation of a database backend for the datacache * @author Christian Grothoff */ #include "platform.h" #include "gnunet_util_lib.h" #include "gnunet_datacache_plugin.h" #define LOG(kind, ...) GNUNET_log_from (kind, "datacache-heap", __VA_ARGS__) #define LOG_STRERROR_FILE(kind, op, fn) GNUNET_log_from_strerror_file (kind, \ "datacache-heap", \ op, fn) #define NUM_HEAPS 24 /** * Context for all functions in this plugin. */ struct Plugin { /** * Our execution environment. */ struct GNUNET_DATACACHE_PluginEnvironment *env; /** * Our hash map. */ struct GNUNET_CONTAINER_MultiHashMap *map; /** * Heaps sorted by distance. */ struct GNUNET_CONTAINER_Heap *heaps[NUM_HEAPS]; }; /** * Entry in the hash map. */ struct Value { /** * Block data. */ struct GNUNET_DATACACHE_Block block; /** * Corresponding node in the heap. */ struct GNUNET_CONTAINER_HeapNode *hn; /** * Put path as a non-const pointer. */ struct GNUNET_DHT_PathElement *put_path; /** * How close is the hash to us? Determines which heap we are in! */ uint32_t distance; }; #define OVERHEAD (sizeof(struct Value) + 64) /** * Closure for #put_cb(). */ struct PutContext { /** * Block data. */ const struct GNUNET_DATACACHE_Block *block; /** * Value to set to true if an equivalent block was found. */ bool found; }; /** * Function called during PUT to detect if an equivalent block * already exists. * * @param cls the `struct PutContext` * @param key the key for the value(s) * @param value an existing value * @return #GNUNET_YES if not found (to continue to iterate) */ static enum GNUNET_GenericReturnValue put_cb (void *cls, const struct GNUNET_HashCode *key, void *value) { struct PutContext *put_ctx = cls; struct Value *val = value; if ((val->block.data_size == put_ctx->block->data_size) && (val->block.type == put_ctx->block->type) && (0 == memcmp (val->block.data, put_ctx->block->data, put_ctx->block->data_size))) { put_ctx->found = true; val->block.expiration_time = GNUNET_TIME_absolute_max (val->block.expiration_time, put_ctx->block->expiration_time); /* replace old path with new path */ GNUNET_free (val->put_path); val->put_path = GNUNET_memdup (put_ctx->block->put_path, put_ctx->block->put_path_length * sizeof (struct GNUNET_DHT_PathElement)); val->block.put_path = val->put_path; val->block.put_path_length = put_ctx->block->put_path_length; GNUNET_CONTAINER_heap_update_cost (val->hn, val->block.expiration_time.abs_value_us); GNUNET_log (GNUNET_ERROR_TYPE_DEBUG, "Got same value for key %s and type %u (size %u vs %u)\n", GNUNET_h2s (key), (unsigned int) val->block.type, (unsigned int) val->block.data_size, (unsigned int) put_ctx->block->data_size); return GNUNET_NO; } return GNUNET_YES; } /** * Store an item in the datastore. * * @param cls closure (our `struct Plugin`) * @param xor_distance how close is @a key to our PID? * @param block data to store * @return 0 if duplicate, -1 on error, number of bytes used otherwise */ static ssize_t heap_plugin_put (void *cls, uint32_t xor_distance, const struct GNUNET_DATACACHE_Block *block) { struct Plugin *plugin = cls; struct Value *val; struct PutContext put_ctx = { .block = block, .found = false }; GNUNET_log (GNUNET_ERROR_TYPE_DEBUG, "Storing %u bytes under key %s with path length %u\n", (unsigned int) block->data_size, GNUNET_h2s (&block->key), block->put_path_length); GNUNET_CONTAINER_multihashmap_get_multiple (plugin->map, &block->key, &put_cb, &put_ctx); if (GNUNET_YES == put_ctx.found) return 0; val = GNUNET_malloc (sizeof(struct Value) + block->data_size); GNUNET_memcpy (&val[1], block->data, block->data_size); val->block = *block; val->block.data = &val[1]; if (xor_distance >= NUM_HEAPS) val->distance = NUM_HEAPS - 1; else val->distance = xor_distance; if (0 != block->put_path_length) { val->put_path = GNUNET_memdup (block->put_path, block->put_path_length * sizeof (struct GNUNET_DHT_PathElement)); val->block.put_path = val->put_path; } (void) GNUNET_CONTAINER_multihashmap_put (plugin->map, &val->block.key, val, GNUNET_CONTAINER_MULTIHASHMAPOPTION_MULTIPLE); val->hn = GNUNET_CONTAINER_heap_insert ( plugin->heaps[val->distance], val, val->block.expiration_time.abs_value_us); return val->block.data_size + OVERHEAD; } /** * Closure for #get_cb(). */ struct GetContext { /** * Function to call for each result. */ GNUNET_DATACACHE_Iterator iter; /** * Closure for @e iter. */ void *iter_cls; /** * Number of results found. */ unsigned int cnt; /** * Block type requested. */ enum GNUNET_BLOCK_Type type; }; /** * Function called during GET to find matching blocks. * Only matches by type. * * @param cls the `struct GetContext` * @param key the key for the value(s) * @param value an existing value * @return #GNUNET_YES to continue to iterate */ static enum GNUNET_GenericReturnValue get_cb (void *cls, const struct GNUNET_HashCode *key, void *value) { struct GetContext *get_ctx = cls; struct Value *val = value; enum GNUNET_GenericReturnValue ret; if ( (get_ctx->type != val->block.type) && (GNUNET_BLOCK_TYPE_ANY != get_ctx->type) ) { GNUNET_log (GNUNET_ERROR_TYPE_DEBUG, "Result for key %s does not match block type %d\n", GNUNET_h2s (key), get_ctx->type); return GNUNET_OK; } if (GNUNET_TIME_absolute_is_past (val->block.expiration_time)) { GNUNET_log (GNUNET_ERROR_TYPE_DEBUG, "Result for key %s is expired\n", GNUNET_h2s (key)); return GNUNET_OK; } GNUNET_log (GNUNET_ERROR_TYPE_DEBUG, "Found result for key %s\n", GNUNET_h2s (key)); if (NULL != get_ctx->iter) ret = get_ctx->iter (get_ctx->iter_cls, &val->block); else ret = GNUNET_YES; get_ctx->cnt++; return ret; } /** * Iterate over the results for a particular key * in the datastore. * * @param cls closure (our `struct Plugin`) * @param key * @param type entries of which type are relevant? * @param iter maybe NULL (to just count) * @param iter_cls closure for @a iter * @return the number of results found */ static unsigned int heap_plugin_get (void *cls, const struct GNUNET_HashCode *key, enum GNUNET_BLOCK_Type type, GNUNET_DATACACHE_Iterator iter, void *iter_cls) { struct Plugin *plugin = cls; struct GetContext get_ctx; get_ctx.type = type; get_ctx.iter = iter; get_ctx.iter_cls = iter_cls; get_ctx.cnt = 0; GNUNET_CONTAINER_multihashmap_get_multiple (plugin->map, key, &get_cb, &get_ctx); return get_ctx.cnt; } /** * Delete the entry with the lowest expiration value * from the datacache right now. * * @param cls closure (our `struct Plugin`) * @return #GNUNET_OK on success, #GNUNET_SYSERR on error */ static enum GNUNET_GenericReturnValue heap_plugin_del (void *cls) { struct Plugin *plugin = cls; struct Value *val; for (unsigned int i = 0; i < NUM_HEAPS; i++) { val = GNUNET_CONTAINER_heap_remove_root (plugin->heaps[i]); if (NULL != val) break; } if (NULL == val) return GNUNET_SYSERR; GNUNET_assert (GNUNET_YES == GNUNET_CONTAINER_multihashmap_remove (plugin->map, &val->block.key, val)); plugin->env->delete_notify (plugin->env->cls, &val->block.key, val->block.data_size + OVERHEAD); GNUNET_free (val->put_path); GNUNET_free (val); return GNUNET_OK; } /** * Closure for #find_closest(). */ struct GetClosestContext { struct Value **values; const struct GNUNET_HashCode *key; enum GNUNET_BLOCK_Type type; unsigned int num_results; }; static enum GNUNET_GenericReturnValue find_closest (void *cls, const struct GNUNET_HashCode *key, void *value) { struct GetClosestContext *gcc = cls; struct Value *val = value; unsigned int j; if (1 != GNUNET_CRYPTO_hash_cmp (key, gcc->key)) return GNUNET_OK; /* useless */ if ( (val->block.type != gcc->type) && (GNUNET_BLOCK_TYPE_ANY != gcc->type) ) return GNUNET_OK; /* useless */ j = gcc->num_results; for (unsigned int i = 0; i < gcc->num_results; i++) { if (NULL == gcc->values[i]) { j = i; break; } if (1 == GNUNET_CRYPTO_hash_cmp (&gcc->values[i]->block.key, key)) { j = i; break; } } if (j == gcc->num_results) return GNUNET_OK; gcc->values[j] = val; return GNUNET_OK; } /** * Iterate over the results that are "close" to a particular key in * the datacache. "close" is defined as numerically larger than @a * key (when interpreted as a circular address space), with small * distance. * * @param cls closure (internal context for the plugin) * @param key area of the keyspace to look into * @param type desired block type for the replies * @param num_results number of results that should be returned to @a iter * @param iter maybe NULL (to just count) * @param iter_cls closure for @a iter * @return the number of results found */ static unsigned int heap_plugin_get_closest (void *cls, const struct GNUNET_HashCode *key, enum GNUNET_BLOCK_Type type, unsigned int num_results, GNUNET_DATACACHE_Iterator iter, void *iter_cls) { struct Plugin *plugin = cls; struct Value *values[num_results]; struct GetClosestContext gcc = { .values = values, .type = type, .num_results = num_results * 2, .key = key }; GNUNET_CONTAINER_multihashmap_iterate (plugin->map, &find_closest, &gcc); for (unsigned int i = 0; i < num_results * 2; i++) { if (NULL == values[i]) return i; if ( (NULL != iter) && (GNUNET_SYSERR == iter (iter_cls, &values[i]->block)) ) { LOG (GNUNET_ERROR_TYPE_DEBUG, "Ending iteration (client error)\n"); return i; } } return num_results * 2; } /** * Entry point for the plugin. * * @param cls closure (the `struct GNUNET_DATACACHE_PluginEnvironmnet`) * @return the plugin's closure (our `struct Plugin`) */ void * libgnunet_plugin_datacache_heap_init (void *cls) { struct GNUNET_DATACACHE_PluginEnvironment *env = cls; struct GNUNET_DATACACHE_PluginFunctions *api; struct Plugin *plugin; plugin = GNUNET_new (struct Plugin); plugin->map = GNUNET_CONTAINER_multihashmap_create (1024, /* FIXME: base on quota! */ GNUNET_YES); for (unsigned int i = 0; i < NUM_HEAPS; i++) plugin->heaps[i] = GNUNET_CONTAINER_heap_create ( GNUNET_CONTAINER_HEAP_ORDER_MIN); plugin->env = env; api = GNUNET_new (struct GNUNET_DATACACHE_PluginFunctions); api->cls = plugin; api->get = &heap_plugin_get; api->put = &heap_plugin_put; api->del = &heap_plugin_del; api->get_closest = &heap_plugin_get_closest; LOG (GNUNET_ERROR_TYPE_INFO, _ ("Heap datacache running\n")); return api; } /** * Exit point from the plugin. * * @param cls closure (our "struct Plugin") * @return NULL */ void * libgnunet_plugin_datacache_heap_done (void *cls) { struct GNUNET_DATACACHE_PluginFunctions *api = cls; struct Plugin *plugin = api->cls; struct Value *val; for (unsigned int i = 0; i < NUM_HEAPS; i++) { while (NULL != (val = GNUNET_CONTAINER_heap_remove_root (plugin->heaps[i]))) { GNUNET_assert (GNUNET_YES == GNUNET_CONTAINER_multihashmap_remove (plugin->map, &val->block.key, val)); GNUNET_free (val->put_path); GNUNET_free (val); } GNUNET_CONTAINER_heap_destroy (plugin->heaps[i]); } GNUNET_CONTAINER_multihashmap_destroy (plugin->map); GNUNET_free (plugin); GNUNET_free (api); return NULL; } /* end of plugin_datacache_heap.c */