#include <libscap/strl.h>
#include "event_class.h"
#include <time.h>
#include <sys/vfs.h> /* or <sys/statfs.h> */
#include <linux/magic.h>

#define MAX_CHARBUF_NUM 16
#define CGROUP_NUMBER 5
#define MAX_CGROUP_STRING_LEN 128
#define MAX_CGROUP_PREFIX_LEN 32

/* This array must follow the same order we use in BPF. */
const char* cgroup_prefix_array[] = {
        "cpuset=/",
        "cpu=/",
        "cpuacct=/",
        "io=/",
        "memory=/",
};

static_assert(sizeof(cgroup_prefix_array) / sizeof(*cgroup_prefix_array) == CGROUP_NUMBER,
              "Wrong number of cgroup_prefix_array.");

/* Messages. */
#define VALUE_NOT_CORRECT ">>>>> value of the param is not correct. Param id = "
#define LEN_NOT_CORRECT ">>>>> len of the param is not correct. Param id = "
#define VALUE_NOT_ZERO ">>>>> value of the param must not be zero. Param id = "

extern const syscall_evt_pair g_syscall_table[SYSCALL_TABLE_SIZE];

/////////////////////////////////
// RETRIEVE EVENT CLASS
/////////////////////////////////

std::unique_ptr<event_test> get_generic_event_test(ppm_sc_code sc_code) {
	return (std::unique_ptr<event_test>)new event_test(sc_code);
}

std::unique_ptr<event_test> get_syscall_event_test(int syscall_id, int event_direction) {
	return (std::unique_ptr<event_test>)new event_test(syscall_id, event_direction);
}

std::unique_ptr<event_test> get_syscall_event_test() {
	return (std::unique_ptr<event_test>)new event_test();
}

/////////////////////////////////
// SYSCALL RESULT ASSERTIONS
/////////////////////////////////

void _assert_syscall_state(int syscall_state,
                           const char* syscall_name,
                           long syscall_rc,
                           assertion_operators op,
                           long expected_rc) {
	bool match = false;

	if(errno == ENOSYS) {
		// it is managed upward by assert_syscall_state macro.
		return;
	}

	switch(op) {
	case EQUAL:
		match = syscall_rc == expected_rc;
		break;

	case NOT_EQUAL:
		match = syscall_rc != expected_rc;
		break;

	default:
		FAIL() << "Operation currently not supported!" << std::endl;
		return;
	}

	if(!match) {
		if(syscall_state == SYSCALL_SUCCESS) {
			FAIL() << ">>>>> The syscall '" << syscall_name
			       << "' must be successful. Errno: " << errno
			       << " err_message: " << strerror(errno) << std::endl;
		} else {
			FAIL() << ">>>>> The syscall '" << syscall_name << "' must fail." << std::endl;
		}
	}
}

/////////////////////////////////
// CONFIGURATION
/////////////////////////////////

event_test::~event_test() {
	/* Stop the capture just to be sure and clean ring buffers */
	scap_stop_capture(s_scap_handle);
	clear_ring_buffers();
}

/* This constructor must be used with generic tracepoints
 * that must attach a dedicated BPF program into the kernel.
 */
event_test::event_test(ppm_sc_code sc_code): m_sc_set(PPM_SC_MAX, 0) {
	m_current_param = 0;

	switch(sc_code) {
	case PPM_SC_SCHED_PROCESS_EXIT:
		m_event_type = PPME_PROCEXIT_1_E;
		break;

	case PPM_SC_SCHED_SWITCH:
		m_event_type = PPME_SCHEDSWITCH_6_E;
		break;

	case PPM_SC_PAGE_FAULT_USER:
		m_event_type = PPME_PAGE_FAULT_E;
		break;

	case PPM_SC_PAGE_FAULT_KERNEL:
		m_event_type = PPME_PAGE_FAULT_E;
		break;

	case PPM_SC_SIGNAL_DELIVER:
		m_event_type = PPME_SIGNALDELIVER_E;
		break;

	default:
		std::cout << " Unable to find the correct BPF program to attach" << std::endl;
		break;
	}

	m_sc_set[sc_code] = 1;
}

/* This constructor must be used with syscalls events */
event_test::event_test(int syscall_id, int event_direction): m_sc_set(PPM_SC_MAX, 0) {
	if(event_direction == ENTER_EVENT) {
		m_event_type = g_syscall_table[syscall_id].enter_event_type;
	} else {
		m_event_type = g_syscall_table[syscall_id].exit_event_type;
		/* If the syscall is generic, the exit event type would be `0`, but we want to associate the
		 * syscall to `PPME_GENERIC_X`, so we patch it on the fly.
		 */
		if(m_event_type == 0) {
			m_event_type = PPME_GENERIC_X;
		}
	}

	m_current_param = 0;
	m_sc_set[g_syscall_table[syscall_id].ppm_sc] = 1;
}

/* This constructor must be used with syscalls events when you
 * want to enable all syscalls.
 */
event_test::event_test(): m_sc_set(PPM_SC_MAX, 0) {
	m_current_param = 0;

	/* Enable all the syscalls and tracepoints */
	for(int ppm_sc = 0; ppm_sc < PPM_SC_MAX; ppm_sc++) {
		m_sc_set[ppm_sc] = 1;
	}
}

void event_test::set_event_type(ppm_event_code evt_type) {
	m_event_type = evt_type;
}

void event_test::enable_capture() {
	for(int ppm_sc = 0; ppm_sc < PPM_SC_MAX; ppm_sc++) {
		if(m_sc_set[ppm_sc]) {
			scap_set_ppm_sc(s_scap_handle, (ppm_sc_code)ppm_sc, true);
		} else {
			scap_set_ppm_sc(s_scap_handle, (ppm_sc_code)ppm_sc, false);
		}
	}
	/* We need to clear all the `ring-buffers` in case of some dirty state */
	clear_ring_buffers();
	scap_start_capture(s_scap_handle);
}

void event_test::enable_sampling_logic(uint32_t sampling_ratio) {
	scap_start_dropping_mode(s_scap_handle, sampling_ratio);
}

void event_test::disable_sampling_logic() {
	scap_stop_dropping_mode(s_scap_handle);
}

void event_test::enable_drop_failed() {
	scap_set_dropfailed(s_scap_handle, true);
}

void event_test::disable_drop_failed() {
	scap_set_dropfailed(s_scap_handle, false);
}

void event_test::set_do_dynamic_snaplen(bool enable) {
	if(enable) {
		scap_enable_dynamic_snaplen(s_scap_handle);
	} else {
		scap_disable_dynamic_snaplen(s_scap_handle);
	}
}

void event_test::set_statsd_port(uint16_t port) {
	scap_set_statsd_port(s_scap_handle, port);
}

void event_test::set_fullcapture_port_range(uint16_t start, uint16_t end) {
	scap_set_fullcapture_port_range(s_scap_handle, start, end);
}

void event_test::disable_capture() {
	scap_stop_capture(s_scap_handle);
}

void event_test::clear_ring_buffers() {
	uint16_t cpu_id = 0;
	uint32_t flags = 0;
	/* First timeout means that all the buffers are empty. If the capture is not
	 * stopped it is possible that we will never receive a `SCAP_TIMEOUT`.
	 */
	while(scap_next(s_scap_handle, (scap_evt**)&m_event_header, &cpu_id, &flags) != SCAP_TIMEOUT) {
	}
}

ppm_evt_hdr* event_test::get_event_from_ringbuffer(uint16_t* cpu_id) {
	ppm_evt_hdr* hdr = NULL;
	uint16_t attempts = 0;
	int32_t res = 0;
	uint32_t flags = 0;

	/* Try 2 times just to be sure that all the buffers are empty. */
	while(attempts <= 1) {
		res = scap_next(s_scap_handle, (scap_evt**)&hdr, cpu_id, &flags);
		if(res == SCAP_SUCCESS && hdr != NULL) {
			break;
		} else if(res != SCAP_TIMEOUT && res != SCAP_SUCCESS) {
			return NULL;
		}
		attempts++;
	}
	return hdr;
}

