/*
 * Copyright (c) 2008, 2010-2018, Arm Limited and affiliates.
 * SPDX-License-Identifier: Apache-2.0
 *
 * Licensed under the Apache License, Version 2.0 (the "License");
 * you may not use this file except in compliance with the License.
 * You may obtain a copy of the License at
 *
 *     http://www.apache.org/licenses/LICENSE-2.0
 *
 * Unless required by applicable law or agreed to in writing, software
 * distributed under the License is distributed on an "AS IS" BASIS,
 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
 * See the License for the specific language governing permissions and
 * limitations under the License.
 */
/**
 * \file address.c
 * \brief Utility functions concernig addresses
 *
 * This file contains all the utility functions that can be used to
 * check, manipulate etc. addresses.
 */
#include "nsconfig.h"
#include "ns_types.h"
#include "ip6string.h"
#include "ns_trace.h"
#include "randLIB.h"
#include "string.h"
#include "nsdynmemLIB.h"
#include "ns_sha256.h"
#include "socket_api.h"
#include "Common_Protocols/ipv6_constants.h"
#include "Common_Protocols/icmpv6.h"
#include "Common_Protocols/mld.h"
#include "6LoWPAN/Thread/thread_common.h"
#include "common_functions.h"

#include "RPL/rpl_control.h"

#define TRACE_GROUP "addr"

typedef struct addr_notification {
    if_address_notification_fn *fn;
    ns_list_link_t link;
} addr_notification_t;

static NS_LIST_DEFINE(addr_notifications, addr_notification_t, link);

typedef struct addr_policy_table_entry_t {
    uint8_t prefix[16];
    uint8_t prefix_len;
    uint8_t precedence;
    uint8_t label;
    ns_list_link_t link;
} addr_policy_table_entry_t;

static NS_LIST_DEFINE(addr_policy_table, addr_policy_table_entry_t, link);

uint32_t addr_preferences_default = SOCKET_IPV6_PREFER_SRC_TMP | SOCKET_IPV6_PREFER_SRC_6LOWPAN_SHORT;

static void addr_policy_table_reset(void);
static void addr_max_entries_check(protocol_interface_info_entry_t *cur, if_address_source_t source);

const uint8_t ADDR_LINK_LOCAL_PREFIX[8]         = { 0xfe, 0x80 };
const uint8_t ADDR_SHORT_ADR_SUFFIC[6]          = { 0x00, 0x00, 0x00, 0xff, 0xfe, 0x00};

const uint8_t ADDR_MULTICAST_SOLICITED[13]      = { 0xff, 0x02, [11] = 0x01, 0xff};
const uint8_t ADDR_IF_LOCAL_ALL_NODES[16]       = { 0xff, 0x01, [15] = 0x01 };
const uint8_t ADDR_IF_LOCAL_ALL_ROUTERS[16]     = { 0xff, 0x01, [15] = 0x02 };
const uint8_t ADDR_LINK_LOCAL_ALL_NODES[16]     = { 0xff, 0x02, [15] = 0x01 };
const uint8_t ADDR_LINK_LOCAL_ALL_ROUTERS[16]   = { 0xff, 0x02, [15] = 0x02 };
const uint8_t ADDR_LINK_LOCAL_ALL_MLDV2_ROUTERS[16] = { 0xff, 0x02, [15] = 0x16 };
const uint8_t ADDR_LINK_LOCAL_MDNS[16]          = { 0xff, 0x02, [15] = 0xfb };
const uint8_t ADDR_REALM_LOCAL_ALL_NODES[16]    = { 0xff, 0x03, [15] = 0x01 };
const uint8_t ADDR_REALM_LOCAL_ALL_ROUTERS[16]  = { 0xff, 0x03, [15] = 0x02 };
const uint8_t ADDR_SITE_LOCAL_ALL_ROUTERS[16]   = { 0xff, 0x05, [15] = 0x02 };
const uint8_t ADDR_ALL_MPL_FORWARDERS[16]       = { 0xff, 0x03, [15] = 0xfc };
const uint8_t ADDR_ALL_DHCP_RELAY_AGENTS_AND_SERVERS[16] = { 0xff, 0x02, [13] = 0x01, 0x00, 0x02 };

const uint8_t ADDR_IPV4_MAPPED_PREFIX[12]       = { [10] = 0xff, 0xff };
const uint8_t ADDR_LOOPBACK[16]                 = { [15] = 1 };
const uint8_t ADDR_UNSPECIFIED[16]              = { 0 };        /* Note a few bits of code check for pointer equality with ADDR_UNSPECIFIED */
#define ADDR_IPV4_COMPATIBLE                    ADDR_LOOPBACK /* First 96 bits match...*/

#define ADDR_MULTICAST_LINK_PREFIX              ADDR_LINK_LOCAL_ALL_NODES /* ff02::xx */
#define ADDR_MULTICAST_REALM_PREFIX             ADDR_ALL_MPL_FORWARDERS /* ff03::xx */

static const uint8_t *addr_iid_secret_key;
static const uint8_t *addr_initial_iid;
static uint8_t addr_iid_secret_key_len;

static bool addr_am_implicit_group_member(const uint8_t group[static 16])
{
    static const uint8_t * const implicit_groups[] = {
        ADDR_LINK_LOCAL_ALL_NODES,
        ADDR_IF_LOCAL_ALL_NODES,
    };

    for (uint_fast8_t i = 0; i < sizeof implicit_groups / sizeof implicit_groups[0]; i++) {
        if (addr_ipv6_equal(implicit_groups[i], group)) {
            return true;
        }
    }
    return false;
}

uint8_t addr_len_from_type(addrtype_t addr_type)
{
    switch (addr_type) {
        case ADDR_NONE:
            return 0;
        case ADDR_802_15_4_SHORT:
            return 2 + 2; /* Some users don't have the PAN ID */
        case ADDR_802_15_4_LONG:
            return 2 + 8;
        case ADDR_EUI_48:
            return 6;
        case ADDR_IPV6:
            return 16;
        case ADDR_BROADCAST:
            return 0; /* Don't really handle this */
    }
    return 0;
}

/**
 * Check if an address is a broadcast address
 *
 * \param addr pointer to an address_t containing the address to be checked
 * \param addr_type the type of the address_t
 * \return 0 if the address is a broadcast address
 */
uint8_t addr_check_broadcast(const address_t addr, addrtype_t addr_type)
{
    switch (addr_type) {
        case ADDR_802_15_4_SHORT:
            break;
        case ADDR_BROADCAST:
            return 0;
        default:
            return 1;
    }

    uint8_t size = 2;
    uint8_t *ptr = (uint8_t *) addr;
    ptr += 2;

    while (size) {
        if ((*ptr++) != 0xFF) {
            break;
        } else {
            size--;
        }
    }
    return (size);

}

