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 */
194 else /* disconnected */
196 mgr->client_tmit = NULL;
200 while (NULL != mgr->handlers[i].callback)
202 const struct GNUNET_CLIENT_MANAGER_MessageHandler *mh = &mgr->handlers[i];
203 if ((mh->type == type) || (mh->type == GNUNET_MESSAGE_TYPE_ALL))
205 if (0 != mh->expected_size
206 && ((GNUNET_NO == mh->is_variable_size && size != mh->expected_size)
207 || (GNUNET_YES == mh->is_variable_size && size < mh->expected_size)))
209 LOG (GNUNET_ERROR_TYPE_ERROR,
210 "Expected %u bytes for message of type %u, got %u.\n",
211 mh->expected_size, type, size);
213 GNUNET_CLIENT_disconnect (mgr->client);
215 recv_message (mgr, NULL);
218 mh->callback (mh->callback_cls, mgr, msg);
222 if (NULL != mgr->client)
224 GNUNET_CLIENT_receive (mgr->client, &recv_message, mgr,
225 GNUNET_TIME_UNIT_FOREVER_REL);
231 * Schedule transmission of the next message from our queue.
233 * @param mgr Client manager connection.
236 transmit_next (struct GNUNET_CLIENT_MANAGER_Connection *mgr);
240 schedule_disconnect (void *cls, const struct GNUNET_SCHEDULER_TaskContext *tc)
242 struct GNUNET_CLIENT_MANAGER_Connection *mgr = cls;
243 GNUNET_CLIENT_MANAGER_disconnect (mgr, GNUNET_NO,
244 mgr->disconnect_cb, mgr->disconnect_cls);
249 * Transmit next message to service.
252 * struct GNUNET_CLIENT_MANAGER_Connection
254 * Number of bytes available in @a buf.
256 * Where to copy the message.
258 * @return Number of bytes copied to @a buf.
261 send_next_message (void *cls, size_t buf_size, void *buf)
263 LOG (GNUNET_ERROR_TYPE_DEBUG, "send_next_message()\n");
264 struct GNUNET_CLIENT_MANAGER_Connection *mgr = cls;
269 recv_message (mgr, NULL);
273 struct MessageQueueItem *mqi = mgr->tmit_head;
277 uint16_t size = ntohs (mqi->msg->size);
278 mgr->client_tmit = NULL;
279 GNUNET_assert (size <= buf_size);
280 memcpy (buf, mqi->msg, size);
282 GNUNET_CONTAINER_DLL_remove (mgr->tmit_head, mgr->tmit_tail, mqi);
283 GNUNET_free (mqi->msg);
286 if (NULL != mgr->tmit_head)
290 else if (GNUNET_YES == mgr->is_disconnecting)
292 GNUNET_SCHEDULER_add_now (&schedule_disconnect, mgr);
296 if (GNUNET_NO == mgr->in_receive)
298 mgr->in_receive = GNUNET_YES;
299 GNUNET_CLIENT_receive (mgr->client, &recv_message, mgr,
300 GNUNET_TIME_UNIT_FOREVER_REL);
307 * Schedule transmission of the next message from our queue.
309 * @param mgr Client manager connection.
312 transmit_next (struct GNUNET_CLIENT_MANAGER_Connection *mgr)
314 LOG (GNUNET_ERROR_TYPE_DEBUG, "transmit_next()\n");
315 if (NULL != mgr->client_tmit || NULL == mgr->client)
318 if (NULL == mgr->tmit_head)
320 if (GNUNET_YES == mgr->is_disconnecting)
321 GNUNET_CLIENT_MANAGER_disconnect (mgr, GNUNET_NO,
322 mgr->disconnect_cb, mgr->disconnect_cls);
327 = GNUNET_CLIENT_notify_transmit_ready (mgr->client,
328 ntohs (mgr->tmit_head->msg->size),
329 GNUNET_TIME_UNIT_FOREVER_REL,
337 * Try again to connect to the service.
345 schedule_reconnect (void *cls, const struct GNUNET_SCHEDULER_TaskContext *tc)
347 struct GNUNET_CLIENT_MANAGER_Connection *mgr = cls;
348 mgr->reconnect_task = NULL;
350 LOG (GNUNET_ERROR_TYPE_DEBUG,
351 "Connecting to %s service.\n", mgr->service_name);
352 GNUNET_assert (NULL == mgr->client);
353 mgr->client = GNUNET_CLIENT_connect (mgr->service_name, mgr->cfg);
354 GNUNET_assert (NULL != mgr->client);
361 * Connect to service.
364 * Configuration to use.
365 * @param service_name
366 * Service name to connect to.
370 * @return Client manager connection handle.
372 struct GNUNET_CLIENT_MANAGER_Connection *
373 GNUNET_CLIENT_MANAGER_connect (const struct GNUNET_CONFIGURATION_Handle *cfg,
374 const char *service_name,
376 GNUNET_CLIENT_MANAGER_MessageHandler *handlers)
378 struct GNUNET_CLIENT_MANAGER_Connection *
379 mgr = GNUNET_malloc (sizeof (*mgr));
381 mgr->service_name = service_name;
382 mgr->handlers = handlers;
383 mgr->reconnect_delay = GNUNET_TIME_UNIT_ZERO;
384 mgr->reconnect_task = GNUNET_SCHEDULER_add_now (&schedule_reconnect, mgr);
390 * Disconnect from the service.
393 * Client manager connection.
394 * @param transmit_queue
395 * Transmit pending messages in queue before disconnecting.
396 * @param disconnect_cb
397 * Function called after disconnected from the service.
399 * Closure for @a disconnect_cb.
402 GNUNET_CLIENT_MANAGER_disconnect (struct GNUNET_CLIENT_MANAGER_Connection *mgr,
404 GNUNET_ContinuationCallback disconnect_cb,
407 LOG (GNUNET_ERROR_TYPE_DEBUG, "Disconnecting (%d)\n", transmit_queue);
408 mgr->disconnect_cb = disconnect_cb;
409 mgr->disconnect_cls = cls;
410 if (NULL != mgr->tmit_head)
412 if (GNUNET_YES == transmit_queue)
414 mgr->is_disconnecting = GNUNET_YES;
420 LOG (GNUNET_ERROR_TYPE_DEBUG,
421 "Disconnecting while there are still messages "
422 "in the transmission queue.\n");
423 GNUNET_CLIENT_MANAGER_drop_queue (mgr);
426 if (mgr->reconnect_task != NULL)
428 GNUNET_SCHEDULER_cancel (mgr->reconnect_task);
429 mgr->reconnect_task = NULL;
431 if (NULL != mgr->client_tmit)
433 GNUNET_CLIENT_notify_transmit_ready_cancel (mgr->client_tmit);
434 mgr->client_tmit = NULL;
436 if (NULL != mgr->client)
438 GNUNET_CLIENT_disconnect (mgr->client);
441 if (NULL != mgr->disconnect_cb)
442 mgr->disconnect_cb (mgr->disconnect_cls);
444 LOG (GNUNET_ERROR_TYPE_DEBUG, "Disconnected.\n");
449 * Reschedule connect to the service using exponential back-off.
452 * Client manager connection.
455 GNUNET_CLIENT_MANAGER_reconnect (struct GNUNET_CLIENT_MANAGER_Connection *mgr)
457 if (NULL != mgr->reconnect_task)
460 if (NULL != mgr->client_tmit)
462 GNUNET_CLIENT_notify_transmit_ready_cancel (mgr->client_tmit);
463 mgr->client_tmit = NULL;
465 if (NULL != mgr->client)
467 GNUNET_CLIENT_disconnect (mgr->client);
470 mgr->in_receive = GNUNET_NO;
471 LOG (GNUNET_ERROR_TYPE_DEBUG,
472 "Scheduling task to reconnect to service in %s.\n",
473 GNUNET_STRINGS_relative_time_to_string (mgr->reconnect_delay, GNUNET_YES));
474 mgr->reconnect_task =
475 GNUNET_SCHEDULER_add_delayed (mgr->reconnect_delay, &schedule_reconnect, mgr);
476 mgr->reconnect_delay = GNUNET_TIME_STD_BACKOFF (mgr->reconnect_delay);
481 * Add a message to the end of the transmission queue.
484 * Client manager connection.
486 * Message to transmit, should be allocated with GNUNET_malloc() or
487 * GNUNET_new(), as it is freed with GNUNET_free() after transmission.
490 GNUNET_CLIENT_MANAGER_transmit (struct GNUNET_CLIENT_MANAGER_Connection *mgr,
491 struct GNUNET_MessageHeader *msg)
493 struct MessageQueueItem *mqi = GNUNET_malloc (sizeof (*mqi));
494 mqi->msg = GNUNET_copy_message (msg);
495 GNUNET_CONTAINER_DLL_insert_tail (mgr->tmit_head, mgr->tmit_tail, mqi);
501 * Add a message to the beginning of the transmission queue.
504 * Client manager connection.
506 * Message to transmit, should be allocated with GNUNET_malloc() or
507 * GNUNET_new(), as it is freed with GNUNET_free() after transmission.
510 GNUNET_CLIENT_MANAGER_transmit_now (struct GNUNET_CLIENT_MANAGER_Connection *mgr,
511 struct GNUNET_MessageHeader *msg)
513 struct MessageQueueItem *mqi = GNUNET_malloc (sizeof (*mqi));
514 mqi->msg = GNUNET_copy_message (msg);
515 GNUNET_CONTAINER_DLL_insert (mgr->tmit_head, mgr->tmit_tail, mqi);
521 * Drop all queued messages.
524 * Client manager connection.
527 GNUNET_CLIENT_MANAGER_drop_queue (struct GNUNET_CLIENT_MANAGER_Connection *mgr)
529 struct MessageQueueItem *cur, *next = mgr->tmit_head;
534 GNUNET_free (cur->msg);
541 * Obtain client connection handle.
544 * Client manager connection.
546 * @return Client connection handle.
548 struct GNUNET_CLIENT_Connection *
549 GNUNET_CLIENT_MANAGER_get_client (struct GNUNET_CLIENT_MANAGER_Connection *mgr)
556 * Return user context associated with the given client.
557 * Note: you should probably use the macro (call without the underscore).
560 * Client manager connection.
562 * Number of bytes in user context struct (for verification only).
564 * @return User context.
567 GNUNET_CLIENT_MANAGER_get_user_context_ (struct GNUNET_CLIENT_MANAGER_Connection *mgr,
570 if ((0 == mgr->user_ctx_size) &&
571 (NULL == mgr->user_ctx))
572 return NULL; /* never set */
573 GNUNET_assert (size == mgr->user_ctx_size);
574 return mgr->user_ctx;
579 * Set user context to be associated with the given client.
580 * Note: you should probably use the macro (call without the underscore).
583 * Client manager connection.
587 * Number of bytes in user context struct (for verification only).
590 GNUNET_CLIENT_MANAGER_set_user_context_ (struct GNUNET_CLIENT_MANAGER_Connection *mgr,
596 mgr->user_ctx_size = 0;
600 mgr->user_ctx_size = size;
606 * Get a unique operation ID to distinguish between asynchronous requests.
609 * Client manager connection.
611 * @return Operation ID to use.
614 GNUNET_CLIENT_MANAGER_op_get_next_id (struct GNUNET_CLIENT_MANAGER_Connection *mgr)
616 return ++mgr->last_op_id;
621 * Find operation by ID.
624 * Client manager connection.
626 * Operation ID to look up.
628 * @return Operation, or NULL if not found.
630 static struct OperationListItem *
631 op_find (struct GNUNET_CLIENT_MANAGER_Connection *mgr, uint64_t op_id)
633 struct OperationListItem *op = mgr->op_head;
636 if (op->op_id == op_id)
645 * Find operation by ID.
648 * Client manager connection.
650 * Operation ID to look up.
651 * @param[out] result_cb
652 * If an operation was found, its result callback is returned here.
654 * If an operation was found, its closure is returned here.
656 * @return #GNUNET_YES if an operation was found,
657 * #GNUNET_NO if not found.
660 GNUNET_CLIENT_MANAGER_op_find (struct GNUNET_CLIENT_MANAGER_Connection *mgr,
662 GNUNET_ResultCallback *result_cb,
665 struct OperationListItem *op = op_find (mgr, op_id);
668 *result_cb = op->result_cb;
677 * Add a new operation.
680 * Client manager connection.
682 * Function to call with the result of the operation.
684 * Closure for @a result_cb.
686 * @return ID of the new operation.
689 GNUNET_CLIENT_MANAGER_op_add (struct GNUNET_CLIENT_MANAGER_Connection *mgr,
690 GNUNET_ResultCallback result_cb,
693 if (NULL == result_cb)
696 struct OperationListItem *op = GNUNET_malloc (sizeof (*op));
697 op->op_id = GNUNET_CLIENT_MANAGER_op_get_next_id (mgr);
698 op->result_cb = result_cb;
700 GNUNET_CONTAINER_DLL_insert_tail (mgr->op_head, mgr->op_tail, op);
702 LOG (GNUNET_ERROR_TYPE_DEBUG,
703 "%p Added operation #%" PRIu64 "\n", mgr, op->op_id);
709 * Remove an operation, and call its result callback (unless it was cancelled).
713 * Client manager connection.
717 * Result of the operation.
719 * Data result of the operation.
723 * Is the operation cancelled?
724 * #GNUNET_NO Not cancelled, result callback is called.
725 * #GNUNET_YES Cancelled, result callback is not called.
727 * @return #GNUNET_YES if the operation was found and removed,
728 * #GNUNET_NO if the operation was not found.
731 op_result (struct GNUNET_CLIENT_MANAGER_Connection *mgr,
732 uint64_t op_id, int64_t result_code,
733 const void *data, uint16_t data_size, uint8_t cancel)
738 struct OperationListItem *op = op_find (mgr, op_id);
741 LOG (GNUNET_ERROR_TYPE_WARNING,
742 "Could not find operation #%" PRIu64 "\n", op_id);
746 GNUNET_CONTAINER_DLL_remove (mgr->op_head, mgr->op_tail, op);
748 if (GNUNET_YES != cancel && NULL != op->result_cb)
749 op->result_cb (op->cls, result_code, data, data_size);
757 * Call the result callback of an operation and remove it.
760 * Client manager connection.
764 * Result of the operation.
766 * Data result of the operation.
770 * @return #GNUNET_YES if the operation was found and removed,
771 * #GNUNET_NO if the operation was not found.
774 GNUNET_CLIENT_MANAGER_op_result (struct GNUNET_CLIENT_MANAGER_Connection *mgr,
775 uint64_t op_id, int64_t result_code,
776 const void *data, uint16_t data_size)
778 LOG (GNUNET_ERROR_TYPE_DEBUG,
779 "%p Received result for operation #%" PRIu64 ": %" PRId64 " (size: %u)\n",
780 mgr, op_id, result_code, data_size);
781 return op_result (mgr, op_id, result_code, data, data_size, GNUNET_NO);
786 * Cancel an operation.
789 * Client manager connection.
793 * @return #GNUNET_YES if the operation was found and removed,
794 * #GNUNET_NO if the operation was not found.
797 GNUNET_CLIENT_MANAGER_op_cancel (struct GNUNET_CLIENT_MANAGER_Connection *mgr,
800 LOG (GNUNET_ERROR_TYPE_DEBUG,
801 "%p Cancelling operation #%" PRIu64 "\n", mgr, op_id);
802 return op_result (mgr, op_id, 0, NULL, 0, GNUNET_YES);