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

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

#include "nm-dhcp-options.h"

#include "libnm-glib-aux/nm-str-buf.h"

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

#define REQ(_num, _name, _include)                      \
    {                                                   \
        .name       = NM_DHCP_OPTION_REQPREFIX ""_name, \
        .option_num = _num,                             \
        .include    = _include,                         \
    }

const NMDhcpOption _nm_dhcp_option_dhcp4_options[] = {
    REQ(NM_DHCP_OPTION_DHCP4_SUBNET_MASK, "subnet_mask", TRUE),
    REQ(NM_DHCP_OPTION_DHCP4_TIME_OFFSET, "time_offset", TRUE),
    REQ(NM_DHCP_OPTION_DHCP4_DOMAIN_NAME_SERVER, "domain_name_servers", TRUE),
    REQ(NM_DHCP_OPTION_DHCP4_HOST_NAME, "host_name", TRUE),
    REQ(NM_DHCP_OPTION_DHCP4_DOMAIN_NAME, "domain_name", TRUE),
    REQ(NM_DHCP_OPTION_DHCP4_INTERFACE_MTU, "interface_mtu", TRUE),
    REQ(NM_DHCP_OPTION_DHCP4_BROADCAST, "broadcast_address", TRUE),
    /* RFC 3442: The Classless Static Routes option code MUST appear in the parameter
     *   request list prior to both the Router option code and the Static
     *   Routes option code, if present. */
    REQ(NM_DHCP_OPTION_DHCP4_CLASSLESS_STATIC_ROUTE, "rfc3442_classless_static_routes", TRUE),
    REQ(NM_DHCP_OPTION_DHCP4_ROUTER, "routers", TRUE),
    REQ(NM_DHCP_OPTION_DHCP4_STATIC_ROUTE, "static_routes", TRUE),
    REQ(NM_DHCP_OPTION_DHCP4_NIS_DOMAIN, "nis_domain", TRUE),
    REQ(NM_DHCP_OPTION_DHCP4_NIS_SERVERS, "nis_servers", TRUE),
    REQ(NM_DHCP_OPTION_DHCP4_NTP_SERVER, "ntp_servers", TRUE),
    REQ(NM_DHCP_OPTION_DHCP4_SERVER_ID, "dhcp_server_identifier", FALSE),
    REQ(NM_DHCP_OPTION_DHCP4_DOMAIN_SEARCH_LIST, "domain_search", TRUE),
    REQ(NM_DHCP_OPTION_DHCP4_PRIVATE_CLASSLESS_STATIC_ROUTE, "ms_classless_static_routes", TRUE),
    REQ(NM_DHCP_OPTION_DHCP4_PRIVATE_PROXY_AUTODISCOVERY, "wpad", TRUE),
    REQ(NM_DHCP_OPTION_DHCP4_ROOT_PATH, "root_path", TRUE),

    REQ(NM_DHCP_OPTION_DHCP4_TIME_SERVERS, "time_servers", FALSE),
    REQ(NM_DHCP_OPTION_DHCP4_IEN116_NAME_SERVERS, "ien116_name_servers", FALSE),
    REQ(NM_DHCP_OPTION_DHCP4_LOG_SERVERS, "log_servers", FALSE),
    REQ(NM_DHCP_OPTION_DHCP4_COOKIE_SERVERS, "cookie_servers", FALSE),
    REQ(NM_DHCP_OPTION_DHCP4_LPR_SERVERS, "lpr_servers", FALSE),
    REQ(NM_DHCP_OPTION_DHCP4_IMPRESS_SERVERS, "impress_servers", FALSE),
    REQ(NM_DHCP_OPTION_DHCP4_RESOURCE_LOCATION_SERVERS, "resource_location_servers", FALSE),
    REQ(NM_DHCP_OPTION_DHCP4_BOOT_FILE_SIZE, "boot_size", FALSE),
    REQ(NM_DHCP_OPTION_DHCP4_MERIT_DUMP, "merit_dump", FALSE),
    REQ(NM_DHCP_OPTION_DHCP4_SWAP_SERVER, "swap_server", FALSE),
    REQ(NM_DHCP_OPTION_DHCP4_EXTENSIONS_PATH, "extensions_path", FALSE),
    REQ(NM_DHCP_OPTION_DHCP4_ENABLE_IP_FORWARDING, "ip_forwarding", FALSE),
    REQ(NM_DHCP_OPTION_DHCP4_ENABLE_SRC_ROUTING, "non_local_source_routing", FALSE),
    REQ(NM_DHCP_OPTION_DHCP4_POLICY_FILTER, "policy_filter", FALSE),
    REQ(NM_DHCP_OPTION_DHCP4_INTERFACE_MDR, "max_dgram_reassembly", FALSE),
    REQ(NM_DHCP_OPTION_DHCP4_INTERFACE_TTL, "default_ip_ttl", FALSE),
    REQ(NM_DHCP_OPTION_DHCP4_INTERFACE_MTU_AGING_TIMEOUT, "path_mtu_aging_timeout", FALSE),
    REQ(NM_DHCP_OPTION_DHCP4_PATH_MTU_PLATEAU_TABLE, "path_mtu_plateau_table", FALSE),
    REQ(NM_DHCP_OPTION_DHCP4_ALL_SUBNETS_LOCAL, "all_subnets_local", FALSE),
    REQ(NM_DHCP_OPTION_DHCP4_PERFORM_MASK_DISCOVERY, "perform_mask_discovery", FALSE),
    REQ(NM_DHCP_OPTION_DHCP4_MASK_SUPPLIER, "mask_supplier", FALSE),
    REQ(NM_DHCP_OPTION_DHCP4_ROUTER_DISCOVERY, "router_discovery", FALSE),
    REQ(NM_DHCP_OPTION_DHCP4_ROUTER_SOLICITATION_ADDR, "router_solicitation_address", FALSE),
    REQ(NM_DHCP_OPTION_DHCP4_TRAILER_ENCAPSULATION, "trailer_encapsulation", FALSE),
    REQ(NM_DHCP_OPTION_DHCP4_ARP_CACHE_TIMEOUT, "arp_cache_timeout", FALSE),
    REQ(NM_DHCP_OPTION_DHCP4_IEEE802_3_ENCAPSULATION, "ieee802_3_encapsulation", FALSE),
    REQ(NM_DHCP_OPTION_DHCP4_DEFAULT_TCP_TTL, "default_tcp_ttl", FALSE),
    REQ(NM_DHCP_OPTION_DHCP4_TCP_KEEPALIVE_INTERVAL, "tcp_keepalive_interval", FALSE),
    REQ(NM_DHCP_OPTION_DHCP4_TCP_KEEPALIVE_GARBAGE, "tcp_keepalive_garbage", FALSE),
    REQ(NM_DHCP_OPTION_DHCP4_VENDOR_SPECIFIC, "vendor_encapsulated_options", FALSE),
    REQ(NM_DHCP_OPTION_DHCP4_NETBIOS_NAMESERVER, "netbios_name_servers", FALSE),
    REQ(NM_DHCP_OPTION_DHCP4_NETBIOS_DD_SERVER, "netbios_dd_server", FALSE),
    REQ(NM_DHCP_OPTION_DHCP4_FONT_SERVERS, "font_servers", FALSE),
    REQ(NM_DHCP_OPTION_DHCP4_X_DISPLAY_MANAGER, "x_display_manager", FALSE),
    REQ(NM_DHCP_OPTION_DHCP4_IP_ADDRESS_LEASE_TIME, "dhcp_lease_time", FALSE),
    REQ(NM_DHCP_OPTION_DHCP4_RENEWAL_T1_TIME, "dhcp_renewal_time", FALSE),
    REQ(NM_DHCP_OPTION_DHCP4_REBINDING_T2_TIME, "dhcp_rebinding_time", FALSE),
    REQ(NM_DHCP_OPTION_DHCP4_CLIENT_ID, "dhcp_client_identifier", FALSE),
    REQ(NM_DHCP_OPTION_DHCP4_NEW_TZDB_TIMEZONE, "tcode", FALSE),
    REQ(NM_DHCP_OPTION_DHCP4_NWIP_DOMAIN, "nwip_domain", FALSE),
    REQ(NM_DHCP_OPTION_DHCP4_NWIP_SUBOPTIONS, "nwip_suboptions", FALSE),
    REQ(NM_DHCP_OPTION_DHCP4_NISPLUS_DOMAIN, "nisplus_domain", FALSE),
    REQ(NM_DHCP_OPTION_DHCP4_NISPLUS_SERVERS, "nisplus_servers", FALSE),
    REQ(NM_DHCP_OPTION_DHCP4_TFTP_SERVER_NAME, "tftp_server_name", FALSE),
    REQ(NM_DHCP_OPTION_DHCP4_BOOTFILE_NAME, "bootfile_name", FALSE),
    REQ(NM_DHCP_OPTION_DHCP4_MOBILE_IP_HOME_AGENT, "mobile_ip_home_agent", FALSE),
    REQ(NM_DHCP_OPTION_DHCP4_SMTP_SERVER, "smtp_server", FALSE),
    REQ(NM_DHCP_OPTION_DHCP4_POP_SERVER, "pop_server", FALSE),
    REQ(NM_DHCP_OPTION_DHCP4_NNTP_SERVER, "nntp_server", FALSE),
    REQ(NM_DHCP_OPTION_DHCP4_WWW_SERVER, "www_server", FALSE),
    REQ(NM_DHCP_OPTION_DHCP4_FINGER_SERVER, "finger_server", FALSE),
    REQ(NM_DHCP_OPTION_DHCP4_IRC_SERVER, "irc_server", FALSE),
    REQ(NM_DHCP_OPTION_DHCP4_STREETTALK_SERVER, "streettalk_server", FALSE),
    REQ(NM_DHCP_OPTION_DHCP4_STREETTALK_DIR_ASSIST_SERVER,
        "streettalk_directory_assistance_server",
        FALSE),
    REQ(NM_DHCP_OPTION_DHCP4_SLP_DIRECTORY_AGENT, "slp_directory_agent", FALSE),
    REQ(NM_DHCP_OPTION_DHCP4_SLP_SERVICE_SCOPE, "slp_service_scope", FALSE),
    REQ(NM_DHCP_OPTION_DHCP4_CLIENT_FQDN, "fqdn", FALSE),
    REQ(NM_DHCP_OPTION_DHCP4_RELAY_AGENT_INFORMATION, "relay_agent_information", FALSE),
    REQ(NM_DHCP_OPTION_DHCP4_NDS_SERVERS, "nds_servers", FALSE),
    REQ(NM_DHCP_OPTION_DHCP4_NDS_TREE_NAME, "nds_tree_name", FALSE),
    REQ(NM_DHCP_OPTION_DHCP4_NDS_CONTEXT, "nds_context", FALSE),
    REQ(NM_DHCP_OPTION_DHCP4_BCMS_CONTROLLER_NAMES, "bcms_controller_names", FALSE),
    REQ(NM_DHCP_OPTION_DHCP4_BCMS_CONTROLLER_ADDRESS, "bcms_controller_address", FALSE),
    REQ(NM_DHCP_OPTION_DHCP4_CLIENT_LAST_TRANSACTION, "client_last_transaction_time", FALSE),
    REQ(NM_DHCP_OPTION_DHCP4_ASSOCIATED_IP, "associated_ip", FALSE),
    REQ(NM_DHCP_OPTION_DHCP4_PXE_SYSTEM_TYPE, "pxe_system_type", FALSE),
    REQ(NM_DHCP_OPTION_DHCP4_PXE_INTERFACE_ID, "pxe_interface_id", FALSE),
    REQ(NM_DHCP_OPTION_DHCP4_PXE_CLIENT_ID, "pxe_client_id", FALSE),
    REQ(NM_DHCP_OPTION_DHCP4_UAP_SERVERS, "uap_servers", FALSE),
    REQ(NM_DHCP_OPTION_DHCP4_GEOCONF_CIVIC, "geoconf_civic", FALSE),
    REQ(NM_DHCP_OPTION_DHCP4_IPV6_ONLY_PREFERRED, "ipv6_only_preferred", FALSE),
    REQ(NM_DHCP_OPTION_DHCP4_NETINFO_SERVER_ADDRESS, "netinfo_server_address", FALSE),
    REQ(NM_DHCP_OPTION_DHCP4_NETINFO_SERVER_TAG, "netinfo_server_tag", FALSE),
    REQ(NM_DHCP_OPTION_DHCP4_DEFAULT_URL, "default_url", FALSE),
    REQ(NM_DHCP_OPTION_DHCP4_AUTO_CONFIG, "auto_config", FALSE),
    REQ(NM_DHCP_OPTION_DHCP4_NAME_SERVICE_SEARCH, "name_service_search", FALSE),
    REQ(NM_DHCP_OPTION_DHCP4_SUBNET_SELECTION, "subnet_selection", FALSE),
    REQ(NM_DHCP_OPTION_DHCP4_VIVCO, "vivco", FALSE),
    REQ(NM_DHCP_OPTION_DHCP4_VIVSO, "vivso", FALSE),
    REQ(NM_DHCP_OPTION_DHCP4_PANA_AGENT, "pana_agent", FALSE),
    REQ(NM_DHCP_OPTION_DHCP4_V4_LOST, "v4_lost", FALSE),
    REQ(NM_DHCP_OPTION_DHCP4_SIP_UA_CS_DOMAINS, "sip_ua_cs_domains", FALSE),
    REQ(NM_DHCP_OPTION_DHCP4_IPV4_ADDRESS_ANDSF, "ipv4_address_andsf", FALSE),
    REQ(NM_DHCP_OPTION_DHCP4_RDNSS_SELECTION, "rndss_selection", FALSE),
    REQ(NM_DHCP_OPTION_DHCP4_TFTP_SERVER_ADDRESS, "tftp_server_address", FALSE),
    REQ(NM_DHCP_OPTION_DHCP4_V4_PORTPARAMS, "v4_portparams", FALSE),
    REQ(NM_DHCP_OPTION_DHCP4_V4_CAPTIVE_PORTAL, "v4_captive_portal", FALSE),
    REQ(NM_DHCP_OPTION_DHCP4_MUD_URL, "mud_url", FALSE),
    REQ(NM_DHCP_OPTION_DHCP4_LOADER_CONFIGFILE, "loader_configfile", FALSE),
    REQ(NM_DHCP_OPTION_DHCP4_LOADER_PATHPREFIX, "loader_pathprefix", FALSE),
    REQ(NM_DHCP_OPTION_DHCP4_LOADER_REBOOTTIME, "loader_reboottime", FALSE),
    REQ(NM_DHCP_OPTION_DHCP4_OPTION_6RD, "option_6rd", FALSE),
    REQ(NM_DHCP_OPTION_DHCP4_V4_ACCESS_DOMAIN, "v4_access_domain", FALSE),
    REQ(NM_DHCP_OPTION_DHCP4_PRIVATE_224, "private_224", FALSE),
    REQ(NM_DHCP_OPTION_DHCP4_PRIVATE_225, "private_225", FALSE),
    REQ(NM_DHCP_OPTION_DHCP4_PRIVATE_226, "private_226", FALSE),
    REQ(NM_DHCP_OPTION_DHCP4_PRIVATE_227, "private_227", FALSE),
    REQ(NM_DHCP_OPTION_DHCP4_PRIVATE_228, "private_228", FALSE),
    REQ(NM_DHCP_OPTION_DHCP4_PRIVATE_229, "private_229", FALSE),
    REQ(NM_DHCP_OPTION_DHCP4_PRIVATE_230, "private_230", FALSE),
    REQ(NM_DHCP_OPTION_DHCP4_PRIVATE_231, "private_231", FALSE),
    REQ(NM_DHCP_OPTION_DHCP4_PRIVATE_232, "private_232", FALSE),
    REQ(NM_DHCP_OPTION_DHCP4_PRIVATE_233, "private_233", FALSE),
    REQ(NM_DHCP_OPTION_DHCP4_PRIVATE_234, "private_234", FALSE),
    REQ(NM_DHCP_OPTION_DHCP4_PRIVATE_235, "private_235", FALSE),
    REQ(NM_DHCP_OPTION_DHCP4_PRIVATE_236, "private_236", FALSE),
    REQ(NM_DHCP_OPTION_DHCP4_PRIVATE_237, "private_237", FALSE),
    REQ(NM_DHCP_OPTION_DHCP4_PRIVATE_238, "private_238", FALSE),
    REQ(NM_DHCP_OPTION_DHCP4_PRIVATE_239, "private_239", FALSE),
    REQ(NM_DHCP_OPTION_DHCP4_PRIVATE_240, "private_240", FALSE),
    REQ(NM_DHCP_OPTION_DHCP4_PRIVATE_241, "private_241", FALSE),
    REQ(NM_DHCP_OPTION_DHCP4_PRIVATE_242, "private_242", FALSE),
    REQ(NM_DHCP_OPTION_DHCP4_PRIVATE_243, "private_243", FALSE),
    REQ(NM_DHCP_OPTION_DHCP4_PRIVATE_244, "private_244", FALSE),
    REQ(NM_DHCP_OPTION_DHCP4_PRIVATE_245, "private_245", FALSE),
    REQ(NM_DHCP_OPTION_DHCP4_PRIVATE_246, "private_246", FALSE),
    REQ(NM_DHCP_OPTION_DHCP4_PRIVATE_247, "private_247", FALSE),
    REQ(NM_DHCP_OPTION_DHCP4_PRIVATE_248, "private_248", FALSE),
    /* NM_DHCP_OPTION_DHCP4_PRIVATE_CLASSLESS_STATIC_ROUTE */
    REQ(NM_DHCP_OPTION_DHCP4_PRIVATE_250, "private_250", FALSE),
    REQ(NM_DHCP_OPTION_DHCP4_PRIVATE_251, "private_251", FALSE),
    /* NM_DHCP_OPTION_DHCP4_PRIVATE_PROXY_AUTODISCOVERY */
    REQ(NM_DHCP_OPTION_DHCP4_PRIVATE_253, "private_253", FALSE),
    REQ(NM_DHCP_OPTION_DHCP4_PRIVATE_254, "private_254", FALSE),

    /* Internal values */
    REQ(NM_DHCP_OPTION_DHCP4_NM_IP_ADDRESS, "ip_address", FALSE),
    REQ(NM_DHCP_OPTION_DHCP4_NM_EXPIRY, "expiry", FALSE),
    REQ(NM_DHCP_OPTION_DHCP4_NM_NEXT_SERVER, "next_server", FALSE),
    REQ(NM_DHCP_OPTION_DHCP4_NM_FILENAME, "filename", FALSE),
};

