Przeglądaj źródła

Initial commit of qdevice net

Jan Friesse 10 lat temu
rodzic
commit
908de5e36a

+ 3 - 0
qdevices/.gitignore

@@ -0,0 +1,3 @@
+corosync-qdevice-net
+corosync-qnetd-certutil
+corosync-qnetd

+ 23 - 0
qdevices/Makefile.am

@@ -34,4 +34,27 @@ if BUILD_QDEVICES
 
 
 SUBDIRS			=
 SUBDIRS			=
 
 
+sbin_PROGRAMS		= corosync-qnetd corosync-qdevice-net
+
+sbin_SCRIPTS             = corosync-qnetd-certutil
+
+corosync_qnetd_SOURCES	= corosync-qnetd.c dynar.c msg.c msgio.c nss-sock.c  \
+			    qnetd-client.c qnetd-clients-list.c qnetd-log.c \
+			    qnetd-poll-array.c timer-list.c tlv.c
+
+corosync_qdevice_net_SOURCES	= corosync-qdevice-net.c dynar.c msg.c msgio.c nss-sock.c  \
+			    qnetd-client.c qnetd-clients-list.c qnetd-log.c \
+			    qnetd-poll-array.c timer-list.c tlv.c
+
+corosync_qnetd_CFLAGS		= $(nss_CFLAGS)
+corosync_qnetd_LDADD		= $(nss_LIBS)
+
+corosync_qdevice_net_CFLAGS	= $(nss_CFLAGS)
+corosync_qdevice_net_LDADD	= $(nss_LIBS)
+
+corosync-qnetd-certutil: corosync-qnetd-certutil.sh
+	sed -e 's#@''DATADIR@#${datadir}#g' \
+	    -e 's#@''BASHPATH@#${BASHPATH}#g' \
+	    $< > $@
+
 endif
 endif

+ 1013 - 0
qdevices/corosync-qdevice-net.c

@@ -0,0 +1,1013 @@
+/*
+ * Copyright (c) 2015 Red Hat, Inc.
+ *
+ * All rights reserved.
+ *
+ * Author: Jan Friesse (jfriesse@redhat.com)
+ *
+ * This software licensed under BSD license, the text of which follows:
+ *
+ * Redistribution and use in source and binary forms, with or without
+ * modification, are permitted provided that the following conditions are met:
+ *
+ * - Redistributions of source code must retain the above copyright notice,
+ *   this list of conditions and the following disclaimer.
+ * - 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.
+ * - Neither the name of the Red Hat, Inc. nor the names of its
+ *   contributors may be used to endorse or promote products derived from this
+ *   software without specific prior written permission.
+ *
+ * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS"
+ * AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
+ * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
+ * ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR CONTRIBUTORS BE
+ * LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR
+ * CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF
+ * SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS
+ * INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN
+ * CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE)
+ * ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF
+ * THE POSSIBILITY OF SUCH DAMAGE.
+ */
+
+#include <stdio.h>
+#include <nss.h>
+#include <secerr.h>
+#include <sslerr.h>
+#include <pk11func.h>
+#include <certt.h>
+#include <ssl.h>
+#include <prio.h>
+#include <prnetdb.h>
+#include <prerror.h>
+#include <prinit.h>
+#include <getopt.h>
+#include <err.h>
+#include <keyhi.h>
+
+#include "dynar.h"
+#include "nss-sock.h"
+#include "tlv.h"
+#include "msg.h"
+#include "msgio.h"
+#include "qnetd-log.h"
+#include "timer-list.h"
+
+#define NSS_DB_DIR	"node/nssdb"
+
+#define QNETD_HOST	"localhost"
+#define QNETD_PORT	4433
+
+#define QNETD_NSS_SERVER_CN		"Qnetd Server"
+#define QDEVICE_NET_NSS_CLIENT_CERT_NICKNAME	"Cluster Cert"
+
+#define QDEVICE_NET_CLUSTER_NAME		"Testcluster"
+
+#define QDEVICE_NET_INITIAL_MSG_RECEIVE_SIZE	(1 << 15)
+#define QDEVICE_NET_INITIAL_MSG_SEND_SIZE	(1 << 15)
+
+#define QDEVICE_NET_MIN_MSG_SEND_SIZE		QDEVICE_NET_INITIAL_MSG_SEND_SIZE
+
+#define QDEVICE_NET_MAX_MSG_RECEIVE_SIZE	(1 << 24)
+
+#define QDEVICE_NET_TLS_SUPPORTED	TLV_TLS_SUPPORTED
+
+#define QDEVICE_NET_NODE_ID		42
+
+#define QDEVICE_NET_DECISION_ALGORITHM		TLV_DECISION_ALGORITHM_TYPE_TEST
+
+#define QDEVICE_NET_HEARTBEAT_INTERVAL		10000
+
+#define qdevice_net_log			qnetd_log
+#define qdevice_net_log_nss		qnetd_log_nss
+#define qdevice_net_log_init		qnetd_log_init
+#define qdevice_net_log_close		qnetd_log_close
+#define qdevice_net_log_set_debug	qnetd_log_set_debug
+
+#define QDEVICE_NET_LOG_TARGET_STDERR		QNETD_LOG_TARGET_STDERR
+#define QDEVICE_NET_LOG_TARGET_SYSLOG		QNETD_LOG_TARGET_SYSLOG
+
+enum qdevice_net_state {
+	QDEVICE_NET_STATE_WAITING_PREINIT_REPLY,
+	QDEVICE_NET_STATE_WAITING_STARTTLS_BEING_SENT,
+	QDEVICE_NET_STATE_WAITING_INIT_REPLY,
+	QDEVICE_NET_STATE_WAITING_SET_OPTION_REPLY,
+};
+
+struct qdevice_net_instance {
+	PRFileDesc *socket;
+	size_t initial_send_size;
+	size_t initial_receive_size;
+	size_t max_receive_size;
+	size_t min_send_size;
+	struct dynar receive_buffer;
+	struct dynar send_buffer;
+	struct dynar echo_request_send_buffer;
+	int sending_msg;
+	int skipping_msg;
+	int sending_echo_request_msg;
+	size_t msg_already_received_bytes;
+	size_t msg_already_sent_bytes;
+	size_t echo_request_msg_already_sent_bytes;
+	enum qdevice_net_state state;
+	uint32_t expected_msg_seq_num;
+	uint32_t echo_request_expected_msg_seq_num;
+	uint32_t echo_reply_received_msg_seq_num;
+	enum tlv_tls_supported tls_supported;
+	int using_tls;
+	uint32_t node_id;
+	uint32_t heartbeat_interval;
+	enum tlv_decision_algorithm_type decision_algorithm;
+	struct timer_list main_timer_list;
+	struct timer_list_entry *echo_request_timer;
+	int schedule_disconnect;
+};
+
+static void
+err_nss(void) {
+	errx(1, "nss error %d: %s", PR_GetError(), PR_ErrorToString(PR_GetError(), PR_LANGUAGE_I_DEFAULT));
+}
+
+static SECStatus
+qdevice_net_nss_bad_cert_hook(void *arg, PRFileDesc *fd) {
+	if (PR_GetError() == SEC_ERROR_EXPIRED_CERTIFICATE ||
+	    PR_GetError() == SEC_ERROR_EXPIRED_ISSUER_CERTIFICATE ||
+	    PR_GetError() == SEC_ERROR_CRL_EXPIRED ||
+	    PR_GetError() == SEC_ERROR_KRL_EXPIRED ||
+	    PR_GetError() == SSL_ERROR_EXPIRED_CERT_ALERT) {
+		qdevice_net_log(LOG_WARNING, "Server certificate is expired.");
+
+		return (SECSuccess);
+        }
+
+	qdevice_net_log_nss(LOG_ERR, "Server certificate verification failure.");
+
+	return (SECFailure);
+}
+
+static SECStatus
+qdevice_net_nss_get_client_auth_data(void *arg, PRFileDesc *sock, struct CERTDistNamesStr *caNames,
+    struct CERTCertificateStr **pRetCert, struct SECKEYPrivateKeyStr **pRetKey)
+{
+	qdevice_net_log(LOG_DEBUG, "Sending client auth data.");
+
+	return (NSS_GetClientAuthData(arg, sock, caNames, pRetCert, pRetKey));
+}
+
+static int
+qdevice_net_schedule_send(struct qdevice_net_instance *instance)
+{
+	if (instance->sending_msg) {
+		/*
+		 * Msg is already scheduled for send
+		 */
+		return (-1);
+	}
+
+	instance->msg_already_sent_bytes = 0;
+	instance->sending_msg = 1;
+
+	return (0);
+}
+
+static int
+qdevice_net_schedule_echo_request_send(struct qdevice_net_instance *instance)
+{
+	if (instance->sending_echo_request_msg) {
+		qdevice_net_log(LOG_ERR, "Can't schedule send of echo request msg, because "
+		    "previous message wasn't yet sent. Disconnecting from server.");
+		return (-1);
+	}
+
+	if (instance->echo_reply_received_msg_seq_num != instance->echo_request_expected_msg_seq_num) {
+		qdevice_net_log(LOG_ERR, "Server didn't send echo reply message on time. "
+		    "Disconnecting from server.");
+		return (-1);
+	}
+
+	instance->echo_request_expected_msg_seq_num++;
+
+	if (msg_create_echo_request(&instance->echo_request_send_buffer, 1, instance->echo_request_expected_msg_seq_num) == -1) {
+		qdevice_net_log(LOG_ERR, "Can't allocate send buffer for echo request msg");
+
+		return (-1);
+	}
+
+	instance->echo_request_msg_already_sent_bytes = 0;
+	instance->sending_echo_request_msg = 1;
+
+	return (0);
+}
+
+static void
+qdevice_net_log_msg_decode_error(int ret)
+{
+
+	switch (ret) {
+	case -1:
+		qdevice_net_log(LOG_WARNING, "Received message with option with invalid length");
+		break;
+	case -2:
+		qdevice_net_log(LOG_CRIT, "Can't allocate memory");
+		break;
+	case -3:
+		qdevice_net_log(LOG_WARNING, "Received inconsistent msg (tlv len > msg size)");
+		break;
+	case -4:
+		qdevice_net_log(LOG_ERR, "Received message with option with invalid value");
+		break;
+	default:
+		qdevice_net_log(LOG_ERR, "Unknown error occured when decoding message");
+		break;
+	}
+}
+
+/*
+ * -1 - Incompatible tls combination
+ *  0 - Don't use TLS
+ *  1 - Use TLS
+ */
+static int
+qdevice_net_check_tls_compatibility(enum tlv_tls_supported server_tls, enum tlv_tls_supported client_tls)
+{
+	int res;
+
+	res = -1;
+
+	switch (server_tls) {
+	case TLV_TLS_UNSUPPORTED:
+		switch (client_tls) {
+		case TLV_TLS_UNSUPPORTED: res = 0; break;
+		case TLV_TLS_SUPPORTED: res = 0; break;
+		case TLV_TLS_REQUIRED: res = -1; break;
+		}
+		break;
+	case TLV_TLS_SUPPORTED:
+		switch (client_tls) {
+		case TLV_TLS_UNSUPPORTED: res = 0; break;
+		case TLV_TLS_SUPPORTED: res = 1; break;
+		case TLV_TLS_REQUIRED: res = 1; break;
+		}
+		break;
+	case TLV_TLS_REQUIRED:
+		switch (client_tls) {
+		case TLV_TLS_UNSUPPORTED: res = -1; break;
+		case TLV_TLS_SUPPORTED: res = 1; break;
+		case TLV_TLS_REQUIRED: res = 1; break;
+		}
+		break;
+	}
+
+	return (res);
+}
+
+static int
+qdevice_net_msg_received_preinit(struct qdevice_net_instance *instance, const struct msg_decoded *msg)
+{
+
+	qdevice_net_log(LOG_ERR, "Received unexpected preinit message. Disconnecting from server");
+
+	return (-1);
+}
+
+static int
+qdevice_net_msg_check_seq_number(struct qdevice_net_instance *instance, const struct msg_decoded *msg)
+{
+
+	if (!msg->seq_number_set || msg->seq_number != instance->expected_msg_seq_num) {
+		qdevice_net_log(LOG_ERR, "Received message doesn't contain seq_number or it's not expected one.");
+
+		return (-1);
+	}
+
+	return (0);
+}
+
+static int
+qdevice_net_msg_check_echo_reply_seq_number(struct qdevice_net_instance *instance, const struct msg_decoded *msg)
+{
+
+	if (!msg->seq_number_set) {
+		qdevice_net_log(LOG_ERR, "Received echo reply message doesn't contain seq_number.");
+
+		return (-1);
+	}
+
+	if (msg->seq_number != instance->echo_request_expected_msg_seq_num) {
+		qdevice_net_log(LOG_ERR, "Server doesn't replied in expected time. Closing connection");
+
+		return (-1);
+	}
+
+	return (0);
+}
+
+static int
+qdevice_net_send_init(struct qdevice_net_instance *instance)
+{
+	enum msg_type *supported_msgs;
+	size_t no_supported_msgs;
+	enum tlv_opt_type *supported_opts;
+	size_t no_supported_opts;
+
+	tlv_get_supported_options(&supported_opts, &no_supported_opts);
+	msg_get_supported_messages(&supported_msgs, &no_supported_msgs);
+	instance->expected_msg_seq_num++;
+
+	if (msg_create_init(&instance->send_buffer, 1, instance->expected_msg_seq_num,
+	    supported_msgs, no_supported_msgs, supported_opts, no_supported_opts,
+	    instance->node_id) == 0) {
+		qdevice_net_log(LOG_ERR, "Can't allocate send buffer for init msg");
+
+		return (-1);
+	}
+
+	if (qdevice_net_schedule_send(instance) != 0) {
+		qdevice_net_log(LOG_ERR, "Can't schedule send of init msg");
+
+		return (-1);
+	}
+
+	instance->state = QDEVICE_NET_STATE_WAITING_INIT_REPLY;
+
+	return (0);
+}
+
+
+static int
+qdevice_net_msg_received_preinit_reply(struct qdevice_net_instance *instance, const struct msg_decoded *msg)
+{
+	int res;
+
+	if (instance->state != QDEVICE_NET_STATE_WAITING_PREINIT_REPLY) {
+		qdevice_net_log(LOG_ERR, "Received unexpected preinit reply message. Disconnecting from server");
+
+		return (-1);
+	}
+
+	if (qdevice_net_msg_check_seq_number(instance, msg) != 0) {
+		return (-1);
+	}
+
+	/*
+	 * Check TLS support
+	 */
+	if (!msg->tls_supported_set || !msg->tls_client_cert_required_set) {
+		qdevice_net_log(LOG_ERR, "Required tls_supported or tls_client_cert_required option is unset");
+
+		return (-1);
+	}
+
+	res = qdevice_net_check_tls_compatibility(msg->tls_supported, instance->tls_supported);
+	if (res == -1) {
+		qdevice_net_log(LOG_ERR, "Incompatible tls configuration (server %u client %u)",
+		    msg->tls_supported, instance->tls_supported);
+
+		return (-1);
+	} else if (res == 1) {
+		/*
+		 * Start TLS
+		 */
+		instance->expected_msg_seq_num++;
+		if (msg_create_starttls(&instance->send_buffer, 1, instance->expected_msg_seq_num) == 0) {
+			qdevice_net_log(LOG_ERR, "Can't allocate send buffer for starttls msg");
+
+			return (-1);
+		}
+
+		if (qdevice_net_schedule_send(instance) != 0) {
+			qdevice_net_log(LOG_ERR, "Can't schedule send of starttls msg");
+
+			return (-1);
+		}
+
+		instance->state = QDEVICE_NET_STATE_WAITING_STARTTLS_BEING_SENT;
+	} else if (res == 0) {
+		if (qdevice_net_send_init(instance) != 0) {
+			return (-1);
+		}
+	}
+
+	return (0);
+}
+
+static int
+qdevice_net_msg_received_init_reply(struct qdevice_net_instance *instance, const struct msg_decoded *msg)
+{
+	size_t zi;
+	int res;
+
+	if (instance->state != QDEVICE_NET_STATE_WAITING_INIT_REPLY) {
+		qdevice_net_log(LOG_ERR, "Received unexpected init reply message. Disconnecting from server");
+
+		return (-1);
+	}
+
+	if (qdevice_net_msg_check_seq_number(instance, msg) != 0) {
+		return (-1);
+	}
+
+	if (!msg->server_maximum_request_size_set || !msg->server_maximum_reply_size_set) {
+		qdevice_net_log(LOG_ERR, "Required maximum_request_size or maximum_reply_size option is unset");
+
+		return (-1);
+	}
+
+	if (msg->supported_messages == NULL || msg->supported_options == NULL) {
+		qdevice_net_log(LOG_ERR, "Required supported messages or supported options option is unset");
+
+		return (-1);
+	}
+
+	if (msg->supported_decision_algorithms == NULL) {
+		qdevice_net_log(LOG_ERR, "Required supported decision algorithms option is unset");
+
+		return (-1);
+	}
+
+	if (msg->server_maximum_request_size < instance->min_send_size) {
+		qdevice_net_log(LOG_ERR,
+		    "Server accepts maximum %zu bytes message but this client minimum is %zu bytes.",
+		    msg->server_maximum_request_size, instance->min_send_size);
+
+		return (-1);
+	}
+
+	if (msg->server_maximum_reply_size > instance->max_receive_size) {
+		qdevice_net_log(LOG_ERR,
+		    "Server may send message up to %zu bytes message but this client maximum is %zu bytes.",
+		    msg->server_maximum_reply_size, instance->max_receive_size);
+
+		return (-1);
+	}
+
+	/*
+	 * Change buffer sizes
+	 */
+	dynar_set_max_size(&instance->receive_buffer, msg->server_maximum_reply_size);
+	dynar_set_max_size(&instance->send_buffer, msg->server_maximum_request_size);
+	dynar_set_max_size(&instance->echo_request_send_buffer, msg->server_maximum_request_size);
+
+
+	/*
+	 * Check if server supports decision algorithm we need
+	 */
+	res = 0;
+
+	for (zi = 0; zi < msg->no_supported_decision_algorithms && !res; zi++) {
+		if (msg->supported_decision_algorithms[zi] == instance->decision_algorithm) {
+			res = 1;
+		}
+	}
+
+	if (!res) {
+		qdevice_net_log(LOG_ERR, "Server doesn't support required decision algorithm");
+
+		return (-1);
+	}
+
+	/*
+	 * Send set options message
+	 */
+	instance->expected_msg_seq_num++;
+
+	if (msg_create_set_option(&instance->send_buffer, 1, instance->expected_msg_seq_num,
+	    1, instance->decision_algorithm, 1, instance->heartbeat_interval) == 0) {
+		qdevice_net_log(LOG_ERR, "Can't allocate send buffer for set option msg");
+
+		return (-1);
+	}
+
+	if (qdevice_net_schedule_send(instance) != 0) {
+		qdevice_net_log(LOG_ERR, "Can't schedule send of set option msg");
+
+		return (-1);
+	}
+
+	instance->state = QDEVICE_NET_STATE_WAITING_SET_OPTION_REPLY;
+
+	return (0);
+}
+
+static int
+qdevice_net_msg_received_stattls(struct qdevice_net_instance *instance, const struct msg_decoded *msg)
+{
+
+	qdevice_net_log(LOG_ERR, "Received unexpected starttls message. Disconnecting from server");
+
+	return (-1);
+}
+
+static int
+qdevice_net_msg_received_server_error(struct qdevice_net_instance *instance, const struct msg_decoded *msg)
+{
+
+	if (!msg->reply_error_code_set) {
+		qdevice_net_log(LOG_ERR, "Received server error without error code set. Disconnecting from server");
+	} else {
+		qdevice_net_log(LOG_ERR, "Received server error %"PRIu16". Disconnecting from server",
+		    msg->reply_error_code);
+	}
+
+	return (-1);
+}
+
+static int
+qdevice_net_msg_received_set_option(struct qdevice_net_instance *instance, const struct msg_decoded *msg)
+{
+
+	qdevice_net_log(LOG_ERR, "Received unexpected set option message. Disconnecting from server");
+
+	return (-1);
+}
+
+static int
+qdevice_net_timer_send_heartbeat(void *data1, void *data2)
+{
+	struct qdevice_net_instance *instance;
+
+	instance = (struct qdevice_net_instance *)data1;
+
+	if (qdevice_net_schedule_echo_request_send(instance) == -1) {
+		instance->schedule_disconnect = 1;
+		return (0);
+	}
+
+	/*
+	 * Schedule this function callback again
+	 */
+	return (-1);
+}
+
+static int
+qdevice_net_msg_received_set_option_reply(struct qdevice_net_instance *instance, const struct msg_decoded *msg)
+{
+
+	if (qdevice_net_msg_check_seq_number(instance, msg) != 0) {
+		return (-1);
+	}
+
+	if (!msg->decision_algorithm_set || !msg->heartbeat_interval_set) {
+		qdevice_net_log(LOG_ERR, "Received set option reply message without required options. "
+		    "Disconnecting from server");
+	}
+
+	if (msg->decision_algorithm != instance->decision_algorithm ||
+	    msg->heartbeat_interval != instance->heartbeat_interval) {
+		qdevice_net_log(LOG_ERR, "Server doesn't accept sent decision algorithm or heartbeat interval.");
+
+		return (-1);
+	}
+
+	/*
+	 * Server accepted heartbeat interval -> schedule regular sending of echo request
+	 */
+	if (instance->heartbeat_interval > 0) {
+		instance->echo_request_timer = timer_list_add(&instance->main_timer_list, instance->heartbeat_interval,
+		    qdevice_net_timer_send_heartbeat, (void *)instance, NULL);
+
+		if (instance->echo_request_timer == NULL) {
+			qdevice_net_log(LOG_ERR, "Can't schedule regular sending of heartbeat.");
+
+			return (-1);
+		}
+	}
+
+	return (0);
+}
+
+static int
+qdevice_net_msg_received_echo_request(struct qdevice_net_instance *instance, const struct msg_decoded *msg)
+{
+
+	qdevice_net_log(LOG_ERR, "Received unexpected echo request message. Disconnecting from server");
+
+	return (-1);
+}
+
+static int
+qdevice_net_msg_received_echo_reply(struct qdevice_net_instance *instance, const struct msg_decoded *msg)
+{
+
+	if (qdevice_net_msg_check_echo_reply_seq_number(instance, msg) != 0) {
+		return (-1);
+	}
+
+	instance->echo_reply_received_msg_seq_num = msg->seq_number;
+
+	return (0);
+}
+
+
+static int
+qdevice_net_msg_received(struct qdevice_net_instance *instance)
+{
+	struct msg_decoded msg;
+	int res;
+	int ret_val;
+
+	msg_decoded_init(&msg);
+
+	res = msg_decode(&instance->receive_buffer, &msg);
+	if (res != 0) {
+		/*
+		 * Error occurred. Disconnect.
+		 */
+		qdevice_net_log_msg_decode_error(res);
+		qdevice_net_log(LOG_ERR, "Disconnecting from server");
+
+		return (-1);
+	}
+
+	ret_val = 0;
+
+	switch (msg.type) {
+	case MSG_TYPE_PREINIT:
+		ret_val = qdevice_net_msg_received_preinit(instance, &msg);
+		break;
+	case MSG_TYPE_PREINIT_REPLY:
+		ret_val = qdevice_net_msg_received_preinit_reply(instance, &msg);
+		break;
+	case MSG_TYPE_STARTTLS:
+		ret_val = qdevice_net_msg_received_stattls(instance, &msg);
+		break;
+	case MSG_TYPE_SERVER_ERROR:
+		ret_val = qdevice_net_msg_received_server_error(instance, &msg);
+		break;
+	case MSG_TYPE_INIT_REPLY:
+		ret_val = qdevice_net_msg_received_init_reply(instance, &msg);
+		break;
+	case MSG_TYPE_SET_OPTION:
+		ret_val = qdevice_net_msg_received_set_option(instance, &msg);
+		break;
+	case MSG_TYPE_SET_OPTION_REPLY:
+		ret_val = qdevice_net_msg_received_set_option_reply(instance, &msg);
+		break;
+	case MSG_TYPE_ECHO_REQUEST:
+		ret_val = qdevice_net_msg_received_echo_request(instance, &msg);
+		break;
+	case MSG_TYPE_ECHO_REPLY:
+		ret_val = qdevice_net_msg_received_echo_reply(instance, &msg);
+		break;
+	default:
+		qdevice_net_log(LOG_ERR, "Received unsupported message %u. Disconnecting from server", msg.type);
+		ret_val = -1;
+		break;
+	}
+
+	msg_decoded_destroy(&msg);
+
+	return (ret_val);
+}
+
+/*
+ * -1 means end of connection (EOF) or some other unhandled error. 0 = success
+ */
+static int
+qdevice_net_socket_read(struct qdevice_net_instance *instance)
+{
+	int res;
+	int ret_val;
+	int orig_skipping_msg;
+
+	orig_skipping_msg = instance->skipping_msg;
+
+	res = msgio_read(instance->socket, &instance->receive_buffer, &instance->msg_already_received_bytes,
+	    &instance->skipping_msg);
+
+	if (!orig_skipping_msg && instance->skipping_msg) {
+		qdevice_net_log(LOG_DEBUG, "msgio_read set skipping_msg");
+	}
+
+	ret_val = 0;
+
+	switch (res) {
+	case 0:
+		/*
+		 * Partial read
+		 */
+		break;
+	case -1:
+		qdevice_net_log(LOG_DEBUG, "Server closed connection");
+		ret_val = -1;
+		break;
+	case -2:
+		qdevice_net_log_nss(LOG_ERR, "Unhandled error when reading from server. Disconnecting from server");
+		ret_val = -1;
+		break;
+	case -3:
+		qdevice_net_log(LOG_ERR, "Can't store message header from server. Disconnecting from server");
+		ret_val = -1;
+		break;
+	case -4:
+		qdevice_net_log(LOG_ERR, "Can't store message from server. Disconnecting from server");
+		ret_val = -1;
+		break;
+	case -5:
+		qdevice_net_log(LOG_WARNING, "Server sent unsupported msg type %u. Disconnecting from server",
+			    msg_get_type(&instance->receive_buffer));
+		ret_val = -1;
+		break;
+	case -6:
+		qdevice_net_log(LOG_WARNING,
+		    "Server wants to send too long message %u bytes. Disconnecting from server",
+		    msg_get_len(&instance->receive_buffer));
+		ret_val = -1;
+		break;
+	case 1:
+		/*
+		 * Full message received / skipped
+		 */
+		if (!instance->skipping_msg) {
+			if (qdevice_net_msg_received(instance) == -1) {
+				ret_val = -1;
+			}
+		} else {
+			errx(1, "net_socket_read in skipping msg state");
+		}
+
+		instance->skipping_msg = 0;
+		instance->msg_already_received_bytes = 0;
+		dynar_clean(&instance->receive_buffer);
+		break;
+	default:
+		errx(1, "qdevice_net_socket_read unhandled error %d", res);
+		break;
+	}
+
+	return (ret_val);
+}
+
+static int
+qdevice_net_socket_write_finished(struct qdevice_net_instance *instance)
+{
+	PRFileDesc *new_pr_fd;
+
+	if (instance->state == QDEVICE_NET_STATE_WAITING_STARTTLS_BEING_SENT) {
+		/*
+		 * StartTLS sent to server. Begin with TLS handshake
+		 */
+		if ((new_pr_fd = nss_sock_start_ssl_as_client(instance->socket, QNETD_NSS_SERVER_CN,
+		    qdevice_net_nss_bad_cert_hook,
+		    qdevice_net_nss_get_client_auth_data, (void *)QDEVICE_NET_NSS_CLIENT_CERT_NICKNAME,
+		    0, NULL)) == NULL) {
+			qdevice_net_log_nss(LOG_ERR, "Can't start TLS");
+
+			return (-1);
+		}
+
+		/*
+		 * And send init msg
+		 */
+		if (qdevice_net_send_init(instance) != 0) {
+			return (-1);
+		}
+
+		instance->socket = new_pr_fd;
+	}
+
+	return (0);
+}
+
+static int
+qdevice_net_socket_write(struct qdevice_net_instance *instance)
+{
+	int res;
+	int send_echo_request;
+
+	/*
+	 * Echo request has extra buffer and special processing. Messages other then echo request
+	 * has higher priority, but if echo request send was not completed
+	 * it's necesary to complete it.
+	 */
+	send_echo_request = !(instance->sending_msg && instance->echo_request_msg_already_sent_bytes == 0);
+
+	if (!send_echo_request) {
+		res = msgio_write(instance->socket, &instance->send_buffer, &instance->msg_already_sent_bytes);
+	} else {
+		res = msgio_write(instance->socket, &instance->echo_request_send_buffer,
+		    &instance->echo_request_msg_already_sent_bytes);
+	}
+
+	if (res == 1) {
+		if (!send_echo_request) {
+			instance->sending_msg = 0;
+
+			if (qdevice_net_socket_write_finished(instance) == -1) {
+				return (-1);
+			}
+		} else {
+			instance->sending_echo_request_msg = 0;
+		}
+	}
+
+	if (res == -1) {
+		qdevice_net_log_nss(LOG_CRIT, "PR_Send returned 0");
+
+		return (-1);
+	}
+
+	if (res == -2) {
+		qdevice_net_log_nss(LOG_ERR, "Unhandled error when sending message to server");
+
+		return (-1);
+	}
+
+	return (0);
+}
+
+
+#define QDEVICE_NET_POLL_NO_FDS		1
+#define QDEVICE_NET_POLL_SOCKET		0
+
+static int
+qdevice_net_poll(struct qdevice_net_instance *instance)
+{
+	PRPollDesc pfds[QDEVICE_NET_POLL_NO_FDS];
+	PRInt32 poll_res;
+	int i;
+
+	pfds[QDEVICE_NET_POLL_SOCKET].fd = instance->socket;
+	pfds[QDEVICE_NET_POLL_SOCKET].in_flags = PR_POLL_READ;
+	if (instance->sending_msg || instance->sending_echo_request_msg) {
+		pfds[QDEVICE_NET_POLL_SOCKET].in_flags |= PR_POLL_WRITE;
+	}
+
+	instance->schedule_disconnect = 0;
+
+	if ((poll_res = PR_Poll(pfds, QDEVICE_NET_POLL_NO_FDS,
+	    timer_list_time_to_expire(&instance->main_timer_list))) > 0) {
+		for (i = 0; i < QDEVICE_NET_POLL_NO_FDS; i++) {
+			if (pfds[i].out_flags & PR_POLL_READ) {
+				switch (i) {
+				case QDEVICE_NET_POLL_SOCKET:
+					if (qdevice_net_socket_read(instance) == -1) {
+						instance->schedule_disconnect = 1;
+					}
+
+					break;
+				default:
+					errx(1, "Unhandled read poll descriptor %u", i);
+					break;
+				}
+			}
+
+			if (!instance->schedule_disconnect && pfds[i].out_flags & PR_POLL_WRITE) {
+				switch (i) {
+				case QDEVICE_NET_POLL_SOCKET:
+					if (qdevice_net_socket_write(instance) == -1) {
+						instance->schedule_disconnect = 1;
+					}
+
+					break;
+				default:
+					errx(1, "Unhandled write poll descriptor %u", i);
+					break;
+				}
+			}
+
+			if (!instance->schedule_disconnect &&
+			    pfds[i].out_flags & (PR_POLL_ERR|PR_POLL_NVAL|PR_POLL_HUP|PR_POLL_EXCEPT)) {
+				switch (i) {
+				case QDEVICE_NET_POLL_SOCKET:
+					qdevice_net_log(LOG_CRIT, "POLL_ERR (%u) on main socket", pfds[i].out_flags);
+
+					return (-1);
+
+					break;
+				default:
+					errx(1, "Unhandled poll err on descriptor %u", i);
+					break;
+				}
+			}
+		}
+	}
+
+	if (!instance->schedule_disconnect) {
+		timer_list_expire(&instance->main_timer_list);
+	}
+
+	if (instance->schedule_disconnect) {
+		/*
+		 * Schedule disconnect can be set by this function or by some timer_list callback
+		 */
+		return (-1);
+	}
+
+	return (0);
+}
+
+static int
+qdevice_net_instance_init(struct qdevice_net_instance *instance, size_t initial_receive_size,
+    size_t initial_send_size, size_t min_send_size, size_t max_receive_size, enum tlv_tls_supported tls_supported,
+    uint32_t node_id, enum tlv_decision_algorithm_type decision_algorithm, uint32_t heartbeat_interval)
+{
+
+	memset(instance, 0, sizeof(*instance));
+
+	instance->initial_receive_size = initial_receive_size;
+	instance->initial_send_size = initial_send_size;
+	instance->min_send_size = min_send_size;
+	instance->max_receive_size = max_receive_size;
+	instance->node_id = node_id;
+	instance->decision_algorithm = decision_algorithm;
+	instance->heartbeat_interval = heartbeat_interval;
+	dynar_init(&instance->receive_buffer, initial_receive_size);
+	dynar_init(&instance->send_buffer, initial_send_size);
+	dynar_init(&instance->echo_request_send_buffer, initial_send_size);
+	timer_list_init(&instance->main_timer_list);
+
+	instance->tls_supported = tls_supported;
+
+	return (0);
+}
+
+static int
+qdevice_net_instance_destroy(struct qdevice_net_instance *instance)
+{
+
+	timer_list_free(&instance->main_timer_list);
+	dynar_destroy(&instance->receive_buffer);
+	dynar_destroy(&instance->send_buffer);
+	dynar_destroy(&instance->echo_request_send_buffer);
+
+	return (0);
+}
+
+int
+main(void)
+{
+	struct qdevice_net_instance instance;
+
+	/*
+	 * Init
+	 */
+	qdevice_net_log_init(QDEVICE_NET_LOG_TARGET_STDERR);
+        qdevice_net_log_set_debug(1);
+
+	if (nss_sock_init_nss((char *)NSS_DB_DIR) != 0) {
+		err_nss();
+	}
+
+	if (qdevice_net_instance_init(&instance,
+	    QDEVICE_NET_INITIAL_MSG_RECEIVE_SIZE, QDEVICE_NET_INITIAL_MSG_SEND_SIZE,
+	    QDEVICE_NET_MIN_MSG_SEND_SIZE, QDEVICE_NET_MAX_MSG_RECEIVE_SIZE,
+	    QDEVICE_NET_TLS_SUPPORTED, QDEVICE_NET_NODE_ID, QDEVICE_NET_DECISION_ALGORITHM,
+	    QDEVICE_NET_HEARTBEAT_INTERVAL) == -1) {
+		errx(1, "Can't initialize qdevice-net");
+	}
+
+	/*
+	 * Try to connect to qnetd host
+	 */
+	instance.socket = nss_sock_create_client_socket(QNETD_HOST, QNETD_PORT, PR_AF_UNSPEC, 100);
+	if (instance.socket == NULL) {
+		err_nss();
+	}
+
+	if (nss_sock_set_nonblocking(instance.socket) != 0) {
+		err_nss();
+	}
+
+	/*
+	 * Create and schedule send of preinit message to qnetd
+	 */
+	instance.expected_msg_seq_num = 1;
+	if (msg_create_preinit(&instance.send_buffer, QDEVICE_NET_CLUSTER_NAME, 1, instance.expected_msg_seq_num) == 0) {
+		errx(1, "Can't allocate buffer");
+	}
+	if (qdevice_net_schedule_send(&instance) != 0) {
+		errx(1, "Can't schedule send of preinit msg");
+	}
+
+	instance.state = QDEVICE_NET_STATE_WAITING_PREINIT_REPLY;
+
+	/*
+	 * Main loop
+	 */
+	while (qdevice_net_poll(&instance) == 0) {
+	}
+
+	/*
+	 * Cleanup
+	 */
+	if (PR_Close(instance.socket) != PR_SUCCESS) {
+		err_nss();
+	}
+
+	qdevice_net_instance_destroy(&instance);
+
+	SSL_ClearSessionCache();
+
+	if (NSS_Shutdown() != SECSuccess) {
+		err_nss();
+	}
+
+	PR_Cleanup();
+
+	qdevice_net_log_close();
+
+	return (0);
+}

