/* SPDX-License-Identifier: GPL-2.0-or-later */
/*
 * Copyright (C) 2010 Red Hat, Inc.
 */

#include "src/core/nm-default-daemon.h"

#include "libnm-core-intern/nm-core-internal.h"

#include "settings/plugins/ifupdown/nms-ifupdown-interface-parser.h"
#include "settings/plugins/ifupdown/nms-ifupdown-parser.h"

#include "nm-test-utils-core.h"

#define TEST_DIR NM_BUILD_SRCDIR "/src/core/settings/plugins/ifupdown/tests"

/*****************************************************************************/

#define _connection_from_if_block(block)                                      \
    ({                                                                        \
        NMConnection *_con;                                                   \
        if_block     *_block = (block);                                       \
        GError       *_local = NULL;                                          \
                                                                              \
        g_assert(_block);                                                     \
        _con = ifupdown_new_connection_from_if_block(_block, FALSE, &_local); \
        nmtst_assert_success(NM_IS_CONNECTION(_con), _local);                 \
        nmtst_assert_connection_verifies_without_normalization(_con);         \
        _con;                                                                 \
    })

#define _connection_first_from_parser(parser)                  \
    ({                                                         \
        if_parser *_parser = (parser);                         \
                                                               \
        g_assert(_parser);                                     \
        _connection_from_if_block(ifparser_getfirst(_parser)); \
    })

/*****************************************************************************/

typedef struct {
    char *key;
    char *data;
} ExpectedKey;

typedef struct {
    char   *type;
    char   *name;
    GSList *keys;
} ExpectedBlock;

typedef struct {
    GSList *blocks;
} Expected;

static ExpectedKey *
expected_key_new(const char *key, const char *data)
{
    ExpectedKey *k;

    k       = g_malloc0(sizeof(ExpectedKey));
    k->key  = g_strdup(key);
    k->data = g_strdup(data);
    return k;
}

static void
expected_key_free(gpointer ptr)
{
    ExpectedKey *k = ptr;

    g_assert(k);
    g_free(k->key);
    g_free(k->data);
    memset(k, 0, sizeof(ExpectedKey));
    g_free(k);
}

static ExpectedBlock *
expected_block_new(const char *type, const char *name)
{
    ExpectedBlock *b;

    g_assert(type);
    g_assert(name);
    b = g_malloc0(sizeof(ExpectedBlock));
    g_assert(b);
    b->type = g_strdup(type);
    b->name = g_strdup(name);
    return b;
}

static void
expected_block_free(gpointer ptr)
{
    ExpectedBlock *b = ptr;

    g_assert(b);
    g_slist_free_full(b->keys, expected_key_free);
    g_free(b->type);
    g_free(b->name);
    memset(b, 0, sizeof(ExpectedBlock));
    g_free(b);
}

static void
expected_block_add_key(ExpectedBlock *b, ExpectedKey *k)
{
    g_assert(b);
    g_assert(k);
    b->keys = g_slist_append(b->keys, k);
}

static Expected *
expected_new(void)
{
    return g_malloc0(sizeof(Expected));
}

static void
expected_add_block(Expected *e, ExpectedBlock *b)
{
    g_assert(e);
    g_assert(b);
    e->blocks = g_slist_append(e->blocks, b);
}

static void
expected_free(Expected *e)
{
    g_assert(e);
    g_slist_free_full(e->blocks, expected_block_free);
    memset(e, 0, sizeof(Expected));
    g_free(e);
}

NM_AUTO_DEFINE_FCN_VOID0(Expected *, _nm_auto_free_expected, expected_free);
#define nm_auto_free_expected nm_auto(_nm_auto_free_expected)