static const NMDhcpOption *const _sorted_options_4[G_N_ELEMENTS(_nm_dhcp_option_dhcp4_options)] = {
#define A(idx) (&_nm_dhcp_option_dhcp4_options[(idx)])
    A(0),   A(1),   A(8),   A(18),  A(19),  A(2),   A(20),  A(21),  A(22),  A(23),  A(24),  A(3),
    A(25),  A(26),  A(4),   A(27),  A(17),  A(28),  A(29),  A(30),  A(31),  A(32),  A(33),  A(34),
    A(35),  A(5),   A(36),  A(6),   A(37),  A(38),  A(39),  A(40),  A(9),   A(41),  A(42),  A(43),
    A(44),  A(45),  A(46),  A(10),  A(11),  A(12),  A(47),  A(48),  A(49),  A(50),  A(51),  A(52),
    A(13),  A(53),  A(54),  A(55),  A(57),  A(58),  A(59),  A(60),  A(61),  A(62),  A(63),  A(64),
    A(65),  A(66),  A(67),  A(68),  A(69),  A(70),  A(71),  A(72),  A(73),  A(74),  A(75),  A(76),
    A(77),  A(78),  A(79),  A(80),  A(81),  A(82),  A(83),  A(84),  A(85),  A(86),  A(87),  A(56),
    A(88),  A(89),  A(90),  A(91),  A(92),  A(93),  A(94),  A(14),  A(7),   A(95),  A(96),  A(97),
    A(98),  A(99),  A(100), A(101), A(102), A(103), A(104), A(105), A(106), A(107), A(108), A(109),
    A(110), A(111), A(112), A(113), A(114), A(115), A(116), A(117), A(118), A(119), A(120), A(121),
    A(122), A(123), A(124), A(125), A(126), A(127), A(128), A(129), A(130), A(131), A(132), A(133),
    A(134), A(135), A(15),  A(136), A(137), A(16),  A(138), A(139), A(140), A(141), A(142), A(143),
#undef A
};