void event_test::parse_event() {
	uint8_t nparams = m_event_header->nparams;
	uint16_t* lens16 = (uint16_t*)((char*)m_event_header + sizeof(ppm_evt_hdr));
	char* valptr = (char*)lens16 + nparams * sizeof(uint16_t);
	uint32_t total_len = sizeof(ppm_evt_hdr) + nparams * sizeof(uint16_t);
	struct param par;

	/* Insert a dummy param just to use index starting from 1 insted of 0. */
	par.len = 0;
	par.valptr = NULL;
	m_event_params.clear();
	m_event_params.push_back(par);

	for(int j = 0; j < nparams; j++) {
		par.valptr = valptr;
		par.len = lens16[j];
		valptr += lens16[j];
		m_event_params.push_back(par);
		total_len += lens16[j];
	}

	/* This event len is the overall len of the event (header + len_vector + data).
	 * Note: we compute this length according to the number of params written in the header by the
	 * bpf program.
	 */
	m_event_len = total_len;
}

/////////////////////////////////
// NETWORK SCAFFOLDING
/////////////////////////////////

void event_test::client_reuse_address_port(int32_t socketfd) {
	/* Allow the socket to reuse the port and address. */
	int option_value = 1;
	assert_syscall_state(SYSCALL_SUCCESS,
	                     "setsockopt (client address)",
	                     syscall(__NR_setsockopt,
	                             socketfd,
	                             SOL_SOCKET,
	                             SO_REUSEADDR,
	                             &option_value,
	                             sizeof(option_value)),
	                     NOT_EQUAL,
	                     -1);

	// Commit https://git.kernel.org/pub/scm/linux/kernel/git/netdev/net.git/commit/?id=5b0af621c3f6
	// restricts SO_REUSEPORT socket option to inet sockets: this means that the following call is
	// going to fail on unix sockets, and we have no way to prevent it. For this reason, simply
	// ignore its return value and hope any subsequent call to bind is going to succeed.
	syscall(__NR_setsockopt,
	        socketfd,
	        SOL_SOCKET,
	        SO_REUSEPORT,
	        &option_value,
	        sizeof(option_value));
}

void event_test::server_reuse_address_port(int32_t socketfd) {
	/* Allow the socket to reuse the port and address. */
	int option_value = 1;
	assert_syscall_state(SYSCALL_SUCCESS,
	                     "setsockopt (server address)",
	                     syscall(__NR_setsockopt,
	                             socketfd,
	                             SOL_SOCKET,
	                             SO_REUSEADDR,
	                             &option_value,
	                             sizeof(option_value)),
	                     NOT_EQUAL,
	                     -1);

	// Commit https://git.kernel.org/pub/scm/linux/kernel/git/netdev/net.git/commit/?id=5b0af621c3f6
	// restricts SO_REUSEPORT socket option to inet sockets: this means that the following call is
	// going to fail on unix sockets, and we have no way to prevent it. For this reason, simply
	// ignore its return value and hope any subsequent call to bind is going to succeed.
	syscall(__NR_setsockopt,
	        socketfd,
	        SOL_SOCKET,
	        SO_REUSEPORT,
	        &option_value,
	        sizeof(option_value));
}

void event_test::client_fill_sockaddr_in(sockaddr_in* sockaddr,
                                         int32_t ipv4_port,
                                         const char* ipv4_string) {
	memset(sockaddr, 0, sizeof(*sockaddr));
	sockaddr->sin_family = AF_INET;
	sockaddr->sin_port = htons(ipv4_port);
	assert_syscall_state(SYSCALL_SUCCESS,
	                     "inet_pton (client)",
	                     inet_pton(AF_INET, ipv4_string, &(sockaddr->sin_addr)),
	                     NOT_EQUAL,
	                     -1);
}

void event_test::server_fill_sockaddr_in(sockaddr_in* sockaddr,
                                         int32_t ipv4_port,
                                         const char* ipv4_string) {
	memset(sockaddr, 0, sizeof(*sockaddr));
	sockaddr->sin_family = AF_INET;
	sockaddr->sin_port = htons(ipv4_port);
	assert_syscall_state(SYSCALL_SUCCESS,
	                     "inet_pton (server)",
	                     inet_pton(AF_INET, ipv4_string, &(sockaddr->sin_addr)),
	                     NOT_EQUAL,
	                     -1);
}

void event_test::client_fill_sockaddr_in6(sockaddr_in6* sockaddr,
                                          int32_t ipv6_port,
                                          const char* ipv6_string) {
	memset(sockaddr, 0, sizeof(*sockaddr));
	sockaddr->sin6_family = AF_INET6;
	sockaddr->sin6_port = htons(ipv6_port);
	assert_syscall_state(SYSCALL_SUCCESS,
	                     "inet_pton (client)",
	                     inet_pton(AF_INET6, ipv6_string, &(sockaddr->sin6_addr)),
	                     NOT_EQUAL,
	                     -1);
}

void event_test::server_fill_sockaddr_in6(sockaddr_in6* sockaddr,
                                          int32_t ipv6_port,
                                          const char* ipv6_string) {
	memset(sockaddr, 0, sizeof(*sockaddr));
	sockaddr->sin6_family = AF_INET6;
	sockaddr->sin6_port = htons(ipv6_port);
	assert_syscall_state(SYSCALL_SUCCESS,
	                     "inet_pton (server)",
	                     inet_pton(AF_INET6, ipv6_string, &(sockaddr->sin6_addr)),
	                     NOT_EQUAL,
	                     -1);
}

void event_test::client_fill_sockaddr_un(sockaddr_un* sockaddr, const char* unix_path) {
	memset(sockaddr, 0, sizeof(*sockaddr));
	sockaddr->sun_family = AF_UNIX;

	strlcpy(sockaddr->sun_path, unix_path, MAX_SUN_PATH);
}

void event_test::server_fill_sockaddr_un(sockaddr_un* sockaddr, const char* unix_path) {
	memset(sockaddr, 0, sizeof(*sockaddr));
	sockaddr->sun_family = AF_UNIX;

	strlcpy(sockaddr->sun_path, unix_path, MAX_SUN_PATH);
}

void event_test::connect_ipv4_client_to_server(int32_t* client_socket,
                                               sockaddr_in* client_sockaddr,
                                               int32_t* server_socket,
                                               sockaddr_in* server_sockaddr,
                                               int32_t port_client,
                                               int32_t port_server) {
	/* Create the server socket. */
	*server_socket = syscall(__NR_socket, AF_INET, SOCK_STREAM | SOCK_NONBLOCK, 0);
	assert_syscall_state(SYSCALL_SUCCESS, "socket (server)", *server_socket, NOT_EQUAL, -1);
	server_reuse_address_port(*server_socket);

	memset(server_sockaddr, 0, sizeof(*server_sockaddr));
	server_fill_sockaddr_in(server_sockaddr, port_server);

	/* Now we bind the server socket with the server address. */
	assert_syscall_state(SYSCALL_SUCCESS,
	                     "bind (server)",
	                     syscall(__NR_bind,
	                             *server_socket,
	                             (sockaddr*)server_sockaddr,
	                             sizeof(*server_sockaddr)),
	                     NOT_EQUAL,
	                     -1);
	assert_syscall_state(SYSCALL_SUCCESS,
	                     "listen (server)",
	                     syscall(__NR_listen, *server_socket, QUEUE_LENGTH),
	                     NOT_EQUAL,
	                     -1);

	/* The server now is ready, we need to create at least one connection from the client. */

	*client_socket = syscall(__NR_socket, AF_INET, SOCK_STREAM, 0);
	assert_syscall_state(SYSCALL_SUCCESS, "socket (client)", *client_socket, NOT_EQUAL, -1);
	client_reuse_address_port(*client_socket);

	memset(client_sockaddr, 0, sizeof(*client_sockaddr));
	client_fill_sockaddr_in(client_sockaddr, port_client);

	/* We need to bind the client socket with an address otherwise we cannot assert against it. */
	assert_syscall_state(SYSCALL_SUCCESS,
	                     "bind (client)",
	                     syscall(__NR_bind,
	                             *client_socket,
	                             (sockaddr*)client_sockaddr,
	                             sizeof(*client_sockaddr)),
	                     NOT_EQUAL,
	                     -1);
	assert_syscall_state(SYSCALL_SUCCESS,
	                     "connect (client)",
	                     syscall(__NR_connect,
	                             *client_socket,
	                             (sockaddr*)server_sockaddr,
	                             sizeof(*server_sockaddr)),
	                     NOT_EQUAL,
	                     -1);
}

