/* SPDX-License-Identifier: LGPL-2.1-or-later */
/*
 * Copyright (C) 2007 - 2008 Novell, Inc.
 * Copyright (C) 2007 - 2018 Red Hat, Inc.
 */

#include "libnm-client-impl/nm-default-libnm.h"

#include "nm-libnm-utils.h"

#include "libnm-glib-aux/nm-time-utils.h"
#include "libnm-core-aux-intern/nm-common-macros.h"
#include "libnm-crypto/nm-crypto.h"
#include "nm-object.h"
#include "nm-utils.h"

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

#define LOG_FILE_FD_UNSET   -3
#define LOG_FILE_FD_NONE    -2
#define LOG_FILE_FD_DEFUNCT -1

volatile int _nml_dbus_log_level   = 0;
const char  *_nml_dbus_log_file    = NULL;
int          _nml_dbus_log_file_fd = LOG_FILE_FD_UNSET;

int
_nml_dbus_log_level_init(void)
{
    const GDebugKey keys[] = {
        {"trace", _NML_DBUS_LOG_LEVEL_TRACE},
        {"debug", _NML_DBUS_LOG_LEVEL_DEBUG},
        {"warn", _NML_DBUS_LOG_LEVEL_WARN},
        {"warning", _NML_DBUS_LOG_LEVEL_WARN},
        {"error", _NML_DBUS_LOG_LEVEL_ERROR},
        {"WARN", _NML_DBUS_LOG_LEVEL_WARN | NML_DBUS_LOG_ASSERT},
        {"WARNING", _NML_DBUS_LOG_LEVEL_WARN | NML_DBUS_LOG_ASSERT},
        {"ERROR", _NML_DBUS_LOG_LEVEL_ERROR | NML_DBUS_LOG_ASSERT},
        {"stdout", NML_DBUS_LOG_STDOUT},
    };
    int l;

    l = _NML_DBUS_LOG_LEVEL_INITIALIZED
        | nm_utils_parse_debug_string(g_getenv("LIBNM_CLIENT_DEBUG"), keys, G_N_ELEMENTS(keys));

    if (!g_atomic_int_compare_and_exchange(&_nml_dbus_log_level, 0, l))
        l = g_atomic_int_get(&_nml_dbus_log_level);

    nm_assert(l & _NML_DBUS_LOG_LEVEL_INITIALIZED);
    return l;
}

static const char *
_nml_dbus_log_file_init(void)
{
    const char *s;

    s = g_getenv("LIBNM_CLIENT_DEBUG_FILE");
    if (nm_str_not_empty(s)) {
        if (strstr(s, "%p")) {
            gs_strfreev char **tokens = NULL;
            char               pid_str[100];

            tokens = g_strsplit(s, "%p", -1);
            nm_sprintf_buf(pid_str, "%lld", (long long) getpid());
            s = nm_str_realloc(g_strjoinv(pid_str, tokens));
        } else
            s = g_strdup(s);
    } else
        s = "";

    if (!g_atomic_pointer_compare_and_exchange(&_nml_dbus_log_file, NULL, s)) {
        if (s[0] != '\0')
            g_free((gpointer) s);
        s = g_atomic_pointer_get(&_nml_dbus_log_file);
    }

    return s;
}

#define nml_dbus_log_file()                             \
    ({                                                  \
        const char *_l;                                 \
                                                        \
        _l = g_atomic_pointer_get(&_nml_dbus_log_file); \
        if (G_UNLIKELY(!_l))                            \
            _l = _nml_dbus_log_file_init();             \
                                                        \
        _l[0] ? _l : NULL;                              \
    })

static int
_nml_dbus_log_file_fd_init(void)
{
    static GMutex mutex;
    NM_G_MUTEX_LOCKED(&mutex);
    int fd;

    fd = g_atomic_int_get(&_nml_dbus_log_file_fd);
    if (fd == LOG_FILE_FD_UNSET) {
        const char *name;

        name = nml_dbus_log_file();
        if (!name)
            fd = LOG_FILE_FD_NONE;
        else {
            fd = open(name, O_WRONLY | O_TRUNC | O_CREAT | O_CLOEXEC, 0600);
            if (fd < 0)
                fd = LOG_FILE_FD_DEFUNCT;
        }
        g_atomic_int_set(&_nml_dbus_log_file_fd, fd);
    }

    return fd;
}