const NMDhcpOption _nm_dhcp_option_dhcp6_options[] = {
    REQ(NM_DHCP_OPTION_DHCP6_CLIENT_ID, "dhcp6_client_id", FALSE),

    /* Don't request server ID by default; some servers don't reply to
     * Information Requests that request the Server ID.
     */
    REQ(NM_DHCP_OPTION_DHCP6_SERVER_ID, "dhcp6_server_id", FALSE),

    REQ(NM_DHCP_OPTION_DHCP6_DNS_SERVERS, "dhcp6_name_servers", TRUE),
    REQ(NM_DHCP_OPTION_DHCP6_DOMAIN_LIST, "dhcp6_domain_search", TRUE),
    REQ(NM_DHCP_OPTION_DHCP6_IA_PD, "ip6_prefix", FALSE),
    REQ(NM_DHCP_OPTION_DHCP6_SNTP_SERVERS, "dhcp6_sntp_servers", TRUE),
    REQ(NM_DHCP_OPTION_DHCP6_FQDN, "fqdn_fqdn", FALSE),
    REQ(NM_DHCP_OPTION_DHCP6_NTP_SERVER, "dhcp6_ntp_servers", TRUE),
    REQ(NM_DHCP_OPTION_DHCP6_MUD_URL, "dhcp6_mud_url", FALSE),

    /* Internal values */
    REQ(NM_DHCP_OPTION_DHCP6_NM_IP_ADDRESS, "ip6_address", FALSE),
    REQ(NM_DHCP_OPTION_DHCP6_NM_PREFIXLEN, "ip6_prefixlen", FALSE),
    REQ(NM_DHCP_OPTION_DHCP6_NM_PREFERRED_LIFE, "preferred_life", FALSE),
    REQ(NM_DHCP_OPTION_DHCP6_NM_MAX_LIFE, "max_life", FALSE),
    REQ(NM_DHCP_OPTION_DHCP6_NM_STARTS, "starts", FALSE),
    REQ(NM_DHCP_OPTION_DHCP6_NM_LIFE_STARTS, "life_starts", FALSE),
    REQ(NM_DHCP_OPTION_DHCP6_NM_RENEW, "renew", FALSE),
    REQ(NM_DHCP_OPTION_DHCP6_NM_REBIND, "rebind", FALSE),
    REQ(NM_DHCP_OPTION_DHCP6_NM_IAID, "iaid", FALSE),
};

