/*
 * Copyright (c) 2016 Intel Corporation
 * Copyright (c) 2023 Nordic Semiconductor ASA
 *
 * SPDX-License-Identifier: Apache-2.0
 */

#include <zephyr/logging/log.h>
LOG_MODULE_DECLARE(net_shell);

#include <zephyr/net/mld.h>

#include "net_shell_private.h"
#include "../ip/ipv6.h"

#if defined(CONFIG_NET_IPV6_FRAGMENT)
void ipv6_frag_cb(struct net_ipv6_reassembly *reass, void *user_data)
{
	struct net_shell_user_data *data = user_data;
	const struct shell *sh = data->sh;
	int *count = data->user_data;
	char src[ADDR_LEN];
	int i;

	if (!*count) {
		PR("\nIPv6 reassembly Id         Remain "
		   "Src             \tDst\n");
	}

	snprintk(src, ADDR_LEN, "%s", net_sprint_ipv6_addr(&reass->src));

	PR("%p      0x%08x  %5d %16s\t%16s\n", reass, reass->id,
	   k_ticks_to_ms_ceil32(k_work_delayable_remaining_get(&reass->timer)),
	   src, net_sprint_ipv6_addr(&reass->dst));

	for (i = 0; i < CONFIG_NET_IPV6_FRAGMENT_MAX_PKT; i++) {
		if (reass->pkt[i]) {
			struct net_buf *frag = reass->pkt[i]->frags;

			PR("[%d] pkt %p->", i, reass->pkt[i]);

			while (frag) {
				PR("%p", frag);

				frag = frag->frags;
				if (frag) {
					PR("->");
				}
			}

			PR("\n");
		}
	}

	(*count)++;
}
#endif /* CONFIG_NET_IPV6_FRAGMENT */

#if defined(CONFIG_NET_IPV6_PE)
static void ipv6_pe_filter_cb(struct in6_addr *prefix, bool is_denylist,
			      void *user_data)
{
	struct net_shell_user_data *data = user_data;
	const struct shell *sh = data->sh;
	int *count = data->user_data;
	char ipaddr[INET6_ADDRSTRLEN + 1];

	net_addr_ntop(AF_INET6, prefix, ipaddr, sizeof(ipaddr) - 1);

	if (*count == 0) {
		PR("IPv6 privacy extension %s list filters :\n",
		   is_denylist ? "deny" : "allow");
	}

	PR("[%d] %s/64\n", *count, ipaddr);

	(*count)++;
}
#endif /* CONFIG_NET_IPV6_PE */

#if defined(CONFIG_NET_IPV6)
static void address_lifetime_cb(struct net_if *iface, void *user_data)
{
	struct net_shell_user_data *data = user_data;
	const struct shell *sh = data->sh;
	struct net_if_ipv6 *ipv6 = iface->config.ip.ipv6;
	const char *extra;

	ARG_UNUSED(user_data);

	PR("\nIPv6 addresses for interface %d (%p) (%s)\n",
	   net_if_get_by_iface(iface), iface, iface2str(iface, &extra));
	PR("============================================%s\n", extra);

	if (!ipv6) {
		PR("No IPv6 config found for this interface.\n");
		return;
	}

	PR("Type      \tState    \tLifetime (sec)\tRef\tAddress\n");

	ARRAY_FOR_EACH(ipv6->unicast, i) {
		char remaining_str[sizeof("01234567890")];
		uint8_t prefix_len = 128U;

		if (!ipv6->unicast[i].is_used ||
		    ipv6->unicast[i].address.family != AF_INET6) {
			continue;
		}

#if defined(CONFIG_NET_NATIVE_IPV6)
		struct net_if_ipv6_prefix *prefix;
		uint32_t remaining;

		remaining = net_timeout_remaining(&ipv6->unicast[i].lifetime,
						  k_uptime_get_32());

		prefix = net_if_ipv6_prefix_get(iface,
					   &ipv6->unicast[i].address.in6_addr);
		if (prefix) {
			prefix_len = prefix->len;
		}

		if (ipv6->unicast[i].is_infinite) {
			snprintk(remaining_str, sizeof(remaining_str) - 1,
				 "infinite");
		} else {
			snprintk(remaining_str, sizeof(remaining_str) - 1,
				 "%u", remaining);
		}
#else
	snprintk(remaining_str, sizeof(remaining_str) - 1, "infinite");
#endif /* CONFIG_NET_NATIVE_IPV6 */

		PR("%s  \t%s\t%14s\t%ld\t%s/%d%s\n",
		   addrtype2str(ipv6->unicast[i].addr_type),
		   addrstate2str(ipv6->unicast[i].addr_state),
		   remaining_str, atomic_get(&ipv6->unicast[i].atomic_ref),
		   net_sprint_ipv6_addr(&ipv6->unicast[i].address.in6_addr),
		   prefix_len,
		   ipv6->unicast[i].is_temporary ? " (temporary)" : "");
	}
}
#endif /* CONFIG_NET_IPV6 */

