use c99
[oweals/gnunet.git] / src / arm / arm_monitor_api.c
1 /*
2      This file is part of GNUnet.
3      Copyright (C) 2009, 2010, 2012, 2013 GNUnet e.V.
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., 51 Franklin Street, Fifth Floor,
18      Boston, MA 02110-1301, USA.
19 */
20
21 /**
22  * @file arm/arm_monitor_api.c
23  * @brief API for monitoring the ARM service
24  * @author Christian Grothoff, LRN
25  */
26 #include "platform.h"
27 #include "gnunet_arm_service.h"
28 #include "gnunet_util_lib.h"
29 #include "gnunet_protocols.h"
30 #include "arm.h"
31
32 #define INIT_TIMEOUT GNUNET_TIME_relative_multiply (GNUNET_TIME_UNIT_SECONDS, 5)
33
34 #define LOG(kind,...) GNUNET_log_from (kind, "arm-monitor-api",__VA_ARGS__)
35
36 /**
37  * Handle for interacting with ARM.
38  */
39 struct GNUNET_ARM_MonitorHandle
40 {
41
42   /**
43    * Our control connection to the ARM service.
44    */
45   struct GNUNET_CLIENT_Connection *monitor;
46
47   /**
48    * The configuration that we are using.
49    */
50   struct GNUNET_CONFIGURATION_Handle *cfg;
51
52   /**
53    * Handle for our current transmission request.
54    */
55   struct GNUNET_CLIENT_TransmitHandle *cth;
56
57   /**
58    * ID of the reconnect task (if any).
59    */
60   struct GNUNET_SCHEDULER_Task * reconnect_task;
61
62   /**
63    * Current delay we use for re-trying to connect to core.
64    */
65   struct GNUNET_TIME_Relative retry_backoff;
66
67   /**
68    * Are we currently disconnected and hence unable to send?
69    */
70   unsigned char currently_down;
71
72   /**
73    * Callback to invoke on status updates.
74    */
75   GNUNET_ARM_ServiceStatusCallback service_status;
76
77   /**
78    * Closure for service_status.
79    */
80   void *cls;
81
82   /**
83    * ID of a task to run if we fail to get a reply to the init message in time.
84    */
85   struct GNUNET_SCHEDULER_Task * init_timeout_task_id;
86 };
87
88
89 static void
90 monitor_notify_handler (void *cls,
91                         const struct GNUNET_MessageHeader *msg);
92
93
94 static int
95 reconnect_arm_monitor (struct GNUNET_ARM_MonitorHandle *h);
96
97
98 /**
99  * Task scheduled to try to re-connect to arm.
100  *
101  * @param cls the 'struct GNUNET_ARM_MonitorHandle'
102  */
103 static void
104 reconnect_arm_monitor_task (void *cls)
105 {
106   struct GNUNET_ARM_MonitorHandle *h = cls;
107
108   h->reconnect_task = NULL;
109   LOG (GNUNET_ERROR_TYPE_DEBUG,
110        "Connecting to ARM service for monitoring after delay\n");
111   reconnect_arm_monitor (h);
112 }
113
114
115 /**
116  * Close down any existing connection to the ARM service and
117  * try re-establishing it later.
118  *
119  * @param h our handle
120  */
121 static void
122 reconnect_arm_monitor_later (struct GNUNET_ARM_MonitorHandle *h)
123 {
124   if (NULL != h->cth)
125   {
126     GNUNET_CLIENT_notify_transmit_ready_cancel (h->cth);
127     h->cth = NULL;
128   }
129
130   if (NULL != h->monitor)
131   {
132     GNUNET_CLIENT_disconnect (h->monitor);
133     h->monitor = NULL;
134   }
135
136   if (NULL != h->init_timeout_task_id)
137   {
138     GNUNET_SCHEDULER_cancel (h->init_timeout_task_id);
139     h->init_timeout_task_id = NULL;
140   }
141
142   GNUNET_assert (NULL == h->reconnect_task);
143   h->reconnect_task =
144       GNUNET_SCHEDULER_add_delayed (h->retry_backoff, &reconnect_arm_monitor_task, h);
145
146   h->retry_backoff = GNUNET_TIME_STD_BACKOFF (h->retry_backoff);
147 }
148
149
150 /**
151  * Init message timed out. Disconnect and try again.
152  *
153  * @param cls arm monitor handle
154  */
155 static void
156 init_timeout_task (void *cls)
157 {
158   struct GNUNET_ARM_MonitorHandle *h = cls;
159
160   LOG (GNUNET_ERROR_TYPE_DEBUG,
161        "Init message timed out\n");
162   h->init_timeout_task_id = NULL;
163   reconnect_arm_monitor_later (h);
164 }
165
166
167 /**
168  * Transmit the monitoring initialization message to the arm service.
169  *
170  * @param cls closure with the 'struct GNUNET_ARM_MonitorHandle'
171  * @param size number of bytes available in buf
172  * @param buf where the callee should write the message
173  * @return number of bytes written to buf
174  */
175 static size_t
176 transmit_monitoring_init_message (void *cls, size_t size, void *buf)
177 {
178   struct GNUNET_ARM_MonitorHandle *h = cls;
179   struct GNUNET_MessageHeader *msg;
180   uint16_t msize;
181
182   GNUNET_assert (NULL == h->reconnect_task);
183   GNUNET_assert (NULL == h->init_timeout_task_id);
184   h->cth = NULL;
185   if (NULL == buf)
186   {
187     LOG (GNUNET_ERROR_TYPE_DEBUG,
188          "Transmission failed, initiating reconnect\n");
189     reconnect_arm_monitor_later (h);
190     return 0;
191   }
192   msize = sizeof (struct GNUNET_MessageHeader);
193   if (size < msize)
194   {
195     LOG (GNUNET_ERROR_TYPE_DEBUG,
196         "Request is too big (%u < %u), not sending it\n", size, msize);
197     h->cth = GNUNET_CLIENT_notify_transmit_ready (h->monitor, msize,
198         GNUNET_TIME_UNIT_FOREVER_REL, GNUNET_NO,
199         transmit_monitoring_init_message, h);
200     return 0;
201   }
202
203   msg = buf;
204   msg->size = htons (msize);
205   msg->type = htons (GNUNET_MESSAGE_TYPE_ARM_MONITOR);
206   LOG (GNUNET_ERROR_TYPE_DEBUG,
207        "Transmitting ARM monitoring init message with %u bytes to arm.\n",
208        (unsigned int) msize);
209
210   h->init_timeout_task_id = GNUNET_SCHEDULER_add_delayed (
211       INIT_TIMEOUT, init_timeout_task, h);
212   GNUNET_CLIENT_receive (h->monitor, &monitor_notify_handler, h,
213                          GNUNET_TIME_UNIT_FOREVER_REL);
214   return msize;
215 }
216
217
218 static int
219 reconnect_arm_monitor (struct GNUNET_ARM_MonitorHandle *h)
220 {
221   GNUNET_assert (NULL == h->monitor);
222   h->monitor = GNUNET_CLIENT_connect ("arm", h->cfg);
223   if (NULL == h->monitor)
224   {
225     LOG (GNUNET_ERROR_TYPE_DEBUG,
226            "arm_api, GNUNET_CLIENT_connect returned NULL\n");
227     if (NULL != h->service_status)
228       h->service_status (h->cls, NULL, GNUNET_ARM_SERVICE_STOPPED);
229     return GNUNET_SYSERR;
230   }
231   LOG (GNUNET_ERROR_TYPE_DEBUG,
232          "arm_api, GNUNET_CLIENT_connect returned non-NULL\n");
233   h->cth = GNUNET_CLIENT_notify_transmit_ready (h->monitor,
234       sizeof (struct GNUNET_MessageHeader), GNUNET_TIME_UNIT_FOREVER_REL,
235       GNUNET_NO, &transmit_monitoring_init_message, h);
236   return GNUNET_OK;
237 }
238
239
240 /**
241  * Setup a context for monitoring ARM, then
242  * start connecting to the ARM service for monitoring using that context.
243  *
244  * @param cfg configuration to use (needed to contact ARM;
245  *        the ARM service may internally use a different
246  *        configuration to determine how to start the service).
247  * @param cont callback to invoke on status updates
248  * @param cont_cls closure
249  * @return context to use for further ARM monitor operations, NULL on error.
250  */
251 struct GNUNET_ARM_MonitorHandle *
252 GNUNET_ARM_monitor (const struct GNUNET_CONFIGURATION_Handle *cfg,
253     GNUNET_ARM_ServiceStatusCallback cont, void *cont_cls)
254 {
255   struct GNUNET_ARM_MonitorHandle *h;
256
257   h = GNUNET_new (struct GNUNET_ARM_MonitorHandle);
258   h->cfg = GNUNET_CONFIGURATION_dup (cfg);
259   h->currently_down = GNUNET_YES;
260   h->reconnect_task = NULL;
261   h->init_timeout_task_id = NULL;
262   h->service_status = cont;
263   h->cls = cont_cls;
264   if (GNUNET_OK != reconnect_arm_monitor (h))
265   {
266     GNUNET_free (h);
267     return NULL;
268   }
269   return h;
270 }
271
272
273 /**
274  * Disconnect from the ARM service (if connected) and destroy the context.
275  *
276  * @param h the handle that was being used
277  */
278 void
279 GNUNET_ARM_monitor_disconnect_and_free (struct GNUNET_ARM_MonitorHandle *h)
280 {
281   LOG (GNUNET_ERROR_TYPE_DEBUG, "Disconnecting from ARM service\n");
282   if (NULL != h->cth)
283   {
284     GNUNET_CLIENT_notify_transmit_ready_cancel (h->cth);
285     h->cth = NULL;
286   }
287   if (NULL != h->init_timeout_task_id)
288   {
289     GNUNET_SCHEDULER_cancel (h->init_timeout_task_id);
290     h->init_timeout_task_id = NULL;
291   }
292   if (NULL != h->monitor)
293   {
294     GNUNET_CLIENT_disconnect (h->monitor);
295     h->monitor = NULL;
296   }
297   if (NULL != h->reconnect_task)
298   {
299     GNUNET_SCHEDULER_cancel (h->reconnect_task);
300     h->reconnect_task = NULL;
301   }
302   GNUNET_CONFIGURATION_destroy (h->cfg);
303   GNUNET_free (h);
304 }
305
306
307 /**
308  * Handler for notification messages received from ARM.
309  *
310  * @param cls our "struct GNUNET_ARM_MonitorHandle"
311  * @param msg the message received from the arm service
312  */
313 static void
314 monitor_notify_handler (void *cls, const struct GNUNET_MessageHeader *msg)
315 {
316   struct GNUNET_ARM_MonitorHandle *h = cls;
317   uint16_t msize;
318   const struct GNUNET_ARM_StatusMessage *res;
319   enum GNUNET_ARM_ServiceStatus status;
320
321   if (NULL == msg)
322   {
323     LOG (GNUNET_ERROR_TYPE_INFO,
324          _("Monitoring client was disconnected from arm service, trying to reconnect.\n"));
325     reconnect_arm_monitor_later (h);
326     return;
327   }
328   msize = ntohs (msg->size);
329   LOG (GNUNET_ERROR_TYPE_DEBUG,
330        "Processing message of type %u and size %u from arm service\n",
331        ntohs (msg->type), msize);
332   switch (ntohs (msg->type))
333   {
334   case GNUNET_MESSAGE_TYPE_ARM_STATUS:
335     if (msize <= sizeof (struct GNUNET_ARM_StatusMessage))
336     {
337       GNUNET_break (0);
338       reconnect_arm_monitor_later (h);
339       return;
340     }
341     if (NULL != h->init_timeout_task_id)
342     {
343       GNUNET_SCHEDULER_cancel (h->init_timeout_task_id);
344       h->init_timeout_task_id = NULL;
345     }
346     res = (const struct GNUNET_ARM_StatusMessage *) msg;
347     LOG (GNUNET_ERROR_TYPE_DEBUG,
348          "Received response from ARM for service `%s': %u\n",
349          (const char *) &res[1], ntohs (msg->type));
350     status = (enum GNUNET_ARM_ServiceStatus) ntohl (res->status);
351     GNUNET_CLIENT_receive (h->monitor, &monitor_notify_handler, h,
352                            GNUNET_TIME_UNIT_FOREVER_REL);
353     if (NULL != h->service_status)
354       h->service_status (h->cls, (const char *) &res[1], status);
355     break;
356   default:
357     reconnect_arm_monitor_later (h);
358     return;
359   }
360 }
361
362
363 /* end of arm_api.c */