/* SPDX-License-Identifier: LGPL-2.1-or-later */

#include "netdev-util.h"
#include "networkd-address.h"
#include "networkd-link.h"
#include "string-table.h"

static const char * const netdev_local_address_type_table[_NETDEV_LOCAL_ADDRESS_TYPE_MAX] = {
        [NETDEV_LOCAL_ADDRESS_IPV4LL]  = "ipv4_link_local",
        [NETDEV_LOCAL_ADDRESS_IPV6LL]  = "ipv6_link_local",
        [NETDEV_LOCAL_ADDRESS_DHCP4]   = "dhcp4",
        [NETDEV_LOCAL_ADDRESS_DHCP6]   = "dhcp6",
        [NETDEV_LOCAL_ADDRESS_SLAAC]   = "slaac",
};

DEFINE_STRING_TABLE_LOOKUP(netdev_local_address_type, NetDevLocalAddressType);

int link_get_local_address(
                Link *link,
                NetDevLocalAddressType type,
                int family,
                int *ret_family,
                union in_addr_union *ret_address) {

        Address *a;

        assert(link);

        switch (type) {
        case NETDEV_LOCAL_ADDRESS_IPV4LL:
                assert(IN_SET(family, AF_UNSPEC, AF_INET));
                family = AF_INET;
                break;
        case NETDEV_LOCAL_ADDRESS_IPV6LL:
                assert(IN_SET(family, AF_UNSPEC, AF_INET6));
                family = AF_INET6;
                break;
        case NETDEV_LOCAL_ADDRESS_DHCP4:
                assert(IN_SET(family, AF_UNSPEC, AF_INET));
                family = AF_INET;
                break;
        case NETDEV_LOCAL_ADDRESS_DHCP6:
                assert(IN_SET(family, AF_UNSPEC, AF_INET6));
                family = AF_INET6;
                break;
        case NETDEV_LOCAL_ADDRESS_SLAAC:
                assert(IN_SET(family, AF_UNSPEC, AF_INET6));
                family = AF_INET6;
                break;
        default:
                assert_not_reached();
        }

        if (!IN_SET(link->state, LINK_STATE_CONFIGURING, LINK_STATE_CONFIGURED))
                return -EBUSY;

        SET_FOREACH(a, link->addresses) {
                if (!address_is_ready(a))
                        continue;

                if (a->family != family)
                        continue;

                if (in_addr_is_set(a->family, &a->in_addr_peer))
                        continue;

                switch (type) {
                case NETDEV_LOCAL_ADDRESS_IPV4LL:
                        if (a->source != NETWORK_CONFIG_SOURCE_IPV4LL)
                                continue;
                        break;
                case NETDEV_LOCAL_ADDRESS_IPV6LL:
                        if (!in6_addr_is_link_local(&a->in_addr.in6))
                                continue;
                        break;
                case NETDEV_LOCAL_ADDRESS_DHCP4:
                        if (a->source != NETWORK_CONFIG_SOURCE_DHCP4)
                                continue;
                        break;
                case NETDEV_LOCAL_ADDRESS_DHCP6:
                        if (a->source != NETWORK_CONFIG_SOURCE_DHCP6)
                                continue;
                        break;
                case NETDEV_LOCAL_ADDRESS_SLAAC:
                        if (a->source != NETWORK_CONFIG_SOURCE_NDISC)
                                continue;
                        break;
                default:
                        assert_not_reached();
                }

                if (ret_family)
                        *ret_family = a->family;
                if (ret_address)
                        *ret_address = a->in_addr;
                return 1;
        }

        return -ENXIO;
}