void event_test::client_to_server(int32_t* client_fd,
                                  int32_t* server_fd,
                                  send_data send_d,
                                  recv_data receive_d,
                                  network_config net_config) {
	int32_t client_socket_fd = 0;
	int32_t server_socket_fd = 0;
	sockaddr_in client_addr = {};
	sockaddr_in server_addr = {};
	sockaddr_in6 client_addr6 = {};
	sockaddr_in6 server_addr6 = {};
	sockaddr* addr = NULL;
	socklen_t addrlen = 0;

	//////////////////////
	// Setup Connection
	//////////////////////

	switch(net_config.proto_L3) {
	case protocol_L3::IPv4:
		if(net_config.proto_L4 == protocol_L4::TCP) {
			this->connect_ipv4_client_to_server(&client_socket_fd,
			                                    &client_addr,
			                                    &server_socket_fd,
			                                    &server_addr,
			                                    net_config.client_port,
			                                    net_config.server_port);
		} else {
			this->connect_ipv4_udp_client_to_server(&client_socket_fd,
			                                        &client_addr,
			                                        &server_socket_fd,
			                                        &server_addr,
			                                        net_config.client_port,
			                                        net_config.server_port);
		}
		// for the `recv*` syscalls we will use the memory of the server sockaddr but this will be
		// overwritten by the kernel so it shouldn't be an issue.
		addr = (sockaddr*)&server_addr;
		addrlen = sizeof(server_addr);
		break;
	case protocol_L3::IPv6:
		if(net_config.proto_L4 == protocol_L4::TCP) {
			this->connect_ipv6_client_to_server(&client_socket_fd,
			                                    &client_addr6,
			                                    &server_socket_fd,
			                                    &server_addr6,
			                                    net_config.client_port,
			                                    net_config.server_port);
		} else {
			this->connect_ipv6_udp_client_to_server(&client_socket_fd,
			                                        &client_addr6,
			                                        &server_socket_fd,
			                                        &server_addr6,
			                                        net_config.client_port,
			                                        net_config.server_port);
		}
		addr = (sockaddr*)&server_addr6;
		addrlen = sizeof(server_addr6);
		break;
	default:
		FAIL() << "Invalid protocol_L3" << std::endl;
		break;
	}

	//////////////////////
	// Send message
	//////////////////////

	switch(send_d.syscall_num) {
	case __NR_sendto: {
		const void* sent_data = NULL;
		size_t sent_data_len = 0;
		uint32_t sendto_flags = 0;
		sent_data = (const void*)SHORT_MESSAGE;
		sent_data_len = SHORT_MESSAGE_LEN;

		if(send_d.greater_snaplen) {
			sent_data = (const void*)LONG_MESSAGE;
			sent_data_len = LONG_MESSAGE_LEN;
		}

		if(send_d.null_sockaddr) {
			addr = NULL;
			addrlen = 0;
		}

		/* Send a message to the server */
		int64_t sent_bytes = syscall(__NR_sendto,
		                             client_socket_fd,
		                             sent_data,
		                             sent_data_len,
		                             sendto_flags,
		                             addr,
		                             addrlen);
		assert_syscall_state(SYSCALL_SUCCESS, "sendto (client)", sent_bytes, NOT_EQUAL, -1);
	} break;

	case __NR_sendmsg: {
		struct msghdr send_msg = {};
		struct iovec iov[1] = {};
		memset(&send_msg, 0, sizeof(send_msg));
		memset(iov, 0, sizeof(iov));
		uint32_t sendmsg_flags = 0;
		send_msg.msg_iov = iov;
		send_msg.msg_iovlen = 1;
		send_msg.msg_name = addr;
		send_msg.msg_namelen = addrlen;
		iov[0].iov_base = (void*)SHORT_MESSAGE;
		iov[0].iov_len = SHORT_MESSAGE_LEN;

		if(send_d.greater_snaplen) {
			iov[0].iov_base = (void*)LONG_MESSAGE;
			iov[0].iov_len = LONG_MESSAGE_LEN;
		}

		if(send_d.null_sockaddr) {
			send_msg.msg_name = NULL;
			send_msg.msg_namelen = 0;
		}

		assert_syscall_state(SYSCALL_SUCCESS,
		                     "sendmsg (client)",
		                     syscall(__NR_sendmsg, client_socket_fd, &send_msg, sendmsg_flags),
		                     NOT_EQUAL,
		                     -1);
	} break;

	case __NR_sendmmsg: {
		struct mmsghdr mmsg = {};
		struct iovec iov = {};
		uint32_t flags = 0;
		uint32_t vlen = 1;
		mmsg.msg_len = vlen;
		mmsg.msg_hdr.msg_iov = &iov;
		mmsg.msg_hdr.msg_iovlen = 1;
		mmsg.msg_hdr.msg_name = addr;
		mmsg.msg_hdr.msg_namelen = addrlen;
		iov.iov_base = (void*)SHORT_MESSAGE;
		iov.iov_len = SHORT_MESSAGE_LEN;

		if(send_d.greater_snaplen) {
			iov.iov_base = (void*)LONG_MESSAGE;
			iov.iov_len = LONG_MESSAGE_LEN;
		}

		if(send_d.null_sockaddr) {
			mmsg.msg_hdr.msg_name = NULL;
			mmsg.msg_hdr.msg_namelen = 0;
		}

		assert_syscall_state(SYSCALL_SUCCESS,
		                     "sendmmsg (client)",
		                     syscall(__NR_sendmmsg, client_socket_fd, &mmsg, vlen, flags),
		                     EQUAL,
		                     vlen);
	} break;

	case __NR_write: {
		const void* sent_data = (const void*)SHORT_MESSAGE;
		size_t sent_data_len = SHORT_MESSAGE_LEN;

		if(send_d.greater_snaplen) {
			sent_data = (const void*)LONG_MESSAGE;
			sent_data_len = LONG_MESSAGE_LEN;
		}

		ssize_t write_bytes = syscall(__NR_write, client_socket_fd, sent_data, sent_data_len);
		assert_syscall_state(SYSCALL_SUCCESS, "write (client)", write_bytes, NOT_EQUAL, -1);
	} break;

	default:
		FAIL() << "Invalid send syscall" << std::endl;
		break;
	}

	if(receive_d.skip_recv_phase) {
		// Cleanup and return immediately
		syscall(__NR_shutdown, server_socket_fd, 2);
		syscall(__NR_shutdown, client_socket_fd, 2);
		syscall(__NR_close, server_socket_fd);
		syscall(__NR_close, client_socket_fd);

		// Return file descriptors to the caller.
		*client_fd = client_socket_fd;
		*server_fd = server_socket_fd;
		return;
	}

	//////////////////////
	// Receive message
	//////////////////////
	int receive_socket_fd = server_socket_fd;
	if(net_config.proto_L4 == protocol_L4::TCP) {
		// In case of TCP we need to accept the connection.
		receive_socket_fd = syscall(__NR_accept4, server_socket_fd, NULL, NULL, 0);
		assert_syscall_state(SYSCALL_SUCCESS, "accept4 (server)", receive_socket_fd, NOT_EQUAL, -1);
	}

	switch(receive_d.syscall_num) {
	case __NR_recvfrom: {
		char received_data[MAX_RECV_BUF_SIZE];
		uint32_t recvfrom_flags = 0;
		char* received_data_ptr = &received_data[0];
		socklen_t received_data_len = MAX_RECV_BUF_SIZE;

		if(receive_d.null_sockaddr) {
			addr = NULL;
			addrlen = 0;
		}

		if(receive_d.null_receiver_buffer) {
			received_data_ptr = NULL;
			received_data_len = 0;
		}

		int64_t received_bytes = syscall(__NR_recvfrom,
		                                 receive_socket_fd,
		                                 received_data_ptr,
		                                 received_data_len,
		                                 recvfrom_flags,
		                                 addr,
		                                 &addrlen);
		assert_syscall_state(SYSCALL_SUCCESS, "recvfrom (server)", received_bytes, NOT_EQUAL, -1);
	} break;

	case __NR_recvmsg: {
		struct msghdr recv_msg = {};
		struct iovec iov[1] = {};
		memset(&recv_msg, 0, sizeof(recv_msg));
		memset(iov, 0, sizeof(iov));
		uint32_t recvmsg_flags = 0;
		char data[MAX_RECV_BUF_SIZE];
		recv_msg.msg_name = addr;
		recv_msg.msg_namelen = addrlen;
		iov[0].iov_base = data;
		iov[0].iov_len = MAX_RECV_BUF_SIZE;
		recv_msg.msg_iov = iov;
		recv_msg.msg_iovlen = 1;

		if(receive_d.null_sockaddr) {
			recv_msg.msg_name = NULL;
			recv_msg.msg_namelen = 0;
		}

		if(receive_d.null_receiver_buffer) {
			recv_msg.msg_iov = NULL;
			recv_msg.msg_iovlen = 0;
		}

		int64_t received_bytes = syscall(__NR_recvmsg, receive_socket_fd, &recv_msg, recvmsg_flags);
		assert_syscall_state(SYSCALL_SUCCESS, "recvmsg (server)", received_bytes, NOT_EQUAL, -1);
	} break;

	case __NR_recvmmsg: {
		// Max number of message we can receive with this method is 1,
		// should be enough for testing.
		uint32_t vlen = 1;
		struct mmsghdr mmsg = {};
		struct iovec iov = {};
		std::array<char, MAX_RECV_BUF_SIZE> data;

		iov.iov_base = data.data();
		iov.iov_len = MAX_RECV_BUF_SIZE;

		mmsg.msg_len = 1;
		mmsg.msg_hdr.msg_name = addr;
		mmsg.msg_hdr.msg_namelen = addrlen;
		mmsg.msg_hdr.msg_iov = &iov;
		mmsg.msg_hdr.msg_iovlen = 1;

		if(receive_d.null_sockaddr) {
			mmsg.msg_hdr.msg_name = NULL;
			mmsg.msg_hdr.msg_namelen = 0;
		}

		if(receive_d.null_receiver_buffer) {
			mmsg.msg_hdr.msg_iov = NULL;
			mmsg.msg_hdr.msg_iovlen = 0;
		}

		int flags = 0;
		int64_t received_messages =
		        syscall(__NR_recvmmsg, receive_socket_fd, &mmsg, vlen, flags, NULL);
		assert_syscall_state(SYSCALL_SUCCESS, "recvmmsg (server)", received_messages, EQUAL, vlen);
	} break;

	case __NR_read: {
		char buf[MAX_RECV_BUF_SIZE];
		char* received_data_ptr = &buf[0];
		size_t received_data_len = MAX_RECV_BUF_SIZE;

		if(receive_d.null_receiver_buffer) {
			received_data_ptr = NULL;
			received_data_len = 0;
		}

		ssize_t read_bytes =
		        syscall(__NR_read, receive_socket_fd, (void*)received_data_ptr, received_data_len);
		assert_syscall_state(SYSCALL_SUCCESS, "read (server)", read_bytes, NOT_EQUAL, -1);
	} break;

	default:
		FAIL() << "Invalid recv syscall" << std::endl;
		break;
	}

	//////////////////////
	// Cleaning phase
	//////////////////////
	if(net_config.proto_L4 == protocol_L4::TCP) {
		syscall(__NR_shutdown, receive_socket_fd, 2);
		syscall(__NR_close, receive_socket_fd);
	}
	syscall(__NR_shutdown, server_socket_fd, 2);
	syscall(__NR_shutdown, client_socket_fd, 2);
	syscall(__NR_close, server_socket_fd);
	syscall(__NR_close, client_socket_fd);

	// Return file descriptors to the caller.
	*client_fd = client_socket_fd;
	*server_fd = receive_socket_fd;
}

