/* SPDX-License-Identifier: GPL-2.0-only */

/*
 * Utility for entering Setup_mode using network
 *
 * Copyright (c) David Bauer <mail@david-bauer.net>
 */

#include <fcntl.h>
#include <stdio.h>
#include <sys/socket.h>
#include <sys/types.h>
#include <unistd.h>
#include <arpa/inet.h>
#include <linux/if_packet.h>
#include <net/ethernet.h>
#include <string.h>
#include <net/if.h>
#include <errno.h>
#include <sys/ioctl.h>
#include <time.h>

#include "gluon-remote-setup-mode.h"

#define MAX_INTERFACES	64
#define BUFSIZE		1024


char destination[]				= { REMOTE_SETUP_MODE_DST_MAC };
char type[]						= { REMOTE_SETUP_MODE_ETHERTYPE };


static int bind_socket_to_device(int fd, int ifindex)
{
	struct sockaddr_ll sll = {
		.sll_family = AF_PACKET,
		.sll_ifindex = ifindex,
		.sll_protocol = htons(ETH_P_ALL)
	}; 

	if((bind(fd , (struct sockaddr *)&sll , sizeof(sll))) < 0) {
		return 1;
	}

	return 0;
}

static int should_reset(char *buf, size_t size)
{
	int should_reset;
	char *tlv_ptr;

	should_reset = 0;

	if (size < REMOTE_SETUP_MODE_DATA_CMD_OFFSET + strlen(REMOTE_SETUP_MODE_DATA_CMD_SETUP) + 1) {
		/* Length mismatch */
		return 0;
	}

	if (buf[size - 1] != 0x00) {
		/* Wrong trailer */
		return 0;
	}

	if (memcmp(destination, &buf[REMOTE_SETUP_MODE_DST_MAC_OFFSET], sizeof(destination))) {
		/* Wrong destination-etheraddr */
		return 0;
	}

	if (memcmp(type, &buf[REMOTE_SETUP_MODE_ETHERTYPE_OFFSET], sizeof(type))) {
		/* Wrong Ethertype */
		return 0;
	}

	if (strcmp(&buf[REMOTE_SETUP_MODE_DATA_CMD_OFFSET], REMOTE_SETUP_MODE_DATA_CMD_SETUP)) {
		/* Wrong Command */
		return 0;
	}

	return 1;
}

int main(int argc, char *argv[])
{
	time_t start_time, current_time;
	int ifindex[MAX_INTERFACES];
	int sockets[MAX_INTERFACES];
	char recvbuf[BUFSIZE];
	int num_interfaces;
	int received;
	int i;

	start_time = time(NULL);

	if (argc < 2) {
		fprintf(stderr, "At least one Interface name is required\n", MAX_INTERFACES);
		return 1;
	}

	if (argc > MAX_INTERFACES) {
		fprintf(stderr, "Exceeded maximum number of supported interfaces of %d\n", MAX_INTERFACES);
		return 1;
	}
	
	num_interfaces = 0;
	for (i = 1; i < argc; i++) {
		ifindex[num_interfaces] = if_nametoindex(argv[i]);
		if (!ifindex[num_interfaces]) {
			fprintf(stderr, "Interface %s not found!\n", argv[i]);
		}
		num_interfaces++;
	}

	for (i = 0; i < num_interfaces; i++) {
		sockets[i] = socket(AF_PACKET, SOCK_RAW, htons(ETH_P_ALL));
		if (sockets[i] < 0) {
			fprintf(stderr, "Could not create socket for ifindex %d\n", ifindex[i]);
			return 1;
		}

		if (bind_socket_to_device(sockets[i], ifindex[i])) {
			fprintf(stderr, "Error binding socket to Interface %d\n", ifindex[i]);
			return 1;
		}
	}

	/* Start receiving */
	while (1) {
		/* Check if timeout is exceeded */
		current_time = time(NULL);
		if (current_time - start_time > REMOTE_SETUP_MODE_RX_TIMEOUT) {
			break;
		}

		for (i = 0; i < num_interfaces; i++) {
			received = recv(sockets[i], recvbuf, BUFSIZE, MSG_DONTWAIT);
			if (received < 0 && errno != EAGAIN && errno != EAGAIN) {
				fprintf(stderr, "Error receiving from ifindex %d - ret %d\n", ifindex[i], errno);
				continue;
			}
			if (received <= 0) {
				continue;
			}

			if (!should_reset(recvbuf, received)) {
				continue;
			}

			return 0;
		}
	}

	return 1;
}