2 This file is part of GNUnet.
3 Copyright (C) 2013 Christian Grothoff (and other contributing authors)
5 GNUnet is free software; you can redistribute it and/or modify
6 it under the terms of the GNU General Public License as published
7 by the Free Software Foundation; either version 3, or (at your
8 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 General Public License for more details.
15 You should have received a copy of the GNU General Public License
16 along with GNUnet; see the file COPYING. If not, write to the
17 Free Software Foundation, Inc., 51 Franklin Street, Fifth Floor,
18 Boston, MA 02110-1301, USA.
22 * @file util/client_manager.c
23 * @brief Client manager; higher level client API with transmission queue
24 * and message handler registration.
25 * @author Gabor X Toth
31 #include "gnunet_util_lib.h"
33 #define LOG(kind,...) GNUNET_log_from (kind, "util-client-mgr", __VA_ARGS__)
36 struct OperationListItem
38 struct OperationListItem *prev;
39 struct OperationListItem *next;
47 * Continuation to invoke with the result of an operation.
49 GNUNET_ResultCallback result_cb;
52 * Closure for @a result_cb.
59 * List of arrays of message handlers.
61 struct HandlersListItem
63 struct HandlersListItem *prev;
64 struct HandlersListItem *next;
67 * NULL-terminated array of handlers.
69 const struct GNUNET_CLIENT_MANAGER_MessageHandler *handlers;
73 struct MessageQueueItem
75 struct MessageQueueItem *prev;
76 struct MessageQueueItem *next;
77 struct GNUNET_MessageHeader *msg;
81 struct GNUNET_CLIENT_MANAGER_Connection
84 * Configuration to use.
86 const struct GNUNET_CONFIGURATION_Handle *cfg;
89 * Client connection to service.
91 struct GNUNET_CLIENT_Connection *client;
94 * Currently pending transmission request, or NULL for none.
96 struct GNUNET_CLIENT_TransmitHandle *client_tmit;
99 * Service name to connect to.
101 const char *service_name;
104 * Head of messages to transmit to the service.
106 struct MessageQueueItem *tmit_head;
109 * Tail of messages to transmit to the service.
111 struct MessageQueueItem *tmit_tail;
116 const struct GNUNET_CLIENT_MANAGER_MessageHandler *handlers;
119 * First operation in the linked list.
121 struct OperationListItem *op_head;
124 * Last operation in the linked list.
126 struct OperationListItem *op_tail;
129 * Last operation ID used.
134 * Disconnect callback.
136 void (*disconnect_cb)(void *);
139 * Disconnect closure.
141 void *disconnect_cls;
144 * User context value.
145 * @see GNUNET_CLIENT_MANAGER_set_user_context()
146 * @see GNUNET_CLIENT_MANAGER_get_user_context()
151 * Last size given when user context was initialized.
152 * Used for sanity check.
154 size_t user_ctx_size;
157 * Task doing exponential back-off trying to reconnect.
159 struct GNUNET_SCHEDULER_Task * reconnect_task;
162 * Time for next connect retry.
164 struct GNUNET_TIME_Relative reconnect_delay;
167 * Are we currently polling for incoming messages?
172 * #GNUNET_YES if GNUNET_CLIENT_MANAGER_disconnect() was called
173 * and we're transmitting the last messages from the queue.
175 uint8_t is_disconnecting;
180 * Handle received messages from the service.
183 recv_message (void *cls, const struct GNUNET_MessageHeader *msg)
185 struct GNUNET_CLIENT_MANAGER_Connection *mgr = cls;
186 uint16_t type = 0, size = 0;
190 type = ntohs (msg->type);
191 size = ntohs (msg->size);
192 /* FIXME: decrease reconnect_delay gradually after a successful reconnection */
196 while (NULL != mgr->handlers[i].callback)
198 const struct GNUNET_CLIENT_MANAGER_MessageHandler *mh = &mgr->handlers[i];
199 if ((mh->type == type) || (mh->type == GNUNET_MESSAGE_TYPE_ALL))
201 if (0 != mh->expected_size
202 && ((GNUNET_NO == mh->is_variable_size && size != mh->expected_size)
203 || (GNUNET_YES == mh->is_variable_size && size < mh->expected_size)))
205 LOG (GNUNET_ERROR_TYPE_ERROR,
206 "Expected %u bytes for message of type %u, got %u.\n",
207 mh->expected_size, type, size);
209 GNUNET_CLIENT_disconnect (mgr->client);
211 recv_message (mgr, NULL);
214 mh->callback (mh->callback_cls, mgr, msg);
218 if (NULL != mgr->client)
220 GNUNET_CLIENT_receive (mgr->client, &recv_message, mgr,
221 GNUNET_TIME_UNIT_FOREVER_REL);
227 * Schedule transmission of the next message from our queue.
229 * @param mgr Client manager connection.
232 transmit_next (struct GNUNET_CLIENT_MANAGER_Connection *mgr);
236 schedule_disconnect (void *cls, const struct GNUNET_SCHEDULER_TaskContext *tc)
238 struct GNUNET_CLIENT_MANAGER_Connection *mgr = cls;
239 GNUNET_CLIENT_MANAGER_disconnect (mgr, GNUNET_NO,
240 mgr->disconnect_cb, mgr->disconnect_cls);
245 * Transmit next message to service.
248 * struct GNUNET_CLIENT_MANAGER_Connection
250 * Number of bytes available in @a buf.
252 * Where to copy the message.
254 * @return Number of bytes copied to @a buf.
257 send_next_message (void *cls, size_t buf_size, void *buf)
259 LOG (GNUNET_ERROR_TYPE_DEBUG, "send_next_message()\n");
260 struct GNUNET_CLIENT_MANAGER_Connection *mgr = cls;
265 recv_message (mgr, NULL);
269 struct MessageQueueItem *mqi = mgr->tmit_head;
273 uint16_t size = ntohs (mqi->msg->size);
274 mgr->client_tmit = NULL;
275 GNUNET_assert (size <= buf_size);
276 memcpy (buf, mqi->msg, size);
278 GNUNET_CONTAINER_DLL_remove (mgr->tmit_head, mgr->tmit_tail, mqi);
279 GNUNET_free (mqi->msg);
282 if (NULL != mgr->tmit_head)
286 else if (GNUNET_YES == mgr->is_disconnecting)
288 GNUNET_SCHEDULER_add_now (&schedule_disconnect, mgr);
292 if (GNUNET_NO == mgr->in_receive)
294 mgr->in_receive = GNUNET_YES;
295 GNUNET_CLIENT_receive (mgr->client, &recv_message, mgr,
296 GNUNET_TIME_UNIT_FOREVER_REL);
303 * Schedule transmission of the next message from our queue.
305 * @param mgr Client manager connection.
308 transmit_next (struct GNUNET_CLIENT_MANAGER_Connection *mgr)
310 LOG (GNUNET_ERROR_TYPE_DEBUG, "transmit_next()\n");
311 if (NULL != mgr->client_tmit || NULL == mgr->client)
314 if (NULL == mgr->tmit_head)
316 if (GNUNET_YES == mgr->is_disconnecting)
317 GNUNET_CLIENT_MANAGER_disconnect (mgr, GNUNET_NO,
318 mgr->disconnect_cb, mgr->disconnect_cls);
323 = GNUNET_CLIENT_notify_transmit_ready (mgr->client,
324 ntohs (mgr->tmit_head->msg->size),
325 GNUNET_TIME_UNIT_FOREVER_REL,
333 * Try again to connect to the service.
341 schedule_reconnect (void *cls, const struct GNUNET_SCHEDULER_TaskContext *tc)
343 struct GNUNET_CLIENT_MANAGER_Connection *mgr = cls;
344 mgr->reconnect_task = NULL;
346 LOG (GNUNET_ERROR_TYPE_DEBUG,
347 "Connecting to %s service.\n", mgr->service_name);
348 GNUNET_assert (NULL == mgr->client);
349 mgr->client = GNUNET_CLIENT_connect (mgr->service_name, mgr->cfg);
350 GNUNET_assert (NULL != mgr->client);
357 * Connect to service.
360 * Configuration to use.
361 * @param service_name
362 * Service name to connect to.
366 * @return Client manager connection handle.
368 struct GNUNET_CLIENT_MANAGER_Connection *
369 GNUNET_CLIENT_MANAGER_connect (const struct GNUNET_CONFIGURATION_Handle *cfg,
370 const char *service_name,
372 GNUNET_CLIENT_MANAGER_MessageHandler *handlers)
374 struct GNUNET_CLIENT_MANAGER_Connection *
375 mgr = GNUNET_malloc (sizeof (*mgr));
377 mgr->service_name = service_name;
378 mgr->handlers = handlers;
379 mgr->reconnect_delay = GNUNET_TIME_UNIT_ZERO;
380 mgr->reconnect_task = GNUNET_SCHEDULER_add_now (&schedule_reconnect, mgr);
386 * Disconnect from the service.
389 * Client manager connection.
390 * @param transmit_queue
391 * Transmit pending messages in queue before disconnecting.
392 * @param disconnect_cb
393 * Function called after disconnected from the service.
395 * Closure for @a disconnect_cb.
398 GNUNET_CLIENT_MANAGER_disconnect (struct GNUNET_CLIENT_MANAGER_Connection *mgr,
400 GNUNET_ContinuationCallback disconnect_cb,
403 LOG (GNUNET_ERROR_TYPE_DEBUG, "Disconnecting (%d)\n", transmit_queue);
404 mgr->disconnect_cb = disconnect_cb;
405 mgr->disconnect_cls = cls;
406 if (NULL != mgr->tmit_head)
408 if (GNUNET_YES == transmit_queue)
410 mgr->is_disconnecting = GNUNET_YES;
416 LOG (GNUNET_ERROR_TYPE_DEBUG,
417 "Disconnecting while there are still messages "
418 "in the transmission queue.\n");
419 GNUNET_CLIENT_MANAGER_drop_queue (mgr);
422 if (mgr->reconnect_task != NULL)
424 GNUNET_SCHEDULER_cancel (mgr->reconnect_task);
425 mgr->reconnect_task = NULL;
427 if (NULL != mgr->client_tmit)
429 GNUNET_CLIENT_notify_transmit_ready_cancel (mgr->client_tmit);
430 mgr->client_tmit = NULL;
432 if (NULL != mgr->client)
434 GNUNET_CLIENT_disconnect (mgr->client);
437 if (NULL != mgr->disconnect_cb)
438 mgr->disconnect_cb (mgr->disconnect_cls);
440 LOG (GNUNET_ERROR_TYPE_DEBUG, "Disconnected.\n");
445 * Reschedule connect to the service using exponential back-off.
448 * Client manager connection.
451 GNUNET_CLIENT_MANAGER_reconnect (struct GNUNET_CLIENT_MANAGER_Connection *mgr)
453 if (NULL != mgr->reconnect_task)
456 if (NULL != mgr->client_tmit)
458 GNUNET_CLIENT_notify_transmit_ready_cancel (mgr->client_tmit);
459 mgr->client_tmit = NULL;
461 if (NULL != mgr->client)
463 GNUNET_CLIENT_disconnect (mgr->client);
466 mgr->in_receive = GNUNET_NO;
467 LOG (GNUNET_ERROR_TYPE_DEBUG,
468 "Scheduling task to reconnect to service in %s.\n",
469 GNUNET_STRINGS_relative_time_to_string (mgr->reconnect_delay, GNUNET_YES));
470 mgr->reconnect_task =
471 GNUNET_SCHEDULER_add_delayed (mgr->reconnect_delay, &schedule_reconnect, mgr);
472 mgr->reconnect_delay = GNUNET_TIME_STD_BACKOFF (mgr->reconnect_delay);
477 * Add a message to the end of the transmission queue.
480 * Client manager connection.
482 * Message to transmit, should be allocated with GNUNET_malloc() or
483 * GNUNET_new(), as it is freed with GNUNET_free() after transmission.
486 GNUNET_CLIENT_MANAGER_transmit (struct GNUNET_CLIENT_MANAGER_Connection *mgr,
487 struct GNUNET_MessageHeader *msg)
489 struct MessageQueueItem *mqi = GNUNET_malloc (sizeof (*mqi));
491 GNUNET_CONTAINER_DLL_insert_tail (mgr->tmit_head, mgr->tmit_tail, mqi);
497 * Add a message to the beginning of the transmission queue.
500 * Client manager connection.
502 * Message to transmit, should be allocated with GNUNET_malloc() or
503 * GNUNET_new(), as it is freed with GNUNET_free() after transmission.
506 GNUNET_CLIENT_MANAGER_transmit_now (struct GNUNET_CLIENT_MANAGER_Connection *mgr,
507 struct GNUNET_MessageHeader *msg)
509 struct MessageQueueItem *mqi = GNUNET_malloc (sizeof (*mqi));
511 GNUNET_CONTAINER_DLL_insert (mgr->tmit_head, mgr->tmit_tail, mqi);
517 * Drop all queued messages.
520 * Client manager connection.
523 GNUNET_CLIENT_MANAGER_drop_queue (struct GNUNET_CLIENT_MANAGER_Connection *mgr)
525 struct MessageQueueItem *cur, *next = mgr->tmit_head;
530 GNUNET_free (cur->msg);
537 * Obtain client connection handle.
540 * Client manager connection.
542 * @return Client connection handle.
544 struct GNUNET_CLIENT_Connection *
545 GNUNET_CLIENT_MANAGER_get_client (struct GNUNET_CLIENT_MANAGER_Connection *mgr)
552 * Return user context associated with the given client.
553 * Note: you should probably use the macro (call without the underscore).
556 * Client manager connection.
558 * Number of bytes in user context struct (for verification only).
560 * @return User context.
563 GNUNET_CLIENT_MANAGER_get_user_context_ (struct GNUNET_CLIENT_MANAGER_Connection *mgr,
566 if ((0 == mgr->user_ctx_size) &&
567 (NULL == mgr->user_ctx))
568 return NULL; /* never set */
569 GNUNET_assert (size == mgr->user_ctx_size);
570 return mgr->user_ctx;
575 * Set user context to be associated with the given client.
576 * Note: you should probably use the macro (call without the underscore).
579 * Client manager connection.
583 * Number of bytes in user context struct (for verification only).
586 GNUNET_CLIENT_MANAGER_set_user_context_ (struct GNUNET_CLIENT_MANAGER_Connection *mgr,
592 mgr->user_ctx_size = 0;
596 mgr->user_ctx_size = size;
602 * Get a unique operation ID to distinguish between asynchronous requests.
605 * Client manager connection.
607 * @return Operation ID to use.
610 GNUNET_CLIENT_MANAGER_op_get_next_id (struct GNUNET_CLIENT_MANAGER_Connection *mgr)
612 return ++mgr->last_op_id;
617 * Find operation by ID.
620 * Client manager connection.
622 * Operation ID to look up.
624 * @return Operation, or NULL if not found.
626 static struct OperationListItem *
627 op_find (struct GNUNET_CLIENT_MANAGER_Connection *mgr, uint64_t op_id)
629 struct OperationListItem *op = mgr->op_head;
632 if (op->op_id == op_id)
641 * Find operation by ID.
644 * Client manager connection.
646 * Operation ID to look up.
647 * @param[out] result_cb
648 * If an operation was found, its result callback is returned here.
650 * If an operation was found, its closure is returned here.
652 * @return #GNUNET_YES if an operation was found,
653 * #GNUNET_NO if not found.
656 GNUNET_CLIENT_MANAGER_op_find (struct GNUNET_CLIENT_MANAGER_Connection *mgr,
658 GNUNET_ResultCallback *result_cb,
661 struct OperationListItem *op = op_find (mgr, op_id);
664 *result_cb = op->result_cb;
673 * Add a new operation.
676 * Client manager connection.
678 * Function to call with the result of the operation.
680 * Closure for @a result_cb.
682 * @return ID of the new operation.
685 GNUNET_CLIENT_MANAGER_op_add (struct GNUNET_CLIENT_MANAGER_Connection *mgr,
686 GNUNET_ResultCallback result_cb,
689 if (NULL == result_cb)
692 struct OperationListItem *op = GNUNET_malloc (sizeof (*op));
693 op->op_id = GNUNET_CLIENT_MANAGER_op_get_next_id (mgr);
694 op->result_cb = result_cb;
696 GNUNET_CONTAINER_DLL_insert_tail (mgr->op_head, mgr->op_tail, op);
698 LOG (GNUNET_ERROR_TYPE_DEBUG,
699 "%p Added operation #%" PRIu64 "\n", mgr, op->op_id);
705 * Remove an operation, and call its result callback (unless it was cancelled).
709 * Client manager connection.
713 * Result of the operation.
715 * Data result of the operation.
719 * Is the operation cancelled?
720 * #GNUNET_NO Not cancelled, result callback is called.
721 * #GNUNET_YES Cancelled, result callback is not called.
723 * @return #GNUNET_YES if the operation was found and removed,
724 * #GNUNET_NO if the operation was not found.
727 op_result (struct GNUNET_CLIENT_MANAGER_Connection *mgr,
728 uint64_t op_id, int64_t result_code,
729 const void *data, uint16_t data_size, uint8_t cancel)
734 struct OperationListItem *op = op_find (mgr, op_id);
737 LOG (GNUNET_ERROR_TYPE_WARNING,
738 "Could not find operation #%" PRIu64 "\n", op_id);
742 GNUNET_CONTAINER_DLL_remove (mgr->op_head, mgr->op_tail, op);
744 if (GNUNET_YES != cancel && NULL != op->result_cb)
745 op->result_cb (op->cls, result_code, data, data_size);
753 * Call the result callback of an operation and remove it.
756 * Client manager connection.
760 * Result of the operation.
762 * Data result of the operation.
766 * @return #GNUNET_YES if the operation was found and removed,
767 * #GNUNET_NO if the operation was not found.
770 GNUNET_CLIENT_MANAGER_op_result (struct GNUNET_CLIENT_MANAGER_Connection *mgr,
771 uint64_t op_id, int64_t result_code,
772 const void *data, uint16_t data_size)
774 LOG (GNUNET_ERROR_TYPE_DEBUG,
775 "%p Received result for operation #%" PRIu64 ": %" PRId64 " (size: %u)\n",
776 mgr, op_id, result_code, data_size);
777 return op_result (mgr, op_id, result_code, data, data_size, GNUNET_NO);
782 * Cancel an operation.
785 * Client manager connection.
789 * @return #GNUNET_YES if the operation was found and removed,
790 * #GNUNET_NO if the operation was not found.
793 GNUNET_CLIENT_MANAGER_op_cancel (struct GNUNET_CLIENT_MANAGER_Connection *mgr,
796 LOG (GNUNET_ERROR_TYPE_DEBUG,
797 "%p Cancelling operation #%" PRIu64 "\n", mgr, op_id);
798 return op_result (mgr, op_id, 0, NULL, 0, GNUNET_YES);