#undef REQ

static const NMDhcpOption *const _sorted_options_6[G_N_ELEMENTS(_nm_dhcp_option_dhcp6_options)] = {
#define A(idx) (&_nm_dhcp_option_dhcp6_options[(idx)])
    A(0),
    A(1),
    A(2),
    A(3),
    A(4),
    A(5),
    A(6),
    A(7),
    A(8),
    A(9),
    A(10),
    A(11),
    A(12),
    A(13),
    A(14),
    A(15),
    A(16),
    A(17),
#undef A
};

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

static int
_sorted_options_generate_sort(gconstpointer pa, gconstpointer pb, gpointer user_data)
{
    const NMDhcpOption *const *a = pa;
    const NMDhcpOption *const *b = pb;

    NM_CMP_DIRECT((*a)->option_num, (*b)->option_num);
    return 0;
}

static char *
_sorted_options_generate(const NMDhcpOption *base, const NMDhcpOption *const *sorted, guint n)
{
    gs_free const NMDhcpOption **sort2 = NULL;
    NMStrBuf                     sbuf  = NM_STR_BUF_INIT(0, FALSE);
    guint                        i;

    sort2 = nm_memdup(sorted, n * sizeof(sorted[0]));

    g_qsort_with_data(sort2, n, sizeof(sort2[0]), _sorted_options_generate_sort, NULL);

    for (i = 0; i < n; i++) {
        if (i > 0)
            nm_str_buf_append(&sbuf, ", ");
        nm_str_buf_append_printf(&sbuf, "A(%d)", (int) (sort2[i] - base));
    }

    return nm_str_buf_finalize(&sbuf, NULL);
}

