/** * \file test_fd_comm.c * \author Michal Vasko * \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 */ #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #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(); } 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(ctx); 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_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; }