/*
 * Copyright (c) 2015-2020 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 <poll.h>

#include "log.h"
#include "qdevice-model.h"
#include "qdevice-model-net.h"
#include "qdevice-net-cast-vote-timer.h"
#include "qdevice-net-instance.h"
#include "qdevice-net-ipc-cmd.h"
#include "qdevice-net-algorithm.h"
#include "qdevice-net-heuristics.h"
#include "qdevice-net-send.h"
#include "qdevice-net-socket.h"
#include "qdevice-net-votequorum.h"
#include "qnet-config.h"
#include "nss-sock.h"

/*
 * Callbacks
 */
static int
check_schedule_disconnect_cb(void *user_data1, void *user_data2)
{
	struct qdevice_net_instance *instance = (struct qdevice_net_instance *)user_data1;

	if (instance->schedule_disconnect) {
		return (-1);
	}

	return (0);
}

static int
qdevice_model_net_timer_connect_timeout(void *data1, void *data2)
{
	struct qdevice_net_instance *instance;

	instance = (struct qdevice_net_instance *)data1;

	log(LOG_ERR, "Connect timeout");

	instance->schedule_disconnect = 1;

	instance->connect_timer = NULL;
	instance->disconnect_reason = QDEVICE_NET_DISCONNECT_REASON_CANT_CONNECT_TO_THE_SERVER;

	return (0);
}

/*
 * Exported functions
 */
int
qdevice_model_net_init(struct qdevice_instance *instance)
{

	struct qdevice_net_instance *net_instance;

	log(LOG_DEBUG, "Initializing qdevice_net_instance");
	if (qdevice_net_instance_init_from_cmap(instance) != 0) {
		return (-1);
	}

	net_instance = instance->model_data;

	log(LOG_DEBUG, "Registering algorithms");
	if (qdevice_net_algorithm_register_all() != 0) {
		return (-1);
	}

	log(LOG_DEBUG, "Initializing NSS");
	if (nss_sock_init_nss((net_instance->tls_supported != TLV_TLS_UNSUPPORTED ?
	    instance->advanced_settings->net_nss_db_dir : NULL)) != 0) {
		log_nss(LOG_ERR, "Can't init nss");
		return (-1);
	}

	if (qdevice_net_cast_vote_timer_update(net_instance, TLV_VOTE_ASK_LATER) != 0) {
		log(LOG_ERR, "Can't update cast vote timer");
		return (-1);
	}

	if (qdevice_net_algorithm_init(net_instance) != 0) {
		log(LOG_ERR, "Algorithm init failed");
		return (-1);
	}

	if (qdevice_net_heuristics_init(net_instance) != 0) {
		log(LOG_ERR, "Can't initialize net heuristics");
		return (-1);
	}

	return (0);
}

int
qdevice_model_net_destroy(struct qdevice_instance *instance)
{
	struct qdevice_net_instance *net_instance;

	net_instance = instance->model_data;

	log(LOG_DEBUG, "Destroying algorithm");
	qdevice_net_algorithm_destroy(net_instance);

	log(LOG_DEBUG, "Destroying qdevice_net_instance");
	qdevice_net_instance_destroy(net_instance);

	log(LOG_DEBUG, "Shutting down NSS");
	SSL_ClearSessionCache();

	if (NSS_Shutdown() != SECSuccess) {
		log_nss(LOG_WARNING, "Can't shutdown NSS");
	}

	if (PR_Cleanup() != PR_SUCCESS) {
		log_nss(LOG_WARNING, "Can't shutdown NSPR");
	}

	free(net_instance);

	return (0);
}


static PRIntn
qdevice_model_net_get_af(const struct qdevice_net_instance *instance)
{
	PRIntn af;

	af = PR_AF_UNSPEC;
	if (instance->force_ip_version == 4) {
		af = PR_AF_INET;
	}

	if (instance->force_ip_version == 6) {
		af = PR_AF_INET6;
	}

	return (af);
}

/*
 *  0 - Continue
 * -1 - End loop
 */
