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

#include <net/if.h>
#include <net/if_arp.h>

#include "sd-lldp-tx.h"

#include "networkd-link.h"
#include "networkd-lldp-tx.h"
#include "networkd-manager.h"
#include "networkd-sysctl.h"
#include "parse-util.h"
#include "string-table.h"
#include "string-util.h"
#include "strv.h"
#include "vlan.h"

static bool link_lldp_tx_enabled(Link *link) {
        assert(link);

        if (link->flags & IFF_LOOPBACK)
                return false;

        if (link->iftype != ARPHRD_ETHER)
                return false;

        if (!link->network)
                return false;

        if (link->kind && STR_IN_SET(link->kind, "bridge", "bond"))
                return false;

        return link->network->lldp_multicast_mode >= 0 &&
                link->network->lldp_multicast_mode < _SD_LLDP_MULTICAST_MODE_MAX;
}

int link_lldp_tx_configure(Link *link) {
        int r;

        assert(link);

        if (!link_lldp_tx_enabled(link))
                return 0;

        if (link->lldp_tx)
                return -EBUSY;

        r = sd_lldp_tx_new(&link->lldp_tx);
        if (r < 0)
                return r;

        r = sd_lldp_tx_attach_event(link->lldp_tx, link->manager->event, 0);
        if (r < 0)
                return r;

        r = sd_lldp_tx_set_ifindex(link->lldp_tx, link->ifindex);
        if (r < 0)
                return r;

        r = sd_lldp_tx_set_hwaddr(link->lldp_tx, &link->hw_addr.ether);
        if (r < 0)
                return r;

        assert(link->network);

        r = sd_lldp_tx_set_multicast_mode(link->lldp_tx, link->network->lldp_multicast_mode);
        if (r < 0)
                return r;

        r = sd_lldp_tx_set_capabilities(link->lldp_tx,
                                        SD_LLDP_SYSTEM_CAPABILITIES_STATION |
                                        SD_LLDP_SYSTEM_CAPABILITIES_BRIDGE |
                                        SD_LLDP_SYSTEM_CAPABILITIES_ROUTER,
                                        (link_get_ip_forwarding(link, AF_INET) > 0 || link_get_ip_forwarding(link, AF_INET6) > 0) ?
                                        SD_LLDP_SYSTEM_CAPABILITIES_ROUTER : SD_LLDP_SYSTEM_CAPABILITIES_STATION);
        if (r < 0)
                return r;

        r = sd_lldp_tx_set_port_description(link->lldp_tx, link->network->description);
        if (r < 0)
                return r;

        r = sd_lldp_tx_set_mud_url(link->lldp_tx, link->network->lldp_mudurl);
        if (r < 0)
                return r;

        if (link->netdev && link->netdev->kind == NETDEV_KIND_VLAN) {
                r = sd_lldp_tx_set_vlan_id(link->lldp_tx, VLAN(link->netdev)->id);
                if (r < 0)
                        return r;
        }

        return 0;
}

int link_lldp_tx_update_capabilities(Link *link) {
        int r;

        assert(link);

        if (!link->lldp_tx)
                return 0; /* disabled, or not configured yet. */

        r = sd_lldp_tx_set_capabilities(link->lldp_tx,
                                        SD_LLDP_SYSTEM_CAPABILITIES_STATION |
                                        SD_LLDP_SYSTEM_CAPABILITIES_BRIDGE |
                                        SD_LLDP_SYSTEM_CAPABILITIES_ROUTER,
                                        (link_get_ip_forwarding(link, AF_INET) > 0 || link_get_ip_forwarding(link, AF_INET6) > 0) ?
                                        SD_LLDP_SYSTEM_CAPABILITIES_ROUTER : SD_LLDP_SYSTEM_CAPABILITIES_STATION);
        if (r < 0)
                return r;

        if (sd_lldp_tx_is_running(link->lldp_tx)) {
                r = sd_lldp_tx_stop(link->lldp_tx);
                if (r < 0)
                        return r;

                r = sd_lldp_tx_start(link->lldp_tx);
                if (r < 0)
                        return r;
        }

        return 0;
}

static const char * const lldp_multicast_mode_table[_SD_LLDP_MULTICAST_MODE_MAX] = {
        [SD_LLDP_MULTICAST_MODE_NEAREST_BRIDGE]  = "nearest-bridge",
        [SD_LLDP_MULTICAST_MODE_NON_TPMR_BRIDGE] = "non-tpmr-bridge",
        [SD_LLDP_MULTICAST_MODE_CUSTOMER_BRIDGE] = "customer-bridge",
};

DEFINE_PRIVATE_STRING_TABLE_LOOKUP_FROM_STRING(lldp_multicast_mode, sd_lldp_multicast_mode_t);

int config_parse_lldp_multicast_mode(
                const char *unit,
                const char *filename,
                unsigned line,
                const char *section,
                unsigned section_line,
                const char *lvalue,
                int ltype,
                const char *rvalue,
                void *data,
                void *userdata) {

        sd_lldp_multicast_mode_t m, *mode = ASSERT_PTR(data);
        int r;

        assert(filename);
        assert(section);
        assert(lvalue);
        assert(rvalue);

        if (isempty(rvalue)) {
                *mode = _SD_LLDP_MULTICAST_MODE_INVALID;
                return 0;
        }

        r = parse_boolean(rvalue);
        if (r >= 0) {
                *mode = r == 0 ? _SD_LLDP_MULTICAST_MODE_INVALID : SD_LLDP_MULTICAST_MODE_NEAREST_BRIDGE;
                return 0;
        }

        m = lldp_multicast_mode_from_string(rvalue);
        if (m < 0) {
                log_syntax(unit, LOG_WARNING, filename, line, m,
                           "Failed to parse %s=, ignoring assignment: %s", lvalue, rvalue);
                return 0;
        }

        *mode = m;
        return 0;
}
