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;
247 GNUNET_CLIENT_MANAGER_disconnect (mgr, GNUNET_NO,
249 mgr->disconnect_cls);
254 * Transmit next message to service.
257 * struct GNUNET_CLIENT_MANAGER_Connection
259 * Number of bytes available in @a buf.
261 * Where to copy the message.
263 * @return Number of bytes copied to @a buf.
266 send_next_message (void *cls, size_t buf_size, void *buf)
268 struct GNUNET_CLIENT_MANAGER_Connection *mgr = cls;
270 LOG (GNUNET_ERROR_TYPE_DEBUG,
271 "send_next_message()\n");
275 recv_message (mgr, NULL);
279 struct MessageQueueItem *mqi = mgr->tmit_head;
283 uint16_t size = ntohs (mqi->msg->size);
284 mgr->client_tmit = NULL;
285 GNUNET_assert (size <= buf_size);
286 memcpy (buf, mqi->msg, size);
288 GNUNET_CONTAINER_DLL_remove (mgr->tmit_head,
291 GNUNET_free (mqi->msg);
294 if (NULL != mgr->tmit_head)
298 else if (GNUNET_YES == mgr->is_disconnecting)
300 (void) GNUNET_SCHEDULER_add_now (&schedule_disconnect, mgr);
304 if (GNUNET_NO == mgr->in_receive)
306 mgr->in_receive = GNUNET_YES;
307 GNUNET_CLIENT_receive (mgr->client, &recv_message, mgr,
308 GNUNET_TIME_UNIT_FOREVER_REL);
315 * Schedule transmission of the next message from our queue.
317 * @param mgr Client manager connection.
320 transmit_next (struct GNUNET_CLIENT_MANAGER_Connection *mgr)
322 LOG (GNUNET_ERROR_TYPE_DEBUG, "transmit_next()\n");
323 if (NULL != mgr->client_tmit || NULL == mgr->client)
326 if (NULL == mgr->tmit_head)
328 if (GNUNET_YES == mgr->is_disconnecting)
329 GNUNET_CLIENT_MANAGER_disconnect (mgr, GNUNET_NO,
331 mgr->disconnect_cls);
336 = GNUNET_CLIENT_notify_transmit_ready (mgr->client,
337 GNUNET_SERVER_MAX_MESSAGE_SIZE - 1,
338 GNUNET_TIME_UNIT_FOREVER_REL,
346 * Try again to connect to the service.
352 schedule_reconnect (void *cls)
354 struct GNUNET_CLIENT_MANAGER_Connection *mgr = cls;
356 mgr->reconnect_task = NULL;
357 LOG (GNUNET_ERROR_TYPE_DEBUG,
358 "Connecting to %s service.\n",
360 GNUNET_assert (NULL == mgr->client);
361 mgr->client = GNUNET_CLIENT_connect (mgr->service_name,
363 GNUNET_assert (NULL != mgr->client);
369 * Connect to service.
372 * Configuration to use.
373 * @param service_name
374 * Service name to connect to.
378 * @return Client manager connection handle.
380 struct GNUNET_CLIENT_MANAGER_Connection *
381 GNUNET_CLIENT_MANAGER_connect (const struct GNUNET_CONFIGURATION_Handle *cfg,
382 const char *service_name,
383 const struct GNUNET_CLIENT_MANAGER_MessageHandler *handlers)
385 struct GNUNET_CLIENT_MANAGER_Connection *mgr;
387 mgr = GNUNET_new (struct GNUNET_CLIENT_MANAGER_Connection);
389 mgr->service_name = service_name;
390 mgr->handlers = handlers;
391 mgr->reconnect_delay = GNUNET_TIME_UNIT_ZERO;
392 mgr->reconnect_task = GNUNET_SCHEDULER_add_now (&schedule_reconnect,
399 * Disconnect from the service.
402 * Client manager connection.
403 * @param transmit_queue
404 * Transmit pending messages in queue before disconnecting.
405 * @param disconnect_cb
406 * Function called after disconnected from the service.
408 * Closure for @a disconnect_cb.
411 GNUNET_CLIENT_MANAGER_disconnect (struct GNUNET_CLIENT_MANAGER_Connection *mgr,
413 GNUNET_ContinuationCallback disconnect_cb,
416 LOG (GNUNET_ERROR_TYPE_DEBUG,
417 "Disconnecting (%d)\n",
419 mgr->disconnect_cb = disconnect_cb;
420 mgr->disconnect_cls = cls;
421 if (NULL != mgr->tmit_head)
423 if (GNUNET_YES == transmit_queue)
425 mgr->is_disconnecting = GNUNET_YES;
431 LOG (GNUNET_ERROR_TYPE_DEBUG,
432 "Disconnecting while there are still messages "
433 "in the transmission queue.\n");
434 GNUNET_CLIENT_MANAGER_drop_queue (mgr);
437 if (NULL != mgr->reconnect_task)
439 GNUNET_SCHEDULER_cancel (mgr->reconnect_task);
440 mgr->reconnect_task = NULL;
442 if (NULL != mgr->client_tmit)
444 GNUNET_CLIENT_notify_transmit_ready_cancel (mgr->client_tmit);
445 mgr->client_tmit = NULL;
447 if (NULL != mgr->client)
449 GNUNET_CLIENT_disconnect (mgr->client);
452 if (NULL != mgr->disconnect_cb)
453 mgr->disconnect_cb (mgr->disconnect_cls);
455 LOG (GNUNET_ERROR_TYPE_DEBUG, "Disconnected.\n");
460 * Reschedule connect to the service using exponential back-off.
463 * Client manager connection.
466 GNUNET_CLIENT_MANAGER_reconnect (struct GNUNET_CLIENT_MANAGER_Connection *mgr)
468 if (NULL != mgr->reconnect_task)
471 if (NULL != mgr->client_tmit)
473 GNUNET_CLIENT_notify_transmit_ready_cancel (mgr->client_tmit);
474 mgr->client_tmit = NULL;
476 if (NULL != mgr->client)
478 GNUNET_CLIENT_disconnect (mgr->client);
481 mgr->in_receive = GNUNET_NO;
482 LOG (GNUNET_ERROR_TYPE_DEBUG,
483 "Scheduling task to reconnect to service in %s.\n",
484 GNUNET_STRINGS_relative_time_to_string (mgr->reconnect_delay, GNUNET_YES));
485 mgr->reconnect_task =
486 GNUNET_SCHEDULER_add_delayed (mgr->reconnect_delay,
489 mgr->reconnect_delay = GNUNET_TIME_STD_BACKOFF (mgr->reconnect_delay);
494 * Add a message to the end of the transmission queue.
497 * Client manager connection.
499 * Message to transmit, should be allocated with GNUNET_malloc() or
500 * GNUNET_new(), as it is freed with GNUNET_free() after transmission.
503 GNUNET_CLIENT_MANAGER_transmit (struct GNUNET_CLIENT_MANAGER_Connection *mgr,
504 struct GNUNET_MessageHeader *msg)
506 struct MessageQueueItem *mqi;
508 mqi = GNUNET_new (struct MessageQueueItem);
509 mqi->msg = GNUNET_copy_message (msg);
510 GNUNET_CONTAINER_DLL_insert_tail (mgr->tmit_head,
518 * Add a message to the beginning of the transmission queue.
521 * Client manager connection.
523 * Message to transmit, should be allocated with GNUNET_malloc() or
524 * GNUNET_new(), as it is freed with GNUNET_free() after transmission.
527 GNUNET_CLIENT_MANAGER_transmit_now (struct GNUNET_CLIENT_MANAGER_Connection *mgr,
528 struct GNUNET_MessageHeader *msg)
530 struct MessageQueueItem *mqi;
532 mqi = GNUNET_new (struct MessageQueueItem);
533 mqi->msg = GNUNET_copy_message (msg);
534 GNUNET_CONTAINER_DLL_insert (mgr->tmit_head,
542 * Drop all queued messages.
545 * Client manager connection.
548 GNUNET_CLIENT_MANAGER_drop_queue (struct GNUNET_CLIENT_MANAGER_Connection *mgr)
550 struct MessageQueueItem *cur;
551 struct MessageQueueItem *next;
553 next = mgr->tmit_head;
558 GNUNET_free (cur->msg);
565 * Obtain client connection handle.
568 * Client manager connection.
570 * @return Client connection handle.
572 struct GNUNET_CLIENT_Connection *
573 GNUNET_CLIENT_MANAGER_get_client (struct GNUNET_CLIENT_MANAGER_Connection *mgr)
580 * Return user context associated with the given client.
581 * Note: you should probably use the macro (call without the underscore).
584 * Client manager connection.
586 * Number of bytes in user context struct (for verification only).
588 * @return User context.
591 GNUNET_CLIENT_MANAGER_get_user_context_ (struct GNUNET_CLIENT_MANAGER_Connection *mgr,
594 if ((0 == mgr->user_ctx_size) &&
595 (NULL == mgr->user_ctx))
596 return NULL; /* never set */
597 GNUNET_assert (size == mgr->user_ctx_size);
598 return mgr->user_ctx;
603 * Set user context to be associated with the given client.
604 * Note: you should probably use the macro (call without the underscore).
607 * Client manager connection.
611 * Number of bytes in user context struct (for verification only).
614 GNUNET_CLIENT_MANAGER_set_user_context_ (struct GNUNET_CLIENT_MANAGER_Connection *mgr,
620 mgr->user_ctx_size = 0;
624 mgr->user_ctx_size = size;
630 * Get a unique operation ID to distinguish between asynchronous requests.
633 * Client manager connection.
635 * @return Operation ID to use.
638 GNUNET_CLIENT_MANAGER_op_get_next_id (struct GNUNET_CLIENT_MANAGER_Connection *mgr)
640 return ++mgr->last_op_id;
645 * Find operation by ID.
648 * Client manager connection.
650 * Operation ID to look up.
652 * @return Operation, or NULL if not found.
654 static struct OperationListItem *
655 op_find (struct GNUNET_CLIENT_MANAGER_Connection *mgr,
658 struct OperationListItem *op;
660 for (op = mgr->op_head; NULL != op; op = op->next)
661 if (op->op_id == op_id)
668 * Find operation by ID.
671 * Client manager connection.
673 * Operation ID to look up.
674 * @param[out] result_cb
675 * If an operation was found, its result callback is returned here.
677 * If an operation was found, its closure is returned here.
679 * @return #GNUNET_YES if an operation was found,
680 * #GNUNET_NO if not found.
683 GNUNET_CLIENT_MANAGER_op_find (struct GNUNET_CLIENT_MANAGER_Connection *mgr,
685 GNUNET_ResultCallback *result_cb,
688 struct OperationListItem *op = op_find (mgr, op_id);
691 *result_cb = op->result_cb;
700 * Add a new operation.
703 * Client manager connection.
705 * Function to call with the result of the operation.
707 * Closure for @a result_cb.
709 * @return ID of the new operation.
712 GNUNET_CLIENT_MANAGER_op_add (struct GNUNET_CLIENT_MANAGER_Connection *mgr,
713 GNUNET_ResultCallback result_cb,
716 struct OperationListItem *op;
718 if (NULL == result_cb)
720 op = GNUNET_new (struct OperationListItem);
721 op->op_id = GNUNET_CLIENT_MANAGER_op_get_next_id (mgr);
722 op->result_cb = result_cb;
724 GNUNET_CONTAINER_DLL_insert_tail (mgr->op_head,
727 LOG (GNUNET_ERROR_TYPE_DEBUG,
728 "%p Added operation #%" PRIu64 "\n",
736 * Remove an operation, and call its result callback (unless it was cancelled).
740 * Client manager connection.
744 * Result of the operation.
746 * Data result of the operation.
750 * Is the operation cancelled?
751 * #GNUNET_NO Not cancelled, result callback is called.
752 * #GNUNET_YES Cancelled, result callback is not called.
754 * @return #GNUNET_YES if the operation was found and removed,
755 * #GNUNET_NO if the operation was not found.
758 op_result (struct GNUNET_CLIENT_MANAGER_Connection *mgr,
768 struct OperationListItem *op = op_find (mgr, op_id);
771 LOG (GNUNET_ERROR_TYPE_WARNING,
772 "Could not find operation #%" PRIu64 "\n", op_id);
776 GNUNET_CONTAINER_DLL_remove (mgr->op_head,
780 if ( (GNUNET_YES != cancel) &&
781 (NULL != op->result_cb) )
782 op->result_cb (op->cls,
791 * Call the result callback of an operation and remove it.
794 * Client manager connection.
798 * Result of the operation.
800 * Data result of the operation.
804 * @return #GNUNET_YES if the operation was found and removed,
805 * #GNUNET_NO if the operation was not found.
808 GNUNET_CLIENT_MANAGER_op_result (struct GNUNET_CLIENT_MANAGER_Connection *mgr,
814 LOG (GNUNET_ERROR_TYPE_DEBUG,
815 "%p Received result for operation #%" PRIu64 ": %" PRId64 " (size: %u)\n",
816 mgr, op_id, result_code, data_size);
817 return op_result (mgr, op_id, result_code, data, data_size, GNUNET_NO);
822 * Cancel an operation.
825 * Client manager connection.
829 * @return #GNUNET_YES if the operation was found and removed,
830 * #GNUNET_NO if the operation was not found.
833 GNUNET_CLIENT_MANAGER_op_cancel (struct GNUNET_CLIENT_MANAGER_Connection *mgr,
836 LOG (GNUNET_ERROR_TYPE_DEBUG,
837 "%p Cancelling operation #%" PRIu64 "\n",
840 return op_result (mgr, op_id, 0, NULL, 0, GNUNET_YES);