int
qdevice_model_net_pre_poll_loop(struct qdevice_instance *instance)
{
	struct qdevice_net_instance *net_instance;
	int res;

	net_instance = instance->model_data;

	net_instance->state = QDEVICE_NET_INSTANCE_STATE_WAITING_CONNECT;
	net_instance->socket = NULL;

	net_instance->connect_timer = timer_list_add(pr_poll_loop_get_timer_list(&instance->main_poll_loop),
		net_instance->connect_timeout, qdevice_model_net_timer_connect_timeout,
		(void *)net_instance, NULL);

	if (net_instance->connect_timer == NULL) {
		log(LOG_CRIT, "Can't schedule connect timer");

		return (-1);
	}

	log(LOG_DEBUG, "Trying connect to qnetd server %s:%u (timeout = %ums)",
	    net_instance->host_addr, net_instance->host_port, net_instance->connect_timeout);

	res = nss_sock_non_blocking_client_init(net_instance->host_addr,
	    net_instance->host_port, qdevice_model_net_get_af(net_instance),
	    &net_instance->non_blocking_client);
	if (res == -1) {
		log_nss(LOG_ERR, "Can't initialize non blocking client connection");
	}

	res = nss_sock_non_blocking_client_try_next(&net_instance->non_blocking_client);
	if (res == -1) {
		log_nss(LOG_ERR, "Can't connect to qnetd host");
		/*
		 * To prevent adding non_blocking_client into loop but still keep loop running
		 */
		nss_sock_non_blocking_client_destroy(&net_instance->non_blocking_client);
	}

	res = qdevice_net_socket_add_to_main_poll_loop(net_instance);
	if (res == -1) {
		goto error_free_non_blocking_client;
	}

	res = pr_poll_loop_add_pre_poll_cb(&instance->main_poll_loop, check_schedule_disconnect_cb,
	    net_instance, NULL);
	if (res == -1) {
		log(LOG_CRIT, "Can't add pre poll callback to main loop");
		goto error_del_from_main_poll_loop;
	}

	return (0);

error_del_from_main_poll_loop:
	(void)qdevice_net_socket_del_from_main_poll_loop(net_instance);

error_free_non_blocking_client:
	nss_sock_non_blocking_client_destroy(&net_instance->non_blocking_client);
	return (-1);
}

/*
 *  1 - Restart loop
 *  0 - End loop with no error
 * -1 - End loop with error -1
 */
int
qdevice_model_net_post_poll_loop(struct qdevice_instance *instance,
    enum qdevice_model_post_poll_loop_exit_reason exit_reason)
{
	struct qdevice_net_instance *net_instance;
	int restart_loop;
	int ret_val;
	enum tlv_vote vote;
	int delay_before_reconnect;

	net_instance = instance->model_data;

	restart_loop = 1;
	ret_val = -1;

	if (net_instance->connect_timer != NULL) {
		timer_list_entry_delete(pr_poll_loop_get_timer_list(&instance->main_poll_loop),
		    net_instance->connect_timer);
		net_instance->connect_timer = NULL;
	}

	if (net_instance->echo_request_timer != NULL) {
		timer_list_entry_delete(pr_poll_loop_get_timer_list(&instance->main_poll_loop),
		    net_instance->echo_request_timer);
		net_instance->echo_request_timer = NULL;
	}

	/*
	 * Map qdevice exit_reason to qdevice-net disconnect reason
	 */
	switch (exit_reason) {
	case QDEVICE_MODEL_POST_POLL_LOOP_EXIT_REASON_MODEL:
		/*
		 * Disconnect reason should be already set
		 */
		break;
	case QDEVICE_MODEL_POST_POLL_LOOP_EXIT_REASON_VOTEQUORUM_CLOSED:
		net_instance->disconnect_reason = QDEVICE_NET_DISCONNECT_REASON_COROSYNC_CONNECTION_CLOSED;
		break;
	case QDEVICE_MODEL_POST_POLL_LOOP_EXIT_REASON_CMAP_CLOSED:
		net_instance->disconnect_reason = QDEVICE_NET_DISCONNECT_REASON_COROSYNC_CONNECTION_CLOSED;
		break;
	case QDEVICE_MODEL_POST_POLL_LOOP_EXIT_REASON_HEURISTICS_CLOSED:
		net_instance->disconnect_reason = QDEVICE_NET_DISCONNECT_REASON_HEURISTICS_WORKER_CLOSED;
		break;
	case QDEVICE_MODEL_POST_POLL_LOOP_EXIT_REASON_IPC_SOCKET_CLOSED:
		net_instance->disconnect_reason = QDEVICE_NET_DISCONNECT_REASON_LOCAL_SOCKET_CLOSED;
		break;
	/*
	 * Default not handled intentionally. Compiler shows warning when new exit reason is added
	 */
	}

