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;
199 if (GNUNET_YES == mgr->is_disconnecting)
203 while (NULL != mgr->handlers[i].callback)
205 const struct GNUNET_CLIENT_MANAGER_MessageHandler *mh = &mgr->handlers[i];
206 if ((mh->type == type) || (mh->type == GNUNET_MESSAGE_TYPE_ALL))
208 if (0 != mh->expected_size
209 && ((GNUNET_NO == mh->is_variable_size && size != mh->expected_size)
210 || (GNUNET_YES == mh->is_variable_size && size < mh->expected_size)))
212 LOG (GNUNET_ERROR_TYPE_ERROR,
213 "Expected %u bytes for message of type %u, got %u.\n",
214 mh->expected_size, type, size);
216 GNUNET_CLIENT_disconnect (mgr->client);
218 recv_message (mgr, NULL);
221 mh->callback (mh->callback_cls, mgr, msg);
225 if (NULL != mgr->client)
227 GNUNET_CLIENT_receive (mgr->client, &recv_message, mgr,
228 GNUNET_TIME_UNIT_FOREVER_REL);
234 * Schedule transmission of the next message from our queue.
236 * @param mgr Client manager connection.
239 transmit_next (struct GNUNET_CLIENT_MANAGER_Connection *mgr);
243 schedule_disconnect (void *cls, const struct GNUNET_SCHEDULER_TaskContext *tc)
245 struct GNUNET_CLIENT_MANAGER_Connection *mgr = cls;
246 GNUNET_CLIENT_MANAGER_disconnect (mgr, GNUNET_NO,
247 mgr->disconnect_cb, mgr->disconnect_cls);
252 * Transmit next message to service.
255 * struct GNUNET_CLIENT_MANAGER_Connection
257 * Number of bytes available in @a buf.
259 * Where to copy the message.
261 * @return Number of bytes copied to @a buf.
264 send_next_message (void *cls, size_t buf_size, void *buf)
266 LOG (GNUNET_ERROR_TYPE_DEBUG, "send_next_message()\n");
267 struct GNUNET_CLIENT_MANAGER_Connection *mgr = cls;
272 recv_message (mgr, NULL);
276 struct MessageQueueItem *mqi = mgr->tmit_head;
280 uint16_t size = ntohs (mqi->msg->size);
281 mgr->client_tmit = NULL;
282 GNUNET_assert (size <= buf_size);
283 memcpy (buf, mqi->msg, size);
285 GNUNET_CONTAINER_DLL_remove (mgr->tmit_head, mgr->tmit_tail, mqi);
286 GNUNET_free (mqi->msg);
289 if (NULL != mgr->tmit_head)
293 else if (GNUNET_YES == mgr->is_disconnecting)
295 GNUNET_SCHEDULER_add_now (&schedule_disconnect, mgr);
299 if (GNUNET_NO == mgr->in_receive)
301 mgr->in_receive = GNUNET_YES;
302 GNUNET_CLIENT_receive (mgr->client, &recv_message, mgr,
303 GNUNET_TIME_UNIT_FOREVER_REL);
310 * Schedule transmission of the next message from our queue.
312 * @param mgr Client manager connection.
315 transmit_next (struct GNUNET_CLIENT_MANAGER_Connection *mgr)
317 LOG (GNUNET_ERROR_TYPE_DEBUG, "transmit_next()\n");
318 if (NULL != mgr->client_tmit || NULL == mgr->client)
321 if (NULL == mgr->tmit_head)
323 if (GNUNET_YES == mgr->is_disconnecting)
324 GNUNET_CLIENT_MANAGER_disconnect (mgr, GNUNET_NO,
325 mgr->disconnect_cb, mgr->disconnect_cls);
330 = GNUNET_CLIENT_notify_transmit_ready (mgr->client,
331 GNUNET_SERVER_MAX_MESSAGE_SIZE - 1,
332 GNUNET_TIME_UNIT_FOREVER_REL,
340 * Try again to connect to the service.
348 schedule_reconnect (void *cls, const struct GNUNET_SCHEDULER_TaskContext *tc)
350 struct GNUNET_CLIENT_MANAGER_Connection *mgr = cls;
351 mgr->reconnect_task = NULL;
353 LOG (GNUNET_ERROR_TYPE_DEBUG,
354 "Connecting to %s service.\n", mgr->service_name);
355 GNUNET_assert (NULL == mgr->client);
356 mgr->client = GNUNET_CLIENT_connect (mgr->service_name, mgr->cfg);
357 GNUNET_assert (NULL != mgr->client);
364 * Connect to service.
367 * Configuration to use.
368 * @param service_name
369 * Service name to connect to.
373 * @return Client manager connection handle.
375 struct GNUNET_CLIENT_MANAGER_Connection *
376 GNUNET_CLIENT_MANAGER_connect (const struct GNUNET_CONFIGURATION_Handle *cfg,
377 const char *service_name,
379 GNUNET_CLIENT_MANAGER_MessageHandler *handlers)
381 struct GNUNET_CLIENT_MANAGER_Connection *
382 mgr = GNUNET_malloc (sizeof (*mgr));
384 mgr->service_name = service_name;
385 mgr->handlers = handlers;
386 mgr->reconnect_delay = GNUNET_TIME_UNIT_ZERO;
387 mgr->reconnect_task = GNUNET_SCHEDULER_add_now (&schedule_reconnect, mgr);
393 * Disconnect from the service.
396 * Client manager connection.
397 * @param transmit_queue
398 * Transmit pending messages in queue before disconnecting.
399 * @param disconnect_cb
400 * Function called after disconnected from the service.
402 * Closure for @a disconnect_cb.
405 GNUNET_CLIENT_MANAGER_disconnect (struct GNUNET_CLIENT_MANAGER_Connection *mgr,
407 GNUNET_ContinuationCallback disconnect_cb,
410 LOG (GNUNET_ERROR_TYPE_DEBUG, "Disconnecting (%d)\n", transmit_queue);
411 mgr->disconnect_cb = disconnect_cb;
412 mgr->disconnect_cls = cls;
413 if (NULL != mgr->tmit_head)
415 if (GNUNET_YES == transmit_queue)
417 mgr->is_disconnecting = GNUNET_YES;
423 LOG (GNUNET_ERROR_TYPE_DEBUG,
424 "Disconnecting while there are still messages "
425 "in the transmission queue.\n");
426 GNUNET_CLIENT_MANAGER_drop_queue (mgr);
429 if (mgr->reconnect_task != NULL)
431 GNUNET_SCHEDULER_cancel (mgr->reconnect_task);
432 mgr->reconnect_task = NULL;
434 if (NULL != mgr->client_tmit)
436 GNUNET_CLIENT_notify_transmit_ready_cancel (mgr->client_tmit);
437 mgr->client_tmit = NULL;
439 if (NULL != mgr->client)
441 GNUNET_CLIENT_disconnect (mgr->client);
444 if (NULL != mgr->disconnect_cb)
445 mgr->disconnect_cb (mgr->disconnect_cls);
447 LOG (GNUNET_ERROR_TYPE_DEBUG, "Disconnected.\n");
452 * Reschedule connect to the service using exponential back-off.
455 * Client manager connection.
458 GNUNET_CLIENT_MANAGER_reconnect (struct GNUNET_CLIENT_MANAGER_Connection *mgr)
460 if (NULL != mgr->reconnect_task)
463 if (NULL != mgr->client_tmit)
465 GNUNET_CLIENT_notify_transmit_ready_cancel (mgr->client_tmit);
466 mgr->client_tmit = NULL;
468 if (NULL != mgr->client)
470 GNUNET_CLIENT_disconnect (mgr->client);
473 mgr->in_receive = GNUNET_NO;
474 LOG (GNUNET_ERROR_TYPE_DEBUG,
475 "Scheduling task to reconnect to service in %s.\n",
476 GNUNET_STRINGS_relative_time_to_string (mgr->reconnect_delay, GNUNET_YES));
477 mgr->reconnect_task =
478 GNUNET_SCHEDULER_add_delayed (mgr->reconnect_delay, &schedule_reconnect, mgr);
479 mgr->reconnect_delay = GNUNET_TIME_STD_BACKOFF (mgr->reconnect_delay);
484 * Add a message to the end of the transmission queue.
487 * Client manager connection.
489 * Message to transmit, should be allocated with GNUNET_malloc() or
490 * GNUNET_new(), as it is freed with GNUNET_free() after transmission.
493 GNUNET_CLIENT_MANAGER_transmit (struct GNUNET_CLIENT_MANAGER_Connection *mgr,
494 struct GNUNET_MessageHeader *msg)
496 struct MessageQueueItem *mqi = GNUNET_malloc (sizeof (*mqi));
497 mqi->msg = GNUNET_copy_message (msg);
498 GNUNET_CONTAINER_DLL_insert_tail (mgr->tmit_head, mgr->tmit_tail, mqi);
504 * Add a message to the beginning of the transmission queue.
507 * Client manager connection.
509 * Message to transmit, should be allocated with GNUNET_malloc() or
510 * GNUNET_new(), as it is freed with GNUNET_free() after transmission.
513 GNUNET_CLIENT_MANAGER_transmit_now (struct GNUNET_CLIENT_MANAGER_Connection *mgr,
514 struct GNUNET_MessageHeader *msg)
516 struct MessageQueueItem *mqi = GNUNET_malloc (sizeof (*mqi));
517 mqi->msg = GNUNET_copy_message (msg);
518 GNUNET_CONTAINER_DLL_insert (mgr->tmit_head, mgr->tmit_tail, mqi);
524 * Drop all queued messages.
527 * Client manager connection.
530 GNUNET_CLIENT_MANAGER_drop_queue (struct GNUNET_CLIENT_MANAGER_Connection *mgr)
532 struct MessageQueueItem *cur, *next = mgr->tmit_head;
537 GNUNET_free (cur->msg);
544 * Obtain client connection handle.
547 * Client manager connection.
549 * @return Client connection handle.
551 struct GNUNET_CLIENT_Connection *
552 GNUNET_CLIENT_MANAGER_get_client (struct GNUNET_CLIENT_MANAGER_Connection *mgr)
559 * Return user context associated with the given client.
560 * Note: you should probably use the macro (call without the underscore).
563 * Client manager connection.
565 * Number of bytes in user context struct (for verification only).
567 * @return User context.
570 GNUNET_CLIENT_MANAGER_get_user_context_ (struct GNUNET_CLIENT_MANAGER_Connection *mgr,
573 if ((0 == mgr->user_ctx_size) &&
574 (NULL == mgr->user_ctx))
575 return NULL; /* never set */
576 GNUNET_assert (size == mgr->user_ctx_size);
577 return mgr->user_ctx;
582 * Set user context to be associated with the given client.
583 * Note: you should probably use the macro (call without the underscore).
586 * Client manager connection.
590 * Number of bytes in user context struct (for verification only).
593 GNUNET_CLIENT_MANAGER_set_user_context_ (struct GNUNET_CLIENT_MANAGER_Connection *mgr,
599 mgr->user_ctx_size = 0;
603 mgr->user_ctx_size = size;
609 * Get a unique operation ID to distinguish between asynchronous requests.
612 * Client manager connection.
614 * @return Operation ID to use.
617 GNUNET_CLIENT_MANAGER_op_get_next_id (struct GNUNET_CLIENT_MANAGER_Connection *mgr)
619 return ++mgr->last_op_id;
624 * Find operation by ID.
627 * Client manager connection.
629 * Operation ID to look up.
631 * @return Operation, or NULL if not found.
633 static struct OperationListItem *
634 op_find (struct GNUNET_CLIENT_MANAGER_Connection *mgr, uint64_t op_id)
636 struct OperationListItem *op = mgr->op_head;
639 if (op->op_id == op_id)
648 * Find operation by ID.
651 * Client manager connection.
653 * Operation ID to look up.
654 * @param[out] result_cb
655 * If an operation was found, its result callback is returned here.
657 * If an operation was found, its closure is returned here.
659 * @return #GNUNET_YES if an operation was found,
660 * #GNUNET_NO if not found.
663 GNUNET_CLIENT_MANAGER_op_find (struct GNUNET_CLIENT_MANAGER_Connection *mgr,
665 GNUNET_ResultCallback *result_cb,
668 struct OperationListItem *op = op_find (mgr, op_id);
671 *result_cb = op->result_cb;
680 * Add a new operation.
683 * Client manager connection.
685 * Function to call with the result of the operation.
687 * Closure for @a result_cb.
689 * @return ID of the new operation.
692 GNUNET_CLIENT_MANAGER_op_add (struct GNUNET_CLIENT_MANAGER_Connection *mgr,
693 GNUNET_ResultCallback result_cb,
696 if (NULL == result_cb)
699 struct OperationListItem *op = GNUNET_malloc (sizeof (*op));
700 op->op_id = GNUNET_CLIENT_MANAGER_op_get_next_id (mgr);
701 op->result_cb = result_cb;
703 GNUNET_CONTAINER_DLL_insert_tail (mgr->op_head, mgr->op_tail, op);
705 LOG (GNUNET_ERROR_TYPE_DEBUG,
706 "%p Added operation #%" PRIu64 "\n", mgr, op->op_id);
712 * Remove an operation, and call its result callback (unless it was cancelled).
716 * Client manager connection.
720 * Result of the operation.
722 * Data result of the operation.
726 * Is the operation cancelled?
727 * #GNUNET_NO Not cancelled, result callback is called.
728 * #GNUNET_YES Cancelled, result callback is not called.
730 * @return #GNUNET_YES if the operation was found and removed,
731 * #GNUNET_NO if the operation was not found.
734 op_result (struct GNUNET_CLIENT_MANAGER_Connection *mgr,
735 uint64_t op_id, int64_t result_code,
736 const void *data, uint16_t data_size, uint8_t cancel)
741 struct OperationListItem *op = op_find (mgr, op_id);
744 LOG (GNUNET_ERROR_TYPE_WARNING,
745 "Could not find operation #%" PRIu64 "\n", op_id);
749 GNUNET_CONTAINER_DLL_remove (mgr->op_head, mgr->op_tail, op);
751 if (GNUNET_YES != cancel && NULL != op->result_cb)
752 op->result_cb (op->cls, result_code, data, data_size);
760 * Call the result callback of an operation and remove it.
763 * Client manager connection.
767 * Result of the operation.
769 * Data result of the operation.
773 * @return #GNUNET_YES if the operation was found and removed,
774 * #GNUNET_NO if the operation was not found.
777 GNUNET_CLIENT_MANAGER_op_result (struct GNUNET_CLIENT_MANAGER_Connection *mgr,
778 uint64_t op_id, int64_t result_code,
779 const void *data, uint16_t data_size)
781 LOG (GNUNET_ERROR_TYPE_DEBUG,
782 "%p Received result for operation #%" PRIu64 ": %" PRId64 " (size: %u)\n",
783 mgr, op_id, result_code, data_size);
784 return op_result (mgr, op_id, result_code, data, data_size, GNUNET_NO);
789 * Cancel an operation.
792 * Client manager connection.
796 * @return #GNUNET_YES if the operation was found and removed,
797 * #GNUNET_NO if the operation was not found.
800 GNUNET_CLIENT_MANAGER_op_cancel (struct GNUNET_CLIENT_MANAGER_Connection *mgr,
803 LOG (GNUNET_ERROR_TYPE_DEBUG,
804 "%p Cancelling operation #%" PRIu64 "\n", mgr, op_id);
805 return op_result (mgr, op_id, 0, NULL, 0, GNUNET_YES);