bool addr_is_ipv6_link_local(const uint8_t addr[static 16])
{
    return addr[0] == 0xfe && (addr[1] & 0xc0) == 0x80;
}

/* Site-local addresses deprecated, but still processed by RFC 6724 address selection */
static bool addr_is_ipv6_site_local(const uint8_t addr[static 16])
{
    return addr[0] == 0xfe && (addr[1] & 0xc0) == 0xc0;
}

static bool addr_is_ipv4_mapped(const uint8_t addr[static 16])
{
    return memcmp(addr, ADDR_IPV4_MAPPED_PREFIX, 12) == 0;
}

/* Scope(A), as defined in RFC 6724 plus RFC 4007 */
uint_fast8_t addr_ipv6_scope(const uint8_t addr[static 16], const protocol_interface_info_entry_t *interface)
{
    if (addr_is_ipv6_multicast(addr)) {
        return addr_ipv6_multicast_scope(addr);
    }
    if (addr_is_ipv6_link_local(addr) || addr_is_ipv6_loopback(addr)) {
        return IPV6_SCOPE_LINK_LOCAL;
    }
    if (addr_is_ipv6_site_local(addr)) {
        return IPV6_SCOPE_SITE_LOCAL;
    }
    /* Thread-specific hack - we want to treat one specific "mesh local" ULA as
     * "realm local" scope. (This is necessary, for example, to use a
     * mesh-local ULA address as source when sending realm-local multicast).
     */
    if (thread_addr_is_mesh_local(addr, interface)) {
        return IPV6_SCOPE_REALM_LOCAL;
    }
    if (addr_is_ipv4_mapped(addr)) {
        if ((addr[12] == 169 && addr[13] == 254) || addr[12] == 127) {
            return IPV6_SCOPE_LINK_LOCAL;
        }
        return IPV6_SCOPE_GLOBAL;
    }
    return IPV6_SCOPE_GLOBAL;
}

void address_module_init(void)
{
    addr_policy_table_reset();
    //mac_reset_short_address();
}

#ifdef MULTICAST_FORWARDING
static if_group_fwd_entry_t *addr_multicast_fwd_list_lookup(if_group_fwd_list_t *list, const uint8_t group[16])
{
    ns_list_foreach(if_group_fwd_entry_t, e, list) {
        if (addr_ipv6_equal(e->group, group)) {
            return e;
        }
    }
    return NULL;
}

bool addr_multicast_fwd_check(protocol_interface_info_entry_t *interface, const uint8_t group[16])
{
    return addr_multicast_fwd_list_lookup(&interface->ip_groups_fwd, group) != NULL;
}

static void addr_multicast_fwd_adjust_upstream(protocol_interface_info_entry_t *downstream, protocol_interface_info_entry_t *upstream, const uint8_t group[16], bool add)
{
    if (!upstream || downstream == upstream || !downstream->ip_multicast_forwarding) {
        return;
    }
    uint8_t group_scope = addr_ipv6_multicast_scope(group);
    if (downstream->zone_index[group_scope] == upstream->zone_index[group_scope]) {
        tr_debug("Multicast proxy %s %s", add ? "add" : "remove", trace_ipv6(group));
        if (add) {
            addr_add_group(upstream, group);
        } else {
            addr_remove_group(upstream, group);
        }
    }
}

void addr_multicast_fwd_adjust_upstream_full(protocol_interface_info_entry_t *upstream, bool add)
{
    ns_list_foreach(protocol_interface_info_entry_t, interface, &protocol_interface_info_list) {
        ns_list_foreach(if_group_fwd_entry_t, group, &interface->ip_groups_fwd) {
            addr_multicast_fwd_adjust_upstream(interface, upstream, group->group, add);
        }
    }
}

void addr_multicast_fwd_set_forwarding(struct protocol_interface_info_entry *interface, bool enable)
{
    /* Do nothing if enable state isn't changing */
    if (interface->ip_multicast_forwarding == enable) {
        return;
    }

    /* If we disable forwarding on upstream, clear it out to avoid maintenance confusion */
    if (!enable && interface == protocol_core_multicast_upstream) {
        multicast_fwd_set_proxy_upstream(-1);
    }

    /* Adjust routine checks that forwarding is on before doing any processing. So make sure it's on
     * now, then we'll set requested state afterwards. */
    interface->ip_multicast_forwarding = true;

    /* Add or remove all groups on changing downstream interface to upstream */
    if (protocol_core_multicast_upstream) {
        ns_list_foreach(if_group_fwd_entry_t, group, &interface->ip_groups_fwd) {
            addr_multicast_fwd_adjust_upstream(interface, protocol_core_multicast_upstream, group->group, enable);
        }
    }

    interface->ip_multicast_forwarding = enable;
}

bool addr_multicast_fwd_add(protocol_interface_info_entry_t *interface, const uint8_t group[16], uint32_t lifetime)
{
    if_group_fwd_entry_t *entry = addr_multicast_fwd_list_lookup(&interface->ip_groups_fwd, group);
    if (entry) {
        if (entry->lifetime < lifetime) {
            entry->lifetime = lifetime;
        }
        return true;
    }
    entry = ns_dyn_mem_alloc(sizeof *entry);
    if (!entry) {
        return false;
    }
    memcpy(entry->group, group, 16);
    ns_list_add_to_end(&interface->ip_groups_fwd, entry);
    addr_multicast_fwd_adjust_upstream(interface, protocol_core_multicast_upstream, group, true);
    entry->lifetime = lifetime;
    tr_debug("MC fwd added to IF %d: %s", interface->id, trace_ipv6(group));
    return true;
}

static void addr_multicast_fwd_delete_entry(protocol_interface_info_entry_t *interface, if_group_fwd_entry_t *entry)
{
    addr_multicast_fwd_adjust_upstream(interface, protocol_core_multicast_upstream, entry->group, false);
    ns_list_remove(&interface->ip_groups_fwd, entry);
    ns_dyn_mem_free(entry);
}

bool addr_multicast_fwd_remove(protocol_interface_info_entry_t *interface, const uint8_t group[16])
{
    if_group_fwd_entry_t *entry = addr_multicast_fwd_list_lookup(&interface->ip_groups_fwd, group);
    if (!entry) {
        return false;
    }

    tr_debug("MC fwd removed from IF %d: %s", interface->id, trace_ipv6(group));
    addr_multicast_fwd_delete_entry(interface, entry);

    return true;
}
#endif // MULTICAST_FORWARDING