+ 335 - 0
qdevices/corosync-qnetd-certutil.sh

@@ -0,0 +1,335 @@
+#!/bin/bash
+
+#
+# Copyright (c) 2015 Red Hat, Inc.
+#
+# All rights reserved.
+#
+# Author: Jan Friesse (jfriesse@redhat.com)
+#
+# This software licensed under BSD license, the text of which follows:
+#
+# Redistribution and use in source and binary forms, with or without
+# modification, are permitted provided that the following conditions are met:
+#
+# - Redistributions of source code must retain the above copyright notice,
+#   this list of conditions and the following disclaimer.
+# - 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.
+# - Neither the name of the Red Hat, Inc. nor the names of its
+#   contributors may be used to endorse or promote products derived from this
+#   software without specific prior written permission.
+#
+# THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS"
+# AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
+# IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
+# ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR CONTRIBUTORS BE
+# LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR
+# CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF
+# SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS
+# INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN
+# CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE)
+# ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF
+# THE POSSIBILITY OF SUCH DAMAGE.
+#
+
+DB_DIR=nssdb
+# Validity of certificate (months)
+CRT_VALIDITY=120
+CA_NICKNAME="QNet CA"
+SERVER_NICKNAME="QNetd Cert"
+CLUSTER_NICKNAME="Cluster Cert"
+CA_SUBJECT="CN=QNet CA"
+SERVER_SUBJECT="CN=Qnetd Server"
+PWD_FILE="$DB_DIR/pwdfile.txt"
+NOISE_FILE="$DB_DIR/noise.txt"
+SERIAL_NO_FILE="$DB_DIR/serial.txt"
+CA_EXPORT_FILE="$DB_DIR/qnetd-cacert.crt"
+CRQ_FILE="$DB_DIR/qnetd-node.crq"
+CRT_FILE="" # Generated from cluster name
+P12_FILE="$DB_DIR/qnetd-node.p12"
+
+usage() {
+    echo "$0: [-I|-i|-m|-M|-r|-s] [-c certificate] [-n cluster_name]"
+    echo
+    echo " -I      Initialize server CA and generate server certificate"
+    echo " -i      Initialize node CA. Needs CA certificate from server"
+    echo ""
+    echo " -m      Import cluster certificate on node (needs pk12 certificate)"
+    echo ""
+    echo " -r      Generate cluster certificate request"
+    echo " -s      Sign cluster certificate on qnetd server (needs cluster certificate)"
+    echo " -M      Import signed cluster certificate and export certificate with key to pk12 file"
+    echo ""
+    echo " -c certificate      Ether CA, CRQ, CRT or pk12 certificate (operation dependant)"
+    echo " -n cluster_name     Name of cluster (for -r and -s operations)"
+    echo ""
+    echo "Typical usage:"
+    echo "- Initialize database on QNetd server by running $0 -I"
+    echo "- Copy exported QNetd CA certificate ($CA_EXPORT_FILE) to every node"
+    echo "- On one of cluster node initialize database by running $0 -i -c `basename $CA_EXPORT_FILE`"
+    echo "- Generate certificate request: $0 -r -n Cluster"
+    echo "- Copy exported CRQ to QNetd server"
+    echo "- On QNetd server sign and export cluster certificate by running $0 -s -c `basename $CRQ_FILE` -n Cluster"
+    echo "- Copy exported CRT to node where certificate request was created"
+    echo "- Import certificate on node where certificate request was created by running $0 -M -c qnetd-cluster-Cluster.crt"
+    echo "- Copy output $P12_FILE to all other cluster nodes"
+    echo "- On all other nodes in cluster:"
+    echo "  - Init database by running $0 -i -c `basename $CA_EXPORT_FILE`"
+    echo "  - Import cluster certificate and key: $0 -m -c `basename $P12_FILE`"
+
+    exit 0
+}
+
+create_new_noise_file() {
+    local noise_file="$1"
+
+    if [ ! -e "$noise_file" ];then
+        echo "Creating new noise file $noise_file"
+
+        (ps -elf; date; w) | sha1sum | (read sha_sum rest; echo $sha_sum) > "$noise_file"
+
+        chown root:root "$noise_file"
+        chmod 400 "$noise_file"
+    else
+        echo "Using existing noise file $noise_file"
+    fi
+}
+
+get_serial_no() {
+    local serial_no
+
+    if ! [ -f "$SERIAL_NO_FILE" ];then
+        echo "100" > $SERIAL_NO_FILE
+    fi
+    serial_no=`cat $SERIAL_NO_FILE`
+    serial_no=$((serial_no+1))
+    echo "$serial_no" > $SERIAL_NO_FILE
+    echo "$serial_no"
+}
+
+init_server_ca() {
+    if [ -f "$DB_DIR/cert8.db" ];then
+        echo "Certificate database already exists. Delete it to continue" >&2
+
+        exit 1
+    fi
+
+    if ! [ -d "$DB_DIR" ];then
+        echo "Creating $DB_DIR"
+        mkdir "$DB_DIR"
+        chown root:root "$DB_DIR"
+        chmod 700 "$DB_DIR"
+    fi
+
+    echo "Creating new key and cert db"
+    echo -n "" > "$PWD_FILE"
+    certutil -N -d "$DB_DIR" -f "$PWD_FILE"
+    chown root:root "$DB_DIR/key3.db" "$DB_DIR/cert8.db" "$DB_DIR/secmod.db"
+    chmod 600 "$DB_DIR/key3.db" "$DB_DIR/cert8.db" "$DB_DIR/secmod.db"
+
+    create_new_noise_file "$NOISE_FILE"
+
+    echo "Creating new CA"
+    # Create self-signed certificate (CA). Asks 3 questions (is this CA, lifetime and critical extension
+    echo -e "y\n0\ny\n" | certutil -S -n "$CA_NICKNAME" -s "$CA_SUBJECT" -x \
+        -t "CT,," -m `get_serial_no` -v $CRT_VALIDITY -d "$DB_DIR" \
+        -z "$NOISE_FILE" -f "$PWD_FILE" -2
+    # Export CA certificate in ascii
+    certutil -L -d "$DB_DIR" -n "$CA_NICKNAME" > "$CA_EXPORT_FILE"
+    certutil -L -d "$DB_DIR" -n "$CA_NICKNAME" -a >> "$CA_EXPORT_FILE"
+
+    certutil -S -n "$SERVER_NICKNAME" -s "$SERVER_SUBJECT" -c "$CA_NICKNAME" -t "u,u,u" -m `get_serial_no` \
+        -v $CRT_VALIDITY -d "$DB_DIR" -z "$NOISE_FILE" -f "$PWD_FILE"
+
+    echo "QNetd CA is exported as $CA_EXPORT_FILE"
+}
+
+init_node_ca() {
+    if [ -f "$DB_DIR/cert8.db" ];then
+        echo "Certificate database already exists. Delete it to continue" >&2
+
+        exit 1
+    fi
+
+    if ! [ -d "$DB_DIR" ];then
+        echo "Creating $DB_DIR"
+        mkdir "$DB_DIR"
+        chown root:root "$DB_DIR"
+        chmod 700 "$DB_DIR"
+    fi
+
+    echo "Creating new key and cert db"
+    echo -n "" > "$PWD_FILE"
+    certutil -N -d "$DB_DIR" -f "$PWD_FILE"
+    chown root:root "$DB_DIR/key3.db" "$DB_DIR/cert8.db" "$DB_DIR/secmod.db"
+    chmod 600 "$DB_DIR/key3.db" "$DB_DIR/cert8.db" "$DB_DIR/secmod.db"
+
+    create_new_noise_file "$NOISE_FILE"
+
+    echo "Importing CA"
+
+    certutil -d "$DB_DIR" -A -t "CT,c,c" -n "$CA_NICKNAME" -f "$PWD_FILE" \
+        -i "$CERTIFICATE_FILE"
+}
+
+gen_cluster_cert_req() {
+    if ! [ -f "$DB_DIR/cert8.db" ];then
+        echo "Certificate database doesn't exists. Use $0 -i to create it" >&2
+
+        exit 1
+    fi
+
+    echo "Creating new certificate request"
+
+    certutil -R -s "CN=$CLUSTER_NAME" -o "$CRQ_FILE" -d "$DB_DIR" -f "$PWD_FILE" -z "$NOISE_FILE"
+
+    echo "Certificate request stored in $CRQ_FILE"
+}
+
+sign_cluster_cert() {
+    if ! [ -f "$DB_DIR/cert8.db" ];then
+        echo "Certificate database doesn't exists. Use $0 -I to create it" >&2
+
+        exit 1
+    fi
+
+    echo "Signing cluster certificate"
+    certutil -C -m `get_serial_no` -i "$CERTIFICATE_FILE" -o "$CRT_FILE" -c "$CA_NICKNAME" -d "$DB_DIR"
+
+    echo "Certificate stored in $CRT_FILE"
+}
+
+import_signed_cert() {
+    if ! [ -f "$DB_DIR/cert8.db" ];then
+        echo "Certificate database doesn't exists. Use $0 -i to create it" >&2
+
+        exit 1
+    fi
+
+    echo "Importing signed cluster certificate"
+    certutil -d "$DB_DIR" -A -t "u,u,u" -n "$CLUSTER_NICKNAME" -i "$CERTIFICATE_FILE"
+
+    pk12util -d "$DB_DIR" -o "$P12_FILE" -W "" -n "$CLUSTER_NICKNAME"
+
+    echo "Certificate stored in $P12_FILE"
+}
+
+import_pk12() {
+    if ! [ -f "$DB_DIR/cert8.db" ];then
+        echo "Certificate database doesn't exists. Use $0 -i to create it" >&2
+
+        exit 1
+    fi
+
+    echo "Importing cluster certificate and key"
+    pk12util -i "$CERTIFICATE_FILE" -d "$DB_DIR" -W ""
+}
+
+OPERATION=""
+CERTIFICATE_FILE=""
+CLUSTER_NAME=""
+
+while getopts ":hIiMmrsc:n:" opt; do
+    case $opt in
+        r)
+            OPERATION=gen_cluster_cert_req
+            ;;
+        I)
+            OPERATION=init_server_ca
+            ;;
+        i)
+            OPERATION=init_node_ca
+            ;;
+        s)
+            OPERATION=sign_cluster_cert
+            ;;
+        m)
+            OPERATION=import_pk12
+            ;;
+        M)
+            OPERATION=import_signed_cert
+            ;;
+        n)
+            CLUSTER_NAME="$OPTARG"
+            ;;
+        h)
+            usage
+            ;;
+        c)
+            CERTIFICATE_FILE="$OPTARG"
+            ;;
+        \?)
+            echo "Invalid option: -$OPTARG" >&2
+
+            exit 1
+            ;;
+        :)
+            echo "Option -$OPTARG requires an argument." >&2
+
+            exit 1
+            ;;
+   esac
+done
+
+case "$OPERATION" in
+    "init_server_ca")
+        init_server_ca
+    ;;
+    "init_node_ca")
+        if ! [ -e "$CERTIFICATE_FILE" ];then
+            echo "Can't open certificate file $CERTIFICATE_FILE" >&2
+
+            exit 2
+        fi
+
+        init_node_ca
+    ;;
+    "gen_cluster_cert_req")
+        if [ "$CLUSTER_NAME" == "" ];then
+            echo "You have to specify cluster name" >&2
+
+            exit 2
+        fi
+
+        gen_cluster_cert_req
+    ;;
+    "sign_cluster_cert")
+        if ! [ -e "$CERTIFICATE_FILE" ];then
+            echo "Can't open certificate file $CERTIFICATE_FILE" >&2
+
+            exit 2
+        fi
+
+        if [ "$CLUSTER_NAME" == "" ];then
+            echo "You have to specify cluster name" >&2
+
+            exit 2
+        fi
+
+        CRT_FILE="$DB_DIR/qnetd-cluster-$CLUSTER_NAME.crt"
+        sign_cluster_cert
+    ;;
+    "import_signed_cert")
+        if ! [ -e "$CERTIFICATE_FILE" ];then
+            echo "Can't open certificate file $CERTIFICATE_FILE" >&2
+
+            exit 2
+        fi
+
+        import_signed_cert
+    ;;
+    "import_pk12")
+        if ! [ -e "$CERTIFICATE_FILE" ];then
+            echo "Can't open certificate file $CERTIFICATE_FILE" >&2
+
+            exit 2
+        fi
+
+        import_pk12
+    ;;
+    *)
+        usage
+    ;;
+esac

+ 1105 - 0
qdevices/corosync-qnetd.c

