Merge pull request #49 from painless-security/jennifer/mon_msg_encoders
authormrw42 <margaret@painless-security.com>
Thu, 3 May 2018 20:00:42 +0000 (16:00 -0400)
committerGitHub <noreply@github.com>
Thu, 3 May 2018 20:00:42 +0000 (16:00 -0400)
Add encoders for monitoring messages (pull request 1)

18 files changed:
CMakeLists.txt [new file with mode: 0644]
Makefile.am
include/mon_internal.h [new file with mode: 0644]
mon/mon_common.c [new file with mode: 0644]
mon/mon_req.c [new file with mode: 0644]
mon/mon_req_decode.c [new file with mode: 0644]
mon/mon_req_encode.c [new file with mode: 0644]
mon/mon_resp.c [new file with mode: 0644]
mon/mon_resp_encode.c [new file with mode: 0644]
mon/tests/req_reconfigure.test [new file with mode: 0644]
mon/tests/req_show_all_options.test [new file with mode: 0644]
mon/tests/req_show_no_options.test [new file with mode: 0644]
mon/tests/resp_reconfigure_error.test [new file with mode: 0644]
mon/tests/resp_reconfigure_success.test [new file with mode: 0644]
mon/tests/resp_show_success.test [new file with mode: 0644]
mon/tests/test_mon_req_decode.c [new file with mode: 0644]
mon/tests/test_mon_req_encode.c [new file with mode: 0644]
mon/tests/test_mon_resp_encode.c [new file with mode: 0644]

diff --git a/CMakeLists.txt b/CMakeLists.txt
new file mode 100644 (file)
index 0000000..5d30c36
--- /dev/null
@@ -0,0 +1,107 @@
+# Rudimentary CMakeLists.txt
+#
+# This is not used for real builds, it mostly exists to enable code navigation in
+# CLion. Real builds use autotools + make.
+#
+cmake_minimum_required(VERSION 3.6)
+project(trust_router)
+
+set(CMAKE_CXX_STANDARD 11)
+
+include(FindPkgConfig)
+pkg_check_modules(GLIB glib-2.0 REQUIRED)
+include_directories(${GLIB_INCLUDE_DIRS})
+
+include_directories(include)
+
+set(SOURCE_FILES
+        common/tests/cfg_test.c
+        common/tests/commtest.c
+    common/tests/dh_test.c
+    common/tests/mq_test.c
+    common/tests/thread_test.c
+    common/jansson_iterators.h
+    common/t_constraint.c
+    common/tr_apc.c
+    common/tr_comm.c
+    common/tr_config.c
+    common/tr_constraint.c
+    common/tr_debug.c
+    common/tr_dh.c
+    common/tr_filter.c
+    common/tr_gss.c
+    common/tr_idp.c
+    common/tr_mq.c
+    common/tr_msg.c
+    common/tr_name.c
+    common/tr_rp.c
+    common/tr_util.c
+    gsscon/test/gsscon_client.c
+    gsscon/test/gsscon_server.c
+    gsscon/gsscon_active.c
+    gsscon/gsscon_common.c
+    gsscon/gsscon_passive.c
+    include/trust_router/tid.h
+    include/trust_router/tr_constraint.h
+    include/trust_router/tr_dh.h
+    include/trust_router/tr_name.h
+    include/trust_router/tr_versioning.h
+    include/trust_router/trp.h
+    include/gsscon.h
+    include/tid_internal.h
+    include/tr.h
+    include/tr_apc.h
+    include/tr_cfgwatch.h
+    include/tr_comm.h
+    include/tr_config.h
+    include/tr_debug.h
+    include/tr_event.h
+    include/tr_filter.h
+    include/tr_gss.h
+    include/tr_idp.h
+    include/tr_mq.h
+    include/tr_msg.h
+    include/tr_rp.h
+    include/tr_tid.h
+    include/tr_trp.h
+    include/tr_util.h
+    include/trp_internal.h
+    include/trp_ptable.h
+    include/trp_rtable.h
+    tid/example/tidc_main.c
+    tid/example/tids_main.c
+    tid/tid_req.c
+    tid/tid_resp.c
+    tid/tidc.c
+    tid/tids.c
+    tr/tr.c
+    tr/tr_cfgwatch.c
+    tr/tr_event.c
+    tr/tr_main.c
+    tr/tr_tid.c
+    tr/tr_trp.c
+    tr/trpc_main.c
+    trp/test/ptbl_test.c
+    trp/test/rtbl_test.c
+    trp/msgtst.c
+    trp/trp_conn.c
+    trp/trp_ptable.c
+    trp/trp_req.c
+    trp/trp_rtable.c
+    trp/trp_upd.c
+    trp/trpc.c
+    trp/trps.c include/tr_name_internal.h mon/mon_req.c mon/mon_req_encode.c mon/mon_req_decode.c
+        mon/mon_resp.c mon/mon_common.c mon/mon_resp_encode.c)
+
+# Does not actually build!
+add_executable(trust_router ${SOURCE_FILES})
+
+# Test build targets - for debugging
+add_executable(test_mon_req_encode mon/mon_common.c mon/mon_req.c mon/tests/test_mon_req_encode.c mon/mon_req_encode.c)
+target_link_libraries(test_mon_req_encode jansson talloc glib-2.0)
+
+add_executable(test_mon_req_decode mon/mon_common.c mon/mon_req.c mon/tests/test_mon_req_decode.c mon/mon_req_decode.c)
+target_link_libraries(test_mon_req_decode jansson talloc glib-2.0)
+
+add_executable(test_mon_resp_encode mon/mon_common.c mon/mon_req.c mon/mon_resp.c mon/mon_resp_encode.c common/tr_name.c mon/tests/test_mon_resp_encode.c)
+target_link_libraries(test_mon_resp_encode jansson talloc glib-2.0)
index ae745ef..845ce17 100644 (file)
@@ -1,8 +1,10 @@
+ACLOCAL_AMFLAGS = -I m4
 DISTCHECK_CONFIGURE_FLAGS = \
        --with-systemdsystemunitdir=$$dc_install_base/$(systemdsystemunitdir)
 bin_PROGRAMS= tr/trust_router tr/trpc tid/example/tidc tid/example/tids common/tests/tr_dh_test common/tests/mq_test \
               common/tests/thread_test trp/msgtst trp/test/rtbl_test trp/test/ptbl_test common/tests/cfg_test \