int_fast8_t addr_policy_table_add_entry(const uint8_t *prefix, uint8_t len, uint8_t precedence, uint8_t label)
{
    addr_policy_table_entry_t *entry = ns_dyn_mem_alloc(sizeof(addr_policy_table_entry_t));
    if (!entry) {
        return -1;
    }
    bitcopy(entry->prefix, prefix, len);
    entry->prefix_len = len;
    entry->precedence = precedence;
    entry->label = label;

    /* Table is sorted longest-prefix-first, for longest-match searching */
    bool inserted = false;
    ns_list_foreach(addr_policy_table_entry_t, before, &addr_policy_table) {
        if (before->prefix_len > len) {
            continue;
        }
        if (before->prefix_len == len && bitsequal(before->prefix, prefix, len)) {
            ns_dyn_mem_free(entry);
            return -2;
        }
        ns_list_add_before(&addr_policy_table, before, entry);
        inserted = true;
        break;
    }

    if (!inserted) {
        ns_list_add_to_end(&addr_policy_table, entry);
    }

    return 0;
}

int_fast8_t addr_policy_table_delete_entry(const uint8_t *prefix, uint8_t len)
{
    ns_list_foreach(addr_policy_table_entry_t, entry, &addr_policy_table) {
        if (entry->prefix_len == len && bitsequal(entry->prefix, prefix, len)) {
            ns_list_remove(&addr_policy_table, entry);
            ns_dyn_mem_free(entry);
            return 0;
        }
    }

    return -1;
}
/// @TODO do we need this test print anymore ?
void addr_policy_table_print(void)
{
    ns_list_foreach(addr_policy_table_entry_t, entry, &addr_policy_table) {
        char addr[40];
        ip6tos(entry->prefix, addr);
        tr_debug("%3d %3d %s/%u\n", entry->precedence, entry->label, addr, entry->prefix_len);
    }
}

static void addr_policy_table_reset(void)
{
    ns_list_foreach_safe(addr_policy_table_entry_t, entry, &addr_policy_table) {
        ns_list_remove(&addr_policy_table, entry);
        ns_dyn_mem_free(entry);
    }

    /* Default policy table from RFC 6724 */
    addr_policy_table_add_entry(ADDR_LOOPBACK,                         128, 50 , 0);
    addr_policy_table_add_entry(NULL,                                    0, 40 , 1);
    addr_policy_table_add_entry(ADDR_IPV4_MAPPED_PREFIX,                96, 35,  4);
    addr_policy_table_add_entry((const uint8_t[]) { 0x20, 0x02 },       16, 30,  2);
    addr_policy_table_add_entry((const uint8_t[]) { 0x20, 0x01, 0, 0 }, 32,  5,  5);
    addr_policy_table_add_entry((const uint8_t[]) { 0xfc },              7,  3, 13);
    addr_policy_table_add_entry(ADDR_IPV4_COMPATIBLE,                   96,  1,  3);
    addr_policy_table_add_entry((const uint8_t[]) { 0xfe, 0xc0 },       10,  1, 11);
    addr_policy_table_add_entry((const uint8_t[]) { 0x3f, 0xfe },       16,  1, 12);
    //addr_policy_table_print();
}

static const addr_policy_table_entry_t *addr_get_policy(const uint8_t addr[static 16])
{
    ns_list_foreach(const addr_policy_table_entry_t, entry, &addr_policy_table) {
        if (bitsequal(entry->prefix, addr, entry->prefix_len)) {
            return entry;
        }
    }

    /* Shouldn't happen - should always have a default entry */
    return NULL;
}

/* RFC 6724 CommonPrefixLen(S, D) */
static uint_fast8_t addr_common_prefix_len(const uint8_t src[static 16], uint_fast8_t src_prefix_len, const uint8_t dst[static 16])
{
    uint_fast8_t common = 0;

    while (src_prefix_len >= 8 && *src == *dst) {
        common += 8;
        src_prefix_len -= 8;
        ++src;
        ++dst;
    }

    if (src_prefix_len) {
        uint8_t trail = common_count_leading_zeros_8(*src ^ *dst);
        if (trail > src_prefix_len) {
            trail = src_prefix_len;
        }
        common += trail;
    }

    return common;
}

if_address_entry_t *addr_get_entry(const protocol_interface_info_entry_t *interface, const uint8_t addr[static 16])
{
    ns_list_foreach(if_address_entry_t, entry, &interface->ip_addresses) {
        if (addr_ipv6_equal(entry->address, addr)) {
            return entry;
        }
    }

    return NULL;
}

bool addr_is_assigned_to_interface(const protocol_interface_info_entry_t *interface, const uint8_t addr[static 16])
{
    if_address_entry_t *entry = addr_get_entry(interface, addr);
    return entry && !entry->tentative;
}

bool addr_is_tentative_for_interface(const protocol_interface_info_entry_t *interface, const uint8_t addr[static 16])
{
    if_address_entry_t *entry = addr_get_entry(interface, addr);
    return entry && entry->tentative;
}

if_group_entry_t *addr_add_group(protocol_interface_info_entry_t *interface, const uint8_t group[static 16])
{
    if_group_entry_t *entry = addr_get_group_entry(interface, group);
    if (entry) {
        if (entry->ref_count != 0xFFFF) {
            entry->ref_count++;
        }
        return entry;
    }

    if (!addr_is_ipv6_multicast(group)) {
        return NULL;
    }

    if (addr_am_implicit_group_member(group)) {
        return NULL;
    }

    entry = ns_dyn_mem_alloc(sizeof(if_group_entry_t));
    if (!entry) {
        return NULL;
    }
    memcpy(entry->group, group, 16);
    entry->ref_count = 1;
    ns_list_add_to_end(&interface->ip_groups, entry);

    mld_start_listening(interface, entry);

    return entry;
}

/* This does reference count */
void addr_remove_group(protocol_interface_info_entry_t *interface, const uint8_t group[static 16])
{
    if_group_entry_t *entry = addr_get_group_entry(interface, group);
    if (entry) {
        if (entry->ref_count != 0xFFFF) {
            if (--entry->ref_count == 0) {
                addr_delete_group_entry(interface, entry);
            }
        }
    }
}

/* This does NOT reference count - it actually deletes the entry */
void addr_delete_group_entry(protocol_interface_info_entry_t *interface, if_group_entry_t *entry)
{
    mld_stop_listening(interface, entry);
    ns_list_remove(&interface->ip_groups, entry);
    ns_dyn_mem_free(entry);
}

