2 This file is part of GNUnet.
3 Copyright (C) 2014, 2015, 2016 GNUnet e.V.
5 GNUnet is free software: you can redistribute it and/or modify it
6 under the terms of the GNU Affero General Public License as published
7 by the Free Software Foundation, either version 3 of the License,
8 or (at your option) any later version.
10 GNUnet is distributed in the hope that it will be useful, but
11 WITHOUT ANY WARRANTY; without even the implied warranty of
12 MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
13 Affero General Public License for more details.
15 You should have received a copy of the GNU Affero General Public License
16 along with this program. If not, see <http://www.gnu.org/licenses/>.
18 SPDX-License-Identifier: AGPL3.0-or-later
22 * @file peerstore/gnunet-service-peerstore.c
23 * @brief peerstore service implementation
24 * @author Omar Tarabai
27 #include "gnunet_util_lib.h"
28 #include "peerstore.h"
29 #include "gnunet_peerstore_plugin.h"
30 #include "peerstore_common.h"
34 * Interval for expired records cleanup (in seconds)
36 #define EXPIRED_RECORDS_CLEANUP_INTERVAL 300 /* 5mins */
41 static const struct GNUNET_CONFIGURATION_Handle *cfg;
44 * Database plugin library name
46 static char *db_lib_name;
51 static struct GNUNET_PEERSTORE_PluginFunctions *db;
54 * Hashmap with all watch requests
56 static struct GNUNET_CONTAINER_MultiHashMap *watchers;
59 * Task run to clean up expired records.
61 static struct GNUNET_SCHEDULER_Task *expire_task;
64 * Are we in the process of shutting down the service? #GNUNET_YES / #GNUNET_NO
66 static int in_shutdown;
69 * Number of connected clients.
71 static unsigned int num_clients;
75 * Perform the actual shutdown operations
80 if (NULL != db_lib_name)
82 GNUNET_break(NULL == GNUNET_PLUGIN_unload(db_lib_name, db));
83 GNUNET_free(db_lib_name);
88 GNUNET_CONTAINER_multihashmap_destroy(watchers);
91 if (NULL != expire_task)
93 GNUNET_SCHEDULER_cancel(expire_task);
96 GNUNET_SCHEDULER_shutdown();
101 * Task run during shutdown.
106 shutdown_task(void *cls)
108 in_shutdown = GNUNET_YES;
109 if (0 == num_clients) /* Only when no connected clients. */
114 /* Forward declaration */
116 expire_records_continuation(void *cls, int success);
120 * Deletes any expired records from storage
123 cleanup_expired_records(void *cls)
128 GNUNET_assert(NULL != db);
129 ret = db->expire_records(db->cls,
130 GNUNET_TIME_absolute_get(),
131 &expire_records_continuation,
133 if (GNUNET_OK != ret)
135 GNUNET_assert(NULL == expire_task);
136 expire_task = GNUNET_SCHEDULER_add_delayed(
137 GNUNET_TIME_relative_multiply(GNUNET_TIME_UNIT_SECONDS,
138 EXPIRED_RECORDS_CLEANUP_INTERVAL),
139 &cleanup_expired_records,
146 * Continuation to expire_records called by the peerstore plugin
149 * @param success count of records deleted or #GNUNET_SYSERR
152 expire_records_continuation(void *cls, int success)
155 GNUNET_log(GNUNET_ERROR_TYPE_INFO, "%d records expired.\n", success);
156 GNUNET_assert(NULL == expire_task);
157 expire_task = GNUNET_SCHEDULER_add_delayed(
158 GNUNET_TIME_relative_multiply(GNUNET_TIME_UNIT_SECONDS,
159 EXPIRED_RECORDS_CLEANUP_INTERVAL),
160 &cleanup_expired_records,
166 * A client disconnected. Remove all of its data structure entries.
168 * @param cls closure, NULL
169 * @param client identification of the client
170 * @param mq the message queue
174 client_connect_cb(void *cls,
175 struct GNUNET_SERVICE_Client *client,
176 struct GNUNET_MQ_Handle *mq)
184 * Search for a disconnected client and remove it
186 * @param cls closuer, a `struct GNUNET_SERVICE_Client`
187 * @param key hash of record key
188 * @param value the watcher client, a `struct GNUNET_SERVICE_Client *`
189 * @return #GNUNET_OK to continue iterating
192 client_disconnect_it(void *cls, const struct GNUNET_HashCode *key, void *value)
196 GNUNET_assert(GNUNET_YES ==
197 GNUNET_CONTAINER_multihashmap_remove(watchers, key, value));
205 * A client disconnected. Remove all of its data structure entries.
207 * @param cls closure, NULL
208 * @param client identification of the client
211 client_disconnect_cb(void *cls,
212 struct GNUNET_SERVICE_Client *client,
215 GNUNET_log(GNUNET_ERROR_TYPE_DEBUG, "A client disconnected, cleaning up.\n");
216 if (NULL != watchers)
217 GNUNET_CONTAINER_multihashmap_iterate(watchers,
218 &client_disconnect_it,
221 if ((0 == num_clients) && in_shutdown)
227 * Function called by for each matching record.
230 * @param record peerstore record found
231 * @param emsg error message or NULL if no errors
232 * @return #GNUNET_YES to continue iteration
235 record_iterator(void *cls,
236 const struct GNUNET_PEERSTORE_Record *record,
239 struct GNUNET_PEERSTORE_Record *cls_record = cls;
240 struct GNUNET_MQ_Envelope *env;
244 /* No more records */
245 struct GNUNET_MessageHeader *endmsg;
247 env = GNUNET_MQ_msg(endmsg, GNUNET_MESSAGE_TYPE_PEERSTORE_ITERATE_END);
248 GNUNET_MQ_send(GNUNET_SERVICE_client_get_mq(cls_record->client), env);
251 GNUNET_SERVICE_client_continue(cls_record->client);
256 GNUNET_log(GNUNET_ERROR_TYPE_ERROR, "Failed to iterate: %s\n", emsg);
257 GNUNET_SERVICE_client_drop(cls_record->client);
259 PEERSTORE_destroy_record(cls_record);
263 env = PEERSTORE_create_record_mq_envelope(
271 GNUNET_MESSAGE_TYPE_PEERSTORE_ITERATE_RECORD);
272 GNUNET_MQ_send(GNUNET_SERVICE_client_get_mq(cls_record->client), env);
277 * Iterator over all watcher clients
278 * to notify them of a new record
280 * @param cls closure, a `struct GNUNET_PEERSTORE_Record *`
281 * @param key hash of record key
282 * @param value the watcher client, a `struct GNUNET_SERVICE_Client *`
283 * @return #GNUNET_YES to continue iterating
286 watch_notifier_it(void *cls, const struct GNUNET_HashCode *key, void *value)
288 struct GNUNET_PEERSTORE_Record *record = cls;
289 struct GNUNET_SERVICE_Client *client = value;
290 struct GNUNET_MQ_Envelope *env;
292 GNUNET_log(GNUNET_ERROR_TYPE_DEBUG, "Found a watcher to update.\n");
293 env = PEERSTORE_create_record_mq_envelope(
301 GNUNET_MESSAGE_TYPE_PEERSTORE_WATCH_RECORD);
302 GNUNET_MQ_send(GNUNET_SERVICE_client_get_mq(client), env);
308 * Given a new record, notifies watchers
310 * @param record changed record to update watchers with
313 watch_notifier(struct GNUNET_PEERSTORE_Record *record)
315 struct GNUNET_HashCode keyhash;
317 PEERSTORE_hash_key(record->sub_system, &record->peer, record->key, &keyhash);
318 GNUNET_CONTAINER_multihashmap_get_multiple(watchers,
326 * Handle a watch cancel request from client
328 * @param cls identification of the client
329 * @param hm the actual message
332 handle_watch_cancel(void *cls, const struct StoreKeyHashMessage *hm)
334 struct GNUNET_SERVICE_Client *client = cls;
336 GNUNET_log(GNUNET_ERROR_TYPE_DEBUG, "Received a watch cancel request.\n");
338 GNUNET_CONTAINER_multihashmap_remove(watchers, &hm->keyhash, client))
341 GNUNET_SERVICE_client_drop(client);
345 GNUNET_SERVICE_client_continue(client);
350 * Handle a watch request from client
352 * @param cls identification of the client
353 * @param hm the actual message
356 handle_watch(void *cls, const struct StoreKeyHashMessage *hm)
358 struct GNUNET_SERVICE_Client *client = cls;
360 GNUNET_log(GNUNET_ERROR_TYPE_DEBUG, "Received a watch request.\n");
361 num_clients--; /* do not count watchers */
362 GNUNET_SERVICE_client_mark_monitor(client);
363 GNUNET_CONTAINER_multihashmap_put(watchers,
366 GNUNET_CONTAINER_MULTIHASHMAPOPTION_MULTIPLE);
367 GNUNET_SERVICE_client_continue(client);
372 * Check an iterate request from client
374 * @param cls client identification of the client
375 * @param srm the actual message
376 * @return #GNUNET_OK if @a srm is well-formed
379 check_iterate(void *cls, const struct StoreRecordMessage *srm)
381 struct GNUNET_PEERSTORE_Record *record;
383 record = PEERSTORE_parse_record_message(srm);
387 return GNUNET_SYSERR;
389 if (NULL == record->sub_system)
392 PEERSTORE_destroy_record(record);
393 return GNUNET_SYSERR;
395 PEERSTORE_destroy_record(record);
401 * Handle an iterate request from client
403 * @param cls identification of the client
404 * @param srm the actual message
407 handle_iterate(void *cls, const struct StoreRecordMessage *srm)
409 struct GNUNET_SERVICE_Client *client = cls;
410 struct GNUNET_PEERSTORE_Record *record;
412 record = PEERSTORE_parse_record_message(srm);
413 GNUNET_log(GNUNET_ERROR_TYPE_DEBUG,
414 "Iterate request: ss `%s', peer `%s', key `%s'\n",
416 GNUNET_i2s(&record->peer),
417 (NULL == record->key) ? "NULL" : record->key);
418 record->client = client;
420 db->iterate_records(db->cls,
422 (ntohs(srm->peer_set)) ? &record->peer : NULL,
428 GNUNET_SERVICE_client_drop(client);
429 PEERSTORE_destroy_record(record);
435 * Continuation of store_record called by the peerstore plugin
438 * @param success result
441 store_record_continuation(void *cls, int success)
443 struct GNUNET_PEERSTORE_Record *record = cls;
445 if (GNUNET_OK == success)
447 watch_notifier(record);
448 GNUNET_SERVICE_client_continue(record->client);
453 GNUNET_SERVICE_client_drop(record->client);
455 PEERSTORE_destroy_record(record);
460 * Check a store request from client
462 * @param cls client identification of the client
463 * @param srm the actual message
464 * @return #GNUNET_OK if @a srm is well-formed
467 check_store(void *cls, const struct StoreRecordMessage *srm)
469 struct GNUNET_PEERSTORE_Record *record;
471 record = PEERSTORE_parse_record_message(srm);
475 return GNUNET_SYSERR;
477 if ((NULL == record->sub_system) || (NULL == record->key))
480 PEERSTORE_destroy_record(record);
481 return GNUNET_SYSERR;
483 PEERSTORE_destroy_record(record);
489 * Handle a store request from client
491 * @param cls client identification of the client
492 * @param srm the actual message
495 handle_store(void *cls, const struct StoreRecordMessage *srm)
497 struct GNUNET_SERVICE_Client *client = cls;
498 struct GNUNET_PEERSTORE_Record *record;
500 record = PEERSTORE_parse_record_message(srm);
502 GNUNET_ERROR_TYPE_INFO,
503 "Received a store request. Sub system `%s' Peer `%s Key `%s' Options: %u.\n",
505 GNUNET_i2s(&record->peer),
507 (uint32_t)ntohl(srm->options));
508 record->client = client;
509 if (GNUNET_OK != db->store_record(db->cls,
517 &store_record_continuation,
521 PEERSTORE_destroy_record(record);
522 GNUNET_SERVICE_client_drop(client);
529 * Peerstore service runner.
532 * @param c configuration to use
533 * @param service the initialized service
537 const struct GNUNET_CONFIGURATION_Handle *c,
538 struct GNUNET_SERVICE_Handle *service)
542 in_shutdown = GNUNET_NO;
544 if (GNUNET_OK != GNUNET_CONFIGURATION_get_value_string(cfg,
549 GNUNET_log_config_missing(GNUNET_ERROR_TYPE_ERROR,
552 GNUNET_SCHEDULER_shutdown();
555 GNUNET_asprintf(&db_lib_name, "libgnunet_plugin_peerstore_%s", database);
556 db = GNUNET_PLUGIN_load(db_lib_name, (void *)cfg);
557 GNUNET_free(database);
560 GNUNET_log(GNUNET_ERROR_TYPE_ERROR,
561 _("Could not load database backend `%s'\n"),
563 GNUNET_SCHEDULER_shutdown();
566 watchers = GNUNET_CONTAINER_multihashmap_create(10, GNUNET_NO);
567 expire_task = GNUNET_SCHEDULER_add_now(&cleanup_expired_records, NULL);
568 GNUNET_SCHEDULER_add_shutdown(&shutdown_task, NULL);
573 * Define "main" method using service macro.
577 GNUNET_SERVICE_OPTION_SOFT_SHUTDOWN,
580 &client_disconnect_cb,
582 GNUNET_MQ_hd_var_size(store,
583 GNUNET_MESSAGE_TYPE_PEERSTORE_STORE,
584 struct StoreRecordMessage,
586 GNUNET_MQ_hd_var_size(iterate,
587 GNUNET_MESSAGE_TYPE_PEERSTORE_ITERATE,
588 struct StoreRecordMessage,
590 GNUNET_MQ_hd_fixed_size(watch,
591 GNUNET_MESSAGE_TYPE_PEERSTORE_WATCH,
592 struct StoreKeyHashMessage,
594 GNUNET_MQ_hd_fixed_size(watch_cancel,
595 GNUNET_MESSAGE_TYPE_PEERSTORE_WATCH_CANCEL,
596 struct StoreKeyHashMessage,
598 GNUNET_MQ_handler_end());
601 /* end of gnunet-service-peerstore.c */