mirror of https://github.com/ARMmbed/mbed-os.git
				
				
				
			
		
			
				
	
	
		
			1166 lines
		
	
	
		
			41 KiB
		
	
	
	
		
			C
		
	
	
			
		
		
	
	
			1166 lines
		
	
	
		
			41 KiB
		
	
	
	
		
			C
		
	
	
/*
 | 
						|
 * Copyright (c) 2015-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.
 | 
						|
 */
 | 
						|
#include "nsconfig.h"
 | 
						|
 | 
						|
#ifdef HAVE_MPL
 | 
						|
 | 
						|
#include "ns_types.h"
 | 
						|
#include "ns_list.h"
 | 
						|
#include "ns_trace.h"
 | 
						|
#include "common_functions.h"
 | 
						|
#include "nsdynmemLIB.h"
 | 
						|
#include "randLIB.h"
 | 
						|
#include <string.h>
 | 
						|
#include "Core/include/ns_buffer.h"
 | 
						|
#include "NWK_INTERFACE/Include/protocol.h"
 | 
						|
#include "NWK_INTERFACE/Include/protocol_timer.h"
 | 
						|
#include "Common_Protocols/ipv6.h"
 | 
						|
#include "Common_Protocols/icmpv6.h"
 | 
						|
#include "Service_Libs/Trickle/trickle.h"
 | 
						|
#include "6LoWPAN/MAC/mac_helper.h"
 | 
						|
#include "6LoWPAN/Thread/thread_common.h"
 | 
						|
#include "6LoWPAN/ws/ws_common.h"
 | 
						|
#include "MPL/mpl.h"
 | 
						|
 | 
						|
#define TRACE_GROUP "mpl"
 | 
						|
 | 
						|
#define MPL_OPT_S_MASK      0xC0
 | 
						|
#define MPL_OPT_S_SHIFT     6
 | 
						|
#define MPL_OPT_M           0x20
 | 
						|
#define MPL_OPT_V           0x10
 | 
						|
 | 
						|
#define MPL_SEED_IPV6_SRC   0
 | 
						|
#define MPL_SEED_16_BIT     1
 | 
						|
#define MPL_SEED_64_BIT     2
 | 
						|
#define MPL_SEED_128_BIT    3
 | 
						|
 | 
						|
#define MAX_BUFFERED_MESSAGES_SIZE 8192
 | 
						|
#define MAX_BUFFERED_MESSAGE_LIFETIME 600 // 1/10 s ticks
 | 
						|
 | 
						|
static bool mpl_timer_running;
 | 
						|
static uint16_t mpl_total_buffered;
 | 
						|
 | 
						|
const trickle_params_t rfc7731_default_data_message_trickle_params = {
 | 
						|
    .Imin = MPL_MS_TO_TICKS(512),   /* RFC 7731 says 10 * expected link latency; ZigBee IP says 512 ms */
 | 
						|
    .Imax = MPL_MS_TO_TICKS(512),   /* RFC 7731 says equal to Imin; ZigBee IP says 512 ms */
 | 
						|
    .k = 1,                         /* RFC 7731 says 1; ZigBee IP says infinite */
 | 
						|
    .TimerExpirations = 3           /* RFC 7731 says 3; ZigBee IP says 2 for routers, 0 for hosts */
 | 
						|
};
 | 
						|
 | 
						|
const trickle_params_t rfc7731_default_control_message_trickle_params = {
 | 
						|
    .Imin = MPL_MS_TO_TICKS(512),   /* RFC 7731 says 10 * worst-case link latency */
 | 
						|
    .Imax = MPL_MS_TO_TICKS(300000),/* 5 minutes, as per RFC 7731 */
 | 
						|
    .k = 1,
 | 
						|
    .TimerExpirations = 10
 | 
						|
};
 | 
						|
 | 
						|
/* Note that we don't use a buffer_t, to save a little RAM. We don't need
 | 
						|
 * any of the metadata it stores...
 | 
						|
 */
 | 
						|
typedef struct mpl_data_message {
 | 
						|
    bool running;
 | 
						|
    bool colour;
 | 
						|
    uint32_t timestamp;
 | 
						|
    trickle_t trickle;
 | 
						|
    ns_list_link_t link;
 | 
						|
    uint16_t mpl_opt_data_offset;   /* offset to option data of MPL option */
 | 
						|
    uint8_t message[];
 | 
						|
} mpl_buffered_message_t;
 | 
						|
 | 
						|
typedef struct mpl_seed {
 | 
						|
    ns_list_link_t link;
 | 
						|
    bool colour;
 | 
						|
    uint16_t lifetime;
 | 
						|
    uint8_t min_sequence;
 | 
						|
    uint8_t id_len;
 | 
						|
    NS_LIST_HEAD(mpl_buffered_message_t, link) messages; /* sequence number order */
 | 
						|
    uint8_t id[];
 | 
						|
} mpl_seed_t;
 | 
						|
 | 
						|
/* For simplicity, we assume each MPL domain is on exactly 1 interface */
 | 
						|
struct mpl_domain {
 | 
						|
    protocol_interface_info_entry_t *interface;
 | 
						|
    uint8_t address[16];
 | 
						|
    uint8_t sequence;
 | 
						|
    bool colour;
 | 
						|
    bool proactive_forwarding;
 | 
						|
    uint16_t seed_set_entry_lifetime;
 | 
						|
    NS_LIST_HEAD(mpl_seed_t, link) seeds;
 | 
						|
    trickle_t trickle;                      // Control timer
 | 
						|
    trickle_params_t data_trickle_params;
 | 
						|
    trickle_params_t control_trickle_params;
 | 
						|
    ns_list_link_t link;
 | 
						|
    multicast_mpl_seed_id_mode_e seed_id_mode;
 | 
						|
    uint8_t seed_id[];
 | 
						|
};
 | 
						|
 | 
						|
static NS_LIST_DEFINE(mpl_domains, mpl_domain_t, link);
 | 
						|
 | 
						|
static void mpl_buffer_delete(mpl_seed_t *seed, mpl_buffered_message_t *message);
 | 
						|
static void mpl_control_reset_or_start(mpl_domain_t *domain);
 | 
						|
static void mpl_schedule_timer(void);
 | 
						|
static void mpl_fast_timer(uint16_t ticks);
 | 
						|
static buffer_t *mpl_exthdr_provider(buffer_t *buf, ipv6_exthdr_stage_t stage, int16_t *result);
 | 
						|
static void mpl_seed_delete(mpl_domain_t *domain, mpl_seed_t *seed);
 | 
						|
 | 
						|
static bool mpl_initted;
 | 
						|
 | 
						|
static void mpl_init(void)
 | 
						|
{
 | 
						|
    if (mpl_initted) {
 | 
						|
        return;
 | 
						|
    }
 | 
						|
    mpl_initted = true;
 | 
						|
 | 
						|
    ipv6_set_exthdr_provider(ROUTE_MPL, mpl_exthdr_provider);
 | 
						|
}
 | 
						|
 | 
						|
static uint8_t mpl_buffer_sequence(const mpl_buffered_message_t *message)
 | 
						|
{
 | 
						|
    return message->message[message->mpl_opt_data_offset + 1];
 | 
						|
}
 | 
						|
 | 
						|
static uint16_t mpl_buffer_size(const mpl_buffered_message_t *message)
 | 
						|
{
 | 
						|
    return IPV6_HDRLEN + common_read_16_bit(message->message + IPV6_HDROFF_PAYLOAD_LENGTH);
 | 
						|
}
 | 
						|
 | 
						|
mpl_domain_t *mpl_domain_lookup(protocol_interface_info_entry_t *cur, const uint8_t address[16])
 | 
						|
{
 | 
						|
    ns_list_foreach(mpl_domain_t, domain, &mpl_domains) {
 | 
						|
        if (domain->interface == cur && addr_ipv6_equal(domain->address, address)) {
 | 
						|
            return domain;
 | 
						|
        }
 | 
						|
    }
 | 
						|
    return NULL;
 | 
						|
}
 | 
						|
 | 
						|