void addr_delete_group(protocol_interface_info_entry_t *interface, const uint8_t group[static 16])
{
    if_group_entry_t *entry = addr_get_group_entry(interface, group);
    if (entry) {
        addr_delete_group_entry(interface, entry);
    }
}

if_group_entry_t *addr_get_group_entry(const protocol_interface_info_entry_t *interface, const uint8_t group[static 16])
{
    ns_list_foreach(if_group_entry_t, entry, &interface->ip_groups) {
        if (addr_ipv6_equal(entry->group, group)) {
            return entry;
        }
    }

    return NULL;
}

static void addr_generate_solicited_node_group(uint8_t group[static 16], const uint8_t addr[static 16])
{
    memcpy(group, ADDR_MULTICAST_SOLICITED, 13);
    memcpy(group + 13, addr + 13, 3);
}

static if_group_entry_t *addr_add_solicited_node_group(protocol_interface_info_entry_t *interface, const uint8_t address[static 16])
{
    uint8_t group[16];
    addr_generate_solicited_node_group(group, address);
    return addr_add_group(interface, group);
}

static void addr_remove_solicited_node_group(protocol_interface_info_entry_t *interface, const uint8_t address[static 16])
{
    uint8_t group[16];
    addr_generate_solicited_node_group(group, address);
    addr_remove_group(interface, group);
}

void addr_add_router_groups(protocol_interface_info_entry_t *interface)
{
    /* The standard IPv6 ones, but not "Realm-Local-All-Routers"
     * which is ZigBee IP / Thread-specific (and not IANA registered)
     */
    addr_add_group(interface, ADDR_IF_LOCAL_ALL_ROUTERS);
    addr_add_group(interface, ADDR_LINK_LOCAL_ALL_ROUTERS);

    /* We only want to join the site address on one interface per site zone,
     * or we'd get multiple copies of packets. Exit if we're already a member.
     */
    ns_list_foreach(protocol_interface_info_entry_t, i, &protocol_interface_info_list) {
        if (i->zone_index[IPV6_SCOPE_SITE_LOCAL] == interface->zone_index[IPV6_SCOPE_SITE_LOCAL] &&
                addr_get_group_entry(i, ADDR_SITE_LOCAL_ALL_ROUTERS)) {
            return;
        }
    }
    addr_add_group(interface, ADDR_SITE_LOCAL_ALL_ROUTERS);
}

bool addr_am_group_member_on_interface(const protocol_interface_info_entry_t *interface, const uint8_t group[static 16])
{
    return addr_am_implicit_group_member(group) || addr_get_group_entry(interface, group);
}

/* RFC 6724 Default source address selection */
const uint8_t *addr_select_source(protocol_interface_info_entry_t *interface, const uint8_t dest[static 16], uint32_t addr_preferences)
{
    /* Let's call existing preferred address "SA" and new candidate "SB", to
     * make it look like a bit like RFC 6724
     */
    if_address_entry_t *SA = NULL;
    uint_fast8_t scope_D = addr_ipv6_scope(dest, interface);
    const addr_policy_table_entry_t *policy_D = addr_get_policy(dest);

    if (addr_preferences == 0) {
        addr_preferences = addr_preferences_default;
    }

    /*
     * As we go around the loop, if we prefer the new "SB", we set SA to SB
     * and continue. If we decide we prefer the existing SA, we just continue.
     *
     * Careful with these macros - they must only be used with if/else, and
     * inside { }, as per the coding style rules.
     */
#define PREFER_SA continue
#define PREFER_SB SA = SB; continue

    ns_list_foreach(if_address_entry_t, SB, &interface->ip_addresses) {
        /* Ignore tentative addresses */
        if (SB->tentative) {
            continue;
        }

        /* First (non-tentative) address seen becomes SA */
        if (!SA) {
            PREFER_SB;
        }

        /* Rule 1: Prefer same address */
        if (memcmp(SB->address, dest, 16) == 0) {
            SA = SB;
            /* It's an exact match, no point checking any other addresses */
            break;
        }

        /* Rule 2: Prefer appropriate scope */
        uint_fast8_t scope_SA = addr_ipv6_scope(SA->address, interface);
        uint_fast8_t scope_SB = addr_ipv6_scope(SB->address, interface);
        if (scope_SA < scope_SB) {
            if (scope_SA < scope_D) {
                PREFER_SB;
            } else {
                PREFER_SA;
            }
        } else if (scope_SB < scope_SA) {
            if (scope_SB < scope_D) {
                PREFER_SA;
            } else {
                PREFER_SB;
            }
        }

        /* Rule 3: Avoid deprecated addresses */
        if (SA->preferred_lifetime != 0 && SB->preferred_lifetime == 0) {
            PREFER_SA;
        } else if (SB->preferred_lifetime != 0 && SA->preferred_lifetime == 0) {
            PREFER_SB;
        }

        /* (Rule 4: Prefer home addresses - Mobile IPv6 not implemented) */
        /* (Rule 5: Prefer outgoing interface - candidate set already limited) */
        /* (Rule 5.5: Prefer addresses in a prefix advertised by the next-hop - we don't track this information) */

        /* Rule 6: Prefer matching label */
        const addr_policy_table_entry_t *policy_SA = addr_get_policy(SA->address);
        const addr_policy_table_entry_t *policy_SB = addr_get_policy(SB->address);
        if (policy_SA->label == policy_D->label && policy_SB->label != policy_D->label) {
            PREFER_SA;
        } else if (policy_SB->label == policy_D->label && policy_SA->label != policy_D->label) {
            PREFER_SB;
        }

        /* Rule 7: Prefer temporary addresses (or the opposite) */
        if (SA->temporary != SB->temporary) {
            bool prefer_public = (addr_preferences & SOCKET_IPV6_PREFER_SRC_PUBLIC);

            if (SA->temporary != prefer_public) {
                PREFER_SA;
            } else {
                PREFER_SB;
            }
        }

        /* Rule 8: Use longest matching prefix */
        uint_fast8_t common_SA = addr_common_prefix_len(SA->address, SA->prefix_len, dest);
        uint_fast8_t common_SB = addr_common_prefix_len(SB->address, SB->prefix_len, dest);
        if (common_SA > common_SB) {
            PREFER_SA;
        } else if (common_SB > common_SA) {
            PREFER_SB;
        }

        /* A tie-breaker: Prefer 6LoWPAN short address (or the opposite) */
        bool short_SA = SA->prefix_len == 64 && memcmp(SA->address + 8, ADDR_SHORT_ADR_SUFFIC, 6) == 0;
        bool short_SB = SB->prefix_len == 64 && memcmp(SB->address + 8, ADDR_SHORT_ADR_SUFFIC, 6) == 0;
        if (short_SA != short_SB) {
            bool prefer_short = (addr_preferences & SOCKET_IPV6_PREFER_SRC_6LOWPAN_SHORT);

            if (short_SA == prefer_short) {
                PREFER_SA;
            } else {
                PREFER_SB;
            }
        }

        /* Tie */
        PREFER_SA;
    }

    return SA ? SA->address : NULL;
}