#define nml_dbus_log_file_fd()                                                          \
    ({                                                                                  \
        int _fd2;                                                                       \
                                                                                        \
        _fd2 = g_atomic_int_get(&_nml_dbus_log_file_fd);                                \
        if (G_UNLIKELY(_fd2 == LOG_FILE_FD_UNSET))                                      \
            _fd2 = _nml_dbus_log_file_fd_init();                                        \
                                                                                        \
        nm_assert(NM_IN_SET(_fd2, LOG_FILE_FD_NONE, LOG_FILE_FD_DEFUNCT) || _fd2 >= 0); \
        _fd2;                                                                           \
    })

#define _log_printf(use_stdout, ...)            \
    G_STMT_START                                \
    {                                           \
        const int _fd = nml_dbus_log_file_fd(); \
                                                \
        if (_fd != LOG_FILE_FD_NONE) {          \
            if (_fd >= 0)                       \
                dprintf(_fd, __VA_ARGS__);      \
        } else if (use_stdout)                  \
            g_print(__VA_ARGS__);               \
        else                                    \
            g_printerr(__VA_ARGS__);            \
    }                                           \
    G_STMT_END

void
_nml_dbus_log(NMLDBusLogLevel level, gboolean use_stdout, const char *fmt, ...)
{
    NMLDBusLogLevel configured_log_level;
    gs_free char   *msg = NULL;
    va_list         args;
    const char     *prefix = "";
    gint64          ts;
    pid_t           pid;

    nm_assert(NM_IN_SET(level,
                        NML_DBUS_LOG_LEVEL_TRACE,
                        NML_DBUS_LOG_LEVEL_DEBUG,
                        NML_DBUS_LOG_LEVEL_WARN,
                        NML_DBUS_LOG_LEVEL_ERROR));

    /* we only call _nml_dbus_log() after nml_dbus_log_enabled(), which already does
     * an atomic access to the variable. Since the value is only initialized once and
     * never changes, we can just access it without additional locking. */
    configured_log_level = _nml_dbus_log_level;

    nm_assert(level & configured_log_level);

    va_start(args, fmt);
    msg = g_strdup_vprintf(fmt, args);
    va_end(args);

    switch (level) {
    case NML_DBUS_LOG_LEVEL_TRACE:
        prefix = "<trace> ";
        break;
    case NML_DBUS_LOG_LEVEL_DEBUG:
        prefix = "<debug> ";
        break;
    case NML_DBUS_LOG_LEVEL_WARN:
        prefix = "<warn > ";
        if (NM_FLAGS_HAS(configured_log_level, NML_DBUS_LOG_ASSERT))
            g_warning("libnm-dbus: %s%s", prefix, msg);
        break;
    case NML_DBUS_LOG_LEVEL_ERROR:
        prefix = "<error> ";
        if (NM_FLAGS_HAS(configured_log_level, NML_DBUS_LOG_ASSERT))
            g_critical("libnm-dbus: %s%s", prefix, msg);
        break;
    default:
        break;
    }

    ts = nm_utils_clock_gettime_nsec(CLOCK_BOOTTIME);

    pid = getpid();

    _log_printf(use_stdout,
                "libnm-dbus[%lld]: %s[%" G_GINT64_FORMAT ".%05" G_GINT64_FORMAT "] %s\n",
                (long long) pid,
                prefix,
                ts / NM_UTILS_NSEC_PER_SEC,
                (ts / (NM_UTILS_NSEC_PER_SEC / 100000)) % 100000,
                msg);
}

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

/* Stolen from dbus-glib */
char *
nm_utils_wincaps_to_dash(const char *caps)
{
    const char *p;
    GString    *str;

    str = g_string_new(NULL);
    p   = caps;
    while (*p) {
        if (g_ascii_isupper(*p)) {
            if (str->len > 0 && (str->len < 2 || str->str[str->len - 2] != '-'))
                g_string_append_c(str, '-');
            g_string_append_c(str, g_ascii_tolower(*p));
        } else
            g_string_append_c(str, *p);
        ++p;
    }

    return g_string_free(str, FALSE);
}

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

