/*
 * Copyright (c) 2001-2022 NVIDIA CORPORATION & AFFILIATES. All rights reserved.
 *
 * This software is available to you under a choice of one of two
 * licenses.  You may choose to be licensed under the terms of the GNU
 * General Public License (GPL) Version 2, available from the file
 * COPYING in the main directory of this source tree, or the
 * BSD license below:
 *
 *     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.
 *
 * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
 * EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
 * MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND
 * NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS
 * BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN
 * ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN
 * CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
 * SOFTWARE.
 */


#include <sys/epoll.h>

#include <vma/iomux/epfd_info.h>
#include <vlogger/vlogger.h>
#include "utils/bullseye.h"
#include "sock-redirect.h"

#include "socket_fd_api.h"

#define MODULE_NAME 		"sapi"
#undef  MODULE_HDR_INFO
#define MODULE_HDR_INFO 	MODULE_NAME "[fd=%d]:%d:%s() "
#undef	__INFO__
#define __INFO__		m_fd

#if _BullseyeCoverage
    #pragma BullseyeCoverage off
#endif

socket_fd_api::socket_fd_api(int fd) : m_epoll_event_flags(0), m_fd(fd), m_n_sysvar_select_poll_os_ratio(safe_mce_sys().select_poll_os_ratio), m_econtext(NULL)
{
}

socket_fd_api::~socket_fd_api()
{
}


void socket_fd_api::destructor_helper()
{
}

int socket_fd_api::shutdown(int __how)
{
	__log_info_func("");
	int ret = orig_os_api.shutdown(m_fd, __how);
	if (ret) {
		__log_info_dbg("shutdown failed (ret=%d %m)", ret);
	}
	return ret;
}

int socket_fd_api::bind(const sockaddr *__addr, socklen_t __addrlen)
{
	__log_info_func("");
	int ret = orig_os_api.bind(m_fd, __addr, __addrlen);
	if (ret) {
		__log_info_dbg("bind failed (ret=%d %m)", ret);
	}
	return ret;
}

int socket_fd_api::connect(const sockaddr *__to, socklen_t __tolen)
{
	__log_info_func("");
	int ret = orig_os_api.connect(m_fd, __to, __tolen);
	if (ret) {
		__log_info_dbg("connect failed (ret=%d %m)", ret);
	}
	return ret;
}

int socket_fd_api::accept(struct sockaddr *__addr, socklen_t *__addrlen)
{
       __log_info_func("");
       int ret = orig_os_api.accept(m_fd, __addr, __addrlen);
       if (ret < 0) {
               __log_info_dbg("accept failed (ret=%d %m)", ret);
       }
       return ret;
}

int socket_fd_api::accept4(struct sockaddr *__addr, socklen_t *__addrlen, int __flags)
{
       __log_info_func("");
       int ret = orig_os_api.accept4(m_fd, __addr, __addrlen, __flags);
       if (ret < 0) {
               __log_info_dbg("accept4 failed (ret=%d %m)", ret);
       }
       return ret;
}

int socket_fd_api::listen(int backlog)
{
       __log_info_func("");
       int ret = orig_os_api.listen(m_fd, backlog);
       if (ret < 0) {
               __log_info_dbg("listen failed (ret=%d %m)", ret);
       }
       return ret;
}

int socket_fd_api::getsockname(sockaddr *__name, socklen_t *__namelen)
{
	__log_info_func("");
	int ret = orig_os_api.getsockname(m_fd, __name, __namelen);
	if (ret) {
		__log_info_dbg("getsockname failed (ret=%d %m)", ret);
	}
	return ret;
}

int socket_fd_api::getpeername(sockaddr *__name, socklen_t *__namelen)
{
	__log_info_func("");
	int ret = orig_os_api.getpeername(m_fd, __name, __namelen);
	if (ret) {
		__log_info_dbg("getpeername failed (ret=%d %m)", ret);
	}
	return ret;
}

int socket_fd_api::setsockopt(int __level, int __optname,
			      __const void *__optval, socklen_t __optlen)
{
	__log_info_func("");
	int ret = orig_os_api.setsockopt(m_fd, __level, __optname, __optval, __optlen);
	if (ret) {
		__log_info_dbg("setsockopt failed (ret=%d %m)", ret);
	}
	return ret;
}

int socket_fd_api::getsockopt(int __level, int __optname, void *__optval,
			      socklen_t *__optlen)
{
	__log_info_func("");
	int ret = orig_os_api.getsockopt(m_fd, __level, __optname, __optval, __optlen);
	if (ret) {
		__log_info_dbg("getsockopt failed (ret=%d %m)", ret);
	}
	return ret;
}


bool socket_fd_api::is_readable(uint64_t *p_poll_sn, fd_array_t* p_fd_array)
{
	NOT_IN_USE(p_poll_sn);
	NOT_IN_USE(p_fd_array);
	__log_info_funcall("");
	return false;
}

void socket_fd_api::set_immediate_os_sample()
{
	__log_info_funcall("");
	return;
}

void socket_fd_api::unset_immediate_os_sample()
{
	__log_info_funcall("");
	return;
}

bool socket_fd_api::is_writeable()
{
	__log_info_funcall("");
	return true;
}

bool socket_fd_api::is_errorable(int *errors)
{
	NOT_IN_USE(errors);
	__log_info_funcall("");
	return false;
}