-              common/tests/commtest common/tests/name_test common/tests/filt_test
+              common/tests/commtest common/tests/name_test common/tests/filt_test mon/tests/test_mon_req_encode \
+              mon/tests/test_mon_req_decode mon/tests/test_mon_resp_encode
 AM_CPPFLAGS=-I$(srcdir)/include $(GLIB_CFLAGS)
 AM_CFLAGS = -Wall -Werror=missing-prototypes -Werror -Wno-parentheses $(GLIB_CFLAGS)
 SUBDIRS = gsscon 
@@ -35,6 +37,14 @@ trp/trp_upd.c \
 common/tr_config.c \
 common/tr_mq.c
 
+mon_srcs =                      \
+    mon/mon_common.c                \
+    mon/mon_req.c            \
+    mon/mon_req_encode.c     \
+    mon/mon_req_decode.c     \
+    mon/mon_resp.c           \
+    mon/mon_resp_encode.c
+
 check_PROGRAMS = common/t_constraint
 TESTS = common/t_constraint
 TEST_CFLAGS = -Wno-missing-prototypes
@@ -164,6 +174,24 @@ common_tests_filt_test_LDADD = gsscon/libgsscon.la $(GLIB_LIBS)
 common_tests_filt_test_LDFLAGS = $(AM_LDFLAGS) -ltalloc -pthread
 common_tests_filt_test_CFLAGS = $(AM_CFLAGS) $(TEST_CFLAGS)
 
+mon_tests_test_mon_req_encode_SOURCES = mon/tests/test_mon_req_encode.c \
+    $(mon_srcs) \
+    common/tr_name.c
+mon_tests_test_mon_req_encode_LDADD = $(GLIB_LIBS)
+mon_tests_test_mon_req_encode_CFLAGS = $(AM_CFLAGS) $(TEST_CFLAGS)
+
+mon_tests_test_mon_req_decode_SOURCES = mon/tests/test_mon_req_decode.c \
+    $(mon_srcs) \
+    common/tr_name.c
+mon_tests_test_mon_req_decode_LDADD = $(GLIB_LIBS)
+mon_tests_test_mon_req_decode_CFLAGS = $(AM_CFLAGS) $(TEST_CFLAGS)
+
+mon_tests_test_mon_resp_encode_SOURCES = mon/tests/test_mon_resp_encode.c \
+    $(mon_srcs) \
+    common/tr_name.c
+mon_tests_test_mon_resp_encode_LDADD = $(GLIB_LIBS)
+mon_tests_test_mon_resp_encode_CFLAGS = $(AM_CFLAGS) $(TEST_CFLAGS)
+
 pkginclude_HEADERS = include/trust_router/tid.h include/trust_router/tr_name.h \
        include/tr_debug.h include/trust_router/trp.h \
        include/trust_router/tr_dh.h \