void event_test::client_to_server_ipv4_tcp(int32_t* client_socket_fd,
                                           int32_t* server_accepted_conn_socket_fd,
                                           send_data send_d,
                                           recv_data receive_d,
                                           int32_t client_port,
                                           int32_t server_port) {
	this->client_to_server(client_socket_fd,
	                       server_accepted_conn_socket_fd,
	                       send_d,
	                       receive_d,
	                       network_config{.proto_L3 = protocol_L3::IPv4,
	                                      .proto_L4 = protocol_L4::TCP,
	                                      .client_port = client_port,
	                                      .server_port = server_port});
}

void event_test::client_to_server_ipv4_udp(int32_t* client_socket_fd,
                                           int32_t* server_socket_fd,
                                           send_data send_d,
                                           recv_data receive_d,
                                           int32_t client_port,
                                           int32_t server_port) {
	this->client_to_server(client_socket_fd,
	                       server_socket_fd,
	                       send_d,
	                       receive_d,
	                       network_config{.proto_L3 = protocol_L3::IPv4,
	                                      .proto_L4 = protocol_L4::UDP,
	                                      .client_port = client_port,
	                                      .server_port = server_port});
}

void event_test::client_to_server_ipv6_tcp(int32_t* client_socket_fd,
                                           int32_t* server_accepted_conn_socket_fd,
                                           send_data send_d,
                                           recv_data receive_d,
                                           int32_t client_port,
                                           int32_t server_port) {
	this->client_to_server(client_socket_fd,
	                       server_accepted_conn_socket_fd,
	                       send_d,
	                       receive_d,
	                       network_config{.proto_L3 = protocol_L3::IPv6,
	                                      .proto_L4 = protocol_L4::TCP,
	                                      .client_port = client_port,
	                                      .server_port = server_port});
}

void event_test::client_to_server_ipv6_udp(int32_t* client_socket_fd,
                                           int32_t* server_socket_fd,
                                           send_data send_d,
                                           recv_data receive_d,
                                           int32_t client_port,
                                           int32_t server_port) {
	this->client_to_server(client_socket_fd,
	                       server_socket_fd,
	                       send_d,
	                       receive_d,
	                       network_config{.proto_L3 = protocol_L3::IPv6,
	                                      .proto_L4 = protocol_L4::UDP,
	                                      .client_port = client_port,
	                                      .server_port = server_port});
}

