- warn on missing cases
[oweals/gnunet.git] / src / arm / arm_api.c
1 /*
2      This file is part of GNUnet.
3      (C) 2009, 2010, 2012, 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 arm/arm_api.c
23  * @brief API for accessing 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 LOG(kind,...) GNUNET_log_from (kind, "arm-api",__VA_ARGS__)
33
34 /**
35  * Handle for interacting with ARM.
36  */
37 struct GNUNET_ARM_Handle
38 {
39   /**
40    * Our control connection to the ARM service.
41    */
42   struct GNUNET_CLIENT_Connection *client;
43
44   /**
45    * The configuration that we are using.
46    */
47   struct GNUNET_CONFIGURATION_Handle *cfg;
48
49   /**
50    * Handle for our current transmission request.
51    */
52   struct GNUNET_CLIENT_TransmitHandle *cth;
53
54   /**
55    * Head of doubly-linked list of pending requests.
56    */
57   struct ARMControlMessage *control_pending_head;
58
59   /**
60    * Tail of doubly-linked list of pending requests.
61    */
62   struct ARMControlMessage *control_pending_tail;
63
64   /**
65    * Head of doubly-linked list of sent requests.
66    */
67   struct ARMControlMessage *control_sent_head;
68
69   /**
70    * Tail of doubly-linked list of sent requests.
71    */
72   struct ARMControlMessage *control_sent_tail;
73
74   /**
75    * ID of the reconnect task (if any).
76    */
77   GNUNET_SCHEDULER_TaskIdentifier reconnect_task;
78
79   /**
80    * Current delay we use for re-trying to connect to core.
81    */
82   struct GNUNET_TIME_Relative retry_backoff;
83
84   /**
85    * Callback to invoke on connection/disconnection.
86    */
87   GNUNET_ARM_ConnectionStatusCallback conn_status;
88
89   /**
90    * Closure for conn_status.
91    */
92   void *conn_status_cls;
93
94   /**
95    * Counter for request identifiers
96    */
97   uint64_t request_id_counter;
98
99   /**
100    * Are we currently disconnected and hence unable to send?
101    */
102   unsigned char currently_down;
103
104   /**
105    * GNUNET_YES if we're running a service test.
106    */
107   unsigned char service_test_is_active;
108 };
109
110
111 /**
112  * Entry in a doubly-linked list of control messages to be transmitted
113  * to the arm service.
114  *
115  * The actual message is allocated at the end of this struct.
116  */
117 struct ARMControlMessage
118 {
119   /**
120    * This is a doubly-linked list.
121    */
122   struct ARMControlMessage *next;
123
124   /**
125    * This is a doubly-linked list.
126    */
127   struct ARMControlMessage *prev;
128
129   /**
130    * ARM handle.
131    */
132   struct GNUNET_ARM_Handle *h;
133
134   /**
135    * Message to send.
136    */
137   struct GNUNET_ARM_Message *msg;
138
139   /**
140    * Callback for service state change requests.
141    */
142   GNUNET_ARM_ResultCallback result_cont;
143
144   /**
145    * Callback for service list requests.
146    */
147   GNUNET_ARM_ServiceListCallback list_cont;
148
149   /**
150    * Closure for 'result_cont' or 'list_cont'.
151    */
152   void *cont_cls;
153
154   /**
155    * Timeout for the operation.
156    */
157   struct GNUNET_TIME_Absolute timeout;
158
159   /**
160    * Flags for passing std descriptors to ARM (when starting ARM).
161    */
162   enum GNUNET_OS_InheritStdioFlags std_inheritance;
163
164   /**
165    * Task to run when request times out.
166    */
167   GNUNET_SCHEDULER_TaskIdentifier timeout_task_id;
168
169   /**
170    * Type of the request expressed as a message type (start, stop or list).
171    */
172   uint16_t type;
173 };
174
175 static void
176 client_notify_handler (void *cls, const struct GNUNET_MessageHeader *msg);
177
178 static int
179 reconnect_arm (struct GNUNET_ARM_Handle *h);
180
181 static void
182 trigger_next_request (struct GNUNET_ARM_Handle *h, int ignore_currently_down);
183
184
185 /**
186  * Task scheduled to try to re-connect to arm.
187  *
188  * @param cls the 'struct GNUNET_ARM_Handle'
189  * @param tc task context
190  */
191 static void
192 reconnect_arm_task (void *cls, const struct GNUNET_SCHEDULER_TaskContext *tc)
193 {
194   struct GNUNET_ARM_Handle *h = cls;
195
196   h->reconnect_task = GNUNET_SCHEDULER_NO_TASK;
197   LOG (GNUNET_ERROR_TYPE_DEBUG, "Connecting to ARM service after delay\n");
198   reconnect_arm (h);
199 }
200
201
202 static void
203 clear_pending_messages (struct GNUNET_ARM_Handle *h, enum GNUNET_ARM_RequestStatus result)
204 {
205   struct ARMControlMessage *cm;
206
207   LOG (GNUNET_ERROR_TYPE_DEBUG,
208        "Clearing pending messages\n");
209
210   while (NULL != (cm = h->control_pending_head))
211   {
212     GNUNET_CONTAINER_DLL_remove (h->control_pending_head,
213                                  h->control_pending_tail, cm);
214     GNUNET_assert (GNUNET_SCHEDULER_NO_TASK != cm->timeout_task_id);
215     GNUNET_SCHEDULER_cancel (cm->timeout_task_id);
216     if (NULL != cm->result_cont)
217       cm->result_cont (cm->cont_cls, cm->h, result, NULL, 0);
218     GNUNET_free_non_null (cm->msg);
219     GNUNET_free (cm);
220   }
221 }
222
223 /**
224  * Close down any existing connection to the ARM service and
225  * try re-establishing it later.
226  *
227  * @param h our handle
228  */
229 static void
230 reconnect_arm_later (struct GNUNET_ARM_Handle *h)
231 {
232   if (GNUNET_NO != h->currently_down)
233     return;
234   if (NULL != h->cth)
235   {
236     GNUNET_CLIENT_notify_transmit_ready_cancel (h->cth);
237     h->cth = NULL;
238   }
239   if (NULL != h->client)
240   {
241     GNUNET_CLIENT_disconnect (h->client);
242     h->client = NULL;
243   }
244   h->currently_down = GNUNET_YES;
245   GNUNET_assert (GNUNET_SCHEDULER_NO_TASK == h->reconnect_task);
246   h->reconnect_task =
247       GNUNET_SCHEDULER_add_delayed (h->retry_backoff, &reconnect_arm_task, h);
248   /* Don't clear pending messages on disconnection, deliver them later 
249   clear_pending_messages (h, GNUNET_ARM_REQUEST_DISCONNECTED);
250   GNUNET_assert (NULL == h->control_pending_head);
251   */
252   h->retry_backoff = GNUNET_TIME_STD_BACKOFF (h->retry_backoff);
253   if (NULL != h->conn_status)
254     h->conn_status (h->conn_status_cls, h, GNUNET_NO);
255 }
256
257 /**
258  * Transmit the next message to the arm service.
259  *
260  * @param cls closure with the 'struct GNUNET_ARM_Handle'
261  * @param size number of bytes available in buf
262  * @param buf where the callee should write the message
263  * @return number of bytes written to buf 
264  */
265 static size_t
266 transmit_arm_message (void *cls, size_t size, void *buf)
267 {
268   struct GNUNET_ARM_Handle *h = cls;
269   struct ARMControlMessage *cm;
270   struct GNUNET_ARM_Message *arm_msg;
271   uint64_t request_id;
272   int notify_connection;
273   uint16_t msize;
274
275   notify_connection = GNUNET_NO;
276   LOG (GNUNET_ERROR_TYPE_DEBUG,
277       "transmit_arm_message is running with %p buffer of size %lu. ARM is known to be %s\n",
278       buf, size, h->currently_down ? "unconnected" : "connected");
279   GNUNET_assert (GNUNET_SCHEDULER_NO_TASK == h->reconnect_task);
280   h->cth = NULL;  
281   if ((GNUNET_YES == h->currently_down) && (NULL != buf))
282   {
283     h->currently_down = GNUNET_NO;
284     notify_connection = GNUNET_YES;
285     h->retry_backoff = GNUNET_TIME_UNIT_MILLISECONDS;
286     GNUNET_CLIENT_receive (h->client, &client_notify_handler, h,
287                            GNUNET_TIME_UNIT_FOREVER_REL);
288   }
289   if (NULL == buf)
290   {
291     LOG (GNUNET_ERROR_TYPE_DEBUG,
292          "Transmission failed, initiating reconnect\n");
293     reconnect_arm_later (h);
294     return 0;
295   }
296   if (NULL == (cm = h->control_pending_head))
297   {
298     LOG (GNUNET_ERROR_TYPE_DEBUG, "Queue is empty, not sending anything\n");
299     msize = 0;
300     goto end;
301   }
302   GNUNET_assert (NULL != cm->msg);
303   msize = ntohs (cm->msg->header.size);
304   if (size < msize)
305   {
306     LOG (GNUNET_ERROR_TYPE_DEBUG,
307         "Request is too big (%u < %u), not sending it\n", size, msize);
308     trigger_next_request (h, GNUNET_NO);
309     msize = 0;
310     goto end;
311   }
312   arm_msg = cm->msg;
313   if (0 == h->request_id_counter)
314     h->request_id_counter++;
315   request_id = h->request_id_counter++;
316   LOG (GNUNET_ERROR_TYPE_DEBUG,
317        "Transmitting control message with %u bytes of type %u to arm with id %llu\n",
318        (unsigned int) msize, (unsigned int) ntohs (cm->msg->header.type), request_id);
319   arm_msg->request_id = GNUNET_htonll (request_id);
320   memcpy (buf, cm->msg, msize);
321   /* Otherwise we won't be able to find it later! */
322   arm_msg->request_id = request_id;
323   GNUNET_CONTAINER_DLL_remove (h->control_pending_head,
324                                h->control_pending_tail, cm);
325   GNUNET_CONTAINER_DLL_insert_tail (h->control_sent_head,
326                                     h->control_sent_tail, cm);
327   /* Don't free msg, keep it around (kind of wasteful, but then we don't
328    * really have many messages to handle, and it'll be freed when it times
329    * out anyway.
330    */
331   trigger_next_request (h, GNUNET_NO);
332
333  end:
334   if ((GNUNET_YES == notify_connection) && (NULL != h->conn_status))
335     h->conn_status (h->conn_status_cls, h, GNUNET_YES);
336   return msize;
337 }
338
339
340 /**
341  * Check the list of pending requests, send the next
342  * one to the arm.
343  *
344  * @param h arm handle
345  * @param ignore_currently_down transmit message even if not initialized?
346  */
347 static void
348 trigger_next_request (struct GNUNET_ARM_Handle *h, int ignore_currently_down)
349 {
350   uint16_t msize;
351
352   msize = sizeof (struct GNUNET_MessageHeader);
353   if ((GNUNET_YES == h->currently_down) && (ignore_currently_down == GNUNET_NO))
354   {
355     LOG (GNUNET_ERROR_TYPE_DEBUG,
356          "ARM connection down, not processing queue\n");
357     return;
358   }
359   if (NULL != h->cth)
360   {
361     LOG (GNUNET_ERROR_TYPE_DEBUG, "Request pending, not processing queue\n");
362     return;
363   }
364   if (NULL != h->control_pending_head)
365     msize =
366         ntohs (((struct GNUNET_MessageHeader *) &h->
367                 control_pending_head[1])->size);
368   else if (GNUNET_NO == ignore_currently_down)
369   {
370     LOG (GNUNET_ERROR_TYPE_DEBUG,
371          "Request queue empty, not processing queue\n");
372     return;                     /* no pending message */
373   }
374   h->cth =
375       GNUNET_CLIENT_notify_transmit_ready (h->client, msize,
376                                            GNUNET_TIME_UNIT_FOREVER_REL,
377                                            GNUNET_NO, &transmit_arm_message, h);
378 }
379
380
381 /**
382  * Connect to arm.
383  *
384  * @param h arm handle
385  */
386 static int
387 reconnect_arm (struct GNUNET_ARM_Handle *h)
388 {
389   GNUNET_assert (NULL == h->client);
390   GNUNET_assert (GNUNET_YES == h->currently_down);
391   h->client = GNUNET_CLIENT_connect ("arm", h->cfg);
392   if (NULL == h->client)
393   {
394     LOG (GNUNET_ERROR_TYPE_DEBUG,
395            "arm_api, GNUNET_CLIENT_connect returned NULL\n");
396     if (NULL != h->conn_status)
397       h->conn_status (h->conn_status_cls, h, GNUNET_SYSERR);
398     return GNUNET_SYSERR;
399   }
400   LOG (GNUNET_ERROR_TYPE_DEBUG,
401          "arm_api, GNUNET_CLIENT_connect returned non-NULL\n");
402   trigger_next_request (h, GNUNET_YES);
403   return GNUNET_OK;
404 }
405
406
407 /**
408  * Set up a context for communicating with ARM, then
409  * start connecting to the ARM service using that context.
410  *
411  * @param cfg configuration to use (needed to contact ARM;
412  *        the ARM service may internally use a different
413  *        configuration to determine how to start the service).
414  * @param conn_status will be called when connecting/disconnecting
415  * @param cls closure for conn_status
416  * @return context to use for further ARM operations, NULL on error.
417  */
418 struct GNUNET_ARM_Handle *
419 GNUNET_ARM_connect (const struct GNUNET_CONFIGURATION_Handle *cfg,
420                     GNUNET_ARM_ConnectionStatusCallback conn_status, void *cls)
421 {
422   struct GNUNET_ARM_Handle *h;
423
424   h = GNUNET_malloc (sizeof (struct GNUNET_ARM_Handle));
425   h->cfg = GNUNET_CONFIGURATION_dup (cfg);
426   h->currently_down = GNUNET_YES;
427   h->reconnect_task = GNUNET_SCHEDULER_NO_TASK;
428   h->conn_status = conn_status;
429   h->conn_status_cls = cls;
430   if (GNUNET_OK != reconnect_arm (h))
431   {
432     GNUNET_free (h);
433     return NULL;
434   }
435   return h;
436 }
437
438
439 /**
440  * Disconnect from the ARM service (if connected) and destroy the context.
441  *
442  * @param h the handle that was being used
443  */
444 void
445 GNUNET_ARM_disconnect_and_free (struct GNUNET_ARM_Handle *h)
446 {
447   LOG (GNUNET_ERROR_TYPE_DEBUG, "Disconnecting from ARM service\n");
448   if (NULL != h->cth)
449   {
450     GNUNET_CLIENT_notify_transmit_ready_cancel (h->cth);
451     h->cth = NULL;
452   }
453   clear_pending_messages (h, GNUNET_ARM_REQUEST_DISCONNECTED);
454   if (NULL != h->client)
455   {
456     GNUNET_CLIENT_disconnect (h->client);
457     h->client = NULL;
458   }
459   if (GNUNET_SCHEDULER_NO_TASK != h->reconnect_task)
460   {
461     GNUNET_SCHEDULER_cancel (h->reconnect_task);
462     h->reconnect_task = GNUNET_SCHEDULER_NO_TASK;
463   }
464   if (GNUNET_NO == h->service_test_is_active)
465   {
466     GNUNET_CONFIGURATION_destroy (h->cfg);
467     GNUNET_free (h);
468   }
469 }
470
471
472 /**
473  * Message timed out. Remove it from the queue.
474  *
475  * @param cls the message (struct ARMControlMessage *)
476  * @param tc task context
477  */
478 static void
479 control_message_timeout (void *cls, const struct GNUNET_SCHEDULER_TaskContext *tc)
480 {
481   struct ARMControlMessage *cm = cls;
482   struct GNUNET_ARM_Message *arm_msg;
483   LOG (GNUNET_ERROR_TYPE_DEBUG,
484        "Control message timed out\n");
485   arm_msg = cm->msg;
486   if ((NULL == arm_msg) || (0 == arm_msg->request_id))
487   {
488     GNUNET_CONTAINER_DLL_remove (cm->h->control_pending_head,
489                                  cm->h->control_pending_tail, cm);
490   }
491   else
492   {
493     GNUNET_CONTAINER_DLL_remove (cm->h->control_sent_head,
494                                  cm->h->control_sent_tail, cm);
495   }
496   if (NULL != cm->result_cont)
497     cm->result_cont (cm->cont_cls, cm->h, GNUNET_ARM_REQUEST_TIMEOUT, NULL, 0);
498   else if (NULL != cm->list_cont)
499     cm->list_cont (cm->cont_cls, cm->h, GNUNET_ARM_REQUEST_TIMEOUT, 0, NULL);
500   GNUNET_free_non_null (cm->msg);
501   GNUNET_free (cm);
502 }
503
504
505 #include "do_start_process.c"
506
507
508 /**
509  * A client specifically requested starting of ARM itself.
510  * This function is called with information about whether
511  * or not ARM is running; if it is, report success.  If
512  * it is not, start the ARM process.
513  *
514  * @param cls the context for the request that we will report on (struct ARMControlMessage *)
515  * @param tc why were we called (reason says if ARM is running)
516  */
517 static void
518 arm_service_report (void *cls, const struct GNUNET_SCHEDULER_TaskContext *tc)
519 {
520   struct ARMControlMessage *cm = cls;
521   struct GNUNET_ARM_Handle *h;
522   struct GNUNET_OS_Process *proc;
523   unsigned char test_is_active;
524   char *cbinary;
525   char *binary;
526   char *config;
527   char *loprefix;
528   char *lopostfix;
529
530   test_is_active = cm->h->service_test_is_active;
531
532   /* FIXME: shouldn't we check for GNUNET_SCHEDULER_REASON_SHUTDOWN ? */
533   if ((GNUNET_YES == test_is_active) &&
534       (0 != (tc->reason & GNUNET_SCHEDULER_REASON_PREREQ_DONE)))
535   {
536     LOG (GNUNET_ERROR_TYPE_DEBUG, "Looks like `%s' is already running.\n",
537          "gnunet-service-arm");
538     /* arm is running! */
539     if (cm->result_cont)
540       cm->result_cont (cm->cont_cls, cm->h, GNUNET_ARM_REQUEST_SENT_OK, "arm", GNUNET_ARM_RESULT_IS_STARTED_ALREADY);
541   }
542   if (GNUNET_NO == test_is_active)
543   {
544     /* User disconnected & destroyed ARM handle in the middle of
545      * the service test, so we kept the handle around until now.
546      */
547     GNUNET_CONFIGURATION_destroy (cm->h->cfg);
548     GNUNET_free (cm->h);
549   }
550   if ((0 != (tc->reason & GNUNET_SCHEDULER_REASON_PREREQ_DONE)) ||
551       (GNUNET_NO == test_is_active))
552   {
553     GNUNET_free (cm);
554     return;
555   }
556   cm->h->service_test_is_active = GNUNET_NO;
557   LOG (GNUNET_ERROR_TYPE_DEBUG,
558        "Looks like `%s' is not running, will start it.\n",
559        "gnunet-service-arm");
560   if (GNUNET_OK != GNUNET_CONFIGURATION_get_value_string (
561       cm->h->cfg, "arm", "PREFIX", &loprefix))
562     loprefix = GNUNET_strdup ("");
563   if (GNUNET_OK != GNUNET_CONFIGURATION_get_value_string (
564       cm->h->cfg, "arm", "OPTIONS", &lopostfix))
565     lopostfix = GNUNET_strdup ("");
566   if (GNUNET_OK != GNUNET_CONFIGURATION_get_value_string (
567       cm->h->cfg, "arm", "BINARY", &cbinary))
568   {
569     GNUNET_log_config_missing (GNUNET_ERROR_TYPE_WARNING, "arm", "BINARY");
570     if (cm->result_cont)
571       cm->result_cont (cm->cont_cls, cm->h, GNUNET_ARM_REQUEST_SENT_OK, "arm", GNUNET_ARM_RESULT_IS_NOT_KNOWN);
572     GNUNET_free (cm);
573     GNUNET_free (loprefix);
574     GNUNET_free (lopostfix);
575     return;
576   }
577   if (GNUNET_OK != GNUNET_CONFIGURATION_get_value_filename (
578       cm->h->cfg, "arm", "CONFIG", &config))
579     config = NULL;
580   binary = GNUNET_OS_get_libexec_binary_path (cbinary);
581   GNUNET_free (cbinary);
582   if ((GNUNET_YES == GNUNET_CONFIGURATION_have_value (
583           cm->h->cfg, "TESTING", "WEAKRANDOM")) &&
584       (GNUNET_YES == GNUNET_CONFIGURATION_get_value_yesno (
585           cm->h->cfg, "TESTING", "WEAKRANDOM")) &&
586       (GNUNET_NO == GNUNET_CONFIGURATION_have_value (
587           cm->h->cfg, "TESTING", "HOSTFILE")))
588   {
589     /* Means we are ONLY running locally */
590     /* we're clearly running a test, don't daemonize */
591     if (NULL == config)
592       proc = do_start_process (GNUNET_NO, cm->std_inheritance,
593                                NULL, loprefix, binary,
594                                /* no daemonization! */
595                                lopostfix, NULL);
596     else
597       proc = do_start_process (GNUNET_NO, cm->std_inheritance,
598                                NULL, loprefix, binary, "-c", config,
599                                /* no daemonization! */
600                                lopostfix, NULL);
601   }
602   else
603   {
604     if (NULL == config)
605       proc = do_start_process (GNUNET_NO, cm->std_inheritance,
606                                NULL, loprefix, binary,
607                                "-d", lopostfix, NULL);
608     else
609       proc = do_start_process (GNUNET_NO, cm->std_inheritance,
610                                NULL, loprefix, binary, "-c", config,
611                                "-d", lopostfix, NULL);
612   }
613   GNUNET_free (binary);
614   GNUNET_free_non_null (config);
615   GNUNET_free (loprefix);
616   GNUNET_free (lopostfix);
617   if (NULL == proc)
618   {
619     if (cm->result_cont)
620       cm->result_cont (cm->cont_cls, cm->h, GNUNET_ARM_REQUEST_SENT_OK, "arm",
621           GNUNET_ARM_RESULT_START_FAILED);
622     GNUNET_free (cm);
623     return;
624   }
625   if (cm->result_cont)
626     cm->result_cont (cm->cont_cls, cm->h, GNUNET_ARM_REQUEST_SENT_OK, "arm",
627         GNUNET_ARM_RESULT_STARTING);
628   GNUNET_OS_process_destroy (proc);
629   h = cm->h;
630   GNUNET_free (cm);
631   reconnect_arm (h);
632 }
633
634
635 /**
636  * Start or stop a service.
637  *
638  * @param h handle to ARM
639  * @param service_name name of the service
640  * @param timeout how long to wait before failing for good
641  * @param cb callback to invoke when service is ready
642  * @param cb_cls closure for callback
643  * @param type type of the request
644  */
645 static void
646 change_service (struct GNUNET_ARM_Handle *h, const char *service_name,
647                 struct GNUNET_TIME_Relative timeout, GNUNET_ARM_ResultCallback cb,
648                 void *cb_cls, uint16_t type)
649 {
650   struct ARMControlMessage *cm;
651   size_t slen;
652   struct GNUNET_ARM_Message *msg;
653
654   slen = strlen (service_name) + 1;
655   if (slen + sizeof (struct GNUNET_ARM_Message) >=
656       GNUNET_SERVER_MAX_MESSAGE_SIZE)
657   {
658     GNUNET_break (0);
659     if (cb != NULL)
660       cb (cb_cls, h, GNUNET_ARM_REQUEST_TOO_LONG, NULL, 0);
661     return;
662   }
663   LOG (GNUNET_ERROR_TYPE_DEBUG, "Requesting %s of service `%s'.\n",
664        (GNUNET_MESSAGE_TYPE_ARM_START == type) ? "start" : "termination",
665        service_name);
666   cm = GNUNET_malloc (sizeof (struct ARMControlMessage) + slen);
667   cm->h = h;
668   cm->result_cont = cb;
669   cm->cont_cls = cb_cls;
670   cm->timeout = GNUNET_TIME_relative_to_absolute (timeout);
671   memcpy (&cm[1], service_name, slen);
672   msg = GNUNET_malloc (sizeof (struct GNUNET_ARM_Message) + slen);
673   msg->header.size = htons (sizeof (struct GNUNET_ARM_Message) + slen);
674   msg->header.type = htons (type);
675   memcpy (&msg[1], service_name, slen);
676   cm->msg = msg;
677   LOG (GNUNET_ERROR_TYPE_DEBUG,
678       "Inserting a control message into the queue. Timeout is %llu\n",
679       GNUNET_TIME_absolute_get_remaining (cm->timeout).rel_value);
680   GNUNET_CONTAINER_DLL_insert_tail (h->control_pending_head,
681                                     h->control_pending_tail, cm);
682   cm->timeout_task_id =
683       GNUNET_SCHEDULER_add_delayed (GNUNET_TIME_absolute_get_remaining
684                                     (cm->timeout), &control_message_timeout, cm);
685   trigger_next_request (h, GNUNET_NO);
686 }
687
688
689 /**
690  * Request for a service to be started.
691  *
692  * @param h handle to ARM
693  * @param service_name name of the service
694  * @param std_inheritance inheritance of std streams
695  * @param timeout how long to wait before failing for good
696  * @param cont callback to invoke after request is sent or not sent
697  * @param cont_cls closure for callback
698  */
699 void
700 GNUNET_ARM_request_service_start (struct GNUNET_ARM_Handle *h,
701     const char *service_name, enum GNUNET_OS_InheritStdioFlags std_inheritance,
702     struct GNUNET_TIME_Relative timeout, GNUNET_ARM_ResultCallback cont,
703     void *cont_cls)
704 {
705   struct ARMControlMessage *cm;
706   size_t slen;
707
708   LOG (GNUNET_ERROR_TYPE_DEBUG,
709        "Asked to start service `%s' within %s\n", service_name,
710        GNUNET_STRINGS_relative_time_to_string (timeout, GNUNET_NO));
711   if (0 == strcasecmp ("arm", service_name))
712   {
713     /* Possible cases:
714      * 1) We're connected to ARM already. Invoke the callback immediately.
715      * 2) We're not connected to ARM.
716      *    Cancel any reconnection attempts temporarily, then perform
717      *    a service test.
718      */
719     if (GNUNET_NO == h->currently_down)
720     {
721       LOG (GNUNET_ERROR_TYPE_DEBUG, "ARM is already running\n");
722       if (NULL != cont)
723         cont (cont_cls, h, GNUNET_ARM_REQUEST_SENT_OK, "arm", GNUNET_ARM_RESULT_IS_STARTED_ALREADY);
724     }
725     else if (GNUNET_NO == h->service_test_is_active)
726     {
727       if (NULL != h->cth)
728       {
729         GNUNET_CLIENT_notify_transmit_ready_cancel (h->cth);
730         h->cth = NULL;
731       }
732       if (NULL != h->client)
733       {
734         GNUNET_CLIENT_disconnect (h->client);
735         h->client = NULL;
736       }
737       if (GNUNET_SCHEDULER_NO_TASK != h->reconnect_task)
738       {
739         GNUNET_SCHEDULER_cancel (h->reconnect_task);
740         h->reconnect_task = GNUNET_SCHEDULER_NO_TASK;
741       }
742
743       LOG (GNUNET_ERROR_TYPE_DEBUG,
744           "Not connected to ARM, will do a service test\n");
745
746       slen = strlen ("arm") + 1;
747       cm = GNUNET_malloc (sizeof (struct ARMControlMessage) + slen);
748       cm->h = h;
749       cm->result_cont = cont;
750       cm->cont_cls = cont_cls;
751       cm->timeout = GNUNET_TIME_relative_to_absolute (timeout);
752       cm->std_inheritance = std_inheritance;
753       memcpy (&cm[1], service_name, slen);
754       h->service_test_is_active = GNUNET_YES;
755       GNUNET_CLIENT_service_test ("arm", h->cfg, timeout, &arm_service_report,
756                                   cm);
757     }
758     else
759     {
760       /* Service test is already running - tell user to chill out and try
761        * again later.
762        */
763       LOG (GNUNET_ERROR_TYPE_DEBUG, "Service test is already in progress, we're busy\n");
764       if (NULL != cont)
765         cont (cont_cls, h, GNUNET_ARM_REQUEST_BUSY, NULL, 0);
766     }
767     return;
768   }
769   change_service (h, service_name, timeout, cont, cont_cls,
770                   GNUNET_MESSAGE_TYPE_ARM_START);
771 }
772
773
774 /**
775  * Request a service to be stopped.
776  * Stopping arm itself will not invalidate its handle, and
777  * ARM API will try to restore connection to the ARM service,
778  * even if ARM connection was lost because you asked for ARM to be stopped.
779  * Call GNUNET_ARM_disconnect_and_free () to free the handle and prevent
780  * further connection attempts.
781  *
782  * @param h handle to ARM
783  * @param service_name name of the service
784  * @param timeout how long to wait before failing for good
785  * @param cont callback to invoke after request is sent or is not sent
786  * @param cont_cls closure for callback
787  */
788 void
789 GNUNET_ARM_request_service_stop (struct GNUNET_ARM_Handle *h,
790     const char *service_name, struct GNUNET_TIME_Relative timeout,
791     GNUNET_ARM_ResultCallback cont, void *cont_cls)
792 {
793   LOG (GNUNET_ERROR_TYPE_DEBUG, 
794        "Stopping service `%s' within %s\n",
795        service_name, 
796        GNUNET_STRINGS_relative_time_to_string (timeout, GNUNET_NO));
797   change_service (h, service_name, timeout, cont, cont_cls,
798                   GNUNET_MESSAGE_TYPE_ARM_STOP);
799 }
800
801
802 /**
803  * Request a list of running services.
804  *
805  * @param h handle to ARM
806  * @param timeout how long to wait before failing for good
807  * @param cont callback to invoke after request is sent or is not sent
808  * @param cont_cls closure for callback
809  */
810 void
811 GNUNET_ARM_request_service_list (struct GNUNET_ARM_Handle *h,
812                                  struct GNUNET_TIME_Relative timeout,
813                                  GNUNET_ARM_ServiceListCallback cont, 
814                                  void *cont_cls)
815 {
816   struct ARMControlMessage *cm;
817   struct GNUNET_ARM_Message *msg;
818
819   LOG (GNUNET_ERROR_TYPE_DEBUG, 
820        "Requesting LIST from ARM service with timeout: %s\n", 
821        GNUNET_STRINGS_relative_time_to_string (timeout, GNUNET_YES));
822   cm = GNUNET_malloc (sizeof (struct ARMControlMessage));
823   cm->h = h;
824   cm->list_cont = cont;
825   cm->cont_cls = cont_cls;
826   cm->timeout = GNUNET_TIME_relative_to_absolute (timeout);
827   msg = GNUNET_malloc (sizeof (struct GNUNET_ARM_Message));
828   msg->header.size = htons (sizeof (struct GNUNET_ARM_Message));
829   msg->header.type = htons (GNUNET_MESSAGE_TYPE_ARM_LIST);
830   cm->msg = msg;
831   GNUNET_CONTAINER_DLL_insert_tail (h->control_pending_head,
832                                     h->control_pending_tail, cm);
833   cm->timeout_task_id =
834       GNUNET_SCHEDULER_add_delayed (GNUNET_TIME_absolute_get_remaining
835                                     (cm->timeout), &control_message_timeout, cm);
836   trigger_next_request (h, GNUNET_NO);
837 }
838
839
840 static struct ARMControlMessage *
841 find_cm_by_id (struct GNUNET_ARM_Handle *h, uint64_t id)
842 {
843   struct ARMControlMessage *result;
844   for (result = h->control_sent_head; result; result = result->next)
845     if (id == result->msg->request_id)
846       return result;
847   return NULL;
848 }
849
850
851 /**
852  * Handler for ARM replies.
853  *
854  * @param cls our "struct GNUNET_ARM_Handle"
855  * @param msg the message received from the arm service
856  */
857 static void
858 client_notify_handler (void *cls, const struct GNUNET_MessageHeader *msg)
859 {
860   struct GNUNET_ARM_Handle *h = cls;
861   const struct GNUNET_ARM_Message *arm_msg;
862   const struct GNUNET_ARM_ResultMessage *res;
863   const struct GNUNET_ARM_ListResultMessage *lres;
864   struct ARMControlMessage *cm;
865   const char **list;
866   const char *pos;
867   uint64_t id;
868   enum GNUNET_ARM_Result result;
869   uint16_t size_check;
870   uint16_t rcount;
871   uint16_t msize;
872   unsigned char fail;
873
874   list = NULL;
875   if (NULL == msg)
876   {
877     LOG (GNUNET_ERROR_TYPE_INFO,
878          _("Client was disconnected from arm service, trying to reconnect.\n"));
879     reconnect_arm_later (h);
880     return;
881   }
882   msize = ntohs (msg->size);
883   LOG (GNUNET_ERROR_TYPE_DEBUG,
884        "Processing message of type %u and size %u from arm service\n",
885        ntohs (msg->type), msize);
886   if (msize < sizeof (struct GNUNET_ARM_Message))
887   {
888     GNUNET_break (0);
889     reconnect_arm_later (h);
890     return;
891   }
892   arm_msg = (const struct GNUNET_ARM_Message *) msg;
893   id = GNUNET_ntohll (arm_msg->request_id);
894   cm = find_cm_by_id (h, id);
895   if (NULL == cm)
896   {
897     LOG (GNUNET_ERROR_TYPE_DEBUG, "Message with unknown id %llu\n", id);
898     return;
899   }  
900   fail = GNUNET_NO;
901   switch (ntohs (msg->type))
902   {
903   case GNUNET_MESSAGE_TYPE_ARM_RESULT:
904     if (msize < sizeof (struct GNUNET_ARM_ResultMessage))
905     {
906       GNUNET_assert (0);
907       fail = GNUNET_YES;
908     }
909     break;
910   case GNUNET_MESSAGE_TYPE_ARM_LIST_RESULT:
911     if (msize < sizeof (struct GNUNET_ARM_ListResultMessage))
912     {
913       GNUNET_break (0);
914       fail = GNUNET_YES;
915       break;
916     }
917     size_check = 0;
918     lres = (const struct GNUNET_ARM_ListResultMessage *) msg;
919     rcount = ntohs (lres->count);
920     {
921       unsigned int i;
922       
923       list = GNUNET_malloc (sizeof (const char *) * rcount);
924       pos = (const char *)&lres[1];
925       for (i = 0; i < rcount; i++)
926       {
927         const char *end = memchr (pos, 0, msize - size_check);
928         if (NULL == end)
929         {
930           GNUNET_break (0);
931           fail = GNUNET_YES;
932           break;
933         }
934         list[i] = pos;
935         size_check += (end - pos) + 1;
936         pos = end + 1;
937       }
938       if (GNUNET_YES == fail)
939       {
940         GNUNET_free (list);
941         list = NULL;
942       }
943     }
944     break;
945   default:
946     fail = GNUNET_YES;
947     break;
948   }
949   GNUNET_assert (GNUNET_SCHEDULER_NO_TASK != cm->timeout_task_id);
950   GNUNET_SCHEDULER_cancel (cm->timeout_task_id);
951   GNUNET_CONTAINER_DLL_remove (h->control_sent_head,
952                                h->control_sent_tail, cm);
953   if (GNUNET_YES == fail)
954   {
955     reconnect_arm_later (h);
956     GNUNET_free (cm->msg);
957     GNUNET_free (cm);
958     return;
959   }
960   GNUNET_CLIENT_receive (h->client, &client_notify_handler, h,
961                          GNUNET_TIME_UNIT_FOREVER_REL);
962   switch (ntohs (msg->type))
963   {
964   case GNUNET_MESSAGE_TYPE_ARM_RESULT:  
965     res = (const struct GNUNET_ARM_ResultMessage *) msg;
966     LOG (GNUNET_ERROR_TYPE_DEBUG,
967          "Received response from ARM for service `%s': %u\n",
968          (const char *) &cm->msg[1], ntohs (msg->type));
969     result = (enum GNUNET_ARM_Result) ntohl (res->result);
970     if (NULL != cm->result_cont)
971       cm->result_cont (cm->cont_cls, h, GNUNET_ARM_REQUEST_SENT_OK,
972                        (const char *) &cm->msg[1], result);
973     break;
974   case GNUNET_MESSAGE_TYPE_ARM_LIST_RESULT:
975     if (NULL != cm->list_cont)
976         cm->list_cont (cm->cont_cls, h, GNUNET_ARM_REQUEST_SENT_OK, rcount,
977                        list);
978     GNUNET_free (list);
979     break;
980   }  
981   GNUNET_free (cm->msg);
982   GNUNET_free (cm);
983 }
984
985 /* end of arm_api.c */