/* A variant of RFC 6724 Default source address selection, to select an address
 * on an interface with a specific prefix. The prefix must match, and some
 * source address rules don't apply, but some are handled similarly. See
 * comments in addr_select_source.
 */
const uint8_t *addr_select_with_prefix(protocol_interface_info_entry_t *cur, const uint8_t *prefix, uint8_t prefix_len, uint32_t addr_preferences)
{
    if_address_entry_t *SA = NULL;

    if (addr_preferences == 0) {
        addr_preferences = addr_preferences_default;
    }

    ns_list_foreach(if_address_entry_t, SB, &cur->ip_addresses) {
        /* Ignore tentative addresses */
        if (SB->tentative) {
            continue;
        }

        /* Prefix must match */
        if (!bitsequal(SB->address, prefix, prefix_len)) {
            continue;
        }

        /* First (non-tentative, matching prefix) address seen becomes SA */
        if (!SA) {
            PREFER_SB;
        }

        /* (Rule 1: Prefer same address - doesn't apply here) */
        /* Rule 2: Was prefer appropriate scope - for this purpose we instead prefer wider scope */
        uint_fast8_t scope_SA = addr_ipv6_scope(SA->address, cur);
        uint_fast8_t scope_SB = addr_ipv6_scope(SB->address, cur);
        if (scope_SA < scope_SB) {
            PREFER_SB;
        } else if (scope_SB < scope_SA) {
            PREFER_SA;
        }

        /* Rule 3: Avoid deprecated addresses */
        if (SA->preferred_lifetime != 0 && SB->preferred_lifetime == 0) {
            PREFER_SA;
        }
        else if (SB->preferred_lifetime != 0 && SA->preferred_lifetime == 0) {
            PREFER_SB;
        }

        /* (Rule 4: Prefer home addresses - Mobile IPv6 not implemented) */
        /* (Rule 5: Prefer outgoing interface - candidate set already limited) */
        /* (Rule 5.5: Prefer addresses in a prefix advertised by the next-hop - we don't track this information) */

        /* Rule 6: Prefer matching label - doesn't apply here. But instead,
         * let's use precedence, like rule 6 of destination selection.
         */
        const addr_policy_table_entry_t *policy_SA = addr_get_policy(SA->address);
        const addr_policy_table_entry_t *policy_SB = addr_get_policy(SB->address);
        if (policy_SA->precedence > policy_SB->precedence) {
            PREFER_SA;
        } else if (policy_SB->precedence > policy_SA->precedence) {
            PREFER_SB;
        }

        /* Rule 7: Prefer temporary addresses (or the opposite) */
        if (SA->temporary != SB->temporary) {
            bool prefer_public = (addr_preferences & SOCKET_IPV6_PREFER_SRC_PUBLIC);

            if (SA->temporary != prefer_public) {
                PREFER_SA;
            } else {
                PREFER_SB;
            }
        }

        /* (Rule 8: Use longest matching prefix - doesn't apply) */

        /* A tie-breaker: Prefer 6LoWPAN short address (or the opposite) */
        bool short_SA = SA->prefix_len == 64 && memcmp(SA->address + 8, ADDR_SHORT_ADR_SUFFIC, 6) == 0;
        bool short_SB = SB->prefix_len == 64 && memcmp(SB->address + 8, ADDR_SHORT_ADR_SUFFIC, 6) == 0;
        if (short_SA != short_SB) {
            bool prefer_short = (addr_preferences & SOCKET_IPV6_PREFER_SRC_6LOWPAN_SHORT);

            if (short_SA == prefer_short) {
                PREFER_SA;
            } else {
                PREFER_SB;
            }
        }

        /* Tie */
        PREFER_SA;
    }

    return SA ? SA->address : NULL;
}

#undef PREFER_SA
#undef PREFER_SB

void addr_delete_entry(protocol_interface_info_entry_t *cur, if_address_entry_t *addr)
{
    if (addr->group_added) {
        addr_remove_solicited_node_group(cur, addr->address);
    }
    ns_list_remove(&cur->ip_addresses, addr);
    addr_cb(cur, addr, ADDR_CALLBACK_DELETED);
    ns_dyn_mem_free(addr);
}

/* ticks is in 1/10s */
void addr_fast_timer(protocol_interface_info_entry_t *cur, uint_fast16_t ticks)
{
    /* Fast timers only run while the interface is active. */
    if (!(cur->lowpan_info & INTERFACE_NWK_ACTIVE)) {
        return;
    }

    ns_list_foreach_safe(if_address_entry_t, addr, &cur->ip_addresses) {
        if (addr->state_timer == 0) {
            continue;
        }

        if (addr->state_timer > ticks) {
            addr->state_timer -= ticks;
            continue;
        }

        addr->state_timer = 0;
        /* Advance DAD state machine if tentative, else give it to the protocol state machine */
        if (addr->tentative) {
            if (addr->count == 0) {
#if 0
                // Initial join delay finished
                // We don't have full MLD support - send 1 report for now
                // This will become a real "join" later
                buffer_t *mld_buf;
                uint8_t sol_addr[16];
                memcpy(sol_addr, ADDR_MULTICAST_SOLICITED, 13);
                memcpy(sol_addr + 13, addr->address + 13, 3);
                mld_buf = mld_build(cur, NULL, ICMPV6_TYPE_INFO_MCAST_LIST_REPORT, 0, sol_addr);
                protocol_push(mld_buf);
#endif
                if (addr_add_solicited_node_group(cur, addr->address)) {
                    addr->group_added = true;
                }
            }
#ifdef HAVE_IPV6_ND
            if (addr->count >= cur->dup_addr_detect_transmits) {
                /* Finished - if we've not been nerfed already, we can transition
                 * to non-tentative.
                 */
                addr->tentative = false;
                addr->count = 0;
                tr_info("DAD passed on IF %d: %s", cur->id, trace_ipv6(addr->address));
                addr_cb(cur, addr, ADDR_CALLBACK_DAD_COMPLETE);
            } else {
                buffer_t *ns_buf = icmpv6_build_ns(cur, addr->address, NULL, false, true, NULL);
                protocol_push(ns_buf);
                addr->state_timer = (cur->ipv6_neighbour_cache.retrans_timer + 50) / 100; // ms -> ticks
                addr->count++;
            }
#endif
        } else {
            addr_cb(cur, addr, ADDR_CALLBACK_TIMER);
        }

        /* If a callback has shut down the interface, break now - this isn't
         * just a nicety; it avoids an iteration failure if shutdown disrupted
         * the address list (as is likely).
         */
        if (!(cur->lowpan_info & INTERFACE_NWK_ACTIVE)) {
            break;
        }
    }
}