static void
compare_expected_to_ifparser(if_parser *parser, Expected *e)
{
    if_block *n;
    GSList   *biter, *kiter;

    g_assert_cmpint(g_slist_length(e->blocks), ==, ifparser_get_num_blocks(parser));

    biter = e->blocks;
    c_list_for_each_entry (n, &parser->block_lst_head, block_lst) {
        if_data       *m;
        ExpectedBlock *b = biter->data;

        g_assert(b->type && n->type);
        g_assert_cmpstr(b->type, ==, n->type);
        g_assert(b->name);
        g_assert_cmpstr(b->name, ==, n->name);

        g_assert_cmpint(g_slist_length(b->keys), ==, ifparser_get_num_info(n));

        kiter = b->keys;
        c_list_for_each_entry (m, &n->data_lst_head, data_lst) {
            ExpectedKey *k = kiter->data;

            g_assert(k->key);
            g_assert_cmpstr(k->key, ==, m->key);
            g_assert(k->data && m->data);
            g_assert_cmpstr(k->data, ==, m->data);

            kiter = g_slist_next(kiter);
        }
        g_assert(!kiter);

        biter = g_slist_next(biter);
    }
    g_assert(!biter);
}

static void
dump_blocks(if_parser *parser)
{
    if_block *n;

    g_message("\n***************************************************");
    c_list_for_each_entry (n, &parser->block_lst_head, block_lst) {
        if_data *m;

        // each block start with its type & name
        // (single quotes used to show typ & name baoundaries)
        g_print("'%s' '%s'\n", n->type, n->name);

        // each key-value pair within a block is indented & separated by a tab
        // (single quotes used to show type & name boundaries)
        c_list_for_each_entry (m, &n->data_lst_head, data_lst)
            g_print("\t'%s'\t'%s'\n", m->key, m->data);

        // blocks are separated by an empty line
        g_print("\n");
    }
    g_message("##################################################\n");
}

static if_parser *
init_ifparser_with_file(const char *file)
{
    if_parser    *parser;
    gs_free char *tmp = NULL;

    tmp    = g_strdup_printf("%s/%s", TEST_DIR, file);
    parser = ifparser_parse(tmp, 1);
    g_assert(parser);
    return parser;
}

static void
test1_ignore_line_before_first_block(void)
{
    nm_auto_free_expected Expected *e = NULL;
    ExpectedBlock                  *b;
    nm_auto_ifparser if_parser     *parser = init_ifparser_with_file("test1");

    e = expected_new();
    b = expected_block_new("auto", "eth0");
    expected_add_block(e, b);
    b = expected_block_new("iface", "eth0");
    expected_add_block(e, b);
    expected_block_add_key(b, expected_key_new("inet", "dhcp"));

    compare_expected_to_ifparser(parser, e);
}

static void
test2_wrapped_line(void)
{
    nm_auto_free_expected Expected *e = NULL;
    ExpectedBlock                  *b;
    nm_auto_ifparser if_parser     *parser = init_ifparser_with_file("test2");

    e = expected_new();
    b = expected_block_new("auto", "lo");
    expected_add_block(e, b);

    compare_expected_to_ifparser(parser, e);
}

static void
test3_wrapped_multiline_multiarg(void)
{
    nm_auto_free_expected Expected *e = NULL;
    ExpectedBlock                  *b;
    nm_auto_ifparser if_parser     *parser = init_ifparser_with_file("test3");

    e = expected_new();
    b = expected_block_new("allow-hotplug", "eth0");
    expected_add_block(e, b);
    b = expected_block_new("allow-hotplug", "wlan0");
    expected_add_block(e, b);
    b = expected_block_new("allow-hotplug", "bnep0");
    expected_add_block(e, b);

    compare_expected_to_ifparser(parser, e);
}

static void
test4_allow_auto_is_auto(void)
{
    nm_auto_free_expected Expected *e = NULL;
    ExpectedBlock                  *b;
    nm_auto_ifparser if_parser     *parser = init_ifparser_with_file("test4");

    e = expected_new();
    b = expected_block_new("auto", "eth0");
    expected_add_block(e, b);

    compare_expected_to_ifparser(parser, e);
}

