Simplify ARM alloc/connect
[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   /**
41    * Our control connection to the ARM service.
42    */
43   struct GNUNET_CLIENT_Connection *client;
44
45   /**
46    * The configuration that we are using.
47    */
48   struct GNUNET_CONFIGURATION_Handle *cfg;
49
50   /**
51    * Handle for our current transmission request.
52    */
53   struct GNUNET_CLIENT_TransmitHandle *cth;
54
55   /**
56    * Head of doubly-linked list of pending requests.
57    */
58   struct ARMControlMessage *control_pending_head;
59
60   /**
61    * Tail of doubly-linked list of pending requests.
62    */
63   struct ARMControlMessage *control_pending_tail;
64
65   /**
66    * Head of doubly-linked list of sent requests.
67    */
68   struct ARMControlMessage *control_sent_head;
69
70   /**
71    * Tail of doubly-linked list of sent requests.
72    */
73   struct ARMControlMessage *control_sent_tail;
74
75   /**
76    * ID of the reconnect task (if any).
77    */
78   GNUNET_SCHEDULER_TaskIdentifier reconnect_task;
79
80   /**
81    * Current delay we use for re-trying to connect to core.
82    */
83   struct GNUNET_TIME_Relative retry_backoff;
84
85   /**
86    * Are we currently disconnected and hence unable to send?
87    */
88   unsigned char currently_down;
89
90   /**
91    * Callback to invoke on connection/disconnection.
92    */
93   GNUNET_ARM_ConnectionStatusCallback conn_status;
94
95   /**
96    * Closure for conn_status.
97    */
98   void *conn_status_cls;
99
100   /**
101    * GNUNET_YES if we're running a service test.
102    */
103   unsigned char service_test_is_active;
104
105   /**
106    * Counter for request identifiers
107    */
108   uint64_t request_id_counter;
109 };
110
111
112 /**
113  * Entry in a doubly-linked list of control messages to be transmitted
114  * to the arm service.
115  *
116  * The actual message is allocated at the end of this struct.
117  */
118 struct ARMControlMessage
119 {
120   /**
121    * This is a doubly-linked list.
122    */
123   struct ARMControlMessage *next;
124
125   /**
126    * This is a doubly-linked list.
127    */
128   struct ARMControlMessage *prev;
129
130   /**
131    * Callback for service state change requests.
132    */
133   GNUNET_ARM_ResultCallback result_cont;
134
135   /**
136    * Callback for service list requests.
137    */
138   GNUNET_ARM_ServiceListCallback list_cont;
139
140   /**
141    * Closure for 'result_cont' or 'list_cont'.
142    */
143   void *cont_cls;
144
145   /**
146    * Timeout for the operation.
147    */
148   struct GNUNET_TIME_Absolute timeout;
149
150   /**
151    * Type of the request expressed as a message type (start, stop or list).
152    */
153   uint16_t type;
154
155   /**
156    * Flags for passing std descriptors to ARM (when starting ARM).
157    */
158   enum GNUNET_OS_InheritStdioFlags std_inheritance;
159
160   /**
161    * ARM handle.
162    */
163   struct GNUNET_ARM_Handle *h;
164
165   /**
166    * Message to send.
167    */
168   struct GNUNET_ARM_Message *msg;
169
170   /**
171    * Task to run when request times out.
172    */
173   GNUNET_SCHEDULER_TaskIdentifier timeout_task_id;
174 };
175
176 static void
177 client_notify_handler (void *cls, const struct GNUNET_MessageHeader *msg);
178
179 static int
180 reconnect_arm (struct GNUNET_ARM_Handle *h);
181
182 static void
183 trigger_next_request (struct GNUNET_ARM_Handle *h, int ignore_currently_down);
184
185
186 /**
187  * Task scheduled to try to re-connect to arm.
188  *
189  * @param cls the 'struct GNUNET_ARM_Handle'
190  * @param tc task context
191  */
192 static void
193 reconnect_arm_task (void *cls, const struct GNUNET_SCHEDULER_TaskContext *tc)
194 {
195   struct GNUNET_ARM_Handle *h = cls;
196
197   h->reconnect_task = GNUNET_SCHEDULER_NO_TASK;
198   LOG (GNUNET_ERROR_TYPE_DEBUG, "Connecting to ARM service after delay\n");
199   reconnect_arm (h);
200 }
201
202
203 static void
204 clear_pending_messages (struct GNUNET_ARM_Handle *h, enum GNUNET_ARM_RequestStatus result)
205 {
206   struct ARMControlMessage *cm;
207
208   LOG (GNUNET_ERROR_TYPE_DEBUG,
209        "Clearing pending messages\n");
210
211   while (NULL != (cm = h->control_pending_head))
212   {
213     GNUNET_CONTAINER_DLL_remove (h->control_pending_head,
214                                  h->control_pending_tail, cm);
215     GNUNET_assert (GNUNET_SCHEDULER_NO_TASK != cm->timeout_task_id);
216     GNUNET_SCHEDULER_cancel (cm->timeout_task_id);
217     if (NULL != cm->result_cont)
218       cm->result_cont (cm->cont_cls, cm->h, result, NULL, 0);
219     GNUNET_free_non_null (cm->msg);
220     GNUNET_free (cm);
221   }
222 }
223
224 /**
225  * Close down any existing connection to the ARM service and
226  * try re-establishing it later.
227  *
228  * @param h our handle
229  */
230 static void
231 reconnect_arm_later (struct GNUNET_ARM_Handle *h)
232 {
233   if (GNUNET_NO != h->currently_down)
234     return;
235
236   if (NULL != h->cth)
237   {
238     GNUNET_CLIENT_notify_transmit_ready_cancel (h->cth);
239     h->cth = NULL;
240   }
241
242   if (NULL != h->client)
243   {
244     GNUNET_CLIENT_disconnect (h->client);
245     h->client = NULL;
246   }
247
248   if (NULL != h->conn_status)
249     h->conn_status (h->conn_status_cls, h, GNUNET_NO);
250
251   h->currently_down = GNUNET_YES;
252
253   GNUNET_assert (GNUNET_SCHEDULER_NO_TASK == h->reconnect_task);
254   h->reconnect_task =
255       GNUNET_SCHEDULER_add_delayed (h->retry_backoff, &reconnect_arm_task, h);
256   /* Don't clear pending messages on disconnection, deliver them later 
257   clear_pending_messages (h, GNUNET_ARM_REQUEST_DISCONNECTED);
258   GNUNET_assert (NULL == h->control_pending_head);
259   */
260   h->retry_backoff = GNUNET_TIME_STD_BACKOFF (h->retry_backoff);
261 }
262
263 /**
264  * Transmit the next message to the arm service.
265  *
266  * @param cls closure with the 'struct GNUNET_ARM_Handle'
267  * @param size number of bytes available in buf
268  * @param buf where the callee should write the message
269  * @return number of bytes written to buf 
270  */
271 static size_t
272 transmit_arm_message (void *cls, size_t size, void *buf)
273 {
274   struct GNUNET_ARM_Handle *h = cls;
275   struct ARMControlMessage *cm;
276   struct GNUNET_ARM_Message *arm_msg;
277   uint16_t msize;
278   uint64_t request_id;
279
280   LOG (GNUNET_ERROR_TYPE_DEBUG,
281       "transmit_arm_message is running with %p buffer of size %lu. ARM is known to be %s\n",
282       buf, size, h->currently_down ? "unconnected" : "connected");
283   GNUNET_assert (GNUNET_SCHEDULER_NO_TASK == h->reconnect_task);
284   h->cth = NULL;
285   if ((GNUNET_YES == h->currently_down) && (NULL != buf))
286   {
287     h->currently_down = GNUNET_NO;
288     if (NULL != h->conn_status)
289       h->conn_status (h->conn_status_cls, h, GNUNET_YES);
290     h->retry_backoff = GNUNET_TIME_UNIT_MILLISECONDS;
291     GNUNET_CLIENT_receive (h->client, &client_notify_handler, h,
292                            GNUNET_TIME_UNIT_FOREVER_REL);
293   }
294   if (NULL == buf)
295   {
296     LOG (GNUNET_ERROR_TYPE_DEBUG,
297          "Transmission failed, initiating reconnect\n");
298     reconnect_arm_later (h);
299     return 0;
300   }
301   if (NULL == (cm = h->control_pending_head))
302   {
303     LOG (GNUNET_ERROR_TYPE_DEBUG, "Queue is empty, not sending anything\n");
304     return 0;
305   }
306
307   GNUNET_assert (NULL != cm->msg);
308   msize = ntohs (cm->msg->header.size);
309   if (size < msize)
310   {
311     LOG (GNUNET_ERROR_TYPE_DEBUG,
312         "Request is too big (%u < %u), not sending it\n", size, msize);
313     trigger_next_request (h, GNUNET_NO);
314     return 0;
315   }
316   arm_msg = cm->msg;
317   if (0 == h->request_id_counter)
318     h->request_id_counter++;
319   request_id = h->request_id_counter++;
320   LOG (GNUNET_ERROR_TYPE_DEBUG,
321        "Transmitting control message with %u bytes of type %u to arm with id %llu\n",
322        (unsigned int) msize, (unsigned int) ntohs (cm->msg->header.type), request_id);
323   arm_msg->request_id = GNUNET_htonll (request_id);
324   memcpy (buf, cm->msg, msize);
325   /* Otherwise we won't be able to find it later! */
326   arm_msg->request_id = request_id;
327
328   GNUNET_CONTAINER_DLL_remove (h->control_pending_head,
329                                h->control_pending_tail, cm);
330   GNUNET_CONTAINER_DLL_insert_tail (h->control_sent_head,
331                                     h->control_sent_tail, cm);
332
333   /* Don't free msg, keep it around (kind of wasteful, but then we don't
334    * really have many messages to handle, and it'll be freed when it times
335    * out anyway.
336    */
337   trigger_next_request (h, GNUNET_NO);
338   return msize;
339 }
340
341
342 /**
343  * Check the list of pending requests, send the next
344  * one to the arm.
345  *
346  * @param h arm handle
347  * @param ignore_currently_down transmit message even if not initialized?
348  */
349 static void
350 trigger_next_request (struct GNUNET_ARM_Handle *h, int ignore_currently_down)
351 {
352   uint16_t msize;
353
354   if ((GNUNET_YES == h->currently_down) && (ignore_currently_down == GNUNET_NO))
355   {
356     LOG (GNUNET_ERROR_TYPE_DEBUG,
357          "ARM connection down, not processing queue\n");
358     return;
359   }
360   if (NULL != h->cth)
361   {
362     LOG (GNUNET_ERROR_TYPE_DEBUG, "Request pending, not processing queue\n");
363     return;
364   }
365   if (NULL != h->control_pending_head)
366     msize =
367         ntohs (((struct GNUNET_MessageHeader *) &h->
368                 control_pending_head[1])->size);
369   else if (GNUNET_NO == ignore_currently_down)
370   {
371     LOG (GNUNET_ERROR_TYPE_DEBUG,
372          "Request queue empty, not processing queue\n");
373     return;                     /* no pending message */
374   }
375   h->cth =
376       GNUNET_CLIENT_notify_transmit_ready (h->client, msize,
377                                            GNUNET_TIME_UNIT_FOREVER_REL,
378                                            GNUNET_NO, &transmit_arm_message, h);
379 }
380
381
382 /**
383  * Connect to arm.
384  *
385  * @param h arm handle
386  */
387 static int
388 reconnect_arm (struct GNUNET_ARM_Handle *h)
389 {
390   GNUNET_assert (NULL == h->client);
391   GNUNET_assert (GNUNET_YES == h->currently_down);
392   h->client = GNUNET_CLIENT_connect ("arm", h->cfg);
393   if (NULL == h->client)
394   {
395     LOG (GNUNET_ERROR_TYPE_DEBUG,
396            "arm_api, GNUNET_CLIENT_connect returned NULL\n");
397     if (NULL != h->conn_status)
398       h->conn_status (h->conn_status_cls, h, GNUNET_SYSERR);
399     return GNUNET_SYSERR;
400   }
401   LOG (GNUNET_ERROR_TYPE_DEBUG,
402          "arm_api, GNUNET_CLIENT_connect returned non-NULL\n");
403   trigger_next_request (h, GNUNET_YES);
404   return GNUNET_OK;
405 }
406
407
408 /**
409  * Set up a context for communicating with ARM, then
410  * start connecting to the ARM service using that context.
411  *
412  * @param cfg configuration to use (needed to contact ARM;
413  *        the ARM service may internally use a different
414  *        configuration to determine how to start the service).
415  * @param conn_status will be called when connecting/disconnecting
416  * @param cls closure for conn_status
417  * @return context to use for further ARM operations, NULL on error.
418  */
419 struct GNUNET_ARM_Handle *
420 GNUNET_ARM_connect (const struct GNUNET_CONFIGURATION_Handle *cfg,
421                     GNUNET_ARM_ConnectionStatusCallback conn_status, void *cls)
422 {
423   struct GNUNET_ARM_Handle *h;
424
425   h = GNUNET_malloc (sizeof (struct GNUNET_ARM_Handle));
426   h->cfg = GNUNET_CONFIGURATION_dup (cfg);
427   h->currently_down = GNUNET_YES;
428   h->reconnect_task = GNUNET_SCHEDULER_NO_TASK;
429   h->conn_status = conn_status;
430   h->conn_status_cls = cls;
431   if (GNUNET_OK != reconnect_arm (h))
432   {
433     GNUNET_free (h);
434     return NULL;
435   }
436   return h;
437 }
438
439
440 /**
441  * Disconnect from the ARM service (if connected) and destroy the context.
442  * Don't call inside an ARM callback!
443  *
444  * @param h the handle that was being used
445  */
446 void
447 GNUNET_ARM_disconnect_and_free (struct GNUNET_ARM_Handle *handle)
448 {
449   LOG (GNUNET_ERROR_TYPE_DEBUG, "Disconnecting from ARM service\n");
450   if (NULL != handle->cth)
451   {
452     GNUNET_CLIENT_notify_transmit_ready_cancel (handle->cth);
453     handle->cth = NULL;
454   }
455   clear_pending_messages (handle, GNUNET_ARM_REQUEST_DISCONNECTED);
456   if (NULL != handle->client)
457   {
458     GNUNET_CLIENT_disconnect (handle->client);
459     handle->client = NULL;
460   }
461   if (GNUNET_SCHEDULER_NO_TASK != handle->reconnect_task)
462   {
463     GNUNET_SCHEDULER_cancel (handle->reconnect_task);
464     handle->reconnect_task = GNUNET_SCHEDULER_NO_TASK;
465   }
466   if (GNUNET_NO == handle->service_test_is_active)
467   {
468     GNUNET_CONFIGURATION_destroy (handle->cfg);
469     GNUNET_free (handle);
470   }
471 }
472
473
474 /**
475  * Message timed out. Remove it from the queue.
476  *
477  * @param cls the message (struct ARMControlMessage *)
478  * @param tc task context
479  */
480 static void
481 control_message_timeout (void *cls, const struct GNUNET_SCHEDULER_TaskContext *tc)
482 {
483   struct ARMControlMessage *cm = cls;
484   struct GNUNET_ARM_Message *arm_msg;
485   LOG (GNUNET_ERROR_TYPE_DEBUG,
486        "Control message timed out\n");
487   arm_msg = cm->msg;
488   if ((NULL == arm_msg) || (0 == arm_msg->request_id))
489   {
490     GNUNET_CONTAINER_DLL_remove (cm->h->control_pending_head,
491                                  cm->h->control_pending_tail, cm);
492   }
493   else
494   {
495     GNUNET_CONTAINER_DLL_remove (cm->h->control_sent_head,
496                                  cm->h->control_sent_tail, cm);
497   }
498   if (NULL != cm->result_cont)
499     cm->result_cont (cm->cont_cls, cm->h, GNUNET_ARM_REQUEST_TIMEOUT, NULL, 0);
500   else if (NULL != cm->list_cont)
501     cm->list_cont (cm->cont_cls, cm->h, GNUNET_ARM_REQUEST_TIMEOUT, 0, NULL);
502   GNUNET_free_non_null (cm->msg);
503   GNUNET_free (cm);
504 }
505
506
507 #include "do_start_process.c"
508
509
510 /**
511  * A client specifically requested starting of ARM itself.
512  * This function is called with information about whether
513  * or not ARM is running; if it is, report success.  If
514  * it is not, start the ARM process.
515  *
516  * @param cls the context for the request that we will report on (struct ARMControlMessage *)
517  * @param tc why were we called (reason says if ARM is running)
518  */
519 static void
520 arm_service_report (void *cls, const struct GNUNET_SCHEDULER_TaskContext *tc)
521 {
522   struct ARMControlMessage *cm = cls;
523   struct GNUNET_OS_Process *proc;
524   unsigned char test_is_active;
525   char *cbinary;
526   char *binary;
527   char *config;
528   char *loprefix;
529   char *lopostfix;
530
531   test_is_active = cm->h->service_test_is_active;
532
533   /* FIXME: shouldn't we check for GNUNET_SCHEDULER_REASON_SHUTDOWN ? */
534   if ((GNUNET_YES == test_is_active) &&
535       (0 != (tc->reason & GNUNET_SCHEDULER_REASON_PREREQ_DONE)))
536   {
537     LOG (GNUNET_ERROR_TYPE_DEBUG, "Looks like `%s' is already running.\n",
538          "gnunet-service-arm");
539     /* arm is running! */
540     if (cm->result_cont)
541       cm->result_cont (cm->cont_cls, cm->h, GNUNET_ARM_REQUEST_SENT_OK, "arm", GNUNET_ARM_RESULT_IS_STARTED_ALREADY);
542   }
543   if (GNUNET_NO == test_is_active)
544   {
545     /* User disconnected & destroyed ARM handle in the middle of
546      * the service test, so we kept the handle around until now.
547      */
548     GNUNET_CONFIGURATION_destroy (cm->h->cfg);
549     GNUNET_free (cm->h);
550   }
551   if ((0 != (tc->reason & GNUNET_SCHEDULER_REASON_PREREQ_DONE)) ||
552       (GNUNET_NO == test_is_active))
553   {
554     GNUNET_free (cm);
555     return;
556   }
557   cm->h->service_test_is_active = GNUNET_NO;
558   LOG (GNUNET_ERROR_TYPE_DEBUG,
559        "Looks like `%s' is not running, will start it.\n",
560        "gnunet-service-arm");
561   if (GNUNET_OK != GNUNET_CONFIGURATION_get_value_string (
562       cm->h->cfg, "arm", "PREFIX", &loprefix))
563     loprefix = GNUNET_strdup ("");
564   if (GNUNET_OK != GNUNET_CONFIGURATION_get_value_string (
565       cm->h->cfg, "arm", "OPTIONS", &lopostfix))
566     lopostfix = GNUNET_strdup ("");
567   if (GNUNET_OK != GNUNET_CONFIGURATION_get_value_string (
568       cm->h->cfg, "arm", "BINARY", &cbinary))
569   {
570     GNUNET_log_config_missing (GNUNET_ERROR_TYPE_WARNING, "arm", "BINARY");
571     if (cm->result_cont)
572       cm->result_cont (cm->cont_cls, cm->h, GNUNET_ARM_REQUEST_SENT_OK, "arm", GNUNET_ARM_RESULT_IS_NOT_KNOWN);
573     GNUNET_free (cm);
574     GNUNET_free (loprefix);
575     GNUNET_free (lopostfix);
576     return;
577   }
578   if (GNUNET_OK != GNUNET_CONFIGURATION_get_value_filename (
579       cm->h->cfg, "arm", "CONFIG", &config))
580     config = NULL;
581   binary = GNUNET_OS_get_libexec_binary_path (cbinary);
582   GNUNET_free (cbinary);
583   if ((GNUNET_YES == GNUNET_CONFIGURATION_have_value (
584           cm->h->cfg, "TESTING", "WEAKRANDOM")) &&
585       (GNUNET_YES == GNUNET_CONFIGURATION_get_value_yesno (
586           cm->h->cfg, "TESTING", "WEAKRANDOM")) &&
587       (GNUNET_NO == GNUNET_CONFIGURATION_have_value (
588           cm->h->cfg, "TESTING", "HOSTFILE")))
589   {
590     /* Means we are ONLY running locally */
591     /* we're clearly running a test, don't daemonize */
592     if (NULL == config)
593       proc = do_start_process (GNUNET_NO, cm->std_inheritance,
594                                NULL, loprefix, binary,
595                                /* no daemonization! */
596                                lopostfix, NULL);
597     else
598       proc = do_start_process (GNUNET_NO, cm->std_inheritance,
599                                NULL, loprefix, binary, "-c", config,
600                                /* no daemonization! */
601                                lopostfix, NULL);
602   }
603   else
604   {
605     if (NULL == config)
606       proc = do_start_process (GNUNET_NO, cm->std_inheritance,
607                                NULL, loprefix, binary,
608                                "-d", lopostfix, NULL);
609     else
610       proc = do_start_process (GNUNET_NO, cm->std_inheritance,
611                                NULL, loprefix, binary, "-c", config,
612                                "-d", lopostfix, NULL);
613   }
614   GNUNET_free (binary);
615   GNUNET_free_non_null (config);
616   GNUNET_free (loprefix);
617   GNUNET_free (lopostfix);
618   if (NULL == proc)
619   {
620     if (cm->result_cont)
621       cm->result_cont (cm->cont_cls, cm->h, GNUNET_ARM_REQUEST_SENT_OK, "arm",
622           GNUNET_ARM_RESULT_START_FAILED);
623     GNUNET_free (cm);
624     return;
625   }
626   if (cm->result_cont)
627     cm->result_cont (cm->cont_cls, cm->h, GNUNET_ARM_REQUEST_SENT_OK, "arm",
628         GNUNET_ARM_RESULT_STARTING);
629   GNUNET_OS_process_destroy (proc);
630   reconnect_arm (cm->h);
631   GNUNET_free (cm);
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, 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
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 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
861   uint16_t msize;
862   uint64_t id;
863   unsigned char fail;
864
865   const struct GNUNET_ARM_Message *arm_msg;
866   const struct GNUNET_ARM_ResultMessage *res;
867   const struct GNUNET_ARM_ListResultMessage *lres;
868   enum GNUNET_ARM_Result result;
869   struct ARMControlMessage *cm;
870
871   const char *pos;
872   uint16_t size_check;
873   uint16_t rcount;
874
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   
901   fail = GNUNET_NO;
902   switch (ntohs (msg->type))
903   {
904   case GNUNET_MESSAGE_TYPE_ARM_RESULT:
905     if (msize < sizeof (struct GNUNET_ARM_ResultMessage))
906     {
907       GNUNET_assert (0);
908       fail = GNUNET_YES;
909       break;
910     }
911     res = (const struct GNUNET_ARM_ResultMessage *) msg;
912     LOG (GNUNET_ERROR_TYPE_DEBUG,
913          "Received response from ARM for service `%s': %u\n",
914          (const char *) &cm->msg[1], ntohs (msg->type));
915     result = (enum GNUNET_ARM_Result) ntohl (res->result);
916     if (NULL != cm->result_cont)
917       cm->result_cont (cm->cont_cls, h, GNUNET_ARM_REQUEST_SENT_OK, (const char *) &cm->msg[1], result);
918     break;
919   case GNUNET_MESSAGE_TYPE_ARM_LIST_RESULT:
920     if (msize < sizeof (struct GNUNET_ARM_ListResultMessage))
921     {
922       GNUNET_break (0);
923       fail = GNUNET_YES;
924       return;
925     }
926     else
927     {
928       size_check = 0;
929       lres = (const struct GNUNET_ARM_ListResultMessage *) msg;
930       rcount = ntohs (lres->count);
931       {
932         const char *list[rcount];
933         unsigned int i;
934
935         pos = (const char *)&lres[1];
936         for (i = 0; i < rcount; i++)
937         {
938           const char *end = memchr (pos, 0, msize - size_check);
939           if (NULL == end)
940           {
941             GNUNET_break (0);
942             fail = GNUNET_YES;
943             break;
944           }
945           list[i] = pos;
946           size_check += (end - pos) + 1;
947           pos = end + 1;
948         }
949         if (GNUNET_YES == fail)
950           break;
951         if (NULL != cm->list_cont)
952           cm->list_cont (cm->cont_cls, h, GNUNET_ARM_REQUEST_SENT_OK, rcount, list);
953       }
954     }
955     break;
956   default:
957     fail = GNUNET_YES;
958     return;
959   }
960
961   GNUNET_assert (GNUNET_SCHEDULER_NO_TASK != cm->timeout_task_id);
962   GNUNET_SCHEDULER_cancel (cm->timeout_task_id);
963   GNUNET_CONTAINER_DLL_remove (h->control_sent_head,
964                                h->control_sent_tail, cm);
965   GNUNET_free (cm->msg);
966   GNUNET_free (cm);
967
968   if (GNUNET_YES == fail)
969     reconnect_arm_later (h);
970   else
971     GNUNET_CLIENT_receive (h->client, &client_notify_handler, h,
972         GNUNET_TIME_UNIT_FOREVER_REL);
973 }
974
975 /* end of arm_api.c */