-indentation 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",__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    * User context value.
98    * @see GNUNET_CLIENT_MANAGER_set_user_context()
99    * @see GNUNET_CLIENT_MANAGER_get_user_context()
100    */
101   void *user_ctx;
102
103   /**
104    * Last size given when user context was initialized.
105    * Used for sanity check.
106    */
107   size_t user_ctx_size;
108
109   /**
110    * Task doing exponential back-off trying to reconnect.
111    */
112   GNUNET_SCHEDULER_TaskIdentifier reconnect_task;
113
114   /**
115    * Time for next connect retry.
116    */
117   struct GNUNET_TIME_Relative reconnect_delay;
118
119   /**
120    * Are we currently polling for incoming messages?
121    */
122   uint8_t in_receive;
123
124   /**
125    * #GNUNET_YES if GNUNET_CLIENT_MANAGER_disconnect() was called
126    * and we're transmitting the last messages from the queue.
127    */
128   uint8_t disconnecting;
129 };
130
131
132 /**
133  * Handle received messages from the service.
134  */
135 static void
136 recv_message (void *cls, const struct GNUNET_MessageHeader *msg)
137 {
138   struct GNUNET_CLIENT_MANAGER_Connection *mgr = cls;
139   uint16_t type = 0, size = 0;
140
141   if (NULL != msg)
142   {
143     type = ntohs (msg->type);
144     size = ntohs (msg->size);
145     /* FIXME: decrease reconnect_delay gradually after a successful reconnection */
146   }
147
148   size_t i = 0;
149   while (NULL != mgr->handlers[i].callback)
150   {
151     const struct GNUNET_CLIENT_MANAGER_MessageHandler *mh = &mgr->handlers[i];
152     if ((mh->type == type) || (mh->type == GNUNET_MESSAGE_TYPE_ALL))
153     {
154       if (0 != mh->expected_size
155           && ((GNUNET_NO == mh->is_variable_size && size != mh->expected_size)
156               || (GNUNET_YES == mh->is_variable_size && size < mh->expected_size)))
157       {
158         LOG (GNUNET_ERROR_TYPE_ERROR,
159              "Expected %u bytes for message of type %u, got %u.\n",
160              mh->expected_size, type, size);
161         GNUNET_break_op (0);
162         GNUNET_CLIENT_disconnect (mgr->client);
163         mgr->client = NULL;
164         recv_message (mgr, NULL);
165         break;
166       }
167       mh->callback (mh->callback_cls, mgr, msg);
168     }
169     i++;
170   }
171   if (NULL != mgr->client)
172   {
173     GNUNET_CLIENT_receive (mgr->client, &recv_message, mgr,
174                            GNUNET_TIME_UNIT_FOREVER_REL);
175   }
176 }
177
178
179 /**
180  * Schedule transmission of the next message from our queue.
181  *
182  * @param mgr  Client manager connection.
183  */
184 static void
185 transmit_next (struct GNUNET_CLIENT_MANAGER_Connection *mgr);
186
187
188 /**
189  * Transmit next message to service.
190  *
191  * @param cls   The struct GNUNET_PSYC_Channel.
192  * @param size  Number of bytes available in @a buf.
193  * @param buf   Where to copy the message.
194  *
195  * @return Number of bytes copied to @a buf.
196  */
197 static size_t
198 send_next_message (void *cls, size_t buf_size, void *buf)
199 {
200   LOG (GNUNET_ERROR_TYPE_DEBUG, "send_next_message()\n");
201   struct GNUNET_CLIENT_MANAGER_Connection *mgr = cls;
202
203   if (NULL == buf)
204   {
205     /* disconnected */
206     recv_message (mgr, NULL);
207     return 0;
208   }
209
210   struct MessageQueueItem *mqi = mgr->tmit_head;
211   if (NULL == mqi)
212     return 0;
213
214   uint16_t size = ntohs (mqi->msg->size);
215   mgr->client_tmit = NULL;
216   GNUNET_assert (size <= buf_size);
217   memcpy (buf, mqi->msg, size);
218
219   GNUNET_CONTAINER_DLL_remove (mgr->tmit_head, mgr->tmit_tail, mqi);
220   GNUNET_free (mqi->msg);
221   GNUNET_free (mqi);
222
223   if (NULL != mgr->tmit_head)
224     transmit_next (mgr);
225
226   if (GNUNET_NO == mgr->in_receive)
227   {
228     mgr->in_receive = GNUNET_YES;
229     GNUNET_CLIENT_receive (mgr->client, &recv_message, mgr,
230                            GNUNET_TIME_UNIT_FOREVER_REL);
231   }
232   return size;
233 }
234
235
236 /**
237  * Schedule transmission of the next message from our queue.
238  *
239  * @param mgr  Client manager connection.
240  */
241 static void
242 transmit_next (struct GNUNET_CLIENT_MANAGER_Connection *mgr)
243 {
244   LOG (GNUNET_ERROR_TYPE_DEBUG, "transmit_next()\n");
245   if (NULL != mgr->client_tmit || NULL == mgr->client)
246     return;
247
248   if (NULL == mgr->tmit_head)
249   {
250     if (GNUNET_YES == mgr->disconnecting)
251       GNUNET_CLIENT_MANAGER_disconnect (mgr, GNUNET_NO);
252     return;
253   }
254
255   mgr->client_tmit
256     = GNUNET_CLIENT_notify_transmit_ready (mgr->client,
257                                            ntohs (mgr->tmit_head->msg->size),
258                                            GNUNET_TIME_UNIT_FOREVER_REL,
259                                            GNUNET_NO,
260                                            &send_next_message,
261                                            mgr);
262 }
263
264
265 /**
266  * Try again to connect to the service.
267  *
268  * @param cls Channel handle.
269  * @param tc Scheduler context.
270  */
271 static void
272 reconnect (void *cls, const struct GNUNET_SCHEDULER_TaskContext *tc)
273 {
274   struct GNUNET_CLIENT_MANAGER_Connection *mgr = cls;
275   mgr->reconnect_task = GNUNET_SCHEDULER_NO_TASK;
276
277   LOG (GNUNET_ERROR_TYPE_DEBUG,
278        "Connecting to %s service.\n", mgr->service_name);
279   GNUNET_assert (NULL == mgr->client);
280   mgr->client = GNUNET_CLIENT_connect (mgr->service_name, mgr->cfg);
281   GNUNET_assert (NULL != mgr->client);
282
283   transmit_next (mgr);
284 }
285
286
287 /**
288  * Connect to service.
289  *
290  * @param cfg           Configuration to use.
291  * @param service_name  Service name to connect to.
292  * @param handlers      Message handlers.
293  *
294  * @return Client manager connection handle.
295  */
296 struct GNUNET_CLIENT_MANAGER_Connection *
297 GNUNET_CLIENT_MANAGER_connect (const struct GNUNET_CONFIGURATION_Handle *cfg,
298                                const char *service_name,
299                                const struct
300                                GNUNET_CLIENT_MANAGER_MessageHandler *handlers)
301 {
302   struct GNUNET_CLIENT_MANAGER_Connection *
303     mgr = GNUNET_malloc (sizeof (*mgr));
304   mgr->cfg = cfg;
305   mgr->service_name = service_name;
306   mgr->handlers = handlers;
307   mgr->reconnect_delay = GNUNET_TIME_UNIT_ZERO;
308   mgr->reconnect_task = GNUNET_SCHEDULER_add_now (&reconnect, mgr);
309   return mgr;
310 }
311
312
313 /**
314  * Disconnect from the service.
315  *
316  * @param mgr             Client manager connection.
317  * @param transmit_queue  Transmit pending messages in queue before disconnecting.
318  */
319 void
320 GNUNET_CLIENT_MANAGER_disconnect (struct GNUNET_CLIENT_MANAGER_Connection *mgr,
321                                   int transmit_queue)
322 {
323   if (NULL != mgr->tmit_head)
324   {
325     if (GNUNET_YES == transmit_queue)
326     {
327       mgr->disconnecting = GNUNET_YES;
328       transmit_next (mgr);
329     }
330     else
331     {
332       LOG (GNUNET_ERROR_TYPE_DEBUG,
333            "Disconnecting while there are still messages "
334            "in the transmission queue.\n");
335       GNUNET_CLIENT_MANAGER_drop_queue (mgr);
336     }
337   }
338   if (mgr->reconnect_task != GNUNET_SCHEDULER_NO_TASK)
339   {
340     GNUNET_SCHEDULER_cancel (mgr->reconnect_task);
341     mgr->reconnect_task = GNUNET_SCHEDULER_NO_TASK;
342   }
343   if (NULL != mgr->client_tmit)
344   {
345     GNUNET_CLIENT_notify_transmit_ready_cancel (mgr->client_tmit);
346     mgr->client_tmit = NULL;
347   }
348   if (NULL != mgr->client)
349   {
350     GNUNET_CLIENT_disconnect (mgr->client);
351     mgr->client = NULL;
352   }
353   GNUNET_free (mgr);
354 }
355
356
357 /**
358  * Reschedule connect to the service using exponential back-off.
359  *
360  * @param mgr  Client manager connection.
361  */
362 void
363 GNUNET_CLIENT_MANAGER_reconnect (struct GNUNET_CLIENT_MANAGER_Connection *mgr)
364 {
365   if (GNUNET_SCHEDULER_NO_TASK != mgr->reconnect_task)
366     return;
367
368   if (NULL != mgr->client_tmit)
369   {
370     GNUNET_CLIENT_notify_transmit_ready_cancel (mgr->client_tmit);
371     mgr->client_tmit = NULL;
372   }
373   if (NULL != mgr->client)
374   {
375     GNUNET_CLIENT_disconnect (mgr->client);
376     mgr->client = NULL;
377   }
378   mgr->in_receive = GNUNET_NO;
379   LOG (GNUNET_ERROR_TYPE_DEBUG,
380        "Scheduling task to reconnect to service in %s.\n",
381        GNUNET_STRINGS_relative_time_to_string (mgr->reconnect_delay, GNUNET_YES));
382   mgr->reconnect_task =
383       GNUNET_SCHEDULER_add_delayed (mgr->reconnect_delay, &reconnect, mgr);
384   mgr->reconnect_delay = GNUNET_TIME_STD_BACKOFF (mgr->reconnect_delay);
385 }
386
387
388 /**
389  * Add a message to the end of the transmission queue.
390  *
391  * @param mgr  Client manager connection.
392  * @param msg  Message to transmit.  It is free()'d after transmission.
393  */
394 void
395 GNUNET_CLIENT_MANAGER_transmit (struct GNUNET_CLIENT_MANAGER_Connection *mgr,
396                                 struct GNUNET_MessageHeader *msg)
397 {
398   struct MessageQueueItem *mqi = GNUNET_malloc (sizeof (*mqi));
399   mqi->msg = msg;
400   GNUNET_CONTAINER_DLL_insert_tail (mgr->tmit_head, mgr->tmit_tail, mqi);
401   transmit_next (mgr);
402 }
403
404
405 /**
406  * Add a message to the beginning of the transmission queue.
407  *
408  * @param mgr  Client manager connection.
409  * @param msg  Message to transmit.  It is free()'d after transmission.
410  */
411 void
412 GNUNET_CLIENT_MANAGER_transmit_now (struct GNUNET_CLIENT_MANAGER_Connection *mgr,
413                                     struct GNUNET_MessageHeader *msg)
414 {
415   struct MessageQueueItem *mqi = GNUNET_malloc (sizeof (*mqi));
416   mqi->msg = msg;
417   GNUNET_CONTAINER_DLL_insert (mgr->tmit_head, mgr->tmit_tail, mqi);
418   transmit_next (mgr);
419 }
420
421
422 /**
423  * Drop all queued messages.
424  *
425  * @param mgr  Client manager connection.
426  */
427 void
428 GNUNET_CLIENT_MANAGER_drop_queue (struct GNUNET_CLIENT_MANAGER_Connection *mgr)
429 {
430   struct MessageQueueItem *cur, *next = mgr->tmit_head;
431   while (NULL != next)
432   {
433     cur = next;
434     next = cur->next;
435     GNUNET_free (cur->msg);
436     GNUNET_free (cur);
437   }
438 }
439
440
441 /**
442  * Obtain client connection handle.
443  *
444  * @param mgr  Client manager connection handle.
445  *
446  * @return Client connection handle.
447  */
448 struct GNUNET_CLIENT_Connection *
449 GNUNET_CLIENT_MANAGER_get_client (struct GNUNET_CLIENT_MANAGER_Connection *mgr)
450 {
451   return mgr->client;
452 }
453
454
455 /**
456  * Return user context associated with the given client.
457  * Note: you should probably use the macro (call without the underscore).
458  *
459  * @param mgr  Client manager connection.
460  * @param size Number of bytes in user context struct (for verification only).
461  * @return User context.
462  */
463 void *
464 GNUNET_CLIENT_MANAGER_get_user_context_ (struct GNUNET_CLIENT_MANAGER_Connection *mgr,
465                                          size_t size)
466 {
467   if ((0 == mgr->user_ctx_size) &&
468       (NULL == mgr->user_ctx))
469     return NULL; /* never set */
470   GNUNET_assert (size == mgr->user_ctx_size);
471   return mgr->user_ctx;
472 }
473
474
475 /**
476  * Set user context to be associated with the given client.
477  * Note: you should probably use the macro (call without the underscore).
478  *
479  * @param mgr  Client manager connection.
480  * @param ctx  User context.
481  * @param size Number of bytes in user context struct (for verification only).
482  */
483 void
484 GNUNET_CLIENT_MANAGER_set_user_context_ (struct GNUNET_CLIENT_MANAGER_Connection *mgr,
485                                          void *ctx,
486                                          size_t size)
487 {
488   if (NULL == ctx)
489   {
490     mgr->user_ctx_size = 0;
491     mgr->user_ctx = ctx;
492     return;
493   }
494   mgr->user_ctx_size = size;
495   mgr->user_ctx = ctx;
496 }