mbed-os/features/nanostack/sal-stack-nanostack/source/6LoWPAN/Thread/thread_diagnostic.c

580 lines
21 KiB
C

/*
* Copyright (c) 2016-2018, Pelion and affiliates.
* SPDX-License-Identifier: BSD-3-Clause
*
* Redistribution and use in source and binary forms, with or without
* modification, are permitted provided that the following conditions are met:
*
* 1. Redistributions of source code must retain the above copyright
* notice, this list of conditions and the following disclaimer.
* 2. Redistributions in binary form must reproduce the above copyright
* notice, this list of conditions and the following disclaimer in the
* documentation and/or other materials provided with the distribution.
* 3. Neither the name of the copyright holder nor the
* names of its contributors may be used to endorse or promote products
* derived from this software without specific prior written permission.
*
* THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS"
* AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
* IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
* ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE
* LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR
* CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF
* SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS
* INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN
* CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE)
* ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE
* POSSIBILITY OF SUCH DAMAGE.
*/
#include "nsconfig.h"
#ifdef HAVE_THREAD
#include <string.h>
#include <ns_types.h>
#include <ns_list.h>
#include <ns_trace.h>
#include "nsdynmemLIB.h"
#include "net_interface.h"
#include "thread_management_if.h"
#include "thread_management_server.h"
#include "thread_common.h"
#include "thread_joiner_application.h"
#include "thread_leader_service.h"
#include "thread_router_bootstrap.h"
#include "6LoWPAN/Thread/thread_neighbor_class.h"
#include "MLE/mle.h"
#include "6LoWPAN/Thread/thread_router_bootstrap.h"
#include "thread_config.h"
#include "thread_network_data_storage.h"
#include "NWK_INTERFACE/Include/protocol.h"
#include "thread_diagcop_lib.h"
#include "common_functions.h"
#include "6LoWPAN/MAC/mac_helper.h"
#include "Service_Libs/mac_neighbor_table/mac_neighbor_table.h"
#include "mac_api.h"
#define TRACE_GROUP "TdiaC"
#include "coap_service_api.h"
typedef struct thread_diagnostic_command {
int8_t interface_id;
int8_t coap_service_id;
ns_list_link_t link;
} thread_diagnostic_command_t;
static NS_LIST_DEFINE(instance_list, thread_diagnostic_command_t, link);
static thread_diagnostic_command_t *thread_diagnostic_command_find(int8_t interface_id)
{
thread_diagnostic_command_t *this = NULL;
ns_list_foreach(thread_diagnostic_command_t, cur_ptr, &instance_list) {
if (cur_ptr->interface_id == interface_id) {
this = cur_ptr;
break;
}
}
return this;
}
static thread_diagnostic_command_t *thread_diagnostic_find_by_service(int8_t service_id)
{
thread_diagnostic_command_t *this = NULL;
ns_list_foreach(thread_diagnostic_command_t, cur_ptr, &instance_list) {
if (cur_ptr->coap_service_id == service_id) {
this = cur_ptr;
break;
}
}
return this;
}
static uint8_t *thread_diagnostic_child_table_tlv_build(uint8_t *data_ptr, protocol_interface_info_entry_t *cur)
{
uint8_t child_count = 0;
uint8_t calculated_timeout;
mac_neighbor_table_list_t *mac_table_list = &mac_neighbor_info(cur)->neighbour_list;
child_count = thread_router_bootstrap_child_count_get(cur);
*data_ptr++ = DIAGCOP_TLV_CHILD_TABLE; // Type
*data_ptr++ = (3 * child_count); // Length
ns_list_foreach(mac_neighbor_table_entry_t, cur_entry, mac_table_list) {
if (thread_router_addr_from_addr(cur_entry->mac16) == mac_helper_mac16_address_get(cur)) {
/* |0|1|2|3|4|5|6|7|8|9|0|1|2|3|4|5|6|7|8|9|0|1|2|3| */
/* |Timeout |Rsv| Child ID | Mode | */
calculated_timeout = thread_log2_aprx(cur_entry->link_lifetime - 1) + 4;
uint8_t mode = 0;
mode |= mle_mode_write_from_mac_entry(cur_entry);
mode |= thread_neighbor_class_mode_write_from_entry(&cur->thread_info->neighbor_class, cur_entry->index);
tr_debug("Write child table TLV entry: %d - %d - %d", calculated_timeout, cur_entry->mac16, mode);
*data_ptr = 0x00; //reserved bytes to zero
*data_ptr = calculated_timeout << 3;
if (cur_entry->mac16 & 0x0100) {
*data_ptr = *data_ptr | 0x01;
}
data_ptr++;
*data_ptr++ = (uint8_t)(cur_entry->mac16 & 0x00ff);
*data_ptr++ = mode;
}
}
return data_ptr;
}
uint8_t thread_diag_mode_get_by_interface_ptr(protocol_interface_info_entry_t *cur)
{
uint8_t mle_mode = 0;
if (!thread_info(cur)) {
return 0;
}
if (!(cur->lowpan_info & INTERFACE_NWK_CONF_MAC_RX_OFF_IDLE)) {
mle_mode |= MLE_RX_ON_IDLE;
}
if (thread_info(cur)->requestFullNetworkData) {
mle_mode |= (MLE_THREAD_REQ_FULL_DATA_SET);
}
/* We always send secured data requests */
mle_mode |= MLE_THREAD_SECURED_DATA_REQUEST;
switch (thread_info(cur)->thread_device_mode) {
case THREAD_DEVICE_MODE_ROUTER:
case THREAD_DEVICE_MODE_FULL_END_DEVICE:
mle_mode |= MLE_FFD_DEV;
break;
default:
break;
}
return mle_mode;
}
static int thread_diagnostic_configuration_calc(protocol_interface_info_entry_t *cur, uint8_t *tlv_list, uint16_t list_len)
{
int payload_len = 0;
uint16_t address_count = 0;
if (!tlv_list || list_len < 1) {
return 0;
}
while (list_len --) {
switch (*tlv_list) {
case DIAGCOP_TLV_EXTENDED_MAC_ADDRESS:
payload_len += 2 + 8;
break;
case DIAGCOP_TLV_ADDRESS16:
payload_len += 2 + 2;
break;
case DIAGCOP_TLV_MODE:
payload_len += 2 + 1;
break;
case DIAGCOP_TLV_TIMEOUT:
if (cur->thread_info->thread_device_mode == THREAD_DEVICE_MODE_SLEEPY_END_DEVICE ||
cur->thread_info->thread_device_mode == THREAD_DEVICE_MODE_END_DEVICE) {
payload_len += 2 + 4;
}
break;
case DIAGCOP_TLV_CONNECTIVITY:
payload_len += 2 + 10;
break;
case DIAGCOP_TLV_ROUTE64:
payload_len += thread_route_option_size(cur);
break;
case DIAGCOP_TLV_LEADER_DATA:
payload_len += 2 + 8; // TLV header + uint16 pan id
break;
case DIAGCOP_TLV_NETWORK_DATA:
payload_len += 2; // TLV header
payload_len += thread_network_data_tlv_size(cur, 1);
break;
case DIAGCOP_TLV_IPV6_ADDRESS_LIST:
arm_net_interface_address_list_size(cur->id, &address_count);
payload_len += 2 + (address_count * 16);
break;
case DIAGCOP_TLV_MAC_COUNTERS:
payload_len += 2 + 36;
break;
case DIAGCOP_TLV_BATTERY_LEVEL:
payload_len += 2 + 1;
break;
case DIAGCOP_TLV_SUPPLY_VOLTAGE:
payload_len += 2 + 2;
break;
case DIAGCOP_TLV_CHILD_TABLE:
/* Value length = Type + Length + 3 * child count */
payload_len += 2 + (3 * thread_router_bootstrap_child_count_get(cur));
break;
case DIAGCOP_TLV_CHANNEL_PAGES:
payload_len += 2 + 1;
break;
case DIAGCOP_TLV_MAX_CHILD_TIMEOUT:
payload_len += 2 + 4;
break;
default:
// todo: Other TLV's not supported atm
break;
}
tlv_list++;
}
return payload_len;
}
static uint8_t *thread_diagnostic_get_build(protocol_interface_info_entry_t *cur, uint8_t *response_ptr, uint8_t *tlv_list, uint16_t list_len)
{
if (!tlv_list || list_len < 1) {
// Request all
return response_ptr;
}
if (!thread_info(cur)) {
return response_ptr;
}
// the following are some tlvs that need to be implemented correctly, this is only dummy data for the moment
uint8_t dummy_data[36] = {0};
uint8_t *ptr;
int written_address_count = 0;
uint16_t ipv6_address_count = 0;
uint32_t max_child_timeout = 0;
uint8_t extended_address[8] = {0};
arm_net_interface_address_list_size(cur->id, &ipv6_address_count);
// 16 bytes for each ipv6 address
uint8_t ipv6_address_list[ipv6_address_count * 16];
tr_debug("tlv list length %d", list_len);
while (list_len --) {
switch (*tlv_list) {
case DIAGCOP_TLV_EXTENDED_MAC_ADDRESS:
if (cur->mac_api) {
//Read dynamicaly generated current extented address from MAC.
cur->mac_api->mac64_get(cur->mac_api, MAC_EXTENDED_DYNAMIC, extended_address);
}
response_ptr = thread_diagcop_tlv_data_write(response_ptr, DIAGCOP_TLV_EXTENDED_MAC_ADDRESS, 8, extended_address);
break;
case DIAGCOP_TLV_ADDRESS16:
response_ptr = thread_diagcop_tlv_data_write_uint16(response_ptr, DIAGCOP_TLV_ADDRESS16, mac_helper_mac16_address_get(cur));
break;
case DIAGCOP_TLV_MODE:
response_ptr = thread_diagcop_tlv_data_write_uint8(response_ptr, DIAGCOP_TLV_MODE, thread_diag_mode_get_by_interface_ptr(cur));
break;
case DIAGCOP_TLV_TIMEOUT:
//must be sleeping poll rate
if (cur->thread_info->thread_device_mode == THREAD_DEVICE_MODE_SLEEPY_END_DEVICE ||
cur->thread_info->thread_device_mode == THREAD_DEVICE_MODE_END_DEVICE) {
response_ptr = thread_diagcop_tlv_data_write_uint32(response_ptr, DIAGCOP_TLV_TIMEOUT, cur->thread_info->host_link_timeout);
}
break;
case DIAGCOP_TLV_CONNECTIVITY:
ptr = response_ptr;
response_ptr = thread_connectivity_tlv_write(response_ptr, cur, 0x0f);
if (ptr != response_ptr) {
ptr[0] = DIAGCOP_TLV_CONNECTIVITY;
}
break;
case DIAGCOP_TLV_ROUTE64:
ptr = response_ptr;
response_ptr = thread_route_option_write(cur, response_ptr);
if (ptr != response_ptr) {
ptr[0] = DIAGCOP_TLV_ROUTE64;
}
break;
case DIAGCOP_TLV_LEADER_DATA:
ptr = response_ptr;
response_ptr = thread_leader_data_tlv_write(response_ptr, cur);
if (ptr != response_ptr) {
ptr[0] = DIAGCOP_TLV_LEADER_DATA;
}
break;
case DIAGCOP_TLV_NETWORK_DATA:
ptr = response_ptr;
response_ptr = thread_network_data_tlv_write(cur, response_ptr, true);
if (ptr != response_ptr) {
ptr[0] = DIAGCOP_TLV_NETWORK_DATA;
}
break;
case DIAGCOP_TLV_IPV6_ADDRESS_LIST:
arm_net_address_list_get(cur->id, ipv6_address_count * 16, ipv6_address_list, &written_address_count);
response_ptr = thread_diagcop_tlv_data_write(response_ptr, DIAGCOP_TLV_IPV6_ADDRESS_LIST, ipv6_address_count * 16, ipv6_address_list);
break;
case DIAGCOP_TLV_MAC_COUNTERS:
/* The following elements from [RFC 2863] are included in this order:
* ifInUnknownProtos (4), ifInErrors (4), ifOutErrors (4), ifInUcastPkts (4),
* ifInBroadcastPkts (4), ifInDiscards (4), ifOutUcastPkts (4), ifOutBroadcastPkts (4), ifOutDiscards (4) */
response_ptr = thread_diagcop_tlv_data_write(response_ptr, DIAGCOP_TLV_MAC_COUNTERS, 36, dummy_data);
break;
case DIAGCOP_TLV_BATTERY_LEVEL:
response_ptr = thread_diagcop_tlv_data_write_uint8(response_ptr, DIAGCOP_TLV_BATTERY_LEVEL, 0);
break;
case DIAGCOP_TLV_SUPPLY_VOLTAGE:
response_ptr = thread_diagcop_tlv_data_write_uint16(response_ptr, DIAGCOP_TLV_SUPPLY_VOLTAGE, 0);
break;
case DIAGCOP_TLV_CHILD_TABLE:
if (thread_router_bootstrap_child_count_get(cur)) {
response_ptr = thread_diagnostic_child_table_tlv_build(response_ptr, cur);
}
break;
case DIAGCOP_TLV_CHANNEL_PAGES:
// Only supporting channel page 0
response_ptr = thread_diagcop_tlv_data_write_uint8(response_ptr, DIAGCOP_TLV_CHANNEL_PAGES, 0);
break;
case DIAGCOP_TLV_MAX_CHILD_TIMEOUT:
if (thread_router_bootstrap_child_max_timeout_get(cur, &max_child_timeout) == 0) {
response_ptr = thread_diagcop_tlv_data_write_uint32(response_ptr, DIAGCOP_TLV_MAX_CHILD_TIMEOUT, max_child_timeout);
}
break;
default:
break;
}
tlv_list++;
}
return response_ptr;
}
/**
* Thread diagnostics request d/dg
*/
static int thread_diagnostic_command_request_cb(int8_t service_id, uint8_t source_address[16], uint16_t source_port, sn_coap_hdr_s *request_ptr)
{
(void) source_address;
(void) source_port;
thread_diagnostic_command_t *this = thread_diagnostic_find_by_service(service_id);
protocol_interface_info_entry_t *cur;
uint8_t *ptr = NULL;
uint8_t *request_tlv_ptr = NULL;
uint16_t request_tlv_len;
int response_len;
uint8_t *response_ptr = NULL;
sn_coap_msg_code_e return_code = COAP_MSG_CODE_RESPONSE_BAD_REQUEST;
if (!this) {//check if there is request
return -1;
}
cur = protocol_stack_interface_info_get_by_id(this->interface_id);
if (!cur) {
return -1;
}
tr_debug("Thread diagnostic command get request");
request_tlv_len = thread_diagcop_tlv_find(request_ptr->payload_ptr, request_ptr->payload_len, DIAGCOP_TLV_TYPE_LIST, &request_tlv_ptr);
// the following function calculates the total memory that is needed to be allocated for response.
// If the request_tlv_len is 0 then the memory allocated is for all the diagnostic command tlvs
response_len = thread_diagnostic_configuration_calc(cur, request_tlv_ptr, request_tlv_len);
if (response_len < 1) {
if (request_tlv_len > 0) {
// TLV was ommitted but ok request. respond with ok status
goto send_response;
}
goto failure;
}
ptr = response_ptr = ns_dyn_mem_alloc(response_len);
if (!response_ptr) {
return_code = COAP_MSG_CODE_RESPONSE_INTERNAL_SERVER_ERROR;
goto failure;
}
ptr = thread_diagnostic_get_build(cur, ptr, request_tlv_ptr,
request_tlv_len);
send_response:
coap_service_response_send(this->coap_service_id, COAP_REQUEST_OPTIONS_NONE,
request_ptr, COAP_MSG_CODE_RESPONSE_CHANGED, COAP_CT_OCTET_STREAM,
response_ptr, ptr - response_ptr);
ns_dyn_mem_free(response_ptr);
return 0;
failure:
coap_service_response_send(this->coap_service_id, COAP_REQUEST_OPTIONS_NONE, request_ptr, return_code, COAP_CT_NONE, NULL, 0);
return 0;
}
/**
* Thread diagnostics reset d/dr
*/
static int thread_diagnostic_command_reset_cb(int8_t service_id, uint8_t source_address[16], uint16_t source_port, sn_coap_hdr_s *request_ptr)
{
(void) source_address;
(void) source_port;
thread_diagnostic_command_t *this = thread_diagnostic_find_by_service(service_id);
if (!this) {//check if there is request
return -1;
}
tr_debug("Thread diagnostic command reset request");
coap_service_response_send(this->coap_service_id, COAP_REQUEST_OPTIONS_NONE,
request_ptr, COAP_MSG_CODE_RESPONSE_CHANGED, COAP_CT_TEXT_PLAIN, NULL, 0);
return 0;
}
/**
* Thread diagnostics query d/dq
*/
static int thread_diagnostic_command_query_cb(int8_t service_id, uint8_t source_address[16], uint16_t source_port, sn_coap_hdr_s *request_ptr)
{
uint8_t address_copy[16];
protocol_interface_info_entry_t *cur;
uint8_t *response_ptr = NULL;
uint8_t *ptr = NULL;
uint8_t *request_tlv_ptr;
uint16_t response_len;
uint16_t request_tlv_len;
sn_coap_msg_code_e return_code = COAP_MSG_CODE_EMPTY;
int8_t response = -1;
/* Source_address is freed when calling coap_service_response_send().
* Anyhow, We need to use it when sending request, so let's make a copy! */
memcpy(address_copy, source_address, 16);
thread_diagnostic_command_t *this = thread_diagnostic_find_by_service(service_id);
if (!this) {//check if there is request
return -1;
}
cur = protocol_stack_interface_info_get_by_id(this->interface_id);
if (!cur) {
return -1;
}
tr_debug("Thread diagnostic command query request from %s:%d", trace_array(source_address, 16), source_port);
// build response
request_tlv_len = thread_diagcop_tlv_find(request_ptr->payload_ptr, request_ptr->payload_len, DIAGCOP_TLV_TYPE_LIST, &request_tlv_ptr);
// the following function calculates the total memory that is needed to be allocated for response.
// If the request_tlv_len is 0 then the memory allocated is for all the diagnostic command tlvs
response_len = thread_diagnostic_configuration_calc(cur, request_tlv_ptr, request_tlv_len);
if (response_len < 1) {
if (request_tlv_len > 0) {
// TLV was ommitted but ok request. respond with ok status
goto send_response;
}
return_code = COAP_MSG_CODE_RESPONSE_BAD_REQUEST;
goto failure;
}
ptr = response_ptr = ns_dyn_mem_alloc(response_len);
if (!response_ptr) {
return_code = COAP_MSG_CODE_RESPONSE_INTERNAL_SERVER_ERROR;
goto failure;
}
ptr = thread_diagnostic_get_build(cur, response_ptr, request_tlv_ptr, request_tlv_len);
/* Send ACK to confirmable request */
if (request_ptr->msg_type == COAP_MSG_TYPE_CONFIRMABLE) {
coap_service_response_send(this->coap_service_id, COAP_REQUEST_OPTIONS_NONE, request_ptr, return_code, COAP_CT_NONE, NULL, 0);
response = 0;
}
/* Send reply to d/da */
send_response:
coap_service_request_send(this->coap_service_id, COAP_REQUEST_OPTIONS_NONE, address_copy, source_port, COAP_MSG_TYPE_CONFIRMABLE, COAP_MSG_CODE_REQUEST_POST,
THREAD_URI_DIAGNOSTIC_ANSWER, COAP_CT_OCTET_STREAM, response_ptr, ptr - response_ptr, NULL);
ns_dyn_mem_free(response_ptr);
return response;
failure:
if (request_ptr->msg_type == COAP_MSG_TYPE_CONFIRMABLE) {
coap_service_response_send(this->coap_service_id, COAP_REQUEST_OPTIONS_NONE, request_ptr, return_code, COAP_CT_NONE, NULL, 0);
response = 0;
}
return response;
}
/**
* Public interface functions
*/
int thread_diagnostic_init(int8_t interface_id)
{
thread_diagnostic_command_t *this = thread_diagnostic_command_find(interface_id);
if (this) {
return 0;
}
this = ns_dyn_mem_alloc(sizeof(thread_diagnostic_command_t));
if (!this) {
return -2;
}
this->interface_id = interface_id;
this->coap_service_id = thread_management_server_service_id_get(interface_id);
if (this->coap_service_id < 0) {
tr_error("Thread diagnostic init failed");
ns_dyn_mem_free(this);
return -3;
}
/**
* Thread Management uri registration
*/
coap_service_register_uri(this->coap_service_id, THREAD_URI_DIAGNOSTIC_REQUEST, COAP_SERVICE_ACCESS_POST_ALLOWED, thread_diagnostic_command_request_cb);
coap_service_register_uri(this->coap_service_id, THREAD_URI_DIAGNOSTIC_RESET, COAP_SERVICE_ACCESS_POST_ALLOWED, thread_diagnostic_command_reset_cb);
coap_service_register_uri(this->coap_service_id, THREAD_URI_DIAGNOSTIC_QUERY, COAP_SERVICE_ACCESS_POST_ALLOWED, thread_diagnostic_command_query_cb);
ns_list_add_to_start(&instance_list, this);
return 0;
}
int thread_diagnostic_delete(int8_t interface_id)
{
thread_diagnostic_command_t *this = thread_diagnostic_command_find(interface_id);
if (!this) {
return -1;
}
coap_service_unregister_uri(this->coap_service_id, THREAD_URI_DIAGNOSTIC_REQUEST);
coap_service_unregister_uri(this->coap_service_id, THREAD_URI_DIAGNOSTIC_RESET);
coap_service_unregister_uri(this->coap_service_id, THREAD_URI_DIAGNOSTIC_QUERY);
ns_list_remove(&instance_list, this);
ns_dyn_mem_free(this);
return 0;
}
#endif