// SPDX-License-Identifier: LGPL-2.1-or-later
/*
 * This file is part of the PCEPlib, a PCEP protocol library.
 *
 * Copyright (C) 2020 Volta Networks https://voltanet.io/
 *
 * Author : Brady Johnson <brady@voltanet.io>
 *
 */


/*
 *  Implementation of public API functions.
 */

#include <zebra.h>

#include <errno.h>
#include <fcntl.h>
#include <netdb.h> // gethostbyname
#include <stdbool.h>
#include <string.h>
#include <unistd.h> // close

#include <arpa/inet.h>	// sockets etc.
#include <sys/types.h>	// sockets etc.
#include <sys/socket.h> // sockets etc.

#include "pcep.h"
#include "pcep_socket_comm.h"
#include "pcep_socket_comm_internals.h"
#include "pcep_utils_logging.h"
#include "pcep_utils_memory.h"
#include "pcep_utils_ordered_list.h"
#include "pcep_utils_queue.h"

bool initialize_socket_comm_pre(void);
bool socket_comm_session_initialize_post(
	pcep_socket_comm_session *socket_comm_session);

pcep_socket_comm_handle *socket_comm_handle_ = NULL;


/* simple compare method callback used by pcep_utils_ordered_list
 * for ordered list insertion. */
int socket_fd_node_compare(void *list_entry, void *new_entry)
{
	return ((pcep_socket_comm_session *)new_entry)->socket_fd
	       - ((pcep_socket_comm_session *)list_entry)->socket_fd;
}


bool initialize_socket_comm_pre(void)
{
	socket_comm_handle_ =
		pceplib_malloc(PCEPLIB_INFRA, sizeof(pcep_socket_comm_handle));
	memset(socket_comm_handle_, 0, sizeof(pcep_socket_comm_handle));

	socket_comm_handle_->active = true;
	socket_comm_handle_->num_active_sessions = 0;
	socket_comm_handle_->read_list =
		ordered_list_initialize(socket_fd_node_compare);
	socket_comm_handle_->write_list =
		ordered_list_initialize(socket_fd_node_compare);
	socket_comm_handle_->session_list =
		ordered_list_initialize(pointer_compare_function);
	FD_ZERO(&socket_comm_handle_->except_master_set);
	FD_ZERO(&socket_comm_handle_->read_master_set);
	FD_ZERO(&socket_comm_handle_->write_master_set);

	if (pthread_mutex_init(&(socket_comm_handle_->socket_comm_mutex), NULL)
	    != 0) {
		pcep_log(LOG_ERR, "%s: Cannot initialize socket_comm mutex.",
			 __func__);
		pceplib_free(PCEPLIB_INFRA, socket_comm_handle_);
		socket_comm_handle_ = NULL;

		return false;
	}

	return true;
}

bool initialize_socket_comm_external_infra(
	void *external_infra_data, ext_socket_read socket_read_cb,
	ext_socket_write socket_write_cb,
	ext_socket_pthread_create_callback thread_create_func)
{
	if (socket_comm_handle_ != NULL) {
		/* already initialized */
		return true;
	}

	if (initialize_socket_comm_pre() == false) {
		return false;
	}

	/* Notice: If the thread_create_func is set, then both the
	 * socket_read_cb and the socket_write_cb SHOULD be NULL. */
	if (thread_create_func != NULL) {
		if (thread_create_func(
			    &(socket_comm_handle_->socket_comm_thread), NULL,
			    socket_comm_loop, socket_comm_handle_,
			    "pceplib_timers")) {
			pcep_log(
				LOG_ERR,
				"%s: Cannot initialize external socket_comm thread.",
				__func__);
			return false;
		}
	}

	socket_comm_handle_->external_infra_data = external_infra_data;
	socket_comm_handle_->socket_write_func = socket_write_cb;
	socket_comm_handle_->socket_read_func = socket_read_cb;

	return true;
}