_nm_unused static void
_ASSERT_sorted(int IS_IPv4, const NMDhcpOption *const *const sorted, int n)
{
    const NMDhcpOption *const options =
        IS_IPv4 ? _nm_dhcp_option_dhcp4_options : _nm_dhcp_option_dhcp6_options;
    int           i;
    int           j;
    gs_free char *sorted_msg = NULL;

    for (i = 0; i < n; i++) {
        const NMDhcpOption *opt = sorted[i];

        if (!opt || opt < options || opt >= &options[n]) {
            g_error("%s:%d: _sorted_options_%c[%d] must be a valid pointer into "
                    "_nm_dhcp_option_dhcp%c_options, but is %p",
                    __FILE__,
                    __LINE__,
                    IS_IPv4 ? '4' : '6',
                    i,
                    IS_IPv4 ? '4' : '6',
                    opt);
        }
        g_assert(opt);
        g_assert(opt >= options);
        g_assert(opt < &options[n]);

        for (j = 0; j < i; j++) {
            const NMDhcpOption *opt2 = sorted[j];

            if (opt == opt2) {
                g_error("%s:%d: _sorted_options_%c[%d] (opt=%u, %s) is duplicated at "
                        "[%d] (SORT: %s)",
                        __FILE__,
                        __LINE__,
                        IS_IPv4 ? '4' : '6',
                        i,
                        opt->option_num,
                        opt->name,
                        j,
                        (sorted_msg = _sorted_options_generate(options, sorted, n)));
            }
        }

        if (i > 0) {
            const NMDhcpOption *opt2 = sorted[i - 1];

            if (opt2->option_num >= opt->option_num) {
                g_error("%s:%d: _sorted_options_%c[%d] (opt=%u, %s) should come before "
                        "[%d] (opt=%u, %s) (SORT: %s)",
                        __FILE__,
                        __LINE__,
                        IS_IPv4 ? '4' : '6',
                        i,
                        opt->option_num,
                        opt->name,
                        i - 1,
                        opt2->option_num,
                        opt2->name,
                        (sorted_msg = _sorted_options_generate(options, sorted, n)));
            }
        }
    }
}

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

