2 This file is part of GNUnet.
3 Copyright (C) 2009, 2010, 2012, 2013, 2016 GNUnet e.V.
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.
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.
15 You should have received a copy of the GNU General Public License
16 along with GNUnet; see the file COPYING. If not, write to the
17 Free Software Foundation, Inc., 51 Franklin Street, Fifth Floor,
18 Boston, MA 02110-1301, USA.
23 * @brief API for accessing the ARM service
24 * @author Christian Grothoff
28 #include "gnunet_util_lib.h"
29 #include "gnunet_arm_service.h"
30 #include "gnunet_protocols.h"
33 #define LOG(kind,...) GNUNET_log_from (kind, "arm-api",__VA_ARGS__)
37 * Entry in a doubly-linked list of operations awaiting for replies
38 * (in-order) from the ARM service.
40 struct GNUNET_ARM_Operation
43 * This is a doubly-linked list.
45 struct GNUNET_ARM_Operation *next;
48 * This is a doubly-linked list.
50 struct GNUNET_ARM_Operation *prev;
55 struct GNUNET_ARM_Handle *h;
58 * Callback for service state change requests.
60 GNUNET_ARM_ResultCallback result_cont;
63 * Callback for service list requests.
65 GNUNET_ARM_ServiceListCallback list_cont;
68 * Closure for @e result_cont or @e list_cont.
73 * Task for async completion.
75 struct GNUNET_SCHEDULER_Task *async;
78 * Unique ID for the request.
83 * Result of this operation for #notify_starting().
85 enum GNUNET_ARM_Result starting_ret;
88 * Is this an operation to stop the ARM service?
95 * Handle for interacting with ARM.
97 struct GNUNET_ARM_Handle
100 * Our connection to the ARM service.
102 struct GNUNET_MQ_Handle *mq;
105 * The configuration that we are using.
107 const struct GNUNET_CONFIGURATION_Handle *cfg;
110 * Head of doubly-linked list of pending operations.
112 struct GNUNET_ARM_Operation *operation_pending_head;
115 * Tail of doubly-linked list of pending operations.
117 struct GNUNET_ARM_Operation *operation_pending_tail;
120 * Callback to invoke on connection/disconnection.
122 GNUNET_ARM_ConnectionStatusCallback conn_status;
125 * Closure for @e conn_status.
127 void *conn_status_cls;
130 * ARM operation where the goal is to wait for ARM shutdown to
131 * complete. This operation is special in that it waits for an
132 * error on the @e mq. So we complete it by calling the
133 * continuation in the #mq_error_handler(). Note that the operation
134 * is no longer in the @e operation_pending_head DLL once it is
135 * referenced from this field.
137 struct GNUNET_ARM_Operation *thm;
140 * ID of the reconnect task (if any).
142 struct GNUNET_SCHEDULER_Task *reconnect_task;
145 * Current delay we use for re-trying to connect to core.
147 struct GNUNET_TIME_Relative retry_backoff;
150 * Counter for request identifiers. They are used to match replies
151 * from ARM to operations in the @e operation_pending_head DLL.
153 uint64_t request_id_counter;
156 * Have we detected that ARM is up?
166 * @param h arm handle
167 * @return #GNUNET_OK on success, #GNUNET_SYSERR on failure
170 reconnect_arm (struct GNUNET_ARM_Handle *h);
174 * Task scheduled to try to re-connect to arm.
176 * @param cls the `struct GNUNET_ARM_Handle`
179 reconnect_arm_task (void *cls)
181 struct GNUNET_ARM_Handle *h = cls;
183 h->reconnect_task = NULL;
189 * Close down any existing connection to the ARM service and
190 * try re-establishing it later.
192 * @param h our handle
195 reconnect_arm_later (struct GNUNET_ARM_Handle *h)
197 struct GNUNET_ARM_Operation *op;
201 GNUNET_MQ_destroy (h->mq);
204 h->currently_up = GNUNET_NO;
205 GNUNET_assert (NULL == h->reconnect_task);
207 GNUNET_SCHEDULER_add_delayed (h->retry_backoff,
210 while (NULL != (op = h->operation_pending_head))
212 if (NULL != op->result_cont)
213 op->result_cont (op->cont_cls,
214 GNUNET_ARM_REQUEST_DISCONNECTED,
216 if (NULL != op->list_cont)
217 op->list_cont (op->cont_cls,
218 GNUNET_ARM_REQUEST_DISCONNECTED,
221 GNUNET_ARM_operation_cancel (op);
223 GNUNET_assert (NULL == h->operation_pending_head);
224 h->retry_backoff = GNUNET_TIME_STD_BACKOFF (h->retry_backoff);
225 if (NULL != h->conn_status)
226 h->conn_status (h->conn_status_cls,
232 * Find a control message by its unique ID.
234 * @param h ARM handle
235 * @param id unique message ID to use for the lookup
236 * @return NULL if not found
238 static struct GNUNET_ARM_Operation *
239 find_op_by_id (struct GNUNET_ARM_Handle *h,
242 struct GNUNET_ARM_Operation *result;
244 for (result = h->operation_pending_head; NULL != result; result = result->next)
245 if (id == result->id)
252 * Handler for ARM replies.
254 * @param cls our `struct GNUNET_ARM_Handle`
255 * @param res the message received from the arm service
258 handle_arm_result (void *cls,
259 const struct GNUNET_ARM_ResultMessage *res)
261 struct GNUNET_ARM_Handle *h = cls;
262 struct GNUNET_ARM_Operation *op;
264 enum GNUNET_ARM_Result result;
265 GNUNET_ARM_ResultCallback result_cont;
266 void *result_cont_cls;
268 id = GNUNET_ntohll (res->arm_msg.request_id);
269 op = find_op_by_id (h,
273 LOG (GNUNET_ERROR_TYPE_DEBUG,
274 "Message with unknown id %llu\n",
275 (unsigned long long) id);
279 result = (enum GNUNET_ARM_Result) ntohl (res->result);
280 if ( (GNUNET_YES == op->is_arm_stop) &&
281 (GNUNET_ARM_RESULT_STOPPING == result) )
283 /* special case: if we are stopping 'gnunet-service-arm', we do not just
284 wait for the result message, but also wait for the service to close
285 the connection (and then we have to close our client handle as well);
286 this is done by installing a different receive handler, waiting for
287 the connection to go down */
291 op->result_cont (h->thm->cont_cls,
292 GNUNET_ARM_REQUEST_SENT_OK,
293 GNUNET_ARM_RESULT_IS_NOT_KNOWN);
294 GNUNET_free (h->thm);
296 GNUNET_CONTAINER_DLL_remove (h->operation_pending_head,
297 h->operation_pending_tail,
302 result_cont = op->result_cont;
303 result_cont_cls = op->cont_cls;
304 GNUNET_ARM_operation_cancel (op);
305 if (NULL != result_cont)
306 result_cont (result_cont_cls,
307 GNUNET_ARM_REQUEST_SENT_OK,
313 * Checked that list result message is well-formed.
315 * @param cls our `struct GNUNET_ARM_Handle`
316 * @param lres the message received from the arm service
317 * @return #GNUNET_OK if message is well-formed
320 check_arm_list_result (void *cls,
321 const struct GNUNET_ARM_ListResultMessage *lres)
323 const char *pos = (const char *) &lres[1];
324 uint16_t rcount = ntohs (lres->count);
325 uint16_t msize = ntohs (lres->arm_msg.header.size) - sizeof (*lres);
329 for (unsigned int i = 0; i < rcount; i++)
331 const char *end = memchr (pos, 0, msize - size_check);
335 return GNUNET_SYSERR;
337 size_check += (end - pos) + 1;
345 * Handler for ARM list replies.
347 * @param cls our `struct GNUNET_ARM_Handle`
348 * @param lres the message received from the arm service
351 handle_arm_list_result (void *cls,
352 const struct GNUNET_ARM_ListResultMessage *lres)
354 struct GNUNET_ARM_Handle *h = cls;
355 uint16_t rcount = ntohs (lres->count);
356 const char *list[rcount];
357 const char *pos = (const char *) &lres[1];
358 uint16_t msize = ntohs (lres->arm_msg.header.size) - sizeof (*lres);
359 struct GNUNET_ARM_Operation *op;
363 id = GNUNET_ntohll (lres->arm_msg.request_id);
364 op = find_op_by_id (h,
368 LOG (GNUNET_ERROR_TYPE_DEBUG,
369 "Message with unknown id %llu\n",
370 (unsigned long long) id);
374 for (unsigned int i = 0; i < rcount; i++)
376 const char *end = memchr (pos,
380 /* Assert, as this was already checked in #check_arm_list_result() */
381 GNUNET_assert (NULL != end);
383 size_check += (end - pos) + 1;
386 if (NULL != op->list_cont)
387 op->list_cont (op->cont_cls,
388 GNUNET_ARM_REQUEST_SENT_OK,
391 GNUNET_ARM_operation_cancel (op);
396 * Receive confirmation from test, ARM service is up.
398 * @param cls closure with the `struct GNUNET_ARM_Handle`
399 * @param msg message received
402 handle_confirm (void *cls,
403 const struct GNUNET_MessageHeader *msg)
405 struct GNUNET_ARM_Handle *h = cls;
407 LOG (GNUNET_ERROR_TYPE_DEBUG,
408 "Got confirmation from ARM that we are up!\n");
409 if (GNUNET_NO == h->currently_up)
411 h->currently_up = GNUNET_YES;
412 if (NULL != h->conn_status)
413 h->conn_status (h->conn_status_cls,
420 * Generic error handler, called with the appropriate error code and
421 * the same closure specified at the creation of the message queue.
422 * Not every message queue implementation supports an error handler.
424 * @param cls closure with the `struct GNUNET_ARM_Handle *`
425 * @param error error code
428 mq_error_handler (void *cls,
429 enum GNUNET_MQ_Error error)
431 struct GNUNET_ARM_Handle *h = cls;
432 struct GNUNET_ARM_Operation *op;
434 h->currently_up = GNUNET_NO;
435 if (NULL != (op = h->thm))
438 op->result_cont (op->cont_cls,
439 GNUNET_ARM_REQUEST_SENT_OK,
440 GNUNET_ARM_RESULT_STOPPED);
443 reconnect_arm_later (h);
450 * @param h arm handle
451 * @return #GNUNET_OK on success, #GNUNET_SYSERR on failure
454 reconnect_arm (struct GNUNET_ARM_Handle *h)
456 struct GNUNET_MQ_MessageHandler handlers[] = {
457 GNUNET_MQ_hd_fixed_size (arm_result,
458 GNUNET_MESSAGE_TYPE_ARM_RESULT,
459 struct GNUNET_ARM_ResultMessage,
461 GNUNET_MQ_hd_var_size (arm_list_result,
462 GNUNET_MESSAGE_TYPE_ARM_LIST_RESULT,
463 struct GNUNET_ARM_ListResultMessage,
465 GNUNET_MQ_hd_fixed_size (confirm,
466 GNUNET_MESSAGE_TYPE_ARM_TEST,
467 struct GNUNET_MessageHeader,
469 GNUNET_MQ_handler_end ()
471 struct GNUNET_MessageHeader *test;
472 struct GNUNET_MQ_Envelope *env;
476 GNUNET_assert (GNUNET_NO == h->currently_up);
477 h->mq = GNUNET_CLIENT_connect (h->cfg,
484 LOG (GNUNET_ERROR_TYPE_DEBUG,
485 "GNUNET_CLIENT_connect returned NULL\n");
486 if (NULL != h->conn_status)
487 h->conn_status (h->conn_status_cls,
489 return GNUNET_SYSERR;
491 LOG (GNUNET_ERROR_TYPE_DEBUG,
492 "Sending TEST message to ARM\n");
493 env = GNUNET_MQ_msg (test,
494 GNUNET_MESSAGE_TYPE_ARM_TEST);
495 GNUNET_MQ_send (h->mq,
502 * Set up a context for communicating with ARM, then
503 * start connecting to the ARM service using that context.
505 * @param cfg configuration to use (needed to contact ARM;
506 * the ARM service may internally use a different
507 * configuration to determine how to start the service).
508 * @param conn_status will be called when connecting/disconnecting
509 * @param conn_status_cls closure for @a conn_status
510 * @return context to use for further ARM operations, NULL on error.
512 struct GNUNET_ARM_Handle *
513 GNUNET_ARM_connect (const struct GNUNET_CONFIGURATION_Handle *cfg,
514 GNUNET_ARM_ConnectionStatusCallback conn_status,
515 void *conn_status_cls)
517 struct GNUNET_ARM_Handle *h;
519 h = GNUNET_new (struct GNUNET_ARM_Handle);
521 h->conn_status = conn_status;
522 h->conn_status_cls = conn_status_cls;
523 if (GNUNET_OK != reconnect_arm (h))
533 * Disconnect from the ARM service (if connected) and destroy the context.
535 * @param h the handle that was being used
538 GNUNET_ARM_disconnect (struct GNUNET_ARM_Handle *h)
540 struct GNUNET_ARM_Operation *op;
542 LOG (GNUNET_ERROR_TYPE_DEBUG,
543 "Disconnecting from ARM service\n");
544 while (NULL != (op = h->operation_pending_head))
546 GNUNET_CONTAINER_DLL_remove (h->operation_pending_head,
547 h->operation_pending_tail,
549 if (NULL != op->result_cont)
550 op->result_cont (op->cont_cls,
551 GNUNET_ARM_REQUEST_DISCONNECTED,
553 if (NULL != op->list_cont)
554 op->list_cont (op->cont_cls,
555 GNUNET_ARM_REQUEST_DISCONNECTED,
558 if (NULL != op->async)
560 GNUNET_SCHEDULER_cancel (op->async);
567 GNUNET_MQ_destroy (h->mq);
570 if (NULL != h->reconnect_task)
572 GNUNET_SCHEDULER_cancel (h->reconnect_task);
573 h->reconnect_task = NULL;
580 * A client specifically requested starting of ARM itself.
581 * Starts the ARM service.
583 * @param h the handle with configuration details
584 * @param std_inheritance inheritance of std streams
585 * @return operation status code
587 static enum GNUNET_ARM_Result
588 start_arm_service (struct GNUNET_ARM_Handle *h,
589 enum GNUNET_OS_InheritStdioFlags std_inheritance)
591 struct GNUNET_OS_Process *proc;
600 GNUNET_CONFIGURATION_get_value_string (h->cfg,
604 loprefix = GNUNET_strdup ("");
606 loprefix = GNUNET_CONFIGURATION_expand_dollar (h->cfg,
609 GNUNET_CONFIGURATION_get_value_string (h->cfg,
613 lopostfix = GNUNET_strdup ("");
615 lopostfix = GNUNET_CONFIGURATION_expand_dollar (h->cfg,
618 GNUNET_CONFIGURATION_get_value_string (h->cfg,
623 GNUNET_log_config_missing (GNUNET_ERROR_TYPE_WARNING,
626 GNUNET_free (loprefix);
627 GNUNET_free (lopostfix);
628 return GNUNET_ARM_RESULT_IS_NOT_KNOWN;
631 GNUNET_CONFIGURATION_get_value_filename (h->cfg,
636 binary = GNUNET_OS_get_libexec_binary_path (cbinary);
637 GNUNET_asprintf ("edbinary,
640 GNUNET_free (cbinary);
642 GNUNET_CONFIGURATION_have_value (h->cfg,
646 GNUNET_CONFIGURATION_get_value_yesno (h->cfg,
650 GNUNET_CONFIGURATION_have_value (h->cfg,
654 /* Means we are ONLY running locally */
655 /* we're clearly running a test, don't daemonize */
657 proc = GNUNET_OS_start_process_s (GNUNET_NO,
662 /* no daemonization! */
666 proc = GNUNET_OS_start_process_s (GNUNET_NO,
672 /* no daemonization! */
679 proc = GNUNET_OS_start_process_s (GNUNET_NO,
684 "-d", /* do daemonize */
687 proc = GNUNET_OS_start_process_s (GNUNET_NO,
693 "-d", /* do daemonize */
697 GNUNET_free (binary);
698 GNUNET_free (quotedbinary);
699 GNUNET_free_non_null (config);
700 GNUNET_free (loprefix);
701 GNUNET_free (lopostfix);
703 return GNUNET_ARM_RESULT_START_FAILED;
704 GNUNET_OS_process_destroy (proc);
705 return GNUNET_ARM_RESULT_STARTING;
710 * Abort an operation. Only prevents the callback from being
711 * called, the operation may still complete.
713 * @param op operation to cancel
716 GNUNET_ARM_operation_cancel (struct GNUNET_ARM_Operation *op)
718 struct GNUNET_ARM_Handle *h = op->h;
722 op->result_cont = NULL;
725 GNUNET_CONTAINER_DLL_remove (h->operation_pending_head,
726 h->operation_pending_tail,
733 * Start or stop a service.
735 * @param h handle to ARM
736 * @param service_name name of the service
737 * @param cb callback to invoke when service is ready
738 * @param cb_cls closure for @a cb
739 * @param type type of the request
740 * @return handle to queue, NULL on error
742 static struct GNUNET_ARM_Operation *
743 change_service (struct GNUNET_ARM_Handle *h,
744 const char *service_name,
745 GNUNET_ARM_ResultCallback cb,
749 struct GNUNET_ARM_Operation *op;
751 struct GNUNET_MQ_Envelope *env;
752 struct GNUNET_ARM_Message *msg;
754 slen = strlen (service_name) + 1;
755 if (slen + sizeof (struct GNUNET_ARM_Message) >=
756 GNUNET_SERVER_MAX_MESSAGE_SIZE)
761 if (0 == h->request_id_counter)
762 h->request_id_counter++;
763 op = GNUNET_new (struct GNUNET_ARM_Operation);
765 op->result_cont = cb;
766 op->cont_cls = cb_cls;
767 op->id = h->request_id_counter++;
768 GNUNET_CONTAINER_DLL_insert_tail (h->operation_pending_head,
769 h->operation_pending_tail,
771 env = GNUNET_MQ_msg_extra (msg,
774 msg->reserved = htonl (0);
775 msg->request_id = GNUNET_htonll (op->id);
776 GNUNET_memcpy (&msg[1],
779 GNUNET_MQ_send (h->mq,
786 * Task run to notify application that ARM is already up.
788 * @param cls the operation that asked ARM to be started
791 notify_running (void *cls)
793 struct GNUNET_ARM_Operation *op = cls;
794 struct GNUNET_ARM_Handle *h = op->h;
797 GNUNET_CONTAINER_DLL_remove (h->operation_pending_head,
798 h->operation_pending_tail,
800 if (NULL != op->result_cont)
801 op->result_cont (op->cont_cls,
802 GNUNET_ARM_REQUEST_SENT_OK,
803 GNUNET_ARM_RESULT_IS_STARTED_ALREADY);
804 if ( (GNUNET_YES == h->currently_up) &&
805 (NULL != h->conn_status) )
806 h->conn_status (h->conn_status_cls,
813 * Task run to notify application that ARM is being started.
815 * @param cls the operation that asked ARM to be started
818 notify_starting (void *cls)
820 struct GNUNET_ARM_Operation *op = cls;
821 struct GNUNET_ARM_Handle *h = op->h;
824 LOG (GNUNET_ERROR_TYPE_DEBUG,
825 "Notifying client that we started the ARM service\n");
826 GNUNET_CONTAINER_DLL_remove (h->operation_pending_head,
827 h->operation_pending_tail,
829 if (NULL != op->result_cont)
830 op->result_cont (op->cont_cls,
831 GNUNET_ARM_REQUEST_SENT_OK,
838 * Request for a service to be started.
840 * @param h handle to ARM
841 * @param service_name name of the service
842 * @param std_inheritance inheritance of std streams
843 * @param cont callback to invoke after request is sent or not sent
844 * @param cont_cls closure for @a cont
845 * @return handle for the operation, NULL on error
847 struct GNUNET_ARM_Operation *
848 GNUNET_ARM_request_service_start (struct GNUNET_ARM_Handle *h,
849 const char *service_name,
850 enum GNUNET_OS_InheritStdioFlags std_inheritance,
851 GNUNET_ARM_ResultCallback cont,
854 struct GNUNET_ARM_Operation *op;
855 enum GNUNET_ARM_Result ret;
857 LOG (GNUNET_ERROR_TYPE_DEBUG,
858 "Starting service `%s'\n",
860 if (0 != strcasecmp ("arm",
862 return change_service (h,
866 GNUNET_MESSAGE_TYPE_ARM_START);
869 * 1) We're connected to ARM already. Invoke the callback immediately.
870 * 2) We're not connected to ARM.
871 * Cancel any reconnection attempts temporarily, then perform
874 if (GNUNET_YES == h->currently_up)
876 LOG (GNUNET_ERROR_TYPE_DEBUG,
877 "ARM is already running\n");
878 op = GNUNET_new (struct GNUNET_ARM_Operation);
880 op->result_cont = cont;
881 op->cont_cls = cont_cls;
882 GNUNET_CONTAINER_DLL_insert_tail (h->operation_pending_head,
883 h->operation_pending_tail,
885 op->async = GNUNET_SCHEDULER_add_now (¬ify_running,
889 /* This is an inherently uncertain choice, as it is of course
890 theoretically possible that ARM is up and we just did not
891 yet complete the MQ handshake. However, given that users
892 are unlikely to hammer 'gnunet-arm -s' on a busy system,
893 the above check should catch 99.99% of the cases where ARM
894 is already running. */
895 LOG (GNUNET_ERROR_TYPE_DEBUG,
896 "Starting ARM service\n");
897 ret = start_arm_service (h,
899 if (GNUNET_ARM_RESULT_STARTING == ret)
901 op = GNUNET_new (struct GNUNET_ARM_Operation);
903 op->result_cont = cont;
904 op->cont_cls = cont_cls;
905 GNUNET_CONTAINER_DLL_insert_tail (h->operation_pending_head,
906 h->operation_pending_tail,
908 op->starting_ret = ret;
909 op->async = GNUNET_SCHEDULER_add_now (¬ify_starting,
916 * Request a service to be stopped. Stopping arm itself will not
917 * invalidate its handle, and ARM API will try to restore connection
918 * to the ARM service, even if ARM connection was lost because you
919 * asked for ARM to be stopped. Call
920 * #GNUNET_ARM_disconnect() to free the handle and prevent
921 * further connection attempts.
923 * @param h handle to ARM
924 * @param service_name name of the service
925 * @param cont callback to invoke after request is sent or is not sent
926 * @param cont_cls closure for @a cont
927 * @return handle for the operation, NULL on error
929 struct GNUNET_ARM_Operation *
930 GNUNET_ARM_request_service_stop (struct GNUNET_ARM_Handle *h,
931 const char *service_name,
932 GNUNET_ARM_ResultCallback cont,
935 struct GNUNET_ARM_Operation *op;
937 LOG (GNUNET_ERROR_TYPE_DEBUG,
938 "Stopping service `%s'\n",
940 op = change_service (h,
944 GNUNET_MESSAGE_TYPE_ARM_STOP);
947 /* If the service is ARM, set a flag as we will use MQ errors
948 to detect that the process is really gone. */
949 if (0 == strcasecmp (service_name,
951 op->is_arm_stop = GNUNET_YES;
957 * Request a list of running services.
959 * @param h handle to ARM
960 * @param cont callback to invoke after request is sent or is not sent
961 * @param cont_cls closure for @a cont
962 * @return handle for the operation, NULL on error
964 struct GNUNET_ARM_Operation *
965 GNUNET_ARM_request_service_list (struct GNUNET_ARM_Handle *h,
966 GNUNET_ARM_ServiceListCallback cont,
969 struct GNUNET_ARM_Operation *op;
970 struct GNUNET_MQ_Envelope *env;
971 struct GNUNET_ARM_Message *msg;
973 LOG (GNUNET_ERROR_TYPE_DEBUG,
974 "Requesting LIST from ARM service\n");
975 if (0 == h->request_id_counter)
976 h->request_id_counter++;
977 op = GNUNET_new (struct GNUNET_ARM_Operation);
979 op->list_cont = cont;
980 op->cont_cls = cont_cls;
981 op->id = h->request_id_counter++;
982 GNUNET_CONTAINER_DLL_insert_tail (h->operation_pending_head,
983 h->operation_pending_tail,
985 env = GNUNET_MQ_msg (msg,
986 GNUNET_MESSAGE_TYPE_ARM_LIST);
987 msg->reserved = htonl (0);
988 msg->request_id = GNUNET_htonll (op->id);
989 GNUNET_MQ_send (h->mq,
995 /* end of arm_api.c */