static void
test5_allow_auto_multiarg(void)
{
    nm_auto_free_expected Expected *e = NULL;
    ExpectedBlock                  *b;
    nm_auto_ifparser if_parser     *parser = init_ifparser_with_file("test5");

    e = expected_new();
    b = expected_block_new("allow-hotplug", "eth0");
    expected_add_block(e, b);
    b = expected_block_new("allow-hotplug", "wlan0");
    expected_add_block(e, b);

    compare_expected_to_ifparser(parser, e);
}

static void
test6_mixed_whitespace(void)
{
    nm_auto_free_expected Expected *e = NULL;
    ExpectedBlock                  *b;
    nm_auto_ifparser if_parser     *parser = init_ifparser_with_file("test6");

    e = expected_new();
    b = expected_block_new("iface", "lo");
    expected_block_add_key(b, expected_key_new("inet", "loopback"));
    expected_add_block(e, b);

    compare_expected_to_ifparser(parser, e);
}

static void
test7_long_line(void)
{
    nm_auto_ifparser if_parser *parser = init_ifparser_with_file("test7");

    g_assert_cmpint(ifparser_get_num_blocks(parser), ==, 0);
}

static void
test8_long_line_wrapped(void)
{
    nm_auto_ifparser if_parser *parser = init_ifparser_with_file("test8");

    g_assert_cmpint(ifparser_get_num_blocks(parser), ==, 0);
}

static void
test9_wrapped_lines_in_block(void)
{
    nm_auto_free_expected Expected *e = NULL;
    ExpectedBlock                  *b;
    nm_auto_ifparser if_parser     *parser = init_ifparser_with_file("test9");

    e = expected_new();
    b = expected_block_new("iface", "eth0");
    expected_add_block(e, b);
    expected_block_add_key(b, expected_key_new("inet", "static"));
    expected_block_add_key(b, expected_key_new("address", "10.250.2.3"));
    expected_block_add_key(b, expected_key_new("netmask", "255.255.255.192"));
    expected_block_add_key(b, expected_key_new("broadcast", "10.250.2.63"));
    expected_block_add_key(b, expected_key_new("gateway", "10.250.2.50"));

    compare_expected_to_ifparser(parser, e);
}

static void
test11_complex_wrap(void)
{
    nm_auto_free_expected Expected *e = NULL;
    ExpectedBlock                  *b;
    nm_auto_ifparser if_parser     *parser = init_ifparser_with_file("test11");

    e = expected_new();
    b = expected_block_new("iface", "pppoe");
    expected_add_block(e, b);
    expected_block_add_key(b, expected_key_new("inet", "manual"));
    expected_block_add_key(b, expected_key_new("pre-up", "/sbin/ifconfig eth0 up"));

    compare_expected_to_ifparser(parser, e);
}

static void
test12_complex_wrap_split_word(void)
{
    nm_auto_free_expected Expected *e = NULL;
    ExpectedBlock                  *b;
    nm_auto_ifparser if_parser     *parser = init_ifparser_with_file("test12");

    e = expected_new();
    b = expected_block_new("iface", "pppoe");
    expected_add_block(e, b);
    expected_block_add_key(b, expected_key_new("inet", "manual"));
    expected_block_add_key(b, expected_key_new("up", "ifup ppp0=dsl"));

    compare_expected_to_ifparser(parser, e);
}

static void
test13_more_mixed_whitespace(void)
{
    nm_auto_free_expected Expected *e = NULL;
    ExpectedBlock                  *b;
    nm_auto_ifparser if_parser     *parser = init_ifparser_with_file("test13");

    e = expected_new();
    b = expected_block_new("iface", "dsl");
    expected_block_add_key(b, expected_key_new("inet", "ppp"));
    expected_add_block(e, b);

    compare_expected_to_ifparser(parser, e);
}