void addr_slow_timer(protocol_interface_info_entry_t *cur, uint_fast16_t seconds)
{
    /* Slow (lifetime) timers run whether the interface is active or not */
    ns_list_foreach_safe(if_address_entry_t, addr, &cur->ip_addresses) {
        if (addr->preferred_lifetime != 0xffffffff && addr->preferred_lifetime != 0) {
            if (addr->preferred_lifetime > seconds) {
                addr->preferred_lifetime -= seconds;
            } else {
                tr_info("Address deprecated: %s", trace_ipv6(addr->address));
                addr->preferred_lifetime = 0;
                addr_cb(cur, addr, ADDR_CALLBACK_DEPRECATED);
            }
        }

        if (addr->valid_lifetime != 0xffffffff) {
            if (addr->valid_lifetime > seconds) {
                addr->valid_lifetime -= seconds;
            } else {
                tr_info("Address invalidated: %s", trace_ipv6(addr->address));
                addr->valid_lifetime = 0;
                addr_cb(cur, addr, ADDR_CALLBACK_INVALIDATED);
                /* Give them the chance to revalidate */
                if (addr->valid_lifetime == 0) {
                    addr_delete_entry(cur, addr);
                }
            }
        }
    }

#ifdef MULTICAST_FORWARDING
    ns_list_foreach_safe(if_group_fwd_entry_t, group, &cur->ip_groups_fwd) {
        if (group->lifetime != 0xffffffff) {
            if (group->lifetime > seconds) {
                group->lifetime -= seconds;
            } else {
                tr_debug("MC fwd expired: %s", trace_ipv6(group->group));
                addr_multicast_fwd_delete_entry(cur, group);
            }
        }
    }
#endif
}

static void addr_max_entries_check(protocol_interface_info_entry_t *cur, if_address_source_t source)
{
    // Limit only auto configuration addresses
    if (source != ADDR_SOURCE_SLAAC || cur->ip_addresses_max_slaac_entries == 0) {
        return;
    }

    uint8_t count = 0;
    if_address_entry_t *first_slaac_entry = NULL;
    ns_list_foreach(if_address_entry_t, e, &cur->ip_addresses) {
        if (e->source == ADDR_SOURCE_SLAAC) {
            if (!first_slaac_entry) {
                first_slaac_entry = e;
            }
            if (++count == 0xff) {
                break;
            }
        }
    }

    if (count > cur->ip_addresses_max_slaac_entries) {
        addr_delete_entry(cur, first_slaac_entry);
    }
}

void addr_max_slaac_entries_set(protocol_interface_info_entry_t *cur, uint8_t max_slaac_entries)
{
    cur->ip_addresses_max_slaac_entries = max_slaac_entries;
}

if_address_entry_t *addr_add(protocol_interface_info_entry_t *cur, const uint8_t address[static 16], uint_fast8_t prefix_len, if_address_source_t source, uint32_t valid_lifetime, uint32_t preferred_lifetime, bool skip_dad)
{
    if (addr_get_entry(cur, address)) {
        return NULL;
    }

    addr_max_entries_check(cur, source);

    if_address_entry_t *entry = ns_dyn_mem_alloc(sizeof(if_address_entry_t));
    if (!entry) {
        return NULL;
    }

    memset(entry, 0, sizeof *entry);
    entry->cb = NULL;
    memcpy(entry->address, address, 16);
    entry->prefix_len = prefix_len;
    entry->source = source;
    entry->valid_lifetime = valid_lifetime;
    entry->preferred_lifetime = preferred_lifetime;
    entry->group_added = false;
#ifndef HAVE_IPV6_ND
    skip_dad = true;
#endif
    if (skip_dad || cur->dup_addr_detect_transmits == 0) {
        entry->tentative = false;
        if (addr_add_solicited_node_group(cur, entry->address)) {
            entry->group_added = true;
        }
        // XXX not right? Want to do delay + MLD join, so don't special-case?
        /* entry->cb isn't set yet, but global notifiers will want call */
        addr_cb(cur, entry, ADDR_CALLBACK_DAD_COMPLETE);
    } else {
        entry->tentative = true;
        // Initial timer is for the multicast join delay
        entry->count = 0;
        entry->state_timer = randLIB_get_random_in_range(1, 10); // MAX_RTR_SOLICITATION_DELAY (1s) in ticks
    }

    tr_info("%sAddress added to IF %d: %s", (entry->tentative ? "Tentative " : ""), cur->id, trace_ipv6(address));

    ns_list_add_to_end(&cur->ip_addresses, entry);

    return entry;
}

int_fast8_t addr_delete(protocol_interface_info_entry_t *cur, const uint8_t address[static 16])
{
    ns_list_foreach(if_address_entry_t, e, &cur->ip_addresses) {
        if (memcmp(e->address, address, 16) == 0) {
            addr_delete_entry(cur, e);
            return 0;
        }
    }

    return -1;
}

void addr_delete_matching(protocol_interface_info_entry_t *cur, const uint8_t *prefix, uint8_t prefix_len, if_address_source_t source)
{
    ns_list_foreach_safe(if_address_entry_t, e, &cur->ip_addresses) {
        if ((source == ADDR_SOURCE_UNKNOWN || e->source == source) &&
                bitsequal(e->address, prefix, prefix_len)) {
            addr_delete_entry(cur, e);
        }
    }

}

void addr_set_non_preferred(protocol_interface_info_entry_t *cur, if_address_source_t source)
{
    ns_list_foreach_safe(if_address_entry_t, e, &cur->ip_addresses) {
        if (e->source == source) {
            // Sets preferred lifetime to zero or deletes tentative addresses
            addr_set_preferred_lifetime(cur, e, 0);
        }
    }
}

