#include <linux/genetlink.h>

#include "sd-netlink.h"
#include "netlink-internal.h"
#include "alloc-util.h"

typedef struct {
        const char* name;
        uint8_t version;
} genl_family;

static const genl_family genl_families[] = {
        [SD_GENL_ID_CTRL] = { .name = "", .version = 1 },
        [SD_GENL_WIREGUARD] = { .name = "wireguard", .version = 1 },
};

int sd_genl_socket_open(sd_netlink **ret) {
        return netlink_open_family(ret, NETLINK_GENERIC);
}
static int lookup_id(sd_netlink *nl, sd_genl_family family, uint16_t *id);

static int genl_message_new(sd_netlink *nl, sd_genl_family family, uint16_t nlmsg_type, uint8_t cmd, sd_netlink_message **ret) {
        int r;
        struct genlmsghdr *genl;
        const NLType *genl_cmd_type, *nl_type;
        const NLTypeSystem *type_system;
        size_t size;
        _cleanup_(sd_netlink_message_unrefp) sd_netlink_message *m = NULL;

        assert_return(nl->protocol == NETLINK_GENERIC, -EINVAL);

        r = type_system_get_type(&genl_family_type_system_root, &genl_cmd_type, family);
        if (r < 0)
                return r;

        r = message_new_empty(nl, &m);
        if (r < 0)
                return r;

        size = NLMSG_SPACE(sizeof(struct genlmsghdr));
        m->hdr = malloc0(size);
        if (!m->hdr)
                return -ENOMEM;

        m->hdr->nlmsg_flags = NLM_F_REQUEST | NLM_F_ACK;

        type_get_type_system(genl_cmd_type, &type_system);

        r = type_system_get_type(type_system, &nl_type, cmd);
        if (r < 0)
                return r;

        m->hdr->nlmsg_len = size;
        m->hdr->nlmsg_type = nlmsg_type;

        type_get_type_system(nl_type, &m->containers[0].type_system);
        genl = NLMSG_DATA(m->hdr);
        genl->cmd = cmd;
        genl->version = genl_families[family].version;

        *ret = m;
        m = NULL;

        return 0;
}

int sd_genl_message_new(sd_netlink *nl, sd_genl_family family, uint8_t cmd, sd_netlink_message **ret) {
        int r;
        uint16_t id = GENL_ID_CTRL;

        if (family != SD_GENL_ID_CTRL) {
                r = lookup_id(nl, family, &id);
                if (r < 0)
                        return r;
        }

        return genl_message_new(nl, family, id, cmd, ret);
}

static int lookup_id(sd_netlink *nl, sd_genl_family family, uint16_t *id) {
        int r;
        _cleanup_(sd_netlink_message_unrefp) sd_netlink_message *req = NULL, *reply = NULL;

        r = sd_genl_message_new(nl, SD_GENL_ID_CTRL, CTRL_CMD_GETFAMILY, &req);
        if (r < 0)
                return r;

        r = sd_netlink_message_append_string(req, CTRL_ATTR_FAMILY_NAME, genl_families[family].name);
        if (r < 0)
                return r;

        r = sd_netlink_call(nl, req, 0, &reply);
        if (r < 0)
                return r;

        return sd_netlink_message_read_u16(reply, CTRL_ATTR_FAMILY_ID, id);
}