static void
test14_mixed_whitespace_block_start(void)
{
    nm_auto_free_expected Expected *e = NULL;
    ExpectedBlock                  *b;
    nm_auto_ifparser if_parser     *parser = init_ifparser_with_file("test14");

    e = expected_new();
    b = expected_block_new("iface", "wlan0");
    expected_block_add_key(b, expected_key_new("inet", "manual"));
    expected_add_block(e, b);
    b = expected_block_new("iface", "wlan-adpm");
    expected_block_add_key(b, expected_key_new("inet", "dhcp"));
    expected_add_block(e, b);
    b = expected_block_new("iface", "wlan-default");
    expected_block_add_key(b, expected_key_new("inet", "dhcp"));
    expected_add_block(e, b);

    compare_expected_to_ifparser(parser, e);
}

static void
test15_trailing_space(void)
{
    nm_auto_free_expected Expected *e = NULL;
    ExpectedBlock                  *b;
    nm_auto_ifparser if_parser     *parser = init_ifparser_with_file("test15");

    e = expected_new();
    b = expected_block_new("iface", "bnep0");
    expected_block_add_key(b, expected_key_new("inet", "static"));
    expected_add_block(e, b);

    compare_expected_to_ifparser(parser, e);
}

static void
test16_missing_newline(void)
{
    nm_auto_free_expected Expected *e      = NULL;
    nm_auto_ifparser if_parser     *parser = init_ifparser_with_file("test16");

    e = expected_new();
    expected_add_block(e, expected_block_new("mapping", "eth0"));

    compare_expected_to_ifparser(parser, e);
}

static void
test17_read_static_ipv4(void)
{
    gs_unref_object NMConnection *connection = NULL;
    NMSettingConnection          *s_con;
    NMSettingIPConfig            *s_ip4;
    NMSettingWired               *s_wired;
    NMIPAddress                  *ip4_addr;
    nm_auto_ifparser if_parser *parser = init_ifparser_with_file("test17-wired-static-verify-ip4");

    connection = _connection_first_from_parser(parser);

    /* ===== CONNECTION SETTING ===== */
    s_con = nm_connection_get_setting_connection(connection);
    g_assert(s_con);
    g_assert_cmpstr(nm_setting_connection_get_id(s_con), ==, "Ifupdown (eth0)");

    /* ===== WIRED SETTING ===== */
    s_wired = nm_connection_get_setting_wired(connection);
    g_assert(s_wired);

    /* ===== IPv4 SETTING ===== */
    s_ip4 = nm_connection_get_setting_ip4_config(connection);
    g_assert(s_ip4);
    g_assert_cmpstr(nm_setting_ip_config_get_method(s_ip4),
                    ==,
                    NM_SETTING_IP4_CONFIG_METHOD_MANUAL);

    g_assert_cmpint(nm_setting_ip_config_get_num_addresses(s_ip4), ==, 1);
    ip4_addr = nm_setting_ip_config_get_address(s_ip4, 0);
    g_assert(ip4_addr != NULL);
    g_assert_cmpstr(nm_ip_address_get_address(ip4_addr), ==, "10.0.0.3");
    g_assert_cmpint(nm_ip_address_get_prefix(ip4_addr), ==, 8);

    g_assert_cmpint(nm_setting_ip_config_get_num_dns(s_ip4), ==, 2);
    g_assert_cmpstr(nm_setting_ip_config_get_dns(s_ip4, 0), ==, "10.0.0.1");
    g_assert_cmpstr(nm_setting_ip_config_get_dns(s_ip4, 1), ==, "10.0.0.2");

    g_assert_cmpint(nm_setting_ip_config_get_num_dns_searches(s_ip4), ==, 2);
    g_assert_cmpstr(nm_setting_ip_config_get_dns_search(s_ip4, 0), ==, "example.com");
    g_assert_cmpstr(nm_setting_ip_config_get_dns_search(s_ip4, 1), ==, "foo.example.com");
}