	restart_loop = qdevice_net_disconnect_reason_try_reconnect(net_instance->disconnect_reason);

	/*
	 * Unpause cast vote timer, because if it is paused we cannot remove tracking
	 */
	qdevice_net_cast_vote_timer_set_paused(net_instance, 0);

	vote = TLV_VOTE_NO_CHANGE;

	if (qdevice_net_algorithm_disconnected(net_instance,
	    net_instance->disconnect_reason, &restart_loop, &vote) != 0) {
		log(LOG_ERR, "Algorithm returned error, force exit");
		return (-1);
	} else {
		log(LOG_DEBUG, "Algorithm result vote is %s",
		    tlv_vote_to_str(vote));
	}

	if (qdevice_net_cast_vote_timer_update(net_instance, vote) != 0) {
		log(LOG_ERR, "qdevice_model_net_run fatal error. "
		    " Can't update cast vote timer vote");
	}

	if (qdevice_net_disconnect_reason_force_disconnect(net_instance->disconnect_reason)) {
		restart_loop = 0;
	}

	/*
	 * Return 0 only when local socket was closed -> regular exit
	 */
	if (net_instance->disconnect_reason == QDEVICE_NET_DISCONNECT_REASON_LOCAL_SOCKET_CLOSED) {
		ret_val = 0;
	}

	/*
	 * Remove pre poll cb
	 */
	if (pr_poll_loop_del_pre_poll_cb(&instance->main_poll_loop, check_schedule_disconnect_cb) == -1) {
		log(LOG_ERR, "Can't delete pre poll callback from main loop");
		restart_loop = 0;
		ret_val = -1;
	}

	/*
	 * Remove socket from loop
	 */
	if (qdevice_net_socket_del_from_main_poll_loop(net_instance) == -1) {
		restart_loop = 0;
		ret_val = -1;
	}

	if (net_instance->socket != NULL) {
		if (PR_Close(net_instance->socket) != PR_SUCCESS) {
			log_nss(LOG_WARNING, "Unable to close connection");
		}
		net_instance->socket = NULL;
	}

	if (!net_instance->non_blocking_client.destroyed) {
		nss_sock_non_blocking_client_destroy(&net_instance->non_blocking_client);
	}

	if (net_instance->non_blocking_client.socket != NULL) {
		if (PR_Close(net_instance->non_blocking_client.socket) != PR_SUCCESS) {
			log_nss(LOG_WARNING, "Unable to close non-blocking client connection");
		}
		net_instance->non_blocking_client.socket = NULL;
	}

	if (restart_loop &&
	    net_instance->state != QDEVICE_NET_INSTANCE_STATE_WAITING_CONNECT) {
		/*
		 * Give qnetd server a little time before reconnect.
		 *
		 * Coverity reports weak_crypto error on following line, what's not a problem
		 * because cryptographically secure pseudorandom number generator is not needed.
		 */
		// coverity[DC.WEAK_CRYPTO:SUPPRESS] random is not used in a security context
		delay_before_reconnect = random() %
		    (int)(net_instance->cast_vote_timer_interval * 0.9);

		log(LOG_DEBUG, "Sleeping for %u ms before reconnect",
		    delay_before_reconnect);
		(void)poll(NULL, 0, delay_before_reconnect);
	}


	qdevice_net_instance_clean(net_instance);

	if (restart_loop) {
		return (1);
	}

	return (ret_val);
}

