2 This file is part of GNUnet.
3 Copyright (C) 2013 GNUnet e.V.
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)
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.
346 schedule_reconnect (void *cls)
348 struct GNUNET_CLIENT_MANAGER_Connection *mgr = cls;
349 mgr->reconnect_task = NULL;
351 LOG (GNUNET_ERROR_TYPE_DEBUG,
352 "Connecting to %s service.\n", mgr->service_name);
353 GNUNET_assert (NULL == mgr->client);
354 mgr->client = GNUNET_CLIENT_connect (mgr->service_name, mgr->cfg);
355 GNUNET_assert (NULL != mgr->client);
362 * Connect to service.
365 * Configuration to use.
366 * @param service_name
367 * Service name to connect to.
371 * @return Client manager connection handle.
373 struct GNUNET_CLIENT_MANAGER_Connection *
374 GNUNET_CLIENT_MANAGER_connect (const struct GNUNET_CONFIGURATION_Handle *cfg,
375 const char *service_name,
377 GNUNET_CLIENT_MANAGER_MessageHandler *handlers)
379 struct GNUNET_CLIENT_MANAGER_Connection *
380 mgr = GNUNET_malloc (sizeof (*mgr));
382 mgr->service_name = service_name;
383 mgr->handlers = handlers;
384 mgr->reconnect_delay = GNUNET_TIME_UNIT_ZERO;
385 mgr->reconnect_task = GNUNET_SCHEDULER_add_now (&schedule_reconnect, mgr);
391 * Disconnect from the service.
394 * Client manager connection.
395 * @param transmit_queue
396 * Transmit pending messages in queue before disconnecting.
397 * @param disconnect_cb
398 * Function called after disconnected from the service.
400 * Closure for @a disconnect_cb.
403 GNUNET_CLIENT_MANAGER_disconnect (struct GNUNET_CLIENT_MANAGER_Connection *mgr,
405 GNUNET_ContinuationCallback disconnect_cb,
408 LOG (GNUNET_ERROR_TYPE_DEBUG, "Disconnecting (%d)\n", transmit_queue);
409 mgr->disconnect_cb = disconnect_cb;
410 mgr->disconnect_cls = cls;
411 if (NULL != mgr->tmit_head)
413 if (GNUNET_YES == transmit_queue)
415 mgr->is_disconnecting = GNUNET_YES;
421 LOG (GNUNET_ERROR_TYPE_DEBUG,
422 "Disconnecting while there are still messages "
423 "in the transmission queue.\n");
424 GNUNET_CLIENT_MANAGER_drop_queue (mgr);
427 if (mgr->reconnect_task != NULL)
429 GNUNET_SCHEDULER_cancel (mgr->reconnect_task);
430 mgr->reconnect_task = NULL;
432 if (NULL != mgr->client_tmit)
434 GNUNET_CLIENT_notify_transmit_ready_cancel (mgr->client_tmit);
435 mgr->client_tmit = NULL;
437 if (NULL != mgr->client)
439 GNUNET_CLIENT_disconnect (mgr->client);
442 if (NULL != mgr->disconnect_cb)
443 mgr->disconnect_cb (mgr->disconnect_cls);
445 LOG (GNUNET_ERROR_TYPE_DEBUG, "Disconnected.\n");
450 * Reschedule connect to the service using exponential back-off.
453 * Client manager connection.
456 GNUNET_CLIENT_MANAGER_reconnect (struct GNUNET_CLIENT_MANAGER_Connection *mgr)
458 if (NULL != mgr->reconnect_task)
461 if (NULL != mgr->client_tmit)
463 GNUNET_CLIENT_notify_transmit_ready_cancel (mgr->client_tmit);
464 mgr->client_tmit = NULL;
466 if (NULL != mgr->client)
468 GNUNET_CLIENT_disconnect (mgr->client);
471 mgr->in_receive = GNUNET_NO;
472 LOG (GNUNET_ERROR_TYPE_DEBUG,
473 "Scheduling task to reconnect to service in %s.\n",
474 GNUNET_STRINGS_relative_time_to_string (mgr->reconnect_delay, GNUNET_YES));
475 mgr->reconnect_task =
476 GNUNET_SCHEDULER_add_delayed (mgr->reconnect_delay, &schedule_reconnect, mgr);
477 mgr->reconnect_delay = GNUNET_TIME_STD_BACKOFF (mgr->reconnect_delay);
482 * Add a message to the end of the transmission queue.
485 * Client manager connection.
487 * Message to transmit, should be allocated with GNUNET_malloc() or
488 * GNUNET_new(), as it is freed with GNUNET_free() after transmission.
491 GNUNET_CLIENT_MANAGER_transmit (struct GNUNET_CLIENT_MANAGER_Connection *mgr,
492 struct GNUNET_MessageHeader *msg)
494 struct MessageQueueItem *mqi = GNUNET_malloc (sizeof (*mqi));
495 mqi->msg = GNUNET_copy_message (msg);
496 GNUNET_CONTAINER_DLL_insert_tail (mgr->tmit_head, mgr->tmit_tail, mqi);
502 * Add a message to the beginning of the transmission queue.
505 * Client manager connection.
507 * Message to transmit, should be allocated with GNUNET_malloc() or
508 * GNUNET_new(), as it is freed with GNUNET_free() after transmission.
511 GNUNET_CLIENT_MANAGER_transmit_now (struct GNUNET_CLIENT_MANAGER_Connection *mgr,
512 struct GNUNET_MessageHeader *msg)
514 struct MessageQueueItem *mqi = GNUNET_malloc (sizeof (*mqi));
515 mqi->msg = GNUNET_copy_message (msg);
516 GNUNET_CONTAINER_DLL_insert (mgr->tmit_head, mgr->tmit_tail, mqi);
522 * Drop all queued messages.
525 * Client manager connection.
528 GNUNET_CLIENT_MANAGER_drop_queue (struct GNUNET_CLIENT_MANAGER_Connection *mgr)
530 struct MessageQueueItem *cur, *next = mgr->tmit_head;
535 GNUNET_free (cur->msg);
542 * Obtain client connection handle.
545 * Client manager connection.
547 * @return Client connection handle.
549 struct GNUNET_CLIENT_Connection *
550 GNUNET_CLIENT_MANAGER_get_client (struct GNUNET_CLIENT_MANAGER_Connection *mgr)
557 * Return user context associated with the given client.
558 * Note: you should probably use the macro (call without the underscore).
561 * Client manager connection.
563 * Number of bytes in user context struct (for verification only).
565 * @return User context.
568 GNUNET_CLIENT_MANAGER_get_user_context_ (struct GNUNET_CLIENT_MANAGER_Connection *mgr,
571 if ((0 == mgr->user_ctx_size) &&
572 (NULL == mgr->user_ctx))
573 return NULL; /* never set */
574 GNUNET_assert (size == mgr->user_ctx_size);
575 return mgr->user_ctx;
580 * Set user context to be associated with the given client.
581 * Note: you should probably use the macro (call without the underscore).
584 * Client manager connection.
588 * Number of bytes in user context struct (for verification only).
591 GNUNET_CLIENT_MANAGER_set_user_context_ (struct GNUNET_CLIENT_MANAGER_Connection *mgr,
597 mgr->user_ctx_size = 0;
601 mgr->user_ctx_size = size;
607 * Get a unique operation ID to distinguish between asynchronous requests.
610 * Client manager connection.
612 * @return Operation ID to use.
615 GNUNET_CLIENT_MANAGER_op_get_next_id (struct GNUNET_CLIENT_MANAGER_Connection *mgr)
617 return ++mgr->last_op_id;
622 * Find operation by ID.
625 * Client manager connection.
627 * Operation ID to look up.
629 * @return Operation, or NULL if not found.
631 static struct OperationListItem *
632 op_find (struct GNUNET_CLIENT_MANAGER_Connection *mgr, uint64_t op_id)
634 struct OperationListItem *op = mgr->op_head;
637 if (op->op_id == op_id)
646 * Find operation by ID.
649 * Client manager connection.
651 * Operation ID to look up.
652 * @param[out] result_cb
653 * If an operation was found, its result callback is returned here.
655 * If an operation was found, its closure is returned here.
657 * @return #GNUNET_YES if an operation was found,
658 * #GNUNET_NO if not found.
661 GNUNET_CLIENT_MANAGER_op_find (struct GNUNET_CLIENT_MANAGER_Connection *mgr,
663 GNUNET_ResultCallback *result_cb,
666 struct OperationListItem *op = op_find (mgr, op_id);
669 *result_cb = op->result_cb;
678 * Add a new operation.
681 * Client manager connection.
683 * Function to call with the result of the operation.
685 * Closure for @a result_cb.
687 * @return ID of the new operation.
690 GNUNET_CLIENT_MANAGER_op_add (struct GNUNET_CLIENT_MANAGER_Connection *mgr,
691 GNUNET_ResultCallback result_cb,
694 if (NULL == result_cb)
697 struct OperationListItem *op = GNUNET_malloc (sizeof (*op));
698 op->op_id = GNUNET_CLIENT_MANAGER_op_get_next_id (mgr);
699 op->result_cb = result_cb;
701 GNUNET_CONTAINER_DLL_insert_tail (mgr->op_head, mgr->op_tail, op);
703 LOG (GNUNET_ERROR_TYPE_DEBUG,
704 "%p Added operation #%" PRIu64 "\n", mgr, op->op_id);
710 * Remove an operation, and call its result callback (unless it was cancelled).
714 * Client manager connection.
718 * Result of the operation.
720 * Data result of the operation.
724 * Is the operation cancelled?
725 * #GNUNET_NO Not cancelled, result callback is called.
726 * #GNUNET_YES Cancelled, result callback is not called.
728 * @return #GNUNET_YES if the operation was found and removed,
729 * #GNUNET_NO if the operation was not found.
732 op_result (struct GNUNET_CLIENT_MANAGER_Connection *mgr,
733 uint64_t op_id, int64_t result_code,
734 const void *data, uint16_t data_size, uint8_t cancel)
739 struct OperationListItem *op = op_find (mgr, op_id);
742 LOG (GNUNET_ERROR_TYPE_WARNING,
743 "Could not find operation #%" PRIu64 "\n", op_id);
747 GNUNET_CONTAINER_DLL_remove (mgr->op_head, mgr->op_tail, op);
749 if (GNUNET_YES != cancel && NULL != op->result_cb)
750 op->result_cb (op->cls, result_code, data, data_size);
758 * Call the result callback of an operation and remove it.
761 * Client manager connection.
765 * Result of the operation.
767 * Data result of the operation.
771 * @return #GNUNET_YES if the operation was found and removed,
772 * #GNUNET_NO if the operation was not found.
775 GNUNET_CLIENT_MANAGER_op_result (struct GNUNET_CLIENT_MANAGER_Connection *mgr,
776 uint64_t op_id, int64_t result_code,
777 const void *data, uint16_t data_size)
779 LOG (GNUNET_ERROR_TYPE_DEBUG,
780 "%p Received result for operation #%" PRIu64 ": %" PRId64 " (size: %u)\n",
781 mgr, op_id, result_code, data_size);
782 return op_result (mgr, op_id, result_code, data, data_size, GNUNET_NO);
787 * Cancel an operation.
790 * Client manager connection.
794 * @return #GNUNET_YES if the operation was found and removed,
795 * #GNUNET_NO if the operation was not found.
798 GNUNET_CLIENT_MANAGER_op_cancel (struct GNUNET_CLIENT_MANAGER_Connection *mgr,
801 LOG (GNUNET_ERROR_TYPE_DEBUG,
802 "%p Cancelling operation #%" PRIu64 "\n", mgr, op_id);
803 return op_result (mgr, op_id, 0, NULL, 0, GNUNET_YES);