static void
test18_read_static_ipv6(void)
{
    gs_unref_object NMConnection *connection = NULL;
    NMSettingConnection          *s_con;
    NMSettingIPConfig            *s_ip6;
    NMSettingWired               *s_wired;
    NMIPAddress                  *ip6_addr;
    nm_auto_ifparser if_parser *parser = init_ifparser_with_file("test18-wired-static-verify-ip6");

    connection = _connection_first_from_parser(parser);

    /* ===== CONNECTION SETTING ===== */
    s_con = nm_connection_get_setting_connection(connection);
    g_assert(s_con);
    g_assert_cmpstr(nm_setting_connection_get_id(s_con), ==, "Ifupdown (myip6tunnel)");

    /* ===== WIRED SETTING ===== */
    s_wired = nm_connection_get_setting_wired(connection);
    g_assert(s_wired);

    /* ===== IPv6 SETTING ===== */
    s_ip6 = nm_connection_get_setting_ip6_config(connection);
    g_assert(s_ip6);
    g_assert_cmpstr(nm_setting_ip_config_get_method(s_ip6),
                    ==,
                    NM_SETTING_IP6_CONFIG_METHOD_MANUAL);

    g_assert_cmpint(nm_setting_ip_config_get_num_addresses(s_ip6), ==, 1);
    ip6_addr = nm_setting_ip_config_get_address(s_ip6, 0);
    g_assert(ip6_addr != NULL);
    g_assert_cmpstr(nm_ip_address_get_address(ip6_addr), ==, "fc00::1");
    g_assert_cmpint(nm_ip_address_get_prefix(ip6_addr), ==, 64);

    g_assert_cmpint(nm_setting_ip_config_get_num_dns(s_ip6), ==, 2);
    g_assert_cmpstr(nm_setting_ip_config_get_dns(s_ip6, 0), ==, "fc00::2");
    g_assert_cmpstr(nm_setting_ip_config_get_dns(s_ip6, 1), ==, "fc00::3");

    g_assert_cmpint(nm_setting_ip_config_get_num_dns_searches(s_ip6), ==, 2);
    g_assert_cmpstr(nm_setting_ip_config_get_dns_search(s_ip6, 0), ==, "example.com");
    g_assert_cmpstr(nm_setting_ip_config_get_dns_search(s_ip6, 1), ==, "foo.example.com");
}

static void
test19_read_static_ipv4_plen(void)
{
    gs_unref_object NMConnection *connection = NULL;
    NMSettingIPConfig            *s_ip4;
    NMIPAddress                  *ip4_addr;
    nm_auto_ifparser if_parser   *parser =
        init_ifparser_with_file("test19-wired-static-verify-ip4-plen");

    connection = _connection_first_from_parser(parser);

    /* ===== IPv4 SETTING ===== */
    s_ip4 = nm_connection_get_setting_ip4_config(connection);
    g_assert(s_ip4);

    g_assert_cmpint(nm_setting_ip_config_get_num_addresses(s_ip4), ==, 1);
    ip4_addr = nm_setting_ip_config_get_address(s_ip4, 0);
    g_assert(ip4_addr != NULL);
    g_assert_cmpstr(nm_ip_address_get_address(ip4_addr), ==, "10.0.0.3");
    g_assert_cmpint(nm_ip_address_get_prefix(ip4_addr), ==, 8);
}

static void
test20_source_stanza(void)
{
    nm_auto_free_expected Expected *e = NULL;
    ExpectedBlock                  *b;
    nm_auto_ifparser if_parser     *parser = init_ifparser_with_file("test20-source-stanza");

    e = expected_new();

    b = expected_block_new("auto", "eth0");
    expected_add_block(e, b);
    b = expected_block_new("iface", "eth0");
    expected_add_block(e, b);
    expected_block_add_key(b, expected_key_new("inet", "dhcp"));

    b = expected_block_new("auto", "eth1");
    expected_add_block(e, b);
    b = expected_block_new("iface", "eth1");
    expected_add_block(e, b);
    expected_block_add_key(b, expected_key_new("inet", "dhcp"));

    compare_expected_to_ifparser(parser, e);
}