/*
 * Called when cmap reload (or nodelist) was requested.
 *
 * nlist is node list
 * config_version is valid only if config_version_set != 0
 *
 * Should return 0 if processing should continue or -1 to call exit
 */
int
qdevice_model_net_config_node_list_changed(struct qdevice_instance *instance,
    const struct node_list *nlist, int config_version_set, uint64_t config_version)
{
	struct qdevice_net_instance *net_instance;
	int send_node_list;
	enum tlv_vote vote;

	net_instance = instance->model_data;

	if (net_instance->state != QDEVICE_NET_INSTANCE_STATE_WAITING_VOTEQUORUM_CMAP_EVENTS) {
		/*
		 * Nodelist changed, but connection to qnetd not initiated yet.
		 */
		send_node_list = 0;

		if (net_instance->cast_vote_timer_vote == TLV_VOTE_ACK) {
			vote = TLV_VOTE_NACK;
		} else {
			vote = TLV_VOTE_NO_CHANGE;
		}
	} else {
		send_node_list = 1;
		vote = TLV_VOTE_NO_CHANGE;
	}

	if (qdevice_net_algorithm_config_node_list_changed(net_instance, nlist, config_version_set,
	    config_version, &send_node_list, &vote) != 0) {
		log(LOG_ERR, "Algorithm returned error, Disconnecting");

		net_instance->disconnect_reason = QDEVICE_NET_DISCONNECT_REASON_ALGO_CONFIG_NODE_LIST_CHANGED_ERR;
		net_instance->schedule_disconnect = 1;

		return (0);
	} else {
		log(LOG_DEBUG, "Algorithm decided to %s node list and result vote is %s",
		    (send_node_list ? "send" : "not send"), tlv_vote_to_str(vote));
	}

	if (qdevice_net_cast_vote_timer_update(net_instance, vote) != 0) {
		log(LOG_CRIT, "qdevice_model_net_config_node_list_changed fatal error. "
				" Can't update cast vote timer vote");
		net_instance->disconnect_reason = QDEVICE_NET_DISCONNECT_REASON_CANT_SCHEDULE_VOTING_TIMER;
		net_instance->schedule_disconnect = 1;

		return (0);
	}

	if (send_node_list) {
		if (qdevice_net_send_config_node_list(net_instance, nlist, config_version_set,
		    config_version, 0) != 0) {
			net_instance->schedule_disconnect = 1;
			net_instance->disconnect_reason = QDEVICE_NET_DISCONNECT_REASON_CANT_ALLOCATE_MSG_BUFFER;

			return (0);
		}
	}

	return (0);
}

/*
 * Called when cmap reload (or nodelist) was requested, but it was not possible to
 * get node list.
 *
 * Should return 0 if processing should continue or -1 to call exit
 */
int
qdevice_model_net_get_config_node_list_failed(struct qdevice_instance *instance)
{
	struct qdevice_net_instance *net_instance;

	net_instance = instance->model_data;

	net_instance->schedule_disconnect = 1;
	net_instance->disconnect_reason = QDEVICE_NET_DISCONNECT_REASON_CANT_ALLOCATE_MSG_BUFFER;

	return (0);
}