bool initialize_socket_comm_loop(void)
{
	if (socket_comm_handle_ != NULL) {
		/* already initialized */
		return true;
	}

	if (initialize_socket_comm_pre() == false) {
		return false;
	}

	/* Launch socket comm loop pthread */
	if (pthread_create(&(socket_comm_handle_->socket_comm_thread), NULL,
			   socket_comm_loop, socket_comm_handle_)) {
		pcep_log(LOG_ERR, "%s: Cannot initialize socket_comm thread.",
			 __func__);
		return false;
	}

	return true;
}


bool destroy_socket_comm_loop(void)
{
	socket_comm_handle_->active = false;

	pthread_join(socket_comm_handle_->socket_comm_thread, NULL);
	ordered_list_destroy(socket_comm_handle_->read_list);
	ordered_list_destroy(socket_comm_handle_->write_list);
	ordered_list_destroy(socket_comm_handle_->session_list);
	pthread_mutex_destroy(&(socket_comm_handle_->socket_comm_mutex));

	pceplib_free(PCEPLIB_INFRA, socket_comm_handle_);
	socket_comm_handle_ = NULL;

	return true;
}

/* Internal common init function */
static pcep_socket_comm_session *socket_comm_session_initialize_pre(
	message_received_handler message_handler,
	message_ready_to_read_handler message_ready_handler,
	message_sent_notifier msg_sent_notifier,
	connection_except_notifier notifier, uint32_t connect_timeout_millis,
	const char *tcp_authentication_str, bool is_tcp_auth_md5,
	void *session_data)
{
	/* check that not both message handlers were set */
	if (message_handler != NULL && message_ready_handler != NULL) {
		pcep_log(
			LOG_WARNING,
			"%s: Only one of <message_received_handler | message_ready_to_read_handler> can be set.",
			__func__);
		return NULL;
	}

	/* check that at least one message handler was set */
	if (message_handler == NULL && message_ready_handler == NULL) {
		pcep_log(
			LOG_WARNING,
			"%s: At least one of <message_received_handler | message_ready_to_read_handler> must be set.",
			__func__);
		return NULL;
	}

	if (!initialize_socket_comm_loop()) {
		pcep_log(LOG_WARNING,
			 "%s: ERROR: cannot initialize socket_comm_loop.",
			 __func__);

		return NULL;
	}

	/* initialize everything for a pcep_session socket_comm */

	pcep_socket_comm_session *socket_comm_session =
		pceplib_malloc(PCEPLIB_INFRA, sizeof(pcep_socket_comm_session));
	memset(socket_comm_session, 0, sizeof(pcep_socket_comm_session));

	socket_comm_handle_->num_active_sessions++;
	socket_comm_session->close_after_write = false;
	socket_comm_session->session_data = session_data;
	socket_comm_session->message_handler = message_handler;
	socket_comm_session->message_ready_to_read_handler =
		message_ready_handler;
	socket_comm_session->message_sent_handler = msg_sent_notifier;
	socket_comm_session->conn_except_notifier = notifier;
	socket_comm_session->message_queue = queue_initialize();
	socket_comm_session->connect_timeout_millis = connect_timeout_millis;
	socket_comm_session->external_socket_data = NULL;
	if (tcp_authentication_str != NULL) {
		socket_comm_session->is_tcp_auth_md5 = is_tcp_auth_md5;
		strlcpy(socket_comm_session->tcp_authentication_str,
			tcp_authentication_str,
			sizeof(socket_comm_session->tcp_authentication_str));
	}

	return socket_comm_session;
}