static char *
_fixup_string(const char        *desc,
              const char *const *ignored_phrases,
              const char *const *ignored_words,
              gboolean           square_brackets_sensible)
{
    char    *desc_full;
    gboolean in_paren = FALSE;
    char    *p, *q;
    int      i;

    if (!desc || !desc[0])
        return NULL;

    /* restore original non-UTF-8-safe text. */
    desc_full = nm_utils_str_utf8safe_unescape_cp(desc, NM_UTILS_STR_UTF8_SAFE_FLAG_NONE);

    /* replace all invalid UTF-8 bytes with space. */
    p = desc_full;
    while (!g_utf8_validate(p, -1, (const char **) &q)) {
        /* the byte is invalid UTF-8. Replace it with space and proceed. */
        *q = ' ';
        p  = q + 1;
    }

    /* replace '_', ',', ASCII control characters and everything inside parentheses, with space. */
    for (p = desc_full; p[0]; p++) {
        if (*p == '(')
            in_paren = TRUE;
        else if (*p == ')')
            in_paren = FALSE;
        else if (NM_IN_SET(*p, '_', ',') || nm_ascii_is_ctrl_or_del(*p) || in_paren) {
            /* pass */
        } else
            continue;
        *p = ' ';
    }

    /* Attempt to shorten ID by ignoring certain phrases */
    for (i = 0; ignored_phrases[i]; i++) {
        p = strstr(desc_full, ignored_phrases[i]);
        if (p) {
            const char *eow = &p[strlen(ignored_phrases[i])];

            /* require that the phrase is delimited by space, or
             * at the beginning or end of the description. */
            if ((p == desc_full || p[-1] == ' ') && NM_IN_SET(eow[0], '\0', ' '))
                memmove(p, eow, strlen(eow) + 1); /* +1 for the \0 */
        }
    }

    /* Attempt to shorten ID by ignoring certain individual words.
     * - word-split the description at spaces
     * - coalesce multiple spaces
     * - skip over ignored_words */
    p = desc_full;
    q = desc_full;
    for (;;) {
        char *eow;
        gsize l;

        /* skip leading spaces. */
        while (p[0] == ' ')
            p++;

        if (!p[0])
            break;

        /* split leading word on first space */
        eow = strchr(p, ' ');
        if (eow)
            *eow = '\0';

        if (nm_strv_contains(ignored_words, -1, p))
            goto next;

        l = strlen(p);
        if (q != p) {
            if (q != desc_full)
                *q++ = ' ';
            memmove(q, p, l);
        }
        q += l;

next:
        if (!eow)
            break;
        p = eow + 1;
    }

    *q++ = '\0';

    p = strchr(desc_full, '[');
    if (p == desc_full) {
        /* All we're left with is in square brackets.
         * Always prefer that to a blank string.*/
        square_brackets_sensible = TRUE;
    }
    if (square_brackets_sensible) {
        /* If there's a [<string>] that survived the substitution, then the string
         * is a short form that is generally preferable. */
        q = strchr(desc_full, ']');
        if (p && q > p) {
            p++;
            memmove(desc_full, p, q - p);
            desc_full[q - p] = '\0';
        }
    } else {
        /* [<string>] sometimes contains the preferred human-readable name, but
         * mostly it's utterly useless. Sigh. Drop it. */
        if (p) {
            if (p > desc_full && p[-1] == ' ')
                p--;
            *p = '\0';
        }
    }

    if (!desc_full[0]) {
        g_free(desc_full);
        return NULL;
    }

    return desc_full;
}

char *
nm_utils_fixup_vendor_string(const char *desc)
{
    static const char *const IGNORED_PHRASES[] = {
        "Access Systems",
        "Business Mobile Networks BV",
        "Communications & Multimedia",
        "Company of Japan",
        "Computer Co.",
        "Computer Corp.",
        "Computer Corporation",
        "Computer Inc.",
        "Computer, Inc.",
        "Information and Communication Products",
        "Macao Commercial Offshore",
        "Mobile Phones",
        "(M) Son",
        "Multimedia Internet Technology",
        "Technology Group Ltd.",
        "Wireless Networks",
        "Wireless Solutions",
        NULL,
    };
    static const char *const IGNORED_WORDS[] = {
        "AB",
        "AG",
        "A/S",
        "ASA",
        "B.V.",
        "Chips",
        "Co.",
        "Co",
        "Communications",
        "Components",
        "Computers",
        "Computertechnik",
        "corp.",
        "Corp.",
        "Corp",
        "Corporation",
        "Design",
        "Electronics",
        "Enterprise",
        "Enterprises",
        "Europe",
        "GmbH",
        "Hardware",
        "[hex]",
        "Holdings",
        "Inc.",
        "Inc",
        "INC.",
        "Incorporated",
        "Instruments",
        "International",
        "Intl.",
        "Labs",
        "Limited.",
        "Limited",
        "Ltd.",
        "Ltd",
        "Microelectronics",
        "Microsystems",
        "MSM",
        "Multimedia",
        "Networks",
        "Norway",
        "Optical",
        "PCS",
        "Semiconductor",
        "Systems",
        "Systemtechnik",
        "Techcenter",
        "Technik",
        "Technologies",
        "Technology",
        "TECHNOLOGY",
        "Telephonics",
        "USA",
        "WCDMA",
        NULL,
    };
    char *desc_full;
    char *p;

    desc_full = _fixup_string(desc, IGNORED_PHRASES, IGNORED_WORDS, TRUE);
    if (!desc_full)
        return NULL;

    /* Chop off everything after a slash. */
    for (p = desc_full; *p; p++) {
        if ((p[0] == ' ' && p[1] == '/') || p[0] == '/') {
            p[0] = '\0';
            break;
        }
    }

    nm_assert(g_utf8_validate(desc_full, -1, NULL));

    return desc_full;
}