@@ -0,0 +1,1105 @@
+/*
+ * Copyright (c) 2015 Red Hat, Inc.
+ *
+ * All rights reserved.
+ *
+ * Author: Jan Friesse (jfriesse@redhat.com)
+ *
+ * This software licensed under BSD license, the text of which follows:
+ *
+ * Redistribution and use in source and binary forms, with or without
+ * modification, are permitted provided that the following conditions are met:
+ *
+ * - Redistributions of source code must retain the above copyright notice,
+ *   this list of conditions and the following disclaimer.
+ * - 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.
+ * - Neither the name of the Red Hat, Inc. nor the names of its
+ *   contributors may be used to endorse or promote products derived from this
+ *   software without specific prior written permission.
+ *
+ * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS"
+ * AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
+ * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
+ * ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR CONTRIBUTORS BE
+ * LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR
+ * CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF
+ * SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS
+ * INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN
+ * CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE)
+ * ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF
+ * THE POSSIBILITY OF SUCH DAMAGE.
+ */
+
+#include <stdio.h>
+#include <nss.h>
+#include <pk11func.h>
+#include <certt.h>
+#include <ssl.h>
+#include <prio.h>
+#include <prnetdb.h>
+#include <prerror.h>
+#include <prinit.h>
+#include <getopt.h>
+#include <err.h>
+#include <keyhi.h>
+#include <syslog.h>
+#include <signal.h>
+
+#include "msg.h"
+#include "msgio.h"
+#include "tlv.h"
+#include "nss-sock.h"
+#include "qnetd-client.h"
+#include "qnetd-clients-list.h"
+#include "qnetd-poll-array.h"
+#include "qnetd-log.h"
+#include "dynar.h"
+#include "timer-list.h"
+
+#define QNETD_HOST      NULL
+#define QNETD_PORT      4433
+#define QNETD_LISTEN_BACKLOG	10
+#define QNETD_MAX_CLIENT_SEND_SIZE	(1 << 15)
+#define QNETD_MAX_CLIENT_RECEIVE_SIZE	(1 << 15)
+
+#define NSS_DB_DIR	"nssdb"
+#define QNETD_CERT_NICKNAME	"QNetd Cert"
+
+#define QNETD_TLS_SUPPORTED			TLV_TLS_SUPPORTED
+#define QNETD_TLS_CLIENT_CERT_REQUIRED		1
+
+#define QNETD_HEARTBEAT_INTERVAL_MIN		1000
+#define QNETD_HEARTBEAT_INTERVAL_MAX		200000
+
+struct qnetd_instance {
+	struct {
+		PRFileDesc *socket;
+		CERTCertificate *cert;
+		SECKEYPrivateKey *private_key;
+	} server;
+	size_t max_client_receive_size;
+	size_t max_client_send_size;
+	struct qnetd_clients_list clients;
+	struct qnetd_poll_array poll_array;
+	enum tlv_tls_supported tls_supported;
+	int tls_client_cert_required;
+};
+
+/*
+ * This is global variable used for comunication with main loop and signal (calls close)
+ */
+PRFileDesc *global_server_socket;
+
+/*
+ * Decision algorithms supported in this server
+ */
+#define QNETD_STATIC_SUPPORTED_DECISION_ALGORITHMS_SIZE		1
+
+enum tlv_decision_algorithm_type
+    qnetd_static_supported_decision_algorithms[QNETD_STATIC_SUPPORTED_DECISION_ALGORITHMS_SIZE] = {
+	TLV_DECISION_ALGORITHM_TYPE_TEST,
+};
+
+static void
+qnetd_err_nss(void) {
+
+	qnetd_log_nss(LOG_CRIT, "NSS error");
+
+	exit(1);
+}
+
+static void
+qnetd_warn_nss(void) {
+
+	qnetd_log_nss(LOG_WARNING, "NSS warning");
+}
+
+static void
+qnetd_client_log_msg_decode_error(int ret)
+{
+
+	switch (ret) {
+	case -1:
+		qnetd_log(LOG_WARNING, "Received message with option with invalid length");
+		break;
+	case -2:
+		qnetd_log(LOG_CRIT, "Can't allocate memory");
+		break;
+	case -3:
+		qnetd_log(LOG_WARNING, "Received inconsistent msg (tlv len > msg size)");
+		break;
+	case -4:
+		qnetd_log(LOG_WARNING, "Received message with option with invalid value");
+		break;
+	default:
+		qnetd_log(LOG_ERR, "Unknown error occured when decoding message");
+		break;
+	}
+}
+
+static int
+qnetd_client_net_schedule_send(struct qnetd_client *client)
+{
+	if (client->sending_msg) {
+		/*
+		 * Client is already sending msg
+		 */
+		return (-1);
+	}
+
+	client->msg_already_sent_bytes = 0;
+	client->sending_msg = 1;
+
+	return (0);
+}
+
+static int
+qnetd_client_send_err(struct qnetd_client *client, int add_msg_seq_number, uint32_t msg_seq_number,
+    enum tlv_reply_error_code reply)
+{
+
+	if (msg_create_server_error(&client->send_buffer, add_msg_seq_number, msg_seq_number, reply) == 0) {
+		qnetd_log(LOG_ERR, "Can't alloc server error msg. Disconnecting client connection.");
+
+		return (-1);
+	};
+
+	if (qnetd_client_net_schedule_send(client) != 0) {
+		qnetd_log(LOG_ERR, "Can't schedule send of error message. Disconnecting client connection.");
+
+		return (-1);
+	}
+
+	return (0);
+}
+
+static int
+qnetd_client_msg_received_preinit(struct qnetd_instance *instance, struct qnetd_client *client,
+	const struct msg_decoded *msg)
+{
+
+	if (msg->cluster_name == NULL) {
+		qnetd_log(LOG_ERR, "Received preinit message without cluster name. Sending error reply.");
+
+		if (qnetd_client_send_err(client, msg->seq_number_set, msg->seq_number,
+		    TLV_REPLY_ERROR_CODE_DOESNT_CONTAIN_REQUIRED_OPTION) != 0) {
+			return (-1);
+		}
+
+		return (0);
+	}
+
+	client->cluster_name = malloc(msg->cluster_name_len + 1);
+	if (client->cluster_name == NULL) {
+		qnetd_log(LOG_ERR, "Can't allocate cluster name. Sending error reply.");
+
+		if (qnetd_client_send_err(client, msg->seq_number_set, msg->seq_number,
+		    TLV_REPLY_ERROR_CODE_INTERNAL_ERROR) != 0) {
+			return (-1);
+		}
+
+		return (0);
+	}
+
+	memcpy(client->cluster_name, msg->cluster_name, msg->cluster_name_len + 1);
+	client->cluster_name_len = msg->cluster_name_len;
+	client->preinit_received = 1;
+
+	if (msg_create_preinit_reply(&client->send_buffer, msg->seq_number_set, msg->seq_number,
+	    instance->tls_supported, instance->tls_client_cert_required) == 0) {
+		qnetd_log(LOG_ERR, "Can't alloc preinit reply msg. Disconnecting client connection.");
+
+		return (-1);
+	};
+
+	if (qnetd_client_net_schedule_send(client) != 0) {
+		qnetd_log(LOG_ERR, "Can't schedule send of preinit message. Disconnecting client connection.");
+
+		return (-1);
+	}
+
+	return (0);
+}
+
+static int
+qnetd_client_msg_received_preinit_reply(struct qnetd_instance *instance, struct qnetd_client *client,
+	const struct msg_decoded *msg)
+{
+
+	qnetd_log(LOG_ERR, "Received preinit reply. Sending back error message");
+
+	if (qnetd_client_send_err(client, msg->seq_number_set, msg->seq_number,
+	    TLV_REPLY_ERROR_CODE_UNEXPECTED_MESSAGE) != 0) {
+		return (-1);
+	}
+
+	return (0);
+}
+
+static int
+qnetd_client_msg_received_starttls(struct qnetd_instance *instance, struct qnetd_client *client,
+	const struct msg_decoded *msg)
+{
+	PRFileDesc *new_pr_fd;
+
+	if (!client->preinit_received) {
+		qnetd_log(LOG_ERR, "Received starttls before preinit message. Sending error reply.");
+
+		if (qnetd_client_send_err(client, msg->seq_number_set, msg->seq_number,
+		    TLV_REPLY_ERROR_CODE_PREINIT_REQUIRED) != 0) {
+			return (-1);
+		}
+
+		return (0);
+	}
+
+	if ((new_pr_fd = nss_sock_start_ssl_as_server(client->socket, instance->server.cert,
+	    instance->server.private_key, instance->tls_client_cert_required, 0, NULL)) == NULL) {
+		qnetd_log_nss(LOG_ERR, "Can't start TLS. Disconnecting client.");
+
+		return (-1);
+	}
+
+	client->tls_started = 1;
+	client->tls_peer_certificate_verified = 0;
+	client->socket = new_pr_fd;
+
+	return (0);
+}
+
+static int
+qnetd_client_msg_received_server_error(struct qnetd_instance *instance, struct qnetd_client *client,
+	const struct msg_decoded *msg)
+{
+	qnetd_log(LOG_ERR, "Received server error. Sending back error message");
+
+	if (qnetd_client_send_err(client, msg->seq_number_set, msg->seq_number,
+	    TLV_REPLY_ERROR_CODE_UNEXPECTED_MESSAGE) != 0) {
+		return (-1);
+	}
+
+	return (0);
+}
+
+/*
+ *  0 - Success
+ * -1 - Disconnect client
+ * -2 - Error reply sent, but no need to disconnect client
+ */
+static int
+qnetd_client_check_tls(struct qnetd_instance *instance, struct qnetd_client *client, const struct msg_decoded *msg)
+{
+	int check_certificate;
+	int tls_required;
+	CERTCertificate *peer_cert;
+
+	check_certificate = 0;
+	tls_required = 0;
+
+	switch (instance->tls_supported) {
+	case TLV_TLS_UNSUPPORTED:
+		tls_required = 0;
+		check_certificate = 0;
+		break;
+	case TLV_TLS_SUPPORTED:
+		tls_required = 0;
+
+		if (client->tls_started && instance->tls_client_cert_required && !client->tls_peer_certificate_verified) {
+			check_certificate = 1;
+		}
+	case TLV_TLS_REQUIRED:
+		tls_required = 1;
+
+		if (instance->tls_client_cert_required && !client->tls_peer_certificate_verified) {
+			check_certificate = 1;
+		}
+		break;
+	default:
+		errx(1, "Unhandled instance tls supported %u\n", instance->tls_supported);
+		break;
+	}
+
+	if (tls_required && !client->tls_started) {
+		qnetd_log(LOG_ERR, "TLS is required but doesn't started yet. Sending back error message");
+
+		if (qnetd_client_send_err(client, msg->seq_number_set, msg->seq_number,
+		    TLV_REPLY_ERROR_CODE_TLS_REQUIRED) != 0) {
+			return (-1);
+		}
+
+		return (-2);
+	}
+
+	if (check_certificate) {
+		peer_cert = SSL_PeerCertificate(client->socket);
+
+		if (peer_cert == NULL) {
+			qnetd_log(LOG_ERR, "Client doesn't sent valid certificate. Disconnecting client");
+
+			return (-1);
+		}
+
+		if (CERT_VerifyCertName(peer_cert, client->cluster_name) != SECSuccess) {
+			qnetd_log(LOG_ERR, "Client doesn't sent certificate with valid CN. Disconnecting client");
+
+			CERT_DestroyCertificate(peer_cert);
+
+			return (-1);
+		}
+
+		CERT_DestroyCertificate(peer_cert);
+
+		client->tls_peer_certificate_verified = 1;
+	}
+
+	return (0);
+}
+
+static int
+qnetd_client_msg_received_init(struct qnetd_instance *instance, struct qnetd_client *client,
+	const struct msg_decoded *msg)
+{
+	int res;
+	enum msg_type *supported_msgs;
+	size_t no_supported_msgs;
+	enum tlv_opt_type *supported_opts;
+	size_t no_supported_opts;
+
+	supported_msgs = NULL;
+	supported_opts = NULL;
+	no_supported_msgs = 0;
+	no_supported_opts = 0;
+
+	if ((res = qnetd_client_check_tls(instance, client, msg)) != 0) {
+		return (res == -1 ? -1 : 0);
+	}
+
+	if (!client->preinit_received) {
+		qnetd_log(LOG_ERR, "Received init before preinit message. Sending error reply.");
+
+		if (qnetd_client_send_err(client, msg->seq_number_set, msg->seq_number,
+		    TLV_REPLY_ERROR_CODE_PREINIT_REQUIRED) != 0) {
+			return (-1);
+		}
+
+		return (0);
+	}
+
+	if (!msg->node_id_set) {
+		qnetd_log(LOG_ERR, "Received init message without node id set. Sending error reply.");
+
+		if (qnetd_client_send_err(client, msg->seq_number_set, msg->seq_number,
+		    TLV_REPLY_ERROR_CODE_DOESNT_CONTAIN_REQUIRED_OPTION) != 0) {
+			return (-1);
+		}
+
+		return (0);
+	}
+
+	if (msg->supported_messages != NULL) {
+		/*
+		 * Client sent supported messages. For now this is ignored but in the future
+		 * this may be used to ensure backward compatibility.
+		 */
+/*
+		for (i = 0; i < msg->no_supported_messages; i++) {
+			qnetd_log(LOG_DEBUG, "Client supports %u message", (int)msg->supported_messages[i]);
+		}
+*/
+
+		/*
+		 * Sent back supported messages
+		 */
+		msg_get_supported_messages(&supported_msgs, &no_supported_msgs);
+	}
+
+	if (msg->supported_options != NULL) {
+		/*
+		 * Client sent supported options. For now this is ignored but in the future
+		 * this may be used to ensure backward compatibility.
+		 */
+/*
+		for (i = 0; i < msg->no_supported_options; i++) {
+			qnetd_log(LOG_DEBUG, "Client supports %u option", (int)msg->supported_messages[i]);
+		}
+*/
+
+		/*
+		 * Send back supported options
+		 */
+		tlv_get_supported_options(&supported_opts, &no_supported_opts);
+	}
+
+	client->node_id_set = 1;
+	client->node_id = msg->node_id;
+	client->init_received = 1;
+
+	if (msg_create_init_reply(&client->send_buffer, msg->seq_number_set, msg->seq_number,
+	    supported_msgs, no_supported_msgs, supported_opts, no_supported_opts,
+	    instance->max_client_receive_size, instance->max_client_send_size,
+	    qnetd_static_supported_decision_algorithms, QNETD_STATIC_SUPPORTED_DECISION_ALGORITHMS_SIZE) == -1) {
+		qnetd_log(LOG_ERR, "Can't alloc init reply msg. Disconnecting client connection.");
+
+		return (-1);
+	}
+
+	if (qnetd_client_net_schedule_send(client) != 0) {
+		qnetd_log(LOG_ERR, "Can't schedule send of init reply message. Disconnecting client connection.");
+
+		return (-1);
+	}
+
+	return (0);
+}
+
+static int
+qnetd_client_msg_received_init_reply(struct qnetd_instance *instance, struct qnetd_client *client,
+	const struct msg_decoded *msg)
+{
+	qnetd_log(LOG_ERR, "Received init reply. Sending back error message");
+
+	if (qnetd_client_send_err(client, msg->seq_number_set, msg->seq_number,
+	    TLV_REPLY_ERROR_CODE_UNEXPECTED_MESSAGE) != 0) {
+		return (-1);
+	}
+
+	return (0);
+}
+
+static int
+qnetd_client_msg_received_set_option_reply(struct qnetd_instance *instance, struct qnetd_client *client,
+	const struct msg_decoded *msg)
+{
+	qnetd_log(LOG_ERR, "Received set option reply. Sending back error message");
+
+	if (qnetd_client_send_err(client, msg->seq_number_set, msg->seq_number,
+	    TLV_REPLY_ERROR_CODE_UNEXPECTED_MESSAGE) != 0) {
+		return (-1);
+	}
+
+	return (0);
+}
+
+static int
+qnetd_client_msg_received_set_option(struct qnetd_instance *instance, struct qnetd_client *client,
+	const struct msg_decoded *msg)
+{
+	int res;
+	size_t zi;
+
+	if ((res = qnetd_client_check_tls(instance, client, msg)) != 0) {
+		return (res == -1 ? -1 : 0);
+	}
+
+	if (!client->init_received) {
+		qnetd_log(LOG_ERR, "Received set option message before init message. Sending error reply.");
+
+		if (qnetd_client_send_err(client, msg->seq_number_set, msg->seq_number,
+		    TLV_REPLY_ERROR_CODE_INIT_REQUIRED) != 0) {
+			return (-1);
+		}
+
+		return (0);
+	}
+
+	if (msg->decision_algorithm_set) {
+		/*
+		 * Check if decision algorithm requested by client is supported
+		 */
+		res = 0;
+
+		for (zi = 0; zi < QNETD_STATIC_SUPPORTED_DECISION_ALGORITHMS_SIZE && !res; zi++) {
+			if (qnetd_static_supported_decision_algorithms[zi] == msg->decision_algorithm) {
+				res = 1;
+			}
+		}
+
+		if (!res) {
+			qnetd_log(LOG_ERR, "Client requested unsupported decision algorithm %u. Sending error reply.",
+			    msg->decision_algorithm);
+
+			if (qnetd_client_send_err(client, msg->seq_number_set, msg->seq_number,
+			    TLV_REPLY_ERROR_CODE_UNSUPPORTED_DECISION_ALGORITHM) != 0) {
+				return (-1);
+			}
+
+			return (0);
+		}
+
+		client->decision_algorithm = msg->decision_algorithm;
+	}
+
+	if (msg->heartbeat_interval_set) {
+		/*
+		 * Check if heartbeat interval is valid
+		 */
+		if (msg->heartbeat_interval != 0 && (msg->heartbeat_interval < QNETD_HEARTBEAT_INTERVAL_MIN ||
+		    msg->heartbeat_interval > QNETD_HEARTBEAT_INTERVAL_MAX)) {
+			qnetd_log(LOG_ERR, "Client requested invalid heartbeat interval %u. Sending error reply.",
+			    msg->heartbeat_interval);
+
+			if (qnetd_client_send_err(client, msg->seq_number_set, msg->seq_number,
+			    TLV_REPLY_ERROR_CODE_INVALID_HEARTBEAT_INTERVAL) != 0) {
+				return (-1);
+			}
+
+			return (0);
+		}
+
+		client->heartbeat_interval = msg->heartbeat_interval;
+	}
+
+	if (msg_create_set_option_reply(&client->send_buffer, msg->seq_number_set, msg->seq_number,
+	    client->decision_algorithm, client->heartbeat_interval) == -1) {
+		qnetd_log(LOG_ERR, "Can't alloc set option reply msg. Disconnecting client connection.");
+
+		return (-1);
+	}
+
+	if (qnetd_client_net_schedule_send(client) != 0) {
+		qnetd_log(LOG_ERR, "Can't schedule send of set option reply message. Disconnecting client connection.");
+
+		return (-1);
+	}
+
+	return (0);
+}
+
+static int
+qnetd_client_msg_received_echo_reply(struct qnetd_instance *instance, struct qnetd_client *client,
+	const struct msg_decoded *msg)
+{
+	qnetd_log(LOG_ERR, "Received echo reply. Sending back error message");
+
+	if (qnetd_client_send_err(client, msg->seq_number_set, msg->seq_number,
+	    TLV_REPLY_ERROR_CODE_UNEXPECTED_MESSAGE) != 0) {
+		return (-1);
+	}
+
+	return (0);
+}
+
+static int
+qnetd_client_msg_received_echo_request(struct qnetd_instance *instance, struct qnetd_client *client,
+	const struct msg_decoded *msg, const struct dynar *msg_orig)
+{
+	int res;
+
+	if ((res = qnetd_client_check_tls(instance, client, msg)) != 0) {
+		return (res == -1 ? -1 : 0);
+	}
+
+	if (!client->init_received) {
+		qnetd_log(LOG_ERR, "Received echo request before init message. Sending error reply.");
+
+		if (qnetd_client_send_err(client, msg->seq_number_set, msg->seq_number,
+		    TLV_REPLY_ERROR_CODE_INIT_REQUIRED) != 0) {
+			return (-1);
+		}
+
+		return (0);
+	}
+
+	if (msg_create_echo_reply(&client->send_buffer, msg_orig) == -1) {
+		qnetd_log(LOG_ERR, "Can't alloc echo reply msg. Disconnecting client connection.");
+
+		return (-1);
+	}
+
+	if (qnetd_client_net_schedule_send(client) != 0) {
+		qnetd_log(LOG_ERR, "Can't schedule send of echo reply message. Disconnecting client connection.");
+
+		return (-1);
+	}
+
+	return (0);
+}
+
+static int
+qnetd_client_msg_received(struct qnetd_instance *instance, struct qnetd_client *client)
+{
+	struct msg_decoded msg;
+	int res;
+	int ret_val;
+
+	msg_decoded_init(&msg);
+
+	res = msg_decode(&client->receive_buffer, &msg);
+	if (res != 0) {
+		/*
+		 * Error occurred. Send server error.
+		 */
+		qnetd_client_log_msg_decode_error(res);
+		qnetd_log(LOG_INFO, "Sending back error message");
+
+		if (qnetd_client_send_err(client, msg.seq_number_set, msg.seq_number,
+		    TLV_REPLY_ERROR_CODE_ERROR_DECODING_MSG) != 0) {
+			return (-1);
+		}
+
+		return (0);
+	}
+
+	ret_val = 0;
+
+	switch (msg.type) {
+	case MSG_TYPE_PREINIT:
+		ret_val = qnetd_client_msg_received_preinit(instance, client, &msg);
+		break;
+	case MSG_TYPE_PREINIT_REPLY:
+		ret_val = qnetd_client_msg_received_preinit_reply(instance, client, &msg);
+		break;
+	case MSG_TYPE_STARTTLS:
+		ret_val = qnetd_client_msg_received_starttls(instance, client, &msg);
+		break;
+	case MSG_TYPE_INIT:
+		ret_val = qnetd_client_msg_received_init(instance, client, &msg);
+		break;
+	case MSG_TYPE_INIT_REPLY:
+		ret_val = qnetd_client_msg_received_init_reply(instance, client, &msg);
+		break;
+	case MSG_TYPE_SERVER_ERROR:
+		ret_val = qnetd_client_msg_received_server_error(instance, client, &msg);
+		break;
+	case MSG_TYPE_SET_OPTION:
+		ret_val = qnetd_client_msg_received_set_option(instance, client, &msg);
+		break;
+	case MSG_TYPE_SET_OPTION_REPLY:
+		ret_val = qnetd_client_msg_received_set_option_reply(instance, client, &msg);
+		break;
+	case MSG_TYPE_ECHO_REQUEST:
+		ret_val = qnetd_client_msg_received_echo_request(instance, client, &msg, &client->receive_buffer);
+		break;
+	case MSG_TYPE_ECHO_REPLY:
+		ret_val = qnetd_client_msg_received_echo_reply(instance, client, &msg);
+		break;
+	default:
+		qnetd_log(LOG_ERR, "Unsupported message %u received from client. Sending back error message",
+		    msg.type);
+
+		if (qnetd_client_send_err(client, msg.seq_number_set, msg.seq_number,
+		    TLV_REPLY_ERROR_CODE_UNSUPPORTED_MESSAGE) != 0) {
+			ret_val = -1;
+		}
+
+		break;
+	}
+
+	msg_decoded_destroy(&msg);
+
+	return (ret_val);
+}
+
+static int
+qnetd_client_net_write_finished(struct qnetd_instance *instance, struct qnetd_client *client)
+{
+
+	/*
+	 * Callback is currently unused
+	 */
+
+	return (0);
+}
+
+static int
+qnetd_client_net_write(struct qnetd_instance *instance, struct qnetd_client *client)
+{
+	int res;
+
+	res = msgio_write(client->socket, &client->send_buffer, &client->msg_already_sent_bytes);
+
+	if (res == 1) {
+		client->sending_msg = 0;
+
+		if (qnetd_client_net_write_finished(instance, client) == -1) {
+			return (-1);
+		}
+	}
+
+	if (res == -1) {
+		qnetd_log_nss(LOG_CRIT, "PR_Send returned 0");
+
+		return (-1);
+	}
+
+	if (res == -2) {
+		qnetd_log_nss(LOG_ERR, "Unhandled error when sending message to client");
+
+		return (-1);
+	}
+
+	return (0);
+}
+
+
+/*
+ * -1 means end of connection (EOF) or some other unhandled error. 0 = success
+ */
+static int
+qnetd_client_net_read(struct qnetd_instance *instance, struct qnetd_client *client)
+{
+	int res;
+	int ret_val;
+	int orig_skipping_msg;
+
+	orig_skipping_msg = client->skipping_msg;
+
+	res = msgio_read(client->socket, &client->receive_buffer, &client->msg_already_received_bytes,
+	    &client->skipping_msg);
+
+	if (!orig_skipping_msg && client->skipping_msg) {
+		qnetd_log(LOG_DEBUG, "msgio_read set skipping_msg");
+	}
+
+	ret_val = 0;
+
+	switch (res) {
+	case 0:
+		/*
+		 * Partial read
+		 */
+		break;
+	case -1:
+		qnetd_log(LOG_DEBUG, "Client closed connection");
+		ret_val = -1;
+		break;
+	case -2:
+		qnetd_log_nss(LOG_ERR, "Unhandled error when reading from client. Disconnecting client");
+		ret_val = -1;
+		break;
+	case -3:
+		qnetd_log(LOG_ERR, "Can't store message header from client. Disconnecting client");
+		ret_val = -1;
+		break;
+	case -4:
+		qnetd_log(LOG_ERR, "Can't store message from client. Skipping message");
+		client->skipping_msg_reason = TLV_REPLY_ERROR_CODE_ERROR_DECODING_MSG;
+		break;
+	case -5:
+		qnetd_log(LOG_WARNING, "Client sent unsupported msg type %u. Skipping message",
+			    msg_get_type(&client->receive_buffer));
+		client->skipping_msg_reason = TLV_REPLY_ERROR_CODE_UNSUPPORTED_MESSAGE;
+		break;
+	case -6:
+		qnetd_log(LOG_WARNING,
+		    "Client wants to send too long message %u bytes. Skipping message",
+		    msg_get_len(&client->receive_buffer));
+		client->skipping_msg_reason = TLV_REPLY_ERROR_CODE_MESSAGE_TOO_LONG;
+		break;
+	case 1:
+		/*
+		 * Full message received / skipped
+		 */
+		if (!client->skipping_msg) {
+			if (qnetd_client_msg_received(instance, client) == -1) {
+				ret_val = -1;
+			}
+		} else {
+			if (qnetd_client_send_err(client, 0, 0, client->skipping_msg_reason) != 0) {
+				ret_val = -1;
+			}
+		}
+
+		client->skipping_msg = 0;
+		client->skipping_msg_reason = TLV_REPLY_ERROR_CODE_NO_ERROR;
+		client->msg_already_received_bytes = 0;
+		dynar_clean(&client->receive_buffer);
+		break;
+	default:
+		errx(1, "Unhandled msgio_read error %d\n", res);
+		break;
+	}
+
+	return (ret_val);
+}
+
+static int
+qnetd_client_accept(struct qnetd_instance *instance)
+{
+	PRNetAddr client_addr;
+	PRFileDesc *client_socket;
+	struct qnetd_client *client;
+
+        if ((client_socket = PR_Accept(instance->server.socket, &client_addr, PR_INTERVAL_NO_TIMEOUT)) == NULL) {
+		qnetd_log_nss(LOG_ERR, "Can't accept connection");
+		return (-1);
+	}
+
+	if (nss_sock_set_nonblocking(client_socket) != 0) {
+		qnetd_log_nss(LOG_ERR, "Can't set client socket to non blocking mode");
+		return (-1);
+	}
+
+	client = qnetd_clients_list_add(&instance->clients, client_socket, &client_addr,
+	    instance->max_client_receive_size, instance->max_client_send_size);
+	if (client == NULL) {
+		qnetd_log(LOG_ERR, "Can't add client to list");
+		return (-2);
+	}
+
+	return (0);
+}
+
+static void
+qnetd_client_disconnect(struct qnetd_instance *instance, struct qnetd_client *client)
+{
+
+	PR_Close(client->socket);
+	qnetd_clients_list_del(&instance->clients, client);
+}
+
+static int
+qnetd_poll(struct qnetd_instance *instance)
+{
+	struct qnetd_client *client;
+	struct qnetd_client *client_next;
+	PRPollDesc *pfds;
+	PRInt32 poll_res;
+	int i;
+	int client_disconnect;
+
+	client = NULL;
+	client_disconnect = 0;
+
+	pfds = qnetd_poll_array_create_from_clients_list(&instance->poll_array,
+	    &instance->clients, instance->server.socket, PR_POLL_READ);
+
+	if (pfds == NULL) {
+		return (-1);
+	}
+
+	if ((poll_res = PR_Poll(pfds, qnetd_poll_array_size(&instance->poll_array),
+	    PR_INTERVAL_NO_TIMEOUT)) > 0) {
+		/*
+		 * Walk thru pfds array and process events
+		 */
+		for (i = 0; i < qnetd_poll_array_size(&instance->poll_array); i++) {
+			/*
+			 * Also traverse clients list
+			 */
+			if (i > 0) {
+				if (i == 1) {
+					client = TAILQ_FIRST(&instance->clients);
+					client_next = TAILQ_NEXT(client, entries);
+				} else {
+					client = client_next;
+					client_next = TAILQ_NEXT(client, entries);
+				}
+			}
+
+			client_disconnect = 0;
+
+			if (!client_disconnect && pfds[i].out_flags & PR_POLL_READ) {
+				if (i == 0) {
+					qnetd_client_accept(instance);
+				} else {
+					if (qnetd_client_net_read(instance, client) == -1) {
+						client_disconnect = 1;
+					}
+				}
+			}
+
+			if (!client_disconnect && pfds[i].out_flags & PR_POLL_WRITE) {
+				if (i == 0) {
+					/*
+					 * Poll write on listen socket -> fatal error
+					 */
+					qnetd_log(LOG_CRIT, "POLL_WRITE on listening socket");
+
+					return (-1);
+				} else {
+					if (qnetd_client_net_write(instance, client) == -1) {
+						client_disconnect = 1;
+					}
+				}
+			}
+
+			if (!client_disconnect &&
+			    pfds[i].out_flags & (PR_POLL_ERR|PR_POLL_NVAL|PR_POLL_HUP|PR_POLL_EXCEPT)) {
+				if (i == 0) {
+					if (pfds[i].out_flags != PR_POLL_NVAL) {
+						/*
+						 * Poll ERR on listening socket is fatal error. POLL_NVAL is
+						 * used as a signal to quit poll loop.
+						 */
+						qnetd_log(LOG_CRIT, "POLL_ERR (%u) on listening socket", pfds[i].out_flags);
+					} else {
+						qnetd_log(LOG_DEBUG, "Listening socket is closed");
+					}
+
+					return (-1);
+
+				} else {
+					qnetd_log(LOG_DEBUG, "POLL_ERR (%u) on client socket. Disconnecting.",
+					    pfds[i].out_flags);
+
+					client_disconnect = 1;
+				}
+			}
+
+			/*
+			 * If client is scheduled for disconnect, disconnect it
+			 */
+			if (client_disconnect) {
+				qnetd_client_disconnect(instance, client);
+			}
+		}
+	}
+
+	return (0);
+}
+
+static int
+qnetd_instance_init_certs(struct qnetd_instance *instance)
+{
+
+	instance->server.cert = PK11_FindCertFromNickname(QNETD_CERT_NICKNAME, NULL);
+	if (instance->server.cert == NULL) {
+		return (-1);
+	}
+
+	instance->server.private_key = PK11_FindKeyByAnyCert(instance->server.cert, NULL);
+	if (instance->server.private_key == NULL) {
+		return (-1);
+	}
+
+	return (0);
+}
+
+static int
+qnetd_instance_init(struct qnetd_instance *instance, size_t max_client_receive_size,
+    size_t max_client_send_size, enum tlv_tls_supported tls_supported, int tls_client_cert_required)
+{
+
+	memset(instance, 0, sizeof(*instance));
+
+	qnetd_poll_array_init(&instance->poll_array);
+	qnetd_clients_list_init(&instance->clients);
+
+	instance->max_client_receive_size = max_client_receive_size;
+	instance->max_client_send_size = max_client_send_size;
+
+	instance->tls_supported = tls_supported;
+	instance->tls_client_cert_required = tls_client_cert_required;
+
+	return (0);
+}
+
+static int
+qnetd_instance_destroy(struct qnetd_instance *instance)
+{
+	struct qnetd_client *client;
+	struct qnetd_client *client_next;
+
+	client = TAILQ_FIRST(&instance->clients);
+	while (client != NULL) {
+		client_next = TAILQ_NEXT(client, entries);
+
+		qnetd_client_disconnect(instance, client);
+
+		client = client_next;
+	}
+
+	qnetd_poll_array_destroy(&instance->poll_array);
+	qnetd_clients_list_free(&instance->clients);
+
+	return (0);
+}
+
+static void
+signal_int_handler(int sig)
+{
+
+	qnetd_log(LOG_DEBUG, "SIGINT received - closing server socket");
+
+	PR_Close(global_server_socket);
+}
+
+static void
+signal_handlers_register(void)
+{
+	struct sigaction act;
+
+	act.sa_handler = signal_int_handler;
+	sigemptyset(&act.sa_mask);
+	act.sa_flags = SA_RESTART;
+
+	sigaction(SIGINT, &act, NULL);
+}
+
+int
+main(void)
+{
+	struct qnetd_instance instance;
+
+	/*
+	 * INIT
+	 */
+	qnetd_log_init(QNETD_LOG_TARGET_STDERR);
+	qnetd_log_set_debug(1);
+
+	if (nss_sock_init_nss((char *)NSS_DB_DIR) != 0) {
+		qnetd_err_nss();
+	}
+
+	if (SSL_ConfigServerSessionIDCache(0, 0, 0, NULL) != SECSuccess) {
+		qnetd_err_nss();
+	}
+
+	if (qnetd_instance_init(&instance, QNETD_MAX_CLIENT_RECEIVE_SIZE, QNETD_MAX_CLIENT_SEND_SIZE,
+	    QNETD_TLS_SUPPORTED, QNETD_TLS_CLIENT_CERT_REQUIRED) == -1) {
+		errx(1, "Can't initialize qnetd");
+	}
+
+	if (qnetd_instance_init_certs(&instance) == -1) {
+		qnetd_err_nss();
+	}
+
+	instance.server.socket = nss_sock_create_listen_socket(QNETD_HOST, QNETD_PORT, PR_AF_INET6);
+	if (instance.server.socket == NULL) {
+		qnetd_err_nss();
+	}
+
+	if (nss_sock_set_nonblocking(instance.server.socket) != 0) {
+		qnetd_err_nss();
+	}
+
+	if (PR_Listen(instance.server.socket, QNETD_LISTEN_BACKLOG) != PR_SUCCESS) {
+		qnetd_err_nss();
+	}
+
+	global_server_socket = instance.server.socket;
+	signal_handlers_register();
+
+	/*
+	 * MAIN LOOP
+	 */
+	while (qnetd_poll(&instance) == 0) {
+	}
+
+	/*
+	 * Cleanup
+	 */
+	CERT_DestroyCertificate(instance.server.cert);
+	SECKEY_DestroyPrivateKey(instance.server.private_key);
+
+	SSL_ClearSessionCache();
+
+	SSL_ShutdownServerSessionIDCache();
+
+	qnetd_instance_destroy(&instance);
+
+	if (NSS_Shutdown() != SECSuccess) {
+		qnetd_warn_nss();
+	}
+
+	if (PR_Cleanup() != PR_SUCCESS) {
+		qnetd_warn_nss();
+	}
+
+	qnetd_log_close();
+
+	return (0);
+}

