From: Jennifer Richards Date: Wed, 11 Apr 2018 02:05:12 +0000 (-0400) Subject: First pass at monitoring request encoder/decoder and tests X-Git-Tag: 3.4.0~1^2~50^2~4 X-Git-Url: http://www.project-moonshot.org/gitweb/?p=trust_router.git;a=commitdiff_plain;h=48283a0470f5605dc45f08f49f5dc22ae3323840 First pass at monitoring request encoder/decoder and tests Works, but not yet integrated with the build system. --- diff --git a/mon/test/req_reconfigure.test b/mon/test/req_reconfigure.test new file mode 100644 index 0000000..75f1690 --- /dev/null +++ b/mon/test/req_reconfigure.test @@ -0,0 +1 @@ +{"command": "reconfigure"} diff --git a/mon/test/req_show_all_options.test b/mon/test/req_show_all_options.test new file mode 100644 index 0000000..799be44 --- /dev/null +++ b/mon/test/req_show_all_options.test @@ -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/test/req_show_no_options.test b/mon/test/req_show_no_options.test new file mode 100644 index 0000000..af1cdce --- /dev/null +++ b/mon/test/req_show_no_options.test @@ -0,0 +1 @@ +{"command": "show"} diff --git a/mon/test/test_mon_req_decode.c b/mon/test/test_mon_req_decode.c new file mode 100644 index 0000000..83ccd43 --- /dev/null +++ b/mon/test/test_mon_req_decode.c @@ -0,0 +1,137 @@ +// +// Created by jlr on 4/9/18. +// + +#include +#include +#include +#include +#include + +#include "../tr_mon_req.h" + +/** + * @return reconfigure command + */ +TR_MON_REQ *reconfigure() +{ + TR_MON_REQ *req = tr_mon_req_new(NULL, MON_CMD_RECONFIGURE); + assert(req); + return req; +} + +/** + * @return show command with no options + */ +TR_MON_REQ *show_plain() +{ + TR_MON_REQ *req = tr_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 + */ +TR_MON_REQ *show_options(const TR_MON_OPT_TYPE *opts) +{ + TR_MON_REQ *req = tr_mon_req_new(NULL, MON_CMD_SHOW); + assert(req); + + while (*opts != OPT_TYPE_UNKNOWN) { + assert(TR_MON_SUCCESS == tr_mon_req_add_option(req, *opts)); + opts++; + } + return req; +} + +/** + * @return show command with every option + */ +TR_MON_REQ *show_all_options() +{ + TR_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); +} + +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 equal(TR_MON_REQ *r1, TR_MON_REQ *r2) +{ + size_t ii; + + if (r1->command != r2->command) + return 0; + + if (tr_mon_req_opt_count(r1) != tr_mon_req_opt_count(r2)) + return 0; + + for (ii=0; ii < tr_mon_req_opt_count(r1); ii++) { + if (tr_mon_req_opt_index(r1, ii)->type != tr_mon_req_opt_index(r2, ii)->type) + return 0; + } + + return 1; +} + +int run_test(const char *filename, TR_MON_REQ *(generator)()) +{ + TR_MON_REQ *req = NULL; + TR_MON_REQ *expected = NULL; + char *req_json_str = NULL; + + expected = generator(); + assert(expected); + + req_json_str = read_file(filename); + assert(req_json_str); + + req = tr_mon_req_decode(NULL, req_json_str); + assert(req); + assert(equal(req, expected)); + + free(req_json_str); + tr_mon_req_free(req); + tr_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/test/test_mon_req_encode.c b/mon/test/test_mon_req_encode.c new file mode 100644 index 0000000..4a6214a --- /dev/null +++ b/mon/test/test_mon_req_encode.c @@ -0,0 +1,126 @@ +// +// Created by jlr on 4/9/18. +// + +#include +#include +#include +#include +#include + +#include "../tr_mon_req.h" + +#define JSON_DUMP_OPTS 0 + +char *reconfigure() +{ + TR_MON_REQ *req = tr_mon_req_new(NULL, MON_CMD_RECONFIGURE); + json_t *req_json = tr_mon_req_encode(req); + char *result = json_dumps(req_json, JSON_DUMP_OPTS); + assert(req); + assert(req_json); + assert(result); + json_decref(req_json); + tr_mon_req_free(req); + return result; +} + +char *show_plain() +{ + TR_MON_REQ *req = tr_mon_req_new(NULL, MON_CMD_SHOW); + json_t *req_json = tr_mon_req_encode(req); + char *result = json_dumps(req_json, JSON_DUMP_OPTS); + assert(req); + assert(req_json); + assert(result); + json_decref(req_json); + tr_mon_req_free(req); + return result; +} + +char *show_options(const TR_MON_OPT_TYPE *opts) +{ + TR_MON_REQ *req = tr_mon_req_new(NULL, MON_CMD_SHOW); + json_t *req_json = NULL; + char *result = NULL; + + assert(req); + + while (*opts != OPT_TYPE_UNKNOWN) { + assert(TR_MON_SUCCESS == tr_mon_req_add_option(req, *opts)); + opts++; + } + + req_json = tr_mon_req_encode(req); + assert(req_json); + + result = json_dumps(req_json, JSON_DUMP_OPTS); + assert(result); + + json_decref(req_json); + tr_mon_req_free(req); + return result; +} + +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; + TR_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/tr_mon_req.c b/mon/tr_mon_req.c new file mode 100644 index 0000000..ec058f3 --- /dev/null +++ b/mon/tr_mon_req.c @@ -0,0 +1,199 @@ +/* + * 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 +#include +#include + +#include "tr_mon_req.h" + +// Monitoring request message common code + +/** + * Destructor used by talloc to ensure proper cleanup + */ +static int tr_mon_req_destructor(void *object) +{ + TR_MON_REQ *req = talloc_get_type_abort(object, TR_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 + */ +TR_MON_REQ *tr_mon_req_new(TALLOC_CTX *mem_ctx, TR_MON_CMD cmd) +{ + TR_MON_REQ *req=talloc(mem_ctx, TR_MON_REQ); + if (req) { + req->command = cmd; + req->options = g_array_new(FALSE, FALSE, sizeof(TR_MON_OPT)); + talloc_set_destructor((void *)req, tr_mon_req_destructor); + } + return req; +} + +/** + * Free a monitoring request + * + * @param req request to free, must not be null + */ +void tr_mon_req_free(TR_MON_REQ *req) +{ + talloc_free(req); +} + +/** + * Add an option to a TR_MON_REQ + * @param req request to operate on, not null + * @param opt_type type of option + * @return TR_MON_SUCCESS on success, error code on error + */ +TR_MON_RC tr_mon_req_add_option(TR_MON_REQ *req, TR_MON_OPT_TYPE opt_type) +{ + TR_MON_OPT new_opt; // not a pointer + + /* Validate parameters */ + if ((req == NULL) || (opt_type == OPT_TYPE_UNKNOWN)) { + return TR_MON_BADARG; + } + + new_opt.type = opt_type; + + /* Add the new option to the list */ + g_array_append_val(req->options, new_opt); + return TR_MON_SUCCESS; +} + +size_t tr_mon_req_opt_count(TR_MON_REQ *req) +{ + return req->options->len; +} + +TR_MON_OPT *tr_mon_req_opt_index(TR_MON_REQ *req, size_t index) +{ + TR_MON_OPT *result = (TR_MON_OPT *) &g_array_index(req->options, TR_MON_OPT, index); + return result; +} + +/** + * This method defines the command strings + */ +const char *cmd_to_string(TR_MON_CMD cmd) +{ + switch(cmd) { + case MON_CMD_UNKNOWN: + return NULL; + + case MON_CMD_RECONFIGURE: + return "reconfigure"; + + case MON_CMD_SHOW: + return "show"; + } +} + +// Helper macro for the cmd_from_string method +#define return_if_matches(s, cmd) \ + do { \ + if (strcmp((s), cmd_to_string(cmd))==0) \ + return (cmd); \ + } while(0) + +TR_MON_CMD 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 *opt_type_to_string(TR_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"; + } +} + +// Helper macro for the opt_type_from_string method +#define return_if_matches(s, cmd) \ + do { \ + if (strcmp((s), opt_type_to_string(cmd))==0) \ + return (cmd); \ + } while(0) + +TR_MON_OPT_TYPE 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/tr_mon_req.h b/mon/tr_mon_req.h new file mode 100644 index 0000000..d32b262 --- /dev/null +++ b/mon/tr_mon_req.h @@ -0,0 +1,116 @@ +/* + * 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_TR_MON_REQ_H +#define TRUST_ROUTER_TR_MON_REQ_H + +#include +#include +#include + +/* Typedefs */ +typedef struct tr_mon_req TR_MON_REQ; + +typedef enum tr_mon_cmd TR_MON_CMD; + +typedef struct tr_mon_opt TR_MON_OPT; +typedef enum tr_mon_opt_type TR_MON_OPT_TYPE; + +typedef enum tr_mon_rc TR_MON_RC; + + +/* Struct and enum definitions */ +enum tr_mon_rc { + TR_MON_SUCCESS=0, + TR_MON_ERROR, /* generic error */ + TR_MON_BADARG, /* problem with the arguments */ + TR_MON_NOMEM, /* out of memory */ + TR_MON_NOPARSE, /* parsing failed */ +}; + +enum tr_mon_cmd { + MON_CMD_UNKNOWN=0, + MON_CMD_RECONFIGURE, + MON_CMD_SHOW +}; + +enum tr_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 tr_mon_opt { + TR_MON_OPT_TYPE type; +}; + +struct tr_mon_req { + TR_MON_CMD command; + GArray *options; +}; + +/* Prototypes */ +TR_MON_REQ *tr_mon_req_new(TALLOC_CTX *mem_ctx, TR_MON_CMD cmd); +void tr_mon_req_free(TR_MON_REQ *req); +TR_MON_RC tr_mon_req_add_option(TR_MON_REQ *req, TR_MON_OPT_TYPE opt_type); +size_t tr_mon_req_opt_count(TR_MON_REQ *req); +TR_MON_OPT *tr_mon_req_opt_index(TR_MON_REQ *req, size_t index); + +const char *cmd_to_string(TR_MON_CMD cmd); +TR_MON_CMD cmd_from_string(const char *s); + +const char *opt_type_to_string(TR_MON_OPT_TYPE opt_type); +TR_MON_OPT_TYPE opt_type_from_string(const char *s); + +/* tr_mon_req_encode.c */ +json_t *tr_mon_req_encode(TR_MON_REQ *req); + +/* tr_mon_req_decode.c */ +TR_MON_REQ *tr_mon_req_decode(TALLOC_CTX *mem_ctx, const char *req_json); + +/* tr_mon_rec_decode.c */ + +#endif //TRUST_ROUTER_TR_MON_REQ_H diff --git a/mon/tr_mon_req_decode.c b/mon/tr_mon_req_decode.c new file mode 100644 index 0000000..1c441de --- /dev/null +++ b/mon/tr_mon_req_decode.c @@ -0,0 +1,181 @@ +#include "tr_mon_req.h"/* + * 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 +#include + +#include "tr_mon_req.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 TR_MON_SUCCESS on success, error on error + */ +static TR_MON_RC tr_mon_decode_one_opt(json_t *opt_json, TR_MON_OPT *dest) +{ + json_t *jstr = NULL; + TR_MON_OPT_TYPE opt_type = OPT_TYPE_UNKNOWN; + + if ( (opt_json == NULL) || (dest == NULL)) + return TR_MON_BADARG; + + if (! json_is_object(opt_json)) + return TR_MON_NOPARSE; + + jstr = json_object_get(opt_json, "type"); + if ( (jstr == NULL) || (! json_is_string(jstr)) ) + return TR_MON_NOPARSE; + + opt_type = opt_type_from_string(json_string_value(jstr)); + if (opt_type == OPT_TYPE_UNKNOWN) + return TR_MON_NOPARSE; + + dest->type = opt_type; + return TR_MON_SUCCESS; +} + +/** + * Decode options array + * + * Format: + * [{option}, {option}, ...] + * + */ +static TR_MON_RC tr_mon_options_decode(json_t *opts_json, TR_MON_REQ *req) +{ + TR_MON_OPT opt; // not a pointer + size_t n_opts=0; + size_t ii=0; + + if ( (opts_json == NULL) || (req == NULL)) + return TR_MON_BADARG; + + if (! json_is_array(opts_json)) + return TR_MON_NOPARSE; + + n_opts = json_array_size(opts_json); + for (ii=0; ii < n_opts; ii++) { + if (tr_mon_decode_one_opt(json_array_get(opts_json, ii), + &opt) != TR_MON_SUCCESS) { + return TR_MON_NOPARSE; + } + tr_mon_req_add_option(req, opt.type); + } + return TR_MON_SUCCESS; +} + +/** + * Parse JSON for a request + */ +static json_t *tr_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 tr_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 + */ +TR_MON_REQ *tr_mon_req_decode(TALLOC_CTX *mem_ctx, const char *req_str) +{ + TALLOC_CTX *tmp_ctx = talloc_new(NULL); + TR_MON_REQ *req = NULL; + json_t *req_json = NULL; + json_t *jval = NULL; + json_t *opts_json = NULL; + TR_MON_CMD cmd = MON_CMD_UNKNOWN; + + req_json = tr_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 = 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 = tr_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 (tr_mon_options_decode(opts_json, req) != TR_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/tr_mon_req_encode.c b/mon/tr_mon_req_encode.c new file mode 100644 index 0000000..3346b5f --- /dev/null +++ b/mon/tr_mon_req_encode.c @@ -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 +#include +#include + +#include "tr_mon_req.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 *tr_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; + TR_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, TR_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(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 tr_mon_opts_to_json()...] + * } + * + * @param req request to encode + * @return reference to a JSON object + */ +json_t *tr_mon_req_encode(TR_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(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 = tr_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; +} +