diff --git a/src/main.c b/src/main.c index 9a161afe..18b49b18 100644 --- a/src/main.c +++ b/src/main.c @@ -249,6 +249,63 @@ np2srv_err_nc(sr_error_info_err_t *err) return NULL; } +/** + * @brief Find the nth substring delimited by quotes. + * + * For example: abcd"ef"ghij"kl"mn -> rank 1 is "ef", rank 2 is "kl". + * + * @param[in] msg Input string with quoted substring. + * @param[in] rank Number starting from 1 specifying the nth substring. + * @return Copied nth substring without quotes. + */ +static char * +np2srv_err_reply_get_quoted_string(const char *msg, uint32_t rank) +{ + char *ret; + const char *start = NULL, *end = NULL, *iter, *tmp; + uint32_t cnt = 0; + + assert(msg && (rank > 0)); + + rank *= 2; + for (iter = msg; *iter != '\0'; ++iter) { + if (*iter == '\"') { + /* updating the start and end pointers */ + tmp = end; + end = iter; + start = tmp; + if (++cnt == rank) { + /* nth substring found */ + break; + } + } + } + + if (!start) { + return NULL; + } + + /* Skip first quote */ + ++start; + /* Copy substring */ + ret = strndup(start, end - start); + + return ret; +} + +/** + * @brief Check that the @p str starts with the @p prefix. + * + * @param[in] prefix Required prefix. + * @param[in] str Input string to check. + * @return True if @p str start with @p prefix otherwise False. + */ +static ly_bool +np2srv_strstarts(const char *prefix, const char *str) +{ + return strncmp(str, prefix, strlen(prefix)) == 0; +} + /** * @brief Create NC error reply based on SR error info. * @@ -262,6 +319,9 @@ np2srv_err_reply_sr(const sr_error_info_t *err_info) struct lyd_node *e; const struct ly_ctx *ly_ctx; size_t i; + char *str, *path; + const struct lysc_node *cn; + NC_ERR_TYPE errtype; /* try to find a NETCONF error(s) */ for (i = 0; i < err_info->err_count; ++i) { @@ -284,16 +344,34 @@ np2srv_err_reply_sr(const sr_error_info_t *err_info) ly_ctx = sr_acquire_context(np2srv.sr_conn); for (i = 0; i < err_info->err_count; ++i) { - /* generic error */ - e = nc_err(ly_ctx, NC_ERR_OP_FAILED, NC_ERR_TYPE_APP); - nc_err_set_msg(e, err_info->err[i].message, "en"); + if (np2srv_strstarts("Mandatory node", err_info->err[i].message) || + np2srv_strstarts("Mandatory choice", err_info->err[i].message)) { + str = np2srv_err_reply_get_quoted_string(err_info->err[i].message, 1); + path = np2srv_err_reply_get_quoted_string(err_info->err[i].message, 2); + cn = lys_find_path(ly_ctx, NULL, path, 0); + if (cn && ((cn->nodetype & LYS_RPC) || (cn->nodetype & LYS_INPUT))) { + errtype = NC_ERR_TYPE_PROT; + } else { + errtype = NC_ERR_TYPE_APP; + } + e = nc_err(ly_ctx, NC_ERR_MISSING_ELEM, errtype, str); + free(str); + free(path); + } else if (err_info->err->err_code == SR_ERR_NO_MEMORY) { + e = nc_err(ly_ctx, NC_ERR_RES_DENIED, NC_ERR_TYPE_APP); + } else { + /* generic error */ + e = nc_err(ly_ctx, NC_ERR_OP_FAILED, NC_ERR_TYPE_APP); + } + nc_err_set_msg(e, err_info->err[i].message, "en"); if (reply) { nc_server_reply_add_err(reply, e); } else { reply = nc_server_reply_err(e); } } + /* clear for other errors */ sr_release_context(np2srv.sr_conn); return reply; diff --git a/tests/CMakeLists.txt b/tests/CMakeLists.txt index f2008d43..75c186c2 100644 --- a/tests/CMakeLists.txt +++ b/tests/CMakeLists.txt @@ -42,7 +42,7 @@ set(TEST_SRC "np2_test.c" "np2_other_client.c") # list of all the tests set(TESTS test_rpc test_edit test_filter test_subscribe_filter test_subscribe_param test_parallel_sessions - test_candidate test_with_defaults test_nacm test_sub_ntf test_sub_ntf_advanced test_sub_ntf_filter test_error) + test_candidate test_with_defaults test_nacm test_sub_ntf test_sub_ntf_advanced test_sub_ntf_filter test_error test_other_client) if(CMAKE_C_FLAGS MATCHES "-fsanitize=thread") message(WARNING "Features which use SIGEV_THREAD are known to be broken under TSAN, disabling tests for YANG-push and confirmed commit") diff --git a/tests/test_other_client.c b/tests/test_other_client.c new file mode 100644 index 00000000..4e5da18c --- /dev/null +++ b/tests/test_other_client.c @@ -0,0 +1,431 @@ +/** + * @file test_other_client.h + * @author Adam Piecek + * @brief An alternative client which communicate with NETCONF server. + * + * @copyright + * Copyright (c) 2019 - 2024 Deutsche Telekom AG. + * Copyright (c) 2017 - 2024 CESNET, z.s.p.o. + * + * This source code is licensed under BSD 3-Clause License (the "License"). + * You may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * https://opensource.org/licenses/BSD-3-Clause + */ + +#define _GNU_SOURCE + +#include +#include +#include +#include +#include +#include + +#include +#include + +#include "np2_other_client.h" +#include "np2_test.h" +#include "np2_test_config.h" + +static int +local_setup(void **state) +{ + char test_name[256]; + const char *modules[] = {NP_TEST_MODULE_DIR "/errors.yang", NULL}; + int rc; + + /* get test name */ + np2_glob_test_setup_test_name(test_name); + + /* setup environment necessary for installing module */ + rc = np2_glob_test_setup_env(test_name); + assert_int_equal(rc, 0); + + /* setup netopeer2 server */ + rc = np2_glob_test_setup_server(state, test_name, modules, NP_GLOB_SETUP_OTHER_CLIENT); + assert_int_equal(rc, 0); + + /* setup NACM */ + rc = np2_glob_test_setup_nacm(state); + assert_int_equal(rc, 0); + + return 0; +} + +static int +local_teardown(void **state) +{ + const char *modules[] = {"errors", NULL}; + + /* close netopeer2 server */ + if (*state) { + return np2_glob_test_teardown(state, modules); + } + + return 0; +} + +static void +test_message_id(void **state) +{ + int rc; + char *msg, *exp; + struct np2_test *st = *state; + struct np_other_client *sess = st->oc_sess; + + /* send malformed message */ + asprintf(&msg, + "" + " " + "", sess->msgid); + rc = oc_send_msg(sess, msg); + assert_int_equal(rc, 0); + free(msg); + rc = oc_recv_msg(sess, &msg); + assert_int_equal(rc, 0); + + /* then send valid message */ + asprintf(&msg, + "" + " " + "", sess->msgid); + rc = oc_send_msg(sess, msg); + assert_int_equal(rc, 0); + free(msg); + rc = oc_recv_msg(sess, &msg); + assert_int_equal(rc, 0); + asprintf(&exp, "", sess->msgid); + assert_string_equal(msg, exp); + free(exp); +} + +static void +test_missing_attribute(void **state) +{ + int rc; + char *msg, *exp; + struct np2_test *st = *state; + struct np_other_client *sess = st->oc_sess; + + /* missing attribute 'message-id' in the rpc layer */ + msg = + "" + " " + ""; + rc = oc_send_msg(sess, msg); + assert_int_equal(rc, 0); + rc = oc_recv_msg(sess, &msg); + assert_int_equal(rc, 0); + assert_string_equal(msg, + "" + "rpc" + "missing-attributeerror" + "An expected attribute is missing." + "message-id" + "rpc" + ""); + + /* missing attribute 'xmlns' in the rpc layer */ + asprintf(&msg, + "" + " " + "", sess->msgid); + rc = oc_send_msg(sess, msg); + assert_int_equal(rc, 0); + free(msg); + rc = oc_recv_msg(sess, &msg); + assert_int_equal(rc, 0); + assert_string_equal(msg, + "" + "rpc" + "missing-attributeerror" + "An expected attribute is missing." + "xmlns" + "rpc" + ""); + + /* missing attribute 'select' in the protocol layer */ + asprintf(&msg, + "" + " " + " " + " " + " " + " " + " " + "", sess->msgid); + rc = oc_send_msg(sess, msg); + assert_int_equal(rc, 0); + free(msg); + rc = oc_recv_msg(sess, &msg); + assert_int_equal(rc, 0); + asprintf(&exp, + "" + "protocol" + "missing-attribute" + "error" + "Missing \"select\" attribute" + "filter", sess->msgid); + assert_string_equal(msg, exp); + free(exp); +} + +static void +test_unknown_attribute(void **state) +{ + int rc; + char *msg, *exp; + struct np2_test *st = *state; + struct np_other_client *sess = st->oc_sess; + + /* unknown attribute 'att' in the rpc layer, + * but in this case it's ok because rfc 6241 is benevolent towards attributes. + */ + asprintf(&msg, + "" + " " + "", sess->msgid); + rc = oc_send_msg(sess, msg); + assert_int_equal(rc, 0); + free(msg); + rc = oc_recv_msg(sess, &msg); + assert_int_equal(rc, 0); + asprintf(&exp, + "" + "", sess->msgid); + assert_string_equal(msg, exp); + free(exp); + + /* unknown attribute 'att' in the protocol layer: annotation not found */ + asprintf(&msg, + "" + " " + "", sess->msgid); + rc = oc_send_msg(sess, msg); + assert_int_equal(rc, 0); + free(msg); + rc = oc_recv_msg(sess, &msg); + assert_int_equal(rc, 0); + asprintf(&exp, + "" + "protocol" + "unknown-attribute" + "error" + "Annotation definition for attribute \"ietf-netconf:att\" not found." + "att" + "discard-changes" + "", sess->msgid); + assert_string_equal(msg, exp); + free(exp); + + /* unknown attribute 'att' in the protocol layer: missing prefix */ + asprintf(&msg, + "" + " " + "", sess->msgid); + rc = oc_send_msg(sess, msg); + assert_int_equal(rc, 0); + free(msg); + rc = oc_recv_msg(sess, &msg); + assert_int_equal(rc, 0); + asprintf(&exp, + "" + "protocol" + "unknown-attribute" + "error" + "Missing mandatory prefix for XML metadata \"att\"." + "att" + "discard-changes" + "", sess->msgid); + assert_string_equal(msg, exp); + free(exp); + + /* unknown attribute 'att' in the protocol layer: unknown XML prefix */ + asprintf(&msg, + "" + " " + "", sess->msgid); + rc = oc_send_msg(sess, msg); + assert_int_equal(rc, 0); + free(msg); + rc = oc_recv_msg(sess, &msg); + assert_int_equal(rc, 0); + asprintf(&exp, + "" + "protocol" + "unknown-attribute" + "error" + "Unknown XML prefix \"el\" at attribute \"att\"." + "att" + "discard-changes" + "", sess->msgid); + assert_string_equal(msg, exp); + free(exp); +} + +static void +test_missing_element(void **state) +{ + int rc; + char *msg, *exp; + struct np2_test *st = *state; + struct np_other_client *sess = st->oc_sess; + + /* missing element in 'edit-content' in the protocol layer: missing mandatory node in the choice-stmt */ + asprintf(&msg, + "" + " " + " ds:running" + " " + "", sess->msgid); + rc = oc_send_msg(sess, msg); + assert_int_equal(rc, 0); + free(msg); + rc = oc_recv_msg(sess, &msg); + assert_int_equal(rc, 0); + asprintf(&exp, + "" + "protocol" + "missing-element" + "error" + "Mandatory choice \"edit-content\" data do not exist." + " (path \"/ietf-netconf-nmda:edit-data\")" + "edit-content" + "" + "" + "application" + "operation-failed" + "error" + "RPC input validation failed." + "", sess->msgid); + assert_string_equal(msg, exp); + free(exp); + + /* missing element 'identifier' in the protocol layer */ + asprintf(&msg, + "" + " " + "", sess->msgid); + rc = oc_send_msg(sess, msg); + assert_int_equal(rc, 0); + free(msg); + rc = oc_recv_msg(sess, &msg); + assert_int_equal(rc, 0); + asprintf(&exp, + "" + "protocol" + "missing-element" + "error" + "Mandatory node \"identifier\" instance does not exist." + " (path \"/ietf-netconf-monitoring:get-schema\")" + "identifier" + "" + "" + "application" + "operation-failed" + "error" + "RPC input validation failed." + "", sess->msgid); + assert_string_equal(msg, exp); + free(exp); + + /* missing element in 'config-choice' in the application layer: missing mandatory node in the choice-stmt */ + asprintf(&msg, + "" + " " + " " + " " + "", sess->msgid); + rc = oc_send_msg(sess, msg); + assert_int_equal(rc, 0); + free(msg); + rc = oc_recv_msg(sess, &msg); + assert_int_equal(rc, 0); + asprintf(&exp, + "" + "application" + "missing-element" + "error" + "Mandatory choice \"config-source\" data do not exist." + " (path \"/ietf-netconf:get-config/source\")" + "config-source" + "" + "" + "application" + "operation-failed" + "error" + "RPC input validation failed." + "", sess->msgid); + assert_string_equal(msg, exp); + free(exp); +} + +static void +test_malformed_message(void **state) +{ + int rc; + char *msg, *exp; + struct np2_test *st = *state; + struct np_other_client *sess = st->oc_sess; + + /* malformed-message xmlns in the rpc layer */ + asprintf(&msg, + "" + " " + "", sess->msgid); + rc = oc_send_msg(sess, msg); + assert_int_equal(rc, 0); + free(msg); + rc = oc_recv_msg(sess, &msg); + assert_int_equal(rc, 0); + assert_string_equal(msg, + "" + "" + "rpc" + "malformed-message" + "error" + "A message could not be handled because it failed to be parsed correctly." + ""); + + /* malformed-message in the non-rpc layer */ + asprintf(&msg, + "" + " " + "", sess->msgid); + rc = oc_send_msg(sess, msg); + assert_int_equal(rc, 0); + free(msg); + rc = oc_recv_msg(sess, &msg); + assert_int_equal(rc, 0); + asprintf(&exp, + "" + "" + "rpc" + "malformed-message" + "error" + "Invalid character sequence \"&ges/></rpc>\"," + " expected element tag end ('>' or '/>') or an attribute." + "", sess->msgid); + assert_string_equal(msg, exp); + free(exp); +} + +int +main(int argc, char **argv) +{ + const struct CMUnitTest tests[] = { + cmocka_unit_test(test_message_id), + cmocka_unit_test(test_missing_attribute), + cmocka_unit_test(test_unknown_attribute), + cmocka_unit_test(test_missing_element), + cmocka_unit_test(test_malformed_message), + }; + + nc_verbosity(NC_VERB_WARNING); + parse_arg(argc, argv); + return cmocka_run_group_tests(tests, local_setup, local_teardown); +}