+ 138 - 0
qdevices/dynar.c

@@ -0,0 +1,138 @@
+/*
+ * Copyright (c) 2015 Red Hat, Inc.
+ *
+ * All rights reserved.
+ *
+ * Author: Jan Friesse (jfriesse@redhat.com)
+ *
+ * This software licensed under BSD license, the text of which follows:
+ *
+ * Redistribution and use in source and binary forms, with or without
+ * modification, are permitted provided that the following conditions are met:
+ *
+ * - Redistributions of source code must retain the above copyright notice,
+ *   this list of conditions and the following disclaimer.
+ * - 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.
+ * - Neither the name of the Red Hat, Inc. nor the names of its
+ *   contributors may be used to endorse or promote products derived from this
+ *   software without specific prior written permission.
+ *
+ * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS"
+ * AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
+ * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
+ * ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR CONTRIBUTORS BE
+ * LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR
+ * CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF
+ * SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS
+ * INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN
+ * CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE)
+ * ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF
+ * THE POSSIBILITY OF SUCH DAMAGE.
+ */
+
+#include <sys/types.h>
+
+#include <stdlib.h>
+#include <string.h>
+
+#include "dynar.h"
+
+void
+dynar_init(struct dynar *array, size_t maximum_size)
+{
+
+	memset(array, 0, sizeof(*array));
+	array->maximum_size = maximum_size;
+}
+
+void
+dynar_set_max_size(struct dynar *array, size_t maximum_size)
+{
+
+	array->maximum_size = maximum_size;
+}
+
+void
+dynar_destroy(struct dynar *array)
+{
+
+	free(array->data);
+	dynar_init(array, array->maximum_size);
+}
+
+void
+dynar_clean(struct dynar *array)
+{
+
+	array->size = 0;
+}
+
+size_t
+dynar_size(const struct dynar *array)
+{
+
+	return (array->size);
+}
+
+size_t
+dynar_max_size(const struct dynar *array)
+{
+
+	return (array->maximum_size);
+}
+
+char *
+dynar_data(const struct dynar *array)
+{
+
+	return (array->data);
+}
+
+static int
+dynar_realloc(struct dynar *array, size_t new_array_size)
+{
+	char *new_data;
+
+	if (new_array_size > array->maximum_size) {
+		return (-1);
+	}
+
+	new_data = realloc(array->data, new_array_size);
+
+	if (new_data == NULL) {
+		return (-1);
+	}
+
+	array->allocated = new_array_size;
+	array->data = new_data;
+
+	return (0);
+}
+
+int
+dynar_cat(struct dynar *array, const void *src, size_t size)
+{
+	size_t new_size;
+
+	if (array->size + size > array->maximum_size) {
+		return (-1);
+	}
+
+	if (array->size + size > array->allocated) {
+		new_size = (array->allocated + size) * 2;
+		if (new_size > array->maximum_size) {
+			new_size = array->maximum_size;
+		}
+
+		if (dynar_realloc(array, new_size) == -1) {
+			return (-1);
+		}
+	}
+
+	memmove(array->data + array->size, src, size);
+	array->size += size;
+
+	return (0);
+}

+ 76 - 0
qdevices/dynar.h

@@ -0,0 +1,76 @@
+/*
+ * Copyright (c) 2015 Red Hat, Inc.
+ *
+ * All rights reserved.
+ *
+ * Author: Jan Friesse (jfriesse@redhat.com)
+ *
+ * This software licensed under BSD license, the text of which follows:
+ *
+ * Redistribution and use in source and binary forms, with or without
+ * modification, are permitted provided that the following conditions are met:
+ *
+ * - Redistributions of source code must retain the above copyright notice,
+ *   this list of conditions and the following disclaimer.
+ * - 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.
+ * - Neither the name of the Red Hat, Inc. nor the names of its
+ *   contributors may be used to endorse or promote products derived from this
+ *   software without specific prior written permission.
+ *
+ * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS"
+ * AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
+ * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
+ * ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR CONTRIBUTORS BE
+ * LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR
+ * CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF
+ * SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS
+ * INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN
+ * CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE)
+ * ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF
+ * THE POSSIBILITY OF SUCH DAMAGE.
+ */
+
+#ifndef _DYNAR_H_
+#define _DYNAR_H_
+
+#include <sys/types.h>
+#include <inttypes.h>
+
+#ifdef __cplusplus
+extern "C" {
+#endif
+
+/*
+ * Dynamic array structure
+ */
+struct dynar {
+	char *data;
+	size_t size;
+	size_t allocated;
+	size_t maximum_size;
+};
+
+extern void	 dynar_init(struct dynar *array, size_t maximum_size);
+
+extern void	 dynar_destroy(struct dynar *array);
+
+extern void	 dynar_clean(struct dynar *array);
+
+extern size_t	 dynar_size(const struct dynar *array);
+
+extern size_t	 dynar_max_size(const struct dynar *array);
+
+extern void	 dynar_set_max_size(struct dynar *array, size_t maximum_size);
+
+extern char	*dynar_data(const struct dynar *array);
+
+extern int	 dynar_cat(struct dynar *array, const void *src, size_t size);
+
+
+#ifdef __cplusplus
+}
+#endif
+
+#endif /* _DYNAR_H_ */

+ 691 - 0
qdevices/msg.c

