/* SPDX-License-Identifier: MIT */
/*
 * Description: verify that timestamp retrieval that needs retrying with
 *		a current list of SKBs works.
 *
 * Based on a test case from Google Big Sleep.
 *
 */
#include <arpa/inet.h>
#include <err.h>
#include <fcntl.h>
#include <sys/time.h>
#include <linux/errqueue.h>
#include <linux/net_tstamp.h>
#include <netinet/in.h>
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <sys/socket.h>
#include <sys/uio.h>
#include <unistd.h>

#include "liburing.h"
#include "helpers.h"

/*
 * Create a socket whose error queue contains both timestamp information and
 * actual errors.
 */
static int create_sock_with_timestamps_and_errors(void)
{
	struct sockaddr_in recv_addr = { };
	struct sockaddr_in closed = { };
	int recv_sock, sock, val;
	char data = 'A';

	sock = socket(AF_INET, SOCK_DGRAM, 0);
	if (sock < 0)
		err(1, "socket");

	/* Enable TX timestamps */
	val = SOF_TIMESTAMPING_SOFTWARE | SOF_TIMESTAMPING_TX_SOFTWARE |
		SOF_TIMESTAMPING_OPT_ID | SOF_TIMESTAMPING_OPT_TSONLY;
	if (setsockopt(sock, SOL_SOCKET, SO_TIMESTAMPING, &val, sizeof(val)) < 0)
		err(1, "setsockopt(SO_TIMESTAMPING)");

	/* Enable IP_RECVERR */
	val = 1;
	if (setsockopt(sock, IPPROTO_IP, IP_RECVERR, &val, sizeof(val)) < 0)
		err(1, "setsockopt(IP_RECVERR)");

	recv_sock = socket(AF_INET, SOCK_DGRAM, 0);
	if (recv_sock < 0)
		err(1, "socket");

	recv_addr.sin_family = AF_INET;
	recv_addr.sin_addr.s_addr = inet_addr("127.0.0.1");
	if (bind(recv_sock, (struct sockaddr*)&recv_addr, sizeof(recv_addr)) < 0)
		err(1, "bind receiver");

	socklen_t recv_len = sizeof(recv_addr);
	if (getsockname(recv_sock, (struct sockaddr*)&recv_addr, &recv_len) < 0)
		err(1, "getsockname");

	/* Send packets to generate TX timestamps. */
	for (int i = 0; i < 2; i++) {
		if (sendto(sock, &data, sizeof(data), 0, (struct sockaddr*)&recv_addr, sizeof(recv_addr)) < 0)
			err(1, "sendto (%d)", i);
	}

	closed.sin_family = AF_INET;
	closed.sin_addr.s_addr = inet_addr("127.0.0.1");
	closed.sin_port = htons(9); /* assuming no one is listening on port 9 */
	if (sendto(sock, &data, sizeof(data), 0, (struct sockaddr*) &closed, sizeof(closed)) < 0)
		warn("sendto");

	return sock;
}

int main(int argc, char *argv[])
{
	struct io_uring_sqe *sqe;
	struct io_uring_cqe *cqe;
	struct io_uring ring;
	struct io_uring_params params = {
		.flags = IORING_SETUP_CQE32, /* required for timestamps */
	};
	char buf[1024], ctrl_recv[1024];
	struct iovec iov_recv = { .iov_base = buf, .iov_len = sizeof(buf) };
	struct msghdr msg_recv = {
		.msg_iov = &iov_recv,
		.msg_iovlen = 1,
		.msg_control = ctrl_recv,
		.msg_controllen = sizeof(ctrl_recv),
	};
	int sock, ret;

	if (argc > 1)
		return T_EXIT_SKIP;

	sock = create_sock_with_timestamps_and_errors();

	ret = io_uring_queue_init_params(1, &ring, &params);
	if (ret < 0) {
		if (ret == -EINVAL)
			return T_EXIT_SKIP;
		err(1, "io_uring_queue_init_params: %d", ret);
	}

	sqe = io_uring_get_sqe(&ring);
	*sqe = (struct io_uring_sqe) {
		.opcode = IORING_OP_URING_CMD,
		.fd = sock,
		.cmd_op = SOCKET_URING_OP_TX_TIMESTAMP,
	};

	io_uring_submit(&ring);

	ret = io_uring_wait_cqe(&ring, &cqe);
	if (ret < 0)
		errx(1, "io_uring_wait_cqe: %d", ret);
	ret = cqe->res;
	io_uring_cqe_seen(&ring, cqe);
	/* kernel doesn't support retrieving timestamps */
	if (ret == -EOPNOTSUPP)
		return T_EXIT_SKIP;

	recvmsg(sock, &msg_recv, MSG_ERRQUEUE | MSG_DONTWAIT);
	return T_EXIT_PASS;
}