void event_test::connect_ipv4_udp_client_to_server(int32_t* client_socket,
                                                   sockaddr_in* client_sockaddr,
                                                   int32_t* server_socket,
                                                   sockaddr_in* server_sockaddr,
                                                   int32_t port_client,
                                                   int32_t port_server) {
	/* Create the server socket. */
	*server_socket = syscall(__NR_socket, AF_INET, SOCK_DGRAM, 0);
	assert_syscall_state(SYSCALL_SUCCESS, "socket (server)", *server_socket, NOT_EQUAL, -1);
	server_reuse_address_port(*server_socket);

	memset(server_sockaddr, 0, sizeof(*server_sockaddr));
	server_fill_sockaddr_in(server_sockaddr, port_server);

	/* Now we bind the server socket with the server address. */
	assert_syscall_state(SYSCALL_SUCCESS,
	                     "bind (server)",
	                     syscall(__NR_bind,
	                             *server_socket,
	                             (sockaddr*)server_sockaddr,
	                             sizeof(*server_sockaddr)),
	                     NOT_EQUAL,
	                     -1);

	/* The server now is ready, we need to create at least one connection from the client. */

	*client_socket = syscall(__NR_socket, AF_INET, SOCK_DGRAM, 0);
	assert_syscall_state(SYSCALL_SUCCESS, "socket (client)", *client_socket, NOT_EQUAL, -1);
	client_reuse_address_port(*client_socket);

	memset(client_sockaddr, 0, sizeof(*client_sockaddr));
	client_fill_sockaddr_in(client_sockaddr, port_client);

	/* We need to bind the client socket with an address otherwise we cannot assert against it. */
	assert_syscall_state(SYSCALL_SUCCESS,
	                     "bind (client)",
	                     syscall(__NR_bind,
	                             *client_socket,
	                             (sockaddr*)client_sockaddr,
	                             sizeof(*client_sockaddr)),
	                     NOT_EQUAL,
	                     -1);
}

void event_test::connect_ipv6_client_to_server(int32_t* client_socket,
                                               sockaddr_in6* client_sockaddr,
                                               int32_t* server_socket,
                                               sockaddr_in6* server_sockaddr,
                                               int32_t port_client,
                                               int32_t port_server) {
	/* Create the server socket. */
	*server_socket = syscall(__NR_socket, AF_INET6, SOCK_STREAM | SOCK_NONBLOCK, 0);
	assert_syscall_state(SYSCALL_SUCCESS, "socket (server)", *server_socket, NOT_EQUAL, -1);
	server_reuse_address_port(*server_socket);

	memset(server_sockaddr, 0, sizeof(*server_sockaddr));
	server_fill_sockaddr_in6(server_sockaddr, port_server);

	/* Now we bind the server socket with the server address. */
	assert_syscall_state(SYSCALL_SUCCESS,
	                     "bind (server)",
	                     syscall(__NR_bind,
	                             *server_socket,
	                             (sockaddr*)server_sockaddr,
	                             sizeof(*server_sockaddr)),
	                     NOT_EQUAL,
	                     -1);
	assert_syscall_state(SYSCALL_SUCCESS,
	                     "listen (server)",
	                     syscall(__NR_listen, *server_socket, QUEUE_LENGTH),
	                     NOT_EQUAL,
	                     -1);

	/* The server now is ready, we need to create at least one connection from the client. */

	*client_socket = syscall(__NR_socket, AF_INET6, SOCK_STREAM, 0);
	assert_syscall_state(SYSCALL_SUCCESS, "socket (client)", *client_socket, NOT_EQUAL, -1);
	client_reuse_address_port(*client_socket);

	memset(client_sockaddr, 0, sizeof(*client_sockaddr));
	client_fill_sockaddr_in6(client_sockaddr, port_client);

	/* We need to bind the client socket with an address otherwise we cannot assert against it. */
	assert_syscall_state(SYSCALL_SUCCESS,
	                     "bind (client)",
	                     syscall(__NR_bind,
	                             *client_socket,
	                             (sockaddr*)client_sockaddr,
	                             sizeof(*client_sockaddr)),
	                     NOT_EQUAL,
	                     -1);
	assert_syscall_state(SYSCALL_SUCCESS,
	                     "connect (client)",
	                     syscall(__NR_connect,
	                             *client_socket,
	                             (sockaddr*)server_sockaddr,
	                             sizeof(*server_sockaddr)),
	                     NOT_EQUAL,
	                     -1);
}

void event_test::connect_ipv6_udp_client_to_server(int32_t* client_socket,
                                                   sockaddr_in6* client_sockaddr,
                                                   int32_t* server_socket,
                                                   sockaddr_in6* server_sockaddr,
                                                   int32_t port_client,
                                                   int32_t port_server) {
	/* Create the server socket. */
	*server_socket = syscall(__NR_socket, AF_INET6, SOCK_DGRAM, 0);
	assert_syscall_state(SYSCALL_SUCCESS, "socket (server)", *server_socket, NOT_EQUAL, -1);
	server_reuse_address_port(*server_socket);

	memset(server_sockaddr, 0, sizeof(*server_sockaddr));
	server_fill_sockaddr_in6(server_sockaddr, port_server);

	/* Now we bind the server socket with the server address. */
	assert_syscall_state(SYSCALL_SUCCESS,
	                     "bind (server)",
	                     syscall(__NR_bind,
	                             *server_socket,
	                             (sockaddr*)server_sockaddr,
	                             sizeof(*server_sockaddr)),
	                     NOT_EQUAL,
	                     -1);

	/* The server now is ready, we need to create at least one connection from the client. */

	*client_socket = syscall(__NR_socket, AF_INET6, SOCK_DGRAM, 0);
	assert_syscall_state(SYSCALL_SUCCESS, "socket (client)", *client_socket, NOT_EQUAL, -1);
	client_reuse_address_port(*client_socket);

	memset(client_sockaddr, 0, sizeof(*client_sockaddr));
	client_fill_sockaddr_in6(client_sockaddr, port_client);

	/* We need to bind the client socket with an address otherwise we cannot assert against it. */
	assert_syscall_state(SYSCALL_SUCCESS,
	                     "bind (client)",
	                     syscall(__NR_bind,
	                             *client_socket,
	                             (sockaddr*)client_sockaddr,
	                             sizeof(*client_sockaddr)),
	                     NOT_EQUAL,
	                     -1);
}

void event_test::connect_unix_tcp_client_to_server(int32_t* client_socket,
                                                   sockaddr_un* client_sockaddr,
                                                   int32_t* server_socket,
                                                   sockaddr_un* server_sockaddr) {
	/* Create the server socket. */
	*server_socket = syscall(__NR_socket, AF_UNIX, SOCK_STREAM | SOCK_NONBLOCK, 0);
	assert_syscall_state(SYSCALL_SUCCESS, "socket (server)", *server_socket, NOT_EQUAL, -1);

	memset(server_sockaddr, 0, sizeof(*server_sockaddr));
	server_fill_sockaddr_un(server_sockaddr);

	/* Now we bind the server socket with the server address. */
	assert_syscall_state(SYSCALL_SUCCESS,
	                     "bind (server)",
	                     syscall(__NR_bind,
	                             *server_socket,
	                             (sockaddr*)server_sockaddr,
	                             sizeof(*server_sockaddr)),
	                     NOT_EQUAL,
	                     -1);
	assert_syscall_state(SYSCALL_SUCCESS,
	                     "listen (server)",
	                     syscall(__NR_listen, *server_socket, QUEUE_LENGTH),
	                     NOT_EQUAL,
	                     -1);

	/* The server now is ready, we need to create at least one connection from the client. */

	*client_socket = syscall(__NR_socket, AF_UNIX, SOCK_STREAM, 0);
	assert_syscall_state(SYSCALL_SUCCESS, "socket (client)", *client_socket, NOT_EQUAL, -1);

	memset(client_sockaddr, 0, sizeof(*client_sockaddr));
	client_fill_sockaddr_un(client_sockaddr);

	/* We need to bind the client socket with an address otherwise we cannot assert against it. */
	assert_syscall_state(SYSCALL_SUCCESS,
	                     "bind (client)",
	                     syscall(__NR_bind,
	                             *client_socket,
	                             (sockaddr*)client_sockaddr,
	                             sizeof(*client_sockaddr)),
	                     NOT_EQUAL,
	                     -1);
	assert_syscall_state(SYSCALL_SUCCESS,
	                     "connect (client)",
	                     syscall(__NR_connect,
	                             *client_socket,
	                             (sockaddr*)server_sockaddr,
	                             sizeof(*server_sockaddr)),
	                     NOT_EQUAL,
	                     -1);
}