mpl_domain_t *mpl_domain_lookup_with_realm_check(protocol_interface_info_entry_t *cur, const uint8_t address[16])
 | 
						|
{
 | 
						|
    if (!addr_is_ipv6_multicast(address)) {
 | 
						|
        return NULL;
 | 
						|
    }
 | 
						|
 | 
						|
    if (addr_ipv6_multicast_scope(address) == IPV6_SCOPE_REALM_LOCAL && cur->mpl_treat_realm_domains_as_one) {
 | 
						|
        address = ADDR_ALL_MPL_FORWARDERS;
 | 
						|
    }
 | 
						|
 | 
						|
    return mpl_domain_lookup(cur, address);
 | 
						|
}
 | 
						|
 | 
						|
/* Look up domain by address, ignoring the scop field, so ff22::1 matches ff23::1 */
 | 
						|
/* We assume all addresses are multicast, so don't bother checking the first byte */
 | 
						|
static mpl_domain_t *mpl_domain_lookup_ignoring_scop(protocol_interface_info_entry_t *cur, const uint8_t address[16])
 | 
						|
{
 | 
						|
    ns_list_foreach(mpl_domain_t, domain, &mpl_domains) {
 | 
						|
        if (domain->interface == cur &&
 | 
						|
                memcmp(address + 2, domain->address + 2, 14) == 0 &&
 | 
						|
                (address[1] & 0xf0) == (domain->address[1] & 0xf0)) {
 | 
						|
            return domain;
 | 
						|
        }
 | 
						|
    }
 | 
						|
    return NULL;
 | 
						|
}
 | 
						|
 | 
						|
static int mpl_domain_count_on_interface(protocol_interface_info_entry_t *cur)
 | 
						|
{
 | 
						|
    int count = 0;
 | 
						|
    ns_list_foreach(mpl_domain_t, domain, &mpl_domains) {
 | 
						|
        if (domain->interface == cur) {
 | 
						|
            count++;
 | 
						|
        }
 | 
						|
    }
 | 
						|
    return count;
 | 
						|
}
 | 
						|
 | 
						|
mpl_domain_t *mpl_domain_create(protocol_interface_info_entry_t *cur, const uint8_t address[16],
 | 
						|
                                const uint8_t *seed_id, multicast_mpl_seed_id_mode_e seed_id_mode,
 | 
						|
                                int_fast8_t proactive_forwarding,
 | 
						|
                                uint16_t seed_set_entry_lifetime,
 | 
						|
                                const trickle_params_t *data_trickle_params,
 | 
						|
                                const trickle_params_t *control_trickle_params)
 | 
						|
{
 | 
						|
    if (!addr_is_ipv6_multicast(address) || addr_ipv6_multicast_scope(address) < IPV6_SCOPE_REALM_LOCAL) {
 | 
						|
        return NULL;
 | 
						|
    }
 | 
						|
 | 
						|
    if (addr_ipv6_multicast_scope(address) == IPV6_SCOPE_REALM_LOCAL && cur->mpl_treat_realm_domains_as_one &&
 | 
						|
            !addr_ipv6_equal(address, ADDR_ALL_MPL_FORWARDERS)) {
 | 
						|
        return NULL;
 | 
						|
    }
 | 
						|
 | 
						|
    mpl_init();
 | 
						|
 | 
						|
    /* We lock out attempts to join two domains differing only by scop - this
 | 
						|
     * is because we couldn't distinguish control messages, which are sent
 | 
						|
     * to the link-local version of the same address. Seems to be a
 | 
						|
     * specification limitation?
 | 
						|
     */
 | 
						|
    if (mpl_domain_lookup_ignoring_scop(cur, address)) {
 | 
						|
        return NULL;
 | 
						|
    }
 | 
						|
 | 
						|
    if (seed_id_mode == MULTICAST_MPL_SEED_ID_DEFAULT) {
 | 
						|
        seed_id_mode = cur->mpl_seed_id_mode;
 | 
						|
        seed_id = cur->mpl_seed_id;
 | 
						|
    }
 | 
						|
 | 
						|
    uint8_t seed_id_len;
 | 
						|
    if (seed_id_mode > 0) {
 | 
						|
        seed_id_len = seed_id_mode;
 | 
						|
    } else {
 | 
						|
        seed_id_len = 0;
 | 
						|
    }
 | 
						|
 | 
						|
    mpl_domain_t *domain = ns_dyn_mem_alloc(sizeof * domain + seed_id_len);
 | 
						|
    if (!domain) {
 | 
						|
        return NULL;
 | 
						|
    }
 | 
						|
    memcpy(domain->address, address, 16);
 | 
						|
    domain->interface = cur;
 | 
						|
    domain->sequence = randLIB_get_8bit();
 | 
						|
    domain->colour = false;
 | 
						|
    ns_list_init(&domain->seeds);
 | 
						|
    domain->proactive_forwarding = proactive_forwarding >= 0 ? proactive_forwarding
 | 
						|
                                   : cur->mpl_proactive_forwarding;
 | 
						|
    domain->seed_set_entry_lifetime = seed_set_entry_lifetime ? seed_set_entry_lifetime
 | 
						|
                                      : cur->mpl_seed_set_entry_lifetime;
 | 
						|
    domain->data_trickle_params = data_trickle_params ? *data_trickle_params
 | 
						|
                                  : cur->mpl_data_trickle_params;
 | 
						|
    domain->control_trickle_params = control_trickle_params ? *control_trickle_params
 | 
						|
                                     : cur->mpl_control_trickle_params;
 | 
						|
    trickle_start(&domain->trickle, &domain->control_trickle_params);
 | 
						|
    trickle_stop(&domain->trickle);
 | 
						|
    domain->seed_id_mode = seed_id_mode;
 | 
						|
    memcpy(domain->seed_id, seed_id, seed_id_len);
 | 
						|
    ns_list_add_to_end(&mpl_domains, domain);
 | 
						|
 | 
						|
    //ipv6_route_add_with_info(address, 128, cur->id, NULL, ROUTE_MPL, domain, 0, 0xffffffff, 0);
 | 
						|
    addr_add_group(cur, address);
 | 
						|
    if (domain->control_trickle_params.TimerExpirations != 0) {
 | 
						|
        uint8_t ll_scope[16];
 | 
						|
        memcpy(ll_scope, address, 16);
 | 
						|
        ll_scope[1] = (ll_scope[1] & 0xf0) | IPV6_SCOPE_LINK_LOCAL;
 | 
						|
        addr_add_group(cur, ll_scope);
 | 
						|
    }
 | 
						|
 | 
						|
    /* If we just created the first domain on an interface, auto-create the all-forwarders domain (this does nothing if we're already a member) */
 | 
						|
    if (mpl_domain_count_on_interface(cur) == 1) {
 | 
						|
        /* Use default interface parameters */
 | 
						|
        mpl_domain_create(cur, ADDR_ALL_MPL_FORWARDERS, NULL, MULTICAST_MPL_SEED_ID_DEFAULT, -1, 0, NULL, NULL);
 | 
						|
        cur->mpl_seed = true;
 | 
						|
    }
 | 
						|
 | 
						|
    return domain;
 | 
						|
}
 | 
						|
 | 
						|
bool mpl_domain_delete(protocol_interface_info_entry_t *cur, const uint8_t address[16])
 | 
						|
{
 | 
						|
    mpl_domain_t *domain = mpl_domain_lookup(cur, address);
 | 
						|
    if (!domain) {
 | 
						|
        return false;
 | 
						|
    }
 | 
						|
    int count = mpl_domain_count_on_interface(cur);
 | 
						|
 | 
						|
    /* Don't let them delete all-mpl-forwarders unless it's the last */
 | 
						|
    if (addr_ipv6_equal(address, ADDR_ALL_MPL_FORWARDERS)) {
 | 
						|
        if (count != 1) {
 | 
						|
            return true;
 | 
						|
        }
 | 
						|
        cur->mpl_seed = false;
 | 
						|
    }
 | 
						|
 | 
						|
    ns_list_foreach_safe(mpl_seed_t, seed, &domain->seeds) {
 | 
						|
        mpl_seed_delete(domain, seed);
 | 
						|
    }
 | 
						|
 | 
						|
    //ipv6_route_delete(address, 128, cur->id, NULL, ROUTE_MPL);
 | 
						|
    addr_delete_group(cur, address);
 | 
						|
    if (domain->control_trickle_params.TimerExpirations != 0) {
 | 
						|
        uint8_t ll_scope[16];
 | 
						|
        memcpy(ll_scope, domain->address, 16);
 | 
						|
        ll_scope[1] = (ll_scope[1] & 0xf0) | IPV6_SCOPE_LINK_LOCAL;
 | 
						|
        addr_delete_group(cur, ll_scope);
 | 
						|
    }
 | 
						|
    ns_list_remove(&mpl_domains, domain);
 | 
						|
    ns_dyn_mem_free(domain);
 | 
						|
    return true;
 | 
						|
}
 | 
						|
 | 
						|