const NMDhcpOption *
nm_dhcp_option_find(int addr_family, guint option)
{
    const int                        IS_IPv4 = NM_IS_IPv4(addr_family);
    const NMDhcpOption *const *const sorted  = IS_IPv4 ? _sorted_options_4 : _sorted_options_6;
    const int                        n       = IS_IPv4 ? G_N_ELEMENTS(_nm_dhcp_option_dhcp4_options)
                                                       : G_N_ELEMENTS(_nm_dhcp_option_dhcp6_options);
    int                              imin    = 0;
    int                              imax    = n - 1;
    int                              imid    = (n - 1) / 2;

#if NM_MORE_ASSERTS > 10
    nm_assert(n < G_MAXINT / 2);
    if (IS_IPv4 && !NM_MORE_ASSERT_ONCE(10)) {
        /* already checked */
    } else if (!IS_IPv4 && !NM_MORE_ASSERT_ONCE(10)) {
        /* already checked */
    } else
        _ASSERT_sorted(IS_IPv4, sorted, n);
#endif

    for (;;) {
        const guint o = sorted[imid]->option_num;

        if (G_UNLIKELY(o == option))
            return sorted[imid];

        if (o < option)
            imin = imid + 1;
        else
            imax = imid - 1;

        if (G_UNLIKELY(imin > imax))
            break;

        imid = (imin + imax) / 2;
    }

    /* Option should always be found */
    return nm_assert_unreachable_val(NULL);
}

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