diff --git a/include/mon_internal.h b/include/mon_internal.h
new file mode 100644 (file)
index 0000000..58cfdee
--- /dev/null
@@ -0,0 +1,142 @@
+/*
+ * Copyright (c) 2018, JANET(UK)
+ * All rights reserved.
+ *
+ * Redistribution and use in source and binary forms, with or without
+ * modification, are permitted provided that the following conditions
+ * are met:
+ *
+ * 1. Redistributions of source code must retain the above copyright
+ *    notice, this list of conditions and the following disclaimer.
+ *
+ * 2. Redistributions in binary form must reproduce the above copyright
+ *    notice, this list of conditions and the following disclaimer in the
+ *    documentation and/or other materials provided with the distribution.
+ *
+ * 3. Neither the name of JANET(UK) nor the names of its contributors
+ *    may be used to endorse or promote products derived from this software
+ *    without specific prior written permission.
+ *
+ * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
+ * "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
+ * LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS
+ * FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE
+ * COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT,
+ * INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES
+ * (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR
+ * SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION)
+ * HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT,
+ * STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE)
+ * ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED
+ * OF THE POSSIBILITY OF SUCH DAMAGE.
+ *
+ */
+
+
+#ifndef TRUST_ROUTER_MON_REQ_H
+#define TRUST_ROUTER_MON_REQ_H
+
+#include <talloc.h>
+#include <jansson.h>
+#include <gmodule.h>
+#include <tr_name_internal.h>
+
+/* Typedefs */
+typedef struct mon_req MON_REQ;
+typedef struct mon_resp MON_RESP;
+
+typedef enum mon_cmd MON_CMD;
+typedef enum mon_resp_code MON_RESP_CODE;
+
+typedef struct mon_opt MON_OPT;
+typedef enum mon_opt_type MON_OPT_TYPE;
+
+typedef enum mon_rc MON_RC;
+
+
+/* Struct and enum definitions */
+enum mon_rc {
+  MON_SUCCESS=0,
+  MON_ERROR, /* generic error */
+  MON_BADARG, /* problem with the arguments */
+  MON_NOMEM, /* out of memory */
+  MON_NOPARSE, /* parsing failed */
+};
+
+enum mon_cmd {
+  MON_CMD_UNKNOWN=0,
+  MON_CMD_RECONFIGURE,
+  MON_CMD_SHOW
+};
+
+/* These should be explicitly numbered because they form part of the public API */
+enum mon_resp_code {
+  MON_RESP_SUCCESS=0,
+  MON_RESP_ERROR=1, // generic error
+};
+
+enum mon_opt_type {
+  OPT_TYPE_UNKNOWN=0,
+
+  // System information
+  OPT_TYPE_SHOW_VERSION,
+  OPT_TYPE_SHOW_SERIAL,
+
+  // System statistics
+  OPT_TYPE_SHOW_UPTIME,
+  OPT_TYPE_SHOW_TID_REQ_COUNT,
+  OPT_TYPE_SHOW_TID_REQ_PENDING,
+
+  // Dynamic trust router state
+  OPT_TYPE_SHOW_ROUTES,
+  OPT_TYPE_SHOW_COMMUNITIES
+};
+
+struct mon_opt {
+  MON_OPT_TYPE type;
+};
+
+struct mon_req {
+  MON_CMD command;
+  GArray *options;
+};
+
+struct mon_resp {
+  MON_REQ *req; // request this responds to
+  MON_RESP_CODE code;
+  TR_NAME *message;
+  json_t *payload;
+};
+
+/* Prototypes */
+/* tr_mon.c */
+const char *mon_cmd_to_string(MON_CMD cmd);
+MON_CMD mon_cmd_from_string(const char *s);
+const char *mon_opt_type_to_string(MON_OPT_TYPE opt_type);
+MON_OPT_TYPE mon_opt_type_from_string(const char *s);
+
+/* mon_req.c */
+MON_REQ *mon_req_new(TALLOC_CTX *mem_ctx, MON_CMD cmd);
+void mon_req_free(MON_REQ *req);
+MON_RC mon_req_add_option(MON_REQ *req, MON_OPT_TYPE opt_type);
+size_t mon_req_opt_count(MON_REQ *req);
+MON_OPT *mon_req_opt_index(MON_REQ *req, size_t index);
+
+/* mon_req_encode.c */
+json_t *mon_req_encode(MON_REQ *req);
+
+/* mon_req_decode.c */
+MON_REQ *mon_req_decode(TALLOC_CTX *mem_ctx, const char *req_json);
+
+/* mon_resp.c */
+MON_RESP *mon_resp_new(TALLOC_CTX *mem_ctx,
+                          MON_REQ *req,
+                          MON_RESP_CODE code,
+                          const char *msg,
+                          json_t *payload);
+void mon_resp_free(MON_RESP *resp);
+
+/* mon_resp_encode.c */
+json_t *mon_resp_encode(MON_RESP *resp);
+
+#endif //TRUST_ROUTER_MON_REQ_H
diff --git a/mon/mon_common.c b/mon/mon_common.c
new file mode 100644 (file)
index 0000000..d0317f7
--- /dev/null
@@ -0,0 +1,128 @@
+/*
+ * Copyright (c) 2018, JANET(UK)
+ * All rights reserved.
+ *
+ * Redistribution and use in source and binary forms, with or without
+ * modification, are permitted provided that the following conditions
+ * are met:
+ *
+ * 1. Redistributions of source code must retain the above copyright
+ *    notice, this list of conditions and the following disclaimer.
+ *
+ * 2. Redistributions in binary form must reproduce the above copyright
+ *    notice, this list of conditions and the following disclaimer in the
+ *    documentation and/or other materials provided with the distribution.
+ *
+ * 3. Neither the name of JANET(UK) nor the names of its contributors
+ *    may be used to endorse or promote products derived from this software
+ *    without specific prior written permission.
+ *
+ * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
+ * "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
+ * LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS
+ * FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE
+ * COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT,
+ * INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES
+ * (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR
+ * SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION)
+ * HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT,
+ * STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE)
+ * ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED
+ * OF THE POSSIBILITY OF SUCH DAMAGE.
+ *
+ */
+
+
+#include <talloc.h>
+#include <gmodule.h>
+#include <string.h>
+
+#include <mon_internal.h>
+
+// Monitoring common code
+
+/**
+ * This method defines the command strings
+ */
+const char *mon_cmd_to_string(MON_CMD cmd)
+{
+  switch(cmd) {
+    case MON_CMD_UNKNOWN:
+      return NULL;
+
+    case MON_CMD_RECONFIGURE:
+      return "reconfigure";
+
+    case MON_CMD_SHOW:
+      return "show";
+  }
+  return NULL;
+}
+
+// Helper macro for the mon_cmd_from_string method
+#define return_if_matches(s, cmd)                \
+  do {                                           \
+    if (strcmp((s), mon_cmd_to_string(cmd))==0)  \
+      return (cmd);                              \
+  } while(0)
+
+MON_CMD mon_cmd_from_string(const char *s)
+{
+  return_if_matches(s, MON_CMD_RECONFIGURE);
+  return_if_matches(s, MON_CMD_SHOW);
+  return MON_CMD_UNKNOWN;
+}
+#undef return_if_matches
+
+/**
+ * This method defines the option type strings
+ */
+const char *mon_opt_type_to_string(MON_OPT_TYPE opt_type)
+{
+  switch(opt_type) {
+    case OPT_TYPE_UNKNOWN:
+      return NULL;
+
+    case OPT_TYPE_SHOW_VERSION:
+      return "version";
+
+    case OPT_TYPE_SHOW_SERIAL:
+      return "serial";
+
+    case OPT_TYPE_SHOW_UPTIME:
+      return "uptime";
+
+    case OPT_TYPE_SHOW_TID_REQ_COUNT:
+      return "tid_req_count";
+
+    case OPT_TYPE_SHOW_TID_REQ_PENDING:
+      return "tid_req_pending";
+
+    case OPT_TYPE_SHOW_ROUTES:
+      return "routes";
+
+    case OPT_TYPE_SHOW_COMMUNITIES:
+      return "communities";
+  }
+  return NULL;
+}
+
+// Helper macro for the mon_opt_type_from_string method
+#define return_if_matches(s, cmd)                     \
+  do {                                                \
+    if (strcmp((s), mon_opt_type_to_string(cmd))==0)  \
+      return (cmd);                                   \
+  } while(0)
+
+MON_OPT_TYPE mon_opt_type_from_string(const char *s)
+{
+  return_if_matches(s, OPT_TYPE_SHOW_VERSION);
+  return_if_matches(s, OPT_TYPE_SHOW_SERIAL);
+  return_if_matches(s, OPT_TYPE_SHOW_UPTIME);
+  return_if_matches(s, OPT_TYPE_SHOW_TID_REQ_COUNT);
+  return_if_matches(s, OPT_TYPE_SHOW_TID_REQ_PENDING);
+  return_if_matches(s, OPT_TYPE_SHOW_ROUTES);
+  return_if_matches(s, OPT_TYPE_SHOW_COMMUNITIES);
+  return OPT_TYPE_UNKNOWN;
+}
+#undef return_if_matches
diff --git a/mon/mon_req.c b/mon/mon_req.c
new file mode 100644 (file)
index 0000000..dd9de4d
--- /dev/null
@@ -0,0 +1,114 @@
+/*
+ * Copyright (c) 2018, JANET(UK)
+ * All rights reserved.
+ *
+ * Redistribution and use in source and binary forms, with or without
+ * modification, are permitted provided that the following conditions
+ * are met:
+ *
+ * 1. Redistributions of source code must retain the above copyright
+ *    notice, this list of conditions and the following disclaimer.
+ *
+ * 2. Redistributions in binary form must reproduce the above copyright
+ *    notice, this list of conditions and the following disclaimer in the
+ *    documentation and/or other materials provided with the distribution.
+ *
+ * 3. Neither the name of JANET(UK) nor the names of its contributors
+ *    may be used to endorse or promote products derived from this software
+ *    without specific prior written permission.
+ *
+ * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
+ * "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
+ * LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS
+ * FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE
+ * COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT,
+ * INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES
+ * (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR
+ * SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION)
+ * HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT,
+ * STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE)
+ * ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED
+ * OF THE POSSIBILITY OF SUCH DAMAGE.
+ *
+ */
+
+
+#include <talloc.h>
+#include <gmodule.h>
+
+#include <mon_internal.h>
+
+// Monitoring request message common code
+
+/**
+ * Destructor used by talloc to ensure proper cleanup
+ */
+static int mon_req_destructor(void *object)
+{
+  MON_REQ *req = talloc_get_type_abort(object, MON_REQ);
+  if (req->options) {
+    g_array_unref(req->options);
+  }
+  return 0;
+}
+
+/**
+ * Allocate a new monitoring request
+ *
+ * @param mem_ctx talloc context for the new request
+ * @param cmd command for the request
+ * @return newly allocated request, or null on error
+ */
+MON_REQ *mon_req_new(TALLOC_CTX *mem_ctx, MON_CMD cmd)
+{
+  MON_REQ *req=talloc(mem_ctx, MON_REQ);
+  if (req) {
+    req->command = cmd;
+    req->options = g_array_new(FALSE, FALSE, sizeof(MON_OPT));
+    talloc_set_destructor((void *)req, mon_req_destructor);
+  }
+  return req;
+}
+
+/**
+ * Free a monitoring request
+ *
+ * @param req request to free, must not be null
+ */
+void mon_req_free(MON_REQ *req)
+{
+  talloc_free(req);
+}
+
+/**
+ * Add an option to a MON_REQ
+ * @param req request to operate on, not null
+ * @param opt_type type of option
+ * @return MON_SUCCESS on success, error code on error
+ */
+MON_RC mon_req_add_option(MON_REQ *req, MON_OPT_TYPE opt_type)
+{
+  MON_OPT new_opt; // not a pointer
+
+  /* Validate parameters */
+  if ((req == NULL) || (opt_type == OPT_TYPE_UNKNOWN)) {
+    return MON_BADARG;
+  }
+
+  new_opt.type = opt_type;
+
+  /* Add the new option to the list */
+  g_array_append_val(req->options, new_opt);
+  return MON_SUCCESS;
+}
+
+size_t mon_req_opt_count(MON_REQ *req)
+{
+  return req->options->len;
+}
+
+MON_OPT *mon_req_opt_index(MON_REQ *req, size_t index)
+{
+  MON_OPT *result = &g_array_index(req->options, MON_OPT, index);
+  return result;
+}
diff --git a/mon/mon_req_decode.c b/mon/mon_req_decode.c
new file mode 100644 (file)
index 0000000..c1eeed1
--- /dev/null
@@ -0,0 +1,181 @@
+/*
+ * Copyright (c) 2018, JANET(UK)
+ * All rights reserved.
+ *
+ * Redistribution and use in source and binary forms, with or without
+ * modification, are permitted provided that the following conditions
+ * are met:
+ *
+ * 1. Redistributions of source code must retain the above copyright
+ *    notice, this list of conditions and the following disclaimer.
+ *
+ * 2. Redistributions in binary form must reproduce the above copyright
+ *    notice, this list of conditions and the following disclaimer in the
+ *    documentation and/or other materials provided with the distribution.
+ *
+ * 3. Neither the name of JANET(UK) nor the names of its contributors
+ *    may be used to endorse or promote products derived from this software
+ *    without specific prior written permission.
+ *
+ * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
+ * "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
+ * LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS
+ * FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE
+ * COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT,
+ * INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES
+ * (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR
+ * SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION)
+ * HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT,
+ * STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE)
+ * ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED
+ * OF THE POSSIBILITY OF SUCH DAMAGE.
+ *
+ */
+
+
+#include <talloc.h>
+#include <jansson.h>
+
+#include <mon_internal.h>
+
+// Monitoring request decoders
+
+/**
+ * Decode a single option
+ *
+ * Format:
+ * { "type": "some_tpye" }
+ *
+ * @param opt_json JSON object reference
+ * @param dest allocated memory for the result
+ * @return MON_SUCCESS on success, error on error
+ */
+static MON_RC mon_decode_one_opt(json_t *opt_json, MON_OPT *dest)
+{
+  json_t *jstr = NULL;
+  MON_OPT_TYPE opt_type = OPT_TYPE_UNKNOWN;
+
+  if ( (opt_json == NULL) || (dest == NULL))
+    return MON_BADARG;
+
+  if (! json_is_object(opt_json))
+    return MON_NOPARSE;
+
+  jstr = json_object_get(opt_json, "type");
+  if ( (jstr == NULL) || (! json_is_string(jstr)) )
+    return MON_NOPARSE;
+
+  opt_type = mon_opt_type_from_string(json_string_value(jstr));
+  if (opt_type == OPT_TYPE_UNKNOWN)
+    return MON_NOPARSE;
+
+  dest->type = opt_type;
+  return MON_SUCCESS;
+}
+
+/**
+ * Decode options array
+ *
+ * Format:
+ * [{option}, {option}, ...]
+ *
+ */
+static MON_RC mon_options_decode(json_t *opts_json, MON_REQ *req)
+{
+  MON_OPT opt; // not a pointer
+  size_t n_opts=0;
+  size_t ii=0;
+
+  if ( (opts_json == NULL) || (req == NULL))
+    return MON_BADARG;
+
+  if (! json_is_array(opts_json))
+    return MON_NOPARSE;
+
+  n_opts = json_array_size(opts_json);
+  for (ii=0; ii < n_opts; ii++) {
+    if (mon_decode_one_opt(json_array_get(opts_json, ii),
+                           &opt) != MON_SUCCESS) {
+      return MON_NOPARSE;
+    }
+    mon_req_add_option(req, opt.type);
+  }
+  return MON_SUCCESS;
+}
+
+/**
+ * Parse JSON for a request
+ */
+static json_t *mon_req_parse(const char *input)
+{
+  json_t *parsed_json = NULL;
+  json_error_t json_error;
+
+  parsed_json = json_loads(input, JSON_REJECT_DUPLICATES, &json_error);
+  return parsed_json;
+}
+
+/**
+ * Decode a JSON request
+ *
+ * Expected format:
+ * {
+ *   "command": "some_command_name",
+ *   "options": [{option1}, ...]
+ * }
+ *
+ * (options are optional)
+ *
+ * Caller must free the return value with MON_REQ_free().
+ *
+ * @param mem_ctx talloc context for the returned struct
+ * @param req_json reference to JSON request object
+ * @return decoded request struct or NULL on failure
+ */
+MON_REQ *mon_req_decode(TALLOC_CTX *mem_ctx, const char *req_str)
+{
+  TALLOC_CTX *tmp_ctx = talloc_new(NULL);
+  MON_REQ *req = NULL;
+  json_t *req_json = NULL;
+  json_t *jval = NULL;
+  json_t *opts_json = NULL;
+  MON_CMD cmd = MON_CMD_UNKNOWN;
+
+  req_json = mon_req_parse(req_str); // TODO: Check errors
+
+  if (! json_is_object(req_json))
+    goto cleanup;
+
+  // Get the command and verify that it is a string value
+  jval = json_object_get(req_json, "command");
+  if (! json_is_string(jval))
+    goto cleanup;
+
+  cmd = mon_cmd_from_string(json_string_value(jval));
+  if (cmd == MON_CMD_UNKNOWN)
+    goto cleanup;
+
+  /* Command is good. Allocate the request in the tmp context */
+  req = mon_req_new(tmp_ctx, cmd);
+  if (req == NULL)
+    goto cleanup;
+
+  /* Parse options if we have any */
+  opts_json = json_object_get(req_json, "options");
+  if (opts_json) {
+    if (mon_options_decode(opts_json, req) != MON_SUCCESS) {
+      req = NULL; // memory still in tmp_ctx, so it will be cleaned up
+      goto cleanup;
+    }
+  }
+
+  /* Success! Put the request in the caller's talloc context */
+  talloc_steal(mem_ctx, req);
+
+cleanup:
+  talloc_free(tmp_ctx);
+  if (req_json)
+    json_decref(req_json);
+
+  return req;
+}
diff --git a/mon/mon_req_encode.c b/mon/mon_req_encode.c
new file mode 100644 (file)
index 0000000..5bcf8b2
--- /dev/null
@@ -0,0 +1,161 @@
+/*
+ * Copyright (c) 2018, JANET(UK)
+ * All rights reserved.
+ *
+ * Redistribution and use in source and binary forms, with or without
+ * modification, are permitted provided that the following conditions
+ * are met:
+ *
+ * 1. Redistributions of source code must retain the above copyright
+ *    notice, this list of conditions and the following disclaimer.
+ *
+ * 2. Redistributions in binary form must reproduce the above copyright
+ *    notice, this list of conditions and the following disclaimer in the
+ *    documentation and/or other materials provided with the distribution.
+ *
+ * 3. Neither the name of JANET(UK) nor the names of its contributors
+ *    may be used to endorse or promote products derived from this software
+ *    without specific prior written permission.
+ *
+ * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
+ * "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
+ * LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS
+ * FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE
+ * COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT,
+ * INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES
+ * (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR
+ * SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION)
+ * HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT,
+ * STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE)
+ * ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED
+ * OF THE POSSIBILITY OF SUCH DAMAGE.
+ *
+ */
+
+
+#include <talloc.h>
+#include <jansson.h>
+#include <glib.h>
+
+#include <mon_internal.h>
+
+// Monitoring request encoders
+
+/**
+ * Encode options array as a JSON array
+ *
+ * Format:
+ * [
+ *   { "type": "first_type" },
+ *   { "type": "second_type"},
+ *   ...
+ * ]
+ *
+ * @param opts array of options
+ * @return reference to a JSON array of options
+ */
+static json_t *mon_opts_decode(GArray *opts)
+{
+  json_t *array_json = json_array(); // the array of options
+  json_t *opt_json = NULL; // individual option JSON object
+  json_t *type_json = NULL;
+  guint ii = 0;
+  MON_OPT this_opt;
+
+  if (array_json == NULL)
+    return NULL; // failed
+
+  /* Iterate over the options */
+  for (ii=0; ii < opts->len; ii++) {
+    this_opt = g_array_index(opts, MON_OPT, ii);
+
+    /* Create the JSON object for this option */
+    opt_json = json_object();
+    if (opt_json == NULL) {
+      json_decref(array_json);
+      return NULL;
+    }
+
+    /* Add to the array, making opt_json a borrowed ref if we succeed */
+    if (json_array_append_new(array_json, opt_json) == -1) {
+      json_decref(array_json);
+      json_decref(opt_json); // handle ourselves because the set failed
+    }
+
+    /* Create the type string for this option */
+    type_json = json_string(mon_opt_type_to_string(this_opt.type));
+    if (type_json == NULL) {
+      json_decref(array_json);
+      return NULL;
+    }
+
+    /* Add the type string to the JSON object, making type_json a borrowed ref */
+    if (json_object_set_new(opt_json, "type", type_json) == -1) {
+      json_decref(array_json);
+      json_decref(type_json); // must handle ourselves because the set failed
+      return NULL;
+    }
+  }
+
+  return array_json;
+}
+
+/**
+ * Encode a request as a JSON object
+ *
+ * Caller must free the return value using json_decref()
+ *
+ * Format:
+ * {
+ *   "command": "some_command",
+ *   "options": [...see mon_opts_to_json()...]
+ * }
+ *
+ * @param req request to encode
+ * @return reference to a JSON object
+ */
+json_t *mon_req_encode(MON_REQ *req)
+{
+  json_t *req_json = NULL;
+  json_t *cmd_json = NULL;
+  json_t *opts_json = NULL;
+
+  /* Allocate the base JSON object */
+  req_json = json_object();
+  if (req_json == NULL)
+    return NULL;
+
+  /* Allocate the JSON string for the command */
+  cmd_json = json_string(mon_cmd_to_string(req->command));
+  if (cmd_json == NULL) {
+    json_decref(req_json);
+    return NULL;
+  }
+
+  /* Add the command string to the base object. Steals the reference to
+   * the string if successful. */
+  if (json_object_set_new(req_json, "command", cmd_json) == -1) {
+    json_decref(cmd_json); // must clean this up ourselves because the set failed
+    json_decref(req_json);
+    return NULL;
+  }
+
+  /* If we have options, add them to the object */
+  if (req->options->len > 0) {
+    opts_json = mon_opts_decode(req->options);
+    if (opts_json == NULL) {
+      json_decref(req_json);
+      return NULL;
+    }
+
+    if (json_object_set_new(req_json, "options", opts_json) == -1) {
+      json_decref(req_json);
+      json_decref(opts_json); // must clean this up ourselves because set failed
+      return NULL;
+    }
+  }
+
+  /* That's it, we succeeded */
+  return req_json;
+}
+
diff --git a/mon/mon_resp.c b/mon/mon_resp.c
new file mode 100644 (file)
index 0000000..e1f0761
--- /dev/null
@@ -0,0 +1,110 @@
+/*
+ * Copyright (c) 2018, JANET(UK)
+ * All rights reserved.
+ *
+ * Redistribution and use in source and binary forms, with or without
+ * modification, are permitted provided that the following conditions
+ * are met:
+ *
+ * 1. Redistributions of source code must retain the above copyright
+ *    notice, this list of conditions and the following disclaimer.
+ *
+ * 2. Redistributions in binary form must reproduce the above copyright
+ *    notice, this list of conditions and the following disclaimer in the
+ *    documentation and/or other materials provided with the distribution.
+ *
+ * 3. Neither the name of JANET(UK) nor the names of its contributors
+ *    may be used to endorse or promote products derived from this software
+ *    without specific prior written permission.
+ *
+ * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
+ * "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
+ * LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS
+ * FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE
+ * COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT,
+ * INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES
+ * (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR
+ * SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION)
+ * HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT,
+ * STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE)
+ * ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED
+ * OF THE POSSIBILITY OF SUCH DAMAGE.
+ *
+ */
+
+
+#include <talloc.h>
+#include <tr_name_internal.h>
+
+#include <mon_internal.h>
+
+// Monitoring request message common code
+
+/**
+ * Destructor used by talloc to ensure proper cleanup
+ */
+static int mon_resp_destructor(void *object)
+{
+  MON_RESP *resp = talloc_get_type_abort(object, MON_RESP);
+  /* free the message */
+  if (resp->message) {
+    tr_free_name(resp->message);
+  }
+  /* free the payload */
+  if (resp->payload) {
+    json_decref(resp->payload);
+  }
+  return 0;
+}
+
+/**
+ * Allocate a new monitoring response
+ *
+ * Caller must free using mon_resp_free().
+ *
+ * Makes its own copy of the message, so caller can dispose of
+ * that after allocating the response.
+ *
+ * Steals the reference to the payload JSON object. Does not modify the
+ * object. Caller should not modify it after allocating the response or
+ * undefined behavior will result. If allocation fails, the stolen reference
+ * will be released --- if you need to keep a reference, use incref before
+ * calling this.
+ *
+ * @param mem_ctx talloc context for allocation
+ * @param req MON_REQ this response corresponds to
+ * @param code numeric response code
+ * @param msg string description of response code
+ * @param payload JSON object to be send as payload, or null for no payload
+ * @return response allocated in the requested talloc context, null on failure
+ */
+MON_RESP *mon_resp_new(TALLOC_CTX *mem_ctx,
+                          MON_REQ *req,
+                          MON_RESP_CODE code,
+                          const char *msg,
+                          json_t *payload)
+{
+  MON_RESP *resp = talloc(mem_ctx, MON_RESP);
+  if (resp) {
+    resp->req = req;
+    resp->code = code;
+    resp->message = tr_new_name(msg);
+    resp->payload = payload;
+    talloc_set_destructor((void *)resp, mon_resp_destructor);
+    if (resp->message == NULL) {
+      talloc_free(resp); // destructor will be called
+      resp = NULL;
+    }
+  }
+  return resp;
+}
+
+/**
+ * Free a monitoring response
+ *
+ * @param resp request to free, must not be null
+ */
+void mon_resp_free(MON_RESP *resp)
+{
+  talloc_free(resp);
+}
diff --git a/mon/mon_resp_encode.c b/mon/mon_resp_encode.c
new file mode 100644 (file)
index 0000000..2a6e5fe
--- /dev/null
@@ -0,0 +1,87 @@
+/*
+ * Copyright (c) 2018, JANET(UK)
+ * All rights reserved.
+ *
+ * Redistribution and use in source and binary forms, with or without
+ * modification, are permitted provided that the following conditions
+ * are met:
+ *
+ * 1. Redistributions of source code must retain the above copyright
+ *    notice, this list of conditions and the following disclaimer.
+ *
+ * 2. Redistributions in binary form must reproduce the above copyright
+ *    notice, this list of conditions and the following disclaimer in the
+ *    documentation and/or other materials provided with the distribution.
+ *
+ * 3. Neither the name of JANET(UK) nor the names of its contributors
+ *    may be used to endorse or promote products derived from this software
+ *    without specific prior written permission.
+ *
+ * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
+ * "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
+ * LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS
+ * FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE
+ * COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT,
+ * INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES
+ * (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR
+ * SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION)
+ * HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT,
+ * STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE)
+ * ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED
+ * OF THE POSSIBILITY OF SUCH DAMAGE.
+ *
+ */
+
+#include <talloc.h>
+#include <jansson.h>
+
+#include <mon_internal.h>
+
+/* Helper for encoding. Adds a newly allocated JSON object to
+ * jobj. If the allocation or setting fails, returns NULL after
+ * cleaning up. */
+#define object_set_or_free_and_return(jobj, tmp_jval, key, jval)  \
+  do {                                                            \
+    (tmp_jval) = (jval);                                          \
+    if ( (tmp_jval) == NULL) {                                    \
+      json_decref(jobj);                                          \
+      return NULL;                                                \
+    }                                                             \
+    if (json_object_set_new((jobj), (key), (tmp_jval)) == -1) {   \
+      json_decref(tmp_jval);                                      \
+      json_decref(jobj);                                          \
+      return NULL;                                                \
+    }                                                             \
+  } while(0)
+
+/**
+ * Encode a monitoring response as a JSON object
+ *
+ * Caller must ensure json_decref() is used to free the return value.
+ *
+ * @param resp response to encode
+ * @return response as a newly allocated JSON object
+ */
+json_t *mon_resp_encode(MON_RESP *resp)
+{
+  json_t *resp_json = NULL;
+  json_t *jval = NULL;
+  const char *cmd_str = NULL;
+
+  /* Get a JSON object */
+  resp_json = json_object();
+  if (resp_json == NULL)
+    return NULL;
+
+  /* Add properties, cleaning up and returning NULL on failure */
+  object_set_or_free_and_return(resp_json, jval, "code",    json_integer(resp->code));
+  object_set_or_free_and_return(resp_json, jval, "message", tr_name_to_json_string(resp->message));
+
+  /* If we have a payload, add it */
+  if (resp->payload) {
+    cmd_str = mon_cmd_to_string(resp->req->command); // key for the response payload
+    object_set_or_free_and_return(resp_json, jval, cmd_str, resp->payload);
+  }
+
+  return resp_json;
+}
diff --git a/mon/tests/req_reconfigure.test b/mon/tests/req_reconfigure.test
new file mode 100644 (file)
index 0000000..75f1690
--- /dev/null
@@ -0,0 +1 @@
+{"command": "reconfigure"}
diff --git a/mon/tests/req_show_all_options.test b/mon/tests/req_show_all_options.test
new file mode 100644 (file)
index 0000000..799be44
--- /dev/null
@@ -0,0 +1 @@
+{"command": "show", "options": [{"type": "serial"}, {"type": "version"}, {"type": "uptime"}, {"type": "tid_req_count"}, {"type": "tid_req_pending"}, {"type": "routes"}, {"type": "communities"}]}
diff --git a/mon/tests/req_show_no_options.test b/mon/tests/req_show_no_options.test
new file mode 100644 (file)
index 0000000..af1cdce
--- /dev/null
@@ -0,0 +1 @@
+{"command": "show"}
diff --git a/mon/tests/resp_reconfigure_error.test b/mon/tests/resp_reconfigure_error.test
new file mode 100644 (file)
index 0000000..3344bb1
--- /dev/null
@@ -0,0 +1 @@
+{"code": 1, "message": "error"}
diff --git a/mon/tests/resp_reconfigure_success.test b/mon/tests/resp_reconfigure_success.test
new file mode 100644 (file)
index 0000000..392c2e0
--- /dev/null
@@ -0,0 +1 @@
+{"code": 0, "message": "success"}
diff --git a/mon/tests/resp_show_success.test b/mon/tests/resp_show_success.test
new file mode 100644 (file)
index 0000000..bfbec19
--- /dev/null
@@ -0,0 +1 @@
+{"code": 0, "message": "success", "show": {"version": "1.2.3-4", "serial": 86400, "tid_req_pending": 13, "tid_req_count": 1432}}
diff --git a/mon/tests/test_mon_req_decode.c b/mon/tests/test_mon_req_decode.c
new file mode 100644 (file)
index 0000000..63570b9
--- /dev/null
@@ -0,0 +1,137 @@
+//
+// Created by jlr on 4/9/18.
+//
+
+#include <talloc.h>
+#include <jansson.h>
+#include <assert.h>
+#include <string.h>
+#include <glib.h>
+
+#include <mon_internal.h>
+
+/**
+ * @return reconfigure command
+ */
+static MON_REQ *reconfigure()
+{
+  MON_REQ *req = mon_req_new(NULL, MON_CMD_RECONFIGURE);
+  assert(req);
+  return req;
+}
+
+/**
+ * @return show command with no options
+ */
+static MON_REQ *show_plain()
+{
+  MON_REQ *req = mon_req_new(NULL, MON_CMD_SHOW);
+  assert(req);
+  return req;
+}
+
+/**
+ * @param opts array of option types, terminated with OPT_TYPE_UNKNOWN
+ * @return show command with the requested options, excluding the terminator
+ */
+static MON_REQ *show_options(const MON_OPT_TYPE *opts)
+{
+  MON_REQ *req = mon_req_new(NULL, MON_CMD_SHOW);
+  assert(req);
+
+  while (*opts != OPT_TYPE_UNKNOWN) {
+    assert(MON_SUCCESS == mon_req_add_option(req, *opts));
+    opts++;
+  }
+  return req;
+}
+
+/**
+ * @return show command with every option
+ */
+static MON_REQ *show_all_options()
+{
+  MON_OPT_TYPE opts[] = {
+      OPT_TYPE_SHOW_SERIAL,
+      OPT_TYPE_SHOW_VERSION,
+      OPT_TYPE_SHOW_UPTIME,
+      OPT_TYPE_SHOW_TID_REQ_COUNT,
+      OPT_TYPE_SHOW_TID_REQ_PENDING,
+      OPT_TYPE_SHOW_ROUTES,
+      OPT_TYPE_SHOW_COMMUNITIES,
+      OPT_TYPE_UNKNOWN // terminator
+  };
+
+  return show_options(opts);
+}
+
+static char *read_file(const char *filename)
+{
+  FILE *f = fopen(filename, "r");
+  char *s = NULL;
+  size_t nn = 0;
+  ssize_t n = getline(&s, &nn, f);
+
+  fclose(f);
+
+  if( (n > 0) && (s[n-1] == '\n'))
+    s[n-1] = 0;
+
+  return s;
+}
+
+static int equal(MON_REQ *r1, MON_REQ *r2)
+{
+  size_t ii;
+
+  if (r1->command != r2->command)
+    return 0;
+
+  if (mon_req_opt_count(r1) != mon_req_opt_count(r2))
+    return 0;
+
+  for (ii=0; ii < mon_req_opt_count(r1); ii++) {
+    if (mon_req_opt_index(r1, ii)->type != mon_req_opt_index(r2, ii)->type)
+      return 0;
+  }
+
+  return 1;
+}
+
+static int run_test(const char *filename, MON_REQ *(generator)())
+{
+  MON_REQ *req = NULL;
+  MON_REQ *expected = NULL;
+  char *req_json_str = NULL;
+
+  expected = generator();
+  assert(expected);
+
+  req_json_str = read_file(filename);
+  assert(req_json_str);
+
+  req = mon_req_decode(NULL, req_json_str);
+  assert(req);
+  assert(equal(req, expected));
+
+  free(req_json_str);
+  mon_req_free(req);
+  mon_req_free(expected);
+
+  return 1;
+}
+
+int main(void)
+{
+
+  // Test reconfigure command
+  assert(run_test("req_reconfigure.test", reconfigure));
+
+  // Test show command with no options
+  assert(run_test("req_show_no_options.test", show_plain));
+
+  // Test show command with all the options
+  assert(run_test("req_show_all_options.test", show_all_options));
+
+  return 0;
+}
\ No newline at end of file
diff --git a/mon/tests/test_mon_req_encode.c b/mon/tests/test_mon_req_encode.c
new file mode 100644 (file)
index 0000000..80811fd
--- /dev/null
@@ -0,0 +1,126 @@
+//
+// Created by jlr on 4/9/18.
+//
+
+#include <talloc.h>
+#include <jansson.h>
+#include <assert.h>
+#include <string.h>
+#include <glib.h>
+
+#include <mon_internal.h>
+
+#define JSON_DUMP_OPTS 0
+
+static char *reconfigure()
+{
+  MON_REQ *req = mon_req_new(NULL, MON_CMD_RECONFIGURE);
+  json_t *req_json = mon_req_encode(req);
+  char *result = json_dumps(req_json, JSON_DUMP_OPTS);
+  assert(req);
+  assert(req_json);
+  assert(result);
+  json_decref(req_json);
+  mon_req_free(req);
+  return result;
+}
+
+static char *show_plain()
+{
+  MON_REQ *req = mon_req_new(NULL, MON_CMD_SHOW);
+  json_t *req_json = mon_req_encode(req);
+  char *result = json_dumps(req_json, JSON_DUMP_OPTS);
+  assert(req);
+  assert(req_json);
+  assert(result);
+  json_decref(req_json);
+  mon_req_free(req);
+  return result;
+}
+
+static char *show_options(const MON_OPT_TYPE *opts)
+{
+  MON_REQ *req = mon_req_new(NULL, MON_CMD_SHOW);
+  json_t *req_json = NULL;
+  char *result = NULL;
+
+  assert(req);
+
+  while (*opts != OPT_TYPE_UNKNOWN) {
+    assert(MON_SUCCESS == mon_req_add_option(req, *opts));
+    opts++;
+  }
+
+  req_json = mon_req_encode(req);
+  assert(req_json);
+
+  result = json_dumps(req_json, JSON_DUMP_OPTS);
+  assert(result);
+
+  json_decref(req_json);
+  mon_req_free(req);
+  return result;
+}
+
+static char *read_file(const char *filename)
+{
+  FILE *f = fopen(filename, "r");
+  char *s = NULL;
+  size_t nn = 0;
+  ssize_t n = getline(&s, &nn, f);
+  fclose(f);
+
+  if( (n > 0) && (s[n-1] == '\n'))
+    s[n-1] = 0;
+
+  return s;
+}
+int main(void)
+{
+  char *s = NULL;
+  MON_OPT_TYPE opts[10];
+  char *expected = NULL;
+
+  // Test reconfigure command
+  s = reconfigure();
+  expected = read_file("req_reconfigure.test");
+  assert(expected);
+  assert(strcmp(expected, s) == 0);
+  free(s);
+  free(expected);
+
+  // Test show without options
+  s = show_plain();
+  expected = read_file("req_show_no_options.test");
+  assert(expected);
+  assert(strcmp(expected, s) == 0);
+  free(s);
+  free(expected);
+
+  // Test show with empty options (this mostly tests the test)
+  opts[0] = OPT_TYPE_UNKNOWN;
+  s = show_options(opts);
+  expected = read_file("req_show_no_options.test");
+  assert(expected);
+  assert(strcmp(expected, s) == 0);
+  free(s);
+  free(expected);
+
+  // Test show with many options
+  opts[0] = OPT_TYPE_SHOW_SERIAL;
+  opts[1] = OPT_TYPE_SHOW_VERSION;
+  opts[2] = OPT_TYPE_SHOW_UPTIME;
+  opts[3] = OPT_TYPE_SHOW_TID_REQ_COUNT;
+  opts[4] = OPT_TYPE_SHOW_TID_REQ_PENDING;
+  opts[5] = OPT_TYPE_SHOW_ROUTES;
+  opts[6] = OPT_TYPE_SHOW_COMMUNITIES;
+  opts[7] = OPT_TYPE_UNKNOWN;
+  s = show_options(opts);
+  expected = read_file("req_show_all_options.test");
+  assert(expected);
+  assert(strcmp(expected, s) == 0);
+  free(s);
+  free(expected);
+
+  return 0;
+}
\ No newline at end of file
diff --git a/mon/tests/test_mon_resp_encode.c b/mon/tests/test_mon_resp_encode.c
new file mode 100644 (file)
index 0000000..b4ecdfe
--- /dev/null
@@ -0,0 +1,130 @@
+//
+// Created by jlr on 4/9/18.
+//
+
+#include <talloc.h>
+#include <jansson.h>
+#include <assert.h>
+#include <string.h>
+
+#include <mon_internal.h>
+
+#define JSON_DUMP_OPTS 0
+
+static char *reconfigure(MON_RESP_CODE code, const char *message)
+{
+  MON_REQ *req = NULL;
+  MON_RESP *resp = NULL;
+  json_t *resp_json = NULL;
+  char *result = NULL;
+
+  req = mon_req_new(NULL, MON_CMD_RECONFIGURE);
+  assert(req);
+
+  resp = mon_resp_new(NULL, req, code, message, NULL);
+  assert(resp);
+
+  resp_json = mon_resp_encode(resp);
+  assert(resp_json);
+
+  result = json_dumps(resp_json, JSON_DUMP_OPTS);
+  assert(result);
+
+  json_decref(resp_json);
+  mon_resp_free(resp);
+  mon_req_free(req);
+  return result;
+}
+
+static char *reconfigure_success()
+{
+  return reconfigure(MON_RESP_SUCCESS, "success");
+}
+
+static char *reconfigure_error()
+{
+  return reconfigure(MON_RESP_ERROR, "error");
+}
+
+static char *show_success()
+{
+  MON_REQ *req = NULL;
+  MON_RESP *resp = NULL;
+  json_t *resp_json = NULL;
+  json_t *payload = NULL;
+  char *result = NULL;
+
+  req = mon_req_new(NULL, MON_CMD_SHOW);
+  // Only need the command to be set in req, don't actually need the options
+  assert(req);
+
+  payload = json_object();
+  assert(payload);
+  assert(! json_object_set_new(payload,
+                               mon_opt_type_to_string(OPT_TYPE_SHOW_VERSION),
+                               json_string("1.2.3-4")));
+  assert(! json_object_set_new(payload,
+                               mon_opt_type_to_string(OPT_TYPE_SHOW_SERIAL),
+                               json_integer(1234567890)));
+  assert(! json_object_set_new(payload,
+                               mon_opt_type_to_string(OPT_TYPE_SHOW_SERIAL),
+                               json_integer(86400)));
+  assert(! json_object_set_new(payload,
+                               mon_opt_type_to_string(OPT_TYPE_SHOW_TID_REQ_PENDING),
+                               json_integer(13)));
+  assert(! json_object_set_new(payload,
+                               mon_opt_type_to_string(OPT_TYPE_SHOW_TID_REQ_COUNT),
+                               json_integer(1432)));
+
+  resp = mon_resp_new(NULL, req, MON_RESP_SUCCESS, "success", payload);
+  assert(resp);
+
+  resp_json = mon_resp_encode(resp);
+  assert(resp_json);
+
+  result = json_dumps(resp_json, JSON_DUMP_OPTS);
+  assert(result);
+
+  json_decref(resp_json);
+  mon_resp_free(resp);
+  mon_req_free(req);
+  return result;
+}
+
+static char *read_file(const char *filename)
+{
+  FILE *f = fopen(filename, "r");
+  char *s = NULL;
+  size_t nn = 0;
+  ssize_t n = getline(&s, &nn, f);
+  fclose(f);
+
+  if( (n > 0) && (s[n-1] == '\n'))
+    s[n-1] = 0;
+
+  return s;
+}
+
+int run_test(const char *filename, char *(generator)())
+{
+  char *s = NULL;
+  char *expected = NULL;
+
+  // Test reconfigure command
+  s = generator();
+  expected = read_file(filename);
+  assert(expected);
+  assert(strcmp(expected, s) == 0);
+  free(s);
+  free(expected);
+
+  return 1;
+}
+
+int main(void)
+{
+  assert(run_test("resp_reconfigure_success.test", reconfigure_success));
+  assert(run_test("resp_reconfigure_error.test", reconfigure_error));
+  assert(run_test("resp_show_success.test", show_success));
+  return 0;
+}