int
qdevice_model_net_votequorum_quorum_notify(struct qdevice_instance *instance,
    uint32_t quorate, uint32_t node_list_entries, votequorum_node_t node_list[])
{
	struct qdevice_net_instance *net_instance;
	int send_node_list;
	enum tlv_vote vote;

	net_instance = instance->model_data;

	if (net_instance->state != QDEVICE_NET_INSTANCE_STATE_WAITING_VOTEQUORUM_CMAP_EVENTS) {
		/*
		 * Nodelist changed, but connection to qnetd not initiated yet.
		 */
		send_node_list = 0;

		if (net_instance->cast_vote_timer_vote == TLV_VOTE_ACK) {
			vote = TLV_VOTE_NACK;
		} else {
			vote = TLV_VOTE_NO_CHANGE;
		}
	} else {
		send_node_list = 1;
		vote = TLV_VOTE_NO_CHANGE;
	}

	if (qdevice_net_algorithm_votequorum_quorum_notify(net_instance, quorate,
	    node_list_entries, node_list, &send_node_list, &vote) != 0) {
		log(LOG_ERR, "Algorithm returned error. Disconnecting.");

		net_instance->disconnect_reason = QDEVICE_NET_DISCONNECT_REASON_ALGO_VOTEQUORUM_QUORUM_NOTIFY_ERR;
		net_instance->schedule_disconnect = 1;

		return (0);
	} else {
		log(LOG_DEBUG, "Algorithm decided to %s list and result vote is %s",
		    (send_node_list ? "send" : "not send"), tlv_vote_to_str(vote));
	}

	if (qdevice_net_cast_vote_timer_update(net_instance, vote) != 0) {
		log(LOG_CRIT, "qdevice_model_net_votequorum_quorum_notify fatal error. "
				" Can't update cast vote timer vote");
		net_instance->disconnect_reason = QDEVICE_NET_DISCONNECT_REASON_CANT_SCHEDULE_VOTING_TIMER;
		net_instance->schedule_disconnect = 1;

		return (0);
	}

	if (send_node_list) {
		if (qdevice_net_send_quorum_node_list(net_instance,
		    (quorate ? TLV_QUORATE_QUORATE : TLV_QUORATE_INQUORATE),
		    node_list_entries, node_list) != 0) {
			/*
			 * Fatal error -> schedule disconnect
			 */
			net_instance->disconnect_reason = QDEVICE_NET_DISCONNECT_REASON_CANT_ALLOCATE_MSG_BUFFER;
			net_instance->schedule_disconnect = 1;

			return (0);
		}
	}

	return (0);
}

int
qdevice_model_net_votequorum_node_list_heuristics_notify(struct qdevice_instance *instance,
    votequorum_ring_id_t votequorum_ring_id, uint32_t node_list_entries, uint32_t node_list[],
    enum qdevice_heuristics_exec_result heuristics_exec_result)
{
	struct qdevice_net_instance *net_instance;
	struct tlv_ring_id tlv_rid;
	enum tlv_vote vote;
	enum tlv_heuristics heuristics;
	int send_node_list;

	net_instance = instance->model_data;

	qdevice_net_votequorum_ring_id_to_tlv(&tlv_rid, &votequorum_ring_id);
	heuristics = qdevice_net_heuristics_exec_result_to_tlv(heuristics_exec_result);

	if (net_instance->state != QDEVICE_NET_INSTANCE_STATE_WAITING_VOTEQUORUM_CMAP_EVENTS) {
		/*
		 * Nodelist changed, but connection to qnetd not initiated yet.
		 */
		send_node_list = 0;

		if (net_instance->cast_vote_timer_vote == TLV_VOTE_ACK) {
			vote = TLV_VOTE_NACK;
		} else {
			vote = TLV_VOTE_NO_CHANGE;
		}
	} else {
		send_node_list = 1;
		vote = TLV_VOTE_WAIT_FOR_REPLY;
	}

	if (qdevice_net_algorithm_votequorum_node_list_heuristics_notify(net_instance, &tlv_rid,
	    node_list_entries, node_list, &send_node_list, &vote, &heuristics) != 0) {
		log(LOG_ERR, "Algorithm returned error. Disconnecting.");

		net_instance->disconnect_reason =
		    QDEVICE_NET_DISCONNECT_REASON_ALGO_VOTEQUORUM_NODE_LIST_HEURISTICS_NOTIFY_ERR;
		net_instance->schedule_disconnect = 1;

		return (0);
	} else {
		log(LOG_DEBUG, "Algorithm decided to %s list, result vote is %s and heuristics is %s",
		    (send_node_list ? "send" : "not send"), tlv_vote_to_str(vote),
		    tlv_heuristics_to_str(heuristics));
	}

	if (send_node_list) {
		if (qdevice_net_send_membership_node_list(net_instance, &tlv_rid,
		    node_list_entries, node_list, heuristics) != 0) {
			/*
			 * Fatal error -> schedule disconnect
			 */
			net_instance->disconnect_reason = QDEVICE_NET_DISCONNECT_REASON_CANT_ALLOCATE_MSG_BUFFER;
			net_instance->schedule_disconnect = 1;

			return (0);
		}
	}