void
nm_dhcp_option_take_option(GHashTable *options,
                           gboolean    static_keys,
                           int         addr_family,
                           guint       option,
                           char       *value)
{
    const char *key;

    nm_assert_addr_family(addr_family);
    nm_assert(value);
    nm_assert(g_utf8_validate(value, -1, NULL));

    if (!options) {
        nm_assert_not_reached();
        g_free(value);
        return;
    }

    key = nm_dhcp_option_request_string(addr_family, option),
    g_hash_table_insert(options, static_keys ? (gpointer) key : g_strdup(key), value);
}

void
nm_dhcp_option_add_option_utf8safe_escape(GHashTable   *options,
                                          gboolean      static_keys,
                                          int           addr_family,
                                          guint         option,
                                          const guint8 *data,
                                          gsize         n_data)
{
    gs_free char *to_free = NULL;
    const char   *escaped;

    escaped = nm_utils_buf_utf8safe_escape((char *) data,
                                           n_data,
                                           NM_UTILS_STR_UTF8_SAFE_FLAG_ESCAPE_CTRL,
                                           &to_free);
    nm_dhcp_option_add_option(options, static_keys, addr_family, option, escaped ?: "");
}

void
nm_dhcp_option_add_option_u64(GHashTable *options,
                              gboolean    static_keys,
                              int         addr_family,
                              guint       option,
                              guint64     value)
{
    nm_dhcp_option_take_option(options,
                               static_keys,
                               addr_family,
                               option,
                               g_strdup_printf("%" G_GUINT64_FORMAT, value));
}