char *
nm_utils_fixup_product_string(const char *desc)
{
    static const char *const IGNORED_PHRASES[] = {
        "100/10 MBit",
        "10/100 Mbps",
        "1.0 GbE",
        "10 GbE",
        "10 Gigabit",
        "10 Mbps",
        "1/10 Gigabit",
        "150 Mbps",
        "2.5 GbE",
        "54 Mbps",
        "Attached Port",
        "+ BT",
        "\"CDC Subset\"",
        "CE Media Processor",
        "Controller Area Network",
        "Converged Network",
        "DEC-Tulip compatible",
        "Dish Adapter",
        "Double 108 Mbps",
        "Dual Band",
        "Dual Port",
        "Embedded UTP",
        "Ethernet Connection",
        "Ethernet Pro 100",
        "Express Module",
        "Fabric Adapter",
        "Fast Ethernet",
        "for 10GBASE-T",
        "for 10GbE backplane",
        "for 10GbE QSFP+",
        "for 10GbE SFP+",
        "for 1GbE",
        "for 20GbE backplane",
        "for 25GbE backplane",
        "for 25GbE SFP28",
        "for 40GbE backplane",
        "for 40GbE QSFP+",
        "G Adapter",
        "Gigabit Desktop Network",
        "Gigabit Ethernet",
        "Gigabit or",
        "Host Interface",
        "Host Virtual Interface",
        "IEEE 802.11a/b/g",
        "IEEE 802.11g",
        "IEEE 802.11G",
        "IEEE 802.11n",
        "MAC + PHY",
        "Mini Card",
        "Mini Wireless",
        "multicore SoC",
        "Multi Function",
        "N Draft 11n Wireless",
        "Network Connection",
        "Network Everywhere",
        "N Wireless",
        "N+ Wireless",
        "OCT To Fast Ethernet Converter",
        "PC Card",
        "PCI Express",
        "Platform Controller Hub",
        "Plus Bluetooth",
        "Quad Gigabit",
        "rev 1",
        "rev 17",
        "rev 2",
        "rev A",
        "rev B",
        "rev F",
        "TO Ethernet",
        "Turbo Wireless Adapter",
        "Unified Wire",
        "USB 1.1",
        "USB 2.0",
        "Virtual media for",
        "WiFi Link",
        "+ WiMAX",
        "WiMAX/WiFi Link",
        "Wireless G",
        "Wireless G+",
        "Wireless Lan",
        "Wireless Mini adapter",
        "Wireless Mini Adapter",
        "Wireless N",
        "with 1000-BASE-T interface",
        "with CX4 copper interface",
        "with Range Amplifier",
        "with SR-XFP optical interface",
        "w/ Upgradable Antenna",
        NULL,
    };
    static const char *const IGNORED_WORDS[] = {
        "1000BaseSX",
        "1000BASE-T",
        "1000Base-ZX",
        "100/10M",
        "100baseFx",
        "100Base-MII",
        "100Base-T",
        "100BaseT4",
        "100Base-TX",
        "100BaseTX",
        "100GbE",
        "100Mbps",
        "100MBps",
        "10/100",
        "10/100/1000",
        "10/100/1000Base-T",
        "10/100/1000BASE-T",
        "10/100BaseT",
        "10/100baseTX",
        "10/100BaseTX",
        "10/100/BNC",
        "10/100M",
        "10/20-Gigabit",
        "10/25/40/50GbE",
        "10/40G",
        "10base-FL",
        "10BaseT",
        "10BASE-T",
        "10G",
        "10Gb",
        "10Gb/25Gb",
        "10Gb/25Gb/40Gb/50Gb",
        "10Gbase-T",
        "10GBase-T",
        "10GBASE-T",
        "10GbE",
        "10Gbps",
        "10-Giga",
        "10-Gigabit",
        "10mbps",
        "10Mbps",
        "1/10GbE",
        "1/10-Gigabit",
        "11b/g/n",
        "11g",
        "150Mbps",
        "16Gbps/10Gbps",
        "1GbE",
        "1x2:2",
        "20GbE",
        "25Gb",
        "25GbE",
        "2-Port",
        "2x3:3",
        "3G",
        "3G/4G",
        "3x3:3",
        "40GbE",
        "4G",
        "54g",
        "54M",
        "54Mbps",
        "56k",
        "5G",
        "802.11",
        "802.11a/b/g",
        "802.11abg",
        "802.11a/b/g/n",
        "802.11abgn",
        "802.11ac",
        "802.11ad",
        "802.11a/g",
        "802.11b",
        "802.11b/g",
        "802.11bg",
        "802.11b/g/n",
        "802.11bgn",
        "802.11b/g/n-draft",
        "802.11g",
        "802.11n",
        "802.11N",
        "802.11n/b/g",
        "802.11ng",
        "802AIN",
        "802UIG-1",
        "adapter",
        "Adapter",
        "adaptor",
        "ADSL",
        "Basic",
        "CAN-Bus",
        "card",
        "Card",
        "Cardbus",
        "CardBus",
        "CDMA",
        "CNA",
        "Composite",
        "controller",
        "Controller",
        "Copper",
        "DB",
        "Desktop",
        "device",
        "Device",
        "dongle",
        "driver",
        "Dual-band",
        "Dual-Protocol",
        "EISA",
        "Enhanced",
        "ethernet.",
        "ethernet",
        "Ethernet",
        "Ethernet/RNDIS",
        "ExpressModule",
        "family",
        "Family",
        "Fast/Gigabit",
        "Fiber",
        "gigabit",
        "Gigabit",
        "G-NIC",
        "Hi-Gain",
        "Hi-Speed",
        "HSDPA",
        "HSUPA",
        "integrated",
        "Integrated",
        "interface",
        "LAN",
        "LAN+Winmodem",
        "Laptop",
        "LTE",
        "LTE/UMTS/GSM",
        "MAC",
        "Micro",
        "Mini-Card",
        "Mini-USB",
        "misprogrammed",
        "modem",
        "Modem",
        "Modem/Networkcard",
        "Module",
        "Multimode",
        "Multithreaded",
        "Name:",
        "net",
        "network",
        "Network",
        "n/g/b",
        "NIC",
        "Notebook",
        "OEM",
        "PCI",
        "PCI64",
        "PCIe",
        "PCI-E",
        "PCI-Express",
        "PCI-X",
        "PCMCIA",
        "PDA",
        "PnP",
        "RDMA",
        "RJ-45",
        "Series",
        "Server",
        "SoC",
        "Switch",
        "Technologies",
        "TOE",
        "USB",
        "USB2.0",
        "USB/Ethernet",
        "UTP",
        "UTP/Coax",
        "v1",
        "v1.1",
        "v2",
        "V2.0",
        "v3",
        "v4",
        "wifi",
        "Wi-Fi",
        "WiFi",
        "wireless",
        "Wireless",
        "Wireless-150N",
        "Wireless-300N",
        "Wireless-G",
        "Wireless-N",
        "WLAN",
        NULL,
    };
    char *desc_full;
    char *p;

    desc_full = _fixup_string(desc, IGNORED_PHRASES, IGNORED_WORDS, FALSE);
    if (!desc_full)
        return NULL;

    /* Chop off everything after a '-'. */
    for (p = desc_full; *p; p++) {
        if (p[0] == ' ' && p[1] == '-' && p[2] == ' ') {
            p[0] = '\0';
            break;
        }
    }

    nm_assert(g_utf8_validate(desc_full, -1, NULL));

    return desc_full;
}

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

