/*
 * i1905_netlink.c - netlink interface to kernel.
 *
 * Copyright (C) 2021 IOPSYS Software Solutions AB. All rights reserved.
 *
 * Author: anjan.chanda@iopsys.eu
 *
 * See LICENSE file for license related information.
 *
 */

#include "dbh_nl.h"

#include <time.h>
#include <easy/utils.h>
#include <easy/if_utils.h>
#include <errno.h>
#include <libubox/list.h>
#include <libubox/uloop.h>
#include <linux/if_link.h>
#include <linux/netlink.h>
#include <linux/rtnetlink.h>
#include <net/if.h>
#include <netlink/attr.h>
#include <netlink/netlink.h>
#include <netlink/genl/genl.h>
#include <stdbool.h>
#include <stdint.h>
#include <stdio.h>
#include <string.h>
#include <sys/socket.h>
#include <linux/if.h>

#include "dynbh.h"

struct nl_msg;


struct i1905_nlevent {
	struct uloop_fd uloop;
	void (*error_cb)(struct i1905_nlevent *e, int error);
	void (*event_cb)(struct i1905_nlevent *e);
};

struct event_socket {
	struct i1905_nlevent ev;
	struct nl_sock *sock;
	int sock_bufsize;
};

static int i1905_nlevents_cb(struct nl_msg *msg, void *arg);

static void handle_error(struct i1905_nlevent *e, int error)
{
	struct event_socket *ev_sock = container_of(e, struct event_socket, ev);

	if (error != ENOBUFS)
		goto err;

	ev_sock->sock_bufsize *= 2;
	if (nl_socket_set_buffer_size(ev_sock->sock, ev_sock->sock_bufsize, 0))
		goto err;

	return;

err:
	e->uloop.cb = NULL;
	uloop_fd_delete(&e->uloop);
}

static void recv_nlevents(struct i1905_nlevent *e)
{
	struct event_socket *ev_sock = container_of(e, struct event_socket, ev);

	nl_recvmsgs_default(ev_sock->sock);
}

static struct event_socket rtnl_event = {
	.ev = {
		.uloop = {.fd = - 1, },
		.error_cb = handle_error,
		.event_cb = recv_nlevents,
	},
	.sock = NULL,
	.sock_bufsize = 0x20000,
};


static int i1905_handle_nlevents_link(struct dynbh_ctx *priv,
				      struct nlmsghdr *hdr, bool add)
{
	struct ifinfomsg *ifi = nlmsg_data(hdr);
	struct nlattr *nla[__IFLA_MAX];
	struct ethport *port;
	uint8_t macaddr[6] = {0};
	char ifname[16] = {0};
	int br_ifindex = 0;
	uint8_t operstate;

	fprintf(stderr, "%s: ------------->\n", __func__);

	if (!nlmsg_valid_hdr(hdr, sizeof(*ifi)))
		return NL_SKIP;

	nlmsg_parse(hdr, sizeof(*ifi), nla, __IFLA_MAX - 1, NULL);
	if (!nla[IFLA_IFNAME])
		return NL_SKIP;

	nla_memcpy(ifname, nla[IFLA_IFNAME], 15);
	nla_memcpy(macaddr, nla[IFLA_ADDRESS], sizeof(macaddr));
	nla_memcpy(&operstate, nla[IFLA_OPERSTATE], 1);

	port = ethport_by_ifname(priv, ifname);
	if (!port)
		return NL_SKIP;


	if (!!(ifi->ifi_flags & IFF_RUNNING)) {
		fprintf(stderr, "%s: %s is UP RUNNING\n", __func__, ifname);
	}

	if (!(ifi->ifi_flags & IFF_UP)) {
		fprintf(stderr, "%s: %s is down. skip..\n", __func__, ifname);
		return NL_OK;
	}

	br_ifindex = if_isbridge_interface(ifname);
	if (br_ifindex < 0) {
		fprintf(stderr, "%s: %s error getting br_ifindex\n", __func__, ifname);
		return NL_SKIP;
	}