	/*
	 * Unpause cast vote timer
	 */
	qdevice_net_cast_vote_timer_set_paused(net_instance, 0);

	if (qdevice_net_cast_vote_timer_update(net_instance, vote) != 0) {
		log(LOG_CRIT, "qdevice_model_net_votequorum_node_list_notify fatal error "
		    "Can't update cast vote timer");
		net_instance->disconnect_reason = QDEVICE_NET_DISCONNECT_REASON_CANT_SCHEDULE_VOTING_TIMER;
		net_instance->schedule_disconnect = 1;

		return (0);
	}

	net_instance->latest_vq_heuristics_result = heuristics;
	net_instance->latest_heuristics_result = heuristics;

	if (qdevice_net_heuristics_schedule_timer(net_instance) != 0) {
		return (0);
	}

	return (0);
}

int
qdevice_model_net_votequorum_node_list_notify(struct qdevice_instance *instance,
    votequorum_ring_id_t votequorum_ring_id, uint32_t node_list_entries, uint32_t node_list[])
{
	struct qdevice_net_instance *net_instance;
	struct tlv_ring_id tlv_rid;
	enum tlv_vote vote;
	int pause_cast_vote_timer;

	net_instance = instance->model_data;

	/*
	 * Stop regular heuristics till qdevice_model_net_votequorum_node_list_heuristics_notify
	 * is called
	 */
	if (qdevice_net_heuristics_stop_timer(net_instance) != 0) {
		return (0);
	}

	pause_cast_vote_timer = 1;
	vote = TLV_VOTE_NO_CHANGE;

	if (net_instance->state != QDEVICE_NET_INSTANCE_STATE_WAITING_VOTEQUORUM_CMAP_EVENTS &&
	    net_instance->cast_vote_timer_vote == TLV_VOTE_ACK) {
		/*
		 * Nodelist changed and vote timer still votes ACK. It's needed to start voting
		 * NACK.
		 */
		if (net_instance->cast_vote_timer_vote == TLV_VOTE_ACK) {
			vote = TLV_VOTE_NACK;
		}
	}

	qdevice_net_votequorum_ring_id_to_tlv(&tlv_rid, &votequorum_ring_id);

	if (qdevice_net_algorithm_votequorum_node_list_notify(net_instance, &tlv_rid,
	    node_list_entries, node_list, &pause_cast_vote_timer, &vote) != 0) {
		log(LOG_ERR, "Algorithm returned error. Disconnecting.");

		net_instance->disconnect_reason = QDEVICE_NET_DISCONNECT_REASON_ALGO_VOTEQUORUM_NODE_LIST_NOTIFY_ERR;
		net_instance->schedule_disconnect = 1;

		return (0);
	} else {
		log(LOG_DEBUG, "Algorithm decided to %s cast vote timer and result vote is %s ",
		    (pause_cast_vote_timer ? "pause" : "not pause"), tlv_vote_to_str(vote));
	}

	if (qdevice_net_cast_vote_timer_update(net_instance, vote) != 0) {
		log(LOG_CRIT, "qdevice_model_net_votequorum_node_list_notify fatal error "
		    "Can't update cast vote timer");
		net_instance->disconnect_reason = QDEVICE_NET_DISCONNECT_REASON_CANT_SCHEDULE_VOTING_TIMER;
		net_instance->schedule_disconnect = 1;

		return (0);
	}

	qdevice_net_cast_vote_timer_set_paused(net_instance, pause_cast_vote_timer);

	return (0);
}