const NMLDBusMetaIface *const _nml_dbus_meta_ifaces[] = {
    &_nml_dbus_meta_iface_nm,
    &_nml_dbus_meta_iface_nm_accesspoint,
    &_nml_dbus_meta_iface_nm_agentmanager,
    &_nml_dbus_meta_iface_nm_checkpoint,
    &_nml_dbus_meta_iface_nm_connection_active,
    &_nml_dbus_meta_iface_nm_dhcp4config,
    &_nml_dbus_meta_iface_nm_dhcp6config,
    &_nml_dbus_meta_iface_nm_device,
    &_nml_dbus_meta_iface_nm_device_adsl,
    &_nml_dbus_meta_iface_nm_device_bluetooth,
    &_nml_dbus_meta_iface_nm_device_bond,
    &_nml_dbus_meta_iface_nm_device_bridge,
    &_nml_dbus_meta_iface_nm_device_dummy,
    &_nml_dbus_meta_iface_nm_device_generic,
    &_nml_dbus_meta_iface_nm_device_hsr,
    &_nml_dbus_meta_iface_nm_device_iptunnel,
    &_nml_dbus_meta_iface_nm_device_infiniband,
    &_nml_dbus_meta_iface_nm_device_loopback,
    &_nml_dbus_meta_iface_nm_device_lowpan,
    &_nml_dbus_meta_iface_nm_device_macsec,
    &_nml_dbus_meta_iface_nm_device_macvlan,
    &_nml_dbus_meta_iface_nm_device_modem,
    &_nml_dbus_meta_iface_nm_device_olpcmesh,
    &_nml_dbus_meta_iface_nm_device_ovsbridge,
    &_nml_dbus_meta_iface_nm_device_ovsinterface,
    &_nml_dbus_meta_iface_nm_device_ovsport,
    &_nml_dbus_meta_iface_nm_device_ppp,
    &_nml_dbus_meta_iface_nm_device_statistics,
    &_nml_dbus_meta_iface_nm_device_team,
    &_nml_dbus_meta_iface_nm_device_tun,
    &_nml_dbus_meta_iface_nm_device_veth,
    &_nml_dbus_meta_iface_nm_device_vlan,
    &_nml_dbus_meta_iface_nm_device_vrf,
    &_nml_dbus_meta_iface_nm_device_vxlan,
    &_nml_dbus_meta_iface_nm_device_wifip2p,
    &_nml_dbus_meta_iface_nm_device_wireguard,
    &_nml_dbus_meta_iface_nm_device_wired,
    &_nml_dbus_meta_iface_nm_device_wireless,
    &_nml_dbus_meta_iface_nm_device_wpan,
    &_nml_dbus_meta_iface_nm_dnsmanager,
    &_nml_dbus_meta_iface_nm_ip4config,
    &_nml_dbus_meta_iface_nm_ip6config,
    &_nml_dbus_meta_iface_nm_settings,
    &_nml_dbus_meta_iface_nm_settings_connection,
    &_nml_dbus_meta_iface_nm_vpn_connection,
    &_nml_dbus_meta_iface_nm_wifip2ppeer,
};