	fprintf(stderr, "%s: %s : %s (" MACFMT ", %d), master = %d, fam = %d, flags = 0x%x operstate:0x%x\n",
	        __func__, (add ? "NEWLINK" : "DELLINK"), ifname,
		MAC2STR(macaddr), ifi->ifi_index, br_ifindex, ifi->ifi_family,
		ifi->ifi_flags, operstate);

	if (br_ifindex > 0 && ifi->ifi_family == AF_BRIDGE &&
	    !port->connected && operstate == IF_OPER_UP) {
		/* port got connected */
		dynbh_handle_port_up(port);
	} else if (port->connected && operstate != IF_OPER_UP) {
		/* port got disconnected */
		dynbh_handle_port_down(priv, port);
	}
	return NL_OK;
}

static int i1905_nlevents_cb(struct nl_msg *msg, void *arg)
{
	struct nlmsghdr *hdr = nlmsg_hdr(msg);
	struct dynbh_ctx *priv = arg;
	int ret = NL_SKIP;
	bool add = false;

	switch (hdr->nlmsg_type) {
	case RTM_NEWLINK:
		add = true;
	//case RTM_DELLINK:
		fprintf(stderr, "%s!\n", (add ? "newlink":"dellink"));
		ret = i1905_handle_nlevents_link(priv, hdr, add);
		break;
	default:
		break;
	}

	return ret;
}


static void i1905_receive_nlevents(struct uloop_fd *u, unsigned int events)
{
	struct i1905_nlevent *e = container_of(u, struct i1905_nlevent, uloop);

	if (u->error) {
		int ret = -1;
		socklen_t ret_len = sizeof(ret);

		u->error = false;
		if (e->error_cb &&
		    getsockopt(u->fd, SOL_SOCKET, SO_ERROR, &ret, &ret_len) == 0) {
			e->error_cb(e, ret);
		}
	}

	if (e->event_cb) {
		e->event_cb(e);
		return;
	}
}

int i1905_register_nlevents(struct dynbh_ctx *priv)
{
	struct nl_sock *sk;

	fprintf(stderr, "Opening netlink!\n");

	sk = nl_socket_alloc();
	if (!sk) {
		fprintf(stderr, "Unable to open nl event socket: %m");
		return -1;
	}

	if (nl_connect(sk, NETLINK_ROUTE) < 0) {
		nl_socket_free(sk);
		return -1;
	}

	rtnl_event.sock = sk;

	if (nl_socket_set_buffer_size(rtnl_event.sock, rtnl_event.sock_bufsize, 0)) {
		fprintf(stderr, "%s: %d\n", __func__, __LINE__);
		goto out_err;
	}

	nl_socket_disable_seq_check(rtnl_event.sock);

	nl_socket_modify_cb(rtnl_event.sock, NL_CB_VALID, NL_CB_CUSTOM,
			    i1905_nlevents_cb, priv);

	if (nl_socket_add_memberships(rtnl_event.sock,
				      RTNLGRP_NEIGH, RTNLGRP_LINK, 0))
		goto out_err;

	rtnl_event.ev.uloop.fd = nl_socket_get_fd(rtnl_event.sock);
	rtnl_event.ev.uloop.cb = i1905_receive_nlevents;
	uloop_fd_add(&rtnl_event.ev.uloop, ULOOP_READ |
		     ((rtnl_event.ev.error_cb) ? ULOOP_ERROR_CB : 0));
	fprintf(stderr, "netlink success!\n");

	return 0;

out_err:
	if (rtnl_event.sock) {
		nl_socket_free(rtnl_event.sock);
		rtnl_event.sock = NULL;
		rtnl_event.ev.uloop.fd = -1;
	}
	fprintf(stderr, "netlink fail!\n");
	return -1;
}

void i1905_unregister_nlevents(struct dynbh_ctx *priv)
{
	UNUSED(priv);

	if (rtnl_event.sock) {
		uloop_fd_delete(&rtnl_event.ev.uloop);
		rtnl_event.ev.uloop.fd = -1;
		nl_socket_free(rtnl_event.sock);
		rtnl_event.sock = NULL;
	}
}