void mpl_domain_change_timing(mpl_domain_t *domain, const struct trickle_params *data_trickle_params, uint16_t seed_set_entry_lifetime)
 | 
						|
{
 | 
						|
    domain->data_trickle_params = *data_trickle_params;
 | 
						|
    domain->seed_set_entry_lifetime = seed_set_entry_lifetime;
 | 
						|
}
 | 
						|
 | 
						|
static void mpl_domain_inconsistent(mpl_domain_t *domain)
 | 
						|
{
 | 
						|
    trickle_inconsistent_heard(&domain->trickle, &domain->control_trickle_params);
 | 
						|
    mpl_schedule_timer();
 | 
						|
}
 | 
						|
 | 
						|
static mpl_seed_t *mpl_seed_lookup(const mpl_domain_t *domain, uint8_t id_len, const uint8_t *seed_id)
 | 
						|
{
 | 
						|
    ns_list_foreach(mpl_seed_t, seed, &domain->seeds) {
 | 
						|
        if (seed->id_len == id_len && memcmp(seed->id, seed_id, id_len) == 0) {
 | 
						|
            return seed;
 | 
						|
        }
 | 
						|
    }
 | 
						|
 | 
						|
    return NULL;
 | 
						|
}
 | 
						|
 | 
						|
static mpl_seed_t *mpl_seed_create(mpl_domain_t *domain, uint8_t id_len, const uint8_t *seed_id, uint8_t sequence)
 | 
						|
{
 | 
						|
    mpl_seed_t *seed = ns_dyn_mem_alloc(sizeof(mpl_seed_t) + id_len);
 | 
						|
    if (!seed) {
 | 
						|
        return NULL;
 | 
						|
    }
 | 
						|
 | 
						|
    seed->min_sequence = sequence;
 | 
						|
    seed->lifetime = domain->seed_set_entry_lifetime;
 | 
						|
    seed->id_len = id_len;
 | 
						|
    seed->colour = domain->colour;
 | 
						|
    ns_list_init(&seed->messages);
 | 
						|
    memcpy(seed->id, seed_id, id_len);
 | 
						|
    ns_list_add_to_end(&domain->seeds, seed);
 | 
						|
    return seed;
 | 
						|
}
 | 
						|
 | 
						|
static void mpl_seed_delete(mpl_domain_t *domain, mpl_seed_t *seed)
 | 
						|
{
 | 
						|
    ns_list_foreach_safe(mpl_buffered_message_t, message, &seed->messages) {
 | 
						|
        mpl_buffer_delete(seed, message);
 | 
						|
    }
 | 
						|
    ns_list_remove(&domain->seeds, seed);
 | 
						|
    ns_dyn_mem_free(seed);
 | 
						|
}
 | 
						|
 | 
						|
static void mpl_seed_advance_min_sequence(mpl_seed_t *seed, uint8_t min_sequence)
 | 
						|
{
 | 
						|
    seed->min_sequence = min_sequence;
 | 
						|
    ns_list_foreach_safe(mpl_buffered_message_t, message, &seed->messages) {
 | 
						|
        if (common_serial_number_greater_8(min_sequence, mpl_buffer_sequence(message))) {
 | 
						|
            mpl_buffer_delete(seed, message);
 | 
						|
        }
 | 
						|
    }
 | 
						|
}
 | 
						|
 | 
						|
static mpl_buffered_message_t *mpl_buffer_lookup(mpl_seed_t *seed, uint8_t sequence)
 | 
						|
{
 | 
						|
    ns_list_foreach(mpl_buffered_message_t, message, &seed->messages) {
 | 
						|
        if (mpl_buffer_sequence(message) == sequence) {
 | 
						|
            return message;
 | 
						|
        }
 | 
						|
    }
 | 
						|
    return NULL;
 | 
						|
}
 | 
						|
 | 
						|
static void mpl_free_space(void)
 | 
						|
{
 | 
						|
    mpl_seed_t *oldest_seed = NULL;
 | 
						|
    mpl_buffered_message_t *oldest_message = NULL;
 | 
						|
 | 
						|
    /* We'll free one message - earliest sequence number from one seed */
 | 
						|
    /* Choose which seed by looking at the timestamp - oldest one first */
 | 
						|
    ns_list_foreach(mpl_domain_t, domain, &mpl_domains) {
 | 
						|
        ns_list_foreach(mpl_seed_t, seed, &domain->seeds) {
 | 
						|
            mpl_buffered_message_t *message = ns_list_get_first(&seed->messages);
 | 
						|
            if (!message) {
 | 
						|
                continue;
 | 
						|
            }
 | 
						|
            if (!oldest_message ||
 | 
						|
                    protocol_core_monotonic_time - message->timestamp > protocol_core_monotonic_time - oldest_message->timestamp) {
 | 
						|
                oldest_message = message;
 | 
						|
                oldest_seed = seed;
 | 
						|
            }
 | 
						|
        }
 | 
						|
    }
 | 
						|
 | 
						|
    if (!oldest_message) {
 | 
						|
        return;
 | 
						|
    }
 | 
						|
 | 
						|
    oldest_seed->min_sequence = mpl_buffer_sequence(oldest_message) + 1;
 | 
						|
    mpl_buffer_delete(oldest_seed, oldest_message);
 | 
						|
}
 | 
						|
 | 
						|
 | 
						|
static mpl_buffered_message_t *mpl_buffer_create(buffer_t *buf, mpl_domain_t *domain, mpl_seed_t *seed, uint8_t sequence, uint8_t hop_limit)
 | 
						|
{
 | 
						|
    /* IP layer ensures buffer length == IP length */
 | 
						|
    uint16_t ip_len = buffer_data_length(buf);
 | 
						|
 | 
						|
    while (mpl_total_buffered + ip_len > MAX_BUFFERED_MESSAGES_SIZE) {
 | 
						|
        tr_debug("MPL MAX buffered message size limit...free space");
 | 
						|
        mpl_free_space();
 | 
						|
    }
 | 
						|
 | 
						|
    /* As we came in, message sequence was >= min_sequence, but mpl_free_space
 | 
						|
     * could end up pushing min_sequence forward. We must take care and
 | 
						|
     * re-check min_sequence.
 | 
						|
     *
 | 
						|
     * For example, let's say min_sequence=1, we're holding 1,3,5, and we receive 2.
 | 
						|
     * a) If mpl_free_space doesn't touch this seed, we're fine.
 | 
						|
     * b) If it frees 1, it will advance min_sequence to 2, and we're fine.
 | 
						|
     * c) If it frees 1 and 3, it will advance min_sequence to 4, and we cannot
 | 
						|
     *    accept this message. (If we forced min_sequence to 2, we'd end up processing
 | 
						|
     *    message 3 again).
 | 
						|
     */
 | 
						|
    if (common_serial_number_greater_8(seed->min_sequence, sequence)) {
 | 
						|
        tr_debug("Can no longer accept %"PRIu8" < %"PRIu8, sequence, seed->min_sequence);
 | 
						|
        return NULL;
 | 
						|
    }
 | 
						|
 | 
						|
    mpl_buffered_message_t *message = ns_dyn_mem_alloc(sizeof(mpl_buffered_message_t) + ip_len);
 | 
						|
    if (!message) {
 | 
						|
        tr_debug("No heap for new MPL message");
 | 
						|
        return NULL;
 | 
						|
    }
 | 
						|
    memcpy(message->message, buffer_data_pointer(buf), ip_len);
 | 
						|
    message->message[IPV6_HDROFF_HOP_LIMIT] = hop_limit;
 | 
						|
    message->mpl_opt_data_offset = buf->mpl_option_data_offset;
 | 
						|
    message->colour = seed->colour;
 | 
						|
    message->timestamp = protocol_core_monotonic_time;
 | 
						|
    /* Make sure trickle structure is initialised */
 | 
						|
    trickle_start(&message->trickle, &domain->data_trickle_params);
 | 
						|
    if (domain->proactive_forwarding) {
 | 
						|
        mpl_schedule_timer();
 | 
						|
    } else {
 | 
						|
        /* Then stop it if not proactive */
 | 
						|
        trickle_stop(&message->trickle);
 | 
						|
    }
 | 
						|
 | 
						|
    /* Messages held ordered - eg for benefit of mpl_seed_bm_len() */
 | 
						|
    bool inserted = false;
 | 
						|
    ns_list_foreach_reverse(mpl_buffered_message_t, m, &seed->messages) {
 | 
						|
        if (common_serial_number_greater_8(sequence, mpl_buffer_sequence(m))) {
 | 
						|
            ns_list_add_after(&seed->messages, m, message);
 | 
						|
            inserted = true;
 | 
						|
            break;
 | 
						|
        }
 | 
						|
    }
 | 
						|
    if (!inserted) {
 | 
						|
        ns_list_add_to_start(&seed->messages, message);
 | 
						|
    }
 | 
						|
    mpl_total_buffered += ip_len;
 | 
						|
 | 
						|
    /* Does MPL spec intend this distinction between start and reset? */
 | 
						|
    mpl_control_reset_or_start(domain);
 | 
						|
 | 
						|
    return message;
 | 
						|
}
 | 
						|
 | 
						|