#define COMMON_PREFIX "org.freedesktop.NetworkManager"

static int
_strcmp_common_prefix(gconstpointer a, gconstpointer b, gpointer user_data)
{
    const NMLDBusMetaIface *iface           = a;
    const char             *dbus_iface_name = b;

    nm_assert(g_str_has_prefix(iface->dbus_iface_name, COMMON_PREFIX));

    return strcmp(&iface->dbus_iface_name[NM_STRLEN(COMMON_PREFIX)], dbus_iface_name);
}

const NMLDBusMetaIface *
nml_dbus_meta_iface_get(const char *dbus_iface_name)
{
    gssize idx;

    nm_assert(dbus_iface_name);

    G_STATIC_ASSERT_EXPR(G_STRUCT_OFFSET(NMLDBusMetaIface, dbus_iface_name) == 0);

    /* we assume that NetworkManager only uses unique interface names. E.g. one
     * interface name always has one particular meaning (and offers one set of
     * properties, signals and methods). This is a convenient assumption, and
     * we sure would never violate it when extending NM's D-Bus API. */

    if (NM_STR_HAS_PREFIX(dbus_iface_name, COMMON_PREFIX)) {
        /* optimize, that in fact all our interfaces have the same prefix. */
        idx = nm_ptrarray_find_bsearch((gconstpointer *) _nml_dbus_meta_ifaces,
                                       G_N_ELEMENTS(_nml_dbus_meta_ifaces),
                                       &dbus_iface_name[NM_STRLEN(COMMON_PREFIX)],
                                       _strcmp_common_prefix,
                                       NULL);
    } else
        return NULL;

    if (idx < 0)
        return NULL;
    return _nml_dbus_meta_ifaces[idx];
}

const NMLDBusMetaProperty *
nml_dbus_meta_property_get(const NMLDBusMetaIface *meta_iface,
                           const char             *dbus_property_name,
                           guint                  *out_idx)
{
    gssize idx;

    nm_assert(meta_iface);
    nm_assert(dbus_property_name);

    idx = nm_array_find_bsearch(meta_iface->dbus_properties,
                                meta_iface->n_dbus_properties,
                                sizeof(meta_iface->dbus_properties[0]),
                                &dbus_property_name,
                                nm_strcmp_p_with_data,
                                NULL);
    if (idx < 0) {
        NM_SET_OUT(out_idx, meta_iface->n_dbus_properties);
        return NULL;
    }
    NM_SET_OUT(out_idx, idx);
    return &meta_iface->dbus_properties[idx];
}