/* Internal common init function */
bool socket_comm_session_initialize_post(
	pcep_socket_comm_session *socket_comm_session)
{
	/* If we dont use SO_REUSEADDR, the socket will take 2 TIME_WAIT
	 * periods before being closed in the kernel if bind() was called */
	int reuse_addr = 1;
	if (setsockopt(socket_comm_session->socket_fd, SOL_SOCKET, SO_REUSEADDR,
		       &reuse_addr, sizeof(int))
	    < 0) {
		pcep_log(
			LOG_WARNING,
			"%s: Error in setsockopt() SO_REUSEADDR errno [%d %s].",
			__func__, errno, strerror(errno));
		socket_comm_session_teardown(socket_comm_session);

		return false;
	}

	struct sockaddr *src_sock_addr =
		(socket_comm_session->is_ipv6
			 ? (struct sockaddr *)&(
				 socket_comm_session->src_sock_addr
					 .src_sock_addr_ipv6)
			 : (struct sockaddr *)&(
				 socket_comm_session->src_sock_addr
					 .src_sock_addr_ipv4));
	int addr_len = (socket_comm_session->is_ipv6
				? sizeof(socket_comm_session->src_sock_addr
						 .src_sock_addr_ipv6)
				: sizeof(socket_comm_session->src_sock_addr
						 .src_sock_addr_ipv4));
	if (bind(socket_comm_session->socket_fd, src_sock_addr, addr_len)
	    == -1) {
		pcep_log(LOG_WARNING,
			 "%s: Cannot bind address to socket errno [%d %s].",
			 __func__, errno, strerror(errno));
		socket_comm_session_teardown(socket_comm_session);

		return false;
	}

	/* Register the session as active with the Socket Comm Loop */
	pthread_mutex_lock(&(socket_comm_handle_->socket_comm_mutex));
	ordered_list_add_node(socket_comm_handle_->session_list,
			      socket_comm_session);
	pthread_mutex_unlock(&(socket_comm_handle_->socket_comm_mutex));

	/* dont connect to the destination yet, since the PCE will have a timer
	 * for max time between TCP connect and PCEP open. we'll connect later
	 * when we send the PCEP open. */

	return true;
}


pcep_socket_comm_session *socket_comm_session_initialize(
	message_received_handler message_handler,
	message_ready_to_read_handler message_ready_handler,
	message_sent_notifier msg_sent_notifier,
	connection_except_notifier notifier, struct in_addr *dest_ip,
	short dest_port, uint32_t connect_timeout_millis,
	const char *tcp_authentication_str, bool is_tcp_auth_md5,
	void *session_data)
{
	return socket_comm_session_initialize_with_src(
		message_handler, message_ready_handler, msg_sent_notifier,
		notifier, NULL, 0, dest_ip, dest_port, connect_timeout_millis,
		tcp_authentication_str, is_tcp_auth_md5, session_data);
}

pcep_socket_comm_session *socket_comm_session_initialize_ipv6(
	message_received_handler message_handler,
	message_ready_to_read_handler message_ready_handler,
	message_sent_notifier msg_sent_notifier,
	connection_except_notifier notifier, struct in6_addr *dest_ip,
	short dest_port, uint32_t connect_timeout_millis,
	const char *tcp_authentication_str, bool is_tcp_auth_md5,
	void *session_data)
{
	return socket_comm_session_initialize_with_src_ipv6(
		message_handler, message_ready_handler, msg_sent_notifier,
		notifier, NULL, 0, dest_ip, dest_port, connect_timeout_millis,
		tcp_authentication_str, is_tcp_auth_md5, session_data);
}