static void mpl_buffer_delete(mpl_seed_t *seed, mpl_buffered_message_t *message)
 | 
						|
{
 | 
						|
    mpl_total_buffered -= mpl_buffer_size(message);
 | 
						|
    ns_list_remove(&seed->messages, message);
 | 
						|
    ns_dyn_mem_free(message);
 | 
						|
}
 | 
						|
 | 
						|
static void mpl_buffer_transmit(mpl_domain_t *domain, mpl_buffered_message_t *message, bool newest)
 | 
						|
{
 | 
						|
    uint16_t ip_len = mpl_buffer_size(message);
 | 
						|
    buffer_t *buf = buffer_get(ip_len);
 | 
						|
    if (!buf) {
 | 
						|
        tr_debug("No heap for MPL transmit");
 | 
						|
        return;
 | 
						|
    }
 | 
						|
 | 
						|
    buffer_data_add(buf, message->message, ip_len);
 | 
						|
 | 
						|
    /* Modify the M flag [Thread says it must be clear] */
 | 
						|
    uint8_t *flag = buffer_data_pointer(buf) + message->mpl_opt_data_offset;
 | 
						|
    if (newest && !thread_info(domain->interface)) {
 | 
						|
        *flag |= MPL_OPT_M;
 | 
						|
    } else {
 | 
						|
        *flag &= ~MPL_OPT_M;
 | 
						|
    }
 | 
						|
 | 
						|
    // Make sure ip_routed_up is set, even on locally-seeded packets, to
 | 
						|
    // distinguishes the "forwarded" copies from the original seed.
 | 
						|
    // Used to suppress extra copies to sleepy children.
 | 
						|
    buf->ip_routed_up = true;
 | 
						|
    buf->dst_sa.addr_type = ADDR_IPV6;
 | 
						|
    buf->src_sa.addr_type = ADDR_IPV6;
 | 
						|
    memcpy(buf->dst_sa.address, message->message + IPV6_HDROFF_DST_ADDR, 16);
 | 
						|
    memcpy(buf->src_sa.address, message->message + IPV6_HDROFF_SRC_ADDR, 16);
 | 
						|
 | 
						|
    ipv6_transmit_multicast_on_interface(buf, domain->interface);
 | 
						|
    tr_debug("MPL transmit %u", mpl_buffer_sequence(message));
 | 
						|
}
 | 
						|
 | 
						|
static void mpl_buffer_inconsistent(const mpl_domain_t *domain, mpl_buffered_message_t *message)
 | 
						|
{
 | 
						|
    trickle_inconsistent_heard(&message->trickle, &domain->data_trickle_params);
 | 
						|
    mpl_schedule_timer();
 | 
						|
}
 | 
						|
 | 
						|
static uint8_t mpl_seed_bm_len(const mpl_seed_t *seed)
 | 
						|
{
 | 
						|
    mpl_buffered_message_t *last = ns_list_get_last(&seed->messages);
 | 
						|
    if (last) {
 | 
						|
        return ((uint8_t)(mpl_buffer_sequence(last) - seed->min_sequence)) / 8 + 1;
 | 
						|
    } else {
 | 
						|
        return 0;
 | 
						|
    }
 | 
						|
}
 | 
						|
 | 
						|
/* Attempt to optimise by saying ID is source IPv6 */
 | 
						|
static uint16_t mpl_seed_info_size(const mpl_seed_t *seed, const uint8_t *src)
 | 
						|
{
 | 
						|
    uint8_t id_len = seed->id_len;
 | 
						|
    if (id_len == 16 && src && addr_ipv6_equal(src, seed->id)) {
 | 
						|
        id_len = 0;
 | 
						|
    }
 | 
						|
    return 2 + id_len + mpl_seed_bm_len(seed);
 | 
						|
}
 | 
						|
 | 
						|
static uint8_t *mpl_write_seed_info(uint8_t *ptr, const mpl_seed_t *seed, const uint8_t *src)
 | 
						|
{
 | 
						|
    uint8_t bm_len = mpl_seed_bm_len(seed);
 | 
						|
    ptr[0] = seed->min_sequence;
 | 
						|
    ptr[1] = bm_len << 2;
 | 
						|
    uint8_t id_len = seed->id_len;
 | 
						|
    if (id_len == 16 && src && addr_ipv6_equal(src, seed->id)) {
 | 
						|
        id_len = 0;
 | 
						|
    }
 | 
						|
    switch (id_len) {
 | 
						|
        case  0:
 | 
						|
            ptr[1] |= MPL_SEED_IPV6_SRC;
 | 
						|
            break;
 | 
						|
        case  2:
 | 
						|
            ptr[1] |= MPL_SEED_16_BIT;
 | 
						|
            break;
 | 
						|
        case  8:
 | 
						|
            ptr[1] |= MPL_SEED_64_BIT;
 | 
						|
            break;
 | 
						|
        case 16:
 | 
						|
            ptr[1] |= MPL_SEED_128_BIT;
 | 
						|
            break;
 | 
						|
        default:
 | 
						|
            return ptr;
 | 
						|
    }
 | 
						|
    ptr += 2;
 | 
						|
    memcpy(ptr, seed->id, id_len);
 | 
						|
    ptr += id_len;
 | 
						|
    memset(ptr, 0, bm_len);
 | 
						|
    ns_list_foreach(mpl_buffered_message_t, buffer, &seed->messages) {
 | 
						|
        uint8_t i = mpl_buffer_sequence(buffer) - seed->min_sequence;
 | 
						|
        bit_set(ptr, i);
 | 
						|
    }
 | 
						|
    ptr += bm_len;
 | 
						|
    return ptr;
 | 
						|
}
 | 
						|
 | 
						|
/* Does MPL spec really intend this distinction between start and reset? */
 | 
						|
/* (Reset sets interval to Imin, Start puts it somewhere random between Imin and Imax) */
 | 
						|
static void mpl_control_reset_or_start(mpl_domain_t *domain)
 | 
						|
{
 | 
						|
    if (trickle_running(&domain->trickle, &domain->control_trickle_params)) {
 | 
						|
        trickle_inconsistent_heard(&domain->trickle, &domain->control_trickle_params);
 | 
						|
    } else {
 | 
						|
        trickle_start(&domain->trickle, &domain->control_trickle_params);
 | 
						|
    }
 | 
						|
    mpl_schedule_timer();
 | 
						|
}
 | 
						|
 | 
						|
static uint8_t mpl_seed_id_len(uint8_t seed_id_type)
 | 
						|
{
 | 
						|
    static const uint8_t len[] = {
 | 
						|
        [MPL_SEED_IPV6_SRC] = 0,
 | 
						|
        [MPL_SEED_16_BIT] = 2,
 | 
						|
        [MPL_SEED_64_BIT] = 8,
 | 
						|
        [MPL_SEED_128_BIT] = 16
 | 
						|
    };
 | 
						|
    return len[seed_id_type];
 | 
						|
}
 | 
						|
 | 
						|
