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 */
 };