@@ -0,0 +1,691 @@
+/*
+ * Copyright (c) 2015 Red Hat, Inc.
+ *
+ * All rights reserved.
+ *
+ * Author: Jan Friesse (jfriesse@redhat.com)
+ *
+ * This software licensed under BSD license, the text of which follows:
+ *
+ * Redistribution and use in source and binary forms, with or without
+ * modification, are permitted provided that the following conditions are met:
+ *
+ * - Redistributions of source code must retain the above copyright notice,
+ *   this list of conditions and the following disclaimer.
+ * - 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.
+ * - Neither the name of the Red Hat, Inc. nor the names of its
+ *   contributors may be used to endorse or promote products derived from this
+ *   software without specific prior written permission.
+ *
+ * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS"
+ * AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
+ * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
+ * ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR CONTRIBUTORS BE
+ * LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR
+ * CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF
+ * SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS
+ * INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN
+ * CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE)
+ * ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF
+ * THE POSSIBILITY OF SUCH DAMAGE.
+ */
+
+#include <sys/types.h>
+#include <arpa/inet.h>
+
+#include <inttypes.h>
+#include <stdlib.h>
+#include <string.h>
+
+#include "msg.h"
+
+#define MSG_TYPE_LENGTH		2
+#define MSG_LENGTH_LENGTH	4
+
+#define MSG_STATIC_SUPPORTED_MESSAGES_SIZE	10
+
+enum msg_type msg_static_supported_messages[MSG_STATIC_SUPPORTED_MESSAGES_SIZE] = {
+    MSG_TYPE_PREINIT,
+    MSG_TYPE_PREINIT_REPLY,
+    MSG_TYPE_STARTTLS,
+    MSG_TYPE_INIT,
+    MSG_TYPE_INIT_REPLY,
+    MSG_TYPE_SERVER_ERROR,
+    MSG_TYPE_SET_OPTION,
+    MSG_TYPE_SET_OPTION_REPLY,
+    MSG_TYPE_ECHO_REQUEST,
+    MSG_TYPE_ECHO_REPLY,
+};
+
+size_t
+msg_get_header_length(void)
+{
+	return (MSG_TYPE_LENGTH + MSG_LENGTH_LENGTH);
+}
+
+static void
+msg_add_type(struct dynar *msg, enum msg_type type)
+{
+	uint16_t ntype;
+
+	ntype = htons((uint16_t)type);
+	dynar_cat(msg, &ntype, sizeof(ntype));
+}
+
+enum msg_type
+msg_get_type(const struct dynar *msg)
+{
+	uint16_t ntype;
+	uint16_t type;
+
+	memcpy(&ntype, dynar_data(msg), sizeof(ntype));
+	type = ntohs(ntype);
+
+	return (type);
+}
+
+/*
+ * We don't know size of message before call of this function, so zero is
+ * added. Real value is set afterwards by msg_set_len.
+ */
+static void
+msg_add_len(struct dynar *msg)
+{
+	uint32_t len;
+
+	len = 0;
+	dynar_cat(msg, &len, sizeof(len));
+}
+
+static void
+msg_set_len(struct dynar *msg, uint32_t len)
+{
+	uint32_t nlen;
+
+	nlen = htonl(len);
+	memcpy(dynar_data(msg) + MSG_TYPE_LENGTH, &nlen, sizeof(nlen));
+}
+
+/*
+ * Used only for echo reply msg. All other messages should use msg_add_type.
+ */
+static void
+msg_set_type(struct dynar *msg, enum msg_type type)
+{
+	uint16_t ntype;
+
+	ntype = htons((uint16_t)type);
+	memcpy(dynar_data(msg), &ntype, sizeof(ntype));
+}
+
+uint32_t
+msg_get_len(const struct dynar *msg)
+{
+	uint32_t nlen;
+	uint32_t len;
+
+	memcpy(&nlen, dynar_data(msg) + MSG_TYPE_LENGTH, sizeof(nlen));
+	len = ntohl(nlen);
+
+	return (len);
+}
+
+
+size_t
+msg_create_preinit(struct dynar *msg, const char *cluster_name, int add_msg_seq_number, uint32_t msg_seq_number)
+{
+
+	dynar_clean(msg);
+
+	msg_add_type(msg, MSG_TYPE_PREINIT);
+	msg_add_len(msg);
+
+	if (add_msg_seq_number) {
+		if (tlv_add_msg_seq_number(msg, msg_seq_number) == -1) {
+			goto small_buf_err;
+		}
+	}
+
+	if (tlv_add_cluster_name(msg, cluster_name) == -1) {
+		goto small_buf_err;
+	}
+
+	msg_set_len(msg, dynar_size(msg) - (MSG_TYPE_LENGTH + MSG_LENGTH_LENGTH));
+
+	return (dynar_size(msg));
+
+small_buf_err:
+	return (0);
+}
+
+size_t
+msg_create_preinit_reply(struct dynar *msg, int add_msg_seq_number, uint32_t msg_seq_number,
+    enum tlv_tls_supported tls_supported, int tls_client_cert_required)
+{
+
+	dynar_clean(msg);
+
+	msg_add_type(msg, MSG_TYPE_PREINIT_REPLY);
+	msg_add_len(msg);
+
+	if (add_msg_seq_number) {
+		if (tlv_add_msg_seq_number(msg, msg_seq_number) == -1) {
+			goto small_buf_err;
+		}
+	}
+
+	if (tlv_add_tls_supported(msg, tls_supported) == -1) {
+		goto small_buf_err;
+	}
+
+	if (tlv_add_tls_client_cert_required(msg, tls_client_cert_required) == -1) {
+		goto small_buf_err;
+	}
+
+	msg_set_len(msg, dynar_size(msg) - (MSG_TYPE_LENGTH + MSG_LENGTH_LENGTH));
+
+	return (dynar_size(msg));
+
+small_buf_err:
+	return (0);
+}
+
+size_t
+msg_create_starttls(struct dynar *msg, int add_msg_seq_number, uint32_t msg_seq_number)
+{
+
+	dynar_clean(msg);
+
+	msg_add_type(msg, MSG_TYPE_STARTTLS);
+	msg_add_len(msg);
+
+	if (add_msg_seq_number) {
+		if (tlv_add_msg_seq_number(msg, msg_seq_number) == -1) {
+			goto small_buf_err;
+		}
+	}
+
+	msg_set_len(msg, dynar_size(msg) - (MSG_TYPE_LENGTH + MSG_LENGTH_LENGTH));
+
+	return (dynar_size(msg));
+
+small_buf_err:
+	return (0);
+}
+
+size_t
+msg_create_server_error(struct dynar *msg, int add_msg_seq_number, uint32_t msg_seq_number,
+    enum tlv_reply_error_code reply_error_code)
+{
+
+	dynar_clean(msg);
+
+	msg_add_type(msg, MSG_TYPE_SERVER_ERROR);
+	msg_add_len(msg);
+
+	if (add_msg_seq_number) {
+		if (tlv_add_msg_seq_number(msg, msg_seq_number) == -1) {
+			goto small_buf_err;
+		}
+	}
+
+	if (tlv_add_reply_error_code(msg, reply_error_code) == -1) {
+		goto small_buf_err;
+	}
+
+	msg_set_len(msg, dynar_size(msg) - (MSG_TYPE_LENGTH + MSG_LENGTH_LENGTH));
+
+	return (dynar_size(msg));
+
+small_buf_err:
+	return (0);
+}
+
+static uint16_t *
+msg_convert_msg_type_array_to_u16_array(const enum msg_type *msg_type_array, size_t array_size)
+{
+	uint16_t *u16a;
+	size_t i;
+
+	u16a = malloc(sizeof(*u16a) * array_size);
+	if (u16a == NULL) {
+		return (NULL);
+	}
+
+	for (i = 0; i < array_size; i++) {
+		u16a[i] = (uint16_t)msg_type_array[i];
+	}
+
+	return (u16a);
+}
+
+size_t
+msg_create_init(struct dynar *msg, int add_msg_seq_number, uint32_t msg_seq_number,
+    const enum msg_type *supported_msgs, size_t no_supported_msgs,
+    const enum tlv_opt_type *supported_opts, size_t no_supported_opts, uint32_t node_id)
+{
+	uint16_t *u16a;
+	int res;
+
+	u16a = NULL;
+
+	dynar_clean(msg);
+
+	msg_add_type(msg, MSG_TYPE_INIT);
+	msg_add_len(msg);
+
+	if (add_msg_seq_number) {
+		if (tlv_add_msg_seq_number(msg, msg_seq_number) == -1) {
+			goto small_buf_err;
+		}
+	}
+
+	if (supported_msgs != NULL && no_supported_msgs > 0) {
+		u16a = msg_convert_msg_type_array_to_u16_array(supported_msgs, no_supported_msgs);
+
+		if (u16a == NULL) {
+			goto small_buf_err;
+		}
+
+		res = tlv_add_u16_array(msg, TLV_OPT_SUPPORTED_MESSAGES, u16a, no_supported_msgs);
+
+		free(u16a);
+
+		if (res == -1) {
+			goto small_buf_err;
+		}
+	}
+
+	if (supported_opts != NULL && no_supported_opts > 0) {
+		if (tlv_add_supported_options(msg, supported_opts, no_supported_opts) == -1) {
+			goto small_buf_err;
+		}
+	}
+
+        if (tlv_add_node_id(msg, node_id) == -1) {
+		goto small_buf_err;
+        }
+
+	msg_set_len(msg, dynar_size(msg) - (MSG_TYPE_LENGTH + MSG_LENGTH_LENGTH));
+
+	return (dynar_size(msg));
+
+small_buf_err:
+	return (0);
+}
+
+size_t
+msg_create_init_reply(struct dynar *msg, int add_msg_seq_number, uint32_t msg_seq_number,
+    const enum msg_type *supported_msgs, size_t no_supported_msgs,
+    const enum tlv_opt_type *supported_opts, size_t no_supported_opts,
+    size_t server_maximum_request_size, size_t server_maximum_reply_size,
+    const enum tlv_decision_algorithm_type *supported_decision_algorithms, size_t no_supported_decision_algorithms)
+{
+	uint16_t *u16a;
+	int res;
+
+	u16a = NULL;
+
+	dynar_clean(msg);
+
+	msg_add_type(msg, MSG_TYPE_INIT_REPLY);
+	msg_add_len(msg);
+
+	if (supported_msgs != NULL && no_supported_msgs > 0) {
+		u16a = msg_convert_msg_type_array_to_u16_array(supported_msgs, no_supported_msgs);
+
+		if (u16a == NULL) {
+			goto small_buf_err;
+		}
+
+		res = tlv_add_u16_array(msg, TLV_OPT_SUPPORTED_MESSAGES, u16a, no_supported_msgs);
+
+		free(u16a);
+
+		if (res == -1) {
+			goto small_buf_err;
+		}
+	}
+
+	if (supported_opts != NULL && no_supported_opts > 0) {
+		if (tlv_add_supported_options(msg, supported_opts, no_supported_opts) == -1) {
+			goto small_buf_err;
+		}
+	}
+
+	if (add_msg_seq_number) {
+		if (tlv_add_msg_seq_number(msg, msg_seq_number) == -1) {
+			goto small_buf_err;
+		}
+	}
+
+	if (tlv_add_server_maximum_request_size(msg, server_maximum_request_size) == -1) {
+		goto small_buf_err;
+	}
+
+	if (tlv_add_server_maximum_reply_size(msg, server_maximum_reply_size) == -1) {
+		goto small_buf_err;
+	}
+
+	if (supported_decision_algorithms != NULL && no_supported_decision_algorithms > 0) {
+		if (tlv_add_supported_decision_algorithms(msg, supported_decision_algorithms,
+		    no_supported_decision_algorithms) == -1) {
+			goto small_buf_err;
+		}
+	}
+
+	msg_set_len(msg, dynar_size(msg) - (MSG_TYPE_LENGTH + MSG_LENGTH_LENGTH));
+
+	return (dynar_size(msg));
+
+small_buf_err:
+	return (0);
+}
+
+size_t
+msg_create_set_option(struct dynar *msg, int add_msg_seq_number, uint32_t msg_seq_number,
+    int add_decision_algorithm, enum tlv_decision_algorithm_type decision_algorithm,
+    int add_heartbeat_interval, uint32_t heartbeat_interval)
+{
+
+	dynar_clean(msg);
+
+	msg_add_type(msg, MSG_TYPE_SET_OPTION);
+	msg_add_len(msg);
+
+	if (add_msg_seq_number) {
+		if (tlv_add_msg_seq_number(msg, msg_seq_number) == -1) {
+			goto small_buf_err;
+		}
+	}
+
+	if (add_decision_algorithm) {
+		if (tlv_add_decision_algorithm(msg, decision_algorithm) == -1) {
+			goto small_buf_err;
+		}
+	}
+
+	if (add_heartbeat_interval) {
+		if (tlv_add_heartbeat_interval(msg, heartbeat_interval) == -1) {
+			goto small_buf_err;
+		}
+	}
+
+	msg_set_len(msg, dynar_size(msg) - (MSG_TYPE_LENGTH + MSG_LENGTH_LENGTH));
+
+	return (dynar_size(msg));
+
+small_buf_err:
+	return (0);
+}
+
+size_t
+msg_create_set_option_reply(struct dynar *msg, int add_msg_seq_number, uint32_t msg_seq_number,
+    enum tlv_decision_algorithm_type decision_algorithm, uint32_t heartbeat_interval)
+{
+
+	dynar_clean(msg);
+
+	msg_add_type(msg, MSG_TYPE_SET_OPTION_REPLY);
+	msg_add_len(msg);
+
+	if (add_msg_seq_number) {
+		if (tlv_add_msg_seq_number(msg, msg_seq_number) == -1) {
+			goto small_buf_err;
+		}
+	}
+
+	if (tlv_add_decision_algorithm(msg, decision_algorithm) == -1) {
+		goto small_buf_err;
+	}
+
+	if (tlv_add_heartbeat_interval(msg, heartbeat_interval) == -1) {
+		goto small_buf_err;
+	}
+
+	msg_set_len(msg, dynar_size(msg) - (MSG_TYPE_LENGTH + MSG_LENGTH_LENGTH));
+
+	return (dynar_size(msg));
+
+small_buf_err:
+	return (0);
+}
+
+size_t
+msg_create_echo_request(struct dynar *msg, int add_msg_seq_number, uint32_t msg_seq_number)
+{
+
+	dynar_clean(msg);
+
+	msg_add_type(msg, MSG_TYPE_ECHO_REQUEST);
+	msg_add_len(msg);
+
+	if (add_msg_seq_number) {
+		if (tlv_add_msg_seq_number(msg, msg_seq_number) == -1) {
+			goto small_buf_err;
+		}
+	}
+
+	msg_set_len(msg, dynar_size(msg) - (MSG_TYPE_LENGTH + MSG_LENGTH_LENGTH));
+
+	return (dynar_size(msg));
+
+small_buf_err:
+	return (0);
+}
+
+size_t
+msg_create_echo_reply(struct dynar *msg, const struct dynar *echo_request_msg)
+{
+
+	dynar_clean(msg);
+
+	if (dynar_cat(msg, dynar_data(echo_request_msg), dynar_size(echo_request_msg)) == -1) {
+		goto small_buf_err;
+	}
+
+	msg_set_type(msg, MSG_TYPE_ECHO_REPLY);
+
+	return (dynar_size(msg));
+
+small_buf_err:
+	return (0);
+}
+
+int
+msg_is_valid_msg_type(const struct dynar *msg)
+{
+	enum msg_type type;
+	size_t i;
+
+	type = msg_get_type(msg);
+
+	for (i = 0; i < MSG_STATIC_SUPPORTED_MESSAGES_SIZE; i++) {
+		if (msg_static_supported_messages[i] == type) {
+			return (1);
+		}
+	}
+
+	return (0);
+}
+
+void
+msg_decoded_init(struct msg_decoded *decoded_msg)
+{
+
+	memset(decoded_msg, 0, sizeof(*decoded_msg));
+}
+
+void
+msg_decoded_destroy(struct msg_decoded *decoded_msg)
+{
+
+	free(decoded_msg->cluster_name);
+	free(decoded_msg->supported_messages);
+	free(decoded_msg->supported_options);
+
+	msg_decoded_init(decoded_msg);
+}
+
+/*
+ *  0 - No error
+ * -1 - option with invalid length
+ * -2 - Unable to allocate memory
+ * -3 - Inconsistent msg (tlv len > msg size)
+ * -4 - invalid option content
+ */
+int
+msg_decode(const struct dynar *msg, struct msg_decoded *decoded_msg)
+{
+	struct tlv_iterator tlv_iter;
+	uint16_t *u16a;
+	uint32_t u32;
+	size_t zi;
+	enum tlv_opt_type opt_type;
+	int iter_res;
+	int res;
+
+	msg_decoded_destroy(decoded_msg);
+
+	decoded_msg->type = msg_get_type(msg);
+
+	tlv_iter_init(msg, msg_get_header_length(), &tlv_iter);
+
+	while ((iter_res = tlv_iter_next(&tlv_iter)) > 0) {
+		opt_type = tlv_iter_get_type(&tlv_iter);
+
+		switch (opt_type) {
+		case TLV_OPT_MSG_SEQ_NUMBER:
+			if (tlv_iter_decode_u32(&tlv_iter, &decoded_msg->seq_number) != 0) {
+				return (-1);
+			}
+
+			decoded_msg->seq_number_set = 1;
+			break;
+		case TLV_OPT_CLUSTER_NAME:
+			if (tlv_iter_decode_str(&tlv_iter, &decoded_msg->cluster_name,
+			    &decoded_msg->cluster_name_len) != 0) {
+				return (-2);
+			}
+			break;
+		case TLV_OPT_TLS_SUPPORTED:
+			if ((res = tlv_iter_decode_tls_supported(&tlv_iter, &decoded_msg->tls_supported)) != 0) {
+				return (res);
+			}
+
+			decoded_msg->tls_supported_set = 1;
+			break;
+		case TLV_OPT_TLS_CLIENT_CERT_REQUIRED:
+			if (tlv_iter_decode_client_cert_required(&tlv_iter, &decoded_msg->tls_client_cert_required) != 0) {
+				return (-1);
+			}
+
+			decoded_msg->tls_client_cert_required_set = 1;
+			break;
+		case TLV_OPT_SUPPORTED_MESSAGES:
+			free(decoded_msg->supported_messages);
+
+			if ((res = tlv_iter_decode_u16_array(&tlv_iter, &u16a,
+			    &decoded_msg->no_supported_messages)) != 0) {
+				return (res);
+			}
+
+			decoded_msg->supported_messages = malloc(sizeof(enum msg_type) * decoded_msg->no_supported_messages);
+			if (decoded_msg->supported_messages == NULL) {
+				free(u16a);
+				return (-2);
+			}
+
+			for (zi = 0; zi < decoded_msg->no_supported_messages; zi++) {
+				decoded_msg->supported_messages[zi] = (enum msg_type)u16a[zi];
+			}
+
+			free(u16a);
+			break;
+		case TLV_OPT_SUPPORTED_OPTIONS:
+			free(decoded_msg->supported_options);
+
+			if ((res = tlv_iter_decode_supported_options(&tlv_iter, &decoded_msg->supported_options,
+			    &decoded_msg->no_supported_options)) != 0) {
+				return (res);
+			}
+			break;
+		case TLV_OPT_REPLY_ERROR_CODE:
+			if (tlv_iter_decode_reply_error_code(&tlv_iter, &decoded_msg->reply_error_code) != 0) {
+				return (-1);
+			}
+
+			decoded_msg->reply_error_code_set = 1;
+			break;
+		case TLV_OPT_SERVER_MAXIMUM_REQUEST_SIZE:
+			if (tlv_iter_decode_u32(&tlv_iter, &u32) != 0) {
+				return (-1);
+			}
+
+			decoded_msg->server_maximum_request_size_set = 1;
+			decoded_msg->server_maximum_request_size = u32;
+			break;
+		case TLV_OPT_SERVER_MAXIMUM_REPLY_SIZE:
+			if (tlv_iter_decode_u32(&tlv_iter, &u32) != 0) {
+				return (-1);
+			}
+
+			decoded_msg->server_maximum_reply_size_set = 1;
+			decoded_msg->server_maximum_reply_size = u32;
+			break;
+		case TLV_OPT_NODE_ID:
+			if (tlv_iter_decode_u32(&tlv_iter, &u32) != 0) {
+				return (-1);
+			}
+
+			decoded_msg->node_id_set = 1;
+			decoded_msg->node_id = u32;
+			break;
+		case TLV_OPT_SUPPORTED_DECISION_ALGORITHMS:
+			free(decoded_msg->supported_decision_algorithms);
+
+			if ((res = tlv_iter_decode_supported_decision_algorithms(&tlv_iter,
+			    &decoded_msg->supported_decision_algorithms,
+			    &decoded_msg->no_supported_decision_algorithms)) != 0) {
+				return (res);
+			}
+			break;
+		case TLV_OPT_DECISION_ALGORITHM:
+			if (tlv_iter_decode_decision_algorithm(&tlv_iter, &decoded_msg->decision_algorithm) != 0) {
+				return (-1);
+			}
+
+			decoded_msg->decision_algorithm_set = 1;
+			break;
+		case TLV_OPT_HEARTBEAT_INTERVAL:
+			if (tlv_iter_decode_u32(&tlv_iter, &u32) != 0) {
+				return (-1);
+			}
+
+			decoded_msg->heartbeat_interval_set = 1;
+			decoded_msg->heartbeat_interval = u32;
+			break;
+		default:
+			/*
+			 * Unknown option
+			 */
+			break;
+		}
+	}
+
+	if (iter_res != 0) {
+		return (-3);
+	}
+
+	return (0);
+}
+
+void
+msg_get_supported_messages(enum msg_type **supported_messages, size_t *no_supported_messages)
+{
+
+	*supported_messages = msg_static_supported_messages;
+	*no_supported_messages = MSG_STATIC_SUPPORTED_MESSAGES_SIZE;
+}

+ 147 - 0
qdevices/msg.h

@@ -0,0 +1,147 @@
+/*
+ * Copyright (c) 2015 Red Hat, Inc.
+ *
+ * All rights reserved.
+ *
+ * Author: Jan Friesse (jfriesse@redhat.com)
+ *
+ * This software licensed under BSD license, the text of which follows:
+ *
+ * Redistribution and use in source and binary forms, with or without
+ * modification, are permitted provided that the following conditions are met:
+ *
+ * - Redistributions of source code must retain the above copyright notice,
+ *   this list of conditions and the following disclaimer.
+ * - 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.
+ * - Neither the name of the Red Hat, Inc. nor the names of its
+ *   contributors may be used to endorse or promote products derived from this
+ *   software without specific prior written permission.
+ *
+ * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS"
+ * AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
+ * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
+ * ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR CONTRIBUTORS BE
+ * LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR
+ * CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF
+ * SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS
+ * INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN
+ * CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE)
+ * ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF
+ * THE POSSIBILITY OF SUCH DAMAGE.
+ */
+
+#ifndef _MSG_H_
+#define _MSG_H_
+
+#include <sys/types.h>
+#include <inttypes.h>
+
+#include "dynar.h"
+#include "tlv.h"
+
+#ifdef __cplusplus
+extern "C" {
+#endif
+
+enum msg_type {
+	MSG_TYPE_PREINIT = 0,
+	MSG_TYPE_PREINIT_REPLY = 1,
+	MSG_TYPE_STARTTLS = 2,
+	MSG_TYPE_INIT = 3,
+	MSG_TYPE_INIT_REPLY = 4,
+	MSG_TYPE_SERVER_ERROR = 5,
+	MSG_TYPE_SET_OPTION = 6,
+	MSG_TYPE_SET_OPTION_REPLY = 7,
+	MSG_TYPE_ECHO_REQUEST = 8,
+	MSG_TYPE_ECHO_REPLY = 9,
+};
+
+struct msg_decoded {
+	enum msg_type type;
+	uint8_t seq_number_set;
+	uint32_t seq_number;		// Only valid if seq_number_set != 0
+	size_t cluster_name_len;
+	char *cluster_name;		// Valid only if != NULL. Trailing \0 is added but not counted in cluster_name_len
+	uint8_t tls_supported_set;
+	enum tlv_tls_supported tls_supported;	// Valid only if tls_supported_set != 0.
+	uint8_t tls_client_cert_required_set;
+	uint8_t tls_client_cert_required;		// Valid only if tls_client_cert_required_set != 0
+	size_t no_supported_messages;
+	enum msg_type *supported_messages;	// Valid only if != NULL
+	size_t no_supported_options;
+	enum tlv_opt_type *supported_options;	// Valid only if != NULL
+	uint8_t reply_error_code_set;
+	enum tlv_reply_error_code reply_error_code;	// Valid only if reply_error_code_set != 0
+	uint8_t server_maximum_request_size_set;
+	size_t server_maximum_request_size;		// Valid only if server_maximum_request_size_set != 0
+	uint8_t server_maximum_reply_size_set;
+	size_t server_maximum_reply_size;		// Valid only if server_maximum_reply_size_set != 0
+	uint8_t node_id_set;
+	uint32_t node_id;
+	size_t no_supported_decision_algorithms;
+	enum tlv_decision_algorithm_type *supported_decision_algorithms;	// Valid only if != NULL
+	uint8_t decision_algorithm_set;
+	enum tlv_decision_algorithm_type decision_algorithm;		// Valid only if decision_algorithm_set != 0
+	uint8_t heartbeat_interval_set;
+	uint32_t heartbeat_interval;					// Valid only if heartbeat_interval_set != 0
+};
+
+extern size_t		msg_create_preinit(struct dynar *msg, const char *cluster_name,
+    int add_msg_seq_number, uint32_t msg_seq_number);
+
+extern size_t		msg_create_preinit_reply(struct dynar *msg, int add_msg_seq_number,
+    uint32_t msg_seq_number, enum tlv_tls_supported tls_supported, int tls_client_cert_required);
+
+extern size_t		msg_create_starttls(struct dynar *msg, int add_msg_seq_number,
+    uint32_t msg_seq_number);
+
+extern size_t		msg_create_init(struct dynar *msg, int add_msg_seq_number, uint32_t msg_seq_number,
+    const enum msg_type *supported_msgs, size_t no_supported_msgs,
+    const enum tlv_opt_type *supported_opts, size_t no_supported_opts, uint32_t node_id);
+
+extern size_t		msg_create_server_error(struct dynar *msg, int add_msg_seq_number, uint32_t msg_seq_number,
+    enum tlv_reply_error_code reply_error_code);
+
+extern size_t		msg_create_init_reply(struct dynar *msg, int add_msg_seq_number, uint32_t msg_seq_number,
+    const enum msg_type *supported_msgs, size_t no_supported_msgs,
+    const enum tlv_opt_type *supported_opts, size_t no_supported_opts,
+    size_t server_maximum_request_size, size_t server_maximum_reply_size,
+    const enum tlv_decision_algorithm_type *supported_decision_algorithms, size_t no_supported_decision_algorithms);
+
+extern size_t		msg_create_set_option(struct dynar *msg,
+    int add_msg_seq_number, uint32_t msg_seq_number,
+    int add_decision_algorithm, enum tlv_decision_algorithm_type decision_algorithm,
+    int add_heartbeat_interval, uint32_t heartbeat_interval);
+
+extern size_t		msg_create_set_option_reply(struct dynar *msg,
+    int add_msg_seq_number, uint32_t msg_seq_number,
+    enum tlv_decision_algorithm_type decision_algorithm, uint32_t heartbeat_interval);
+
+extern size_t		msg_create_echo_request(struct dynar *msg, int add_msg_seq_number, uint32_t msg_seq_number);
+
+extern size_t		msg_create_echo_reply(struct dynar *msg, const struct dynar *echo_request_msg);
+
+extern size_t		msg_get_header_length(void);
+
+extern uint32_t		msg_get_len(const struct dynar *msg);
+
+extern enum msg_type	msg_get_type(const struct dynar *msg);
+
+extern int		msg_is_valid_msg_type(const struct dynar *msg);
+
+extern void		msg_decoded_init(struct msg_decoded *decoded_msg);
+
+extern void		msg_decoded_destroy(struct msg_decoded *decoded_msg);
+
+extern int		msg_decode(const struct dynar *msg, struct msg_decoded *decoded_msg);
+
+extern void		msg_get_supported_messages(enum msg_type **supported_messages,
+    size_t *no_supported_messages);
+
+#ifdef __cplusplus
+}
+#endif
+
+#endif /* _MSG_H_ */

+ 211 - 0
qdevices/msgio.c

@@ -0,0 +1,211 @@
+/*
+ * Copyright (c) 2015 Red Hat, Inc.
+ *
+ * All rights reserved.
+ *
+ * Author: Jan Friesse (jfriesse@redhat.com)
+ *
+ * This software licensed under BSD license, the text of which follows:
+ *
+ * Redistribution and use in source and binary forms, with or without
+ * modification, are permitted provided that the following conditions are met:
+ *
+ * - Redistributions of source code must retain the above copyright notice,
+ *   this list of conditions and the following disclaimer.
+ * - 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.
+ * - Neither the name of the Red Hat, Inc. nor the names of its
+ *   contributors may be used to endorse or promote products derived from this
+ *   software without specific prior written permission.
+ *
+ * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS"
+ * AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
+ * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
+ * ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR CONTRIBUTORS BE
+ * LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR
+ * CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF
+ * SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS
+ * INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN
+ * CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE)
+ * ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF
+ * THE POSSIBILITY OF SUCH DAMAGE.
+ */
+
+#include "msgio.h"
+#include "msg.h"
+
+#define MSGIO_LOCAL_BUF_SIZE			(1 << 10)
+
+ssize_t
+msgio_send(PRFileDesc *sock, const char *msg, size_t msg_len, size_t *start_pos)
+{
+	ssize_t sent_bytes;
+
+	if ((sent_bytes = PR_Send(sock, msg + *start_pos,
+	    msg_len - *start_pos, 0, PR_INTERVAL_NO_TIMEOUT)) != -1) {
+		*start_pos += sent_bytes;
+	}
+
+	return (sent_bytes);
+}
+
+ssize_t
+msgio_send_blocking(PRFileDesc *sock, const char *msg, size_t msg_len)
+{
+	PRPollDesc pfd;
+	size_t already_sent_bytes;
+	PRInt32 res;
+	ssize_t ret;
+
+	already_sent_bytes = 0;
+	ret = 0;
+
+	while (ret != -1 && already_sent_bytes < msg_len) {
+		pfd.fd = sock;
+		pfd.in_flags = PR_POLL_WRITE;
+		pfd.out_flags = 0;
+
+		if ((res = PR_Poll(&pfd, 1, PR_INTERVAL_NO_TIMEOUT)) > 0) {
+			if (pfd.out_flags & PR_POLL_WRITE) {
+				if ((msgio_send(sock, msg, msg_len, &already_sent_bytes) == -1) &&
+				    PR_GetError() != PR_WOULD_BLOCK_ERROR) {
+					ret = -1;
+				} else {
+					ret = already_sent_bytes;
+				}
+			} else if (pfd.out_flags & (PR_POLL_ERR | PR_POLL_NVAL | PR_POLL_HUP)) {
+				PR_SetError(PR_IO_ERROR, 0);
+				ret = -1;
+			}
+		} else {
+			ret = -1;
+		}
+	}
+
+	return (ret);
+}
+
+/*
+ * -1 means send returned 0, -2  unhandled error. 0 = success but whole buffer is still not sent, 1 = all data was sent
+ */
+int
+msgio_write(PRFileDesc *sock, const struct dynar *msg, size_t *already_sent_bytes)
+{
+	PRInt32 sent;
+	PRInt32 to_send;
+
+	to_send = dynar_size(msg) - *already_sent_bytes;
+	if (to_send > MSGIO_LOCAL_BUF_SIZE) {
+		to_send = MSGIO_LOCAL_BUF_SIZE;
+	}
+
+	sent = PR_Send(sock, dynar_data(msg) + *already_sent_bytes, to_send, 0, PR_INTERVAL_NO_TIMEOUT);
+
+	if (sent > 0) {
+		*already_sent_bytes += sent;
+
+		if (*already_sent_bytes == dynar_size(msg)) {
+			/*
+			 * All data sent
+			 */
+			return (1);
+		}
+	}
+
+	if (sent == 0) {
+		return (-1);
+	}
+
+	if (sent < 0 && PR_GetError() != PR_WOULD_BLOCK_ERROR) {
+		return (-2);
+	}
+
+	return (0);
+}
+
+/*
+ * -1 End of connection
+ * -2 Unhandled error
+ * -3 Fatal error. Unable to store message header
+ * -4 Unable to store message
+ * -5 Invalid msg type
+ * -6 Msg too long
+ */
+int
+msgio_read(PRFileDesc *sock, struct dynar *msg, size_t *already_received_bytes, int *skipping_msg)
+{
+	char local_read_buffer[MSGIO_LOCAL_BUF_SIZE];
+	PRInt32 readed;
+	PRInt32 to_read;
+	int ret;
+
+	ret = 0;
+
+	if (*already_received_bytes < msg_get_header_length()) {
+		/*
+		 * Complete reading of header
+		 */
+		to_read = msg_get_header_length() - *already_received_bytes;
+	} else {
+		/*
+		 * Read rest of message (or at least as much as possible)
+		 */
+		to_read = (msg_get_header_length() + msg_get_len(msg)) - *already_received_bytes;
+	}
+
+	if (to_read > MSGIO_LOCAL_BUF_SIZE) {
+		to_read = MSGIO_LOCAL_BUF_SIZE;
+	}
+
+	readed = PR_Recv(sock, local_read_buffer, to_read, 0, PR_INTERVAL_NO_TIMEOUT);
+	if (readed > 0) {
+		*already_received_bytes += readed;
+
+		if (!*skipping_msg) {
+			if (dynar_cat(msg, local_read_buffer, readed) == -1) {
+				*skipping_msg = 1;
+				ret = -4;
+			}
+		}
+
+		if (*skipping_msg && *already_received_bytes < msg_get_header_length()) {
+			/*
+			 * Fatal error. We were unable to store even message header
+			 */
+			return (-3);
+		}
+
+		if (!*skipping_msg && *already_received_bytes == msg_get_header_length()) {
+			/*
+			 * Full header received. Check type, maximum size, ...
+			 */
+			if (!msg_is_valid_msg_type(msg)) {
+				*skipping_msg = 1;
+				ret = -5;
+			} else if (msg_get_header_length() + msg_get_len(msg) > dynar_max_size(msg)) {
+				*skipping_msg = 1;
+				ret = -6;
+			}
+		}
+
+		if (*already_received_bytes >= msg_get_header_length() &&
+		    *already_received_bytes == (msg_get_header_length() + msg_get_len(msg))) {
+			/*
+			 * Full message skipped or received
+			 */
+			ret = 1;
+		}
+
+	}
+
+	if (readed == 0) {
+		return (-1);
+	}
+
+	if (readed < 0 && PR_GetError() != PR_WOULD_BLOCK_ERROR) {
+		return (-2);
+	}
+
+	return (ret);
+}