static int cmd_net_ipv6(const struct shell *sh, size_t argc, char *argv[])
{
#if defined(CONFIG_NET_IPV6)
	struct net_shell_user_data user_data;
#endif /* CONFIG_NET_IPV6 */

	PR("IPv6 support                              : %s\n",
	   IS_ENABLED(CONFIG_NET_IPV6) ?
	   "enabled" : "disabled");
	if (!IS_ENABLED(CONFIG_NET_IPV6)) {
		return -ENOEXEC;
	}

#if defined(CONFIG_NET_NATIVE_IPV6)
	PR("IPv6 fragmentation support                : %s\n",
	   IS_ENABLED(CONFIG_NET_IPV6_FRAGMENT) ? "enabled" :
	   "disabled");
	PR("Multicast Listener Discovery support      : %s\n",
	   IS_ENABLED(CONFIG_NET_IPV6_MLD) ? "enabled" :
	   "disabled");
	PR("Neighbor cache support                    : %s\n",
	   IS_ENABLED(CONFIG_NET_IPV6_NBR_CACHE) ? "enabled" :
	   "disabled");
	PR("Neighbor discovery support                : %s\n",
	   IS_ENABLED(CONFIG_NET_IPV6_ND) ? "enabled" :
	   "disabled");
	PR("Duplicate address detection (DAD) support : %s\n",
	   IS_ENABLED(CONFIG_NET_IPV6_DAD) ? "enabled" :
	   "disabled");
	PR("Router advertisement RDNSS option support : %s\n",
	   IS_ENABLED(CONFIG_NET_IPV6_RA_RDNSS) ? "enabled" :
	   "disabled");
	PR("6lo header compression support            : %s\n",
	   IS_ENABLED(CONFIG_NET_6LO) ? "enabled" :
	   "disabled");

	if (IS_ENABLED(CONFIG_NET_6LO_CONTEXT)) {
		PR("6lo context based compression "
		   "support     : %s\n",
		   IS_ENABLED(CONFIG_NET_6LO_CONTEXT) ? "enabled" :
		   "disabled");
	}

	PR("Privacy extension support                 : %s\n",
	   IS_ENABLED(CONFIG_NET_IPV6_PE) ? "enabled" : "disabled");
	PR("SLAAC IID generation method               : %s\n",
	   IS_ENABLED(CONFIG_NET_IPV6_IID_STABLE) ?
	   "stable (RFC 7217)" : "EUI-64 (RFC 4862)");

#if defined(CONFIG_NET_IPV6_PE)
	PR("Max number of IPv6 privacy extension filters "
	   "                : %d\n",
	   CONFIG_NET_IPV6_PE_FILTER_PREFIX_COUNT);
#endif /* CONFIG_NET_IPV6_PE */

	PR("Path MTU Discovery (PMTU)                 : %s\n",
	   IS_ENABLED(CONFIG_NET_IPV6_PMTU) ? "enabled" : "disabled");

#endif /* CONFIG_NET_NATIVE_IPV6 */

#if defined(CONFIG_NET_IPV6)
	PR("Max number of IPv6 network interfaces "
	   "in the system          : %d\n",
	   CONFIG_NET_IF_MAX_IPV6_COUNT);
	PR("Max number of unicast IPv6 addresses "
	   "per network interface   : %d\n",
	   CONFIG_NET_IF_UNICAST_IPV6_ADDR_COUNT);
	PR("Max number of multicast IPv6 addresses "
	   "per network interface : %d\n",
	   CONFIG_NET_IF_MCAST_IPV6_ADDR_COUNT);
	PR("Max number of IPv6 prefixes per network "
	   "interface            : %d\n",
	   CONFIG_NET_IF_IPV6_PREFIX_COUNT);

	user_data.sh = sh;
	user_data.user_data = NULL;

	/* Print information about address lifetime */
	net_if_foreach(address_lifetime_cb, &user_data);
#endif /* CONFIG_NET_IPV6 */

	return 0;
}