void socket_fd_api::statistics_print(vlog_levels_t log_level /* = VLOG_DEBUG */)
{
	int epoll_fd = get_epoll_context_fd();

	// Socket data
	vlog_printf(log_level, "Fd number : %d\n", m_fd);
	if (epoll_fd) {
		vlog_printf(log_level, "Socket epoll Fd : %d\n", epoll_fd);
		vlog_printf(log_level, "Socket epoll flags : 0x%x\n", m_fd_rec.events);
	}

}

ssize_t socket_fd_api::rx_os(const rx_call_t call_type, iovec* p_iov,
			     ssize_t sz_iov, const int flags, sockaddr *__from,
			     socklen_t *__fromlen, struct msghdr *__msg)
{
	errno = 0;
	switch (call_type) {
	case RX_READ:
		__log_info_func("calling os receive with orig read");
		return orig_os_api.read(m_fd, p_iov[0].iov_base, p_iov[0].iov_len);

	case RX_READV:
		__log_info_func("calling os receive with orig readv");
		return orig_os_api.readv(m_fd, p_iov, sz_iov);

	case RX_RECV:
		__log_info_func("calling os receive with orig recv");
		return orig_os_api.recv(m_fd, p_iov[0].iov_base, p_iov[0].iov_len,
		                        flags);

	case RX_RECVFROM:
		__log_info_func("calling os receive with orig recvfrom");
		return orig_os_api.recvfrom(m_fd, p_iov[0].iov_base, p_iov[0].iov_len,
		                            flags, __from, __fromlen);

	case RX_RECVMSG: {
		__log_info_func("calling os receive with orig recvmsg");
		return orig_os_api.recvmsg(m_fd, __msg, flags);
		}
	}
	return (ssize_t) -1;
}

ssize_t socket_fd_api::tx_os(const tx_call_t call_type,
			     const iovec* p_iov, const ssize_t sz_iov,
			     const int __flags, const sockaddr *__to,
			     const socklen_t __tolen)
{
	errno = 0;

	// Ignore dummy messages for OS
	if (unlikely(IS_DUMMY_PACKET(__flags))) {
		errno = EINVAL;
		return -1;
	}

	switch (call_type) {
	case TX_WRITE:
		__log_info_func("calling os transmit with orig write");
		return orig_os_api.write(m_fd, p_iov[0].iov_base, p_iov[0].iov_len);

	case TX_WRITEV:
		__log_info_func("calling os transmit with orig writev");
		return orig_os_api.writev(m_fd, p_iov, sz_iov);

	case TX_SEND:
		__log_info_func("calling os transmit with orig send");
		return orig_os_api.send(m_fd, p_iov[0].iov_base, p_iov[0].iov_len,
		                        __flags);

	case TX_SENDTO:
		__log_info_func("calling os transmit with orig sendto");
		return orig_os_api.sendto(m_fd, p_iov[0].iov_base, p_iov[0].iov_len,
		                          __flags, __to, __tolen);

	case TX_SENDMSG: {
		msghdr __message;
		memset(&__message, 0, sizeof(__message));
		__message.msg_iov = (iovec*) p_iov;
		__message.msg_iovlen = sz_iov;
		__message.msg_name = (void*) __to;
		__message.msg_namelen = __tolen;

		__log_info_func("calling os transmit with orig sendmsg");
		return orig_os_api.sendmsg(m_fd, &__message, __flags);
		}
	default:
		__log_info_func("calling undefined os call type!");
		break;
	}
	return (ssize_t) -1;
}

int socket_fd_api::register_callback(vma_recv_callback_t callback, void *context)
{
	NOT_IN_USE(callback);
	NOT_IN_USE(context);
	return -1;
}

int socket_fd_api::free_packets(struct vma_packet_t *pkts, size_t count)
{
	NOT_IN_USE(pkts);
	NOT_IN_USE(count);
	return -1;
}

int socket_fd_api::free_buffs(uint16_t len)
{
	NOT_IN_USE(len);
	return -1;
}

int socket_fd_api::add_epoll_context(epfd_info *epfd)
{
	if (!m_econtext) {
		// This socket is not registered to any epfd
		m_econtext = epfd;
		return 0;
	} else {
		// Currently VMA does not support more then 1 epfd listed
		errno = (m_econtext == epfd) ? EEXIST : ENOMEM;
		return -1;
	}
}

void socket_fd_api::remove_epoll_context(epfd_info *epfd)
{
	if (m_econtext == epfd)
		m_econtext = NULL;
}

void socket_fd_api::notify_epoll_context(uint32_t events)
{
	if (m_econtext) {
		m_econtext->insert_epoll_event_cb(this, events);
	}
}

void socket_fd_api::notify_epoll_context_add_ring(ring* ring)
{
	if (m_econtext) {
		m_econtext->increase_ring_ref_count(ring);
	}
}

void socket_fd_api::notify_epoll_context_remove_ring(ring* ring)
{
	if (m_econtext){
		m_econtext->decrease_ring_ref_count(ring);
	}
}

bool socket_fd_api::notify_epoll_context_verify(epfd_info *epfd)
{
	return m_econtext == epfd;
}

void socket_fd_api::notify_epoll_context_fd_is_offloaded()
{
	if (m_econtext) {
		m_econtext->remove_fd_from_epoll_os(m_fd);
	}
}

int socket_fd_api::get_epoll_context_fd() {
	if (m_econtext) {
		return  m_econtext->get_epoll_fd();
	}
	return 0;
}

#if _BullseyeCoverage
    #pragma BullseyeCoverage on
#endif