+ 59 - 0
qdevices/msgio.h

@@ -0,0 +1,59 @@
+/*
+ * Copyright (c) 2015 Red Hat, Inc.
+ *
+ * All rights reserved.
+ *
+ * Author: Jan Friesse (jfriesse@redhat.com)
+ *
+ * This software licensed under BSD license, the text of which follows:
+ *
+ * Redistribution and use in source and binary forms, with or without
+ * modification, are permitted provided that the following conditions are met:
+ *
+ * - Redistributions of source code must retain the above copyright notice,
+ *   this list of conditions and the following disclaimer.
+ * - 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.
+ * - Neither the name of the Red Hat, Inc. nor the names of its
+ *   contributors may be used to endorse or promote products derived from this
+ *   software without specific prior written permission.
+ *
+ * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS"
+ * AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
+ * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
+ * ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR CONTRIBUTORS BE
+ * LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR
+ * CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF
+ * SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS
+ * INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN
+ * CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE)
+ * ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF
+ * THE POSSIBILITY OF SUCH DAMAGE.
+ */
+
+#ifndef _MSGIO_H_
+#define _MSGIO_H_
+
+#include <nspr.h>
+
+#include "dynar.h"
+
+#ifdef __cplusplus
+extern "C" {
+#endif
+
+extern ssize_t	msgio_send(PRFileDesc *sock, const char *msg, size_t msg_len,
+    size_t *start_pos);
+
+extern ssize_t	msgio_send_blocking(PRFileDesc *sock, const char *msg, size_t msg_len);
+
+extern int	msgio_write(PRFileDesc *sock, const struct dynar *msg, size_t *already_sent_bytes);
+
+extern int	msgio_read(PRFileDesc *sock, struct dynar *msg, size_t *already_received_bytes, int *skipping_msg);
+
+#ifdef __cplusplus
+}
+#endif
+
+#endif /* _MSGIO_H_ */

+ 331 - 0
qdevices/nss-sock.c

@@ -0,0 +1,331 @@
+/*
+ * Copyright (c) 2015 Red Hat, Inc.
+ *
+ * All rights reserved.
+ *
+ * Author: Jan Friesse (jfriesse@redhat.com)
+ *
+ * This software licensed under BSD license, the text of which follows:
+ *
+ * Redistribution and use in source and binary forms, with or without
+ * modification, are permitted provided that the following conditions are met:
+ *
+ * - Redistributions of source code must retain the above copyright notice,
+ *   this list of conditions and the following disclaimer.
+ * - 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.
+ * - Neither the name of the Red Hat, Inc. nor the names of its
+ *   contributors may be used to endorse or promote products derived from this
+ *   software without specific prior written permission.
+ *
+ * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS"
+ * AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
+ * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
+ * ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR CONTRIBUTORS BE
+ * LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR
+ * CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF
+ * SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS
+ * INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN
+ * CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE)
+ * ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF
+ * THE POSSIBILITY OF SUCH DAMAGE.
+ */
+
+#include <prnetdb.h>
+
+#include "nss-sock.h"
+
+int
+nss_sock_init_nss(char *config_dir)
+{
+	if (NSS_Init(config_dir) != SECSuccess) {
+		return (-1);
+	}
+
+	if (NSS_SetDomesticPolicy() != SECSuccess) {
+		return (-1);
+	}
+
+	return (0);
+}
+
+/*
+ * Set NSS socket non-blocking
+ */
+int
+nss_sock_set_nonblocking(PRFileDesc *sock)
+{
+	PRSocketOptionData sock_opt;
+
+	memset(&sock_opt, 0, sizeof(sock_opt));
+	sock_opt.option = PR_SockOpt_Nonblocking;
+	sock_opt.value.non_blocking = PR_TRUE;
+	if (PR_SetSocketOption(sock, &sock_opt) != PR_SUCCESS) {
+		return (-1);
+	}
+
+	return (0);
+}
+
+/*
+ * Create TCP socket with af family. If reuse_addr is set, socket option
+ * for reuse address is set.
+ */
+static PRFileDesc *
+nss_sock_create_socket(PRIntn af, int reuse_addr)
+{
+	PRFileDesc *sock;
+	PRSocketOptionData socket_option;
+
+	sock = PR_OpenTCPSocket(af);
+	if (sock == NULL) {
+		return (NULL);
+	}
+
+	if (reuse_addr) {
+		socket_option.option = PR_SockOpt_Reuseaddr;
+		socket_option.value.reuse_addr = PR_TRUE;
+		if (PR_SetSocketOption(sock, &socket_option) != PR_SUCCESS) {
+			return (NULL);
+	         }
+	}
+
+	return (sock);
+}
+
+/*
+ * Create listen socket and bind it to address. hostname can be NULL and then
+ * any address is used. Address family (af) can be ether PR_AF_INET6 or
+ * PR_AF_INET.
+ */
+PRFileDesc *
+nss_sock_create_listen_socket(const char *hostname, uint16_t port, PRIntn af)
+{
+	PRNetAddr addr;
+	PRFileDesc *sock;
+	PRAddrInfo *addr_info;
+	PRIntn tmp_af;
+	void *addr_iter;
+
+	sock = NULL;
+
+	if (hostname == NULL) {
+		memset(&addr, 0, sizeof(addr));
+
+		if (PR_InitializeNetAddr(PR_IpAddrAny, port, &addr) != PR_SUCCESS) {
+			return (NULL);
+		}
+		addr.raw.family = af;
+
+		sock = nss_sock_create_socket(af, 1);
+		if (sock == NULL) {
+			return (NULL);
+		}
+
+		if (PR_Bind(sock, &addr) != PR_SUCCESS) {
+			PR_Close(sock);
+
+			return (NULL);
+		}
+	} else {
+		tmp_af = PR_AF_UNSPEC;
+		if (af == PR_AF_INET)
+			tmp_af = PR_AF_INET;
+
+		addr_info = PR_GetAddrInfoByName(hostname, tmp_af, PR_AI_ADDRCONFIG);
+		if (addr_info == NULL) {
+			return (NULL);
+		}
+
+		addr_iter = NULL;
+
+		while ((addr_iter = PR_EnumerateAddrInfo(addr_iter, addr_info, port, &addr)) != NULL) {
+			if (addr.raw.family == af) {
+				sock = nss_sock_create_socket(af, 1);
+				if (sock == NULL) {
+					continue ;
+				}
+
+				if (PR_Bind(sock, &addr) != PR_SUCCESS) {
+					PR_Close(sock);
+					sock = NULL;
+
+					continue ;
+				}
+
+				/*
+				 * Socket is sucesfully bound
+				 */
+				break;
+			}
+		}
+
+		PR_FreeAddrInfo(addr_info);
+
+		if (sock == NULL) {
+			/*
+			 * No address succeeded
+			 */
+			PR_SetError(PR_ADDRESS_NOT_AVAILABLE_ERROR, 0);
+
+			return (NULL);
+		}
+	}
+
+	return (sock);
+}
+
+/*
+ * Create listen socket and bind it to address. hostname can be NULL and then
+ * any address is used. Address family (af) can be ether PR_AF_UNSPEC or
+ * PR_AF_INET.
+ */
+PRFileDesc *
+nss_sock_create_client_socket(const char *hostname, uint16_t port, PRIntn af, PRIntervalTime timeout)
+{
+	PRNetAddr addr;
+	PRFileDesc *sock;
+	PRAddrInfo *addr_info;
+	void *addr_iter;
+	PRStatus res;
+	int connect_failed;
+
+	sock = NULL;
+	connect_failed = 0;
+
+	addr_info = PR_GetAddrInfoByName(hostname, af, PR_AI_ADDRCONFIG);
+	if (addr_info == NULL) {
+		return (NULL);
+	}
+
+	addr_iter = NULL;
+
+	while ((addr_iter = PR_EnumerateAddrInfo(addr_iter, addr_info, port, &addr)) != NULL) {
+		sock = nss_sock_create_socket(addr.raw.family, 0);
+		if (sock == NULL) {
+			continue ;
+		}
+
+		if ((res = PR_Connect(sock, &addr, timeout)) != PR_SUCCESS) {
+			PR_Close(sock);
+			sock = NULL;
+			connect_failed = 1;
+		}
+
+		/*
+		 * Connection attempt finished
+		 */
+		break;
+	}
+
+	PR_FreeAddrInfo(addr_info);
+
+	if (sock == NULL && !connect_failed) {
+		PR_SetError(PR_ADDRESS_NOT_AVAILABLE_ERROR, 0);
+	}
+
+	return (sock);
+}
+
+/*
+ * Start client side SSL connection. This can block.
+ *
+ * ssl_url is expected server URL, bad_cert_hook is callback called when server certificate
+ * verification fails.
+ */
+PRFileDesc *
+nss_sock_start_ssl_as_client(PRFileDesc *input_sock, const char *ssl_url, SSLBadCertHandler bad_cert_hook,
+    SSLGetClientAuthData client_auth_hook, void *client_auth_hook_arg, int force_handshake, int *reset_would_block)
+{
+	PRFileDesc *ssl_sock;
+
+	if (force_handshake) {
+		*reset_would_block = 0;
+	}
+
+	ssl_sock = SSL_ImportFD(NULL, input_sock);
+	if (ssl_sock == NULL) {
+		return (NULL);
+	}
+
+	if (SSL_SetURL(ssl_sock, ssl_url) != SECSuccess) {
+		return (NULL);
+	}
+
+	if ((SSL_OptionSet(ssl_sock, SSL_SECURITY, PR_TRUE) != SECSuccess) ||
+	    (SSL_OptionSet(ssl_sock, SSL_HANDSHAKE_AS_SERVER, PR_FALSE) != SECSuccess) ||
+	    (SSL_OptionSet(ssl_sock, SSL_HANDSHAKE_AS_CLIENT, PR_TRUE) != SECSuccess)) {
+		return (NULL);
+	}
+	if (bad_cert_hook != NULL && SSL_BadCertHook(ssl_sock, bad_cert_hook, NULL) != SECSuccess) {
+		return (NULL);
+	}
+
+	if (client_auth_hook != NULL &&
+	    (SSL_GetClientAuthDataHook(ssl_sock, client_auth_hook, client_auth_hook_arg) != SECSuccess)) {
+		return (NULL);
+	}
+
+	if (SSL_ResetHandshake(ssl_sock, PR_FALSE) != SECSuccess) {
+		return (NULL);
+	}
+
+	if (force_handshake && SSL_ForceHandshake(ssl_sock) != SECSuccess) {
+		if (PR_GetError() == PR_WOULD_BLOCK_ERROR) {
+			/*
+			 * Mask would block error.
+			 */
+			*reset_would_block = 1;
+		} else {
+	                return (NULL);
+	        }
+	}
+
+	return (ssl_sock);
+}
+
+PRFileDesc *
+nss_sock_start_ssl_as_server(PRFileDesc *input_sock, CERTCertificate *server_cert, SECKEYPrivateKey *server_key,
+    int require_client_cert, int force_handshake, int *reset_would_block)
+{
+	PRFileDesc *ssl_sock;
+
+	if (force_handshake) {
+		*reset_would_block = 0;
+	}
+
+	ssl_sock = SSL_ImportFD(NULL, input_sock);
+	if (ssl_sock == NULL) {
+		return (NULL);
+	}
+
+	if (SSL_ConfigSecureServer(ssl_sock, server_cert, server_key, NSS_FindCertKEAType(server_cert)) != SECSuccess) {
+		return (NULL);
+	}
+
+	if ((SSL_OptionSet(ssl_sock, SSL_SECURITY, PR_TRUE) != SECSuccess) ||
+	    (SSL_OptionSet(ssl_sock, SSL_HANDSHAKE_AS_SERVER, PR_TRUE) != SECSuccess) ||
+	    (SSL_OptionSet(ssl_sock, SSL_HANDSHAKE_AS_CLIENT, PR_FALSE) != SECSuccess) ||
+	    (SSL_OptionSet(ssl_sock, SSL_REQUEST_CERTIFICATE, require_client_cert) != SECSuccess) ||
+	    (SSL_OptionSet(ssl_sock, SSL_REQUIRE_CERTIFICATE, require_client_cert) != SECSuccess)) {
+		return (NULL);
+	}
+
+	if (SSL_ResetHandshake(ssl_sock, PR_TRUE) != SECSuccess) {
+		return (NULL);
+	}
+
+        if (force_handshake && SSL_ForceHandshake(ssl_sock) != SECSuccess) {
+		if (PR_GetError() == PR_WOULD_BLOCK_ERROR) {
+			/*
+			 * Mask would block error.
+			 */
+			*reset_would_block = 1;
+		} else {
+	                return (NULL);
+	        }
+        }
+
+	return (ssl_sock);
+}

+ 61 - 0
qdevices/nss-sock.h

@@ -0,0 +1,61 @@
+/*
+ * Copyright (c) 2015 Red Hat, Inc.
+ *
+ * All rights reserved.
+ *
+ * Author: Jan Friesse (jfriesse@redhat.com)
+ *
+ * This software licensed under BSD license, the text of which follows:
+ *
+ * Redistribution and use in source and binary forms, with or without
+ * modification, are permitted provided that the following conditions are met:
+ *
+ * - Redistributions of source code must retain the above copyright notice,
+ *   this list of conditions and the following disclaimer.
+ * - 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.
+ * - Neither the name of the Red Hat, Inc. nor the names of its
+ *   contributors may be used to endorse or promote products derived from this
+ *   software without specific prior written permission.
+ *
+ * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS"
+ * AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
+ * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
+ * ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR CONTRIBUTORS BE
+ * LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR
+ * CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF
+ * SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS
+ * INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN
+ * CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE)
+ * ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF
+ * THE POSSIBILITY OF SUCH DAMAGE.
+ */
+
+#ifndef _NSS_SOCK_H_
+#define _NSS_SOCK_H_
+
+#include <nss.h>
+#include <ssl.h>
+
+#ifdef __cplusplus
+extern "C" {
+#endif
+
+extern int		nss_sock_init_nss(char *config_dir);
+extern PRFileDesc	*nss_sock_create_listen_socket(const char *hostname, uint16_t port, PRIntn af);
+extern int		nss_sock_set_nonblocking(PRFileDesc *sock);
+extern PRFileDesc 	*nss_sock_create_client_socket(const char *hostname, uint16_t port, PRIntn af, PRIntervalTime timeout);
+
+extern PRFileDesc	*nss_sock_start_ssl_as_client(PRFileDesc *input_sock, const char *ssl_url,
+    SSLBadCertHandler bad_cert_hook, SSLGetClientAuthData client_auth_hook, void *client_auth_hook_arg,
+    int force_handshake, int *reset_would_block);
+
+extern PRFileDesc	*nss_sock_start_ssl_as_server(PRFileDesc *input_sock, CERTCertificate *server_cert,
+    SECKEYPrivateKey *server_key, int require_client_cert, int force_handshake, int *reset_would_block);
+
+#ifdef __cplusplus
+}
+#endif
+
+#endif /* _NSS_SOCK_H_ */

+ 59 - 0
qdevices/qnetd-client.c

@@ -0,0 +1,59 @@
+/*
+ * Copyright (c) 2015 Red Hat, Inc.
+ *
+ * All rights reserved.
+ *
+ * Author: Jan Friesse (jfriesse@redhat.com)
+ *
+ * This software licensed under BSD license, the text of which follows:
+ *
+ * Redistribution and use in source and binary forms, with or without
+ * modification, are permitted provided that the following conditions are met:
+ *
+ * - Redistributions of source code must retain the above copyright notice,
+ *   this list of conditions and the following disclaimer.
+ * - 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.
+ * - Neither the name of the Red Hat, Inc. nor the names of its
+ *   contributors may be used to endorse or promote products derived from this
+ *   software without specific prior written permission.
+ *
+ * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS"
+ * AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
+ * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
+ * ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR CONTRIBUTORS BE
+ * LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR
+ * CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF
+ * SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS
+ * INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN
+ * CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE)
+ * ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF
+ * THE POSSIBILITY OF SUCH DAMAGE.
+ */
+
+#include <sys/types.h>
+
+#include <string.h>
+
+#include "qnetd-client.h"
+
+void
+qnetd_client_init(struct qnetd_client *client, PRFileDesc *sock, PRNetAddr *addr,
+    size_t max_receive_size, size_t max_send_size)
+{
+
+	memset(client, 0, sizeof(*client));
+	client->socket = sock;
+	memcpy(&client->addr, addr, sizeof(*addr));
+	dynar_init(&client->receive_buffer, max_receive_size);
+	dynar_init(&client->send_buffer, max_send_size);
+}
+
+void
+qnetd_client_destroy(struct qnetd_client *client)
+{
+
+	dynar_destroy(&client->receive_buffer);
+	dynar_destroy(&client->send_buffer);
+}

+ 83 - 0
qdevices/qnetd-client.h

@@ -0,0 +1,83 @@
+/*
+ * Copyright (c) 2015 Red Hat, Inc.
+ *
+ * All rights reserved.
+ *
+ * Author: Jan Friesse (jfriesse@redhat.com)
+ *
+ * This software licensed under BSD license, the text of which follows:
+ *
+ * Redistribution and use in source and binary forms, with or without
+ * modification, are permitted provided that the following conditions are met:
+ *
+ * - Redistributions of source code must retain the above copyright notice,
+ *   this list of conditions and the following disclaimer.
+ * - 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.
+ * - Neither the name of the Red Hat, Inc. nor the names of its
+ *   contributors may be used to endorse or promote products derived from this
+ *   software without specific prior written permission.
+ *
+ * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS"
+ * AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
+ * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
+ * ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR CONTRIBUTORS BE
+ * LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR
+ * CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF
+ * SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS
+ * INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN
+ * CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE)
+ * ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF
+ * THE POSSIBILITY OF SUCH DAMAGE.
+ */
+
+#ifndef _QNETD_CLIENT_H_
+#define _QNETD_CLIENT_H_
+
+#include <sys/types.h>
+
+#include <sys/queue.h>
+#include <inttypes.h>
+
+#include <nspr.h>
+#include "dynar.h"
+#include "tlv.h"
+
+#ifdef __cplusplus
+extern "C" {
+#endif
+
+struct qnetd_client {
+	PRFileDesc *socket;
+	PRNetAddr addr;
+	struct dynar receive_buffer;
+	struct dynar send_buffer;
+	size_t msg_already_received_bytes;
+	size_t msg_already_sent_bytes;
+	int sending_msg;	// Have message to sent
+	int skipping_msg;	// When incorrect message was received skip it
+	int tls_started;	// Set after TLS started
+	int tls_peer_certificate_verified;	// Certificate is verified only once
+	int preinit_received;
+	int init_received;
+	char *cluster_name;
+	size_t cluster_name_len;
+	uint8_t node_id_set;
+	uint32_t node_id;
+	enum tlv_decision_algorithm_type decision_algorithm;
+	uint32_t heartbeat_interval;
+	enum tlv_reply_error_code skipping_msg_reason;
+	TAILQ_ENTRY(qnetd_client) entries;
+};
+
+extern void		qnetd_client_init(struct qnetd_client *client, PRFileDesc *sock, PRNetAddr *addr,
+    size_t max_receive_size, size_t max_send_size);
+
+extern void		qnetd_client_destroy(struct qnetd_client *client);
+
+#ifdef __cplusplus
+}
+#endif
+
+#endif /* _QNETD_CLIENT_H_ */

+ 95 - 0
qdevices/qnetd-clients-list.c

@@ -0,0 +1,95 @@
+/*
+ * Copyright (c) 2015 Red Hat, Inc.
+ *
+ * All rights reserved.
+ *
+ * Author: Jan Friesse (jfriesse@redhat.com)
+ *
+ * This software licensed under BSD license, the text of which follows:
+ *
+ * Redistribution and use in source and binary forms, with or without
+ * modification, are permitted provided that the following conditions are met:
+ *
+ * - Redistributions of source code must retain the above copyright notice,
+ *   this list of conditions and the following disclaimer.
+ * - 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.
+ * - Neither the name of the Red Hat, Inc. nor the names of its
+ *   contributors may be used to endorse or promote products derived from this
+ *   software without specific prior written permission.
+ *
+ * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS"
+ * AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
+ * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
+ * ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR CONTRIBUTORS BE
+ * LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR
+ * CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF
+ * SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS
+ * INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN
+ * CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE)
+ * ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF
+ * THE POSSIBILITY OF SUCH DAMAGE.
+ */
+
+#include <sys/types.h>
+#include <arpa/inet.h>
+
+#include <inttypes.h>
+#include <stdlib.h>
+#include <string.h>
+
+#include "qnetd-clients-list.h"
+
+void
+qnetd_clients_list_init(struct qnetd_clients_list *clients_list)
+{
+
+	TAILQ_INIT(clients_list);
+}
+
+struct qnetd_client *
+qnetd_clients_list_add(struct qnetd_clients_list *clients_list, PRFileDesc *sock, PRNetAddr *addr,
+	size_t max_receive_size, size_t max_send_size)
+{
+	struct qnetd_client *client;
+
+	client = (struct qnetd_client *)malloc(sizeof(*client));
+	if (client == NULL) {
+		return (NULL);
+	}
+
+	qnetd_client_init(client, sock, addr, max_receive_size, max_send_size);
+
+	TAILQ_INSERT_TAIL(clients_list, client, entries);
+
+	return (client);
+}
+
+void
+qnetd_clients_list_free(struct qnetd_clients_list *clients_list)
+{
+	struct qnetd_client *client;
+	struct qnetd_client *client_next;
+
+	client = TAILQ_FIRST(clients_list);
+
+	while (client != NULL) {
+		client_next = TAILQ_NEXT(client, entries);
+
+		free(client);
+
+		client = client_next;
+	}
+
+	TAILQ_INIT(clients_list);
+}
+
+void
+qnetd_clients_list_del(struct qnetd_clients_list *clients_list, struct qnetd_client *client)
+{
+
+	TAILQ_REMOVE(clients_list, client, entries);
+	qnetd_client_destroy(client);
+	free(client);
+}

+ 63 - 0
qdevices/qnetd-clients-list.h

@@ -0,0 +1,63 @@
+/*
+ * Copyright (c) 2015 Red Hat, Inc.
+ *
+ * All rights reserved.
+ *
+ * Author: Jan Friesse (jfriesse@redhat.com)
+ *
+ * This software licensed under BSD license, the text of which follows:
+ *
+ * Redistribution and use in source and binary forms, with or without
+ * modification, are permitted provided that the following conditions are met:
+ *
+ * - Redistributions of source code must retain the above copyright notice,
+ *   this list of conditions and the following disclaimer.
+ * - 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.
+ * - Neither the name of the Red Hat, Inc. nor the names of its
+ *   contributors may be used to endorse or promote products derived from this
+ *   software without specific prior written permission.
+ *
+ * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS"
+ * AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
+ * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
+ * ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR CONTRIBUTORS BE
+ * LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR
+ * CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF
+ * SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS
+ * INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN
+ * CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE)
+ * ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF
+ * THE POSSIBILITY OF SUCH DAMAGE.
+ */
+
+#ifndef _QNETD_CLIENTS_LIST_H_
+#define _QNETD_CLIENTS_LIST_H_
+
+#include <sys/types.h>
+#include <inttypes.h>
+
+#include "qnetd-client.h"
+
+#ifdef __cplusplus
+extern "C" {
+#endif
+
+TAILQ_HEAD(qnetd_clients_list, qnetd_client);
+
+extern void			 qnetd_clients_list_init(struct qnetd_clients_list *clients_list);
+
+extern struct qnetd_client	*qnetd_clients_list_add(struct qnetd_clients_list *clients_list,
+    PRFileDesc *sock, PRNetAddr *addr, size_t max_receive_size, size_t max_send_size);
+
+extern void			 qnetd_clients_list_free(struct qnetd_clients_list *clients_list);
+
+extern void			 qnetd_clients_list_del(struct qnetd_clients_list *clients_list,
+    struct qnetd_client *client);
+
+#ifdef __cplusplus
+}
+#endif
+
+#endif /* _QNETD_CLIENTS_LIST_H_ */

