FEATURE basic sessions and RPCs code
- prototypes for functions to connect client to server
- implementation of nc_session_connect_inout()
- basic functionality to create RPC objects on client side
- lock, unlock and get-config (including filter manipulation)
diff --git a/src/datastore.c b/src/datastore.c
new file mode 100644
index 0000000..7a62f15
--- /dev/null
+++ b/src/datastore.c
@@ -0,0 +1,27 @@
+/**
+ * \file datastore.c
+ * \author Radek Krejci <rkrejci@cesnet.cz>
+ * \brief libnetconf2 - datastore functions
+ *
+ * Copyright (c) 2015 CESNET, z.s.p.o.
+ *
+ * Redistribution and use in source and binary forms, with or without
+ * modification, are permitted provided that the following conditions
+ * are met:
+ * 1. Redistributions of source code must retain the above copyright
+ * notice, this list of conditions and the following disclaimer.
+ * 2. Redistributions in binary form must reproduce the above copyright
+ * notice, this list of conditions and the following disclaimer in
+ * the documentation and/or other materials provided with the
+ * distribution.
+ * 3. Neither the name of the Company nor the names of its contributors
+ * may be used to endorse or promote products derived from this
+ * software without specific prior written permission.
+ *
+ */
+
+#include "config.h"
+#include "libnetconf.h"
+#include "datastore_p.h"
+
+const char *ncds2str[] = {NULL, "config", "url", "running", "startup", "candidate"};
diff --git a/src/datastore_p.h b/src/datastore_p.h
new file mode 100644
index 0000000..c3e7652
--- /dev/null
+++ b/src/datastore_p.h
@@ -0,0 +1,28 @@
+/**
+ * \file datastore_p.h
+ * \author Radek Krejci <rkrejci@cesnet.cz>
+ * \brief libnetconf2 datastore internal manipulation
+ *
+ * Copyright (c) 2015 CESNET, z.s.p.o.
+ *
+ * Redistribution and use in source and binary forms, with or without
+ * modification, are permitted provided that the following conditions
+ * are met:
+ * 1. Redistributions of source code must retain the above copyright
+ * notice, this list of conditions and the following disclaimer.
+ * 2. Redistributions in binary form must reproduce the above copyright
+ * notice, this list of conditions and the following disclaimer in
+ * the documentation and/or other materials provided with the
+ * distribution.
+ * 3. Neither the name of the Company nor the names of its contributors
+ * may be used to endorse or promote products derived from this
+ * software without specific prior written permission.
+ *
+ */
+
+#ifndef NC_DATASTORE_PRIVATE_H_
+#define NC_DATASTORE_PRIVATE_H_
+
+extern const char *ncds2str[];
+
+#endif /* NC_DATASTORE_PRIVATE_H_ */
diff --git a/src/io.c b/src/io.c
index b896caa..8311e3a 100644
--- a/src/io.c
+++ b/src/io.c
@@ -54,14 +54,6 @@
switch(session->ti_type) {
case NC_TI_FD:
/* read via standard file descriptor */
- if (session->ti.fd.c) {
- /* get character from buffer (ungetc() simulation) */
- buf[size++] = session->ti.fd.c;
- session->ti.fd.c = 0;
-
- count--;
- }
- /* read data from file descriptor */
while(count) {
r = read(session->ti.fd.in, &(buf[size]), count);
if (r < 0) {
@@ -430,7 +422,7 @@
free(*data);
*data = NULL;
- if (session->side == NC_SIDE_SERVER && session->version == NC_VERSION_11) {
+ if (session->side == NC_SERVER && session->version == NC_VERSION_11) {
/* NETCONF version 1.1 define sending error reply from the server */
/* TODO
reply = nc_reply_error(nc_err_new(NC_ERR_MALFORMED_MSG));
@@ -578,11 +570,12 @@
static int
write_msg_10(struct nc_session *session, NC_MSG_TYPE type, va_list ap)
{
- int count;
+ int count, i;
const char *attrs;
struct lyd_node *content;
- struct nc_rpc *rpc;
- struct lyxml_elem *capabilities;
+ struct nc_rpc_server *rpc;
+ const char **capabilities;
+ uint32_t *sid = NULL;
char *buf = NULL;
struct wclb_arg arg;
@@ -625,7 +618,7 @@
break;
case NC_MSG_REPLY:
- rpc = va_arg(ap, struct nc_rpc *);
+ rpc = va_arg(ap, struct nc_rpc_server *);
switch (session->ti_type) {
case NC_TI_FD:
write(session->ti.fd.out, "<rpc-reply", 10);
@@ -689,12 +682,19 @@
break;
case NC_MSG_HELLO:
- capabilities = va_arg(ap, struct lyxml_elem *);
+ capabilities = va_arg(ap, const char **);
+ sid = va_arg(ap, uint32_t*);
switch (session->ti_type) {
case NC_TI_FD:
- dprintf(session->ti.fd.out, "<hello xmlns=\"%s\">", NC_NS_BASE);
- lyxml_dump_fd(session->ti.fd.out, capabilities, 0);
- write(session->ti.fd.out, "</hello>]]>]]>", 11);
+ dprintf(session->ti.fd.out, "<hello xmlns=\"%s\"><capabilities>", NC_NS_BASE);
+ for (i = 0; capabilities[i]; i++) {
+ dprintf(session->ti.fd.out, "<capability>%s</capability>", capabilities[i]);
+ }
+ if (sid) {
+ dprintf(session->ti.fd.out, "</capabilities><session-id>%u</session-id></hello>]]>]]>", *sid);
+ } else {
+ write(session->ti.fd.out, "</capabilities></hello>]]>]]>", 29);
+ }
break;
#ifdef ENABLE_LIBSSH
@@ -704,11 +704,21 @@
case NC_TI_OPENSSL:
#endif
#if defined(ENABLE_LIBSSH) || defined(ENABLE_TLS)
- count = asprintf(&buf, "<hello xmlns=\"%s\">", NC_NS_BASE);
+ count = asprintf(&buf, "<hello xmlns=\"%s\"><capabilities>", NC_NS_BASE);
write_clb((void *)&arg, buf, count);
free(buf);
- lyxml_dump_clb(write_clb, (void *)&arg, capabilities, 0);
- write_clb((void *)&arg, "</hello>", 8);
+ for (i = 0; capabilities[i]; i++) {
+ count = asprintf(&buf, "<capability>%s</capability>", capabilities[i]);
+ write_clb((void *)&arg, buf, count);
+ free(buf);
+ }
+ if (sid) {
+ asprintf(&buf, "</capabilities><session-id>%u</session-id></hello>", *sid);
+ write_clb((void *)&arg, buf, count);
+ free(buf);
+ } else {
+ write_clb((void *)&arg, "</capabilities></hello>", 23);
+ }
/* flush message */
write_clb((void *)&arg, NULL, 0);
@@ -733,7 +743,7 @@
int count;
const char *attrs;
struct lyd_node *content;
- struct nc_rpc *rpc;
+ struct nc_rpc_server *rpc;
char *buf = NULL;
struct wclb_arg arg;
@@ -754,7 +764,7 @@
session->msgid++;
break;
case NC_MSG_REPLY:
- rpc = va_arg(ap, struct nc_rpc *);
+ rpc = va_arg(ap, struct nc_rpc_server *);
write_clb((void *)&arg, "<rpc-reply", 10);
lyxml_dump_clb(write_clb, (void *)&arg, rpc->root, LYXML_DUMP_ATTRS);
write_clb((void *)&arg, ">", 1);
diff --git a/src/messages.c b/src/messages.c
index cdf4dd1..5f0cc19 100644
--- a/src/messages.c
+++ b/src/messages.c
@@ -21,21 +21,136 @@
*/
#include <stdlib.h>
+#include <string.h>
#include <libyang/libyang.h>
#include "libnetconf.h"
#include "messages_p.h"
+API struct nc_filter *
+nc_filter_new(NC_FILTER type, char *data, int constdata)
+{
+ struct nc_filter *filter;
+
+ if (!data) {
+ data = "";
+ constdata = 1;
+ }
+
+ filter = malloc(sizeof *filter);
+ if (!filter) {
+ ERRMEM;
+ return NULL;
+ }
+
+ filter->type = type;
+ filter->refs = 1;
+ if (constdata) {
+ filter->data = strdup(data);
+ } else {
+ filter->data = data;
+ }
+
+ return filter;
+}
+
+API void
+nc_filter_free(struct nc_filter *filter)
+{
+ if (!filter) {
+ return;
+ }
+
+ filter->refs--;
+
+ if (!filter->refs) {
+ free(filter->data);
+ free(filter);
+ }
+}
+
+API struct nc_rpc *
+nc_rpc_getconfig(NC_DATASTORE source, struct nc_filter *filter)
+{
+ struct nc_rpc_getconfig *rpc;
+
+ rpc = calloc(1, sizeof *rpc);
+ if (!rpc) {
+ ERRMEM;
+ return NULL;
+ }
+
+ rpc->type = NC_RPC_GETCONFIG;
+ rpc->source = source;
+ if (filter) {
+ filter->refs++;
+ rpc->filter = filter;
+ }
+
+ return (struct nc_rpc *)rpc;
+}
+
+API struct nc_rpc *
+nc_rpc_lock(NC_DATASTORE target)
+{
+ struct nc_rpc_lock *rpc;
+
+ rpc = malloc(sizeof *rpc);
+ if (!rpc) {
+ ERRMEM;
+ return NULL;
+ }
+
+ rpc->type = NC_RPC_LOCK;
+ rpc->target = target;
+
+ return (struct nc_rpc *)rpc;
+}
+
+API struct nc_rpc *
+nc_rpc_unlock(NC_DATASTORE target)
+{
+ struct nc_rpc_lock *rpc;
+
+ rpc = malloc(sizeof *rpc);
+ if (!rpc) {
+ ERRMEM;
+ return NULL;
+ }
+
+ rpc->type = NC_RPC_UNLOCK;
+ rpc->target = target;
+
+ return (struct nc_rpc *)rpc;
+}
+
API void
nc_rpc_free(struct nc_rpc *rpc)
{
+ struct nc_rpc_server *rpc_server;
+ struct nc_rpc_getconfig *rpc_getconfig;
+
if (!rpc) {
return;
}
- lyxml_free_elem(rpc->ctx, rpc->root);
- lyd_free(rpc->tree);
+ switch(rpc->type) {
+ case NC_RPC_SERVER:
+ rpc_server = (struct nc_rpc_server *)rpc;
+ lyxml_free_elem(rpc_server->ctx, rpc_server->root);
+ lyd_free(rpc_server->tree);
+ break;
+ case NC_RPC_GETCONFIG:
+ rpc_getconfig = (struct nc_rpc_getconfig *)rpc;
+ nc_filter_free(rpc_getconfig->filter);
+ break;
+ case NC_RPC_LOCK:
+ case NC_RPC_UNLOCK:
+ /* nothing special needed */
+ break;
+ }
+
free(rpc);
}
diff --git a/src/messages.h b/src/messages.h
index 7d505ef..341af39 100644
--- a/src/messages.h
+++ b/src/messages.h
@@ -23,10 +23,41 @@
#ifndef NC_MESSAGES_H_
#define NC_MESSAGES_H_
+typedef enum {
+ NC_FILTER_SUBTREE,
+ NC_FILTER_XPATH
+} NC_FILTER;
+
+struct nc_filter;
+
+/**
+ * @brief Create NETCONF filter for \<get\> or \<get-config\> RPCs.
+ *
+ * The returned object can be used repeatedly. Caller is supposed to free it using nc_filter_free().
+ *
+ * @param[in] type Filter type of the \p data. #NC_FILTER_SUBTREE and #NC_FILTER_XPATH are supported.
+ * Note that #NC_FILTER_XPATH is accepted only on sessions supporting the :xpath capability.
+ * @param[in] data Content of the filter. Serialized XML data in case of #NC_FILTER_SUBTREE and XPath query
+ * in case of #NC_FILTER_XPATH (use YANG schema names as namespace prefixes).
+ * @param[in] constdata Flag for handling \p data. If set, the \p data is handled as const char* and the string
+ * is duplicated for internal use. If not set, \p data is not duplicated but caller is supposed
+ * to forget about the provided string.
+ * @return Created filter structure to be used in nc_rpc_getconfig() and nc_rpc_get(). NULL in case of failure.
+ */
+struct nc_filter *nc_filter_new(NC_FILTER type, char *data, int constdata);
+
+/**
+ * @brief Free the NETCONF filter object.
+ *
+ * @param[in] filter Object to free.
+ */
+void nc_filter_free(struct nc_filter *filter);
+
/**
* @brief NETCONF RPC object
*/
struct nc_rpc;
+struct nc_rpc_server;
/**
* @brief NETCONF RPC reply object
@@ -39,6 +70,49 @@
struct nc_notif;
/**
+ * @brief Create NETCONF RPC \<get-config\>
+ *
+ * Note that functions to create any RPC object do not check validity of the provided
+ * parameters. It is checked later while sending the RPC via a specific NETCONF session
+ * (nc_send_rpc()) since the NETCONF capabilities of the session are needed for such a
+ * check. Created object can be sent via any NETCONF session which supports all the
+ * needed NETCONF capabilities for the RPC.
+ *
+ * @param[in] source Source datastore being queried.
+ * @param[in] filter Optional filter data, see nc_filter_new().
+ * @return Created RPC object to send via a NETCONF session or NULL in case of (memory allocation) error.
+ */
+struct nc_rpc *nc_rpc_getconfig(NC_DATASTORE source, struct nc_filter *filter);
+
+/**
+ * @brief Create NETCONF RPC \<lock\>
+ *
+ * Note that functions to create any RPC object do not check validity of the provided
+ * parameters. It is checked later while sending the RPC via a specific NETCONF session
+ * (nc_send_rpc()) since the NETCONF capabilities of the session are needed for such a
+ * check. Created object can be sent via any NETCONF session which supports all the
+ * needed NETCONF capabilities for the RPC.
+ *
+ * @param[in] target Target datastore of the operation.
+ * @return Created RPC object to send via a NETCONF session or NULL in case of (memory allocation) error.
+ */
+struct nc_rpc *nc_rpc_lock(NC_DATASTORE target);
+
+/**
+ * @brief Create NETCONF RPC \<unlock\>
+ *
+ * Note that functions to create any RPC object do not check validity of the provided
+ * parameters. It is checked later while sending the RPC via a specific NETCONF session
+ * (nc_send_rpc()) since the NETCONF capabilities of the session are needed for such a
+ * check. Created object can be sent via any NETCONF session which supports all the
+ * needed NETCONF capabilities for the RPC.
+ *
+ * @param[in] target Target datastore of the operation.
+ * @return Created RPC object to send via a NETCONF session or NULL in case of (memory allocation) error.
+ */
+struct nc_rpc *nc_rpc_unlock(NC_DATASTORE target);
+
+/**
* @brief Free the NETCONF RPC object.
* @param[in] rpc Object to free.
*/
diff --git a/src/messages_p.h b/src/messages_p.h
index 20bbfc7..0b4c27a 100644
--- a/src/messages_p.h
+++ b/src/messages_p.h
@@ -27,10 +27,46 @@
#include "messages.h"
+typedef enum {
+ NC_RPC_SERVER, /**< server-side RPC object, see #nc_rpc_server. All other values define client-side RPC object. */
+ NC_RPC_GETCONFIG, /**< \<get-config\> RPC, see #nc_rpc_getconfig. */
+ NC_RPC_EDIT, /**< \<edit-config\> RPC, see #nc_rpc_edit. */
+ NC_RPC_COPY, /**< \<copy-config\> RPC, see #nc_rpc_copy. */
+ NC_RPC_DELETE, /**< \<delete-config\> RPC, see #nc_rpc_delete. */
+ NC_RPC_LOCK, /**< \<lock\> RPC, see #nc_rpc_lock. */
+ NC_RPC_UNLOCK, /**< \<unlock\> RPC, see #nc_rpc_lock. */
+ NC_RPC_GET, /**< \<get\> RPC, see #nc_rpc_get. */
+ /* NC_RPC_CLOSE is not defined since sending \<close-session\> is done by nc_session_free() */
+ NC_RPC_KILL, /**< \<kill-session\> RPC, see #nc_rpc_kill. */
+ NC_RPC_GENERIC /**< user-defined generic RPC */
+} NC_RPC_TYPE;
+
+struct nc_filter {
+ NC_FILTER type; /**< filter type */
+ int refs; /**< number of references */
+ char *data; /**< filter data according to type */
+};
+
struct nc_rpc {
- struct ly_ctx *ctx;
- struct lyxml_elem *root;
- struct lyd_node *tree; /**< libyang data tree of the message */
+ NC_RPC_TYPE type;
+};
+
+struct nc_rpc_server {
+ NC_RPC_TYPE type; /**< NC_RPC_SERVER */
+ struct ly_ctx *ctx; /**< context of the received RPC data */
+ struct lyxml_elem *root; /**< RPC element of the received XML message */
+ struct lyd_node *tree; /**< libyang data tree of the message (NETCONF operation) */
+};
+
+struct nc_rpc_getconfig {
+ NC_RPC_TYPE type; /**< NC_RPC_GETCONFIG */
+ NC_DATASTORE source; /**< NETCONF datastore being queried */
+ struct nc_filter *filter;/**< data filter */
+};
+
+struct nc_rpc_lock {
+ NC_RPC_TYPE type; /**< NC_RPC_LOCK or NC_RPC_UNLOCK */
+ NC_DATASTORE target;
};
struct nc_reply {
diff --git a/src/netconf.h b/src/netconf.h
index b38a7c1..d66d1d7 100644
--- a/src/netconf.h
+++ b/src/netconf.h
@@ -68,6 +68,18 @@
} NC_MSG_TYPE;
/**
+ * @brief Enumeration of the supported types of datastores defined by NETCONF
+ */
+typedef enum NC_DATASTORE_TYPE {
+ NC_DATASTORE_ERROR, /**< error state of functions returning the datastore type */
+ NC_DATASTORE_CONFIG, /**< value describing that the datastore is set as config */
+ NC_DATASTORE_URL, /**< value describing that the datastore data should be given from the URL */
+ NC_DATASTORE_RUNNING, /**< base NETCONF's datastore containing the current device configuration */
+ NC_DATASTORE_STARTUP, /**< separated startup datastore as defined in Distinct Startup Capability */
+ NC_DATASTORE_CANDIDATE /**< separated working datastore as defined in Candidate Configuration Capability */
+} NC_DATASTORE;
+
+/**
* @brief Transform given time_t (seconds since the epoch) into the RFC 3339 format
* accepted by NETCONF functions.
*
diff --git a/src/session.c b/src/session.c
index a1c28d6..112cf5d 100644
--- a/src/session.c
+++ b/src/session.c
@@ -22,8 +22,11 @@
#include <assert.h>
#include <errno.h>
+#include <fcntl.h>
#include <stdlib.h>
#include <string.h>
+#include <sys/stat.h>
+#include <sys/types.h>
#include <pthread.h>
#include <unistd.h>
@@ -33,9 +36,29 @@
#include "libnetconf.h"
#include "messages_p.h"
#include "session_p.h"
+#include "datastore_p.h"
#define TIMEOUT_STEP 50
+static NC_MSG_TYPE nc_send_hello_(struct nc_session *session);
+static NC_MSG_TYPE nc_recv_hello(struct nc_session *session);
+static NC_MSG_TYPE nc_send_rpc_(struct nc_session *session, struct lyd_node *op);
+
+static char *schema_searchpath = NULL;
+
+/* session configuration */
+static struct {
+ uint16_t hello_timeout; /**< hello-timeout in seconds, default is 600 */
+} cfg = {600};
+
+API int
+nc_schema_searchpath(const char *path)
+{
+ schema_searchpath = strdup(path);
+
+ return schema_searchpath ? 0 : 1;
+}
+
/*
* @return 0 - success
* -1 - timeout
@@ -49,7 +72,7 @@
if (timeout >= 0) {
/* limited waiting for lock */
do {
- r = pthread_mutex_trylock(&session->ti_lock);
+ r = pthread_mutex_trylock(session->ti_lock);
if (r == EBUSY) {
/* try later until timeout passes */
usleep(TIMEOUT_STEP);
@@ -69,18 +92,461 @@
return -1;
} else {
/* infinite waiting for lock */
- return pthread_mutex_lock(&session->ti_lock);
+ return pthread_mutex_lock(session->ti_lock);
}
}
static int
session_ti_unlock(struct nc_session *session)
{
- return pthread_mutex_unlock(&session->ti_lock);
+ return pthread_mutex_unlock(session->ti_lock);
+}
+
+static int
+connect_load_schemas(struct ly_ctx *ctx)
+{
+ int fd;
+ struct lys_module *ietfnc;
+
+ fd = open(SCHEMAS_DIR"ietf-netconf.yin", O_RDONLY);
+ if (fd < 0) {
+ ERR("Loading base NETCONF schema (%s) failed (%s).", SCHEMAS_DIR"ietf-netconf", strerror(errno));
+ return 1;
+ }
+ if (!(ietfnc = lys_read(ctx, fd, LYS_IN_YIN))) {
+ ERR("Loading base NETCONF schema (%s) failed.", SCHEMAS_DIR"ietf-netconf");
+ return 1;
+ }
+ close(fd);
+
+ /* set supported capabilities from ietf-netconf */
+ lys_features_enable(ietfnc, "writable-running");
+ lys_features_enable(ietfnc, "candidate");
+ //lys_features_enable(ietfnc, "confirmed-commit");
+ lys_features_enable(ietfnc, "rollback-on-error");
+ lys_features_enable(ietfnc, "validate");
+ lys_features_enable(ietfnc, "startup");
+ lys_features_enable(ietfnc, "url");
+ lys_features_enable(ietfnc, "xpath");
+
+ return 0;
+}
+
+API struct nc_session *
+nc_session_connect_inout(int fdin, int fdout, struct ly_ctx *ctx)
+{
+ int r;
+ NC_MSG_TYPE type;
+ const char *str;
+ struct nc_session *session = NULL;
+
+ if (fdin < 0 || fdout < 0) {
+ ERR("%s: Invalid parameter", __func__);
+ return NULL;
+ }
+
+ /* prepare session structure */
+ session = calloc(1, sizeof *session);
+ if (!session) {
+ ERRMEM;
+ return NULL;
+ }
+ session->status = NC_STATUS_STARTING;
+ session->side = NC_CLIENT;
+ session->ti_type = NC_TI_FD;
+ session->ti.fd.in = fdin;
+ session->ti.fd.out = fdout;
+
+ /* transport lock */
+ session->ti_lock = malloc(sizeof *session->ti_lock);
+ if (!session->ti_lock) {
+ ERRMEM;
+ return NULL;
+ }
+ pthread_mutex_init(session->ti_lock, NULL);
+
+ /* YANG context for the session */
+ if (ctx) {
+ session->flags |= NC_SESSION_SHAREDCTX;
+ session->ctx = ctx;
+
+ /* check presence of the required schemas */
+ if (!ly_ctx_get_module(session->ctx, "ietf-netconf", NULL)) {
+ str = ly_ctx_get_searchdir(session->ctx);
+ ly_ctx_set_searchdir(session->ctx, SCHEMAS_DIR);
+ r = connect_load_schemas(session->ctx);
+ ly_ctx_set_searchdir(session->ctx, str);
+
+ if (r) {
+ goto error;
+ }
+ }
+ } else {
+ session->ctx = ly_ctx_new(SCHEMAS_DIR);
+
+ /* load basic NETCONF schemas required for libnetconf work */
+ if (connect_load_schemas(session->ctx)) {
+ goto error;
+ }
+
+ ly_ctx_set_searchdir(session->ctx, schema_searchpath);
+ }
+
+ /* NETCONF handshake */
+ type = nc_send_hello_(session);
+ if (type != NC_MSG_HELLO) {
+ goto error;
+ }
+
+ type = nc_recv_hello(session);
+ if (type != NC_MSG_HELLO) {
+ goto error;
+ }
+
+ session->status = NC_STATUS_RUNNING;
+ return session;
+
+error:
+ nc_session_free(session);
+ return NULL;
+}
+
+#ifdef ENABLE_LIBSSH
+
+API struct nc_session *
+nc_session_connect_ssh(const char *host, unsigned short port, const char* username, struct ly_ctx *ctx)
+{
+ (void) host;
+ (void) port;
+ (void) username;
+ (void) ctx;
+
+ return NULL;
+}
+
+API struct nc_session *
+nc_session_connect_libssh(ssh_session ssh_session, struct ly_ctx *ctx)
+{
+ (void) ssh_session;
+ (void) ctx;
+
+ return NULL;
+}
+
+API struct nc_session *
+nc_session_connect_ssh_channel(struct nc_session *session)
+{
+ (void) session;
+
+ return NULL;
+}
+
+#endif /* ENABLE_LIBSSH */
+
+#ifdef ENABLE_TLS
+
+API struct nc_session *
+nc_session_connect_tls(const char *host, unsigned short port, const char *username, struct ly_ctx *ctx)
+{
+ (void) host;
+ (void) port;
+ (void) username;
+ (void) ctx;
+
+ return NULL;
+}
+
+API struct nc_session *
+nc_session_connect_libssl(SSL *tls, struct ly_ctx *ctx)
+{
+ (void) tls;
+ (void) ctx;
+
+ return NULL;
+}
+
+#endif /* ENABLE_TLS */
+
+API void
+nc_session_free(struct nc_session *session)
+{
+ int r, i;
+ int multisession = 0; /* flag for more NETCONF session on a single SSH session */
+ struct nc_session *siter;
+ struct nc_notif_cont *ntfiter;
+ struct nc_reply_cont *rpliter;
+ struct lyd_node *close_rpc;
+ struct lys_module *ietfnc;
+ void *p;
+
+ if (!session || session->status < NC_STATUS_INVALID) {
+ return;
+ }
+
+ /* mark session for closing */
+ do {
+ r = session_ti_lock(session, 0);
+ } while (r < 0);
+ if (r) {
+ return;
+ }
+
+ /* stop notifications loop if any */
+ if (session->notif) {
+ pthread_cancel(*session->notif);
+ pthread_join(*session->notif, NULL);
+ }
+
+ if (session->side == NC_CLIENT && session->status == NC_STATUS_RUNNING) {
+ /* cleanup message queues */
+ /* notifications */
+ for (ntfiter = session->notifs; ntfiter; ) {
+ nc_notif_free(ntfiter->msg);
+
+ p = ntfiter;
+ ntfiter = ntfiter->next;
+ free(p);
+ }
+
+ /* rpc replies */
+ for (rpliter = session->replies; rpliter; ) {
+ nc_reply_free(rpliter->msg);
+
+ p = rpliter;
+ rpliter = rpliter->next;
+ free(p);
+ }
+
+ /* send closing info to the other side */
+ ietfnc = ly_ctx_get_module(session->ctx, "ietf-netconf", NULL);
+ if (!ietfnc) {
+ WRN("%s: Missing ietf-netconf schema in context (session %u), unable to send <close-session\\>", session->id);
+ } else {
+ close_rpc = lyd_new(NULL, ietfnc, "close-session");
+ nc_send_rpc_(session, close_rpc);
+ lyd_free(close_rpc);
+ }
+
+ /* list of server's capabilities */
+ if (session->cpblts) {
+ for (i = 0; session->cpblts[i]; i++) {
+ lydict_remove(session->ctx, session->cpblts[i]);
+ }
+ free(session->cpblts);
+ }
+ }
+
+ session->status = NC_STATUS_CLOSING;
+
+ /* transport implementation cleanup */
+ switch (session->ti_type) {
+ case NC_TI_FD:
+ /* nothing needed - file descriptors were provided by caller,
+ * so it is up to the caller to close them correctly
+ * TODO use callbacks
+ */
+ break;
+
+#ifdef ENABLE_LIBSSH
+ case NC_TI_LIBSSH:
+ ssh_channel_free(session->ti.libssh.channel);
+ /* There can be multiple NETCONF sessions on the same SSH session (NETCONF session maps to
+ * SSH channel). So destroy the SSH session only if there is no other NETCONF session using
+ * it.
+ */
+ if (!session->ti.libssh.next) {
+ ssh_disconnect(session->ti.libssh.session);
+ ssh_free(session->ti.libssh.session);
+ } else {
+ /* multiple NETCONF sessions on a single SSH session */
+ multisession = 1;
+ /* remove the session from the list */
+ for (siter = session->ti.libssh.next; siter->ti.libssh.next != session; siter = siter->ti.libssh.next);
+ if (siter->ti.libssh.next == session->ti.libssh.next) {
+ /* there will be only one session */
+ siter->ti.libssh.next = NULL;
+ } else {
+ /* there are still multiple sessions, keep the ring list */
+ siter->ti.libssh.next = session->ti.libssh.next;
+ }
+ }
+ break;
+#endif
+
+#ifdef ENABLE_TLS
+ case NC_TI_OPENSSL:
+ SSL_shutdown(session->ti.tls);
+ SSL_free(session->ti.tls);
+ break;
+#endif
+ }
+
+ /* final cleanup */
+ if (multisession) {
+ session_ti_unlock(session);
+ } else {
+ pthread_mutex_destroy(session->ti_lock);
+ free(session->ti_lock);
+ }
+
+ if (!(session->flags & NC_SESSION_SHAREDCTX)) {
+ ly_ctx_destroy(session->ctx);
+ }
+
+ free(session);
+}
+
+static int
+parse_cpblts(struct lyxml_elem *xml, const char ***list)
+{
+ struct lyxml_elem *cpblt;
+ int ver = -1;
+ int i = 0;
+
+ if (list) {
+ /* get the storage for server's capabilities */
+ LY_TREE_FOR(xml->child, cpblt) {
+ i++;
+ }
+ *list = calloc(i, sizeof **list);
+ if (!*list) {
+ ERRMEM;
+ return -1;
+ }
+ i = 0;
+ }
+
+ LY_TREE_FOR(xml->child, cpblt) {
+ if (strcmp(cpblt->name, "capability") && cpblt->ns && cpblt->ns->value &&
+ !strcmp(cpblt->ns->value, NC_NS_BASE)) {
+ ERR("Unexpected <%s> element in client's <hello>.", cpblt->name);
+ return -1;
+ } else if (!cpblt->ns || !cpblt->ns->value || strcmp(cpblt->ns->value, NC_NS_BASE)) {
+ continue;
+ }
+
+ /* detect NETCONF version */
+ if (ver < 0 && !strcmp(cpblt->content, "urn:ietf:params:netconf:base:1.0")) {
+ ver = 0;
+ } else if (ver < 1 && !strcmp(cpblt->content, "urn:ietf:params:netconf:base:1.1")) {
+ ver = 1;
+ }
+
+ /* store capabilities */
+ if (list) {
+ (*list)[i] = cpblt->content;
+ cpblt->content = NULL;
+ i++;
+ }
+ }
+
+ if (ver == -1) {
+ ERR("Peer does not support compatible NETCONF version.");
+ }
+
+ return ver;
+}
+
+static NC_MSG_TYPE
+nc_recv_hello(struct nc_session *session)
+{
+ struct lyxml_elem *xml = NULL, *node;
+ NC_MSG_TYPE msgtype = 0; /* NC_MSG_ERROR */
+ int ver = -1;
+ char *str;
+ long long int id;
+ int flag = 0;
+
+ msgtype = nc_read_msg(session, cfg.hello_timeout * 1000, &xml);
+
+ switch(msgtype) {
+ case NC_MSG_HELLO:
+ /* parse <hello> data */
+ if (session->side == NC_SERVER) {
+ /* get know NETCONF version */
+ LY_TREE_FOR(xml->child, node) {
+ if (!node->ns || !node->ns->value || strcmp(node->ns->value, NC_NS_BASE)) {
+ continue;
+ } else if (strcmp(node->name, "capabilities")) {
+ ERR("Unexpected <%s> element in client's <hello>.", node->name);
+ goto error;
+ }
+
+ if (flag) {
+ /* multiple capabilities elements */
+ ERR("Invalid <hello> message (multiple <capabilities> elements)");
+ goto error;
+ }
+ flag = 1;
+
+ if ((ver = parse_cpblts(node, NULL)) < 0) {
+ goto error;
+ }
+ session->version = ver;
+ }
+ } else { /* NC_CLIENT */
+ LY_TREE_FOR(xml->child, node) {
+ if (!node->ns || !node->ns->value || strcmp(node->ns->value, NC_NS_BASE)) {
+ continue;
+ } else if (!strcmp(node->name, "session-id")) {
+ if (!node->content || !strlen(node->content)) {
+ ERR("No value of <session-id> element in server's <hello>");
+ goto error;
+ }
+ str = NULL;
+ id = strtoll(node->content, &str, 10);
+ if (*str || id < 1 || id > UINT32_MAX) {
+ ERR("Invalid value of <session-id> element in server's <hello>");
+ goto error;
+ }
+ session->id = (uint32_t)id;
+ continue;
+ } else if (strcmp(node->name, "capabilities")) {
+ ERR("Unexpected <%s> element in client's <hello>.", node->name);
+ goto error;
+ }
+
+ if (flag) {
+ /* multiple capabilities elements */
+ ERR("Invalid <hello> message (multiple <capabilities> elements)");
+ goto error;
+ }
+ flag = 1;
+
+ if ((ver = parse_cpblts(node, NULL)) < 0) {
+ goto error;
+ }
+ session->version = ver;
+ }
+
+ if (!session->id) {
+ ERR("Missing <session-id> in server's <hello>");
+ goto error;
+ }
+ }
+ break;
+ case NC_MSG_ERROR:
+ /* nothing special, just pass it out */
+ break;
+ default:
+ ERR("Unexpected message received instead of <hello>.");
+ msgtype = NC_MSG_ERROR;
+ }
+
+ /* cleanup */
+ lyxml_free_elem(session->ctx, xml);
+
+ return msgtype;
+
+error:
+ /* cleanup */
+ lyxml_free_elem(session->ctx, xml);
+
+ return NC_MSG_ERROR;
}
API NC_MSG_TYPE
-nc_recv_rpc(struct nc_session *session, int timeout, struct nc_rpc **rpc)
+nc_recv_rpc(struct nc_session *session, int timeout, struct nc_rpc_server **rpc)
{
int r;
struct lyxml_elem *xml = NULL;
@@ -89,8 +555,8 @@
if (!session || !rpc) {
ERR("%s: Invalid parameter", __func__);
return NC_MSG_ERROR;
- } else if (session->side != NC_SIDE_SERVER) {
- ERR("%s: only servers are allowed to receive RPCs.", __func__);
+ } else if (session->status != NC_STATUS_RUNNING || session->side != NC_SERVER) {
+ ERR("%s: invalid session to receive RPCs.", __func__);
return NC_MSG_ERROR;
}
@@ -109,6 +575,7 @@
switch(msgtype) {
case NC_MSG_RPC:
*rpc = malloc(sizeof **rpc);
+ (*rpc)->type = NC_RPC_SERVER;
(*rpc)->ctx = session->ctx;
(*rpc)->tree = lyd_parse_xml(session->ctx, xml, 0);
(*rpc)->root = xml;
@@ -140,7 +607,7 @@
}
API NC_MSG_TYPE
-nc_recv_reply(struct nc_session* session, int timeout, struct nc_reply **reply)
+nc_recv_reply(struct nc_session *session, int timeout, struct nc_reply **reply)
{
int r;
struct lyxml_elem *xml;
@@ -152,8 +619,8 @@
if (!session || !reply) {
ERR("%s: Invalid parameter", __func__);
return NC_MSG_ERROR;
- } else if (session->side != NC_SIDE_CLIENT) {
- ERR("%s: only clients are allowed to receive RPC replies.", __func__);
+ } else if (session->status != NC_STATUS_RUNNING || session->side != NC_CLIENT) {
+ ERR("%s: invalid session to receive RPC replies.", __func__);
return NC_MSG_ERROR;
}
@@ -246,7 +713,7 @@
}
API NC_MSG_TYPE
-nc_recv_notif(struct nc_session* session, int timeout, struct nc_notif **notif)
+nc_recv_notif(struct nc_session *session, int timeout, struct nc_notif **notif)
{
int r;
struct lyxml_elem *xml;
@@ -258,8 +725,8 @@
if (!session || !notif) {
ERR("%s: Invalid parameter", __func__);
return NC_MSG_ERROR;
- } else if (session->side != NC_SIDE_CLIENT) {
- ERR("%s: only clients are allowed to receive Notifications.", __func__);
+ } else if (session->status != NC_STATUS_RUNNING || session->side != NC_CLIENT) {
+ ERR("%s: invalid session to receive Notifications.", __func__);
return NC_MSG_ERROR;
}
@@ -345,28 +812,37 @@
return NC_MSG_ERROR;
}
-API NC_MSG_TYPE
-nc_send_rpc(struct nc_session* session, struct lyd_node *op, const char *attrs)
+static NC_MSG_TYPE
+nc_send_hello_(struct nc_session *session)
+{
+ int r;
+ char **cpblts;
+
+ if (session->side == NC_CLIENT) {
+ /* client side hello - send only NETCONF base capabilities */
+ cpblts = malloc(3 * sizeof *cpblts);
+ cpblts[0] = "urn:ietf:params:netconf:base:1.0";
+ cpblts[1] = "urn:ietf:params:netconf:base:1.1";
+ cpblts[2] = NULL;
+
+ r = nc_write_msg(session, NC_MSG_HELLO, cpblts, NULL);
+ free(cpblts);
+ }
+
+
+ if (r) {
+ return NC_MSG_ERROR;
+ } else {
+ return NC_MSG_HELLO;
+ }
+}
+
+static NC_MSG_TYPE
+nc_send_rpc_(struct nc_session *session, struct lyd_node *op)
{
int r;
- if (!session || !op) {
- ERR("%s: Invalid parameter", __func__);
- return NC_MSG_ERROR;
- } else if (session->side != NC_SIDE_CLIENT) {
- ERR("%s: only clients are allowed to send RPCs.", __func__);
- return NC_MSG_ERROR;
- }
-
- r = session_ti_lock(session, 0);
- if (r != 0) {
- /* error or blocking */
- return NC_MSG_WOULDBLOCK;
- }
-
- r = nc_write_msg(session, NC_MSG_RPC, op, attrs);
-
- session_ti_unlock(session);
+ r = nc_write_msg(session, NC_MSG_RPC, op, NULL);
if (r) {
return NC_MSG_ERROR;
@@ -375,3 +851,86 @@
}
}
+API NC_MSG_TYPE
+nc_send_rpc(struct nc_session *session, struct nc_rpc *rpc)
+{
+ NC_MSG_TYPE r;
+ struct nc_rpc_lock *rpc_lock;
+ struct nc_rpc_getconfig *rpc_gc;
+ struct lyd_node *data, *node;
+ struct lys_module *ietfnc;
+
+ if (!session || !rpc || rpc->type == NC_RPC_SERVER) {
+ ERR("%s: Invalid parameter", __func__);
+ return NC_MSG_ERROR;
+ } else if (session->status != NC_STATUS_RUNNING || session->side != NC_CLIENT) {
+ ERR("%s: invalid session to send RPCs.", __func__);
+ return NC_MSG_ERROR;
+ }
+
+ ietfnc = ly_ctx_get_module(session->ctx, "ietf-netconf", NULL);
+ if (!ietfnc) {
+ ERR("%s: Missing ietf-netconf schema in context (session %u)", session->id);
+ return NC_MSG_ERROR;
+ }
+
+ switch(rpc->type) {
+ case NC_RPC_GETCONFIG:
+ rpc_gc = (struct nc_rpc_getconfig *)rpc;
+
+ data = lyd_new(NULL, ietfnc, "get-config");
+ node = lyd_new(data, ietfnc, "source");
+ node = lyd_new_leaf_str(node, ietfnc, ncds2str[rpc_gc->source], LY_TYPE_EMPTY, NULL);
+ if (!node) {
+ lyd_free(data);
+ return NC_MSG_ERROR;
+ }
+ if (rpc_gc->filter) {
+ if (rpc_gc->filter->type == NC_FILTER_SUBTREE) {
+ node = lyd_new_anyxml(data, ietfnc, "filter", rpc_gc->filter->data);
+ lyd_insert_attr(node, "type", "subtree");
+ } else if (rpc_gc->filter->type == NC_FILTER_XPATH) {
+ node = lyd_new_anyxml(data, ietfnc, "filter", NULL);
+ /* TODO - handle namespaces from XPATH query */
+ lyd_insert_attr(node, "type", "xpath");
+ lyd_insert_attr(node, "select", rpc_gc->filter->data);
+ }
+ }
+ break;
+ case NC_RPC_LOCK:
+ rpc_lock = (struct nc_rpc_lock *)rpc;
+
+ data = lyd_new(NULL, ietfnc, "lock");
+ node = lyd_new(data, ietfnc, "target");
+ node = lyd_new_leaf_str(node, ietfnc, ncds2str[rpc_lock->target], LY_TYPE_EMPTY, NULL);
+ if (!node) {
+ lyd_free(data);
+ return NC_MSG_ERROR;
+ }
+ break;
+ case NC_RPC_UNLOCK:
+ rpc_lock = (struct nc_rpc_lock *)rpc;
+
+ data = lyd_new(NULL, ietfnc, "unlock");
+ node = lyd_new(data, ietfnc, "target");
+ node = lyd_new_leaf_str(node, ietfnc, ncds2str[rpc_lock->target], LY_TYPE_EMPTY, NULL);
+ if (!node) {
+ lyd_free(data);
+ return NC_MSG_ERROR;
+ }
+ break;
+ }
+
+ r = session_ti_lock(session, 0);
+ if (r != 0) {
+ /* error or blocking */
+ r = NC_MSG_WOULDBLOCK;
+ } else {
+ /* send RPC */
+ r = nc_send_rpc_(session, data);
+ }
+ session_ti_unlock(session);
+
+ lyd_free(data);
+ return r;
+}
diff --git a/src/session.h b/src/session.h
index 086e1cd..917ed86 100644
--- a/src/session.h
+++ b/src/session.h
@@ -25,13 +25,169 @@
#include <stdint.h>
+#ifdef ENABLE_LIBSSH
+# include <libssh/libssh.h>
+#endif /* ENABLE_LIBSSH */
+
+#ifdef ENABLE_TLS
+# include <openssl/ssl.h>
+#endif /* ENABLE_TLS */
+
#include "messages.h"
/**
+ * @brief Enumeration of possible session statuses
+ */
+typedef enum {
+ NC_STATUS_STARTING, /**< session is not yet fully initiated */
+ NC_STATUS_CLOSING, /**< session is being closed */
+ NC_STATUS_INVALID, /**< session is corrupted and it is supposed to be closed (nc_session_free()) */
+ NC_STATUS_RUNNING /**< up and running */
+} NC_STATUS;
+
+/**
* @brief NETCONF session object
*/
struct nc_session;
+#ifdef NC_CLIENT_H_
+
+/**
+ * @brief Set location where libnetconf tries to search for YANG/YIN schemas.
+ *
+ * The location is search when connecting to a NETCONF server and building
+ * YANG context for further processing of the NETCONF messages and data.
+ *
+ * Function is provided only via nc_client.h header file.
+ *
+ * @param[in] path Directory where to search for YANG/YIN schemas.
+ * @return 0 on success, 1 on (memory allocation) failure.
+ */
+int nc_schema_searchpath(const char *path);
+
+/**
+ * @brief Connect to the NETCONF server via proviaded input/output file descriptors.
+ *
+ * Transport layer is supposed to be already set. Function do not cover authentication
+ * or any other manipulation with the transport layer, it only establish NETCONF session
+ * by sending and processing NETCONF \<hello\> messages.
+ *
+ * Function is provided only via nc_client.h header file.
+ *
+ * @param[in] fdin Input file descriptor for reading (clear) data from NETCONF server.
+ * @param[in] fdout Output file descriptor for writing (clear) data for NETCONF server.
+ * @param[in] ctx Optional parameter. If set, provides strict YANG context for the session
+ * (ignoring what is actually supported by the server side). If not set,
+ * YANG context is created for the session using \<get-schema\> (if supported
+ * by the server side) or/and by searching for YANG schemas in the searchpath
+ * (see nc_schema_searchpath()).
+ * @return Created NETCONF session object or NULL in case of error.
+ */
+struct nc_session *nc_session_connect_inout(int fdin, int fdout, struct ly_ctx *ctx);
+
+#ifdef ENABLE_LIBSSH
+
+/**
+ * @brief Connect to the NETCONF server using SSH transport (via libssh).
+ *
+ * SSH session is created with default options. If a caller need to change SSH session properties,
+ * it is supposed to use nc_session_connect_libssh().
+ *
+ * Function is provided only via nc_client.h header file and only when libnetconf2 is compiled with libssh support.
+ *
+ * @param[in] host Hostname or address (both Ipv4 and IPv6 are accepted) of the target server.
+ * 'localhost' is used by default if NULL is specified.
+ * @param[in] port Port number of the target server. Default value 830 is used if 0 is specified.
+ * @param[in] username Name of the user to login to the server. The user running the application (detected from the
+ * effective UID) is used if NULL is specified.
+ * @param[in] ctx Optional parameter. If set, provides strict YANG context for the session
+ * (ignoring what is actually supported by the server side). If not set,
+ * YANG context is created for the session using \<get-schema\> (if supported
+ * by the server side) or/and by searching for YANG schemas in the searchpath
+ * (see nc_schema_searchpath()).
+ * @return Created NETCONF session object or NULL in case of error.
+ */
+struct nc_session *nc_session_connect_ssh(const char *host, unsigned short port, const char* username, struct ly_ctx *ctx);
+
+/**
+ * @brief Connect to the NETCONF server using the provided SSH (libssh) session.
+ *
+ * Function is provided only via nc_client.h header file and only when libnetconf2 is compiled with libssh support.
+ *
+ * @param[in] ssh_session libssh structure representing SSH session object.
+ * @param[in] ctx Optional parameter. If set, provides strict YANG context for the session
+ * (ignoring what is actually supported by the server side). If not set,
+ * YANG context is created for the session using \<get-schema\> (if supported
+ * by the server side) or/and by searching for YANG schemas in the searchpath
+ * (see nc_schema_searchpath()).
+ * @return Created NETCONF session object or NULL in case of error.
+ */
+struct nc_session *nc_session_connect_libssh(ssh_session *ssh_session, struct ly_ctx *ctx);
+
+/**
+ * @brief Create another NETCONF session on existing SSH session using separated SSH channel.
+ *
+ * Function is provided only via nc_client.h header file and only when libnetconf2 is compiled with libssh support.
+ *
+ * @param[in] session Existing NETCONF session. The session has to be created on SSH transport layer using libssh -
+ * it has to be created by nc_session_connect_ssh(), nc_session_connect_libssh() or
+ * nc_session_connect_ssh_channel().
+ * @return Created NETCONF session object or NULL in case of error.
+ */
+struct nc_session *nc_session_connect_ssh_channel(struct nc_session *session);
+
+#endif /* ENABLE_LIBSSH */
+
+#ifdef ENABLE_TLS
+
+/**
+ * @brief Connect to the NETCONF server using TLS transport (via libssl)
+ *
+ * TLS session is created with default options. If a caller need to change TLS session properties,
+ * it is supposed to use nc_session_connect_libssl().
+ *
+ * Function is provided only via nc_client.h header file and only when libnetconf2 is compiled with TLS support.
+ *
+ * @param[in] host Hostname or address (both Ipv4 and IPv6 are accepted) of the target server.
+ * 'localhost' is used by default if NULL is specified.
+ * @param[in] port Port number of the target server. Default value 6513 is used if 0 is specified.
+ * @param[in] username Name of the user to login to the server. The user running the application (detected from the
+ * effective UID) is used if NULL is specified.
+ * @param[in] ctx Optional parameter. If set, provides strict YANG context for the session
+ * (ignoring what is actually supported by the server side). If not set,
+ * YANG context is created for the session using \<get-schema\> (if supported
+ * by the server side) or/and by searching for YANG schemas in the searchpath
+ * (see nc_schema_searchpath()).
+ * @return Created NETCONF session object or NULL in case of error.
+ */
+struct nc_session *nc_session_connect_tls(const char *host, unsigned short port, const char *username, struct ly_ctx *ctx);
+
+/**
+ * @brief Connect to the NETCONF server using the provided TLS (libssl) session.
+ *
+ * Function is provided only via nc_client.h header file and only when libnetconf2 is compiled with TLS support.
+ *
+ * @param[in] tls libssl structure representing TLS session object.
+ * @param[in] ctx Optional parameter. If set, provides strict YANG context for the session
+ * (ignoring what is actually supported by the server side). If not set,
+ * YANG context is created for the session using \<get-schema\> (if supported
+ * by the server side) or/and by searching for YANG schemas in the searchpath
+ * (see nc_schema_searchpath()).
+ * @return Created NETCONF session object or NULL in case of error.
+ */
+struct nc_session *nc_session_connect_libssl(SSL *tls, struct ly_ctx *ctx);
+
+#endif /* ENABLE_TLS */
+
+#endif /* NC_CLIENT_H_ */
+
+/**
+ * @brief Free the NETCONF session object.
+ *
+ * @param[in] session Object to free.
+ */
+void nc_session_free(struct nc_session *session);
+
/**
* @brief Receive NETCONF RPC.
*
@@ -43,7 +199,7 @@
* @return NC_MSG_RPC for success, NC_MSG_WOULDBLOCK if timeout reached and NC_MSG_ERROR
* when reading has failed.
*/
-NC_MSG_TYPE nc_recv_rpc(struct nc_session* session, int timeout, struct nc_rpc **rpc);
+NC_MSG_TYPE nc_recv_rpc(struct nc_session* session, int timeout, struct nc_rpc_server **rpc);
/**
* @brief Receive NETCONF RPC reply.
@@ -75,12 +231,11 @@
* @brief Send NETCONF RPC message via the session.
*
* @param[in] session NETCONF session where the RPC will be written.
- * @param[in] op NETCONF RPC operation to be sent.
- * @param[in] attrs Additional (optional) XML attributes to be added into the \<rpc\> element.
- * Note, that "message-id" attribute is added automatically.
+ * @param[in] rpc NETCOFN RPC object to send via specified session. Object can be created by
+ nc_rpc_lock(), nc_rpc_unlock() and nc_rpc_generic() functions.
* @return #NC_MSG_RPC on success, #NC_MSG_WOULDBLOCK in case of busy session
* (try to repeat the function call) and #NC_MSG_ERROR in case of error.
*/
-NC_MSG_TYPE nc_send_rpc(struct nc_session* session, struct lyd_node *op, const char *attrs);
+NC_MSG_TYPE nc_send_rpc(struct nc_session *session, struct nc_rpc *rpc);
#endif /* NC_SESSION_H_ */
diff --git a/src/session_p.h b/src/session_p.h
index 7770304..d52691a 100644
--- a/src/session_p.h
+++ b/src/session_p.h
@@ -61,11 +61,11 @@
} NC_TRANSPORT_IMPL;
/**
- * @brief Enumeration of possible session types (communication sides)
+ * @brief type of the session
*/
typedef enum {
- NC_SIDE_SERVER, /**< server side */
- NC_SIDE_CLIENT /**< client side */
+ NC_CLIENT, /**< client side */
+ NC_SERVER /**< server side */
} NC_SIDE;
/**
@@ -99,26 +99,30 @@
* @brief NETCONF session structure
*/
struct nc_session {
- NC_SIDE side; /**< type of the session: client or server */
+ NC_STATUS status; /**< status of the session */
+ NC_SIDE side; /**< side of the session: client or server */
/* NETCONF data */
uint32_t id; /**< NETCONF session ID (session-id-type) */
NC_VERSION version; /**< NETCONF protocol version */
- pthread_t *notif; /**< running notifications thread */
+ pthread_t *notif; /**< running notifications thread - TODO server-side only? */
/* Transport implementation */
NC_TRANSPORT_IMPL ti_type; /**< transport implementation type to select items from ti union */
- pthread_mutex_t ti_lock; /**< lock to access ti */
+ pthread_mutex_t *ti_lock; /**< lock to access ti. Note that in case of libssh TI, it can be shared with other
+ NETCONF sessions on the same SSH session */
union {
struct {
int in; /**< input file descriptor */
int out; /**< output file descriptor */
- char c; /**< internal buffer (ungetc() simulation */
} fd; /**< NC_TI_FD transport implementation structure */
#ifdef ENABLE_LIBSSH
struct {
- ssh_session session;
ssh_channel channel;
+ ssh_session session;
+ struct nc_session *next; /**< pointer to the next NETCONF session on the same
+ SSH session, but different SSH channel. If no such session exists, it is NULL.
+ otherwise there is a ring list of the NETCONF sessions */
} libssh;
#endif
#ifdef ENABLE_TLS
@@ -128,9 +132,12 @@
/* other */
struct ly_ctx *ctx; /**< libyang context of the session */
+ uint8_t flags; /**< various flags of the session - TODO combine with status and/or side */
+#define NC_SESSION_SHAREDCTX 0x1
/* client side only data */
uint64_t msgid;
+ const char **cpblts; /**< list of server's capabilities on client side */
struct nc_reply_cont *replies; /**< queue for RPC replies received instead of notifications */
struct nc_notif_cont *notifs; /**< queue for notifications received instead of RPC reply */
};