void addr_duplicate_detected(struct protocol_interface_info_entry *interface, const uint8_t addr[static 16])
{
    if_address_entry_t *entry = addr_get_entry(interface, addr);
    if (!entry) {
        return;
    }

    tr_warn("DAD failed: %s", trace_ipv6(addr));

    interface->dad_failures++;
    addr_cb(interface, entry, ADDR_CALLBACK_DAD_FAILED);

    /* Callback may have done something drastic like shut down the interface.
     * Don't assume entry is still valid - remove it by IP address.
     */
    addr_delete(interface, addr);
}

void addr_notification_register(if_address_notification_fn *fn)
{
    ns_list_foreach(addr_notification_t, n, &addr_notifications) {
        if (n->fn == fn) {
            return;
        }
    }

    addr_notification_t *n = ns_dyn_mem_alloc(sizeof(addr_notification_t));
    if (!n) {
        tr_error("addr_notification_register mem");
        return;
    }
    n->fn = fn;
    ns_list_add_to_end(&addr_notifications, n);
}

void addr_cb(protocol_interface_info_entry_t *interface, if_address_entry_t *addr, if_address_callback_t reason)
{
    ns_list_foreach(addr_notification_t, n, &addr_notifications) {
        n->fn(interface, addr, reason);
    }
    if (addr->cb) {
        addr->cb(interface, addr, reason);
    }
}

void addr_set_valid_lifetime(protocol_interface_info_entry_t *interface, if_address_entry_t *addr, uint32_t valid_lifetime)
{
    if (valid_lifetime != addr->valid_lifetime) {
        addr->valid_lifetime = valid_lifetime;
        addr_cb(interface, addr, ADDR_CALLBACK_REFRESHED);
    }
}

void addr_set_preferred_lifetime(protocol_interface_info_entry_t *interface, if_address_entry_t *addr, uint32_t preferred_lifetime)
{
    if (preferred_lifetime != addr->preferred_lifetime) {
        addr->preferred_lifetime = preferred_lifetime;
        if (preferred_lifetime == 0) {
            // Entry is deleted if it is tentative
            if (addr->tentative) {
                addr_delete_entry(interface, addr);
            } else {
                addr_cb(interface, addr, ADDR_CALLBACK_DEPRECATED);
            }
        }
    }
}

void memswap(uint8_t *restrict a, uint8_t *restrict b, uint_fast8_t len)
{
    while (len--) {
        uint8_t t = *a;
        *a++ = *b;
        *b++ = t;
    }
}

/* Optimised for quick discard of mismatching addresses (eg in a cache lookup):
 * searches BACKWARDS, as last bytes are most likely to differ.
 */
bool addr_ipv6_equal(const uint8_t a[static 16], const uint8_t b[static 16])
{
    for (int_fast8_t n = 15; n >= 0; n--) {
        if (a[n] != b[n]) {
            return false;
        }
    }
    return true;
}

bool addr_iid_matches_eui64(const uint8_t iid[static 8], const uint8_t eui64[static 8])
{
    for (int_fast8_t n = 7; n >= 1; n--) {
        if (iid[n] != eui64[n]) {
            return false;
        }
    }
    return iid[0] == (eui64[0] ^ 2);
}

bool addr_iid_matches_lowpan_short(const uint8_t iid[static 8], uint16_t short_addr)
{
    if (iid[7] != (short_addr & 0xFF) ||
            iid[6] != (short_addr >> 8)) {
        return false;
    }

    for (int_fast8_t n = 5; n >= 0; n--) {
        if (iid[n] != ADDR_SHORT_ADR_SUFFIC[n]) {
            return false;
        }
    }
    return true;
}

bool addr_iid_reserved(const uint8_t iid[static 8])
{
    static const uint8_t reserved_iana[5] = { 0x02, 0x00, 0x5e, 0xff, 0xfe };
    static const uint8_t reserved_subnet_anycast[7] = { 0xfd, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff };

    if (memcmp(iid, ADDR_UNSPECIFIED + 8, 8) == 0) {
        return true; // subnet-router anycast
    }

    if (memcmp(iid, reserved_iana, 5) == 0) {
        return true;
    }

    if (memcmp(iid, reserved_subnet_anycast, 7) == 0 && iid[7] >= 0x80) {
        return true;
    }

    return false;
}

int_fast8_t addr_opaque_iid_key_set(const void *secret_key, uint8_t key_len)
{
    /* Delete existing info */
    if (addr_iid_secret_key) {
        ns_dyn_mem_free((void *) addr_iid_secret_key);
        addr_iid_secret_key = NULL;
        addr_iid_secret_key_len = 0;
    }

    /* If disabling, that's it */
    if (secret_key == NULL) {
        return 0;
    }
    /* Attempt to copy new info */
    uint8_t *copy = ns_dyn_mem_alloc(key_len);
    if (!copy) {
        return -1;
    }
    addr_iid_secret_key = memcpy(copy, secret_key, key_len);
    addr_iid_secret_key_len = key_len;
    return 0;
}

int_fast8_t addr_opaque_initial_iid_set(const void *iid)
{
    /* Delete existing info */
    if (addr_initial_iid) {
        ns_dyn_mem_free((void *) addr_initial_iid);
        addr_initial_iid = NULL;
    }
    if (!iid) {
        return 0;
    }
    /* Attempt to copy new info */
    uint8_t *copy = ns_dyn_mem_alloc(8);
    if (!copy) {
        return -1;
    }
    addr_initial_iid = memcpy(copy, iid, 8);
    return 0;
}

bool addr_opaque_iid_key_is_set(void)
{
    return addr_iid_secret_key != NULL;
}

/* RFC 7217 generation: addr must be prepopulated with 8-byte prefix, and secret key must be set */
void addr_generate_opaque_iid(protocol_interface_info_entry_t *cur, uint8_t addr[static 16])
{
opaque_retry:

    if (addr_initial_iid && !cur->dad_failures) {
        // This is test implementations use only normally should not need this.
        memcpy(addr + 8,addr_initial_iid,8);
        return;
    }
    {
        // Limit scope to try to minimise stack, given the goto
        ns_sha256_context ctx;
        ns_sha256_init(&ctx);
        ns_sha256_starts(&ctx);
        ns_sha256_update(&ctx, addr, 8);
        if (cur->interface_name) {
            /* This isn't ideal - there's no guarantee each instance of a driver has a distinct name */
            ns_sha256_update(&ctx, cur->interface_name, strlen(cur->interface_name));
        } else {
            ns_sha256_update(&ctx, &cur->id, sizeof cur->id);
        }
        ns_sha256_update(&ctx, &cur->dad_failures, sizeof cur->dad_failures);
        ns_sha256_update(&ctx, addr_iid_secret_key, addr_iid_secret_key_len);
        ns_sha256_finish_nbits(&ctx, addr + 8, 64);
        ns_sha256_free(&ctx);
    }
    /* Note that we only check for reserved IIDs - as per RFC 7217,
     * there's no restriction on U/G bits.
     */
    if (addr_iid_reserved(addr + 8)) {
        cur->dad_failures++;
        goto opaque_retry;
    }
}