+ 48 - 0
qdevices/qnetd-defines.h

@@ -0,0 +1,48 @@
+/*
+ * Copyright (c) 2015 Red Hat, Inc.
+ *
+ * All rights reserved.
+ *
+ * Author: Jan Friesse (jfriesse@redhat.com)
+ *
+ * This software licensed under BSD license, the text of which follows:
+ *
+ * Redistribution and use in source and binary forms, with or without
+ * modification, are permitted provided that the following conditions are met:
+ *
+ * - Redistributions of source code must retain the above copyright notice,
+ *   this list of conditions and the following disclaimer.
+ * - 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.
+ * - Neither the name of the Red Hat, Inc. nor the names of its
+ *   contributors may be used to endorse or promote products derived from this
+ *   software without specific prior written permission.
+ *
+ * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS"
+ * AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
+ * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
+ * ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR CONTRIBUTORS BE
+ * LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR
+ * CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF
+ * SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS
+ * INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN
+ * CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE)
+ * ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF
+ * THE POSSIBILITY OF SUCH DAMAGE.
+ */
+
+#ifndef _QNETD_DEFINES_H_
+#define _QNETD_DEFINES_H_
+
+#ifdef __cplusplus
+extern "C" {
+#endif
+
+#define QNETD_PROGRAM_NAME	"corosync-qnetd"
+
+#ifdef __cplusplus
+}
+#endif
+
+#endif /* _QNETD_DEFINES_H_ */

+ 91 - 0
qdevices/qnetd-log.c

@@ -0,0 +1,91 @@
+/*
+ * Copyright (c) 2015 Red Hat, Inc.
+ *
+ * All rights reserved.
+ *
+ * Author: Jan Friesse (jfriesse@redhat.com)
+ *
+ * This software licensed under BSD license, the text of which follows:
+ *
+ * Redistribution and use in source and binary forms, with or without
+ * modification, are permitted provided that the following conditions are met:
+ *
+ * - Redistributions of source code must retain the above copyright notice,
+ *   this list of conditions and the following disclaimer.
+ * - 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.
+ * - Neither the name of the Red Hat, Inc. nor the names of its
+ *   contributors may be used to endorse or promote products derived from this
+ *   software without specific prior written permission.
+ *
+ * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS"
+ * AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
+ * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
+ * ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR CONTRIBUTORS BE
+ * LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR
+ * CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF
+ * SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS
+ * INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN
+ * CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE)
+ * ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF
+ * THE POSSIBILITY OF SUCH DAMAGE.
+ */
+
+#include <syslog.h>
+#include <stdarg.h>
+#include <stdio.h>
+
+#include "qnetd-defines.h"
+#include "qnetd-log.h"
+
+static int qnetd_log_config_target = 0;
+static int qnetd_log_config_debug = 0;
+
+void
+qnetd_log_init(int target)
+{
+
+	qnetd_log_config_target = target;
+
+	if (qnetd_log_config_target & QNETD_LOG_TARGET_SYSLOG) {
+		openlog(QNETD_PROGRAM_NAME, LOG_PID, LOG_DAEMON);
+	}
+}
+
+void
+qnetd_log_printf(int priority, const char *format, ...)
+{
+	va_list ap;
+
+	if (priority != LOG_DEBUG || (qnetd_log_config_debug)) {
+		if (qnetd_log_config_target & QNETD_LOG_TARGET_STDERR) {
+			va_start(ap, format);
+			vfprintf(stderr, format, ap);
+			fprintf(stderr, "\n");
+			va_end(ap);
+		}
+
+		if (qnetd_log_config_target & QNETD_LOG_TARGET_SYSLOG) {
+			va_start(ap, format);
+			vsyslog(priority, format, ap);
+			va_end(ap);
+		}
+	}
+}
+
+void
+qnetd_log_close(void)
+{
+
+	if (qnetd_log_config_target & QNETD_LOG_TARGET_SYSLOG) {
+		closelog();
+	}
+}
+
+void
+qnetd_log_set_debug(int enabled)
+{
+
+	qnetd_log_config_debug = enabled;
+}

+ 64 - 0
qdevices/qnetd-log.h

@@ -0,0 +1,64 @@
+/*
+ * Copyright (c) 2015 Red Hat, Inc.
+ *
+ * All rights reserved.
+ *
+ * Author: Jan Friesse (jfriesse@redhat.com)
+ *
+ * This software licensed under BSD license, the text of which follows:
+ *
+ * Redistribution and use in source and binary forms, with or without
+ * modification, are permitted provided that the following conditions are met:
+ *
+ * - Redistributions of source code must retain the above copyright notice,
+ *   this list of conditions and the following disclaimer.
+ * - 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.
+ * - Neither the name of the Red Hat, Inc. nor the names of its
+ *   contributors may be used to endorse or promote products derived from this
+ *   software without specific prior written permission.
+ *
+ * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS"
+ * AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
+ * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
+ * ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR CONTRIBUTORS BE
+ * LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR
+ * CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF
+ * SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS
+ * INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN
+ * CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE)
+ * ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF
+ * THE POSSIBILITY OF SUCH DAMAGE.
+ */
+
+#ifndef _QNETD_LOG_H_
+#define _QNETD_LOG_H_
+
+#include <syslog.h>
+
+#ifdef __cplusplus
+extern "C" {
+#endif
+
+#define QNETD_LOG_TARGET_STDERR		1
+#define QNETD_LOG_TARGET_SYSLOG		2
+
+#define qnetd_log(...)	qnetd_log_printf(__VA_ARGS__)
+#define qnetd_log_nss(priority, str) qnetd_log_printf(priority, "%s (%d): %s", \
+    str, PR_GetError(), PR_ErrorToString(PR_GetError(), PR_LANGUAGE_I_DEFAULT));
+
+extern void		qnetd_log_init(int target);
+
+extern void		qnetd_log_printf(int priority, const char *format, ...)
+    __attribute__((__format__(__printf__, 2, 3)));;
+
+extern void		qnetd_log_close(void);
+
+extern void		qnetd_log_set_debug(int enabled);
+
+#ifdef __cplusplus
+}
+#endif
+
+#endif /* _QNETD_LOG_H_ */

+ 164 - 0
qdevices/qnetd-poll-array.c

@@ -0,0 +1,164 @@
+/*
+ * Copyright (c) 2015 Red Hat, Inc.
+ *
+ * All rights reserved.
+ *
+ * Author: Jan Friesse (jfriesse@redhat.com)
+ *
+ * This software licensed under BSD license, the text of which follows:
+ *
+ * Redistribution and use in source and binary forms, with or without
+ * modification, are permitted provided that the following conditions are met:
+ *
+ * - Redistributions of source code must retain the above copyright notice,
+ *   this list of conditions and the following disclaimer.
+ * - 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.
+ * - Neither the name of the Red Hat, Inc. nor the names of its
+ *   contributors may be used to endorse or promote products derived from this
+ *   software without specific prior written permission.
+ *
+ * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS"
+ * AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
+ * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
+ * ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR CONTRIBUTORS BE
+ * LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR
+ * CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF
+ * SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS
+ * INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN
+ * CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE)
+ * ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF
+ * THE POSSIBILITY OF SUCH DAMAGE.
+ */
+
+#include <sys/types.h>
+#include <arpa/inet.h>
+
+#include <inttypes.h>
+#include <stdlib.h>
+#include <string.h>
+
+#include "qnetd-poll-array.h"
+
+void
+qnetd_poll_array_init(struct qnetd_poll_array *poll_array)
+{
+
+	memset(poll_array, 0, sizeof(*poll_array));
+}
+
+void
+qnetd_poll_array_destroy(struct qnetd_poll_array *poll_array)
+{
+
+	free(poll_array->array);
+	qnetd_poll_array_init(poll_array);
+}
+
+void
+qnetd_poll_array_clean(struct qnetd_poll_array *poll_array)
+{
+
+	poll_array->items = 0;
+}
+
+static int
+qnetd_poll_array_realloc(struct qnetd_poll_array *poll_array,
+    unsigned int new_array_size)
+{
+	PRPollDesc *new_array;
+
+	new_array = realloc(poll_array->array,
+	    sizeof(PRPollDesc) * new_array_size);
+
+	if (new_array == NULL) {
+		return (-1);
+	}
+
+	poll_array->allocated = new_array_size;
+	poll_array->array = new_array;
+
+	return (0);
+}
+
+unsigned int
+qnetd_poll_array_size(struct qnetd_poll_array *poll_array)
+{
+
+	return (poll_array->items);
+}
+
+PRPollDesc *
+qnetd_poll_array_add(struct qnetd_poll_array *poll_array)
+{
+
+	if (qnetd_poll_array_size(poll_array) >= poll_array->allocated) {
+		if (qnetd_poll_array_realloc(poll_array, (poll_array->allocated * 2) + 1)) {
+			return (NULL);
+		}
+	}
+
+	poll_array->items++;
+
+	return (&poll_array->array[qnetd_poll_array_size(poll_array) - 1]);
+}
+
+static void
+qnetd_poll_array_gc(struct qnetd_poll_array *poll_array)
+{
+
+	if (poll_array->allocated > (qnetd_poll_array_size(poll_array) * 3) + 1) {
+		qnetd_poll_array_realloc(poll_array, (qnetd_poll_array_size(poll_array) * 2) + 1);
+	}
+}
+
+PRPollDesc *
+qnetd_poll_array_get(const struct qnetd_poll_array *poll_array, unsigned int pos)
+{
+
+	if (pos >= poll_array->items) {
+		return (NULL);
+	}
+
+	return (&poll_array->array[pos]);
+}
+
+PRPollDesc *
+qnetd_poll_array_create_from_clients_list(struct qnetd_poll_array *poll_array,
+    const struct qnetd_clients_list *clients_list,
+    PRFileDesc *extra_fd, PRInt16 extra_fd_in_flags)
+{
+	struct qnetd_client *client;
+	PRPollDesc *poll_desc;
+
+	qnetd_poll_array_clean(poll_array);
+
+	if (extra_fd != NULL) {
+		poll_desc = qnetd_poll_array_add(poll_array);
+		if (poll_desc == NULL) {
+			return (NULL);
+		}
+
+		poll_desc->fd = extra_fd;
+		poll_desc->in_flags = extra_fd_in_flags;
+		poll_desc->out_flags = 0;
+	}
+
+	TAILQ_FOREACH(client, clients_list, entries) {
+		poll_desc = qnetd_poll_array_add(poll_array);
+		if (poll_desc == NULL) {
+			return (NULL);
+		}
+		poll_desc->fd = client->socket;
+		poll_desc->in_flags = PR_POLL_READ;
+		if (client->sending_msg) {
+			poll_desc->in_flags |= PR_POLL_WRITE;
+		}
+		poll_desc->out_flags = 0;
+	}
+
+	qnetd_poll_array_gc(poll_array);
+
+	return (poll_array->array);
+}

+ 76 - 0
qdevices/qnetd-poll-array.h

@@ -0,0 +1,76 @@
+/*
+ * Copyright (c) 2015 Red Hat, Inc.
+ *
+ * All rights reserved.
+ *
+ * Author: Jan Friesse (jfriesse@redhat.com)
+ *
+ * This software licensed under BSD license, the text of which follows:
+ *
+ * Redistribution and use in source and binary forms, with or without
+ * modification, are permitted provided that the following conditions are met:
+ *
+ * - Redistributions of source code must retain the above copyright notice,
+ *   this list of conditions and the following disclaimer.
+ * - 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.
+ * - Neither the name of the Red Hat, Inc. nor the names of its
+ *   contributors may be used to endorse or promote products derived from this
+ *   software without specific prior written permission.
+ *
+ * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS"
+ * AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
+ * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
+ * ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR CONTRIBUTORS BE
+ * LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR
+ * CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF
+ * SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS
+ * INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN
+ * CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE)
+ * ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF
+ * THE POSSIBILITY OF SUCH DAMAGE.
+ */
+
+#ifndef _QNETD_POLL_ARRAY_H_
+#define _QNETD_POLL_ARRAY_H_
+
+#include <sys/types.h>
+#include <inttypes.h>
+
+#include <nspr.h>
+
+#include "qnetd-client.h"
+#include "qnetd-clients-list.h"
+
+#ifdef __cplusplus
+extern "C" {
+#endif
+
+struct qnetd_poll_array {
+	PRPollDesc *array;
+	unsigned int allocated;
+	unsigned int items;
+};
+
+
+extern void		 qnetd_poll_array_init(struct qnetd_poll_array *poll_array);
+
+extern void		 qnetd_poll_array_destroy(struct qnetd_poll_array *poll_array);
+
+extern void		 qnetd_poll_array_clean(struct qnetd_poll_array *poll_array);
+
+extern unsigned int	 qnetd_poll_array_size(struct qnetd_poll_array *poll_array);
+
+extern PRPollDesc	*qnetd_poll_array_add(struct qnetd_poll_array *poll_array);
+
+extern PRPollDesc 	*qnetd_poll_array_get(const struct qnetd_poll_array *poll_array, unsigned int pos);
+
+extern PRPollDesc	*qnetd_poll_array_create_from_clients_list(struct qnetd_poll_array *poll_array,
+    const struct qnetd_clients_list *clients_list, PRFileDesc *extra_fd, PRInt16 extra_fd_in_flags);
+
+#ifdef __cplusplus
+}
+#endif
+
+#endif /* _QNETD_POLL_ARRAY_H_ */

+ 216 - 0
qdevices/timer-list.c

@@ -0,0 +1,216 @@
+/*
+ * Copyright (c) 2015 Red Hat, Inc.
+ *
+ * All rights reserved.
+ *
+ * Author: Jan Friesse (jfriesse@redhat.com)
+ *
+ * This software licensed under BSD license, the text of which follows:
+ *
+ * Redistribution and use in source and binary forms, with or without
+ * modification, are permitted provided that the following conditions are met:
+ *
+ * - Redistributions of source code must retain the above copyright notice,
+ *   this list of conditions and the following disclaimer.
+ * - 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.
+ * - Neither the name of the Red Hat, Inc. nor the names of its
+ *   contributors may be used to endorse or promote products derived from this
+ *   software without specific prior written permission.
+ *
+ * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS"
+ * AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
+ * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
+ * ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR CONTRIBUTORS BE
+ * LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR
+ * CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF
+ * SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS
+ * INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN
+ * CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE)
+ * ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF
+ * THE POSSIBILITY OF SUCH DAMAGE.
+ */
+
+#include <string.h>
+#include <assert.h>
+
+#include "timer-list.h"
+
+void
+timer_list_init(struct timer_list *tlist)
+{
+
+	memset(tlist, 0, sizeof(*tlist));
+
+	TAILQ_INIT(&tlist->list);
+	TAILQ_INIT(&tlist->free_list);
+}
+
+struct timer_list_entry *
+timer_list_add(struct timer_list *tlist, PRUint32 interval, timer_list_cb_fn func, void *data1, void *data2)
+{
+	struct timer_list_entry *entry;
+
+	assert(tlist->list_expire_in_progress == 0);
+
+	if (interval > (0xffffffffUL / 4)) {
+		return (NULL);
+	}
+
+	if (!TAILQ_EMPTY(&tlist->free_list)) {
+		/*
+		 * Use free list entry
+		 */
+		entry = TAILQ_FIRST(&tlist->free_list);
+		TAILQ_REMOVE(&tlist->free_list, entry, entries);
+	} else {
+		/*
+		 * Alloc new entry
+		 */
+		entry = malloc(sizeof(*entry));
+		if (entry == NULL) {
+			return (NULL);
+		}
+	}
+
+	memset(entry, 0, sizeof(*entry));
+	entry->epoch = PR_IntervalNow();
+	entry->interval = interval;
+	entry->func = func;
+	entry->user_data1 = data1;
+	entry->user_data2 = data2;
+	TAILQ_INSERT_TAIL(&tlist->list, entry, entries);
+
+	return (entry);
+}
+
+void
+timer_list_expire(struct timer_list *tlist)
+{
+	PRIntervalTime now;
+	struct timer_list_entry *entry;
+	struct timer_list_entry *entry_next;
+	PRUint32 delta;
+	int res;
+
+	tlist->list_expire_in_progress = 1;
+
+	now = PR_IntervalNow();
+
+	entry = TAILQ_FIRST(&tlist->list);
+
+	while (entry != NULL) {
+		entry_next = TAILQ_NEXT(entry, entries);
+
+		delta = PR_IntervalToMilliseconds(now - entry->epoch);
+		if (delta >= entry->interval) {
+			/*
+			 * Expired
+			 */
+			res = entry->func(entry->user_data1, entry->user_data2);
+			if (res == 0) {
+				/*
+				 * Move item to free list
+				 */
+				TAILQ_REMOVE(&tlist->list, entry, entries);
+				TAILQ_INSERT_HEAD(&tlist->free_list, entry, entries);
+			} else {
+				/*
+				 * Schedule again
+				 */
+				entry->epoch = now;
+			}
+		}
+
+		entry = entry_next;
+	}
+
+	tlist->list_expire_in_progress = 0;
+}
+
+PRIntervalTime
+timer_list_time_to_expire(struct timer_list *tlist)
+{
+	PRIntervalTime now;
+	struct timer_list_entry *entry;
+	PRUint32 delta;
+	PRUint32 timeout;
+	PRUint32 min_timeout;
+	int min_timeout_set;
+
+	min_timeout_set = 0;
+
+	now = PR_IntervalNow();
+
+	TAILQ_FOREACH(entry, &tlist->list, entries) {
+		delta = PR_IntervalToMilliseconds(now - entry->epoch);
+
+		if (delta >= entry->interval) {
+			/*
+			 * One of timer already expired
+			 */
+			return (PR_INTERVAL_NO_WAIT);
+		}
+
+		timeout = entry->interval - delta;
+
+		if (!min_timeout_set) {
+			min_timeout_set = 1;
+			min_timeout = timeout;
+		}
+
+		if (timeout < min_timeout) {
+			min_timeout = timeout;
+		}
+	}
+
+	if (!min_timeout_set) {
+		return (PR_INTERVAL_NO_TIMEOUT);
+	}
+
+	return (PR_MillisecondsToInterval(min_timeout));
+}
+
+void
+timer_list_delete(struct timer_list *tlist, struct timer_list_entry *entry)
+{
+
+	assert(tlist->list_expire_in_progress == 0);
+
+	/*
+	 * Move item to free list
+	 */
+	TAILQ_REMOVE(&tlist->list, entry, entries);
+	TAILQ_INSERT_HEAD(&tlist->free_list, entry, entries);
+}
+
+void
+timer_list_free(struct timer_list *tlist)
+{
+	struct timer_list_entry *entry;
+	struct timer_list_entry *entry_next;
+
+
+	entry = TAILQ_FIRST(&tlist->list);
+
+	while (entry != NULL) {
+		entry_next = TAILQ_NEXT(entry, entries);
+
+		free(entry);
+
+		entry = entry_next;
+	}
+
+	entry = TAILQ_FIRST(&tlist->free_list);
+
+	while (entry != NULL) {
+		entry_next = TAILQ_NEXT(entry, entries);
+
+		free(entry);
+
+		entry = entry_next;
+	}
+
+	timer_list_init(tlist);
+}

+ 81 - 0
qdevices/timer-list.h

@@ -0,0 +1,81 @@
+/*
+ * Copyright (c) 2015 Red Hat, Inc.
+ *
+ * All rights reserved.
+ *
+ * Author: Jan Friesse (jfriesse@redhat.com)
+ *
+ * This software licensed under BSD license, the text of which follows:
+ *
+ * Redistribution and use in source and binary forms, with or without
+ * modification, are permitted provided that the following conditions are met:
+ *
+ * - Redistributions of source code must retain the above copyright notice,
+ *   this list of conditions and the following disclaimer.
+ * - 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.
+ * - Neither the name of the Red Hat, Inc. nor the names of its
+ *   contributors may be used to endorse or promote products derived from this
+ *   software without specific prior written permission.
+ *
+ * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS"
+ * AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
+ * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
+ * ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR CONTRIBUTORS BE
+ * LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR
+ * CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF
+ * SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS
+ * INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN
+ * CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE)
+ * ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF
+ * THE POSSIBILITY OF SUCH DAMAGE.
+ */
+
+#ifndef _TIMER_LIST_H_
+#define _TIMER_LIST_H_
+
+#include <sys/queue.h>
+
+#include <nspr.h>
+
+#ifdef __cplusplus
+extern "C" {
+#endif
+
+typedef int (*timer_list_cb_fn)(void *data1, void *data2);
+
+struct timer_list_entry {
+	PRIntervalTime epoch;
+	PRUint32 interval;
+	timer_list_cb_fn func;
+	void *user_data1;
+	void *user_data2;
+	TAILQ_ENTRY(timer_list_entry) entries;
+};
+
+struct timer_list {
+	TAILQ_HEAD(, timer_list_entry) list;
+	TAILQ_HEAD(, timer_list_entry) free_list;
+	int list_expire_in_progress;
+};
+
+extern void				 timer_list_init(struct timer_list *tlist);
+
+extern struct timer_list_entry		*timer_list_add(struct timer_list *tlist, PRUint32 interval,
+    timer_list_cb_fn func, void *data1, void *data2);
+
+extern void				 timer_list_delete(struct timer_list *tlist,
+    struct timer_list_entry *entry);
+
+extern void				 timer_list_expire(struct timer_list *tlist);
+
+extern PRIntervalTime			 timer_list_time_to_expire(struct timer_list *tlist);
+
+extern void				 timer_list_free(struct timer_list *tlist);
+
+#ifdef __cplusplus
+}
+#endif
+
+#endif /* _TIMER_LIST_H_ */

+ 566 - 0
qdevices/tlv.c