static uint8_t mpl_seed_id_type(uint8_t seed_id_len)
 | 
						|
{
 | 
						|
    switch (seed_id_len) {
 | 
						|
        default:
 | 
						|
            return MPL_SEED_IPV6_SRC;
 | 
						|
        case 2:
 | 
						|
            return MPL_SEED_16_BIT;
 | 
						|
        case 8:
 | 
						|
            return MPL_SEED_64_BIT;
 | 
						|
        case 16:
 | 
						|
            return MPL_SEED_128_BIT;
 | 
						|
    }
 | 
						|
}
 | 
						|
 | 
						|
/*
 | 
						|
 *   0                   1                   2                   3
 | 
						|
 *   0 1 2 3 4 5 6 7 8 9 0 1 2 3 4 5 6 7 8 9 0 1 2 3 4 5 6 7 8 9 0 1
 | 
						|
 *  +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+
 | 
						|
 *  |   min-seqno   |  bm-len   | S |   seed-id (0/2/8/16 octets)   |
 | 
						|
 *  +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+
 | 
						|
 *  |                                                               |
 | 
						|
 *  .            buffered-mpl-messages (variable length)            .
 | 
						|
 *  .                                                               .
 | 
						|
 *  +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+
 | 
						|
 */
 | 
						|
static void mpl_send_control(mpl_domain_t *domain)
 | 
						|
{
 | 
						|
    uint16_t size = 0;
 | 
						|
    const uint8_t *src = NULL;
 | 
						|
 | 
						|
    ns_list_foreach(mpl_seed_t, seed, &domain->seeds) {
 | 
						|
        /* If not chosen yet, pick source to match a seed id, to save 16 bytes */
 | 
						|
        if (!src && seed->id_len == 16 && addr_is_assigned_to_interface(domain->interface, seed->id)) {
 | 
						|
            src = seed->id;
 | 
						|
        }
 | 
						|
        size += mpl_seed_info_size(seed, src);
 | 
						|
    }
 | 
						|
    buffer_t *buf = buffer_get(size);
 | 
						|
    if (!buf) {
 | 
						|
        return;
 | 
						|
    }
 | 
						|
    uint8_t *ptr = buffer_data_pointer(buf);
 | 
						|
    ns_list_foreach(mpl_seed_t, seed, &domain->seeds) {
 | 
						|
        ptr = mpl_write_seed_info(ptr, seed, src);
 | 
						|
    }
 | 
						|
    buffer_data_end_set(buf, ptr);
 | 
						|
    buf->options.type = ICMPV6_TYPE_INFO_MPL_CONTROL;
 | 
						|
    buf->options.code = 0;
 | 
						|
    buf->options.hop_limit = 255;
 | 
						|
    memcpy(buf->dst_sa.address, domain->address, 16);
 | 
						|
    buf->dst_sa.address[1] = (buf->dst_sa.address[1] & 0xf0) | IPV6_SCOPE_LINK_LOCAL;
 | 
						|
    buf->dst_sa.addr_type = ADDR_IPV6;
 | 
						|
    if (src) {
 | 
						|
        buf->src_sa.addr_type = ADDR_IPV6;
 | 
						|
        memcpy(buf->src_sa.address, src, 16);
 | 
						|
    }
 | 
						|
    buf->info = (buffer_info_t)(B_FROM_ICMP | B_TO_ICMP | B_DIR_DOWN);
 | 
						|
    buf->interface = domain->interface;
 | 
						|
    protocol_push(buf);
 | 
						|
}
 | 
						|
 | 
						|
/*
 | 
						|
 * There is an edge case in control handling when the hop limit runs out. This
 | 
						|
 * is handled as follows:
 | 
						|
 *
 | 
						|
 *          Hop Limit 2                Hop Limit 1                [Won't Forward]
 | 
						|
 *    Seed ---------------> Forwarder -------------> Final Node  -------X------> Neighbour Node
 | 
						|
 * In Message Set        In Message Set           MinSequence advanced        Not In Message Set
 | 
						|
 *
 | 
						|
 * The Final Node does NOT add the message to its buffered message set, and it
 | 
						|
 * advances MinSequence so that doesn't have to report about the message either
 | 
						|
 * positively or negatively in control messages.
 | 
						|
 *
 | 
						|
 * If it reported "present" in control messages, the Neighbour Node would see a "missing"
 | 
						|
 * message and reset its control timer. If it reported "absent", the Forwarder would
 | 
						|
 * notice the inconsistency and resend. So we sidestep the issue by advancing MinSequence.
 | 
						|
 * This also saves RAM - we'd never retransmit the message anyway, so why buffer it?
 | 
						|
 *
 | 
						|
 * This means we drop out-of-order packets at the edge of a hop limit boundary,
 | 
						|
 * but this isn't a huge deal.
 | 
						|
 */
 | 
						|
buffer_t *mpl_control_handler(buffer_t *buf, protocol_interface_info_entry_t *cur)
 | 
						|
{
 | 
						|
    if (!addr_is_ipv6_multicast(buf->dst_sa.address) || addr_ipv6_multicast_scope(buf->dst_sa.address) != IPV6_SCOPE_LINK_LOCAL || buf->options.hop_limit != 255) {
 | 
						|
        tr_warn("Invalid control");
 | 
						|
        return buffer_free(buf);
 | 
						|
    }
 | 
						|
 | 
						|
    /* Um, how do we distinguish between multiple domains with different scop?
 | 
						|
     * Control messages just have the domain address with scop 2. Currently
 | 
						|
     * deal with that by not letting users join two domains differing only in
 | 
						|
     * scop.
 | 
						|
     */
 | 
						|
 | 
						|
    mpl_domain_t *domain = mpl_domain_lookup_ignoring_scop(cur, buf->dst_sa.address);
 | 
						|
    if (!domain) {
 | 
						|
        return buffer_free(buf);
 | 
						|
    }
 | 
						|
 | 
						|
    bool they_have_new_data = false;
 | 
						|
    bool we_have_new_data = false;
 | 
						|
    const uint8_t *ptr = buffer_data_pointer(buf);
 | 
						|
    const uint8_t *end = buffer_data_end(buf);
 | 
						|
 | 
						|
    // All objects will currently have the same colour. The scan
 | 
						|
    // of the control message will flip the colour of every mentioned seed
 | 
						|
    // and data message. Then the omission of anything we have will be detected
 | 
						|
    // by its colour not being flipped.
 | 
						|
    // This is equivalent to having a "mentioned" flag, except we don't have
 | 
						|
    // to have a separate "reset" loop.
 | 
						|
    domain->colour = !domain->colour;
 | 
						|
    bool new_colour = domain->colour;
 | 
						|
 | 
						|
    while (ptr < end) {
 | 
						|
        if (end - ptr < 2) {
 | 
						|
            tr_err("MPL control error");
 | 
						|
            break;
 | 
						|
        }
 | 
						|
        uint8_t min_seqno = ptr[0];
 | 
						|
        uint8_t bm_len = ptr[1] >> 2;
 | 
						|
        uint8_t seed_id_type = ptr[1] & 3;
 | 
						|
        uint8_t seed_id_len = mpl_seed_id_len(seed_id_type);
 | 
						|
        ptr += 2;
 | 
						|
        /* Sequence number is 8-bit, so bitmask should never be bigger than 32 bytes */
 | 
						|
        if (bm_len > 32 || end - ptr < seed_id_len + bm_len) {
 | 
						|
            tr_err("MPL control error");
 | 
						|
            break;
 | 
						|
        }
 | 
						|
        const uint8_t *seed_id;
 | 
						|
        if (seed_id_type == MPL_SEED_IPV6_SRC) {
 | 
						|
            seed_id = buf->src_sa.address;
 | 
						|
            seed_id_len = 16;
 | 
						|
            /* Thread spec says, or at least implies, that ML16/RLOC address is
 | 
						|
             * matched against corresponding 16-bit seed id (although
 | 
						|
             * Thread doesn't use control messages...) */
 | 
						|
            if (thread_addr_is_mesh_local_16(seed_id, cur)) {
 | 
						|
                seed_id += 14;
 | 
						|
                seed_id_len = 2;
 | 
						|
            }
 | 
						|
        } else {
 | 
						|
            seed_id = ptr;
 | 
						|
            ptr += seed_id_len;
 | 
						|
        }
 | 
						|
        mpl_seed_t *seed = mpl_seed_lookup(domain, seed_id_len, seed_id);
 | 
						|
        if (!seed) {
 | 
						|
            they_have_new_data = true;
 | 
						|
            ptr += bm_len;
 | 
						|
            continue;
 | 
						|
        }
 | 
						|
 | 
						|
        seed->colour = new_colour;
 | 
						|
        /* They are assumed to not be interested in messages lower than their min_seqno */
 | 
						|
        ns_list_foreach(mpl_buffered_message_t, message, &seed->messages) {
 | 
						|
            if (common_serial_number_greater_8(min_seqno, mpl_buffer_sequence(message))) {
 | 
						|
                message->colour = new_colour;
 | 
						|
            }
 | 
						|
        }
 | 
						|
        for (uint8_t i = 0; i / 8 < bm_len; i++) {
 | 
						|
            if (bit_test(ptr, i)) {
 | 
						|
                mpl_buffered_message_t *message = mpl_buffer_lookup(seed, min_seqno + i);
 | 
						|
 | 
						|
                if (!message && common_serial_number_greater_8(min_seqno + i, seed->min_sequence)) {
 | 
						|
                    they_have_new_data = true;
 | 
						|
                } else if (message) {
 | 
						|
                    message->colour = new_colour;
 | 
						|
                }
 | 
						|
            }
 | 
						|
        }
 | 
						|
        ptr += bm_len;
 | 
						|
    }
 | 
						|
 | 
						|
    /* Search for seeds or messages they haven't mentioned */
 | 
						|
    ns_list_foreach(mpl_seed_t, seed, &domain->seeds) {
 | 
						|
        if (seed->colour != new_colour) {
 | 
						|
            seed->colour = new_colour;
 | 
						|
            we_have_new_data = true;
 | 
						|
        }
 | 
						|
        ns_list_foreach(mpl_buffered_message_t, message, &seed->messages) {
 | 
						|
            if (message->colour != new_colour) {
 | 
						|
                message->colour = new_colour;
 | 
						|
                mpl_buffer_inconsistent(domain, message);
 | 
						|
                we_have_new_data = true;
 | 
						|
            }
 | 
						|
        }
 | 
						|
    }
 | 
						|
 | 
						|
    if (they_have_new_data || we_have_new_data) {
 | 
						|
        if (they_have_new_data) {
 | 
						|
            tr_info("%s has new MPL data", trace_ipv6(buf->src_sa.address));
 | 
						|
        }
 | 
						|
        if (we_have_new_data) {
 | 
						|
            tr_info("We have new MPL data for %s", trace_ipv6(buf->src_sa.address));
 | 
						|
        }
 | 
						|
        mpl_domain_inconsistent(domain);
 | 
						|
    } else {
 | 
						|
        trickle_consistent_heard(&domain->trickle);
 | 
						|
    }
 | 
						|
 | 
						|
 | 
						|
    return buffer_free(buf);
 | 
						|
}
 | 
						|
 | 
						|