void
_nml_dbus_meta_class_init_with_properties_impl(GObjectClass                  *object_class,
                                               const NMLDBusMetaIface *const *meta_ifaces)
{
    int i_iface;

    nm_assert(G_IS_OBJECT_CLASS(object_class));
    nm_assert(meta_ifaces);
    nm_assert(meta_ifaces[0]);

    for (i_iface = 0; meta_ifaces[i_iface]; i_iface++) {
        const NMLDBusMetaIface *meta_iface = meta_ifaces[i_iface];
        guint8                 *reverse_idx;
        guint8                  i;

        nm_assert(g_type_is_a(meta_iface->get_type_fcn(), G_OBJECT_CLASS_TYPE(object_class)));
        nm_assert(meta_iface->n_obj_properties > 0);
        nm_assert(meta_iface->obj_properties);
        nm_assert(meta_iface->obj_properties_reverse_idx[0] == 0);
        nm_assert(meta_iface->obj_properties == meta_ifaces[0]->obj_properties);

        if (i_iface == 0)
            g_object_class_install_properties(object_class,
                                              meta_iface->n_obj_properties,
                                              (GParamSpec **) meta_iface->obj_properties);

        reverse_idx = (guint8 *) meta_iface->obj_properties_reverse_idx;

        for (i = 0; i < meta_iface->n_obj_properties; i++)
            reverse_idx[i] = 0xFFu;
        for (i = 0; i < meta_iface->n_dbus_properties; i++) {
            const NMLDBusMetaProperty *mpr = &meta_iface->dbus_properties[i];

            if (mpr->obj_properties_idx != 0 && !mpr->obj_property_no_reverse_idx) {
                nm_assert(mpr->obj_properties_idx < meta_iface->n_obj_properties);
                nm_assert(reverse_idx[mpr->obj_properties_idx] == 0xFFu);

                reverse_idx[mpr->obj_properties_idx] = i;
            }
        }
    }
}

gboolean
nm_utils_g_param_spec_is_default(const GParamSpec *pspec)
{
    g_return_val_if_fail(pspec, FALSE);

    if (pspec->value_type == G_TYPE_BOOLEAN)
        return ((((GParamSpecBoolean *) pspec)->default_value) == FALSE);
    if (pspec->value_type == G_TYPE_UCHAR)
        return ((((GParamSpecUChar *) pspec)->default_value) == 0u);
    if (pspec->value_type == G_TYPE_INT)
        return ((((GParamSpecInt *) pspec)->default_value) == 0);
    if (pspec->value_type == G_TYPE_UINT)
        return ((((GParamSpecUInt *) pspec)->default_value) == 0u);
    if (pspec->value_type == G_TYPE_INT64)
        return ((((GParamSpecInt64 *) pspec)->default_value) == 0);
    if (pspec->value_type == G_TYPE_UINT64)
        return ((((GParamSpecUInt64 *) pspec)->default_value) == 0u);
    if (g_type_is_a(pspec->value_type, G_TYPE_ENUM))
        return ((((GParamSpecEnum *) pspec)->default_value) == 0);
    if (g_type_is_a(pspec->value_type, G_TYPE_FLAGS))
        return ((((GParamSpecFlags *) pspec)->default_value) == 0u);
    if (pspec->value_type == G_TYPE_STRING)
        return ((((GParamSpecString *) pspec)->default_value) == NULL);
    if (NM_IN_SET(pspec->value_type,
                  G_TYPE_BYTES,
                  G_TYPE_PTR_ARRAY,
                  G_TYPE_ARRAY,
                  G_TYPE_HASH_TABLE,
                  G_TYPE_STRV)) {
        /* boxed types have NULL default. */
        g_return_val_if_fail(G_IS_PARAM_SPEC_BOXED(pspec), FALSE);
        g_return_val_if_fail(G_TYPE_IS_BOXED(pspec->value_type), FALSE);
        return TRUE;
    }
    if (g_type_is_a(pspec->value_type, NM_TYPE_OBJECT)) {
        /* object types have NULL default. */
        g_return_val_if_fail(G_IS_PARAM_SPEC_OBJECT(pspec), FALSE);
        g_return_val_if_fail(G_TYPE_IS_OBJECT(pspec->value_type), FALSE);
        return TRUE;
    }

    /* This function is only used for asserting/testing. It thus
     * strictly asserts and only support argument types that we expect. */
    g_return_val_if_reached(FALSE);
}

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