@@ -0,0 +1,566 @@
+/*
+ * Copyright (c) 2015 Red Hat, Inc.
+ *
+ * All rights reserved.
+ *
+ * Author: Jan Friesse (jfriesse@redhat.com)
+ *
+ * This software licensed under BSD license, the text of which follows:
+ *
+ * Redistribution and use in source and binary forms, with or without
+ * modification, are permitted provided that the following conditions are met:
+ *
+ * - Redistributions of source code must retain the above copyright notice,
+ *   this list of conditions and the following disclaimer.
+ * - 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.
+ * - Neither the name of the Red Hat, Inc. nor the names of its
+ *   contributors may be used to endorse or promote products derived from this
+ *   software without specific prior written permission.
+ *
+ * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS"
+ * AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
+ * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
+ * ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR CONTRIBUTORS BE
+ * LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR
+ * CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF
+ * SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS
+ * INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN
+ * CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE)
+ * ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF
+ * THE POSSIBILITY OF SUCH DAMAGE.
+ */
+
+#include <sys/types.h>
+#include <arpa/inet.h>
+
+#include <inttypes.h>
+#include <stdlib.h>
+#include <string.h>
+
+#include "tlv.h"
+
+#define TLV_TYPE_LENGTH		2
+#define TLV_LENGTH_LENGTH	2
+
+#define TLV_STATIC_SUPPORTED_OPTIONS_SIZE      13
+
+enum tlv_opt_type tlv_static_supported_options[TLV_STATIC_SUPPORTED_OPTIONS_SIZE] = {
+    TLV_OPT_MSG_SEQ_NUMBER,
+    TLV_OPT_CLUSTER_NAME,
+    TLV_OPT_TLS_SUPPORTED,
+    TLV_OPT_TLS_CLIENT_CERT_REQUIRED,
+    TLV_OPT_SUPPORTED_MESSAGES,
+    TLV_OPT_SUPPORTED_OPTIONS,
+    TLV_OPT_REPLY_ERROR_CODE,
+    TLV_OPT_SERVER_MAXIMUM_REQUEST_SIZE,
+    TLV_OPT_SERVER_MAXIMUM_REPLY_SIZE,
+    TLV_OPT_NODE_ID,
+    TLV_OPT_SUPPORTED_DECISION_ALGORITHMS,
+    TLV_OPT_DECISION_ALGORITHM,
+    TLV_OPT_HEARTBEAT_INTERVAL,
+};
+
+int
+tlv_add(struct dynar *msg, enum tlv_opt_type opt_type, uint16_t opt_len, const void *value)
+{
+	uint16_t nlen;
+	uint16_t nopt_type;
+
+	if (dynar_size(msg) + sizeof(nopt_type) + sizeof(nlen) + opt_len > dynar_max_size(msg)) {
+		return (-1);
+	}
+
+	nopt_type = htons((uint16_t)opt_type);
+	nlen = htons(opt_len);
+
+	dynar_cat(msg, &nopt_type, sizeof(nopt_type));
+	dynar_cat(msg, &nlen, sizeof(nlen));
+	dynar_cat(msg, value, opt_len);
+
+	return (0);
+}
+
+int
+tlv_add_u32(struct dynar *msg, enum tlv_opt_type opt_type, uint32_t u32)
+{
+	uint32_t nu32;
+
+	nu32 = htonl(u32);
+
+	return (tlv_add(msg, opt_type, sizeof(nu32), &nu32));
+}
+
+int
+tlv_add_u8(struct dynar *msg, enum tlv_opt_type opt_type, uint8_t u8)
+{
+
+	return (tlv_add(msg, opt_type, sizeof(u8), &u8));
+}
+
+int
+tlv_add_u16(struct dynar *msg, enum tlv_opt_type opt_type, uint16_t u16)
+{
+	uint16_t nu16;
+
+	nu16 = htons(u16);
+
+	return (tlv_add(msg, opt_type, sizeof(nu16), &nu16));
+}
+
+int
+tlv_add_string(struct dynar *msg, enum tlv_opt_type opt_type, const char *str)
+{
+
+	return (tlv_add(msg, opt_type, strlen(str), str));
+}
+
+int
+tlv_add_msg_seq_number(struct dynar *msg, uint32_t msg_seq_number)
+{
+
+	return (tlv_add_u32(msg, TLV_OPT_MSG_SEQ_NUMBER, msg_seq_number));
+}
+
+int
+tlv_add_cluster_name(struct dynar *msg, const char *cluster_name)
+{
+
+	return (tlv_add_string(msg, TLV_OPT_CLUSTER_NAME, cluster_name));
+}
+
+int
+tlv_add_tls_supported(struct dynar *msg, enum tlv_tls_supported tls_supported)
+{
+
+	return (tlv_add_u8(msg, TLV_OPT_TLS_SUPPORTED, tls_supported));
+}
+
+int
+tlv_add_tls_client_cert_required(struct dynar *msg, int tls_client_cert_required)
+{
+
+	return (tlv_add_u8(msg, TLV_OPT_TLS_CLIENT_CERT_REQUIRED, tls_client_cert_required));
+}
+
+int
+tlv_add_u16_array(struct dynar *msg, enum tlv_opt_type opt_type, const uint16_t *array, size_t array_size)
+{
+	size_t i;
+	uint16_t *nu16a;
+	uint16_t opt_len;
+	int res;
+
+	nu16a = malloc(sizeof(uint16_t) * array_size);
+	if (nu16a == NULL) {
+		return (-1);
+	}
+
+	for (i = 0; i < array_size; i++) {
+		nu16a[i] = htons(array[i]);
+	}
+
+	opt_len = sizeof(uint16_t) * array_size;
+
+	res = tlv_add(msg, opt_type, opt_len, nu16a);
+
+	free(nu16a);
+
+	return (res);
+}
+
+int
+tlv_add_supported_options(struct dynar *msg, const enum tlv_opt_type *supported_options,
+    size_t no_supported_options)
+{
+	uint16_t *u16a;
+	size_t i;
+	int res;
+
+	u16a = malloc(sizeof(*u16a) * no_supported_options);
+	if (u16a == NULL) {
+		return (-1);
+	}
+
+	for (i = 0; i < no_supported_options; i++) {
+		u16a[i] = (uint16_t)supported_options[i];
+	}
+
+	res = (tlv_add_u16_array(msg, TLV_OPT_SUPPORTED_OPTIONS, u16a, no_supported_options));
+
+	free(u16a);
+
+	return (res);
+}
+
+int
+tlv_add_supported_decision_algorithms(struct dynar *msg, const enum tlv_decision_algorithm_type *supported_algorithms,
+    size_t no_supported_algorithms)
+{
+	uint16_t *u16a;
+	size_t i;
+	int res;
+
+	u16a = malloc(sizeof(*u16a) * no_supported_algorithms);
+	if (u16a == NULL) {
+		return (-1);
+	}
+
+	for (i = 0; i < no_supported_algorithms; i++) {
+		u16a[i] = (uint16_t)supported_algorithms[i];
+	}
+
+	res = (tlv_add_u16_array(msg, TLV_OPT_SUPPORTED_DECISION_ALGORITHMS, u16a, no_supported_algorithms));
+
+	free(u16a);
+
+	return (res);
+}
+
+int
+tlv_add_reply_error_code(struct dynar *msg, enum tlv_reply_error_code error_code)
+{
+
+	return (tlv_add_u16(msg, TLV_OPT_REPLY_ERROR_CODE, (uint16_t)error_code));
+}
+
+int
+tlv_add_server_maximum_request_size(struct dynar *msg, size_t server_maximum_request_size)
+{
+
+	return (tlv_add_u32(msg, TLV_OPT_SERVER_MAXIMUM_REQUEST_SIZE, server_maximum_request_size));
+}
+
+int
+tlv_add_server_maximum_reply_size(struct dynar *msg, size_t server_maximum_reply_size)
+{
+
+	return (tlv_add_u32(msg, TLV_OPT_SERVER_MAXIMUM_REPLY_SIZE, server_maximum_reply_size));
+}
+
+int
+tlv_add_node_id(struct dynar *msg, uint32_t node_id)
+{
+
+	return (tlv_add_u32(msg, TLV_OPT_NODE_ID, node_id));
+}
+
+int
+tlv_add_decision_algorithm(struct dynar *msg, enum tlv_decision_algorithm_type decision_algorithm)
+{
+
+	return (tlv_add_u16(msg, TLV_OPT_DECISION_ALGORITHM, (uint16_t)decision_algorithm));
+}
+
+int
+tlv_add_heartbeat_interval(struct dynar *msg, uint32_t heartbeat_interval)
+{
+
+	return (tlv_add_u32(msg, TLV_OPT_HEARTBEAT_INTERVAL, heartbeat_interval));
+}
+
+void
+tlv_iter_init(const struct dynar *msg, size_t msg_header_len, struct tlv_iterator *tlv_iter)
+{
+
+	tlv_iter->msg = msg;
+	tlv_iter->current_pos = 0;
+	tlv_iter->msg_header_len = msg_header_len;
+}
+
+enum tlv_opt_type
+tlv_iter_get_type(const struct tlv_iterator *tlv_iter)
+{
+	uint16_t ntype;
+	uint16_t type;
+
+	memcpy(&ntype, dynar_data(tlv_iter->msg) + tlv_iter->current_pos, sizeof(ntype));
+	type = ntohs(ntype);
+
+	return (type);
+}
+
+uint16_t
+tlv_iter_get_len(const struct tlv_iterator *tlv_iter)
+{
+	uint16_t nlen;
+	uint16_t len;
+
+	memcpy(&nlen, dynar_data(tlv_iter->msg) + tlv_iter->current_pos + TLV_TYPE_LENGTH, sizeof(nlen));
+	len = ntohs(nlen);
+
+	return (len);
+}
+
+const char *
+tlv_iter_get_data(const struct tlv_iterator *tlv_iter)
+{
+
+	return (dynar_data(tlv_iter->msg) + tlv_iter->current_pos + TLV_TYPE_LENGTH + TLV_LENGTH_LENGTH);
+}
+
+int
+tlv_iter_next(struct tlv_iterator *tlv_iter)
+{
+	uint16_t len;
+
+	if (tlv_iter->current_pos == 0) {
+		tlv_iter->current_pos = tlv_iter->msg_header_len;
+
+		goto check_tlv_validity;
+	}
+
+	len = tlv_iter_get_len(tlv_iter);
+
+	if (tlv_iter->current_pos + TLV_TYPE_LENGTH + TLV_LENGTH_LENGTH + len >= dynar_size(tlv_iter->msg)) {
+		return (0);
+	}
+
+	tlv_iter->current_pos += TLV_TYPE_LENGTH + TLV_LENGTH_LENGTH + len;
+
+check_tlv_validity:
+	/*
+	 * Check if tlv is valid = is not larger than whole message
+	 */
+	len = tlv_iter_get_len(tlv_iter);
+
+	if (tlv_iter->current_pos + TLV_TYPE_LENGTH + TLV_LENGTH_LENGTH + len > dynar_size(tlv_iter->msg)) {
+		return (-1);
+	}
+
+	return (1);
+}
+
+int
+tlv_iter_decode_u32(struct tlv_iterator *tlv_iter, uint32_t *res)
+{
+	const char *opt_data;
+	uint16_t opt_len;
+	uint32_t nu32;
+
+	opt_len = tlv_iter_get_len(tlv_iter);
+	opt_data = tlv_iter_get_data(tlv_iter);
+
+	if (opt_len != sizeof(nu32)) {
+		return (-1);
+	}
+
+	memcpy(&nu32, opt_data, sizeof(nu32));
+	*res = ntohl(nu32);
+
+	return (0);
+}
+
+int
+tlv_iter_decode_u8(struct tlv_iterator *tlv_iter, uint8_t *res)
+{
+	const char *opt_data;
+	uint16_t opt_len;
+
+	opt_len = tlv_iter_get_len(tlv_iter);
+	opt_data = tlv_iter_get_data(tlv_iter);
+
+	if (opt_len != sizeof(*res)) {
+		return (-1);
+	}
+
+	memcpy(res, opt_data, sizeof(*res));
+
+	return (0);
+}
+
+int
+tlv_iter_decode_client_cert_required(struct tlv_iterator *tlv_iter, uint8_t *client_cert_required)
+{
+
+	return (tlv_iter_decode_u8(tlv_iter, client_cert_required));
+}
+
+int
+tlv_iter_decode_str(struct tlv_iterator *tlv_iter, char **str, size_t *str_len)
+{
+	const char *opt_data;
+	uint16_t opt_len;
+	char *tmp_str;
+
+	opt_len = tlv_iter_get_len(tlv_iter);
+	opt_data = tlv_iter_get_data(tlv_iter);
+
+	tmp_str = malloc(opt_len + 1);
+	if (tmp_str == NULL) {
+		return (-1);
+	}
+
+	memcpy(tmp_str, opt_data, opt_len);
+	tmp_str[opt_len] = '\0';
+
+	*str = tmp_str;
+	*str_len = opt_len;
+
+	return (0);
+}
+
+int
+tlv_iter_decode_u16_array(struct tlv_iterator *tlv_iter, uint16_t **u16a, size_t *no_items)
+{
+	uint16_t opt_len;
+	uint16_t *u16a_res;
+	size_t i;
+
+	opt_len = tlv_iter_get_len(tlv_iter);
+
+	if (opt_len % sizeof(uint16_t) != 0) {
+		return (-1);
+	}
+
+	*no_items = opt_len / sizeof(uint16_t);
+
+	u16a_res = malloc(sizeof(uint16_t) * *no_items);
+	if (u16a_res == NULL) {
+		return (-2);
+	}
+
+	memcpy(u16a_res, tlv_iter_get_data(tlv_iter), opt_len);
+
+	for (i = 0; i < *no_items; i++) {
+		u16a_res[i] = ntohs(u16a_res[i]);
+	}
+
+	*u16a = u16a_res;
+
+	return (0);
+}
+
+int
+tlv_iter_decode_supported_options(struct tlv_iterator *tlv_iter, enum tlv_opt_type **supported_options,
+    size_t *no_supported_options)
+{
+	uint16_t *u16a;
+	enum tlv_opt_type *tlv_opt_array;
+	size_t i;
+	int res;
+
+	res = tlv_iter_decode_u16_array(tlv_iter, &u16a, no_supported_options);
+	if (res != 0) {
+		return (res);
+	}
+
+	tlv_opt_array = malloc(sizeof(enum tlv_opt_type) * *no_supported_options);
+	if (tlv_opt_array == NULL) {
+		free(u16a);
+		return (-2);
+	}
+
+	for (i = 0; i < *no_supported_options; i++) {
+		tlv_opt_array[i] = (enum tlv_opt_type)u16a[i];
+	}
+
+	free(u16a);
+
+	*supported_options = tlv_opt_array;
+
+	return (0);
+}
+
+int
+tlv_iter_decode_supported_decision_algorithms(struct tlv_iterator *tlv_iter,
+    enum tlv_decision_algorithm_type **supported_decision_algorithms, size_t *no_supported_decision_algorithms)
+{
+	uint16_t *u16a;
+	enum tlv_decision_algorithm_type *tlv_decision_algorithm_type_array;
+	size_t i;
+	int res;
+
+	res = tlv_iter_decode_u16_array(tlv_iter, &u16a, no_supported_decision_algorithms);
+	if (res != 0) {
+		return (res);
+	}
+
+	tlv_decision_algorithm_type_array = malloc(
+	    sizeof(enum tlv_decision_algorithm_type) * *no_supported_decision_algorithms);
+
+	if (tlv_decision_algorithm_type_array == NULL) {
+		free(u16a);
+		return (-2);
+	}
+
+	for (i = 0; i < *no_supported_decision_algorithms; i++) {
+		tlv_decision_algorithm_type_array[i] = (enum tlv_decision_algorithm_type)u16a[i];
+	}
+
+	free(u16a);
+
+	*supported_decision_algorithms = tlv_decision_algorithm_type_array;
+
+	return (0);
+}
+
+int
+tlv_iter_decode_u16(struct tlv_iterator *tlv_iter, uint16_t *u16)
+{
+	const char *opt_data;
+	uint16_t opt_len;
+	uint16_t nu16;
+
+	opt_len = tlv_iter_get_len(tlv_iter);
+	opt_data = tlv_iter_get_data(tlv_iter);
+
+	if (opt_len != sizeof(nu16)) {
+		return (-1);
+	}
+
+	memcpy(&nu16, opt_data, sizeof(nu16));
+	*u16 = ntohs(nu16);
+
+	return (0);
+}
+
+int
+tlv_iter_decode_reply_error_code(struct tlv_iterator *tlv_iter, enum tlv_reply_error_code *reply_error_code)
+{
+
+	return (tlv_iter_decode_u16(tlv_iter, (uint16_t *)reply_error_code));
+}
+
+int
+tlv_iter_decode_tls_supported(struct tlv_iterator *tlv_iter, enum tlv_tls_supported *tls_supported)
+{
+	uint8_t u8;
+
+	if (tlv_iter_decode_u8(tlv_iter, &u8) != 0) {
+		return (-1);
+	}
+
+	*tls_supported = u8;
+
+	if (*tls_supported != TLV_TLS_UNSUPPORTED &&
+	    *tls_supported != TLV_TLS_SUPPORTED &&
+	    *tls_supported != TLV_TLS_REQUIRED) {
+		return (-4);
+	}
+
+	return (0);
+}
+
+int
+tlv_iter_decode_decision_algorithm(struct tlv_iterator *tlv_iter, enum tlv_decision_algorithm_type *decision_algorithm)
+{
+	uint16_t u16;
+
+	if (tlv_iter_decode_u16(tlv_iter, &u16) != 0) {
+		return (-1);
+	}
+
+	*decision_algorithm = (enum tlv_decision_algorithm_type)u16;
+
+	return (0);
+}
+
+void
+tlv_get_supported_options(enum tlv_opt_type **supported_options, size_t *no_supported_options)
+{
+
+	*supported_options = tlv_static_supported_options;
+	*no_supported_options = TLV_STATIC_SUPPORTED_OPTIONS_SIZE;
+}

+ 186 - 0
qdevices/tlv.h

@@ -0,0 +1,186 @@
+/*
+ * Copyright (c) 2015 Red Hat, Inc.
+ *
+ * All rights reserved.
+ *
+ * Author: Jan Friesse (jfriesse@redhat.com)
+ *
+ * This software licensed under BSD license, the text of which follows:
+ *
+ * Redistribution and use in source and binary forms, with or without
+ * modification, are permitted provided that the following conditions are met:
+ *
+ * - Redistributions of source code must retain the above copyright notice,
+ *   this list of conditions and the following disclaimer.
+ * - 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.
+ * - Neither the name of the Red Hat, Inc. nor the names of its
+ *   contributors may be used to endorse or promote products derived from this
+ *   software without specific prior written permission.
+ *
+ * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS"
+ * AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
+ * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
+ * ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR CONTRIBUTORS BE
+ * LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR
+ * CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF
+ * SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS
+ * INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN
+ * CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE)
+ * ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF
+ * THE POSSIBILITY OF SUCH DAMAGE.
+ */
+
+#ifndef _TLV_H_
+#define _TLV_H_
+
+#include <sys/types.h>
+#include <inttypes.h>
+
+#include "dynar.h"
+
+#ifdef __cplusplus
+extern "C" {
+#endif
+
+enum tlv_opt_type {
+	TLV_OPT_MSG_SEQ_NUMBER = 0,
+	TLV_OPT_CLUSTER_NAME = 1,
+	TLV_OPT_TLS_SUPPORTED = 2,
+	TLV_OPT_TLS_CLIENT_CERT_REQUIRED = 3,
+	TLV_OPT_SUPPORTED_MESSAGES = 4,
+	TLV_OPT_SUPPORTED_OPTIONS = 5,
+	TLV_OPT_REPLY_ERROR_CODE = 6,
+	TLV_OPT_SERVER_MAXIMUM_REQUEST_SIZE = 7,
+	TLV_OPT_SERVER_MAXIMUM_REPLY_SIZE = 8,
+	TLV_OPT_NODE_ID = 9,
+	TLV_OPT_SUPPORTED_DECISION_ALGORITHMS = 10,
+	TLV_OPT_DECISION_ALGORITHM = 11,
+	TLV_OPT_HEARTBEAT_INTERVAL = 12,
+};
+
+enum tlv_tls_supported {
+	TLV_TLS_UNSUPPORTED = 0,
+	TLV_TLS_SUPPORTED = 1,
+	TLV_TLS_REQUIRED = 2,
+};
+
+enum tlv_reply_error_code {
+	TLV_REPLY_ERROR_CODE_NO_ERROR = 0,
+	TLV_REPLY_ERROR_CODE_UNSUPPORTED_NEEDED_MESSAGE = 1,
+	TLV_REPLY_ERROR_CODE_UNSUPPORTED_NEEDED_OPTION = 2,
+	TLV_REPLY_ERROR_CODE_TLS_REQUIRED = 3,
+	TLV_REPLY_ERROR_CODE_UNSUPPORTED_MESSAGE = 4,
+	TLV_REPLY_ERROR_CODE_MESSAGE_TOO_LONG = 5,
+	TLV_REPLY_ERROR_CODE_PREINIT_REQUIRED = 6,
+	TLV_REPLY_ERROR_CODE_DOESNT_CONTAIN_REQUIRED_OPTION = 7,
+	TLV_REPLY_ERROR_CODE_UNEXPECTED_MESSAGE = 8,
+	TLV_REPLY_ERROR_CODE_ERROR_DECODING_MSG = 9,
+	TLV_REPLY_ERROR_CODE_INTERNAL_ERROR = 10,
+	TLV_REPLY_ERROR_CODE_INIT_REQUIRED = 11,
+	TLV_REPLY_ERROR_CODE_UNSUPPORTED_DECISION_ALGORITHM = 12,
+	TLV_REPLY_ERROR_CODE_INVALID_HEARTBEAT_INTERVAL = 13,
+};
+
+enum tlv_decision_algorithm_type {
+	TLV_DECISION_ALGORITHM_TYPE_TEST = 0,
+};
+
+struct tlv_iterator {
+	const struct dynar *msg;
+	size_t current_pos;
+	size_t msg_header_len;
+};
+
+extern int			 tlv_add(struct dynar *msg, enum tlv_opt_type opt_type, uint16_t opt_len,
+    const void *value);
+
+extern int			 tlv_add_u32(struct dynar *msg, enum tlv_opt_type opt_type, uint32_t u32);
+
+extern int			 tlv_add_u8(struct dynar *msg, enum tlv_opt_type opt_type, uint8_t u8);
+
+extern int			 tlv_add_u16(struct dynar *msg, enum tlv_opt_type opt_type, uint16_t u16);
+
+extern int			 tlv_add_string(struct dynar *msg, enum tlv_opt_type opt_type, const char *str);
+
+extern int			 tlv_add_u16_array(struct dynar *msg, enum tlv_opt_type opt_type,
+    const uint16_t *array, size_t array_size);
+
+extern int			 tlv_add_supported_options(struct dynar *msg,
+    const enum tlv_opt_type *supported_options, size_t no_supported_options);
+
+extern int			 tlv_add_msg_seq_number(struct dynar *msg, uint32_t msg_seq_number);
+
+extern int			 tlv_add_cluster_name(struct dynar *msg, const char *cluster_name);
+
+extern int			 tlv_add_tls_supported(struct dynar *msg, enum tlv_tls_supported tls_supported);
+
+extern int			 tlv_add_tls_client_cert_required(struct dynar *msg, int tls_client_cert_required);
+
+extern int			 tlv_add_reply_error_code(struct dynar *msg, enum tlv_reply_error_code error_code);
+
+extern int			 tlv_add_node_id(struct dynar *msg, uint32_t node_id);
+
+extern int			 tlv_add_server_maximum_request_size(struct dynar *msg,
+    size_t server_maximum_request_size);
+
+extern int			 tlv_add_server_maximum_reply_size(struct dynar *msg,
+    size_t server_maximum_reply_size);
+
+extern int			 tlv_add_supported_decision_algorithms(struct dynar *msg,
+    const enum tlv_decision_algorithm_type *supported_algorithms, size_t no_supported_algorithms);
+
+extern int			 tlv_add_decision_algorithm(struct dynar *msg,
+    enum tlv_decision_algorithm_type decision_algorithm);
+
+extern int			 tlv_add_heartbeat_interval(struct dynar *msg, uint32_t heartbeat_interval);
+
+extern void			 tlv_iter_init(const struct dynar *msg, size_t msg_header_len,
+    struct tlv_iterator *tlv_iter);
+
+extern enum tlv_opt_type	 tlv_iter_get_type(const struct tlv_iterator *tlv_iter);
+
+extern uint16_t			 tlv_iter_get_len(const struct tlv_iterator *tlv_iter);
+
+extern const char		*tlv_iter_get_data(const struct tlv_iterator *tlv_iter);
+
+extern int			 tlv_iter_next(struct tlv_iterator *tlv_iter);
+
+extern int			 tlv_iter_decode_u8(struct tlv_iterator *tlv_iter, uint8_t *res);
+
+extern int			 tlv_iter_decode_tls_supported(struct tlv_iterator *tlv_iter,
+    enum tlv_tls_supported *tls_supported);
+
+extern int			 tlv_iter_decode_u32(struct tlv_iterator *tlv_iter, uint32_t *res);
+
+extern int			 tlv_iter_decode_str(struct tlv_iterator *tlv_iter, char **str, size_t *str_len);
+
+extern int			 tlv_iter_decode_client_cert_required(struct tlv_iterator *tlv_iter,
+    uint8_t *client_cert_required);
+
+extern int			 tlv_iter_decode_u16_array(struct tlv_iterator *tlv_iter,
+    uint16_t **u16a, size_t *no_items);
+
+extern int			 tlv_iter_decode_supported_options(struct tlv_iterator *tlv_iter,
+    enum tlv_opt_type **supported_options, size_t *no_supported_options);
+
+extern int			 tlv_iter_decode_supported_decision_algorithms(struct tlv_iterator *tlv_iter,
+    enum tlv_decision_algorithm_type **supported_decision_algorithms, size_t *no_supported_decision_algorithms);
+
+extern int			 tlv_iter_decode_u16(struct tlv_iterator *tlv_iter, uint16_t *u16);
+
+extern int			 tlv_iter_decode_reply_error_code(struct tlv_iterator *tlv_iter,
+    enum tlv_reply_error_code *reply_error_code);
+
+extern int			 tlv_iter_decode_decision_algorithm(struct tlv_iterator *tlv_iter,
+    enum tlv_decision_algorithm_type *decision_algorithm);
+
+extern void			 tlv_get_supported_options(enum tlv_opt_type **supported_options,
+    size_t *no_supported_options);
+
+#ifdef __cplusplus
+}
+#endif
+
+#endif /* _TLV_H_ */