static int cmd_net_ip6_add(const struct shell *sh, size_t argc, char *argv[])
{
#if defined(CONFIG_NET_IPV6)
	struct net_if *iface = NULL;
	int idx;
	struct in6_addr addr;

	if (argc != 3) {
		PR_ERROR("Correct usage: net ipv6 add <index> <address>\n");
		return -EINVAL;
	}

	idx = get_iface_idx(sh, argv[1]);
	if (idx < 0) {
		return -ENOEXEC;
	}

	iface = net_if_get_by_index(idx);
	if (!iface) {
		PR_WARNING("No such interface in index %d\n", idx);
		return -ENOENT;
	}

	if (net_addr_pton(AF_INET6, argv[2], &addr)) {
		PR_ERROR("Invalid address: %s\n", argv[2]);
		return -EINVAL;
	}

	if (net_ipv6_is_addr_mcast(&addr)) {
		int ret;

		ret = net_ipv6_mld_join(iface, &addr);
		if (ret < 0) {
			PR_ERROR("Cannot %s multicast group %s for interface %d (%d)\n",
				 "join", net_sprint_ipv6_addr(&addr), idx, ret);
			if (ret == -ENOTSUP) {
				PR_INFO("Enable CONFIG_NET_IPV6_MLD for %s multicast "
					"group\n", "joining");
			}
			return ret;
		}
	} else {
		if (!net_if_ipv6_addr_add(iface, &addr, NET_ADDR_MANUAL, 0)) {
			PR_ERROR("Failed to add %s address to interface %p\n", argv[2], iface);
		}
	}

#else /* CONFIG_NET_IPV6 */
	PR_INFO("Set %s to enable %s support.\n", "CONFIG_NET_IPV6", "IPv6");
#endif /* CONFIG_NET_IPV6 */
	return 0;
}

static int cmd_net_ip6_del(const struct shell *sh, size_t argc, char *argv[])
{
#if defined(CONFIG_NET_IPV6)
	struct net_if *iface = NULL;
	int idx;
	struct in6_addr addr;

	if (argc != 3) {
		PR_ERROR("Correct usage: net ipv6 del <index> <address>\n");
		return -EINVAL;
	}

	idx = get_iface_idx(sh, argv[1]);
	if (idx < 0) {
		return -ENOEXEC;
	}

	iface = net_if_get_by_index(idx);
	if (!iface) {
		PR_WARNING("No such interface in index %d\n", idx);
		return -ENOENT;
	}

	if (net_addr_pton(AF_INET6, argv[2], &addr)) {
		PR_ERROR("Invalid address: %s\n", argv[2]);
		return -EINVAL;
	}

	if (net_ipv6_is_addr_mcast(&addr)) {
		int ret;

		ret = net_ipv6_mld_leave(iface, &addr);
		if (ret < 0) {
			PR_ERROR("Cannot %s multicast group %s for interface %d (%d)\n",
				 "leave", net_sprint_ipv6_addr(&addr), idx, ret);
			if (ret == -ENOTSUP) {
				PR_INFO("Enable CONFIG_NET_IPV6_MLD for %s multicast "
					"group\n", "leaving");
			}
			return ret;
		}
	} else {
		if (!net_if_ipv6_addr_rm(iface, &addr)) {
			PR_ERROR("Failed to delete %s\n", argv[2]);
			return -1;
		}
	}

#else /* CONFIG_NET_IPV6 */
	PR_INFO("Set %s to enable %s support.\n", "CONFIG_NET_IPV6", "IPv6");
#endif /* CONFIG_NET_IPV6 */
	return 0;
}

