- allow disconnecting from ARM from callbacks
[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   if ((GNUNET_YES == h->currently_down) && (ignore_currently_down == GNUNET_NO))
353   {
354     LOG (GNUNET_ERROR_TYPE_DEBUG,
355          "ARM connection down, not processing queue\n");
356     return;
357   }
358   if (NULL != h->cth)
359   {
360     LOG (GNUNET_ERROR_TYPE_DEBUG, "Request pending, not processing queue\n");
361     return;
362   }
363   if (NULL != h->control_pending_head)
364     msize =
365         ntohs (((struct GNUNET_MessageHeader *) &h->
366                 control_pending_head[1])->size);
367   else if (GNUNET_NO == ignore_currently_down)
368   {
369     LOG (GNUNET_ERROR_TYPE_DEBUG,
370          "Request queue empty, not processing queue\n");
371     return;                     /* no pending message */
372   }
373   h->cth =
374       GNUNET_CLIENT_notify_transmit_ready (h->client, msize,
375                                            GNUNET_TIME_UNIT_FOREVER_REL,
376                                            GNUNET_NO, &transmit_arm_message, h);
377 }
378
379
380 /**
381  * Connect to arm.
382  *
383  * @param h arm handle
384  */
385 static int
386 reconnect_arm (struct GNUNET_ARM_Handle *h)
387 {
388   GNUNET_assert (NULL == h->client);
389   GNUNET_assert (GNUNET_YES == h->currently_down);
390   h->client = GNUNET_CLIENT_connect ("arm", h->cfg);
391   if (NULL == h->client)
392   {
393     LOG (GNUNET_ERROR_TYPE_DEBUG,
394            "arm_api, GNUNET_CLIENT_connect returned NULL\n");
395     if (NULL != h->conn_status)
396       h->conn_status (h->conn_status_cls, h, GNUNET_SYSERR);
397     return GNUNET_SYSERR;
398   }
399   LOG (GNUNET_ERROR_TYPE_DEBUG,
400          "arm_api, GNUNET_CLIENT_connect returned non-NULL\n");
401   trigger_next_request (h, GNUNET_YES);
402   return GNUNET_OK;
403 }
404
405
406 /**
407  * Set up a context for communicating with ARM, then
408  * start connecting to the ARM service using that context.
409  *
410  * @param cfg configuration to use (needed to contact ARM;
411  *        the ARM service may internally use a different
412  *        configuration to determine how to start the service).
413  * @param conn_status will be called when connecting/disconnecting
414  * @param cls closure for conn_status
415  * @return context to use for further ARM operations, NULL on error.
416  */
417 struct GNUNET_ARM_Handle *
418 GNUNET_ARM_connect (const struct GNUNET_CONFIGURATION_Handle *cfg,
419                     GNUNET_ARM_ConnectionStatusCallback conn_status, void *cls)
420 {
421   struct GNUNET_ARM_Handle *h;
422
423   h = GNUNET_malloc (sizeof (struct GNUNET_ARM_Handle));
424   h->cfg = GNUNET_CONFIGURATION_dup (cfg);
425   h->currently_down = GNUNET_YES;
426   h->reconnect_task = GNUNET_SCHEDULER_NO_TASK;
427   h->conn_status = conn_status;
428   h->conn_status_cls = cls;
429   if (GNUNET_OK != reconnect_arm (h))
430   {
431     GNUNET_free (h);
432     return NULL;
433   }
434   return h;
435 }
436
437
438 /**
439  * Disconnect from the ARM service (if connected) and destroy the context.
440  *
441  * @param h the handle that was being used
442  */
443 void
444 GNUNET_ARM_disconnect_and_free (struct GNUNET_ARM_Handle *h)
445 {
446   LOG (GNUNET_ERROR_TYPE_DEBUG, "Disconnecting from ARM service\n");
447   if (NULL != h->cth)
448   {
449     GNUNET_CLIENT_notify_transmit_ready_cancel (h->cth);
450     h->cth = NULL;
451   }
452   clear_pending_messages (h, GNUNET_ARM_REQUEST_DISCONNECTED);
453   if (NULL != h->client)
454   {
455     GNUNET_CLIENT_disconnect (h->client);
456     h->client = NULL;
457   }
458   if (GNUNET_SCHEDULER_NO_TASK != h->reconnect_task)
459   {
460     GNUNET_SCHEDULER_cancel (h->reconnect_task);
461     h->reconnect_task = GNUNET_SCHEDULER_NO_TASK;
462   }
463   if (GNUNET_NO == h->service_test_is_active)
464   {
465     GNUNET_CONFIGURATION_destroy (h->cfg);
466     GNUNET_free (h);
467   }
468 }
469
470
471 /**
472  * Message timed out. Remove it from the queue.
473  *
474  * @param cls the message (struct ARMControlMessage *)
475  * @param tc task context
476  */
477 static void
478 control_message_timeout (void *cls, const struct GNUNET_SCHEDULER_TaskContext *tc)
479 {
480   struct ARMControlMessage *cm = cls;
481   struct GNUNET_ARM_Message *arm_msg;
482   LOG (GNUNET_ERROR_TYPE_DEBUG,
483        "Control message timed out\n");
484   arm_msg = cm->msg;
485   if ((NULL == arm_msg) || (0 == arm_msg->request_id))
486   {
487     GNUNET_CONTAINER_DLL_remove (cm->h->control_pending_head,
488                                  cm->h->control_pending_tail, cm);
489   }
490   else
491   {
492     GNUNET_CONTAINER_DLL_remove (cm->h->control_sent_head,
493                                  cm->h->control_sent_tail, cm);
494   }
495   if (NULL != cm->result_cont)
496     cm->result_cont (cm->cont_cls, cm->h, GNUNET_ARM_REQUEST_TIMEOUT, NULL, 0);
497   else if (NULL != cm->list_cont)
498     cm->list_cont (cm->cont_cls, cm->h, GNUNET_ARM_REQUEST_TIMEOUT, 0, NULL);
499   GNUNET_free_non_null (cm->msg);
500   GNUNET_free (cm);
501 }
502
503
504 #include "do_start_process.c"
505
506
507 /**
508  * A client specifically requested starting of ARM itself.
509  * This function is called with information about whether
510  * or not ARM is running; if it is, report success.  If
511  * it is not, start the ARM process.
512  *
513  * @param cls the context for the request that we will report on (struct ARMControlMessage *)
514  * @param tc why were we called (reason says if ARM is running)
515  */
516 static void
517 arm_service_report (void *cls, const struct GNUNET_SCHEDULER_TaskContext *tc)
518 {
519   struct ARMControlMessage *cm = cls;
520   struct GNUNET_ARM_Handle *h;
521   struct GNUNET_OS_Process *proc;
522   unsigned char test_is_active;
523   char *cbinary;
524   char *binary;
525   char *config;
526   char *loprefix;
527   char *lopostfix;
528
529   test_is_active = cm->h->service_test_is_active;
530
531   /* FIXME: shouldn't we check for GNUNET_SCHEDULER_REASON_SHUTDOWN ? */
532   if ((GNUNET_YES == test_is_active) &&
533       (0 != (tc->reason & GNUNET_SCHEDULER_REASON_PREREQ_DONE)))
534   {
535     LOG (GNUNET_ERROR_TYPE_DEBUG, "Looks like `%s' is already running.\n",
536          "gnunet-service-arm");
537     /* arm is running! */
538     if (cm->result_cont)
539       cm->result_cont (cm->cont_cls, cm->h, GNUNET_ARM_REQUEST_SENT_OK, "arm", GNUNET_ARM_RESULT_IS_STARTED_ALREADY);
540   }
541   if (GNUNET_NO == test_is_active)
542   {
543     /* User disconnected & destroyed ARM handle in the middle of
544      * the service test, so we kept the handle around until now.
545      */
546     GNUNET_CONFIGURATION_destroy (cm->h->cfg);
547     GNUNET_free (cm->h);
548   }
549   if ((0 != (tc->reason & GNUNET_SCHEDULER_REASON_PREREQ_DONE)) ||
550       (GNUNET_NO == test_is_active))
551   {
552     GNUNET_free (cm);
553     return;
554   }
555   cm->h->service_test_is_active = GNUNET_NO;
556   LOG (GNUNET_ERROR_TYPE_DEBUG,
557        "Looks like `%s' is not running, will start it.\n",
558        "gnunet-service-arm");
559   if (GNUNET_OK != GNUNET_CONFIGURATION_get_value_string (
560       cm->h->cfg, "arm", "PREFIX", &loprefix))
561     loprefix = GNUNET_strdup ("");
562   if (GNUNET_OK != GNUNET_CONFIGURATION_get_value_string (
563       cm->h->cfg, "arm", "OPTIONS", &lopostfix))
564     lopostfix = GNUNET_strdup ("");
565   if (GNUNET_OK != GNUNET_CONFIGURATION_get_value_string (
566       cm->h->cfg, "arm", "BINARY", &cbinary))
567   {
568     GNUNET_log_config_missing (GNUNET_ERROR_TYPE_WARNING, "arm", "BINARY");
569     if (cm->result_cont)
570       cm->result_cont (cm->cont_cls, cm->h, GNUNET_ARM_REQUEST_SENT_OK, "arm", GNUNET_ARM_RESULT_IS_NOT_KNOWN);
571     GNUNET_free (cm);
572     GNUNET_free (loprefix);
573     GNUNET_free (lopostfix);
574     return;
575   }
576   if (GNUNET_OK != GNUNET_CONFIGURATION_get_value_filename (
577       cm->h->cfg, "arm", "CONFIG", &config))
578     config = NULL;
579   binary = GNUNET_OS_get_libexec_binary_path (cbinary);
580   GNUNET_free (cbinary);
581   if ((GNUNET_YES == GNUNET_CONFIGURATION_have_value (
582           cm->h->cfg, "TESTING", "WEAKRANDOM")) &&
583       (GNUNET_YES == GNUNET_CONFIGURATION_get_value_yesno (
584           cm->h->cfg, "TESTING", "WEAKRANDOM")) &&
585       (GNUNET_NO == GNUNET_CONFIGURATION_have_value (
586           cm->h->cfg, "TESTING", "HOSTFILE")))
587   {
588     /* Means we are ONLY running locally */
589     /* we're clearly running a test, don't daemonize */
590     if (NULL == config)
591       proc = do_start_process (GNUNET_NO, cm->std_inheritance,
592                                NULL, loprefix, binary,
593                                /* no daemonization! */
594                                lopostfix, NULL);
595     else
596       proc = do_start_process (GNUNET_NO, cm->std_inheritance,
597                                NULL, loprefix, binary, "-c", config,
598                                /* no daemonization! */
599                                lopostfix, NULL);
600   }
601   else
602   {
603     if (NULL == config)
604       proc = do_start_process (GNUNET_NO, cm->std_inheritance,
605                                NULL, loprefix, binary,
606                                "-d", lopostfix, NULL);
607     else
608       proc = do_start_process (GNUNET_NO, cm->std_inheritance,
609                                NULL, loprefix, binary, "-c", config,
610                                "-d", lopostfix, NULL);
611   }
612   GNUNET_free (binary);
613   GNUNET_free_non_null (config);
614   GNUNET_free (loprefix);
615   GNUNET_free (lopostfix);
616   if (NULL == proc)
617   {
618     if (cm->result_cont)
619       cm->result_cont (cm->cont_cls, cm->h, GNUNET_ARM_REQUEST_SENT_OK, "arm",
620           GNUNET_ARM_RESULT_START_FAILED);
621     GNUNET_free (cm);
622     return;
623   }
624   if (cm->result_cont)
625     cm->result_cont (cm->cont_cls, cm->h, GNUNET_ARM_REQUEST_SENT_OK, "arm",
626         GNUNET_ARM_RESULT_STARTING);
627   GNUNET_OS_process_destroy (proc);
628   h = cm->h;
629   GNUNET_free (cm);
630   reconnect_arm (h);
631 }
632
633
634 /**
635  * Start or stop a service.
636  *
637  * @param h handle to ARM
638  * @param service_name name of the service
639  * @param timeout how long to wait before failing for good
640  * @param cb callback to invoke when service is ready
641  * @param cb_cls closure for callback
642  * @param type type of the request
643  */
644 static void
645 change_service (struct GNUNET_ARM_Handle *h, const char *service_name,
646                 struct GNUNET_TIME_Relative timeout, GNUNET_ARM_ResultCallback cb,
647                 void *cb_cls, uint16_t type)
648 {
649   struct ARMControlMessage *cm;
650   size_t slen;
651   struct GNUNET_ARM_Message *msg;
652
653   slen = strlen (service_name) + 1;
654   if (slen + sizeof (struct GNUNET_ARM_Message) >=
655       GNUNET_SERVER_MAX_MESSAGE_SIZE)
656   {
657     GNUNET_break (0);
658     if (cb != NULL)
659       cb (cb_cls, h, GNUNET_ARM_REQUEST_TOO_LONG, NULL, 0);
660     return;
661   }
662   LOG (GNUNET_ERROR_TYPE_DEBUG, "Requesting %s of service `%s'.\n",
663        (GNUNET_MESSAGE_TYPE_ARM_START == type) ? "start" : "termination",
664        service_name);
665   cm = GNUNET_malloc (sizeof (struct ARMControlMessage) + slen);
666   cm->h = h;
667   cm->result_cont = cb;
668   cm->cont_cls = cb_cls;
669   cm->timeout = GNUNET_TIME_relative_to_absolute (timeout);
670   memcpy (&cm[1], service_name, slen);
671   msg = GNUNET_malloc (sizeof (struct GNUNET_ARM_Message) + slen);
672   msg->header.size = htons (sizeof (struct GNUNET_ARM_Message) + slen);
673   msg->header.type = htons (type);
674   memcpy (&msg[1], service_name, slen);
675   cm->msg = msg;
676   LOG (GNUNET_ERROR_TYPE_DEBUG,
677       "Inserting a control message into the queue. Timeout is %llu\n",
678       GNUNET_TIME_absolute_get_remaining (cm->timeout).rel_value);
679   GNUNET_CONTAINER_DLL_insert_tail (h->control_pending_head,
680                                     h->control_pending_tail, cm);
681   cm->timeout_task_id =
682       GNUNET_SCHEDULER_add_delayed (GNUNET_TIME_absolute_get_remaining
683                                     (cm->timeout), &control_message_timeout, cm);
684   trigger_next_request (h, GNUNET_NO);
685 }
686
687
688 /**
689  * Request for a service to be started.
690  *
691  * @param h handle to ARM
692  * @param service_name name of the service
693  * @param std_inheritance inheritance of std streams
694  * @param timeout how long to wait before failing for good
695  * @param cont callback to invoke after request is sent or not sent
696  * @param cont_cls closure for callback
697  */
698 void
699 GNUNET_ARM_request_service_start (struct GNUNET_ARM_Handle *h,
700     const char *service_name, enum GNUNET_OS_InheritStdioFlags std_inheritance,
701     struct GNUNET_TIME_Relative timeout, GNUNET_ARM_ResultCallback cont,
702     void *cont_cls)
703 {
704   struct ARMControlMessage *cm;
705   size_t slen;
706
707   LOG (GNUNET_ERROR_TYPE_DEBUG,
708        "Asked to start service `%s' within %s\n", service_name,
709        GNUNET_STRINGS_relative_time_to_string (timeout, GNUNET_NO));
710   if (0 == strcasecmp ("arm", service_name))
711   {
712     /* Possible cases:
713      * 1) We're connected to ARM already. Invoke the callback immediately.
714      * 2) We're not connected to ARM.
715      *    Cancel any reconnection attempts temporarily, then perform
716      *    a service test.
717      */
718     if (GNUNET_NO == h->currently_down)
719     {
720       LOG (GNUNET_ERROR_TYPE_DEBUG, "ARM is already running\n");
721       if (NULL != cont)
722         cont (cont_cls, h, GNUNET_ARM_REQUEST_SENT_OK, "arm", GNUNET_ARM_RESULT_IS_STARTED_ALREADY);
723     }
724     else if (GNUNET_NO == h->service_test_is_active)
725     {
726       if (NULL != h->cth)
727       {
728         GNUNET_CLIENT_notify_transmit_ready_cancel (h->cth);
729         h->cth = NULL;
730       }
731       if (NULL != h->client)
732       {
733         GNUNET_CLIENT_disconnect (h->client);
734         h->client = NULL;
735       }
736       if (GNUNET_SCHEDULER_NO_TASK != h->reconnect_task)
737       {
738         GNUNET_SCHEDULER_cancel (h->reconnect_task);
739         h->reconnect_task = GNUNET_SCHEDULER_NO_TASK;
740       }
741
742       LOG (GNUNET_ERROR_TYPE_DEBUG,
743           "Not connected to ARM, will do a service test\n");
744
745       slen = strlen ("arm") + 1;
746       cm = GNUNET_malloc (sizeof (struct ARMControlMessage) + slen);
747       cm->h = h;
748       cm->result_cont = cont;
749       cm->cont_cls = cont_cls;
750       cm->timeout = GNUNET_TIME_relative_to_absolute (timeout);
751       cm->std_inheritance = std_inheritance;
752       memcpy (&cm[1], service_name, slen);
753       h->service_test_is_active = GNUNET_YES;
754       GNUNET_CLIENT_service_test ("arm", h->cfg, timeout, &arm_service_report,
755                                   cm);
756     }
757     else
758     {
759       /* Service test is already running - tell user to chill out and try
760        * again later.
761        */
762       LOG (GNUNET_ERROR_TYPE_DEBUG, "Service test is already in progress, we're busy\n");
763       if (NULL != cont)
764         cont (cont_cls, h, GNUNET_ARM_REQUEST_BUSY, NULL, 0);
765     }
766     return;
767   }
768   change_service (h, service_name, timeout, cont, cont_cls,
769                   GNUNET_MESSAGE_TYPE_ARM_START);
770 }
771
772
773 /**
774  * Request a service to be stopped.
775  * Stopping arm itself will not invalidate its handle, and
776  * ARM API will try to restore connection to the ARM service,
777  * even if ARM connection was lost because you asked for ARM to be stopped.
778  * Call GNUNET_ARM_disconnect_and_free () to free the handle and prevent
779  * further connection attempts.
780  *
781  * @param h handle to ARM
782  * @param service_name name of the service
783  * @param timeout how long to wait before failing for good
784  * @param cont callback to invoke after request is sent or is not sent
785  * @param cont_cls closure for callback
786  */
787 void
788 GNUNET_ARM_request_service_stop (struct GNUNET_ARM_Handle *h,
789     const char *service_name, struct GNUNET_TIME_Relative timeout,
790     GNUNET_ARM_ResultCallback cont, void *cont_cls)
791 {
792   LOG (GNUNET_ERROR_TYPE_DEBUG, 
793        "Stopping service `%s' within %s\n",
794        service_name, 
795        GNUNET_STRINGS_relative_time_to_string (timeout, GNUNET_NO));
796   change_service (h, service_name, timeout, cont, cont_cls,
797                   GNUNET_MESSAGE_TYPE_ARM_STOP);
798 }
799
800
801 /**
802  * Request a list of running services.
803  *
804  * @param h handle to ARM
805  * @param timeout how long to wait before failing for good
806  * @param cont callback to invoke after request is sent or is not sent
807  * @param cont_cls closure for callback
808  */
809 void
810 GNUNET_ARM_request_service_list (struct GNUNET_ARM_Handle *h,
811                                  struct GNUNET_TIME_Relative timeout,
812                                  GNUNET_ARM_ServiceListCallback cont, 
813                                  void *cont_cls)
814 {
815   struct ARMControlMessage *cm;
816   struct GNUNET_ARM_Message *msg;
817
818   LOG (GNUNET_ERROR_TYPE_DEBUG, 
819        "Requesting LIST from ARM service with timeout: %s\n", 
820        GNUNET_STRINGS_relative_time_to_string (timeout, GNUNET_YES));
821   cm = GNUNET_malloc (sizeof (struct ARMControlMessage));
822   cm->h = h;
823   cm->list_cont = cont;
824   cm->cont_cls = cont_cls;
825   cm->timeout = GNUNET_TIME_relative_to_absolute (timeout);
826   msg = GNUNET_malloc (sizeof (struct GNUNET_ARM_Message));
827   msg->header.size = htons (sizeof (struct GNUNET_ARM_Message));
828   msg->header.type = htons (GNUNET_MESSAGE_TYPE_ARM_LIST);
829   cm->msg = msg;
830   GNUNET_CONTAINER_DLL_insert_tail (h->control_pending_head,
831                                     h->control_pending_tail, cm);
832   cm->timeout_task_id =
833       GNUNET_SCHEDULER_add_delayed (GNUNET_TIME_absolute_get_remaining
834                                     (cm->timeout), &control_message_timeout, cm);
835   trigger_next_request (h, GNUNET_NO);
836 }
837
838
839 static struct ARMControlMessage *
840 find_cm_by_id (struct GNUNET_ARM_Handle *h, uint64_t id)
841 {
842   struct ARMControlMessage *result;
843   for (result = h->control_sent_head; result; result = result->next)
844     if (id == result->msg->request_id)
845       return result;
846   return NULL;
847 }
848
849
850 /**
851  * Handler for ARM replies.
852  *
853  * @param cls our "struct GNUNET_ARM_Handle"
854  * @param msg the message received from the arm service
855  */
856 static void
857 client_notify_handler (void *cls, const struct GNUNET_MessageHeader *msg)
858 {
859   struct GNUNET_ARM_Handle *h = cls;
860   const struct GNUNET_ARM_Message *arm_msg;
861   const struct GNUNET_ARM_ResultMessage *res;
862   const struct GNUNET_ARM_ListResultMessage *lres;
863   struct ARMControlMessage *cm;
864   const char **list;
865   const char *pos;
866   uint64_t id;
867   enum GNUNET_ARM_Result result;
868   uint16_t size_check;
869   uint16_t rcount;
870   uint16_t msize;
871   unsigned char fail;
872
873   list = NULL;
874   if (NULL == msg)
875   {
876     LOG (GNUNET_ERROR_TYPE_INFO,
877          _("Client was disconnected from arm service, trying to reconnect.\n"));
878     reconnect_arm_later (h);
879     return;
880   }
881   msize = ntohs (msg->size);
882   LOG (GNUNET_ERROR_TYPE_DEBUG,
883        "Processing message of type %u and size %u from arm service\n",
884        ntohs (msg->type), msize);
885   if (msize < sizeof (struct GNUNET_ARM_Message))
886   {
887     GNUNET_break (0);
888     reconnect_arm_later (h);
889     return;
890   }
891   arm_msg = (const struct GNUNET_ARM_Message *) msg;
892   id = GNUNET_ntohll (arm_msg->request_id);
893   cm = find_cm_by_id (h, id);
894   if (NULL == cm)
895   {
896     LOG (GNUNET_ERROR_TYPE_DEBUG, "Message with unknown id %llu\n", id);
897     return;
898   }  
899   fail = GNUNET_NO;
900   switch (ntohs (msg->type))
901   {
902   case GNUNET_MESSAGE_TYPE_ARM_RESULT:
903     if (msize < sizeof (struct GNUNET_ARM_ResultMessage))
904     {
905       GNUNET_assert (0);
906       fail = GNUNET_YES;
907     }
908     break;
909   case GNUNET_MESSAGE_TYPE_ARM_LIST_RESULT:
910     if (msize < sizeof (struct GNUNET_ARM_ListResultMessage))
911     {
912       GNUNET_break (0);
913       fail = GNUNET_YES;
914       break;
915     }
916     size_check = 0;
917     lres = (const struct GNUNET_ARM_ListResultMessage *) msg;
918     rcount = ntohs (lres->count);
919     {
920       unsigned int i;
921       
922       list = GNUNET_malloc (sizeof (const char *) * rcount);
923       pos = (const char *)&lres[1];
924       for (i = 0; i < rcount; i++)
925       {
926         const char *end = memchr (pos, 0, msize - size_check);
927         if (NULL == end)
928         {
929           GNUNET_break (0);
930           fail = GNUNET_YES;
931           break;
932         }
933         list[i] = pos;
934         size_check += (end - pos) + 1;
935         pos = end + 1;
936       }
937       if (GNUNET_YES == fail)
938       {
939         GNUNET_free (list);
940         list = NULL;
941       }
942     }
943     break;
944   default:
945     fail = GNUNET_YES;
946     break;
947   }
948   GNUNET_assert (GNUNET_SCHEDULER_NO_TASK != cm->timeout_task_id);
949   GNUNET_SCHEDULER_cancel (cm->timeout_task_id);
950   GNUNET_CONTAINER_DLL_remove (h->control_sent_head,
951                                h->control_sent_tail, cm);
952   if (GNUNET_YES == fail)
953   {
954     reconnect_arm_later (h);
955     GNUNET_free (cm->msg);
956     GNUNET_free (cm);
957     return;
958   }
959   GNUNET_CLIENT_receive (h->client, &client_notify_handler, h,
960                          GNUNET_TIME_UNIT_FOREVER_REL);
961   switch (ntohs (msg->type))
962   {
963   case GNUNET_MESSAGE_TYPE_ARM_RESULT:  
964     res = (const struct GNUNET_ARM_ResultMessage *) msg;
965     LOG (GNUNET_ERROR_TYPE_DEBUG,
966          "Received response from ARM for service `%s': %u\n",
967          (const char *) &cm->msg[1], ntohs (msg->type));
968     result = (enum GNUNET_ARM_Result) ntohl (res->result);
969     if (NULL != cm->result_cont)
970       cm->result_cont (cm->cont_cls, h, GNUNET_ARM_REQUEST_SENT_OK,
971                        (const char *) &cm->msg[1], result);
972     break;
973   case GNUNET_MESSAGE_TYPE_ARM_LIST_RESULT:
974     if (NULL != cm->list_cont)
975         cm->list_cont (cm->cont_cls, h, GNUNET_ARM_REQUEST_SENT_OK, rcount,
976                        list);
977     GNUNET_free (list);
978     break;
979   }  
980   GNUNET_free (cm->msg);
981   GNUNET_free (cm);
982 }
983
984 /* end of arm_api.c */