diff --git a/src/err_netconf.c b/src/err_netconf.c index 519cea8b..99dd6e6a 100644 --- a/src/err_netconf.c +++ b/src/err_netconf.c @@ -122,6 +122,12 @@ np_err_missing_element(sr_session_ctx_t *ev_sess, const char *elem_name) sr_session_set_netconf_error(ev_sess, "protocol", "missing-element", NULL, NULL, msg, 1, "bad-element", elem_name); } +void +np_err_missing_attribute(sr_session_ctx_t *ev_sess, const char *message, const char *elem_name) +{ + sr_session_set_netconf_error(ev_sess, "protocol", "missing-attribute", NULL, NULL, message, 1, "bad-element", elem_name); +} + void np_err_bad_element(sr_session_ctx_t *ev_sess, const char *elem_name, const char *description) { diff --git a/src/err_netconf.h b/src/err_netconf.h index b9bf4ace..0a142f1e 100644 --- a/src/err_netconf.h +++ b/src/err_netconf.h @@ -31,6 +31,8 @@ void np_err_lock_denied(sr_session_ctx_t *ev_sess, const char *err_msg, uint32_t void np_err_missing_element(sr_session_ctx_t *ev_sess, const char *elem_name); +void np_err_missing_attribute(sr_session_ctx_t *ev_sess, const char *message, const char *elem_name); + void np_err_bad_element(sr_session_ctx_t *ev_sess, const char *elem_name, const char *description); void np_err_invalid_value(sr_session_ctx_t *ev_sess, const char *description, const char *bad_elem_name); diff --git a/src/main.c b/src/main.c index 9a161afe..7cf3e30b 100644 --- a/src/main.c +++ b/src/main.c @@ -249,6 +249,64 @@ 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 -> index 0 is "ef", index 1 is "kl". + * + * @param[in] msg Input string with quoted substring. + * @param[in] index Number starting from 0 specifying the nth substring. + * @return Copied nth substring without quotes. + */ +static char * +np2srv_err_reply_get_quoted_string(const char *msg, uint32_t index) +{ + char *ret; + const char *start = NULL, *end = NULL, *iter, *tmp; + uint32_t quote_cnt = 0, last_quote; + + assert(msg); + + last_quote = (index + 1) * 2; + for (iter = msg; *iter; ++iter) { + if (*iter != '\"') { + continue; + } + /* updating the start and end pointers - swap */ + tmp = end; + end = iter; + start = tmp; + if (++quote_cnt == last_quote) { + /* 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 +320,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 +345,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, 0); + path = np2srv_err_reply_get_quoted_string(err_info->err[i].message, 1); + 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/src/netconf.c b/src/netconf.c index 31dd5901..e0b790fb 100644 --- a/src/netconf.c +++ b/src/netconf.c @@ -155,7 +155,7 @@ np2srv_rpc_get_cb(sr_session_ctx_t *session, uint32_t UNUSED(sub_id), const char if (meta && !strcmp(lyd_get_meta_value(meta), "xpath")) { meta = lyd_find_meta(node->meta, NULL, "ietf-netconf:select"); if (!meta) { - ERR("RPC with an XPath filter without the \"select\" attribute."); + np_err_missing_attribute(session, "Missing \"select\" attribute", "filter"); rc = SR_ERR_INVAL_ARG; goto cleanup; } @@ -933,7 +933,7 @@ np2srv_rpc_subscribe_cb(sr_session_ctx_t *session, uint32_t UNUSED(sub_id), cons if (meta && !strcmp(lyd_get_meta_value(meta), "xpath")) { meta = lyd_find_meta(node->meta, NULL, "ietf-netconf:select"); if (!meta) { - ERR("RPC with an XPath filter without the \"select\" attribute."); + np_err_missing_attribute(session, "Missing \"select\" attribute", "filter"); rc = SR_ERR_INVAL_ARG; goto cleanup; } diff --git a/tests/CMakeLists.txt b/tests/CMakeLists.txt index 805e8c8f..75c186c2 100644 --- a/tests/CMakeLists.txt +++ b/tests/CMakeLists.txt @@ -38,11 +38,11 @@ include_directories(${CMAKE_CURRENT_SOURCE_DIR}) include_directories(${CMAKE_CURRENT_BINARY_DIR}) # base test source -set(TEST_SRC "np2_test.c") +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/np2_other_client.c b/tests/np2_other_client.c new file mode 100644 index 00000000..f4ec6633 --- /dev/null +++ b/tests/np2_other_client.c @@ -0,0 +1,354 @@ +/** + * @file np2_other_client.c + * @author Adam Piecek + * @brief An alternative test interface for communicating with the 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 +#include +#include + +#include "libnetconf2/netconf.h" +#include "np2_other_client.h" + +#define OC_FAIL_LOG \ + fprintf(stderr, "Netconf client fail in %s:%d.\n", __FILE__, __LINE__) + +/** + * Full timeout of read or write in seconds. + */ +#define OC_TIMEOUT_SEC 60 + +/** + * Microseconds after which tasks are repeated until the full timeout elapses. + */ +#define OC_TIMEOUT_STEP 100 + +/** + * Check if the timeout has expired. + */ +#define OC_TIMEOUT(START_TIME) \ + (((double)(time(NULL) - START_TIME)) > OC_TIMEOUT_SEC) + +/** + * @brief Write message to socket. + * + * @param[in] oc_sess Client session. + * @param[in] msg Message to send. + * @param[in] msglen Length of @p msg. + * @return 0 on success. + * @return negative number on error. + */ +static int +oc_write(struct np_other_client *oc_sess, const char *msg, uint64_t msglen) +{ + uint64_t written = 0; + int64_t cnt; + int interrupted; + + msglen = msglen ? msglen : strlen(msg); + do { + cnt = write(oc_sess->unixsock, msg + written, msglen - written); + written += cnt; + if ((cnt < 0) && (errno == EAGAIN)) { + cnt = 0; + } else if ((cnt < 0) && (errno == EINTR)) { + cnt = 0; + interrupted = 1; + } else if (cnt < 0) { + fprintf(stderr, "Socket error (%s).\n", strerror(errno)); + return -1; + } + if ((cnt == 0) && !interrupted) { + /* we must wait */ + usleep(OC_TIMEOUT_STEP); + } + } while (written < msglen); + + return 0; +} + +/** + * @brief Reallocation of internal buffer in the struct np_other_client. + * + * @param[in] oc_sess Client session. + * @return 0 on success. + * @return negative number on error. + */ +static int +oc_realloc(struct np_other_client *oc_sess) +{ + void *tmp; + + tmp = realloc(oc_sess->buf, oc_sess->bufsize * 2); + if (!tmp) { + fprintf(stderr, "Memory allocation error.\n"); + return -1; + } + oc_sess->buf = tmp; + oc_sess->bufsize *= 2; + + return 0; +} + +/** + * @defgroup ocreadflags Flags for oc_read(). + * @{ + */ +#define OC_READ_HELLO_MSG 0x1 /**< read the response to the hello message from the server */ +/** @} ocreadflags */ + +/** + * @brief Read message from socket. + * + * @param[in] oc_sess Client session. + * @param[in] flags Option for function (@ref ocreadflags). + * @return positive number representing number of characters written into @p oc_sess buffer. + * @return negative number on error. + */ +static int64_t +oc_read(struct np_other_client *oc_sess, uint32_t flags) +{ + int64_t rd, rdall = 0; + time_t tm; + int interrupted; + const char *endtag; + + tm = time(NULL); + oc_sess->buf[0] = 0; + + do { + interrupted = 0; + rd = read(oc_sess->unixsock, oc_sess->buf + rdall, oc_sess->bufsize - rdall); + if (rd < 0) { + if (errno == EAGAIN) { + /* endtag not found */ + rd = 0; + } else if (errno == EINTR) { + rd = 0; + interrupted = 1; + break; + } else { + fprintf(stderr, "Reading from file descriptor (%d) failed (%s).\n", oc_sess->unixsock, strerror(errno)); + return -1; + } + } else if (rd == 0) { + fprintf(stderr, "Communication file descriptor (%d) unexpectedly closed.\n", oc_sess->unixsock); + return -1; + } + if (rd == 0) { + /* nothing read */ + if (!interrupted) { + usleep(OC_TIMEOUT_STEP); + } + if (OC_TIMEOUT(tm)) { + /* waiting too long */ + fprintf(stderr, "Message took too long to read\n"); + return -1; + } + } else { + /* something read */ + rdall += rd; + } + + if ((flags & OC_READ_HELLO_MSG) && (rdall > 5)) { + /* check hello end tag, (strlen("]]>]]>") == 6) */ + endtag = (oc_sess->buf + rdall) - 6; + if (!strncmp(endtag, "]]>]]>", 6)) { + /* success */ + break; + } + } else if (rdall > 3) { + /* check classic end tag, (strlen(\n##\n) == 4) */ + endtag = (oc_sess->buf + rdall) - 4; + if (!strncmp(endtag, "\n##\n", 4)) { + /* success */ + break; + } + } + + if ((oc_sess->bufsize - rdall) == 0) { + if (oc_realloc(oc_sess)) { + return -1; + } + } + } while (1); + + return rdall; +} + +/** + * @brief Establish NETCONF session. + * + * @param[in] oc_sess Client session. + * @return 0 on success. + */ +static int +oc_hello_handshake(struct np_other_client *oc_sess) +{ + int rc; + + const char *msg = + "" + "urn:ietf:params:netconf:base:1.0" + "urn:ietf:params:netconf:base:1.1]]>]]>"; + + rc = oc_write(oc_sess, msg, 0); + if (rc) { + return rc; + } + + return (oc_read(oc_sess, OC_READ_HELLO_MSG) >= 0) ? 0 : -1; +} + +struct np_other_client * +oc_connect_unix(const char *address) +{ + struct sockaddr_un sun; + struct np_other_client *oc_sess = NULL; + int rc; + + oc_sess = calloc(1, sizeof *oc_sess); + if (!oc_sess) { + OC_FAIL_LOG; + return NULL; + } + + oc_sess->unixsock = socket(AF_UNIX, SOCK_STREAM, 0); + if (oc_sess->unixsock < 0) { + OC_FAIL_LOG; + return NULL; + } + + memset(&sun, 0, sizeof(sun)); + sun.sun_family = AF_UNIX; + snprintf(sun.sun_path, sizeof(sun.sun_path), "%s", address); + + if (connect(oc_sess->unixsock, (struct sockaddr *)&sun, sizeof(sun)) < 0) { + OC_FAIL_LOG; + return NULL; + } + + if (fcntl(oc_sess->unixsock, F_SETFL, O_NONBLOCK) < 0) { + OC_FAIL_LOG; + return NULL; + } + + oc_sess->buf = malloc(2048); + if (!oc_sess->buf) { + return NULL; + } + oc_sess->bufsize = 2048; + + rc = oc_hello_handshake(oc_sess); + if (rc) { + return NULL; + } + + oc_sess->msgid = 1; + + return oc_sess; +} + +int +oc_send_msg(struct np_other_client *oc_sess, const char *msg) +{ + int rc; + char *starttag = NULL; + uint64_t msglen; + + /* increment message-id but do not increment after initial handshake */ + oc_sess->msgid = (oc_sess->msgid != 1) ? oc_sess->msgid + 1 : oc_sess->msgid; + + msglen = strlen(msg); + asprintf(&starttag, "\n#%" PRIu64 "\n", msglen); + if (!starttag) { + OC_FAIL_LOG; + return -1; + goto cleanup; + } + + rc = oc_write(oc_sess, starttag, 0); + if (rc) { + OC_FAIL_LOG; + goto cleanup; + } + rc = oc_write(oc_sess, msg, msglen); + if (rc) { + OC_FAIL_LOG; + goto cleanup; + } + rc = oc_write(oc_sess, "\n##\n", 0); + if (rc) { + OC_FAIL_LOG; + goto cleanup; + } + +cleanup: + free(starttag); + return rc; +} + +int +oc_recv_msg(struct np_other_client *oc_sess, char **msg) +{ + int64_t len; + char *endtag; + + len = oc_read(oc_sess, 0); + + if (len < 0) { + return -1; + } else if (len == (int64_t)oc_sess->bufsize) { + /* unlikely, though no space for zero character */ + if (oc_realloc(oc_sess)) { + return -1; + } + } + + /* Delete end tag: \n##\n */ + endtag = (oc_sess->buf + len) - 4; + *endtag = '\0'; + + /* Skip first start tag: \n##number\n */ + *msg = strchr(oc_sess->buf + 1, '\n'); + if (**msg == '\0') { + return -1; + } + *msg = *msg + 1; + + return 0; +} + +void +oc_session_free(struct np_other_client *oc_sess) +{ + if (!oc_sess) { + return; + } + if (oc_sess->unixsock > 0) { + close(oc_sess->unixsock); + } + free(oc_sess->buf); + free(oc_sess); +} diff --git a/tests/np2_other_client.h b/tests/np2_other_client.h new file mode 100644 index 00000000..6684bbd2 --- /dev/null +++ b/tests/np2_other_client.h @@ -0,0 +1,73 @@ +/** + * @file np2_other_client.h + * @author Adam Piecek + * @brief An alternative test interface for communicating with the 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 + */ + +#ifndef _NP2_OTHER_CLIENT_H_ +#define _NP2_OTHER_CLIENT_H_ + +#include "stdint.h" + +/** + * @brief Client session structure. + */ +struct np_other_client { + int unixsock; /**< file descriptor of unix socket */ + uint64_t msgid; /**< message-id of the last message sent */ + char *buf; /**< private buffer used when reading a message */ + uint64_t bufsize; /**< size of np_other_client.buf */ +}; + +/** + * @brief Connect to Netopeer server by unix socket. + * + * Function do not cover authentication or any other manipulation with the transport layer, + * it only establish NETCONF session by sending and processing NETCONF \ messages. + * + * @param[in] address Path to the unix socket. + * @return Client session structure or NULL. + */ +struct np_other_client *oc_connect_unix(const char *address); + +/** + * @brief Send message to server. + * + * @param[in] oc_sess Client session. + * @param[in] msg Message to send. + * @return 0 on success. + */ +int oc_send_msg(struct np_other_client *oc_sess, const char *msg); + +/** + * @brief Send message to server. + * + * As expected, the first start tag '\n##number\n' is not present in @p msg. But if the response is long, + * it will consist of several chunks, so there will be another start tag somewhere in the @p msg response. + * The implementation does not delete these internal start tags because this API is used for testing error + * messages that are not that long. + * + * @param[in] oc_sess Client session. + * @param[out] msg Received message. Do not deallocate memory. + * @return 0 on success. + */ +int oc_recv_msg(struct np_other_client *oc_sess, char **msg); + +/** + * @brief Release client session. + * + * @param[in] oc_sess Client session. + */ +void oc_session_free(struct np_other_client *oc_sess); + +#endif /* _NP2_OTHER_CLIENT_H_ */ diff --git a/tests/np2_test.c b/tests/np2_test.c index f510e652..95930fc1 100644 --- a/tests/np2_test.c +++ b/tests/np2_test.c @@ -37,6 +37,7 @@ #include #include +#include "np2_other_client.h" #include "np2_test_config.h" #ifdef NETOPEER2_LIB @@ -248,7 +249,7 @@ np2_glob_test_setup_sess_ctx(struct nc_session *sess, const char **modules) } int -np2_glob_test_setup_server(void **state, const char *test_name, const char **modules) +np2_glob_test_setup_server(void **state, const char *test_name, const char **modules, uint32_t flags) { struct np2_test *st; pid_t pid = 0; @@ -432,22 +433,30 @@ np2_glob_test_setup_server(void **state, const char *test_name, const char **mod /* disable automatic YANG retrieval */ nc_client_set_new_session_context_autofill(0); - /* create NETCONF sessions, with a single context */ - st->nc_sess2 = nc_connect_unix(st->socket_path, NULL); - if (!st->nc_sess2) { - SETUP_FAIL_LOG; - return 1; - } - if (np2_glob_test_setup_sess_ctx(st->nc_sess2, modules)) { - SETUP_FAIL_LOG; - return 1; - } + if (flags & NP_GLOB_SETUP_OTHER_CLIENT) { + st->oc_sess = oc_connect_unix(st->socket_path); + if (!st->oc_sess) { + SETUP_FAIL_LOG; + return 1; + } + } else { + /* create NETCONF sessions, with a single context */ + st->nc_sess2 = nc_connect_unix(st->socket_path, NULL); + if (!st->nc_sess2) { + SETUP_FAIL_LOG; + return 1; + } + if (np2_glob_test_setup_sess_ctx(st->nc_sess2, modules)) { + SETUP_FAIL_LOG; + return 1; + } - ly_ctx = (struct ly_ctx *)nc_session_get_ctx(st->nc_sess2); - st->nc_sess = nc_connect_unix(st->socket_path, ly_ctx); - if (!st->nc_sess) { - SETUP_FAIL_LOG; - return 1; + ly_ctx = (struct ly_ctx *)nc_session_get_ctx(st->nc_sess2); + st->nc_sess = nc_connect_unix(st->socket_path, ly_ctx); + if (!st->nc_sess) { + SETUP_FAIL_LOG; + return 1; + } } return 0; @@ -510,6 +519,8 @@ np2_glob_test_teardown(void **state, const char **modules) /* release context */ sr_release_context(st->conn); + oc_session_free(st->oc_sess); + #ifdef NETOPEER2_LIB if (np2_server_test_stop()) { printf("np2_server_test_stop() failed\n"); diff --git a/tests/np2_test.h b/tests/np2_test.h index 79980086..15465298 100644 --- a/tests/np2_test.h +++ b/tests/np2_test.h @@ -45,6 +45,7 @@ struct np2_test { char *str; char *path; uint32_t ntf_id; + struct np_other_client *oc_sess; }; #define SETUP_FAIL_LOG \ @@ -299,7 +300,8 @@ void np2_glob_test_setup_test_name(char *buf); int np2_glob_test_setup_env(const char *test_name); -int np2_glob_test_setup_server(void **state, const char *test_name, const char **modules); +#define NP_GLOB_SETUP_OTHER_CLIENT 0x1 +int np2_glob_test_setup_server(void **state, const char *test_name, const char **modules, uint32_t flags); int np2_glob_test_teardown_notif(const char *test_name); diff --git a/tests/test_candidate.c b/tests/test_candidate.c index f066f52f..aebb749e 100644 --- a/tests/test_candidate.c +++ b/tests/test_candidate.c @@ -47,7 +47,7 @@ local_setup(void **state) assert_int_equal(rc, 0); /* setup netopeer2 server */ - rc = np2_glob_test_setup_server(state, test_name, modules); + rc = np2_glob_test_setup_server(state, test_name, modules, 0); assert_int_equal(rc, 0); st = *state; diff --git a/tests/test_confirmed_commit.c b/tests/test_confirmed_commit.c index 52f15a00..02e55e30 100644 --- a/tests/test_confirmed_commit.c +++ b/tests/test_confirmed_commit.c @@ -123,7 +123,7 @@ local_setup(void **state) assert_int_equal(rc, 0); /* setup netopeer2 server */ - rc = np2_glob_test_setup_server(state, test_name, modules); + rc = np2_glob_test_setup_server(state, test_name, modules, 0); assert_int_equal(rc, 0); st = *state; diff --git a/tests/test_edit.c b/tests/test_edit.c index a67c1a98..4ef2ad84 100644 --- a/tests/test_edit.c +++ b/tests/test_edit.c @@ -51,7 +51,7 @@ local_setup(void **state) assert_int_equal(rc, 0); /* setup netopeer2 server */ - rc = np2_glob_test_setup_server(state, test_name, modules); + rc = np2_glob_test_setup_server(state, test_name, modules, 0); assert_int_equal(rc, 0); /* setup NACM */ diff --git a/tests/test_error.c b/tests/test_error.c index b5023996..18398133 100644 --- a/tests/test_error.c +++ b/tests/test_error.c @@ -46,7 +46,7 @@ local_setup(void **state) assert_int_equal(rc, 0); /* setup netopeer2 server */ - rc = np2_glob_test_setup_server(state, test_name, modules); + rc = np2_glob_test_setup_server(state, test_name, modules, 0); assert_int_equal(rc, 0); /* setup NACM */ diff --git a/tests/test_filter.c b/tests/test_filter.c index 6d5474b3..ccc0254b 100644 --- a/tests/test_filter.c +++ b/tests/test_filter.c @@ -317,7 +317,7 @@ local_setup(void **state) assert_int_equal(rc, 0); /* setup netopeer2 server */ - rc = np2_glob_test_setup_server(state, test_name, modules); + rc = np2_glob_test_setup_server(state, test_name, modules, 0); assert_int_equal(rc, 0); st = *state; diff --git a/tests/test_nacm.c b/tests/test_nacm.c index 12338fdc..d16b9584 100644 --- a/tests/test_nacm.c +++ b/tests/test_nacm.c @@ -52,7 +52,7 @@ local_setup(void **state) assert_int_equal(rc, 0); /* setup netopeer2 server */ - rc = np2_glob_test_setup_server(state, test_name, modules); + rc = np2_glob_test_setup_server(state, test_name, modules, 0); assert_int_equal(rc, 0); st = *state; 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); +} diff --git a/tests/test_parallel_sessions.c b/tests/test_parallel_sessions.c index 56507fcb..82ff4f08 100644 --- a/tests/test_parallel_sessions.c +++ b/tests/test_parallel_sessions.c @@ -48,7 +48,7 @@ local_setup(void **state) rc = np2_glob_test_setup_env(test_name); assert_int_equal(rc, 0); - return np2_glob_test_setup_server(state, test_name, NULL); + return np2_glob_test_setup_server(state, test_name, NULL, 0); } static int diff --git a/tests/test_rpc.c b/tests/test_rpc.c index c8056799..9d96b6ff 100644 --- a/tests/test_rpc.c +++ b/tests/test_rpc.c @@ -51,7 +51,7 @@ local_setup(void **state) assert_int_equal(rc, 0); /* setup netopeer2 server */ - rc = np2_glob_test_setup_server(state, test_name, modules); + rc = np2_glob_test_setup_server(state, test_name, modules, 0); assert_int_equal(rc, 0); /* setup NACM */ diff --git a/tests/test_sub_ntf.c b/tests/test_sub_ntf.c index eea618ed..b4c92e80 100644 --- a/tests/test_sub_ntf.c +++ b/tests/test_sub_ntf.c @@ -49,7 +49,7 @@ local_setup(void **state) assert_int_equal(rc, 0); /* setup netopeer2 server */ - rc = np2_glob_test_setup_server(state, test_name, modules); + rc = np2_glob_test_setup_server(state, test_name, modules, 0); assert_int_equal(rc, 0); st = *state; diff --git a/tests/test_sub_ntf_advanced.c b/tests/test_sub_ntf_advanced.c index 6f02ebb4..cc862a0d 100644 --- a/tests/test_sub_ntf_advanced.c +++ b/tests/test_sub_ntf_advanced.c @@ -50,7 +50,7 @@ local_setup(void **state) assert_int_equal(rc, 0); /* setup netopeer2 server */ - rc = np2_glob_test_setup_server(state, test_name, modules); + rc = np2_glob_test_setup_server(state, test_name, modules, 0); assert_int_equal(rc, 0); st = *state; diff --git a/tests/test_sub_ntf_filter.c b/tests/test_sub_ntf_filter.c index b1ffb1c8..ca76cfe4 100644 --- a/tests/test_sub_ntf_filter.c +++ b/tests/test_sub_ntf_filter.c @@ -49,7 +49,7 @@ local_setup(void **state) assert_int_equal(rc, 0); /* setup netopeer2 server */ - rc = np2_glob_test_setup_server(state, test_name, modules); + rc = np2_glob_test_setup_server(state, test_name, modules, 0); assert_int_equal(rc, 0); st = *state; diff --git a/tests/test_subscribe_filter.c b/tests/test_subscribe_filter.c index 7dc9ecc0..4e662167 100644 --- a/tests/test_subscribe_filter.c +++ b/tests/test_subscribe_filter.c @@ -88,7 +88,7 @@ local_setup(void **state) assert_int_equal(rc, 0); /* setup netopeer2 server */ - rc = np2_glob_test_setup_server(state, test_name, modules); + rc = np2_glob_test_setup_server(state, test_name, modules, 0); assert_int_equal(rc, 0); st = *state; diff --git a/tests/test_subscribe_param.c b/tests/test_subscribe_param.c index 952fd03f..42959621 100644 --- a/tests/test_subscribe_param.c +++ b/tests/test_subscribe_param.c @@ -69,7 +69,7 @@ local_setup(void **state) assert_int_equal(rc, 0); /* setup netopeer2 server */ - rc = np2_glob_test_setup_server(state, test_name, modules); + rc = np2_glob_test_setup_server(state, test_name, modules, 0); assert_int_equal(rc, 0); st = *state; diff --git a/tests/test_url.c b/tests/test_url.c index e0c5132d..47b521bd 100644 --- a/tests/test_url.c +++ b/tests/test_url.c @@ -82,7 +82,7 @@ local_setup(void **state) assert_int_equal(rc, 0); /* setup netopeer2 server */ - rc = np2_glob_test_setup_server(state, test_name, modules); + rc = np2_glob_test_setup_server(state, test_name, modules, 0); assert_int_equal(rc, 0); /* setup NACM */ diff --git a/tests/test_with_defaults.c b/tests/test_with_defaults.c index 7f8fb881..353bac2b 100644 --- a/tests/test_with_defaults.c +++ b/tests/test_with_defaults.c @@ -46,7 +46,7 @@ local_setup(void **state) assert_int_equal(rc, 0); /* setup netopeer2 server */ - rc = np2_glob_test_setup_server(state, test_name, modules); + rc = np2_glob_test_setup_server(state, test_name, modules, 0); assert_int_equal(rc, 0); return 0; diff --git a/tests/test_yang_push.c b/tests/test_yang_push.c index 036a3c38..a98988ee 100644 --- a/tests/test_yang_push.c +++ b/tests/test_yang_push.c @@ -49,7 +49,7 @@ local_setup(void **state) assert_int_equal(rc, 0); /* setup netopeer2 server */ - rc = np2_glob_test_setup_server(state, test_name, modules); + rc = np2_glob_test_setup_server(state, test_name, modules, 0); assert_int_equal(rc, 0); st = *state; diff --git a/tests/test_yang_push_advanced.c b/tests/test_yang_push_advanced.c index 92bed2da..0f4ee4cb 100644 --- a/tests/test_yang_push_advanced.c +++ b/tests/test_yang_push_advanced.c @@ -49,7 +49,7 @@ local_setup(void **state) assert_int_equal(rc, 0); /* setup netopeer2 server */ - rc = np2_glob_test_setup_server(state, test_name, modules); + rc = np2_glob_test_setup_server(state, test_name, modules, 0); assert_int_equal(rc, 0); st = *state;