/////////////////////////////////
// GENERIC EVENT ASSERTIONS
/////////////////////////////////

void event_test::assert_event_presence(pid_t pid_to_search, int event_to_search) {
	assert_event_in_buffers(pid_to_search, event_to_search, true);
}

void event_test::assert_event_absence(pid_t pid_to_search, int event_to_search) {
	assert_event_in_buffers(pid_to_search, event_to_search, false);
}

void event_test::assert_header() {
	/* TODO: Here we need a `scap` function that exposes some fields of the table and not all the
	 * table!! */
	int num_params_from_bpf_table = scap_get_event_info_table()[m_event_type].nparams;

	/* the bpf event gets the correct number of parameters from the param table. */
	ASSERT_EQ(m_event_header->nparams, num_params_from_bpf_table)
	        << "'nparams' in the header is not correct." << std::endl;
	/* the len specified in the header matches the real event len. */
	ASSERT_EQ(m_event_header->len, m_event_len)
	        << "'event_len' in the header is not correct." << std::endl;
}

void event_test::assert_num_params_pushed(int total_params) {
	/* TODO: Here we need a `scap` function that exposes some fields of the table and not all the
	 * table!! */
	int num_params_from_bpf_table = scap_get_event_info_table()[m_event_type].nparams;
	ASSERT_EQ(total_params, num_params_from_bpf_table)
	        << "for this event we have not pushed the right number of parameters." << std::endl;
}

/////////////////////////////////
// PARAM ASSERTIONS
/////////////////////////////////

void event_test::assert_empty_param(int param_num) {
	assert_param_boundaries(param_num);
	/* The param length must be 0. */
	assert_param_len(0);
}

void event_test::assert_only_param_len(int param_num, uint16_t expected_size) {
	assert_param_boundaries(param_num);
	assert_param_len(expected_size);
}

template<typename T>
void event_test::assert_numeric_param(int param_num, T param, assertion_operators op) {
	assert_param_boundaries(param_num);
	assert_param_len(sizeof(T));

	switch(op) {
	case EQUAL:
		ASSERT_EQ(*(T*)(m_event_params[m_current_param].valptr), param)
		        << VALUE_NOT_CORRECT << m_current_param << std::endl;
		break;

	case NOT_EQUAL:
		ASSERT_NE(*(T*)(m_event_params[m_current_param].valptr), param)
		        << VALUE_NOT_CORRECT << m_current_param << std::endl;
		break;

	case GREATER_EQUAL:
		ASSERT_GE(*(T*)(m_event_params[m_current_param].valptr), param)
		        << VALUE_NOT_CORRECT << m_current_param << std::endl;
		break;

	case LESS_EQUAL:
		ASSERT_LE(*(T*)(m_event_params[m_current_param].valptr), param)
		        << VALUE_NOT_CORRECT << m_current_param << std::endl;
		break;

	default:
		FAIL() << "Operation currently not supported!";
		return;
	}
}

template void event_test::assert_numeric_param<uint8_t>(int, uint8_t, assertion_operators);
template void event_test::assert_numeric_param<uint16_t>(int, uint16_t, assertion_operators);
template void event_test::assert_numeric_param<uint32_t>(int, uint32_t, assertion_operators);
template void event_test::assert_numeric_param<uint64_t>(int, uint64_t, assertion_operators);
template void event_test::assert_numeric_param<int8_t>(int, int8_t, assertion_operators);
template void event_test::assert_numeric_param<int16_t>(int, int16_t, assertion_operators);
template void event_test::assert_numeric_param<int32_t>(int, int32_t, assertion_operators);
template void event_test::assert_numeric_param<int64_t>(int, int64_t, assertion_operators);

void event_test::assert_charbuf_param(int param_num, const char* param) {
	assert_param_boundaries(param_num);
	/* 'strlen()' does not include the terminating null byte while bpf adds it. */
	assert_param_len(strlen(param) + 1);
	/* The following assertion compares two C strings, not std::string */
	ASSERT_STREQ(m_event_params[m_current_param].valptr, param)
	        << VALUE_NOT_CORRECT << m_current_param << std::endl;
}

void event_test::assert_charbuf_param_any_of(const int param_num,
                                             const std::vector<const char*>& candidates) {
	assert_param_boundaries(param_num);
	ASSERT_GT(candidates.size(), 0);
	const auto& [param_value, param_size] = m_event_params[m_current_param];
	for(const auto& candidate : candidates) {
		// 'strlen()' does not include the terminating null byte while drivers adds it.
		if(const auto candidate_size = strlen(candidate) + 1; param_size != candidate_size) {
			continue;
		}
		if(!strncmp(param_value, candidate, param_size)) {
			return;
		}
	}

	// The parameter didn't match any of the provided candidates. Fail with a useful error message.
	std::ostringstream oss;
	oss << VALUE_NOT_CORRECT << m_current_param << ", value = \"" << param_value
	    << "\". It must be equal to any of the provided candidates:\n";
	for(const auto& value : candidates) {
		oss << "- \"" << std::string{value} << "\"\n";
	}
	FAIL() << oss.str();
}

void event_test::assert_charbuf_array_param(int param_num, const char** param) {
	assert_param_boundaries(param_num);
	uint16_t total_len = 0;

	for(int index = 0; index < MAX_CHARBUF_NUM; index++) {
		if(param[index] == NULL) {
			break;
		}
		/* We can use `STREQ` because every `charbuf` is `\0` terminated. */
		ASSERT_EQ(strlen(m_event_params[m_current_param].valptr + total_len), strlen(param[index]))
		        << LEN_NOT_CORRECT << m_current_param << std::endl;
		ASSERT_STREQ(m_event_params[m_current_param].valptr + total_len, param[index])
		        << VALUE_NOT_CORRECT << m_current_param << std::endl;
		total_len += strlen(param[index]) + 1;
	}
	assert_param_len(total_len);
}

void event_test::assert_cgroup_param(int param_num) {
	assert_param_boundaries(param_num);
	uint16_t total_len = 0;
	/* 'cgroup_string' is composed by 'cgroup_subsytem_name' + 'cgroup_path'.
	 * Here we can assert only the 'cgroup_subsytem_name' + the presence of the '/' in the path.
	 * We call 'cgroup_prefix' this substring we have to assert.
	 */
	char cgroup_string[MAX_CGROUP_STRING_LEN];
	char cgroup_prefix[MAX_CGROUP_PREFIX_LEN];
	int prefix_len = 0;

	for(int index = 0; index < CGROUP_NUMBER; index++) {
		strlcpy(cgroup_string,
		        m_event_params[m_current_param].valptr + total_len,
		        MAX_CGROUP_STRING_LEN);
		total_len += strlen(cgroup_string) + 1;

		prefix_len = strlen(cgroup_prefix_array[index]);
		strlcpy(cgroup_prefix, cgroup_string, prefix_len + 1);
		ASSERT_STREQ(cgroup_prefix, cgroup_prefix_array[index])
		        << VALUE_NOT_CORRECT << m_current_param;
	}

	/* With the kmod we send more cgroups than the 5 we send in bpf and modern bpf */
	if(is_kmod_engine()) {
		assert_param_len_ge(total_len);
	} else {
		assert_param_len(total_len);
	}
}

