social: implement enter/leave/messaging; psyc: improvements and fixes
[oweals/gnunet.git] / src / util / client_manager.c
1 /*
2      This file is part of GNUnet.
3      (C) 2013 Christian Grothoff (and other contributing authors)
4
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.
9
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.
14
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.
19 */
20
21 /**
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
26  */
27
28 #include <inttypes.h>
29
30 #include "platform.h"
31 #include "gnunet_util_lib.h"
32
33 #define LOG(kind,...) GNUNET_log_from (kind, "util-client-mgr", __VA_ARGS__)
34
35
36 /**
37  * List of arrays of message handlers.
38  */
39 struct HandlersListItem
40 {
41   struct HandlersListItem *prev;
42   struct HandlersListItem *next;
43
44   /**
45    * NULL-terminated array of handlers.
46    */
47   const struct GNUNET_CLIENT_MANAGER_MessageHandler *handlers;
48 };
49
50
51 struct MessageQueueItem
52 {
53   struct MessageQueueItem *prev;
54   struct MessageQueueItem *next;
55   struct GNUNET_MessageHeader *msg;
56 };
57
58
59 struct GNUNET_CLIENT_MANAGER_Connection
60 {
61   /**
62    * Configuration to use.
63    */
64   const struct GNUNET_CONFIGURATION_Handle *cfg;
65
66   /**
67    * Client connection to service.
68    */
69   struct GNUNET_CLIENT_Connection *client;
70
71   /**
72    * Currently pending transmission request, or NULL for none.
73    */
74   struct GNUNET_CLIENT_TransmitHandle *client_tmit;
75
76   /**
77    * Service name to connect to.
78    */
79   const char *service_name;
80
81   /**
82    * Head of messages to transmit to the service.
83    */
84   struct MessageQueueItem *tmit_head;
85
86   /**
87    * Tail of messages to transmit to the service.
88    */
89   struct MessageQueueItem *tmit_tail;
90
91   /**
92    * Message handlers.
93    */
94   const struct GNUNET_CLIENT_MANAGER_MessageHandler *handlers;
95
96   /**
97    * Disconnect callback.
98    */
99   void (*disconnect_cb)(void *);
100
101   /**
102    * Disconnect closure.
103    */
104   void *disconnect_cls;
105
106   /**
107    * User context value.
108    * @see GNUNET_CLIENT_MANAGER_set_user_context()
109    * @see GNUNET_CLIENT_MANAGER_get_user_context()
110    */
111   void *user_ctx;
112
113   /**
114    * Last size given when user context was initialized.
115    * Used for sanity check.
116    */
117   size_t user_ctx_size;
118
119   /**
120    * Task doing exponential back-off trying to reconnect.
121    */
122   GNUNET_SCHEDULER_TaskIdentifier reconnect_task;
123
124   /**
125    * Time for next connect retry.
126    */
127   struct GNUNET_TIME_Relative reconnect_delay;
128
129   /**
130    * Are we currently polling for incoming messages?
131    */
132   uint8_t in_receive;
133
134   /**
135    * #GNUNET_YES if GNUNET_CLIENT_MANAGER_disconnect() was called
136    * and we're transmitting the last messages from the queue.
137    */
138   uint8_t is_disconnecting;
139 };
140
141
142 /**
143  * Handle received messages from the service.
144  */
145 static void
146 recv_message (void *cls, const struct GNUNET_MessageHeader *msg)
147 {
148   struct GNUNET_CLIENT_MANAGER_Connection *mgr = cls;
149   uint16_t type = 0, size = 0;
150
151   if (NULL != msg)
152   {
153     type = ntohs (msg->type);
154     size = ntohs (msg->size);
155     /* FIXME: decrease reconnect_delay gradually after a successful reconnection */
156   }
157
158   size_t i = 0;
159   while (NULL != mgr->handlers[i].callback)
160   {
161     const struct GNUNET_CLIENT_MANAGER_MessageHandler *mh = &mgr->handlers[i];
162     if ((mh->type == type) || (mh->type == GNUNET_MESSAGE_TYPE_ALL))
163     {
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)))
167       {
168         LOG (GNUNET_ERROR_TYPE_ERROR,
169              "Expected %u bytes for message of type %u, got %u.\n",
170              mh->expected_size, type, size);
171         GNUNET_break_op (0);
172         GNUNET_CLIENT_disconnect (mgr->client);
173         mgr->client = NULL;
174         recv_message (mgr, NULL);
175         break;
176       }
177       mh->callback (mh->callback_cls, mgr, msg);
178     }
179     i++;
180   }
181   if (NULL != mgr->client)
182   {
183     GNUNET_CLIENT_receive (mgr->client, &recv_message, mgr,
184                            GNUNET_TIME_UNIT_FOREVER_REL);
185   }
186 }
187
188
189 /**
190  * Schedule transmission of the next message from our queue.
191  *
192  * @param mgr  Client manager connection.
193  */
194 static void
195 transmit_next (struct GNUNET_CLIENT_MANAGER_Connection *mgr);
196
197
198 static void
199 schedule_disconnect (void *cls, const struct GNUNET_SCHEDULER_TaskContext *tc)
200 {
201   struct GNUNET_CLIENT_MANAGER_Connection *mgr = cls;
202   GNUNET_CLIENT_MANAGER_disconnect (mgr, GNUNET_NO,
203                                     mgr->disconnect_cb, mgr->disconnect_cls);
204 }
205
206
207 /**
208  * Transmit next message to service.
209  *
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.
213  *
214  * @return Number of bytes copied to @a buf.
215  */
216 static size_t
217 send_next_message (void *cls, size_t buf_size, void *buf)
218 {
219   LOG (GNUNET_ERROR_TYPE_DEBUG, "send_next_message()\n");
220   struct GNUNET_CLIENT_MANAGER_Connection *mgr = cls;
221
222   if (NULL == buf)
223   {
224     /* disconnected */
225     recv_message (mgr, NULL);
226     return 0;
227   }
228
229   struct MessageQueueItem *mqi = mgr->tmit_head;
230   if (NULL == mqi)
231     return 0;
232
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);
237
238   GNUNET_CONTAINER_DLL_remove (mgr->tmit_head, mgr->tmit_tail, mqi);
239   GNUNET_free (mqi->msg);
240   GNUNET_free (mqi);
241
242   if (NULL != mgr->tmit_head)
243   {
244     transmit_next (mgr);
245   }
246   else if (GNUNET_YES == mgr->is_disconnecting)
247   {
248     GNUNET_SCHEDULER_add_now (&schedule_disconnect, mgr);
249     return size;
250   }
251
252   if (GNUNET_NO == mgr->in_receive)
253   {
254     mgr->in_receive = GNUNET_YES;
255     GNUNET_CLIENT_receive (mgr->client, &recv_message, mgr,
256                            GNUNET_TIME_UNIT_FOREVER_REL);
257   }
258   return size;
259 }
260
261
262 /**
263  * Schedule transmission of the next message from our queue.
264  *
265  * @param mgr  Client manager connection.
266  */
267 static void
268 transmit_next (struct GNUNET_CLIENT_MANAGER_Connection *mgr)
269 {
270   LOG (GNUNET_ERROR_TYPE_DEBUG, "transmit_next()\n");
271   if (NULL != mgr->client_tmit || NULL == mgr->client)
272     return;
273
274   if (NULL == mgr->tmit_head)
275   {
276     if (GNUNET_YES == mgr->is_disconnecting)
277       GNUNET_CLIENT_MANAGER_disconnect (mgr, GNUNET_NO,
278                                         mgr->disconnect_cb, mgr->disconnect_cls);
279     return;
280   }
281
282   mgr->client_tmit
283     = GNUNET_CLIENT_notify_transmit_ready (mgr->client,
284                                            ntohs (mgr->tmit_head->msg->size),
285                                            GNUNET_TIME_UNIT_FOREVER_REL,
286                                            GNUNET_NO,
287                                            &send_next_message,
288                                            mgr);
289 }
290
291
292 /**
293  * Try again to connect to the service.
294  *
295  * @param cls Channel handle.
296  * @param tc Scheduler context.
297  */
298 static void
299 schedule_reconnect (void *cls, const struct GNUNET_SCHEDULER_TaskContext *tc)
300 {
301   struct GNUNET_CLIENT_MANAGER_Connection *mgr = cls;
302   mgr->reconnect_task = GNUNET_SCHEDULER_NO_TASK;
303
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);
309
310   transmit_next (mgr);
311 }
312
313
314 /**
315  * Connect to service.
316  *
317  * @param cfg           Configuration to use.
318  * @param service_name  Service name to connect to.
319  * @param handlers      Message handlers.
320  *
321  * @return Client manager connection handle.
322  */
323 struct GNUNET_CLIENT_MANAGER_Connection *
324 GNUNET_CLIENT_MANAGER_connect (const struct GNUNET_CONFIGURATION_Handle *cfg,
325                                const char *service_name,
326                                const struct
327                                GNUNET_CLIENT_MANAGER_MessageHandler *handlers)
328 {
329   struct GNUNET_CLIENT_MANAGER_Connection *
330     mgr = GNUNET_malloc (sizeof (*mgr));
331   mgr->cfg = cfg;
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);
336   return mgr;
337 }
338
339
340 /**
341  * Disconnect from the service.
342  *
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.
347  */
348 void
349 GNUNET_CLIENT_MANAGER_disconnect (struct GNUNET_CLIENT_MANAGER_Connection *mgr,
350                                   int transmit_queue,
351                                   GNUNET_ContinuationCallback disconnect_cb,
352                                   void *disconnect_cls)
353 {
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)
358   {
359     if (GNUNET_YES == transmit_queue)
360     {
361       mgr->is_disconnecting = GNUNET_YES;
362       transmit_next (mgr);
363       return;
364     }
365     else
366     {
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);
371     }
372   }
373   if (mgr->reconnect_task != GNUNET_SCHEDULER_NO_TASK)
374   {
375     GNUNET_SCHEDULER_cancel (mgr->reconnect_task);
376     mgr->reconnect_task = GNUNET_SCHEDULER_NO_TASK;
377   }
378   if (NULL != mgr->client_tmit)
379   {
380     GNUNET_CLIENT_notify_transmit_ready_cancel (mgr->client_tmit);
381     mgr->client_tmit = NULL;
382   }
383   if (NULL != mgr->client)
384   {
385     GNUNET_CLIENT_disconnect (mgr->client);
386     mgr->client = NULL;
387   }
388   if (NULL != mgr->disconnect_cb)
389     mgr->disconnect_cb (mgr->disconnect_cls);
390   GNUNET_free (mgr);
391   LOG (GNUNET_ERROR_TYPE_DEBUG, "Disconnected.\n");
392 }
393
394
395 /**
396  * Reschedule connect to the service using exponential back-off.
397  *
398  * @param mgr  Client manager connection.
399  */
400 void
401 GNUNET_CLIENT_MANAGER_reconnect (struct GNUNET_CLIENT_MANAGER_Connection *mgr)
402 {
403   if (GNUNET_SCHEDULER_NO_TASK != mgr->reconnect_task)
404     return;
405
406   if (NULL != mgr->client_tmit)
407   {
408     GNUNET_CLIENT_notify_transmit_ready_cancel (mgr->client_tmit);
409     mgr->client_tmit = NULL;
410   }
411   if (NULL != mgr->client)
412   {
413     GNUNET_CLIENT_disconnect (mgr->client);
414     mgr->client = NULL;
415   }
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);
423 }
424
425
426 /**
427  * Add a message to the end of the transmission queue.
428  *
429  * @param mgr  Client manager connection.
430  * @param msg  Message to transmit.  It is free()'d after transmission.
431  */
432 void
433 GNUNET_CLIENT_MANAGER_transmit (struct GNUNET_CLIENT_MANAGER_Connection *mgr,
434                                 struct GNUNET_MessageHeader *msg)
435 {
436   struct MessageQueueItem *mqi = GNUNET_malloc (sizeof (*mqi));
437   mqi->msg = msg;
438   GNUNET_CONTAINER_DLL_insert_tail (mgr->tmit_head, mgr->tmit_tail, mqi);
439   transmit_next (mgr);
440 }
441
442
443 /**
444  * Add a message to the beginning of the transmission queue.
445  *
446  * @param mgr  Client manager connection.
447  * @param msg  Message to transmit.  It is free()'d after transmission.
448  */
449 void
450 GNUNET_CLIENT_MANAGER_transmit_now (struct GNUNET_CLIENT_MANAGER_Connection *mgr,
451                                     struct GNUNET_MessageHeader *msg)
452 {
453   struct MessageQueueItem *mqi = GNUNET_malloc (sizeof (*mqi));
454   mqi->msg = msg;
455   GNUNET_CONTAINER_DLL_insert (mgr->tmit_head, mgr->tmit_tail, mqi);
456   transmit_next (mgr);
457 }
458
459
460 /**
461  * Drop all queued messages.
462  *
463  * @param mgr  Client manager connection.
464  */
465 void
466 GNUNET_CLIENT_MANAGER_drop_queue (struct GNUNET_CLIENT_MANAGER_Connection *mgr)
467 {
468   struct MessageQueueItem *cur, *next = mgr->tmit_head;
469   while (NULL != next)
470   {
471     cur = next;
472     next = cur->next;
473     GNUNET_free (cur->msg);
474     GNUNET_free (cur);
475   }
476 }
477
478
479 /**
480  * Obtain client connection handle.
481  *
482  * @param mgr  Client manager connection handle.
483  *
484  * @return Client connection handle.
485  */
486 struct GNUNET_CLIENT_Connection *
487 GNUNET_CLIENT_MANAGER_get_client (struct GNUNET_CLIENT_MANAGER_Connection *mgr)
488 {
489   return mgr->client;
490 }
491
492
493 /**
494  * Return user context associated with the given client.
495  * Note: you should probably use the macro (call without the underscore).
496  *
497  * @param mgr  Client manager connection.
498  * @param size Number of bytes in user context struct (for verification only).
499  * @return User context.
500  */
501 void *
502 GNUNET_CLIENT_MANAGER_get_user_context_ (struct GNUNET_CLIENT_MANAGER_Connection *mgr,
503                                          size_t size)
504 {
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;
510 }
511
512
513 /**
514  * Set user context to be associated with the given client.
515  * Note: you should probably use the macro (call without the underscore).
516  *
517  * @param mgr  Client manager connection.
518  * @param ctx  User context.
519  * @param size Number of bytes in user context struct (for verification only).
520  */
521 void
522 GNUNET_CLIENT_MANAGER_set_user_context_ (struct GNUNET_CLIENT_MANAGER_Connection *mgr,
523                                          void *ctx,
524                                          size_t size)
525 {
526   if (NULL == ctx)
527   {
528     mgr->user_ctx_size = 0;
529     mgr->user_ctx = ctx;
530     return;
531   }
532   mgr->user_ctx_size = size;
533   mgr->user_ctx = ctx;
534 }