pcep_socket_comm_session *socket_comm_session_initialize_with_src(
	message_received_handler message_handler,
	message_ready_to_read_handler message_ready_handler,
	message_sent_notifier msg_sent_notifier,
	connection_except_notifier notifier, struct in_addr *src_ip,
	short src_port, struct in_addr *dest_ip, short dest_port,
	uint32_t connect_timeout_millis, const char *tcp_authentication_str,
	bool is_tcp_auth_md5, void *session_data)
{
	if (dest_ip == NULL) {
		pcep_log(LOG_WARNING, "%s: dest_ipv4 is NULL", __func__);
		return NULL;
	}

	pcep_socket_comm_session *socket_comm_session =
		socket_comm_session_initialize_pre(
			message_handler, message_ready_handler,
			msg_sent_notifier, notifier, connect_timeout_millis,
			tcp_authentication_str, is_tcp_auth_md5, session_data);
	if (socket_comm_session == NULL) {
		return NULL;
	}

	socket_comm_session->socket_fd =
		socket(PF_INET, SOCK_STREAM, IPPROTO_TCP);
	if (socket_comm_session->socket_fd == -1) {
		pcep_log(LOG_WARNING,
			 "%s: Cannot create ipv4 socket errno [%d %s].",
			 __func__, errno, strerror(errno));
		socket_comm_session_teardown(
			socket_comm_session); // socket_comm_session freed
					      // inside fn so NOLINT next.

		return NULL; // NOLINT(clang-analyzer-unix.Malloc)
	}

	socket_comm_session->is_ipv6 = false;
	socket_comm_session->dest_sock_addr.dest_sock_addr_ipv4.sin_family =
		AF_INET;
	socket_comm_session->src_sock_addr.src_sock_addr_ipv4.sin_family =
		AF_INET;
	socket_comm_session->dest_sock_addr.dest_sock_addr_ipv4.sin_port =
		htons(dest_port);
	socket_comm_session->src_sock_addr.src_sock_addr_ipv4.sin_port =
		htons(src_port);
	socket_comm_session->dest_sock_addr.dest_sock_addr_ipv4.sin_addr
		.s_addr = dest_ip->s_addr;
	if (src_ip != NULL) {
		socket_comm_session->src_sock_addr.src_sock_addr_ipv4.sin_addr
			.s_addr = src_ip->s_addr;
	} else {
		socket_comm_session->src_sock_addr.src_sock_addr_ipv4.sin_addr
			.s_addr = INADDR_ANY;
	}

	if (socket_comm_session_initialize_post(socket_comm_session) == false) {
		return NULL;
	}

	return socket_comm_session;
}

pcep_socket_comm_session *socket_comm_session_initialize_with_src_ipv6(
	message_received_handler message_handler,
	message_ready_to_read_handler message_ready_handler,
	message_sent_notifier msg_sent_notifier,
	connection_except_notifier notifier, struct in6_addr *src_ip,
	short src_port, struct in6_addr *dest_ip, short dest_port,
	uint32_t connect_timeout_millis, const char *tcp_authentication_str,
	bool is_tcp_auth_md5, void *session_data)
{
	if (dest_ip == NULL) {
		pcep_log(LOG_WARNING, "%s: dest_ipv6 is NULL", __func__);
		return NULL;
	}

	pcep_socket_comm_session *socket_comm_session =
		socket_comm_session_initialize_pre(
			message_handler, message_ready_handler,
			msg_sent_notifier, notifier, connect_timeout_millis,
			tcp_authentication_str, is_tcp_auth_md5, session_data);
	if (socket_comm_session == NULL) {
		return NULL;
	}

	socket_comm_session->socket_fd =
		socket(PF_INET6, SOCK_STREAM, IPPROTO_TCP);
	if (socket_comm_session->socket_fd == -1) {
		pcep_log(LOG_WARNING,
			 "%s: Cannot create ipv6 socket errno [%d %s].",
			 __func__, errno, strerror(errno));
		socket_comm_session_teardown(
			socket_comm_session); // socket_comm_session freed
					      // inside fn so NOLINT next.

		return NULL; // NOLINT(clang-analyzer-unix.Malloc)
	}

	socket_comm_session->is_ipv6 = true;
	socket_comm_session->dest_sock_addr.dest_sock_addr_ipv6.sin6_family =
		AF_INET6;
	socket_comm_session->src_sock_addr.src_sock_addr_ipv6.sin6_family =
		AF_INET6;
	socket_comm_session->dest_sock_addr.dest_sock_addr_ipv6.sin6_port =
		htons(dest_port);
	socket_comm_session->src_sock_addr.src_sock_addr_ipv6.sin6_port =
		htons(src_port);
	memcpy(&socket_comm_session->dest_sock_addr.dest_sock_addr_ipv6
			.sin6_addr,
	       dest_ip, sizeof(struct in6_addr));
	if (src_ip != NULL) {
		memcpy(&socket_comm_session->src_sock_addr.src_sock_addr_ipv6
				.sin6_addr,
		       src_ip, sizeof(struct in6_addr));
	} else {
		socket_comm_session->src_sock_addr.src_sock_addr_ipv6
			.sin6_addr = in6addr_any;
	}

	if (socket_comm_session_initialize_post(socket_comm_session) == false) {
		return NULL;
	}

	return socket_comm_session;
}


