2 This file is part of GNUnet.
3 (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., 59 Temple Place - Suite 330,
18 Boston, MA 02111-1307, 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__)
37 * List of arrays of message handlers.
39 struct HandlersListItem
41 struct HandlersListItem *prev;
42 struct HandlersListItem *next;
45 * NULL-terminated array of handlers.
47 const struct GNUNET_CLIENT_MANAGER_MessageHandler *handlers;
51 struct MessageQueueItem
53 struct MessageQueueItem *prev;
54 struct MessageQueueItem *next;
55 struct GNUNET_MessageHeader *msg;
59 struct GNUNET_CLIENT_MANAGER_Connection
62 * Configuration to use.
64 const struct GNUNET_CONFIGURATION_Handle *cfg;
67 * Client connection to service.
69 struct GNUNET_CLIENT_Connection *client;
72 * Currently pending transmission request, or NULL for none.
74 struct GNUNET_CLIENT_TransmitHandle *client_tmit;
77 * Service name to connect to.
79 const char *service_name;
82 * Head of messages to transmit to the service.
84 struct MessageQueueItem *tmit_head;
87 * Tail of messages to transmit to the service.
89 struct MessageQueueItem *tmit_tail;
94 const struct GNUNET_CLIENT_MANAGER_MessageHandler *handlers;
97 * Disconnect callback.
99 void (*disconnect_cb)(void *);
102 * Disconnect closure.
104 void *disconnect_cls;
107 * User context value.
108 * @see GNUNET_CLIENT_MANAGER_set_user_context()
109 * @see GNUNET_CLIENT_MANAGER_get_user_context()
114 * Last size given when user context was initialized.
115 * Used for sanity check.
117 size_t user_ctx_size;
120 * Task doing exponential back-off trying to reconnect.
122 GNUNET_SCHEDULER_TaskIdentifier reconnect_task;
125 * Time for next connect retry.
127 struct GNUNET_TIME_Relative reconnect_delay;
130 * Are we currently polling for incoming messages?
135 * #GNUNET_YES if GNUNET_CLIENT_MANAGER_disconnect() was called
136 * and we're transmitting the last messages from the queue.
138 uint8_t is_disconnecting;
143 * Handle received messages from the service.
146 recv_message (void *cls, const struct GNUNET_MessageHeader *msg)
148 struct GNUNET_CLIENT_MANAGER_Connection *mgr = cls;
149 uint16_t type = 0, size = 0;
153 type = ntohs (msg->type);
154 size = ntohs (msg->size);
155 /* FIXME: decrease reconnect_delay gradually after a successful reconnection */
159 while (NULL != mgr->handlers[i].callback)
161 const struct GNUNET_CLIENT_MANAGER_MessageHandler *mh = &mgr->handlers[i];
162 if ((mh->type == type) || (mh->type == GNUNET_MESSAGE_TYPE_ALL))
164 if (0 != mh->expected_size
165 && ((GNUNET_NO == mh->is_variable_size && size != mh->expected_size)
166 || (GNUNET_YES == mh->is_variable_size && size < mh->expected_size)))
168 LOG (GNUNET_ERROR_TYPE_ERROR,
169 "Expected %u bytes for message of type %u, got %u.\n",
170 mh->expected_size, type, size);
172 GNUNET_CLIENT_disconnect (mgr->client);
174 recv_message (mgr, NULL);
177 mh->callback (mh->callback_cls, mgr, msg);
181 if (NULL != mgr->client)
183 GNUNET_CLIENT_receive (mgr->client, &recv_message, mgr,
184 GNUNET_TIME_UNIT_FOREVER_REL);
190 * Schedule transmission of the next message from our queue.
192 * @param mgr Client manager connection.
195 transmit_next (struct GNUNET_CLIENT_MANAGER_Connection *mgr);
199 schedule_disconnect (void *cls, const struct GNUNET_SCHEDULER_TaskContext *tc)
201 struct GNUNET_CLIENT_MANAGER_Connection *mgr = cls;
202 GNUNET_CLIENT_MANAGER_disconnect (mgr, GNUNET_NO,
203 mgr->disconnect_cb, mgr->disconnect_cls);
208 * Transmit next message to service.
210 * @param cls The struct GNUNET_PSYC_Channel.
211 * @param size Number of bytes available in @a buf.
212 * @param buf Where to copy the message.
214 * @return Number of bytes copied to @a buf.
217 send_next_message (void *cls, size_t buf_size, void *buf)
219 LOG (GNUNET_ERROR_TYPE_DEBUG, "send_next_message()\n");
220 struct GNUNET_CLIENT_MANAGER_Connection *mgr = cls;
225 recv_message (mgr, NULL);
229 struct MessageQueueItem *mqi = mgr->tmit_head;
233 uint16_t size = ntohs (mqi->msg->size);
234 mgr->client_tmit = NULL;
235 GNUNET_assert (size <= buf_size);
236 memcpy (buf, mqi->msg, size);
238 GNUNET_CONTAINER_DLL_remove (mgr->tmit_head, mgr->tmit_tail, mqi);
239 GNUNET_free (mqi->msg);
242 if (NULL != mgr->tmit_head)
246 else if (GNUNET_YES == mgr->is_disconnecting)
248 GNUNET_SCHEDULER_add_now (&schedule_disconnect, mgr);
252 if (GNUNET_NO == mgr->in_receive)
254 mgr->in_receive = GNUNET_YES;
255 GNUNET_CLIENT_receive (mgr->client, &recv_message, mgr,
256 GNUNET_TIME_UNIT_FOREVER_REL);
263 * Schedule transmission of the next message from our queue.
265 * @param mgr Client manager connection.
268 transmit_next (struct GNUNET_CLIENT_MANAGER_Connection *mgr)
270 LOG (GNUNET_ERROR_TYPE_DEBUG, "transmit_next()\n");
271 if (NULL != mgr->client_tmit || NULL == mgr->client)
274 if (NULL == mgr->tmit_head)
276 if (GNUNET_YES == mgr->is_disconnecting)
277 GNUNET_CLIENT_MANAGER_disconnect (mgr, GNUNET_NO,
278 mgr->disconnect_cb, mgr->disconnect_cls);
283 = GNUNET_CLIENT_notify_transmit_ready (mgr->client,
284 ntohs (mgr->tmit_head->msg->size),
285 GNUNET_TIME_UNIT_FOREVER_REL,
293 * Try again to connect to the service.
295 * @param cls Channel handle.
296 * @param tc Scheduler context.
299 schedule_reconnect (void *cls, const struct GNUNET_SCHEDULER_TaskContext *tc)
301 struct GNUNET_CLIENT_MANAGER_Connection *mgr = cls;
302 mgr->reconnect_task = GNUNET_SCHEDULER_NO_TASK;
304 LOG (GNUNET_ERROR_TYPE_DEBUG,
305 "Connecting to %s service.\n", mgr->service_name);
306 GNUNET_assert (NULL == mgr->client);
307 mgr->client = GNUNET_CLIENT_connect (mgr->service_name, mgr->cfg);
308 GNUNET_assert (NULL != mgr->client);
315 * Connect to service.
317 * @param cfg Configuration to use.
318 * @param service_name Service name to connect to.
319 * @param handlers Message handlers.
321 * @return Client manager connection handle.
323 struct GNUNET_CLIENT_MANAGER_Connection *
324 GNUNET_CLIENT_MANAGER_connect (const struct GNUNET_CONFIGURATION_Handle *cfg,
325 const char *service_name,
327 GNUNET_CLIENT_MANAGER_MessageHandler *handlers)
329 struct GNUNET_CLIENT_MANAGER_Connection *
330 mgr = GNUNET_malloc (sizeof (*mgr));
332 mgr->service_name = service_name;
333 mgr->handlers = handlers;
334 mgr->reconnect_delay = GNUNET_TIME_UNIT_ZERO;
335 mgr->reconnect_task = GNUNET_SCHEDULER_add_now (&schedule_reconnect, mgr);
341 * Disconnect from the service.
343 * @param mgr Client manager connection.
344 * @param transmit_queue Transmit pending messages in queue before disconnecting.
345 * @param disconnect_cb Function called after disconnected from the service.
346 * @param disconnect_cls Closure for @a disconnect_cb.
349 GNUNET_CLIENT_MANAGER_disconnect (struct GNUNET_CLIENT_MANAGER_Connection *mgr,
351 GNUNET_ContinuationCallback disconnect_cb,
352 void *disconnect_cls)
354 LOG (GNUNET_ERROR_TYPE_DEBUG, "Disconnecting (%d)\n", transmit_queue);
355 mgr->disconnect_cb = disconnect_cb;
356 mgr->disconnect_cls = disconnect_cls;
357 if (NULL != mgr->tmit_head)
359 if (GNUNET_YES == transmit_queue)
361 mgr->is_disconnecting = GNUNET_YES;
367 LOG (GNUNET_ERROR_TYPE_DEBUG,
368 "Disconnecting while there are still messages "
369 "in the transmission queue.\n");
370 GNUNET_CLIENT_MANAGER_drop_queue (mgr);
373 if (mgr->reconnect_task != GNUNET_SCHEDULER_NO_TASK)
375 GNUNET_SCHEDULER_cancel (mgr->reconnect_task);
376 mgr->reconnect_task = GNUNET_SCHEDULER_NO_TASK;
378 if (NULL != mgr->client_tmit)
380 GNUNET_CLIENT_notify_transmit_ready_cancel (mgr->client_tmit);
381 mgr->client_tmit = NULL;
383 if (NULL != mgr->client)
385 GNUNET_CLIENT_disconnect (mgr->client);
388 if (NULL != mgr->disconnect_cb)
389 mgr->disconnect_cb (mgr->disconnect_cls);
391 LOG (GNUNET_ERROR_TYPE_DEBUG, "Disconnected.\n");
396 * Reschedule connect to the service using exponential back-off.
398 * @param mgr Client manager connection.
401 GNUNET_CLIENT_MANAGER_reconnect (struct GNUNET_CLIENT_MANAGER_Connection *mgr)
403 if (GNUNET_SCHEDULER_NO_TASK != mgr->reconnect_task)
406 if (NULL != mgr->client_tmit)
408 GNUNET_CLIENT_notify_transmit_ready_cancel (mgr->client_tmit);
409 mgr->client_tmit = NULL;
411 if (NULL != mgr->client)
413 GNUNET_CLIENT_disconnect (mgr->client);
416 mgr->in_receive = GNUNET_NO;
417 LOG (GNUNET_ERROR_TYPE_DEBUG,
418 "Scheduling task to reconnect to service in %s.\n",
419 GNUNET_STRINGS_relative_time_to_string (mgr->reconnect_delay, GNUNET_YES));
420 mgr->reconnect_task =
421 GNUNET_SCHEDULER_add_delayed (mgr->reconnect_delay, &schedule_reconnect, mgr);
422 mgr->reconnect_delay = GNUNET_TIME_STD_BACKOFF (mgr->reconnect_delay);
427 * Add a message to the end of the transmission queue.
429 * @param mgr Client manager connection.
430 * @param msg Message to transmit. It is free()'d after transmission.
433 GNUNET_CLIENT_MANAGER_transmit (struct GNUNET_CLIENT_MANAGER_Connection *mgr,
434 struct GNUNET_MessageHeader *msg)
436 struct MessageQueueItem *mqi = GNUNET_malloc (sizeof (*mqi));
438 GNUNET_CONTAINER_DLL_insert_tail (mgr->tmit_head, mgr->tmit_tail, mqi);
444 * Add a message to the beginning of the transmission queue.
446 * @param mgr Client manager connection.
447 * @param msg Message to transmit. It is free()'d after transmission.
450 GNUNET_CLIENT_MANAGER_transmit_now (struct GNUNET_CLIENT_MANAGER_Connection *mgr,
451 struct GNUNET_MessageHeader *msg)
453 struct MessageQueueItem *mqi = GNUNET_malloc (sizeof (*mqi));
455 GNUNET_CONTAINER_DLL_insert (mgr->tmit_head, mgr->tmit_tail, mqi);
461 * Drop all queued messages.
463 * @param mgr Client manager connection.
466 GNUNET_CLIENT_MANAGER_drop_queue (struct GNUNET_CLIENT_MANAGER_Connection *mgr)
468 struct MessageQueueItem *cur, *next = mgr->tmit_head;
473 GNUNET_free (cur->msg);
480 * Obtain client connection handle.
482 * @param mgr Client manager connection handle.
484 * @return Client connection handle.
486 struct GNUNET_CLIENT_Connection *
487 GNUNET_CLIENT_MANAGER_get_client (struct GNUNET_CLIENT_MANAGER_Connection *mgr)
494 * Return user context associated with the given client.
495 * Note: you should probably use the macro (call without the underscore).
497 * @param mgr Client manager connection.
498 * @param size Number of bytes in user context struct (for verification only).
499 * @return User context.
502 GNUNET_CLIENT_MANAGER_get_user_context_ (struct GNUNET_CLIENT_MANAGER_Connection *mgr,
505 if ((0 == mgr->user_ctx_size) &&
506 (NULL == mgr->user_ctx))
507 return NULL; /* never set */
508 GNUNET_assert (size == mgr->user_ctx_size);
509 return mgr->user_ctx;
514 * Set user context to be associated with the given client.
515 * Note: you should probably use the macro (call without the underscore).
517 * @param mgr Client manager connection.
518 * @param ctx User context.
519 * @param size Number of bytes in user context struct (for verification only).
522 GNUNET_CLIENT_MANAGER_set_user_context_ (struct GNUNET_CLIENT_MANAGER_Connection *mgr,
528 mgr->user_ctx_size = 0;
532 mgr->user_ctx_size = size;