void event_test::assert_bytebuf_param(int param_num, const char* param, int buf_dimension) {
	assert_param_boundaries(param_num);
	assert_param_len(buf_dimension);

	std::string msg = "\nparam: ";
	for(int i = 0; i < buf_dimension; i++) {
		msg += m_event_params[m_current_param].valptr[i];
	}
	msg += "\nexpected param: ";
	for(int i = 0; i < buf_dimension; i++) {
		msg += param[i];
	}
	msg += "\n\n";
	SCOPED_TRACE(msg);

	/* We have to use `memcmp` because we could have bytebuf with string terminator `\0` inside. */
	ASSERT_EQ(memcmp(m_event_params[m_current_param].valptr, param, buf_dimension), 0)
	        << VALUE_NOT_CORRECT << m_current_param << std::endl;
}

void event_test::assert_addr_info_inet_param(int param_num,
                                             uint8_t desired_family,
                                             const char* desired_ipv4,
                                             const char* desired_port) {
	assert_param_boundaries(param_num);

	/* Assert family. */
	assert_address_family(desired_family, 0);

	/* Assert ipv4. */
	assert_ipv4_string(desired_ipv4, 1);

	/* Assert port. */
	assert_port_string(desired_port, 5);

	/* Assert (family + ipv4 + port) */
	assert_param_len(FAMILY_SIZE + IPV4_SIZE + PORT_SIZE);
}

void event_test::assert_addr_info_inet6_param(int param_num,
                                              uint8_t desired_family,
                                              const char* desired_ipv6,
                                              const char* desired_port) {
	assert_param_boundaries(param_num);

	/* Assert family. */
	assert_address_family(desired_family, 0);

	/* Assert ipv6. */
	assert_ipv6_string(desired_ipv6, 1);

	/* Assert port. */
	assert_port_string(desired_port, 17);

	/* Assert (family + ipv6 + port) */
	assert_param_len(FAMILY_SIZE + IPV6_SIZE + PORT_SIZE);
}

void event_test::assert_addr_info_unix_param(int param_num,
                                             uint8_t desired_family,
                                             const char* desired_path) {
	assert_param_boundaries(param_num);

	/* Assert family. */
	assert_address_family(desired_family, 0);

	/* Assert unix path. */
	assert_unix_path(desired_path, 1);

	/* Assert (family + unix_path + null terminator) */
	assert_param_len(FAMILY_SIZE + strlen(desired_path) + 1);
}

void event_test::assert_tuple_inet_param(int param_num,
                                         uint8_t desired_family,
                                         const char* desired_src_ipv4,
                                         const char* desired_dest_ipv4,
                                         const char* desired_src_port,
                                         const char* desired_dest_port) {
	assert_param_boundaries(param_num);

	/* Assert family. */
	assert_address_family(desired_family, 0);

	/* Assert src ipv4. */
	assert_ipv4_string(desired_src_ipv4, 1, SOURCE);

	/* Assert src port. */
	assert_port_string(desired_src_port, 5, SOURCE);

	/* Assert dest ipv4. */
	assert_ipv4_string(desired_dest_ipv4, 7, DEST);

	/* Assert dest port. */
	assert_port_string(desired_dest_port, 11, DEST);

	/* Assert (family + ipv4_src + port_src + ipv4_dest + port_dest) */
	assert_param_len(FAMILY_SIZE + IPV4_SIZE + PORT_SIZE + IPV4_SIZE + PORT_SIZE);
}

void event_test::assert_tuple_inet6_param(int param_num,
                                          uint8_t desired_family,
                                          const char* desired_src_ipv6,
                                          const char* desired_dest_ipv6,
                                          const char* desired_src_port,
                                          const char* desired_dest_port) {
	assert_param_boundaries(param_num);

	/* Assert family. */
	assert_address_family(desired_family, 0);

	/* Assert src ipv6. */
	assert_ipv6_string(desired_src_ipv6, 1, SOURCE);

	/* Assert src port. */
	assert_port_string(desired_src_port, 17, SOURCE);

	/* Assert dest ipv6. */
	assert_ipv6_string(desired_dest_ipv6, 19, DEST);

	/* Assert dest port. */
	assert_port_string(desired_dest_port, 35, DEST);

	/* Assert (family + ipv6_src + port_src + ipv6_dest + port_dest)*/
	assert_param_len(FAMILY_SIZE + IPV6_SIZE + PORT_SIZE + IPV6_SIZE + PORT_SIZE);
}

void event_test::assert_tuple_unix_param(int param_num,
                                         uint8_t desired_family,
                                         const char* desired_path) {
	assert_param_boundaries(param_num);

	/* Assert family. */
	assert_address_family(desired_family, 0);

	/* Here we have the two pointers:
	 * - destination OS pointer.
	 * - source OS pointer.
	 * but we cannot make assertions on that.
	 */

	/* Assert unix path. */
	assert_unix_path(desired_path, 17);

	/* Assert (family + 2 (8-byte) pointers + unix_path + null_terminator) */
	assert_param_len(FAMILY_SIZE + 8 + 8 + strlen(desired_path) + 1);
}

void event_test::assert_setsockopt_val(int param_num,
                                       int sockopt,
                                       void* option_value,
                                       int option_len) {
	/* 1 byte for the PPM type. */
	assert_param_boundaries(param_num);
	uint16_t expected_size = 1;
	ASSERT_EQ(*(uint8_t*)(m_event_params[m_current_param].valptr), sockopt)
	        << VALUE_NOT_CORRECT << m_current_param << std::endl;

	switch(sockopt) {
	case PPM_SOCKOPT_IDX_ERRNO:
		ASSERT_EQ(*(int64_t*)(m_event_params[m_current_param].valptr + 1), *(int64_t*)option_value)
		        << VALUE_NOT_CORRECT << m_current_param << std::endl;
		expected_size += 8;
		break;

	case PPM_SOCKOPT_IDX_TIMEVAL:
		ASSERT_EQ(*(uint64_t*)(m_event_params[m_current_param].valptr + 1),
		          *(uint64_t*)option_value)
		        << VALUE_NOT_CORRECT << m_current_param << std::endl;
		expected_size += 8;
		break;

	case PPM_SOCKOPT_IDX_UINT64:
		ASSERT_EQ(*(uint64_t*)(m_event_params[m_current_param].valptr + 1),
		          *(uint64_t*)option_value)
		        << VALUE_NOT_CORRECT << m_current_param << std::endl;
		expected_size += 8;
		break;

	case PPM_SOCKOPT_IDX_UINT32:
		ASSERT_EQ(*(uint32_t*)(m_event_params[m_current_param].valptr + 1),
		          *(uint32_t*)option_value)
		        << VALUE_NOT_CORRECT << m_current_param << std::endl;
		expected_size += 4;
		break;

	case PPM_SOCKOPT_IDX_UNKNOWN:
		/* if option_len is zero we should have just the `scap` code.*/
		if(option_len == 0) {
			assert_param_len(expected_size);
			break;
		}
		ASSERT_EQ(*(uint32_t*)(m_event_params[m_current_param].valptr + 1),
		          *(uint32_t*)option_value)
		        << VALUE_NOT_CORRECT << m_current_param << std::endl;
		expected_size += 4;
		break;
	default:
		FAIL();
		break;
	}
	assert_param_len(expected_size);
}

void event_test::assert_ptrace_addr(int param_num) {
	assert_param_boundaries(param_num);

	/* Right now we test only the failure case.
	 * - type: `PPM_PTRACE_IDX_UINT64`
	 * - val: `0`
	 */
	assert_param_len(sizeof(uint8_t) + sizeof(uint64_t));
	ASSERT_EQ(*(uint8_t*)(m_event_params[m_current_param].valptr), PPM_PTRACE_IDX_UINT64)
	        << VALUE_NOT_CORRECT << m_current_param << std::endl;
	ASSERT_EQ(*(uint64_t*)(m_event_params[m_current_param].valptr + 1), 0)
	        << VALUE_NOT_CORRECT << m_current_param << std::endl;
}