bool mpl_hbh_len_check(const uint8_t *opt_data, uint8_t opt_data_len)
 | 
						|
{
 | 
						|
    if (opt_data_len < 2) {
 | 
						|
        return false;
 | 
						|
    }
 | 
						|
    if (opt_data[0] & MPL_OPT_V) {
 | 
						|
        return true; /* No length complaint - we let "process" drop */
 | 
						|
    }
 | 
						|
 | 
						|
    uint8_t seed_id_type = (opt_data[0] & MPL_OPT_S_MASK) >> MPL_OPT_S_SHIFT;
 | 
						|
    /* Note that option is allowed to be longer - spec allows for extension
 | 
						|
     * beyond seed-id.
 | 
						|
     */
 | 
						|
    if (opt_data_len < 2 + mpl_seed_id_len(seed_id_type)) {
 | 
						|
        return false;
 | 
						|
    }
 | 
						|
    return true;
 | 
						|
}
 | 
						|
 | 
						|
/*      0                   1                   2                   3
 | 
						|
 *      0 1 2 3 4 5 6 7 8 9 0 1 2 3 4 5 6 7 8 9 0 1 2 3 4 5 6 7 8 9 0 1
 | 
						|
 *                                     +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+
 | 
						|
 *                                     |  Option Type  |  Opt Data Len |
 | 
						|
 *     +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+
 | 
						|
 *     | S |M|V|  rsv  |   sequence    |      seed-id (optional)       |
 | 
						|
 *     +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+
 | 
						|
 */
 | 
						|
 | 
						|
bool mpl_process_hbh(buffer_t *buf, protocol_interface_info_entry_t *cur, uint8_t *opt_data)
 | 
						|
{
 | 
						|
    if ((buf->options.ip_extflags & IPEXT_HBH_MPL) || buf->options.ll_security_bypass_rx) {
 | 
						|
        tr_warn("Bad MPL");
 | 
						|
        return false;
 | 
						|
    }
 | 
						|
 | 
						|
    /* mpl_hbh_len_check has already returned true, so know length is okay */
 | 
						|
 | 
						|
    /* V flag indicates incompatible new version - packets MUST be dropped */
 | 
						|
    if (opt_data[0] & MPL_OPT_V) {
 | 
						|
        tr_warn("MPL V!");
 | 
						|
        return false;
 | 
						|
    }
 | 
						|
 | 
						|
    mpl_domain_t *domain = mpl_domain_lookup_with_realm_check(cur, buf->dst_sa.address);
 | 
						|
    if (!domain) {
 | 
						|
        tr_debug("No MPL domain");
 | 
						|
        return false;
 | 
						|
    }
 | 
						|
 | 
						|
    buf->options.ip_extflags |= IPEXT_HBH_MPL;
 | 
						|
    buf->mpl_option_data_offset = opt_data - buffer_data_pointer(buf);
 | 
						|
 | 
						|
    return true;
 | 
						|
    // return mpl_forwarder_process_message(buf, domain, opt_data);
 | 
						|
}
 | 
						|
 | 
						|
/* seeding is true if this is processing an outgoing message */
 | 
						|
