diff --git a/tests/CMakeLists.txt b/tests/CMakeLists.txt index 805e8c8f..f2008d43 100644 --- a/tests/CMakeLists.txt +++ b/tests/CMakeLists.txt @@ -38,7 +38,7 @@ 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 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_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;