static int cmd_net_ip6_pe(const struct shell *sh, size_t argc, char *argv[])
{
#if defined(CONFIG_NET_NATIVE_IPV6)
#if CONFIG_NET_IPV6_PE_FILTER_PREFIX_COUNT > 0
	bool do_allowlisting = true;
	struct in6_addr prefix;
	bool do_add;
	int arg = 1;
	int ret;

	if (argc == 0) {
		PR_ERROR("Correct usage: net ipv6 pe [add | del] [allow | deny] [<prefix>]\n");
		return -EINVAL;
	}

	if (argc == 1) {
		struct net_shell_user_data user_data;
		int count = 0;

		user_data.sh = sh;
		user_data.user_data = &count;

		count = net_ipv6_pe_filter_foreach(ipv6_pe_filter_cb, &user_data);
		if (count == 0) {
			PR("No privacy extension filters found.");
		}

		return 0;
	}

	if (strcmp(argv[arg], "add") == 0) {
		arg++;
		do_add = true;
	} else if (strcmp(argv[arg], "del") == 0) {
		arg++;
		do_add = false;
	} else {
		PR("Unknown sub-option \"%s\"\n", argv[arg]);
		return 0;
	}

	if (!argv[arg]) {
		PR("No sub-options given. See \"help net ipv6\" "
		   "command for details.\n");
		return 0;
	}

	if (strcmp(argv[arg], "allow") == 0) {
		arg++;
	} else if (strcmp(argv[arg], "deny") == 0) {
		arg++;
		do_allowlisting = false;
	}

	if (!argv[arg]) {
		PR("No sub-options given. See \"help net ipv6\" "
		   "command for details.\n");
		return 0;
	}

	ret = net_addr_pton(AF_INET6, argv[arg], &prefix);
	if (ret < 0) {
		PR("Invalid prefix \"%s\"\n", argv[arg]);
		if (strstr(argv[arg], "/")) {
			PR("Do not add the prefix length.\n");
		}

		return 0;
	}

	if (do_add) {
		ret = net_ipv6_pe_add_filter(&prefix, !do_allowlisting);
	} else {
		ret = net_ipv6_pe_del_filter(&prefix);
	}

	if (ret < 0) {
		if (ret == -EALREADY) {
			PR("Filter %s already in %s list\n",
			   net_sprint_ipv6_addr(&prefix),
			   do_allowlisting ? "allow" : "deny");
		} else if (ret == -ENOENT) {
			PR("No such filter %s found\n",
			   net_sprint_ipv6_addr(&prefix));
		} else {
			PR("Cannot %s %s %sfilter (%d)\n",
			   do_add ? "add" : "delete",
			   argv[arg],
			   do_add ?
			   (do_allowlisting ? "allowlist " :
			    "denylist ") : "",
			   ret);
		}

		return 0;
	}

	PR("%s %s%sfilter for %s\n",
	   do_add ? "Added" : "Deleted",
	   do_add ? (do_allowlisting ? "allow" : "deny") : "",
	   do_add ? " list " : "",
	   argv[arg]);
#else
	PR("IPv6 privacy extension filter support is disabled.\n");
	PR("Set CONFIG_NET_IPV6_PE_FILTER_PREFIX_COUNT > 0 to enable it.\n");
#endif
#else /* CONFIG_NET_NATIVE_IPV6 */
	PR_INFO("Set %s and %s to enable native %s support.\n",
			"CONFIG_NET_NATIVE", "CONFIG_NET_IPV6", "IPv6");
#endif /* CONFIG_NET_NATIVE_IPV6 */
	return 0;
}

SHELL_STATIC_SUBCMD_SET_CREATE(net_cmd_ip6,
	SHELL_CMD(add, NULL,
		  "'net ipv6 add <index> <address>' adds the address to the interface.",
		  cmd_net_ip6_add),
	SHELL_CMD(del, NULL,
		  "'net ipv6 del <index> <address>' deletes the address from the interface.",
		  cmd_net_ip6_del),
	SHELL_CMD(pe, NULL,
		  "net ipv6 pe add [allow|deny] <IPv6 prefix>\n"
		  "Add IPv6 address to filter list. The allow/deny "
		  "parameter tells if this is allow listed (accepted) or "
		  "deny listed (declined) prefix. Default is to allow list "
		  "the prefix.\n"
		  "ipv6 pe del <IPv6 prefix>\n"
		  "Delete IPv6 address from filter list.",
		  cmd_net_ip6_pe),
	SHELL_SUBCMD_SET_END
);

SHELL_SUBCMD_ADD((net), ipv6, &net_cmd_ip6,
		 "Print information about IPv6 specific information and "
		 "configuration.",
		 cmd_net_ipv6, 1, 0);