/* Write a LoWPAN IPv6 address, based on a prefix and short address */
uint8_t *addr_ipv6_write_from_lowpan_short(uint8_t dst[static 16], const uint8_t prefix[static 8], uint16_t short_addr)
{
    common_write_16_bit(short_addr, dst + 14);
    memcpy(dst + 8, ADDR_SHORT_ADR_SUFFIC, 6);
    return memcpy(dst, prefix, 8);
}

/* Turn an address (either MAC or IP) into a base IP address for context compression */
bool addr_iid_from_outer(uint8_t iid_out[static 8], const sockaddr_t *addr_in)
{
    switch (addr_in->addr_type) {
        case ADDR_802_15_4_LONG:
            memcpy(iid_out, addr_in->address + 2, 8);
            iid_out[0] ^= 2;
            break;
        case ADDR_BROADCAST:
        case ADDR_802_15_4_SHORT:
            memcpy(iid_out, ADDR_SHORT_ADR_SUFFIC, 6);
            iid_out[6] = addr_in->address[2];
            iid_out[7] = addr_in->address[3];
            break;
        case ADDR_IPV6:
            memcpy(iid_out, addr_in->address + 8, 8);
            break;
        default:
            return false;
    }

    return true;
}

int addr_interface_set_ll64(protocol_interface_info_entry_t *cur, if_address_callback_fn *cb)
{
    int ret_val = -1;
    if_address_entry_t *address_entry = NULL;
    uint8_t temp_ll64[16];
    memcpy(temp_ll64, ADDR_LINK_LOCAL_PREFIX, 8);
    memcpy(temp_ll64 + 8, cur->iid_eui64, 8);

    address_entry = addr_add(cur, temp_ll64, 64, ADDR_SOURCE_UNKNOWN, 0xffffffff, 0xffffffff, false);
    if (address_entry) {
        tr_debug("LL64 Register OK!");
        ret_val = 0;
        address_entry->cb = cb;
        if (!address_entry->tentative) {
            addr_cb(cur, address_entry, ADDR_CALLBACK_DAD_COMPLETE);
        }
    }
    return ret_val;
}

/* address_type 0 means "any" address - we return short by preference */
/* address_type 1 means long address - we ignore short addresses */
int8_t addr_interface_get_ll_address(protocol_interface_info_entry_t *cur, uint8_t *address_ptr, uint8_t address_type)
{
    const uint8_t *short_addr = NULL;
    const uint8_t *long_addr = NULL;

    if (!cur) {
        return -1;
    }

    ns_list_foreach(if_address_entry_t, e, &cur->ip_addresses) {
        if (!e->tentative && addr_is_ipv6_link_local(e->address)) {
            if (cur->nwk_id == IF_6LoWPAN && memcmp(e->address + 8, ADDR_SHORT_ADR_SUFFIC, 6) == 0) {
                short_addr = e->address;
            } else {
                long_addr = e->address;
            }

            if (long_addr && short_addr) {
                break;
            }
        }
    }

    if (short_addr && address_type != 1) {
        if (address_ptr) {
            memcpy(address_ptr, short_addr, 16);
        }
        return 0;
    } else if (long_addr) {
        if (address_ptr) {
            memcpy(address_ptr, long_addr, 16);
        }
        return 0;
    } else {
        return -1;
    }
}

bool addr_interface_all_address_ready(protocol_interface_info_entry_t *cur)
{
    if (!cur) {
        return false;
    }

    ns_list_foreach(if_address_entry_t, e, &cur->ip_addresses) {
        if (e->tentative) {
            return false;
        }
    }
    return true;
}

int8_t addr_interface_gp_prefix_compare(protocol_interface_info_entry_t *cur, const uint8_t *prefix)
{
    if (cur->global_address_available) {
        ns_list_foreach(if_address_entry_t, e, &cur->ip_addresses) {
            if (memcmp(e->address, prefix, 8) == 0) {
                return 0;
            }
        }
    }
    return -1;
}

int8_t addr_interface_address_compare(protocol_interface_info_entry_t *cur, const uint8_t *addr)
{
    /* First check the specified interface */
    if (addr_is_assigned_to_interface(cur, addr)) {
        return 0;
    }

    /* Then check other interfaces, enforcing scope zones */
    uint_fast8_t scope = addr_ipv6_scope(addr, cur);
    ns_list_foreach(protocol_interface_info_entry_t, other, &protocol_interface_info_list) {
        if (other != cur &&
                other->zone_index[scope] == cur->zone_index[scope] &&
                addr_is_assigned_to_interface(other, addr)) {
            // special handling for Thread - external global-scope ULA coming in,
            // which would match, but we need to restrict if that ULA is mesh-local
            // on the Thread side.
            if (thread_info(other) && addr_ipv6_scope(addr, other) <= IPV6_SCOPE_REALM_LOCAL) {
                continue;
            }
            return 0;
        }
    }

    return -1;
}

int8_t addr_interface_select_source(protocol_interface_info_entry_t *cur, uint8_t *src_ptr, const uint8_t *dest, uint32_t addr_preferences)
{
    int8_t ret_val = -1;
    if (cur) {
        const uint8_t *src = addr_select_source(cur, dest, addr_preferences);
        if (src) {
            memcpy(src_ptr, src, 16);
            ret_val = 0;
        }
    }
    return ret_val;
}

// This last function must always be compiled with tracing enabled
#ifndef FEA_TRACE_SUPPORT
#define FEA_TRACE_SUPPORT 1
#include "mbed-trace/mbed_trace.h"
#endif
char* trace_sockaddr(const sockaddr_t* addr, bool panid_prefix)
{
    uint8_t length = addr_len_from_type(addr->addr_type);

    if (length == 0) {
        return "<n/a>";
    }

    /* Awkward hack for 802.15.4 address types */
    if (addr->addr_type == ADDR_802_15_4_SHORT ||
        addr->addr_type == ADDR_802_15_4_LONG) {
        length -= (panid_prefix) ? 0 : 2;
    }

    /* Start from index 0 (prints PAN ID if exists) */
    return trace_array(&addr->address[0], length);
}