bool socket_comm_session_connect_tcp(
	pcep_socket_comm_session *socket_comm_session)
{
	if (socket_comm_session == NULL) {
		pcep_log(
			LOG_WARNING,
			"%s: socket_comm_session_connect_tcp NULL socket_comm_session.",
			__func__);
		return NULL;
	}

	/* Set the socket to non-blocking, so connect() does not block */
	int fcntl_arg;
	if ((fcntl_arg = fcntl(socket_comm_session->socket_fd, F_GETFL, NULL))
	    < 0) {
		pcep_log(LOG_WARNING, "%s: Error fcntl(..., F_GETFL) [%d %s]",
			 __func__, errno, strerror(errno));
		return false;
	}

	fcntl_arg |= O_NONBLOCK;
	if (fcntl(socket_comm_session->socket_fd, F_SETFL, fcntl_arg) < 0) {
		pcep_log(LOG_WARNING, "%s: Error fcntl(..., F_SETFL) [%d %s]",
			 __func__, errno, strerror(errno));
		return false;
	}

#if HAVE_DECL_TCP_MD5SIG
	/* TCP authentication, currently only TCP MD5 RFC2385 is supported */
	if (socket_comm_session->tcp_authentication_str[0] != '\0') {
#if defined(linux) || defined(GNU_LINUX)
		struct tcp_md5sig sig;
		memset(&sig, 0, sizeof(sig));
		if (socket_comm_session->is_ipv6) {
			memcpy(&sig.tcpm_addr,
			       &socket_comm_session->dest_sock_addr
					.dest_sock_addr_ipv6,
			       sizeof(struct sockaddr_in6));
		} else {
			memcpy(&sig.tcpm_addr,
			       &socket_comm_session->dest_sock_addr
					.dest_sock_addr_ipv4,
			       sizeof(struct sockaddr_in));
		}
		sig.tcpm_keylen =
			strlen(socket_comm_session->tcp_authentication_str);
		memcpy(sig.tcpm_key,
		       socket_comm_session->tcp_authentication_str,
		       sig.tcpm_keylen);
#else
		int sig = 1;
#endif
		if (setsockopt(socket_comm_session->socket_fd, IPPROTO_TCP,
			       TCP_MD5SIG, &sig, sizeof(sig))
		    == -1) {
			pcep_log(LOG_ERR, "%s: Failed to setsockopt(): [%d %s]",
				 __func__, errno, strerror(errno));
			return false;
		}
	}
#endif

	int connect_result = 0;
	if (socket_comm_session->is_ipv6) {
		connect_result = connect(
			socket_comm_session->socket_fd,
			(struct sockaddr *)&(socket_comm_session->dest_sock_addr
						     .dest_sock_addr_ipv6),
			sizeof(socket_comm_session->dest_sock_addr
				       .dest_sock_addr_ipv6));
	} else {
		connect_result = connect(
			socket_comm_session->socket_fd,
			(struct sockaddr *)&(socket_comm_session->dest_sock_addr
						     .dest_sock_addr_ipv4),
			sizeof(socket_comm_session->dest_sock_addr
				       .dest_sock_addr_ipv4));
	}

	if (connect_result < 0) {
		if (errno == EINPROGRESS) {
			/* Calculate the configured timeout in seconds and
			 * microseconds */
			struct timeval tv;
			if (socket_comm_session->connect_timeout_millis
			    > 1000) {
				tv.tv_sec = socket_comm_session
						    ->connect_timeout_millis
					    / 1000;
				tv.tv_usec = (socket_comm_session
						      ->connect_timeout_millis
					      - (tv.tv_sec * 1000))
					     * 1000;
			} else {
				tv.tv_sec = 0;
				tv.tv_usec = socket_comm_session
						     ->connect_timeout_millis
					     * 1000;
			}

			/* Use select to wait a max timeout for connect
			 * https://stackoverflow.com/questions/2597608/c-socket-connection-timeout
			 */
			fd_set fdset;
			FD_ZERO(&fdset);
			FD_SET(socket_comm_session->socket_fd, &fdset);
			if (select(socket_comm_session->socket_fd + 1, NULL,
				   &fdset, NULL, &tv)
			    > 0) {
				int so_error;
				socklen_t len = sizeof(so_error);
				getsockopt(socket_comm_session->socket_fd,
					   SOL_SOCKET, SO_ERROR, &so_error,
					   &len);
				if (so_error) {
					pcep_log(
						LOG_WARNING,
						"%s: TCP connect failed on socket_fd [%d].",
						__func__,
						socket_comm_session->socket_fd);
					return false;
				}
			} else {
				pcep_log(
					LOG_WARNING,
					"%s: TCP connect timed-out on socket_fd [%d].",
					__func__,
					socket_comm_session->socket_fd);
				return false;
			}
		} else {
			pcep_log(
				LOG_WARNING,
				"%s: TCP connect, error connecting on socket_fd [%d] errno [%d %s]",
				__func__, socket_comm_session->socket_fd, errno,
				strerror(errno));
			return false;
		}
	}

	pthread_mutex_lock(&(socket_comm_handle_->socket_comm_mutex));
	/* once the TCP connection is open, we should be ready to read at any
	 * time */
	ordered_list_add_node(socket_comm_handle_->read_list,
			      socket_comm_session);

	if (socket_comm_handle_->socket_read_func != NULL) {
		socket_comm_handle_->socket_read_func(
			socket_comm_handle_->external_infra_data,
			&socket_comm_session->external_socket_data,
			socket_comm_session->socket_fd, socket_comm_handle_);
	}
	pthread_mutex_unlock(&(socket_comm_handle_->socket_comm_mutex));

	return true;
}