bool mpl_forwarder_process_message(buffer_t *buf, mpl_domain_t *domain, bool seeding)
 | 
						|
{
 | 
						|
    const uint8_t *opt_data = buffer_data_pointer(buf) + buf->mpl_option_data_offset;
 | 
						|
    uint8_t sequence = opt_data[1];
 | 
						|
    uint8_t seed_id_type = (opt_data[0] & MPL_OPT_S_MASK) >> MPL_OPT_S_SHIFT;
 | 
						|
    const uint8_t *seed_id = opt_data + 2;
 | 
						|
    uint8_t seed_id_len = mpl_seed_id_len(seed_id_type);
 | 
						|
 | 
						|
    tr_debug("MPL %s %"PRIu8, seeding ? "transmit" : "received", sequence);
 | 
						|
    /* Special handling - just ignore the MPL option if receiving loopback copy.
 | 
						|
     * (MPL gets to process the outgoing message, and with seeding true - when
 | 
						|
     * looping back, we want to accept it without MPL getting in the way).
 | 
						|
     */
 | 
						|
    if (!seeding && buf->options.multicast_loop) {
 | 
						|
        return true;
 | 
						|
    }
 | 
						|
 | 
						|
    if (!domain) {
 | 
						|
        domain = mpl_domain_lookup_with_realm_check(buf->interface, buf->dst_sa.address);
 | 
						|
        if (!domain) {
 | 
						|
            tr_debug("No domain %s  %s", tr_ipv6(domain->address), tr_array(seed_id, seed_id_len));
 | 
						|
            return false;
 | 
						|
        }
 | 
						|
    }
 | 
						|
 | 
						|
    if (seed_id_type == MPL_SEED_IPV6_SRC) {
 | 
						|
        seed_id = buf->src_sa.address;
 | 
						|
        seed_id_len = 16;
 | 
						|
        /* Thread spec says, or at least implies, that ML16/RLOC address is
 | 
						|
         * matched against corresponding 16-bit seed id */
 | 
						|
        if (thread_addr_is_mesh_local_16(seed_id, buf->interface)) {
 | 
						|
            seed_id += 14;
 | 
						|
            seed_id_len = 2;
 | 
						|
        }
 | 
						|
    }
 | 
						|
 | 
						|
    tr_debug("seed %s seq %"PRIu8, tr_array(seed_id, seed_id_len), sequence);
 | 
						|
    mpl_seed_t *seed = mpl_seed_lookup(domain, seed_id_len, seed_id);
 | 
						|
    if (!seed) {
 | 
						|
        seed = mpl_seed_create(domain, seed_id_len, seed_id, sequence);
 | 
						|
        if (!seed) {
 | 
						|
            tr_debug("No seed %s  %s", tr_ipv6(domain->address), tr_array(seed_id, seed_id_len));
 | 
						|
            return false;
 | 
						|
        }
 | 
						|
    }
 | 
						|
 | 
						|
    /* If the M flag is set, we report an inconsistency against any messages with higher sequences */
 | 
						|
    if ((opt_data[0] & MPL_OPT_M) && !thread_info(buf->interface)) {
 | 
						|
        ns_list_foreach(mpl_buffered_message_t, message, &seed->messages) {
 | 
						|
            if (common_serial_number_greater_8(mpl_buffer_sequence(message), sequence)) {
 | 
						|
                mpl_buffer_inconsistent(domain, message);
 | 
						|
            }
 | 
						|
        }
 | 
						|
    }
 | 
						|
 | 
						|
    /* Drop old messages (sequence < MinSequence) */
 | 
						|
    if (common_serial_number_greater_8(seed->min_sequence, sequence)) {
 | 
						|
        tr_debug("Old MPL message %"PRIu8" < %"PRIu8, sequence, seed->min_sequence);
 | 
						|
        return false;
 | 
						|
    }
 | 
						|
 | 
						|
    mpl_buffered_message_t *message = mpl_buffer_lookup(seed, sequence);
 | 
						|
    if (message) {
 | 
						|
        tr_debug("Repeated MPL message %"PRIu8, sequence);
 | 
						|
        trickle_consistent_heard(&message->trickle);
 | 
						|
        return false;
 | 
						|
    }
 | 
						|
 | 
						|
    seed->lifetime = domain->seed_set_entry_lifetime;
 | 
						|
 | 
						|
    uint8_t hop_limit = buffer_data_pointer(buf)[IPV6_HDROFF_HOP_LIMIT];
 | 
						|
    if (!seeding && hop_limit != 0) {
 | 
						|
        hop_limit--;
 | 
						|
    }
 | 
						|
 | 
						|
    if (domain->data_trickle_params.TimerExpirations == 0 || hop_limit == 0 ||
 | 
						|
            (thread_info(domain->interface) && !thread_i_am_router(domain->interface))) {
 | 
						|
        /* As a non-forwarder, just accept the packet and advance the
 | 
						|
         * min_sequence - means we will drop anything arriving out-of-order, but
 | 
						|
         * old implementation always did this in all cases anyway (even if
 | 
						|
         * being a forwarder).
 | 
						|
         *
 | 
						|
         * We also do this if hop limit is 0, so we are not going to forward.
 | 
						|
         * This avoids the edge case discussed in the comment above mpl_control_handler.
 | 
						|
         *
 | 
						|
         * And finally, also treat Thread non-routers like this, to avoid
 | 
						|
         * need to dynamically changing TimerExpirations.
 | 
						|
         */
 | 
						|
        mpl_seed_advance_min_sequence(seed, sequence + 1);
 | 
						|
        return true;
 | 
						|
    }
 | 
						|
 | 
						|
    message = mpl_buffer_create(buf, domain, seed, sequence, hop_limit);
 | 
						|
    if (!message) {
 | 
						|
        tr_debug("MPL Buffer Craete fail");
 | 
						|
    }
 | 
						|
 | 
						|
    return true;
 | 
						|
}
 | 
						|
 | 
						|
 | 
						|
static void mpl_schedule_timer(void)
 | 
						|
{
 | 
						|
    if (!mpl_timer_running) {
 | 
						|
        mpl_timer_running = true;
 | 
						|
        protocol_timer_start(PROTOCOL_TIMER_MULTICAST_TIM, mpl_fast_timer, MPL_TICK_MS);
 | 
						|
    }
 | 
						|
}
 | 
						|
 | 
						|
static void mpl_fast_timer(uint16_t ticks)
 | 
						|
{
 | 
						|
    bool need_timer = false;
 | 
						|
    mpl_timer_running = false;
 | 
						|
 | 
						|
    ns_list_foreach(mpl_domain_t, domain, &mpl_domains) {
 | 
						|
        if (trickle_timer(&domain->trickle, &domain->control_trickle_params, ticks)) {
 | 
						|
            mpl_send_control(domain);
 | 
						|
        }
 | 
						|
        ns_list_foreach(mpl_seed_t, seed, &domain->seeds) {
 | 
						|
            ns_list_foreach(mpl_buffered_message_t, message, &seed->messages) {
 | 
						|
                if (trickle_timer(&message->trickle, &domain->data_trickle_params, ticks)) {
 | 
						|
                    mpl_buffer_transmit(domain, message, ns_list_get_next(&seed->messages, message) == NULL);
 | 
						|
                }
 | 
						|
                need_timer = need_timer || trickle_running(&message->trickle, &domain->data_trickle_params);
 | 
						|
            }
 | 
						|
        }
 | 
						|
        need_timer = need_timer || trickle_running(&domain->trickle, &domain->control_trickle_params);
 | 
						|
    }
 | 
						|
 | 
						|
    if (need_timer) {
 | 
						|
        mpl_schedule_timer();
 | 
						|
    }
 | 
						|
}
 | 
						|
 | 
						|
void mpl_slow_timer(uint16_t seconds)
 | 
						|
{
 | 
						|
    ns_list_foreach(mpl_domain_t, domain, &mpl_domains) {
 | 
						|
        uint32_t message_age_limit = (domain->seed_set_entry_lifetime * UINT32_C(10)) / 4;
 | 
						|
        if (message_age_limit > MAX_BUFFERED_MESSAGE_LIFETIME) {
 | 
						|
            message_age_limit = MAX_BUFFERED_MESSAGE_LIFETIME;
 | 
						|
        }
 | 
						|
        ns_list_foreach_safe(mpl_seed_t, seed, &domain->seeds) {
 | 
						|
            /* Count down seed lifetime, and expire immediately when hit */
 | 
						|
            if (seed->lifetime > seconds) {
 | 
						|
                seed->lifetime -= seconds;
 | 
						|
            } else {
 | 
						|
                mpl_seed_delete(domain, seed);
 | 
						|
                continue;
 | 
						|
            }
 | 
						|
            /* Once data trickle timer has stopped, we MAY delete a message by
 | 
						|
             * advancing MinSequence. We use timestamp to control this, so we
 | 
						|
             * can hold beyond just the initial data transmission, permitting
 | 
						|
             * it to be restarted by control messages.
 | 
						|
             */
 | 
						|
            ns_list_foreach_safe(mpl_buffered_message_t, message, &seed->messages) {
 | 
						|
                if (!trickle_running(&message->trickle, &domain->data_trickle_params) &&
 | 
						|
                        protocol_core_monotonic_time - message->timestamp >= message_age_limit) {
 | 
						|
                    seed->min_sequence = mpl_buffer_sequence(message) + 1;
 | 
						|
                    mpl_buffer_delete(seed, message);
 | 
						|
                } else {
 | 
						|
                    break;
 | 
						|
                }
 | 
						|
            }
 | 
						|
        }
 | 
						|
    }
 | 
						|
}
 | 
						|
 | 
						|
void mpl_clear_realm_scope_seeds(protocol_interface_info_entry_t *cur)
 | 
						|
{
 | 
						|
    ns_list_foreach(mpl_domain_t, domain, &mpl_domains) {
 | 
						|
        if (domain->interface == cur && addr_ipv6_multicast_scope(domain->address) <= IPV6_SCOPE_REALM_LOCAL) {
 | 
						|
            ns_list_foreach_safe(mpl_seed_t, seed, &domain->seeds) {
 | 
						|
                mpl_seed_delete(domain, seed);
 | 
						|
            }
 | 
						|
        }
 | 
						|
    }
 | 
						|
}
 | 
						|
 | 
						|