int
qdevice_model_net_votequorum_expected_votes_notify(struct qdevice_instance *instance,
    uint32_t expected_votes)
{
	struct qdevice_net_instance *net_instance;
	enum tlv_vote vote;

	net_instance = instance->model_data;

	log(LOG_DEBUG, "qdevice_model_net_votequorum_expected_votes_notify"
	    " (expected votes old=%"PRIu32" / new=%"PRIu32")",
	    net_instance->qdevice_instance_ptr->vq_expected_votes, expected_votes);

	vote = TLV_VOTE_NO_CHANGE;

	if (qdevice_net_algorithm_votequorum_expected_votes_notify(net_instance, expected_votes,
	    &vote) != 0) {
		log(LOG_DEBUG, "Algorithm returned error. Disconnecting.");

		net_instance->disconnect_reason = QDEVICE_NET_DISCONNECT_REASON_ALGO_VOTEQUORUM_EXPECTED_VOTES_NOTIFY_ERR;
		net_instance->schedule_disconnect = 1;

		return (0);
	} else {
		log(LOG_DEBUG, "Algorithm result vote is %s", tlv_vote_to_str(vote));
	}

	if (qdevice_net_cast_vote_timer_update(net_instance, vote) != 0) {
		log(LOG_CRIT, "qdevice_model_net_votequorum_expected_votes_notify fatal error. "
				" Can't update cast vote timer vote");
		net_instance->disconnect_reason = QDEVICE_NET_DISCONNECT_REASON_CANT_SCHEDULE_VOTING_TIMER;
		net_instance->schedule_disconnect = 1;

		return (0);
	}

	return (0);
}

int
qdevice_model_net_cmap_changed(struct qdevice_instance *instance,
    const struct qdevice_cmap_change_events *events)
{
	struct qdevice_net_instance *net_instance;
	enum qdevice_heuristics_mode active_heuristics_mode;
	int heuristics_enabled;

	net_instance = instance->model_data;

	if (events->heuristics) {
		active_heuristics_mode = instance->heuristics_instance.mode;
		heuristics_enabled = (active_heuristics_mode == QDEVICE_HEURISTICS_MODE_ENABLED ||
		    active_heuristics_mode == QDEVICE_HEURISTICS_MODE_SYNC);

		if (net_instance->state == QDEVICE_NET_INSTANCE_STATE_WAITING_VOTEQUORUM_CMAP_EVENTS &&
		    !net_instance->server_supports_heuristics && heuristics_enabled) {
			log(LOG_ERR, "Heuristics are enabled but not supported by the server");

			net_instance->disconnect_reason =
			    QDEVICE_NET_DISCONNECT_REASON_SERVER_DOESNT_SUPPORT_REQUIRED_OPT;

			net_instance->schedule_disconnect = 1;

			return (0);
		}

		if (qdevice_net_heuristics_schedule_timer(net_instance) != 0) {
			return (0);
		}
	}

	return (0);
}

int
qdevice_model_net_ipc_cmd_status(struct qdevice_instance *instance,
    struct dynar *outbuf, int verbose)
{
	struct qdevice_net_instance *net_instance;

	net_instance = instance->model_data;

	if (!qdevice_net_ipc_cmd_status(net_instance, outbuf, verbose)) {
		return (-1);
	}

	return (0);
}

static struct qdevice_model qdevice_model_net = {
	.name					= "net",
	.init					= qdevice_model_net_init,
	.destroy				= qdevice_model_net_destroy,
	.pre_poll_loop				= qdevice_model_net_pre_poll_loop,
	.post_poll_loop				= qdevice_model_net_post_poll_loop,
	.get_config_node_list_failed		= qdevice_model_net_get_config_node_list_failed,
	.config_node_list_changed		= qdevice_model_net_config_node_list_changed,
	.votequorum_quorum_notify		= qdevice_model_net_votequorum_quorum_notify,
	.votequorum_node_list_notify		= qdevice_model_net_votequorum_node_list_notify,
	.votequorum_node_list_heuristics_notify	= qdevice_model_net_votequorum_node_list_heuristics_notify,
	.votequorum_expected_votes_notify	= qdevice_model_net_votequorum_expected_votes_notify,
	.cmap_changed				= qdevice_model_net_cmap_changed,
	.ipc_cmd_status				= qdevice_model_net_ipc_cmd_status,
};

int
qdevice_model_net_register(void)
{

	return (qdevice_model_register(QDEVICE_MODEL_TYPE_NET, &qdevice_model_net));
}