static void
test21_source_dir_stanza(void)
{
    nm_auto_free_expected Expected *e = NULL;
    ExpectedBlock                  *b;
    nm_auto_ifparser if_parser     *parser = init_ifparser_with_file("test21-source-dir-stanza");

    e = expected_new();

    b = expected_block_new("auto", "eth0");
    expected_add_block(e, b);
    b = expected_block_new("iface", "eth0");
    expected_add_block(e, b);
    expected_block_add_key(b, expected_key_new("inet", "dhcp"));

    compare_expected_to_ifparser(parser, e);
}

static void
test22_duplicate_stanzas(void)
{
    nm_auto_free_expected Expected *e = NULL;
    ExpectedBlock                  *b;
    nm_auto_ifparser if_parser     *parser = init_ifparser_with_file("test22-duplicate-stanzas");

    e = expected_new();

    b = expected_block_new("iface", "br10");
    expected_add_block(e, b);
    expected_block_add_key(b, expected_key_new("inet", "manual"));
    expected_block_add_key(b, expected_key_new("bridge-ports", "enp6s0.15"));
    expected_block_add_key(b, expected_key_new("bridge-stp", "off"));
    expected_block_add_key(b, expected_key_new("bridge-maxwait", "0"));
    expected_block_add_key(b, expected_key_new("bridge-fd", "0"));
    b = expected_block_new("iface", "br10");
    expected_add_block(e, b);
    expected_block_add_key(b, expected_key_new("inet", "auto"));
    expected_block_add_key(b, expected_key_new("bridge-ports", "enp6s0.15"));

    compare_expected_to_ifparser(parser, e);
}

/*****************************************************************************/

NMTST_DEFINE();

int
main(int argc, char **argv)
{
    nmtst_init_assert_logging(&argc, &argv, "WARN", "DEFAULT");

    (void) dump_blocks;

    g_test_add_func("/ifupdate/ignore_line_before_first_block",
                    test1_ignore_line_before_first_block);
    g_test_add_func("/ifupdate/wrapped_line", test2_wrapped_line);
    g_test_add_func("/ifupdate/wrapped_multiline_multiarg", test3_wrapped_multiline_multiarg);
    g_test_add_func("/ifupdate/allow_auto_is_auto", test4_allow_auto_is_auto);
    g_test_add_func("/ifupdate/allow_auto_multiarg", test5_allow_auto_multiarg);
    g_test_add_func("/ifupdate/mixed_whitespace", test6_mixed_whitespace);
    g_test_add_func("/ifupdate/long_line", test7_long_line);
    g_test_add_func("/ifupdate/long_line_wrapped", test8_long_line_wrapped);
    g_test_add_func("/ifupdate/wrapped_lines_in_block", test9_wrapped_lines_in_block);
    g_test_add_func("/ifupdate/complex_wrap", test11_complex_wrap);
    g_test_add_func("/ifupdate/complex_wrap_split_word", test12_complex_wrap_split_word);
    g_test_add_func("/ifupdate/more_mixed_whitespace", test13_more_mixed_whitespace);
    g_test_add_func("/ifupdate/mixed_whitespace_block_start", test14_mixed_whitespace_block_start);
    g_test_add_func("/ifupdate/trailing_space", test15_trailing_space);
    g_test_add_func("/ifupdate/missing_newline", test16_missing_newline);
    g_test_add_func("/ifupdate/read_static_ipv4", test17_read_static_ipv4);
    g_test_add_func("/ifupdate/read_static_ipv6", test18_read_static_ipv6);
    g_test_add_func("/ifupdate/read_static_ipv4_plen", test19_read_static_ipv4_plen);
    g_test_add_func("/ifupdate/source_stanza", test20_source_stanza);
    g_test_add_func("/ifupdate/source_dir_stanza", test21_source_dir_stanza);
    g_test_add_func("/ifupdate/test22-duplicate-stanzas", test22_duplicate_stanzas);

    return g_test_run();
}
