ud3tn/test/unit/test_json_config.c
Felix Walter 030b0bf75a routing/compat: Implement JSON configuration parser
This adds the JSON parser logic as an alternative to the custom legacy
format and also adds necessary tests.

Signed-off-by: Felix Walter <felix.walter@d3tn.com>
2025-06-05 17:03:39 +02:00

503 lines
12 KiB
C

// SPDX-License-Identifier: AGPL-3.0-or-later
#ifndef DISABLE_JSON
#include "routing/compat/json_config.h"
#include "routing/compat/node.h"
#include "routing/compat/router.h"
#include "ud3tn/result.h"
#include "testud3tn_unity.h"
#include <jansson.h>
#include <stdlib.h>
#include <string.h>
static const char *TEST_STRING_ADD_FULL = (
"{"
"\"command\": \"ADD\","
"\"node_id\": \"dtn://next-hop.dtn/\","
"\"cla_addr\": \"mtcp:127.0.0.1:5555\","
"\"reachable_eids\": ["
"\"ipn:1.0\","
"\"dtn://abc.dtn/\""
"],"
"\"contact_list\": ["
"{"
"\"start\": 1234,"
"\"end\": 5678,"
"\"data_rate\": 1234,"
"\"reachable_eids\": ["
"\"ipn:2.0\","
"\"ipn:3.0\""
"]"
"},"
"{"
"\"start\": 5679,"
"\"end\": 6789,"
"\"data_rate\": 5555"
"}"
"]"
"}"
);
static const char *TEST_STRING_INVALID_CMD = (
"{"
"\"command\": \"update\","
"\"node_id\": \"dtn://next-hop.dtn/\""
"}"
);
static const char *TEST_STRING_DELETE = (
"{"
"\"command\": \"DELETE\","
"\"node_id\": \"ipn:2.0\""
"}"
);
static const char *TEST_STRING_SIMPLE_UPDATE = (
"{"
"\"command\": \"UPDATE\","
"\"cla_addr\": \"mtcp:127.0.0.1:5555\","
"\"node_id\": \"dtn://test/\""
"}"
);
static const char *TEST_STRING_SIMPLE_QUERY = (
"{"
"\"command\": \"QUERY\","
"\"node_id\": \"dtn://test/\""
"}"
);
static const char *TEST_STRING_BAD_EIDS_TYPE = (
"{"
"\"command\": \"ADD\","
"\"node_id\": \"dtn://test/\","
"\"reachable_eids\": \"not-an-array\""
"}"
);
static const char *TEST_STRING_BAD_CONTACTS_TYPE = (
"{"
"\"command\": \"ADD\","
"\"node_id\": \"dtn://test/\","
"\"contact_list\": 123"
"}"
);
static const char *TEST_STRING_NEG_START = (
"{"
"\"command\":\"ADD\","
"\"node_id\":\"dtn://test/\","
"\"contact_list\":[{"
"\"start\": -1,"
"\"end\": 100,"
"\"data_rate\": 10"
"}]"
"}"
);
// UINT64_MAX/1000 = 18446744073709551 (rounded down)
static const char *TEST_STRING_OVERFLOW_START = (
"{"
"\"command\": \"ADD\","
"\"node_id\": \"dtn://test/\","
"\"contact_list\": [{"
"\"start\": 18446744073709551,"
"\"end\": 1,"
"\"data_rate\": 1"
"}]"
"}"
);
static const char *TEST_STRING_NO_OVERFLOW_START = (
"{"
"\"command\": \"ADD\","
"\"node_id\": \"dtn://test/\","
"\"contact_list\": [{"
"\"start\": 18446744073709550,"
"\"end\": 1,"
"\"data_rate\": 1"
"}]"
"}"
);
// UINT32_MAX = 4294967295
static const char *TEST_STRING_OVERFLOW_RATE = (
"{"
"\"command\": \"ADD\","
"\"node_id\": \"dtn://test/\","
"\"contact_list\": [{"
"\"start\": 1,"
"\"end\": 1,"
"\"data_rate\": 4294967296"
"}]"
"}"
);
static const char *TEST_STRING_NO_OVERFLOW_RATE = (
"{"
"\"command\": \"ADD\","
"\"node_id\": \"dtn://test/\","
"\"contact_list\": [{"
"\"start\": 1,"
"\"end\": 1,"
"\"data_rate\": 4294967295"
"}]"
"}"
);
static const char *TEST_STRING_BAD_NODE_EID = (
"{"
"\"command\": \"ADD\","
"\"cla_addr\": \"mtcp:127.0.0.1:5555\","
"\"node_id\": \"not-an-eid\""
"}"
);
static const char *TEST_STRING_BAD_EID_IN_LIST = (
"{"
"\"command\": \"ADD\","
"\"node_id\": \"dtn://test/\","
"\"reachable_eids\": [\"invalid_eid\"]"
"}"
);
static const char *TEST_STRING_BAD_EID_TYPE_IN_LIST = (
"{"
"\"command\": \"ADD\","
"\"node_id\": \"dtn://test/\","
"\"reachable_eids\": [55]"
"}"
);
static const char *TEST_STRING_BAD_EID_TEST_VALID_COUNTEREXAMPLE = (
"{"
"\"command\": \"ADD\","
"\"node_id\": \"dtn://test/\","
"\"contact_list\": [{"
"\"start\": 1,"
"\"end\": 1,"
"\"data_rate\": 1,"
"\"reachable_eids\": [\"ipn:1.0\"]"
"}]"
"}"
);
static const char *TEST_STRING_BAD_EID_IN_CONTACT_LIST = (
"{"
"\"command\": \"ADD\","
"\"node_id\": \"dtn://test/\","
"\"contact_list\": [{"
"\"start\": 1,"
"\"end\": 1,"
"\"data_rate\": 1,"
"\"reachable_eids\": [\"invalid_eid\"]"
"}]"
"}"
);
static const char *TEST_STRING_BAD_EID_TYPE_IN_CONTACT_LIST = (
"{"
"\"command\": \"ADD\","
"\"node_id\": \"dtn://test/\","
"\"contact_list\": [{"
"\"start\": 1,"
"\"end\": 1,"
"\"data_rate\": 1,"
"\"reachable_eids\": [55]"
"}]"
"}"
);
TEST_GROUP(json_config);
TEST_SETUP(json_config) {}
TEST_TEAR_DOWN(json_config) {}
TEST(json_config, parse_valid_full)
{
struct router_command *const cmd = parse_json_command(
TEST_STRING_ADD_FULL,
strlen(TEST_STRING_ADD_FULL)
);
TEST_ASSERT_NOT_NULL(cmd);
TEST_ASSERT_EQUAL(ROUTER_COMMAND_ADD, cmd->type);
TEST_ASSERT_NOT_NULL(cmd->data);
TEST_ASSERT_EQUAL_STRING("dtn://next-hop.dtn/", cmd->data->eid);
TEST_ASSERT_EQUAL_STRING("mtcp:127.0.0.1:5555", cmd->data->cla_addr);
const struct endpoint_list *const e = cmd->data->endpoints;
TEST_ASSERT_NOT_NULL(e);
TEST_ASSERT_EQUAL_STRING("ipn:1.0", e->eid);
TEST_ASSERT_NOT_NULL(e->next);
TEST_ASSERT_EQUAL_STRING("dtn://abc.dtn/", e->next->eid);
TEST_ASSERT_NULL(e->next->next);
const struct contact_list *const c = cmd->data->contacts;
TEST_ASSERT_NOT_NULL(c);
TEST_ASSERT_EQUAL_UINT64(1234 * 1000ULL, c->data->from_ms);
TEST_ASSERT_EQUAL_UINT64(5678 * 1000ULL, c->data->to_ms);
TEST_ASSERT_EQUAL_UINT32(1234U, c->data->bitrate_bytes_per_s);
const struct endpoint_list *const ce = c->data->contact_endpoints;
TEST_ASSERT_EQUAL_STRING("ipn:2.0", ce->eid);
TEST_ASSERT_EQUAL_STRING("ipn:3.0", ce->next->eid);
TEST_ASSERT_NULL(ce->next->next);
TEST_ASSERT_NOT_NULL(c->next);
TEST_ASSERT_EQUAL_UINT64(5679 * 1000ULL, c->next->data->from_ms);
TEST_ASSERT_EQUAL_UINT64(6789 * 1000ULL, c->next->data->to_ms);
TEST_ASSERT_EQUAL_UINT32(5555U, c->next->data->bitrate_bytes_per_s);
TEST_ASSERT_NULL(c->next->data->contact_endpoints);
TEST_ASSERT_NULL(c->next->next);
free_node(cmd->data);
free(cmd);
}
TEST(json_config, parse_simple)
{
struct router_command *cmd;
cmd = parse_json_command(
TEST_STRING_SIMPLE_UPDATE,
strlen(TEST_STRING_SIMPLE_UPDATE)
);
TEST_ASSERT_NOT_NULL(cmd);
TEST_ASSERT_EQUAL(ROUTER_COMMAND_UPDATE, cmd->type);
TEST_ASSERT_NOT_NULL(cmd->data);
TEST_ASSERT_EQUAL_STRING("dtn://test/", cmd->data->eid);
TEST_ASSERT_EQUAL_STRING("mtcp:127.0.0.1:5555", cmd->data->cla_addr);
TEST_ASSERT_NULL(cmd->data->endpoints);
TEST_ASSERT_NULL(cmd->data->contacts);
free_node(cmd->data);
free(cmd);
cmd = parse_json_command(
TEST_STRING_SIMPLE_QUERY,
// also support incl null terminator
strlen(TEST_STRING_SIMPLE_QUERY) + 1
);
TEST_ASSERT_NOT_NULL(cmd);
TEST_ASSERT_EQUAL(ROUTER_COMMAND_QUERY, cmd->type);
TEST_ASSERT_NOT_NULL(cmd->data);
TEST_ASSERT_EQUAL_STRING("dtn://test/", cmd->data->eid);
TEST_ASSERT_NULL(cmd->data->cla_addr);
TEST_ASSERT_NULL(cmd->data->endpoints);
TEST_ASSERT_NULL(cmd->data->contacts);
free_node(cmd->data);
free(cmd);
}
TEST(json_config, parse_invalid_command_type)
{
struct router_command *cmd = parse_json_command(
TEST_STRING_INVALID_CMD,
strlen(TEST_STRING_INVALID_CMD)
);
TEST_ASSERT_NULL(cmd);
}
TEST(json_config, parse_delete)
{
struct router_command *cmd = parse_json_command(
TEST_STRING_DELETE,
strlen(TEST_STRING_DELETE)
);
TEST_ASSERT_NOT_NULL(cmd);
TEST_ASSERT_EQUAL(ROUTER_COMMAND_DELETE, cmd->type);
TEST_ASSERT_EQUAL_STRING("ipn:2.0", cmd->data->eid);
free_node(cmd->data);
free(cmd);
}
TEST(json_config, parse_invalid_types_and_overflows)
{
struct router_command *cmd;
cmd = parse_json_command(
TEST_STRING_BAD_EIDS_TYPE,
strlen(TEST_STRING_BAD_EIDS_TYPE)
);
TEST_ASSERT_NULL(cmd);
cmd = parse_json_command(
TEST_STRING_BAD_CONTACTS_TYPE,
strlen(TEST_STRING_BAD_CONTACTS_TYPE)
);
TEST_ASSERT_NULL(cmd);
cmd = parse_json_command(
TEST_STRING_NEG_START,
strlen(TEST_STRING_NEG_START)
);
TEST_ASSERT_NULL(cmd);
cmd = parse_json_command(
TEST_STRING_OVERFLOW_START,
strlen(TEST_STRING_OVERFLOW_START)
);
TEST_ASSERT_NULL(cmd);
cmd = parse_json_command(
TEST_STRING_NO_OVERFLOW_START,
strlen(TEST_STRING_NO_OVERFLOW_START)
);
TEST_ASSERT_NOT_NULL(cmd);
free_node(cmd->data);
free(cmd);
cmd = parse_json_command(
TEST_STRING_OVERFLOW_RATE,
strlen(TEST_STRING_OVERFLOW_RATE)
);
TEST_ASSERT_NULL(cmd);
cmd = parse_json_command(
TEST_STRING_NO_OVERFLOW_RATE,
strlen(TEST_STRING_NO_OVERFLOW_RATE)
);
TEST_ASSERT_NOT_NULL(cmd);
free_node(cmd->data);
free(cmd);
}
TEST(json_config, parse_invalid_eids)
{
struct router_command *cmd;
cmd = parse_json_command(
TEST_STRING_BAD_NODE_EID,
strlen(TEST_STRING_BAD_NODE_EID)
);
TEST_ASSERT_NULL(cmd);
cmd = parse_json_command(
TEST_STRING_BAD_EID_IN_LIST,
strlen(TEST_STRING_BAD_EID_IN_LIST)
);
TEST_ASSERT_NULL(cmd);
cmd = parse_json_command(
TEST_STRING_BAD_EID_TYPE_IN_LIST,
strlen(TEST_STRING_BAD_EID_TYPE_IN_LIST)
);
TEST_ASSERT_NULL(cmd);
cmd = parse_json_command(
TEST_STRING_BAD_EID_TEST_VALID_COUNTEREXAMPLE,
strlen(TEST_STRING_BAD_EID_TEST_VALID_COUNTEREXAMPLE)
);
TEST_ASSERT_NOT_NULL(cmd);
free_node(cmd->data);
free(cmd);
cmd = parse_json_command(
TEST_STRING_BAD_EID_IN_CONTACT_LIST,
strlen(TEST_STRING_BAD_EID_IN_CONTACT_LIST)
);
TEST_ASSERT_NULL(cmd);
cmd = parse_json_command(
TEST_STRING_BAD_EID_TYPE_IN_CONTACT_LIST,
strlen(TEST_STRING_BAD_EID_TYPE_IN_CONTACT_LIST)
);
TEST_ASSERT_NULL(cmd);
}
// Normally those internal functions are not exported, but we'd like to test
// them separately.
struct endpoint_list *parse_json_eid_list(json_t *const jp);
struct contact_list *parse_json_contact_list(json_t *const jp, struct node *node);
TEST(json_config, direct_parse_json_eid_list)
{
json_t *const arr = json_array();
json_array_append_new(arr, json_string("ipn:9.9"));
json_array_append_new(arr, json_string("dtn://xyz/"));
struct endpoint_list *list = parse_json_eid_list(arr);
TEST_ASSERT_NOT_NULL(list);
TEST_ASSERT_EQUAL_STRING("ipn:9.9", list->eid);
TEST_ASSERT_EQUAL_STRING("dtn://xyz/", list->next->eid);
while (list)
list = endpoint_list_free(list);
json_decref(arr);
// invalid element
json_t *const arr2 = json_array();
json_array_append_new(arr2, json_integer(42));
list = parse_json_eid_list(arr2);
TEST_ASSERT_NULL(list);
json_decref(arr2);
}
TEST(json_config, direct_parse_json_contact_list)
{
struct node *const node = node_create(NULL);
TEST_ASSERT_NOT_NULL(node);
// valid contact w/o reachable_eids
json_t *const arr = json_array();
json_t *const o = json_object();
json_object_set_new(o, "start", json_integer(10));
json_object_set_new(o, "end", json_integer(20));
json_object_set_new(o, "data_rate", json_integer(100));
json_array_append_new(arr, o);
struct contact_list *cl = parse_json_contact_list(arr, node);
TEST_ASSERT_NOT_NULL(cl);
TEST_ASSERT_EQUAL_UINT64(10 * 1000ULL, cl->data->from_ms);
TEST_ASSERT_EQUAL_UINT64(20 * 1000ULL, cl->data->to_ms);
TEST_ASSERT_EQUAL_UINT32(100U, cl->data->bitrate_bytes_per_s);
TEST_ASSERT_NULL(cl->data->contact_endpoints);
while (cl)
cl = contact_list_free(cl);
json_decref(arr);
// invalid reachable_eids type
json_t *const arr2 = json_array();
json_t *const o2 = json_object();
json_object_set_new(o2, "start", json_integer(1));
json_object_set_new(o2, "end", json_integer(2));
json_object_set_new(o2, "data_rate", json_integer(3));
json_object_set_new(o2, "reachable_eids", json_string("oops"));
json_array_append_new(arr2, o2);
cl = parse_json_contact_list(arr2, node);
TEST_ASSERT_NULL(cl);
json_decref(arr2);
free_node(node);
}
TEST_GROUP_RUNNER(json_config)
{
RUN_TEST_CASE(json_config, parse_valid_full);
RUN_TEST_CASE(json_config, parse_simple);
RUN_TEST_CASE(json_config, parse_invalid_command_type);
RUN_TEST_CASE(json_config, parse_delete);
RUN_TEST_CASE(json_config, parse_invalid_types_and_overflows);
RUN_TEST_CASE(json_config, parse_invalid_eids);
RUN_TEST_CASE(json_config, direct_parse_json_eid_list);
RUN_TEST_CASE(json_config, direct_parse_json_contact_list);
}
#endif // DISABLE_JSON