/* SPDX-License-Identifier: GPL-2.0-or-later */
/*
 * em_u32.c		U32 Ematch
 *
 * Authors:	Thomas Graf <tgraf@suug.ch>
 */

#include <stdio.h>
#include <stdlib.h>
#include <unistd.h>
#include <fcntl.h>
#include <sys/socket.h>
#include <netinet/in.h>
#include <arpa/inet.h>
#include <string.h>
#include <errno.h>

#include "m_ematch.h"

extern struct ematch_util u32_ematch_util;

static void u32_print_usage(FILE *fd)
{
	fprintf(fd,
	    "Usage: u32(ALIGN VALUE MASK at [ nexthdr+ ] OFFSET)\n" \
	    "where: ALIGN  := { u8 | u16 | u32 }\n" \
	    "\n" \
	    "Example: u32(u16 0x1122 0xffff at nexthdr+4)\n");
}

static int u32_parse_eopt(struct nlmsghdr *n, struct tcf_ematch_hdr *hdr,
			  struct bstr *args)
{
	struct bstr *a;
	int align, nh_len;
	unsigned long key, mask, offmask = 0, offset;
	struct tc_u32_key u_key = {};

#define PARSE_ERR(CARG, FMT, ARGS...) \
	em_parse_error(EINVAL, args, CARG, &u32_ematch_util, FMT, ##ARGS)

	if (args == NULL)
		return PARSE_ERR(args, "u32: missing arguments");

	if (!bstrcmp(args, "u8"))
		align = 1;
	else if (!bstrcmp(args, "u16"))
		align = 2;
	else if (!bstrcmp(args, "u32"))
		align = 4;
	else
		return PARSE_ERR(args, "u32: invalid alignment");

	a = bstr_next(args);
	if (a == NULL)
		return PARSE_ERR(a, "u32: missing key");

	key = bstrtoul(a);
	if (key == ULONG_MAX)
		return PARSE_ERR(a, "u32: invalid key, must be numeric");

	a = bstr_next(a);
	if (a == NULL)
		return PARSE_ERR(a, "u32: missing mask");

	mask = bstrtoul(a);
	if (mask == ULONG_MAX)
		return PARSE_ERR(a, "u32: invalid mask, must be numeric");

	a = bstr_next(a);
	if (a == NULL || bstrcmp(a, "at") != 0)
		return PARSE_ERR(a, "u32: missing \"at\"");

	a = bstr_next(a);
	if (a == NULL)
		return PARSE_ERR(a, "u32: missing offset");

	nh_len = strlen("nexthdr+");
	if (a->len > nh_len && !memcmp(a->data, "nexthdr+", nh_len)) {
		char buf[a->len - nh_len + 1];

		offmask = -1;
		strncpy(buf, a->data + nh_len, a->len - nh_len + 1);
		offset = strtoul(buf, NULL, 0);
	} else if (!bstrcmp(a, "nexthdr+")) {
		a = bstr_next(a);
		if (a == NULL)
			return PARSE_ERR(a, "u32: missing offset");
		offset = bstrtoul(a);
	} else
		offset = bstrtoul(a);

	if (offset == ULONG_MAX)
		return PARSE_ERR(a, "u32: invalid offset");

	if (a->next)
		return PARSE_ERR(a->next, "u32: unexpected trailer");

	switch (align) {
		case 1:
			if (key > 0xFF)
				return PARSE_ERR(a, "Illegal key (>0xFF)");
			if (mask > 0xFF)
				return PARSE_ERR(a, "Illegal mask (>0xFF)");

			key <<= 24 - ((offset & 3) * 8);
			mask <<= 24 - ((offset & 3) * 8);
			offset &= ~3;
			break;

		case 2:
			if (key > 0xFFFF)
				return PARSE_ERR(a, "Illegal key (>0xFFFF)");
			if (mask > 0xFFFF)
				return PARSE_ERR(a, "Illegal mask (>0xFFFF)");

			if ((offset & 3) == 0) {
				key <<= 16;
				mask <<= 16;
			}
			offset &= ~3;
			break;
	}

	key = htonl(key);
	mask = htonl(mask);

	if (offset % 4)
		return PARSE_ERR(a, "u32: invalid offset alignment, " \
		    "must be aligned to 4.");

	key &= mask;

	u_key.mask = mask;
	u_key.val = key;
	u_key.off = offset;
	u_key.offmask = offmask;

	addraw_l(n, MAX_MSG, hdr, sizeof(*hdr));
	addraw_l(n, MAX_MSG, &u_key, sizeof(u_key));

#undef PARSE_ERR
	return 0;
}

static int u32_print_eopt(FILE *fd, struct tcf_ematch_hdr *hdr, void *data,
			  int data_len)
{
	struct tc_u32_key *u_key = data;

	if (data_len < sizeof(*u_key)) {
		fprintf(stderr, "U32 header size mismatch\n");
		return -1;
	}

	fprintf(fd, "%08x/%08x at %s%d",
	    (unsigned int) ntohl(u_key->val),
	    (unsigned int) ntohl(u_key->mask),
	    u_key->offmask ? "nexthdr+" : "",
	    u_key->off);

	return 0;
}

struct ematch_util u32_ematch_util = {
	.kind = "u32",
	.kind_num = TCF_EM_U32,
	.parse_eopt = u32_parse_eopt,
	.print_eopt = u32_print_eopt,
	.print_usage = u32_print_usage
};
