blob: 624de08acb70c68501c3be464dffa734e9f5dbad [file] [log] [blame]
/**
*
* Copyright (c) 2020 Project CHIP Authors
*
* 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.
*/
/**
*
* Copyright (c) 2020 Silicon Labs
*
* 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
* @brief Code common to both the Host and SOC (system
*on a chip) versions of the Application Framework.
*******************************************************************************
******************************************************************************/
//#include PLATFORM_HEADER // Micro and compiler specific typedefs and macros
#if defined EZSP_HOST
#include "stack/include/ember-types.h"
#include "stack/include/error.h"
#include "stack/include/library.h"
#else
// Ember stack and related utilities
//#include "stack/include/cbke-crypto-engine.h"
//#include "stack/include/ember.h" // Main stack definitions
#endif
// HAL - hardware abstraction layer
//#include "hal/hal.h"
//#include "plugin/serial/serial.h" // Serial utility APIs
// CLI - command line interface
//#include "app/util/serial/command-interpreter2.h"
#if defined EZSP_HOST
// EZSP - ember serial host protocol
#include "app/util/ezsp/ezsp-protocol.h"
#include "app/util/ezsp/ezsp-utils.h"
#include "app/util/ezsp/ezsp.h"
#include "app/util/ezsp/serial-interface.h"
#endif
// Sub-GHz client, for a last-minute chance to block sending ZCL messgaes within the suspend period
#ifdef EMBER_AF_PLUGIN_SUB_GHZ_CLIENT
#include "app/framework/plugin/sub-ghz-client/sub-ghz-client.h"
#endif
// Sub-GHz server, for an automatic reply if a client attempt to communicate within the suspend period
#ifdef EMBER_AF_PLUGIN_SUB_GHZ_SERVER
#include "app/framework/plugin/sub-ghz-server/sub-ghz-server.h"
#endif
// Fragmentation.
#ifdef EMBER_AF_PLUGIN_FRAGMENTATION
#include "app/framework/plugin/fragmentation/fragmentation.h"
#endif
#ifdef EMBER_AF_PLUGIN_CRITICAL_MESSAGE_QUEUE
#include "app/framework/plugin/critical-message-queue/critical-message-queue.h"
#endif // EMBER_AF_PLUGIN_CRITICAL_MESSAGE_QUEUE
// Service discovery library
//#include "service-discovery.h"
// determines the number of in-clusters and out-clusters based on defines
// in config.h
#include <app/util/af-main.h>
//#include "app/framework/security/af-security.h"
//#include "app/framework/security/crypto-state.h"
#include "app/util/common.h"
#include <app-common/zap-generated/callback.h>
#include <app/util/attribute-storage.h>
#include <app/util/attribute-table.h>
#include <app/util/config.h>
//#include "print.h"
#include <app/util/binding-table.h>
#include <app/util/chip-message-send.h>
#include <app/util/util.h>
using namespace chip;
// Querying the Ember Stack for what libraries are present.
//#include "app/util/common/library.h"
// ZDO - ZigBee Device Object
//#include "app/util/zigbee-framework/zigbee-device-common.h"
//#include "app/framework/plugin/ota-storage-common/ota-storage.h"
//#include "app/framework/plugin/partner-link-key-exchange/partner-link-key-exchange.h"
//------------------------------------------------------------------------------
#define INVALID_MESSAGE_TAG 0xFF
#if defined(EMBER_AF_HAS_COORDINATOR_NETWORK)
#if !defined(EMBER_AF_PLUGIN_CONCENTRATOR)
#error "A Coordinator device (Trust Center) MUST enable the concentrator plugin to function correctly."
#endif
#endif
#ifdef EMBER_AF_GENERATED_PLUGIN_STACK_STATUS_FUNCTION_DECLARATIONS
EMBER_AF_GENERATED_PLUGIN_STACK_STATUS_FUNCTION_DECLARATIONS
#endif
#ifdef EMBER_AF_GENERATED_PLUGIN_MESSAGE_SENT_FUNCTION_DECLARATIONS
EMBER_AF_GENERATED_PLUGIN_MESSAGE_SENT_FUNCTION_DECLARATIONS
#endif
#ifdef EMBER_AF_GENERATED_PLUGIN_ZDO_MESSAGE_RECEIVED_FUNCTION_DECLARATIONS
EMBER_AF_GENERATED_PLUGIN_ZDO_MESSAGE_RECEIVED_FUNCTION_DECLARATIONS
#endif
static CallbackTableEntry messageSentCallbacks[EMBER_AF_MESSAGE_SENT_CALLBACK_TABLE_SIZE];
// We declare this variable 'const' but NOT const. Those functions that we may use
// this variable would also have to declare it const in order to function
// correctly, which is not the case (e.g. emberFindKeyTableEntry()).
const EmberEUI64 emberAfNullEui64 = { 0, 0, 0, 0, 0, 0, 0, 0 };
//------------------------------------------------------------------------------
// Forward declarations
static uint8_t getMessageSentCallbackIndex(void);
static void invalidateMessageSentCallbackEntry(uint8_t messageTag);
static EmberAfMessageSentFunction getMessageSentCallback(uint8_t tag);
static uint8_t getMessageSentCallbackIndex(void)
{
uint8_t i;
for (i = 0; i < EMBER_AF_MESSAGE_SENT_CALLBACK_TABLE_SIZE; i++)
{
if (messageSentCallbacks[i].tag == INVALID_MESSAGE_TAG)
{
return i;
}
}
return INVALID_MESSAGE_TAG;
}
static void invalidateMessageSentCallbackEntry(uint8_t tag)
{
uint8_t i;
for (i = 0; i < EMBER_AF_MESSAGE_SENT_CALLBACK_TABLE_SIZE; i++)
{
if (messageSentCallbacks[i].tag == tag)
{
messageSentCallbacks[i].tag = INVALID_MESSAGE_TAG;
messageSentCallbacks[i].callback = NULL;
return;
}
}
}
static EmberAfMessageSentFunction getMessageSentCallback(uint8_t tag)
{
uint8_t i;
for (i = 0; i < EMBER_AF_MESSAGE_SENT_CALLBACK_TABLE_SIZE; i++)
{
if (messageSentCallbacks[i].tag == tag)
{
return messageSentCallbacks[i].callback;
}
}
return NULL;
}
void emAfInitializeMessageSentCallbackArray(void)
{
uint8_t i;
for (i = 0; i < EMBER_AF_MESSAGE_SENT_CALLBACK_TABLE_SIZE; i++)
{
messageSentCallbacks[i].tag = INVALID_MESSAGE_TAG;
messageSentCallbacks[i].callback = NULL;
}
}
static EmberStatus send(const MessageSendDestination & destination, EmberApsFrame * apsFrame, uint16_t messageLength,
uint8_t * message, bool broadcast, EmberNodeId alias, uint8_t sequence, EmberAfMessageSentFunction callback)
{
EmberStatus status;
uint16_t index;
uint8_t messageSentIndex;
uint8_t messageTag = INVALID_MESSAGE_TAG;
// The send APIs only deal with ZCL messages, so they must at least contain
// the ZCL header.
if (messageLength < EMBER_AF_ZCL_OVERHEAD)
{
return EMBER_ERR_FATAL;
}
else if ((message[0] & ZCL_MANUFACTURER_SPECIFIC_MASK) != 0U)
{
if (messageLength < EMBER_AF_ZCL_MANUFACTURER_SPECIFIC_OVERHEAD)
{
return EMBER_ERR_FATAL;
}
}
messageSentIndex = getMessageSentCallbackIndex();
if (callback != NULL && messageSentIndex == INVALID_MESSAGE_TAG)
{
return EMBER_TABLE_FULL;
}
// The source endpoint in the APS frame MUST be valid at this point. We use
// it to set the appropriate outgoing network in the APS frame.
EmberAfEndpointInfoStruct endpointInfo;
uint8_t networkIndex = 0;
if (emberAfGetEndpointInfoCallback(apsFrame->sourceEndpoint, &networkIndex, &endpointInfo))
{
// status = emberAfPushNetworkIndex(networkIndex);
// if (status != EMBER_SUCCESS)
// {
// return status;
// }
}
else
{
index = emberAfIndexFromEndpoint(apsFrame->sourceEndpoint);
if (index == 0xFFFF)
{
return EMBER_INVALID_ENDPOINT;
}
// status = emberAfPushEndpointNetworkIndex(apsFrame->sourceEndpoint);
// if (status != EMBER_SUCCESS)
// {
// return status;
// }
}
#ifdef EMBER_AF_PLUGIN_SUB_GHZ_CLIENT
// If the Sub-GHz client is present and currently in the "suspended" state,
// block any outgoing message unless it comes from the Sub-GHz client itself.
if (emberAfPluginSubGhzClientIsSendingZclMessagesSuspended() && apsFrame->clusterId != ZCL_SUB_GHZ_CLUSTER_ID)
{
return EMBER_TRANSMISSION_SUSPENDED;
}
#endif
{
EmberAfMessageStruct messageStruct = {
callback, apsFrame, message, destination, messageLength, broadcast,
};
// Called prior to fragmentation in case the mesasge does not go out over the
// Zigbee radio, and instead goes to some other transport that does not require
// low level ZigBee fragmentation.
if (emberAfPreMessageSendCallback(&messageStruct, &status))
{
return status;
}
}
// SE 1.4 requires an option to disable APS ACK and Default Response
emAfApplyDisableDefaultResponse(&message[0]);
emAfApplyRetryOverride(&apsFrame->options);
if (messageLength <= EMBER_AF_MAXIMUM_SEND_PAYLOAD_LENGTH)
{
status = emAfSend(destination, apsFrame, messageLength, message, &messageTag, alias, sequence);
}
else
{
status = EMBER_MESSAGE_TOO_LONG;
}
#ifdef EMBER_AF_PLUGIN_CRITICAL_MESSAGE_QUEUE
// If this was a critical message queue entry, fire the callback
if ((status != EMBER_SUCCESS) &&
(callback == emberAfPluginCriticalMessageQueueEnqueueCallback || callback == emAfPluginCriticalMessageQueueRetryCallback))
{
callback(destination, apsFrame, messageLength, message, status);
}
#endif // EMBER_AF_PLUGIN_CRITICAL_MESSAGE_QUEUE
if (callback != NULL && status == EMBER_SUCCESS && messageTag != INVALID_MESSAGE_TAG &&
messageSentIndex < EMBER_AF_MESSAGE_SENT_CALLBACK_TABLE_SIZE)
{
messageSentCallbacks[messageSentIndex].tag = messageTag;
messageSentCallbacks[messageSentIndex].callback = callback;
}
if (status == EMBER_OPERATION_IN_PROGRESS && apsFrame->options & EMBER_APS_OPTION_DSA_SIGN)
{
// We consider "in progress" signed messages as being sent successfully.
// The stack will send the message after signing.
status = EMBER_SUCCESS;
// TODO: Can we actually hit this case in CHIP, or can this whole block
// go away?
// emAfSetCryptoOperationInProgress();
}
if (status == EMBER_SUCCESS)
{
emberAfAddToCurrentAppTasks(EMBER_AF_WAITING_FOR_DATA_ACK | EMBER_AF_WAITING_FOR_ZCL_RESPONSE);
}
// emberAfPopNetworkIndex();
return status;
}
EmberStatus emberAfSendMulticastWithAliasWithCallback(GroupId multicastId, EmberApsFrame * apsFrame, uint16_t messageLength,
uint8_t * message, EmberNodeId alias, uint8_t sequence,
EmberAfMessageSentFunction callback)
{
apsFrame->groupId = multicastId;
const auto destination = MessageSendDestination::MulticastWithAlias(multicastId);
return send(destination, apsFrame, messageLength, message,
true, // broadcast
alias, sequence, callback);
}
EmberStatus emberAfSendMulticastWithCallback(GroupId multicastId, EmberApsFrame * apsFrame, uint16_t messageLength,
uint8_t * message, EmberAfMessageSentFunction callback)
{
apsFrame->groupId = multicastId;
const auto destination = MessageSendDestination::Multicast(multicastId);
return send(destination, apsFrame, messageLength, message,
true, // broadcast?
0, // alias
0, // sequence
callback);
}
EmberStatus emberAfSendMulticast(GroupId multicastId, EmberApsFrame * apsFrame, uint16_t messageLength, uint8_t * message)
{
return emberAfSendMulticastWithCallback(multicastId, apsFrame, messageLength, message, NULL);
}
EmberStatus emberAfSendMulticastToBindings(EmberApsFrame * apsFrame, uint16_t messageLength, uint8_t * message)
{
EmberStatus status = EMBER_INVALID_BINDING_INDEX;
uint8_t i;
EmberBindingTableEntry binding;
GroupId groupDest;
if ((NULL == apsFrame) || (0 == messageLength) || (NULL == message))
{
return EMBER_BAD_ARGUMENT;
}
for (i = 0; i < EMBER_BINDING_TABLE_SIZE; i++)
{
status = emberGetBinding(i, &binding);
if (status != EMBER_SUCCESS)
{
return status;
}
if (binding.type == EMBER_MULTICAST_BINDING && binding.local == apsFrame->sourceEndpoint &&
binding.clusterId == apsFrame->clusterId)
{
groupDest = binding.groupId;
apsFrame->groupId = groupDest;
apsFrame->destinationEndpoint = binding.remote;
status = emberAfSendMulticast(groupDest, // multicast ID
apsFrame, messageLength, message);
if (status != EMBER_SUCCESS)
{
return status;
}
}
}
return status;
}
// TODO: Figure out whether we need emberAfSendBroadcastWithCallback at all.
// EmberStatus emberAfSendBroadcastWithCallback(EmberNodeId destination, EmberApsFrame * apsFrame, uint16_t messageLength,
// uint8_t * message, EmberAfMessageSentFunction callback)
//{
// return send(EMBER_OUTGOING_BROADCAST, destination, apsFrame, messageLength, message,
// true, // broadcast?
// 0, // alias
// 0, // sequence
// callback);
//}
// TODO: Figure out whether we need emberAfSendBroadcastWithAliasWithCallback at all.
// EmberStatus emberAfSendBroadcastWithAliasWithCallback(EmberNodeId destination, EmberApsFrame * apsFrame, uint16_t messageLength,
// uint8_t * message, EmberNodeId alias, uint8_t sequence,
// EmberAfMessageSentFunction callback)
//{
// return send(EMBER_OUTGOING_BROADCAST_WITH_ALIAS, destination, apsFrame, messageLength, message,
// true, // broadcast?
// alias, // alias
// sequence, // sequence
// callback);
//}
// TODO: Figure out whether we need emberAfSendBroadcast at all.
// EmberStatus emberAfSendBroadcast(EmberNodeId destination, EmberApsFrame * apsFrame, uint16_t messageLength, uint8_t * message)
//{
// return emberAfSendBroadcastWithCallback(destination, apsFrame, messageLength, message, NULL);
//}
EmberStatus emberAfSendUnicastWithCallback(const MessageSendDestination & destination, EmberApsFrame * apsFrame,
uint16_t messageLength, uint8_t * message, EmberAfMessageSentFunction callback)
{
// The source endpoint in the APS frame MAY NOT be valid at this point if the
// outgoing type is "via binding."
if (destination.IsViaBinding())
{
// If using binding, set the endpoints based on those in the binding. The
// cluster in the binding is not used because bindings can be used to send
// messages with any cluster id, not just the one set in the binding.
EmberBindingTableEntry binding;
// TODO: This cast should go away once
// https://github.com/project-chip/connectedhomeip/issues/3584 is fixed.
EmberStatus status = emberGetBinding(destination.GetBindingIndex(), &binding);
if (status != EMBER_SUCCESS)
{
return status;
}
apsFrame->sourceEndpoint = binding.local;
apsFrame->destinationEndpoint = binding.remote;
}
return send(destination, apsFrame, messageLength, message,
false, // broadcast?
0, // alias
0, // sequence
callback);
}
EmberStatus emberAfSendUnicast(const MessageSendDestination & destination, EmberApsFrame * apsFrame, uint16_t messageLength,
uint8_t * message)
{
return emberAfSendUnicastWithCallback(destination, apsFrame, messageLength, message, NULL);
}
EmberStatus emberAfSendUnicastToBindingsWithCallback(EmberApsFrame * apsFrame, uint16_t messageLength, uint8_t * message,
EmberAfMessageSentFunction callback)
{
EmberStatus status = EMBER_INVALID_BINDING_INDEX;
uint8_t i;
for (i = 0; i < EMBER_BINDING_TABLE_SIZE; i++)
{
EmberBindingTableEntry binding;
status = emberGetBinding(i, &binding);
if (status != EMBER_SUCCESS)
{
return status;
}
if (binding.type == EMBER_UNICAST_BINDING && binding.local == apsFrame->sourceEndpoint &&
binding.clusterId == apsFrame->clusterId)
{
apsFrame->destinationEndpoint = binding.remote;
const MessageSendDestination destination = MessageSendDestination::ViaBinding(i);
status = send(destination, apsFrame, messageLength, message,
false, // broadcast?
0, // alias
0, // sequence
callback);
if (status != EMBER_SUCCESS)
{
return status;
}
}
}
return status;
}
EmberStatus emberAfSendUnicastToBindings(EmberApsFrame * apsFrame, uint16_t messageLength, uint8_t * message)
{
return emberAfSendUnicastToBindingsWithCallback(apsFrame, messageLength, message, NULL);
}
EmberStatus emberAfSendInterPan(EmberPanId panId, const EmberEUI64 destinationLongId, EmberNodeId destinationShortId,
GroupId multicastId, ClusterId clusterId, uint16_t messageLength, uint8_t * messageBytes)
{
EmberAfInterpanHeader header;
memset(&header, 0, sizeof(EmberAfInterpanHeader));
header.panId = panId;
header.shortAddress = destinationShortId;
if (destinationLongId != NULL)
{
memmove(header.longAddress, destinationLongId, EUI64_SIZE);
header.options |= EMBER_AF_INTERPAN_OPTION_MAC_HAS_LONG_ADDRESS;
header.messageType = EMBER_AF_INTER_PAN_UNICAST;
}
else if (multicastId != 0)
{
header.groupId = multicastId;
header.messageType = EMBER_AF_INTER_PAN_MULTICAST;
}
else
{
header.messageType =
(destinationShortId < EMBER_BROADCAST_ADDRESS ? EMBER_AF_INTER_PAN_UNICAST : EMBER_AF_INTER_PAN_BROADCAST);
}
header.clusterId = clusterId;
return emberAfInterpanSendMessageCallback(&header, messageLength, messageBytes);
}
void emberAfPrintMessageData(uint8_t * data, uint16_t length)
{
#if defined EMBER_AF_PRINT_APP
emberAfAppPrint(" payload (len %2x) [", length);
emberAfAppPrintBuffer(data, length, true);
emberAfAppPrintln("]");
#endif
}
void emAfPrintStatus(const char * task, EmberStatus status)
{
if (status == EMBER_SUCCESS)
{
emberAfPrint(emberAfPrintActiveArea, "%p: %p", "Success", task);
}
else
{
emberAfPrint(emberAfPrintActiveArea, "%p: %p: 0x%x", "Error", task, status);
}
}
// ******************************************************************
// Functions called by the Serial Command Line Interface (CLI)
// ******************************************************************
static void printMessage(EmberApsFrame * apsFrame, uint16_t messageLength, uint8_t * messageContents)
{
emberAfAppPrint("Cluster: " ChipLogFormatMEI ", %d bytes,", ChipLogValueMEI(apsFrame->clusterId), messageLength);
if (messageLength >= 3)
{
emberAfAppPrint(" ZCL %p Cmd ID: %d", (messageContents[0] & ZCL_CLUSTER_SPECIFIC_COMMAND ? "Cluster" : "Global"),
messageContents[2]);
}
emberAfAppPrintln("%s", "");
}
void emAfMessageSentHandler(const MessageSendDestination & destination, EmberApsFrame * apsFrame, EmberStatus status,
uint16_t messageLength, uint8_t * messageContents, uint8_t messageTag)
{
EmberAfMessageSentFunction callback;
if (status != EMBER_SUCCESS)
{
emberAfAppPrint("%ptx %x, ", "ERROR: ", status);
printMessage(apsFrame, messageLength, messageContents);
}
callback = getMessageSentCallback(messageTag);
invalidateMessageSentCallbackEntry(messageTag);
emberAfRemoveFromCurrentAppTasks(EMBER_AF_WAITING_FOR_DATA_ACK);
if (messageContents != NULL && messageContents[0] & ZCL_CLUSTER_SPECIFIC_COMMAND)
{
emberAfClusterMessageSentWithMfgCodeCallback(
destination, apsFrame, messageLength, messageContents, status,
// If the manufacturer specific flag is set
// get read it as next part of message
// else use null code.
(((messageContents[0] & ZCL_MANUFACTURER_SPECIFIC_MASK) == ZCL_MANUFACTURER_SPECIFIC_MASK)
? emberAfGetInt16u(messageContents, 1, messageLength)
: EMBER_AF_NULL_MANUFACTURER_CODE));
}
if (callback != NULL)
{
(*callback)(destination, apsFrame, messageLength, messageContents, status);
}
#ifdef EMBER_AF_GENERATED_PLUGIN_MESSAGE_SENT_FUNCTION_CALLS
EMBER_AF_GENERATED_PLUGIN_MESSAGE_SENT_FUNCTION_CALLS
#endif
emberAfMessageSentCallback(destination, apsFrame, messageLength, messageContents, status);
}
#ifdef EMBER_AF_PLUGIN_FRAGMENTATION
void emAfFragmentationMessageSentHandler(const MessageSendDestination & destination, EmberApsFrame * apsFrame, uint8_t * buffer,
uint16_t bufLen, EmberStatus status, uint8_t messageTag)
{
// the fragmented message is no longer in process
emberAfDebugPrintln("%pend.", "Fragmentation:");
emAfMessageSentHandler(destination, apsFrame, status, bufLen, buffer, messageTag);
// EMZIGBEE-4437: setting back the buffers to the original in case someone set
// that to something else.
emberAfSetExternalBuffer(appResponseData, EMBER_AF_RESPONSE_BUFFER_LEN, &appResponseLength, &emberAfResponseApsFrame);
}
#endif // EMBER_AF_PLUGIN_FRAGMENTATION
EmberStatus emAfSend(const MessageSendDestination & destination, EmberApsFrame * apsFrame, uint16_t messageLength,
uint8_t * message, uint8_t * messageTag, EmberNodeId alias, uint8_t sequence)
{
// TODO: There's an impedance mismatch here in a few ways:
// 1) The caller expects to get a messageTag out that will identify this
// message somewhat uniquely. Right now we just ignore that and claim an
// invalid tag, which means message-sent callbacks don't get called.
//
// 2) The caller expects us to call emAfMessageSentHandler when we get an
// ack or time out, and pass it the original contents of the message, so it
// can invoke message-sent callbacks as needed. But we may not have the
// contents of the message if we've done in-place encryption since then.
// Need to figure out whether any of this matters.
//
// https://github.com/project-chip/connectedhomeip/issues/2435 sort of
// tracks this.
*messageTag = INVALID_MESSAGE_TAG;
EmberStatus status = EMBER_SUCCESS;
if (destination.IsViaBinding())
{
EmberBindingTableEntry binding;
status = emberGetBinding(destination.GetBindingIndex(), &binding);
if (status != EMBER_SUCCESS)
{
return status;
}
if (binding.type != EMBER_UNICAST_BINDING)
{
return EMBER_INVALID_BINDING_INDEX;
}
return chipSendUnicast(binding.nodeId, apsFrame, messageLength, message);
}
else if (destination.IsDirect())
{
return chipSendUnicast(destination.GetDirectNodeId(), apsFrame, messageLength, message);
}
else if (destination.IsViaExchange())
{
return chipSendUnicast(destination.GetExchangeContext(), apsFrame, messageLength, message);
}
else
{
// No implementation yet.
return EMBER_ERR_FATAL;
}
}