bool socket_comm_session_close_tcp(
	pcep_socket_comm_session *socket_comm_session)
{
	if (socket_comm_session == NULL) {
		pcep_log(
			LOG_WARNING,
			"%s: socket_comm_session_close_tcp NULL socket_comm_session.",
			__func__);
		return false;
	}

	pcep_log(LOG_DEBUG,
		 "%s: socket_comm_session_close_tcp close() socket fd [%d]",
		 __func__, socket_comm_session->socket_fd);

	pthread_mutex_lock(&(socket_comm_handle_->socket_comm_mutex));
	ordered_list_remove_first_node_equals(socket_comm_handle_->read_list,
					      socket_comm_session);
	ordered_list_remove_first_node_equals(socket_comm_handle_->write_list,
					      socket_comm_session);
	// TODO should it be close() or shutdown()??
	close(socket_comm_session->socket_fd);
	socket_comm_session->socket_fd = -1;
	pthread_mutex_unlock(&(socket_comm_handle_->socket_comm_mutex));

	return true;
}


bool socket_comm_session_close_tcp_after_write(
	pcep_socket_comm_session *socket_comm_session)
{
	if (socket_comm_session == NULL) {
		pcep_log(
			LOG_WARNING,
			"%s: socket_comm_session_close_tcp_after_write NULL socket_comm_session.",
			__func__);
		return false;
	}

	pthread_mutex_lock(&(socket_comm_handle_->socket_comm_mutex));
	socket_comm_session->close_after_write = true;
	pthread_mutex_unlock(&(socket_comm_handle_->socket_comm_mutex));

	return true;
}