void
nm_dhcp_option_add_option_in_addr(GHashTable *options,
                                  gboolean    static_keys,
                                  int         addr_family,
                                  guint       option,
                                  in_addr_t   value)
{
    char sbuf[NM_INET_ADDRSTRLEN];

    nm_dhcp_option_add_option(options,
                              static_keys,
                              addr_family,
                              option,
                              nm_inet4_ntop(value, sbuf));
}

void
nm_dhcp_option_add_requests_to_options(GHashTable *options, int addr_family)
{
    const int                 IS_IPv4 = NM_IS_IPv4(addr_family);
    const NMDhcpOption *const all_options =
        IS_IPv4 ? _nm_dhcp_option_dhcp4_options : _nm_dhcp_option_dhcp6_options;
    int n_options = IS_IPv4 ? G_N_ELEMENTS(_nm_dhcp_option_dhcp4_options)
                            : G_N_ELEMENTS(_nm_dhcp_option_dhcp6_options);
    int i;

    for (i = 0; i < n_options; i++) {
        if (all_options[i].include)
            g_hash_table_insert(options, (gpointer) all_options[i].name, g_strdup("1"));
    }
}

GHashTable *
nm_dhcp_option_create_options_dict(gboolean static_keys)
{
    return g_hash_table_new_full(nm_str_hash, g_str_equal, static_keys ? NULL : g_free, g_free);
}