/**
 * nm_utils_print:
 * @output_mode: if 1 it uses g_print(). If 2, it uses g_printerr().
 *   If 0, it uses the same output as internal libnm debug logging
 *   does. That is, depending on LIBNM_CLIENT_DEBUG's "stdout" flag
 *   it uses g_print() or g_printerr() and if LIBNM_CLIENT_DEBUG_FILE is
 *   set, it writes the output to file instead
 * @msg: the message to print. The function does not append
 *   a trailing newline.
 *
 * The only purpose of this function is to give access to g_print()
 * or g_printerr() from pygobject. libnm can do debug logging by
 * setting LIBNM_CLIENT_DEBUG and uses thereby g_printerr() or
 * g_print(). A plain "print()" function in python is not in sync
 * with these functions (it implements additional buffering). By
 * using nm_utils_print(), the same logging mechanisms can be used.
 *
 * LIBNM_CLIENT_DEBUG is a list of keywords separated by commas. The keyword
 * "trace" enables printing messages of the lowest up to the highest severity.
 * Likewise, the severities "debug", "warn" ("warning") and "error" are honored
 * in similar way. Setting the flags "ERROR" or "WARN" ("WARNING") implies that
 * respective levels are enabled, but also are ERROR messages printed with
 * g_critical() and WARN messages with g_warning(). Together with G_DEBUG="fatal-warnings"
 * or G_DEBUG="fatal-critical" this can be used to abort the program on errors.
 * Note that all &lt;error&gt; messages imply an unexpected data on the D-Bus API
 * (due to a bug). &lt;warn&gt; also implies unexepected data, but that can happen
 * when using different versions of libnm and daemon. For testing, it is
 * good to turn these into assertions.
 *
 * By default, messages are printed to stderr, unless LIBNM_CLIENT_DEBUG
 * contains "stdout" flag. Also, libnm honors LIBNM_CLIENT_DEBUG_FILE
 * environment. If this is set to a filename pattern (accepting "%%p" for the
 * process ID), then the debug log is written to that file instead of
 * stderr/stdout. With @output_mode zero, the same location will be written.
 *
 * LIBNM_CLIENT_DEBUG_FILE is supported since 1.44. "ERROR", "WARN" and "WARNING"
 * are supported since 1.46.
 *
 * Since: 1.30
 */
void
nm_utils_print(int output_mode, const char *msg)
{
    gboolean use_stdout;

    g_return_if_fail(msg);

    switch (output_mode) {
    case 0:
        nml_dbus_log_enabled_full(NML_DBUS_LOG_LEVEL_ANY, &use_stdout);
        _log_printf(use_stdout, "%s", msg);
        break;
    case 1:
        g_print("%s", msg);
        break;
    case 2:
        g_printerr("%s", msg);
        break;
    default:
        g_return_if_reached();
        break;
    }
}

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

/**
 * nm_utils_file_is_certificate:
 * @filename: name of the file to test
 *
 * Tests if @filename has a valid extension for an X.509 certificate file
 * (".cer", ".crt", ".der", or ".pem"), and contains a certificate in a format
 * recognized by NetworkManager.
 *
 * Returns: %TRUE if the file is a certificate, %FALSE if it is not
 **/
gboolean
nm_utils_file_is_certificate(const char *filename)
{
    g_return_val_if_fail(filename != NULL, FALSE);

    return nm_crypto_utils_file_is_certificate(filename);
}

/**
 * nm_utils_file_is_private_key:
 * @filename: name of the file to test
 * @out_encrypted: (out): on return, whether the file is encrypted
 *
 * Tests if @filename has a valid extension for an X.509 private key file
 * (".der", ".key", ".pem", or ".p12"), and contains a private key in a format
 * recognized by NetworkManager.
 *
 * Returns: %TRUE if the file is a private key, %FALSE if it is not
 **/
gboolean
nm_utils_file_is_private_key(const char *filename, gboolean *out_encrypted)
{
    g_return_val_if_fail(filename != NULL, FALSE);

    return nm_crypto_utils_file_is_private_key(filename, out_encrypted);
}

/**
 * nm_utils_file_is_pkcs12:
 * @filename: name of the file to test
 *
 * Tests if @filename is a PKCS#<!-- -->12 file.
 *
 * Returns: %TRUE if the file is PKCS#<!-- -->12, %FALSE if it is not
 **/
gboolean
nm_utils_file_is_pkcs12(const char *filename)
{
    g_return_val_if_fail(filename != NULL, FALSE);

    return nm_crypto_is_pkcs12_file(filename, NULL);
}