void event_test::assert_ptrace_data(int param_num) {
	assert_param_boundaries(param_num);

	/* Right now we test only the failure case.
	 * - type: `PPM_PTRACE_IDX_UINT64`
	 * - val: `0`
	 */
	assert_param_len(sizeof(uint8_t) + sizeof(uint64_t));
	ASSERT_EQ(*(uint8_t*)(m_event_params[m_current_param].valptr), PPM_PTRACE_IDX_UINT64)
	        << VALUE_NOT_CORRECT << m_current_param << std::endl;
	ASSERT_EQ(*(uint64_t*)(m_event_params[m_current_param].valptr + 1), 0)
	        << VALUE_NOT_CORRECT << m_current_param << std::endl;
}

void event_test::assert_fd_list(int param_num,
                                struct fd_poll* expected_fds,
                                int32_t expected_nfds) {
	assert_param_boundaries(param_num);
	uint16_t bytes_read = 0;

	/* Assert the pair's number */
	ASSERT_EQ(*(int16_t*)(m_event_params[m_current_param].valptr + bytes_read), expected_nfds)
	        << VALUE_NOT_CORRECT << m_current_param << " the expected number of nfds doesn't match!"
	        << std::endl;
	bytes_read += sizeof(uint16_t);

	for(int j = 0; j < expected_nfds; j++) {
		/* Assert the `fd` */
		ASSERT_EQ(*(int64_t*)(m_event_params[m_current_param].valptr + bytes_read),
		          expected_fds[j].fd)
		        << VALUE_NOT_CORRECT << m_current_param << " index: " << j << std::endl;
		bytes_read += sizeof(int64_t);
		/* Assert `flags` */
		ASSERT_EQ(*(int16_t*)(m_event_params[m_current_param].valptr + bytes_read),
		          expected_fds[j].flags)
		        << VALUE_NOT_CORRECT << m_current_param << " index: " << j << std::endl;
		bytes_read += sizeof(int16_t);
	}
	assert_param_len(sizeof(uint16_t) + ((sizeof(int64_t) + sizeof(int16_t)) * expected_nfds));
}

/////////////////////////////////
// INTERNAL ASSERTIONS
/////////////////////////////////

void event_test::assert_param_boundaries(int param_num) {
	m_current_param = param_num;
	ASSERT_GE(m_current_param, 1) << ">>>>> The param id '" << m_current_param << "' is too low."
	                              << std::endl;
	ASSERT_LE(m_current_param, m_event_header->nparams)
	        << ">>>>> The param id '" << m_current_param << "' is too big." << std::endl;
}

void event_test::assert_param_len(uint16_t expected_size) {
	uint16_t size = m_event_params[m_current_param].len;
	ASSERT_EQ(size, expected_size)
	        << ">>>>> length of the param is not correct. Param id = " << m_current_param
	        << std::endl;
}

void event_test::assert_param_len_ge(uint16_t expected_size) {
	uint16_t size = m_event_params[m_current_param].len;
	ASSERT_GE(size, expected_size)
	        << ">>>>> length of the param is not correct. Param id = " << m_current_param
	        << std::endl;
}

void event_test::assert_address_family(uint8_t desired_family, int starting_index) {
	uint8_t family = (uint8_t)(m_event_params[m_current_param].valptr[starting_index]);
	ASSERT_EQ(family, desired_family)
	        << VALUE_NOT_CORRECT << m_current_param << " (address family)" << std::endl;
}

void event_test::assert_ipv4_string(const char* desired_ipv4, int starting_index, direction dir) {
	char ipv4_string[ADDRESS_LENGTH];
	if(inet_ntop(AF_INET,
	             (uint8_t*)(m_event_params[m_current_param].valptr + starting_index),
	             ipv4_string,
	             ADDRESS_LENGTH) == NULL) {
		FAIL() << "'inet_ntop' must not fail. Param id = " << m_current_param << std::endl;
	}

	if(dir == DEST) {
		ASSERT_STREQ(ipv4_string, desired_ipv4)
		        << VALUE_NOT_CORRECT << m_current_param << " (dest ipv4)" << std::endl;
	} else {
		ASSERT_STREQ(ipv4_string, desired_ipv4)
		        << VALUE_NOT_CORRECT << m_current_param << " (source ipv4)" << std::endl;
	}
}

void event_test::assert_port_string(const char* desired_port, int starting_index, direction dir) {
	uint16_t port = *(uint16_t*)(m_event_params[m_current_param].valptr + starting_index);
	auto port_string = std::to_string(port);

	if(dir == DEST) {
		ASSERT_STREQ(port_string.c_str(), desired_port)
		        << VALUE_NOT_CORRECT << m_current_param << " (dest port)" << std::endl;
	} else {
		ASSERT_STREQ(port_string.c_str(), desired_port)
		        << VALUE_NOT_CORRECT << m_current_param << " (source port)" << std::endl;
	}
}

void event_test::assert_ipv6_string(const char* desired_ipv6, int starting_index, direction dir) {
	char ipv6_string[ADDRESS_LENGTH];
	if(inet_ntop(AF_INET6,
	             (uint32_t*)(m_event_params[m_current_param].valptr + starting_index),
	             ipv6_string,
	             ADDRESS_LENGTH) == NULL) {
		FAIL() << "'inet_ntop' must not fail. Param id = " << m_current_param << std::endl;
	}

	if(dir == DEST) {
		ASSERT_STREQ(ipv6_string, desired_ipv6)
		        << VALUE_NOT_CORRECT << m_current_param << "(dest ipv6)" << std::endl;
	} else {
		ASSERT_STREQ(ipv6_string, desired_ipv6)
		        << VALUE_NOT_CORRECT << m_current_param << "(source ipv6)" << std::endl;
	}
}

void event_test::assert_unix_path(const char* desired_path, int starting_index) {
	const char* unix_path = m_event_params[m_current_param].valptr + starting_index;
	ASSERT_STREQ(unix_path, desired_path) << VALUE_NOT_CORRECT << m_current_param;
}

void event_test::assert_event_in_buffers(pid_t pid_to_search, int event_to_search, bool presence) {
	uint16_t cpu_id = 0;
	pid_t pid = 0;

	if(pid_to_search == CURRENT_PID) {
		pid = ::getpid();
	} else {
		pid = pid_to_search;
	}

	if(event_to_search != CURRENT_EVENT_TYPE) {
		m_event_type = (ppm_event_code)event_to_search;
	}

	/* We need the while loop because in the buffers there could be different events
	 * with the type we are searching for. Even if we explicitly create only one event
	 * of this type, the system could create other events of the same type during the test!
	 */
	while(true) {
		m_event_header = get_event_from_ringbuffer(&cpu_id);
		if(m_event_header == NULL) {
			if(presence) {
				FAIL() << "There is no event '" << m_event_type << "' in the buffers." << std::endl;
			} else {
				break;
			}
		}
		if(m_event_header->tid == (uint64_t)pid && m_event_header->type == m_event_type) {
			if(presence) {
				break;
			} else {
				FAIL() << "There is an event '" << m_event_type
				       << "' in the buffers, but it shouldn't be there" << std::endl;
			}
		}
	}
}

bool event_test::is_ext4_fs(int fd) {
#ifdef __NR_fstatfs
	struct statfs buf;
	if(fstatfs(fd, &buf) != 0) {
		return false;
	}
	if(buf.f_type == EXT4_SUPER_MAGIC) {
		return true;
	}
#endif
	return false;
}