static buffer_t *mpl_exthdr_provider(buffer_t *buf, ipv6_exthdr_stage_t stage, int16_t *result)
 | 
						|
{
 | 
						|
    mpl_domain_t *domain = mpl_domain_lookup_with_realm_check(buf->interface, buf->dst_sa.address);
 | 
						|
 | 
						|
    /* Deal with simpler modify-already-created-header case first. Note that no error returns. */
 | 
						|
    if (stage == IPV6_EXTHDR_MODIFY) {
 | 
						|
        if (!domain) {
 | 
						|
            *result = IPV6_EXTHDR_MODIFY_TUNNEL;
 | 
						|
            memcpy(buf->dst_sa.address, ADDR_ALL_MPL_FORWARDERS, 16);
 | 
						|
            buf->src_sa.addr_type = ADDR_NONE; // force auto-selection
 | 
						|
            return buf;
 | 
						|
        }
 | 
						|
 | 
						|
        if (buf->options.ip_extflags & IPEXT_HBH_MPL_UNFILLED) {
 | 
						|
            /* We assume we created this, therefore our option is in place
 | 
						|
             * in the expected place. Sequence is set now, AFTER
 | 
						|
             * fragmentation.
 | 
						|
             */
 | 
						|
            uint8_t *iphdr = buffer_data_pointer(buf);
 | 
						|
            uint8_t *ext = iphdr + IPV6_HDRLEN;
 | 
						|
            if (iphdr[IPV6_HDROFF_NH] != IPV6_NH_HOP_BY_HOP || ext[2] != IPV6_OPTION_MPL) {
 | 
						|
                tr_err("modify");
 | 
						|
                return buffer_free(buf);
 | 
						|
            }
 | 
						|
            /* We don't bother setting the M flag on these initial packets. Setting to 0 is always acceptable. */
 | 
						|
            ext[5] = domain->sequence++;
 | 
						|
            buf->options.ip_extflags &= ~ IPEXT_HBH_MPL_UNFILLED;
 | 
						|
            buf->mpl_option_data_offset = IPV6_HDRLEN + 4;
 | 
						|
            mpl_forwarder_process_message(buf, domain, true);
 | 
						|
        }
 | 
						|
        *result = 0;
 | 
						|
        return buf;
 | 
						|
    }
 | 
						|
 | 
						|
    /* Rest of code deals with header insertion */
 | 
						|
    if (!domain) {
 | 
						|
        // We will need to tunnel - do nothing on the inner packet
 | 
						|
        *result = 0;
 | 
						|
        return buf;
 | 
						|
    }
 | 
						|
 | 
						|
    const uint8_t *seed_id;
 | 
						|
    uint8_t seed_id_len;
 | 
						|
    uint8_t seed_id_buf[16];
 | 
						|
    if (domain->seed_id_mode > 0) {
 | 
						|
        seed_id_len = domain->seed_id_mode;
 | 
						|
        seed_id = domain->seed_id;
 | 
						|
    } else switch (domain->seed_id_mode) {
 | 
						|
            case MULTICAST_MPL_SEED_ID_MAC_SHORT: {
 | 
						|
                uint16_t addr = mac_helper_mac16_address_get(buf->interface);
 | 
						|
                if (addr < 0xfffe) {
 | 
						|
                    common_write_16_bit(addr, seed_id_buf);
 | 
						|
                    seed_id = seed_id_buf;
 | 
						|
                    seed_id_len = 2;
 | 
						|
                    break;
 | 
						|
                }
 | 
						|
            // Otherwise fall through to extended
 | 
						|
                case MULTICAST_MPL_SEED_ID_MAC:
 | 
						|
                    seed_id = buf->interface->mac;
 | 
						|
                    seed_id_len = 8;
 | 
						|
                    break;
 | 
						|
 | 
						|
                case MULTICAST_MPL_SEED_ID_IID_EUI64:
 | 
						|
                    seed_id = buf->interface->iid_eui64;
 | 
						|
                    seed_id_len = 8;
 | 
						|
                    break;
 | 
						|
 | 
						|
                case MULTICAST_MPL_SEED_ID_IID_SLAAC:
 | 
						|
                    seed_id = buf->interface->iid_slaac;
 | 
						|
                    seed_id_len = 8;
 | 
						|
                    break;
 | 
						|
                }
 | 
						|
 | 
						|
            default:
 | 
						|
            case MULTICAST_MPL_SEED_ID_IPV6_SRC_FOR_DOMAIN:
 | 
						|
                seed_id = addr_select_source(buf->interface, domain->address, 0);
 | 
						|
                seed_id_len = 16;
 | 
						|
                break;
 | 
						|
        }
 | 
						|
 | 
						|
    if (!seed_id) {
 | 
						|
        tr_err("No MPL Seed ID");
 | 
						|
        return buffer_free(buf);
 | 
						|
    }
 | 
						|
 | 
						|
    /* "Compress" seed ID if it's the IPv6 source address */
 | 
						|
    /* (For Thread, also compress if source is the 16-bit address) */
 | 
						|
    /* (For Wi-sun, not support seed id address compression */
 | 
						|
    if (!ws_info(buf->interface) && seed_id_len == 16 && addr_ipv6_equal(seed_id, buf->src_sa.address)) {
 | 
						|
        seed_id_len = 0;
 | 
						|
    } else if (seed_id_len == 2 && thread_addr_is_mesh_local_16(buf->src_sa.address, buf->interface) &&
 | 
						|
               seed_id[0] == buf->src_sa.address[14] && seed_id[1] == buf->src_sa.address[15]) {
 | 
						|
        seed_id_len = 0;
 | 
						|
    }
 | 
						|
 | 
						|
    switch (stage) {
 | 
						|
        case IPV6_EXTHDR_SIZE:
 | 
						|
            *result = 4 + seed_id_len;
 | 
						|
            return buf;
 | 
						|
        case IPV6_EXTHDR_INSERT: {
 | 
						|
            /* Only have 4 possible lengths/padding patterns to consider:
 | 
						|
              * HbH 2 + Option header 4 + Seed 0 + Padding 2 = 8
 | 
						|
              * HbH 2 + Option header 4 + Seed 2 + Padding 0 = 8
 | 
						|
              * HbH 2 + Option header 4 + Seed 8 + Padding 2 = 16
 | 
						|
              * HbH 2 + Option header 4 + Seed 16 + Padding 2 = 24
 | 
						|
              */
 | 
						|
            uint8_t extlen = (6 + seed_id_len + 7) & ~ 7;
 | 
						|
            buf = buffer_headroom(buf, extlen);
 | 
						|
            if (!buf) {
 | 
						|
                return NULL;
 | 
						|
            }
 | 
						|
            uint8_t *ext = buffer_data_reserve_header(buf, extlen);
 | 
						|
            ext[0] = buf->options.type;
 | 
						|
            buf->options.type = IPV6_NH_HOP_BY_HOP;
 | 
						|
            ext[1] = (extlen / 8) - 1;
 | 
						|
            ext[2] = IPV6_OPTION_MPL;
 | 
						|
            ext[3] = 2 + seed_id_len;
 | 
						|
            ext[4] = (mpl_seed_id_type(seed_id_len) << MPL_OPT_S_SHIFT);
 | 
						|
            ext[5] = 0; // sequence placeholder
 | 
						|
            memcpy(ext + 6, seed_id, seed_id_len);
 | 
						|
            if (seed_id_len != 2) {
 | 
						|
                ext[extlen - 2] = IPV6_OPTION_PADN;
 | 
						|
                ext[extlen - 1] = 0;
 | 
						|
            }
 | 
						|
 | 
						|
            *result = 0;
 | 
						|
            buf->options.ip_extflags |= IPEXT_HBH_MPL | IPEXT_HBH_MPL_UNFILLED;
 | 
						|
            return buf;
 | 
						|
        }
 | 
						|
        default:
 | 
						|
            return buffer_free(buf);
 | 
						|
    }
 | 
						|
}
 | 
						|
 | 
						|
#endif /* HAVE_MPL */
 | 
						|
 |