// SPDX-License-Identifier: GPL-2.0-or-later
/*
 * Copyright (c) International Business Machines  Corp., 2001
 * 07/2001 Ported by Wayne Boyer
 * Copyright (C) 2024 SUSE LLC Andrea Manzini <andrea.manzini@suse.com>
 */

/*\
 * [Description]
 *
 * Verify that getpeername() returns the proper errno for various failure cases:
 *
 * - EBADF on invalid address.
 * - ENOTSOCK on socket opened on /dev/null.
 * - ENOTCONN on socket not connected.
 * - EINVAL on negative addrlen.
 * - EFAULT on invalid addr/addrlen pointers.
 */

#include "tst_test.h"

static struct sockaddr_in server_addr;
static struct sockaddr_in fsin1;
static socklen_t sinlen;
static socklen_t invalid_sinlen = -1;
static int sv[2];
static int sockfd = -1;

static void setup_fd_file(void)
{
	sockfd = SAFE_OPEN("/dev/null", O_WRONLY, 0666);
}

static void setup_fd_stream(void)
{
	sockfd = SAFE_SOCKET(PF_INET, SOCK_STREAM, 0);
	SAFE_BIND(sockfd, (struct sockaddr *)&server_addr, sizeof(server_addr));
}

static void cleanup_fd(void)
{
	if (sockfd)
		SAFE_CLOSE(sockfd);
}

static void setup_pair(void)
{
	SAFE_SOCKETPAIR(PF_UNIX, SOCK_STREAM, 0, sv);
	sockfd = sv[0];
}

static void cleanup_pair(void)
{
	if (sv[0])
		SAFE_CLOSE(sv[0]);

	if (sv[1])
		SAFE_CLOSE(sv[1]);
}

static struct test_case {
	struct sockaddr *sockaddr;
	socklen_t *addrlen;
	int experrno;
	void (*setup)(void);
	void (*cleanup)(void);
} test_cases[] = {
	{.addrlen = &sinlen, .experrno = EBADF},
	{.addrlen = &sinlen, .experrno = ENOTSOCK, .setup = setup_fd_file,
		.cleanup = cleanup_fd },
	{.addrlen = &sinlen, .experrno = ENOTCONN, .setup = setup_fd_stream,
		.cleanup = cleanup_fd },
	{.addrlen = &invalid_sinlen, .experrno = EINVAL, .setup = setup_pair,
		.cleanup = cleanup_pair},
	{.sockaddr = (struct sockaddr *) -1, .addrlen = &sinlen, .experrno = EFAULT,
		.setup = setup_pair, .cleanup = cleanup_pair},
	{.experrno = EFAULT, .setup = setup_pair, .cleanup = cleanup_pair},
	{.addrlen = (socklen_t *) 1, .experrno = EFAULT, .setup = setup_pair,
		.cleanup = cleanup_pair },
};

static void verify_getpeername(unsigned int nr)
{
	struct test_case *tc = &test_cases[nr];

	if (tc->setup != NULL)
		tc->setup();

	if (!tc->sockaddr)
		tc->sockaddr = (struct sockaddr *)&fsin1;

	TST_EXP_FAIL(getpeername(sockfd, tc->sockaddr, tc->addrlen), tc->experrno);

	if (tc->cleanup != NULL)
		tc->cleanup();
}

static void setup(void)
{
	server_addr.sin_family = AF_INET;
	server_addr.sin_port = 0;
	server_addr.sin_addr.s_addr = INADDR_ANY;
	sinlen = sizeof(fsin1);
}

static struct tst_test test = {
	.setup = setup,
	.test = verify_getpeername,
	.tcnt = ARRAY_SIZE(test_cases),
};