bool socket_comm_session_teardown(pcep_socket_comm_session *socket_comm_session)
{
	if (socket_comm_handle_ == NULL) {
		pcep_log(LOG_WARNING,
			 "%s: Cannot teardown NULL socket_comm_handle",
			 __func__);
		return false;
	}

	if (socket_comm_session == NULL) {
		pcep_log(LOG_WARNING, "%s: Cannot teardown NULL session",
			 __func__);
		return false;
	}

	if (comm_session_exists_locking(socket_comm_handle_,
					socket_comm_session)
	    == false) {
		pcep_log(LOG_WARNING,
			 "%s: Cannot teardown session that does not exist",
			 __func__);
		return false;
	}

	if (socket_comm_session->socket_fd >= 0) {
		shutdown(socket_comm_session->socket_fd, SHUT_RDWR);
		close(socket_comm_session->socket_fd);
	}

	pthread_mutex_lock(&(socket_comm_handle_->socket_comm_mutex));
	queue_destroy(socket_comm_session->message_queue);
	ordered_list_remove_first_node_equals(socket_comm_handle_->session_list,
					      socket_comm_session);
	ordered_list_remove_first_node_equals(socket_comm_handle_->read_list,
					      socket_comm_session);
	ordered_list_remove_first_node_equals(socket_comm_handle_->write_list,
					      socket_comm_session);
	socket_comm_handle_->num_active_sessions--;
	pthread_mutex_unlock(&(socket_comm_handle_->socket_comm_mutex));

	pcep_log(
		LOG_INFO,
		"%s: [%ld-%ld] socket_comm_session fd [%d] destroyed, [%d] sessions remaining",
		__func__, time(NULL), pthread_self(),
		socket_comm_session->socket_fd,
		socket_comm_handle_->num_active_sessions);

	pceplib_free(PCEPLIB_INFRA, socket_comm_session);

	/* It would be nice to call destroy_socket_comm_loop() here if
	 * socket_comm_handle_->num_active_sessions == 0, but this function
	 * will usually be called from the message_sent_notifier callback,
	 * which gets called in the middle of the socket_comm_loop, and that
	 * is dangerous, so destroy_socket_comm_loop() must be called upon
	 * application exit. */

	return true;
}


void socket_comm_session_send_message(
	pcep_socket_comm_session *socket_comm_session,
	const char *encoded_message, unsigned int msg_length,
	bool free_after_send)
{
	if (socket_comm_session == NULL) {
		pcep_log(
			LOG_WARNING,
			"%s: socket_comm_session_send_message NULL socket_comm_session.",
			__func__);
		return;
	}

	pcep_socket_comm_queued_message *queued_message = pceplib_malloc(
		PCEPLIB_MESSAGES, sizeof(pcep_socket_comm_queued_message));
	queued_message->encoded_message = encoded_message;
	queued_message->msg_length = msg_length;
	queued_message->free_after_send = free_after_send;

	pthread_mutex_lock(&(socket_comm_handle_->socket_comm_mutex));

	/* Do not proceed if the socket_comm_session has been deleted */
	if (ordered_list_find(socket_comm_handle_->session_list,
			      socket_comm_session)
	    == NULL) {
		/* Should never get here, only if the session was deleted and
		 * someone still tries to write on it */
		pcep_log(
			LOG_WARNING,
			"%s: Cannot write a message on a deleted socket comm session, discarding message",
			__func__);
		pthread_mutex_unlock(&(socket_comm_handle_->socket_comm_mutex));
		pceplib_free(PCEPLIB_MESSAGES, queued_message);

		return;
	}

	/* Do not proceed if the socket has been closed */
	if (socket_comm_session->socket_fd < 0) {
		/* Should never get here, only if the session was deleted and
		 * someone still tries to write on it */
		pcep_log(
			LOG_WARNING,
			"%s: Cannot write a message on a closed socket, discarding message",
			__func__);
		pthread_mutex_unlock(&(socket_comm_handle_->socket_comm_mutex));
		pceplib_free(PCEPLIB_MESSAGES, queued_message);

		return;
	}

	queue_enqueue(socket_comm_session->message_queue, queued_message);

	/* Add it to the write list only if its not already there */
	if (ordered_list_find(socket_comm_handle_->write_list,
			      socket_comm_session)
	    == NULL) {
		ordered_list_add_node(socket_comm_handle_->write_list,
				      socket_comm_session);
	}

	if (socket_comm_handle_->socket_write_func != NULL) {
		socket_comm_handle_->socket_write_func(
			socket_comm_handle_->external_infra_data,
			&socket_comm_session->external_socket_data,
			socket_comm_session->socket_fd, socket_comm_handle_);
	}
	pthread_mutex_unlock(&(socket_comm_handle_->socket_comm_mutex));
}
