| /** |
| * \file test_fd_comm.c |
| * \author Michal Vasko <mvasko@cesnet.cz> |
| * \brief libnetconf2 tests - file descriptor basic RPC communication |
| * |
| * Copyright (c) 2015 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 <errno.h> |
| #include <fcntl.h> |
| #include <pthread.h> |
| #include <setjmp.h> |
| #include <stdarg.h> |
| #include <stddef.h> |
| #include <stdint.h> |
| #include <stdlib.h> |
| #include <string.h> |
| #include <sys/socket.h> |
| #include <sys/stat.h> |
| #include <sys/types.h> |
| #include <unistd.h> |
| |
| #include <cmocka.h> |
| #include <libyang/libyang.h> |
| |
| #include <messages_p.h> |
| #include <session_client.h> |
| #include <session_p.h> |
| #include <session_server.h> |
| #include "tests/config.h" |
| |
| struct nc_session *server_session; |
| struct nc_session *client_session; |
| struct ly_ctx *ctx; |
| pthread_mutex_t state_lock = PTHREAD_MUTEX_INITIALIZER; |
| pthread_barrier_t barrier; |
| int glob_state; |
| |
| struct nc_server_reply * |
| my_get_rpc_clb(struct lyd_node *rpc, struct nc_session *session) |
| { |
| assert_string_equal(rpc->schema->name, "get"); |
| assert_ptr_equal(session, server_session); |
| |
| return nc_server_reply_ok(); |
| } |
| |
| struct nc_server_reply * |
| my_getconfig_rpc_clb(struct lyd_node *rpc, struct nc_session *session) |
| { |
| struct lyd_node *data; |
| |
| assert_string_equal(rpc->schema->name, "get-config"); |
| assert_ptr_equal(session, server_session); |
| |
| lyd_new_path(NULL, session->ctx, "/ietf-netconf:get-config/data", NULL, LYD_NEW_PATH_OUTPUT, &data); |
| assert_non_null(data); |
| |
| return nc_server_reply_data(data, NC_WD_EXPLICIT, NC_PARAMTYPE_FREE); |
| } |
| |
| struct nc_server_reply * |
| my_commit_rpc_clb(struct lyd_node *rpc, struct nc_session *session) |
| { |
| assert_string_equal(rpc->schema->name, "commit"); |
| assert_ptr_equal(session, server_session); |
| |
| /* update state */ |
| pthread_mutex_lock(&state_lock); |
| glob_state = 1; |
| |
| /* wait until the client receives the notification */ |
| while (glob_state != 3) { |
| pthread_mutex_unlock(&state_lock); |
| usleep(100000); |
| pthread_mutex_lock(&state_lock); |
| } |
| pthread_mutex_unlock(&state_lock); |
| |
| return nc_server_reply_ok(); |
| } |
| |
| static struct nc_session * |
| test_new_session(NC_SIDE side) |
| { |
| struct nc_session *sess; |
| |
| sess = calloc(1, sizeof *sess); |
| if (!sess) { |
| return NULL; |
| } |
| |
| sess->side = side; |
| |
| if (side == NC_SERVER) { |
| pthread_mutex_init(&sess->opts.server.rpc_lock, NULL); |
| pthread_cond_init(&sess->opts.server.rpc_cond, NULL); |
| sess->opts.server.rpc_inuse = 0; |
| } |
| |
| sess->io_lock = malloc(sizeof *sess->io_lock); |
| if (!sess->io_lock) { |
| goto error; |
| } |
| pthread_mutex_init(sess->io_lock, NULL); |
| |
| return sess; |
| |
| error: |
| free(sess); |
| return NULL; |
| } |
| |
| static int |
| setup_sessions(void **state) |
| { |
| (void)state; |
| int sock[2]; |
| |
| /* create communication channel */ |
| socketpair(AF_UNIX, SOCK_STREAM, 0, sock); |
| |
| /* create server session */ |
| server_session = test_new_session(NC_SERVER); |
| server_session->status = NC_STATUS_RUNNING; |
| server_session->id = 1; |
| server_session->ti_type = NC_TI_FD; |
| server_session->ti.fd.in = sock[0]; |
| server_session->ti.fd.out = sock[0]; |
| server_session->ctx = ctx; |
| server_session->flags = NC_SESSION_SHAREDCTX; |
| |
| /* create client session */ |
| client_session = test_new_session(NC_CLIENT); |
| client_session->status = NC_STATUS_RUNNING; |
| client_session->id = 1; |
| client_session->ti_type = NC_TI_FD; |
| client_session->ti.fd.in = sock[1]; |
| client_session->ti.fd.out = sock[1]; |
| client_session->ctx = ctx; |
| client_session->flags = NC_SESSION_SHAREDCTX; |
| client_session->opts.client.msgid = 50; |
| |
| return 0; |
| } |
| |
| static int |
| teardown_sessions(void **state) |
| { |
| (void)state; |
| |
| close(server_session->ti.fd.in); |
| server_session->ti.fd.in = -1; |
| nc_session_free(server_session, NULL); |
| |
| close(client_session->ti.fd.in); |
| client_session->ti.fd.in = -1; |
| nc_session_free(client_session, NULL); |
| |
| return 0; |
| } |
| |
| static void |
| test_send_recv_ok(void) |
| { |
| int ret; |
| uint64_t msgid; |
| NC_MSG_TYPE msgtype; |
| struct nc_rpc *rpc; |
| struct lyd_node *envp, *op; |
| struct nc_pollsession *ps; |
| |
| /* client RPC */ |
| rpc = nc_rpc_get(NULL, 0, 0); |
| assert_non_null(rpc); |
| |
| msgtype = nc_send_rpc(client_session, rpc, 0, &msgid); |
| assert_int_equal(msgtype, NC_MSG_RPC); |
| |
| /* server RPC, send reply */ |
| ps = nc_ps_new(); |
| assert_non_null(ps); |
| nc_ps_add_session(ps, server_session); |
| |
| ret = nc_ps_poll(ps, 0, NULL); |
| assert_int_equal(ret, NC_PSPOLL_RPC); |
| |
| /* server finished */ |
| nc_ps_free(ps); |
| |
| /* client reply */ |
| msgtype = nc_recv_reply(client_session, rpc, msgid, 0, &envp, &op); |
| assert_int_equal(msgtype, NC_MSG_REPLY); |
| |
| nc_rpc_free(rpc); |
| assert_null(op); |
| assert_string_equal(LYD_NAME(lyd_child(envp)), "ok"); |
| lyd_free_tree(envp); |
| } |
| |
| static void |
| test_send_recv_ok_10(void **state) |
| { |
| (void)state; |
| |
| server_session->version = NC_VERSION_10; |
| client_session->version = NC_VERSION_10; |
| |
| test_send_recv_ok(); |
| } |
| |
| static void |
| test_send_recv_ok_11(void **state) |
| { |
| (void)state; |
| |
| server_session->version = NC_VERSION_11; |
| client_session->version = NC_VERSION_11; |
| |
| test_send_recv_ok(); |
| } |
| |
| static void |
| test_send_recv_error(void) |
| { |
| int ret; |
| uint64_t msgid; |
| NC_MSG_TYPE msgtype; |
| struct nc_rpc *rpc; |
| struct lyd_node *envp, *op, *node; |
| struct nc_pollsession *ps; |
| |
| /* client RPC */ |
| rpc = nc_rpc_kill(1); |
| assert_non_null(rpc); |
| |
| msgtype = nc_send_rpc(client_session, rpc, 0, &msgid); |
| assert_int_equal(msgtype, NC_MSG_RPC); |
| |
| /* server RPC, send reply */ |
| ps = nc_ps_new(); |
| assert_non_null(ps); |
| nc_ps_add_session(ps, server_session); |
| |
| ret = nc_ps_poll(ps, 0, NULL); |
| assert_int_equal(ret, NC_PSPOLL_RPC | NC_PSPOLL_REPLY_ERROR); |
| |
| /* server finished */ |
| nc_ps_free(ps); |
| |
| /* client reply */ |
| msgtype = nc_recv_reply(client_session, rpc, msgid, 0, &envp, &op); |
| assert_int_equal(msgtype, NC_MSG_REPLY); |
| |
| nc_rpc_free(rpc); |
| assert_string_equal(LYD_NAME(lyd_child(envp)), "rpc-error"); |
| lyd_find_sibling_opaq_next(lyd_child(lyd_child(envp)), "error-tag", &node); |
| assert_non_null(node); |
| assert_string_equal(((struct lyd_node_opaq *)node)->value, "operation-not-supported"); |
| lyd_free_tree(envp); |
| assert_null(op); |
| } |
| |
| static void |
| test_send_recv_error_10(void **state) |
| { |
| (void)state; |
| |
| server_session->version = NC_VERSION_10; |
| client_session->version = NC_VERSION_10; |
| |
| test_send_recv_error(); |
| } |
| |
| static void |
| test_send_recv_error_11(void **state) |
| { |
| (void)state; |
| |
| server_session->version = NC_VERSION_11; |
| client_session->version = NC_VERSION_11; |
| |
| test_send_recv_error(); |
| } |
| |
| static void |
| test_send_recv_data(void) |
| { |
| int ret; |
| uint64_t msgid; |
| NC_MSG_TYPE msgtype; |
| struct nc_rpc *rpc; |
| struct lyd_node *envp, *op; |
| struct nc_pollsession *ps; |
| |
| /* client RPC */ |
| rpc = nc_rpc_getconfig(NC_DATASTORE_RUNNING, NULL, 0, 0); |
| assert_non_null(rpc); |
| |
| msgtype = nc_send_rpc(client_session, rpc, 0, &msgid); |
| assert_int_equal(msgtype, NC_MSG_RPC); |
| |
| /* server RPC, send reply */ |
| ps = nc_ps_new(); |
| assert_non_null(ps); |
| nc_ps_add_session(ps, server_session); |
| |
| ret = nc_ps_poll(ps, 0, NULL); |
| assert_int_equal(ret, NC_PSPOLL_RPC); |
| |
| /* server finished */ |
| nc_ps_free(ps); |
| |
| /* client reply */ |
| msgtype = nc_recv_reply(client_session, rpc, msgid, 0, &envp, &op); |
| assert_int_equal(msgtype, NC_MSG_REPLY); |
| |
| nc_rpc_free(rpc); |
| assert_non_null(envp); |
| lyd_free_tree(envp); |
| assert_non_null(op); |
| lyd_free_tree(op); |
| } |
| |
| static void |
| test_send_recv_data_10(void **state) |
| { |
| (void)state; |
| |
| server_session->version = NC_VERSION_10; |
| client_session->version = NC_VERSION_10; |
| |
| test_send_recv_data(); |
| } |
| |
| static void |
| test_send_recv_data_11(void **state) |
| { |
| (void)state; |
| |
| server_session->version = NC_VERSION_11; |
| client_session->version = NC_VERSION_11; |
| |
| test_send_recv_data(); |
| } |
| |
| static void * |
| server_send_notif_thread(void *arg) |
| { |
| NC_MSG_TYPE msg_type; |
| struct lyd_node *notif_tree; |
| struct nc_server_notif *notif; |
| struct timespec ts; |
| char *buf; |
| |
| (void)arg; |
| |
| /* wait for the RPC callback to be called */ |
| pthread_mutex_lock(&state_lock); |
| while (glob_state != 1) { |
| pthread_mutex_unlock(&state_lock); |
| usleep(1000); |
| pthread_mutex_lock(&state_lock); |
| } |
| |
| /* create notif */ |
| lyd_new_path(NULL, ctx, "/nc-notifications:notificationComplete", NULL, 0, ¬if_tree); |
| assert_non_null(notif_tree); |
| clock_gettime(CLOCK_REALTIME, &ts); |
| ly_time_ts2str(&ts, &buf); |
| notif = nc_server_notif_new(notif_tree, buf, NC_PARAMTYPE_FREE); |
| assert_non_null(notif); |
| |
| /* send notif */ |
| nc_session_inc_notif_status(server_session); |
| msg_type = nc_server_notif_send(server_session, notif, 100); |
| nc_server_notif_free(notif); |
| assert_int_equal(msg_type, NC_MSG_NOTIF); |
| |
| /* update state */ |
| glob_state = 2; |
| pthread_barrier_wait(&barrier); |
| pthread_mutex_unlock(&state_lock); |
| |
| return NULL; |
| } |
| |
| static void * |
| thread_recv_notif(void *arg) |
| { |
| struct nc_session *session = (struct nc_session *)arg; |
| struct lyd_node *envp; |
| struct lyd_node *op; |
| NC_MSG_TYPE msgtype; |
| |
| pthread_barrier_wait(&barrier); |
| msgtype = nc_recv_notif(session, 1000, &envp, &op); |
| assert_int_equal(msgtype, NC_MSG_NOTIF); |
| assert_string_equal(op->schema->name, "notificationComplete"); |
| |
| lyd_free_tree(envp); |
| lyd_free_tree(op); |
| |
| pthread_mutex_lock(&state_lock); |
| glob_state = 3; |
| pthread_mutex_unlock(&state_lock); |
| |
| return (void *)0; |
| } |
| |
| static void |
| test_send_recv_notif(void) |
| { |
| int ret; |
| pthread_t tid[2]; |
| uint64_t msgid; |
| NC_MSG_TYPE msgtype; |
| struct nc_rpc *rpc; |
| struct lyd_node *envp, *op; |
| struct nc_pollsession *ps; |
| |
| /* client RPC */ |
| rpc = nc_rpc_commit(0, 0, NULL, NULL, 0); |
| assert_non_null(rpc); |
| |
| msgtype = nc_send_rpc(client_session, rpc, 0, &msgid); |
| assert_int_equal(msgtype, NC_MSG_RPC); |
| |
| /* client subscription */ |
| pthread_create(&tid[0], NULL, thread_recv_notif, client_session); |
| |
| /* create server */ |
| ps = nc_ps_new(); |
| assert_non_null(ps); |
| nc_ps_add_session(ps, server_session); |
| |
| /* server will send a notification */ |
| pthread_mutex_lock(&state_lock); |
| glob_state = 0; |
| pthread_mutex_unlock(&state_lock); |
| ret = pthread_create(&tid[1], NULL, server_send_notif_thread, NULL); |
| assert_int_equal(ret, 0); |
| |
| /* server blocked on RPC */ |
| ret = nc_ps_poll(ps, 0, NULL); |
| assert_int_equal(ret, NC_PSPOLL_RPC); |
| |
| /* RPC, notification finished fine */ |
| pthread_mutex_lock(&state_lock); |
| assert_int_equal(glob_state, 3); |
| pthread_mutex_unlock(&state_lock); |
| |
| /* server finished */ |
| ret = 0; |
| ret |= pthread_join(tid[0], NULL); |
| ret |= pthread_join(tid[1], NULL); |
| assert_int_equal(ret, 0); |
| nc_ps_free(ps); |
| |
| /* client reply */ |
| msgtype = nc_recv_reply(client_session, rpc, msgid, 0, &envp, &op); |
| assert_int_equal(msgtype, NC_MSG_REPLY); |
| nc_rpc_free(rpc); |
| |
| assert_string_equal(LYD_NAME(lyd_child(envp)), "ok"); |
| lyd_free_tree(envp); |
| assert_null(op); |
| } |
| |
| static void |
| test_send_recv_notif_10(void **state) |
| { |
| (void)state; |
| |
| server_session->version = NC_VERSION_10; |
| client_session->version = NC_VERSION_10; |
| |
| test_send_recv_notif(); |
| } |
| |
| static void |
| test_send_recv_notif_11(void **state) |
| { |
| (void)state; |
| |
| server_session->version = NC_VERSION_11; |
| client_session->version = NC_VERSION_11; |
| |
| test_send_recv_notif(); |
| } |
| |
| static void |
| test_send_recv_malformed_10(void **state) |
| { |
| int ret; |
| struct nc_pollsession *ps; |
| struct nc_rpc *rpc; |
| struct lyd_node *envp, *op, *node; |
| NC_MSG_TYPE msgtype; |
| const char *msg; |
| |
| (void)state; |
| |
| server_session->version = NC_VERSION_10; |
| client_session->version = NC_VERSION_10; |
| |
| /* write malformed message */ |
| msg = |
| "<nc:rpc xmlns:nc=\"urn:ietf:params:xml:ns:netconf:base:1.0\">" |
| " <nc:commit/>" |
| "</nc:rpc>" |
| "]]>]]>"; |
| assert_int_equal(write(client_session->ti.fd.out, msg, strlen(msg)), strlen(msg)); |
| rpc = nc_rpc_commit(0, 0, NULL, NULL, 0); |
| assert_non_null(rpc); |
| |
| /* server RPC, send reply */ |
| ps = nc_ps_new(); |
| assert_non_null(ps); |
| nc_ps_add_session(ps, server_session); |
| |
| ret = nc_ps_poll(ps, 0, NULL); |
| assert_int_equal(ret, NC_PSPOLL_BAD_RPC | NC_PSPOLL_REPLY_ERROR); |
| |
| /* server finished */ |
| nc_ps_free(ps); |
| |
| /* client reply */ |
| msgtype = nc_recv_reply(client_session, rpc, 0, 0, &envp, &op); |
| assert_int_equal(msgtype, NC_MSG_REPLY_ERR_MSGID); |
| |
| nc_rpc_free(rpc); |
| assert_string_equal(LYD_NAME(lyd_child(envp)), "rpc-error"); |
| lyd_find_sibling_opaq_next(lyd_child(lyd_child(envp)), "error-tag", &node); |
| assert_non_null(node); |
| assert_string_equal(((struct lyd_node_opaq *)node)->value, "missing-attribute"); |
| lyd_free_tree(envp); |
| assert_null(op); |
| } |
| |
| int |
| main(void) |
| { |
| int ret; |
| const struct lys_module *module; |
| struct lysc_node *node; |
| const char *nc_features[] = {"candidate", NULL}; |
| |
| pthread_barrier_init(&barrier, NULL, 2); |
| |
| /* create ctx */ |
| ly_ctx_new(TESTS_DIR "/data/modules", 0, &ctx); |
| assert_non_null(ctx); |
| |
| /* load modules */ |
| module = ly_ctx_load_module(ctx, "ietf-netconf-acm", NULL, NULL); |
| assert_non_null(module); |
| |
| module = ly_ctx_load_module(ctx, "ietf-netconf", NULL, nc_features); |
| assert_non_null(module); |
| |
| module = ly_ctx_load_module(ctx, "nc-notifications", NULL, NULL); |
| assert_non_null(module); |
| |
| /* set RPC callbacks */ |
| node = (struct lysc_node *)lys_find_path(module->ctx, NULL, "/ietf-netconf:get", 0); |
| assert_non_null(node); |
| node->priv = my_get_rpc_clb; |
| |
| node = (struct lysc_node *)lys_find_path(module->ctx, NULL, "/ietf-netconf:get-config", 0); |
| assert_non_null(node); |
| node->priv = my_getconfig_rpc_clb; |
| |
| node = (struct lysc_node *)lys_find_path(module->ctx, NULL, "/ietf-netconf:commit", 0); |
| assert_non_null(node); |
| node->priv = my_commit_rpc_clb; |
| |
| nc_server_init(); |
| |
| const struct CMUnitTest comm[] = { |
| cmocka_unit_test_setup_teardown(test_send_recv_ok_10, setup_sessions, teardown_sessions), |
| cmocka_unit_test_setup_teardown(test_send_recv_error_10, setup_sessions, teardown_sessions), |
| cmocka_unit_test_setup_teardown(test_send_recv_data_10, setup_sessions, teardown_sessions), |
| cmocka_unit_test_setup_teardown(test_send_recv_notif_10, setup_sessions, teardown_sessions), |
| cmocka_unit_test_setup_teardown(test_send_recv_malformed_10, setup_sessions, teardown_sessions), |
| cmocka_unit_test_setup_teardown(test_send_recv_ok_11, setup_sessions, teardown_sessions), |
| cmocka_unit_test_setup_teardown(test_send_recv_error_11, setup_sessions, teardown_sessions), |
| cmocka_unit_test_setup_teardown(test_send_recv_data_11, setup_sessions, teardown_sessions), |
| cmocka_unit_test_setup_teardown(test_send_recv_notif_11, setup_sessions, teardown_sessions), |
| }; |
| |
| ret = cmocka_run_group_tests(comm, NULL, NULL); |
| |
| nc_server_destroy(); |
| ly_ctx_destroy(ctx); |
| pthread_barrier_destroy(&barrier); |
| |
| return ret; |
| } |