blob: c5cefb840aeb33ab37ceeaff4b60ff857915e1e4 [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
*******************************************************************************
******************************************************************************/
#include "client-api.h"
#include "common.h"
#include "util.h"
#include <stdarg.h>
using namespace chip;
uint8_t * emAfZclBuffer = NULL;
uint16_t emAfZclBufferLen = 0;
// Pointer to where this API should put the length
uint16_t * emAfResponseLengthPtr = NULL;
// Pointer to where the API should put the cluster ID
EmberApsFrame * emAfCommandApsFrame = NULL;
/////////////////
// Method that fills in the buffer.
// It returns number of bytes filled
// and 0 on error.
static uint16_t vFillBuffer(uint8_t * buffer, uint16_t bufferLen, uint8_t frameControl, uint16_t manufacturerCode,
CommandId commandId, const char * format, va_list argPointer)
{
uint32_t value;
uint8_t valueLen;
uint8_t * data;
uint16_t dataLen;
uint8_t i;
uint16_t bytes = 0;
// The destination buffer must be at least large enough to hold the ZCL
// overhead: frame control, manufacturer code (if applicable), sequence
// number, and command id. If it is, add these in order.
if (bufferLen < EMBER_AF_ZCL_OVERHEAD ||
(manufacturerCode != EMBER_AF_NULL_MANUFACTURER_CODE && bufferLen < EMBER_AF_ZCL_MANUFACTURER_SPECIFIC_OVERHEAD))
{
emberAfDebugPrintln("ERR: Buffer too short for ZCL header");
return 0;
}
if (manufacturerCode != EMBER_AF_NULL_MANUFACTURER_CODE)
{
frameControl |= ZCL_MANUFACTURER_SPECIFIC_MASK;
}
buffer[bytes++] = frameControl;
if (manufacturerCode != EMBER_AF_NULL_MANUFACTURER_CODE)
{
buffer[bytes++] = EMBER_LOW_BYTE(manufacturerCode);
buffer[bytes++] = EMBER_HIGH_BYTE(manufacturerCode);
}
buffer[bytes++] = emberAfNextSequence();
buffer[bytes++] = commandId;
// Each argument comes in as an integer value, a pointer to a buffer, or a
// pointer to a buffer followed by an integer length.
for (i = 0; format[i] != 0; i++)
{
char cmd;
value = 0;
valueLen = 0;
data = 0;
cmd = format[i];
if (cmd <= 's')
{
// 0--9, A--G, L, S, b, l, and s all have a pointer to a buffer. The
// length of that buffer is implied by 0--9 and A--G (0 to 16 bytes).
// For L, S, and b, a separate integer specifies the length. That length
// will precede the data in the destination buffer for L and S, which
// turns them into regular ZigBee strings. In this case, care must be
// taken to account for invalid strings, which have length 0xFFFF or 0xFF
// for L and S respectively. In the case of invalid strings, the length
// byte(s) are copied to the destination buffer but the string itself is
// not. Finally, l and s are just ZigBee strings and the length of the
// string data is contained within the buffer itself and the entire
// buffer is copied as is to the destination buffer. Note that
// emberAf(Long)StringLength handles invalid strings, but it does not
// include the length byte(s) in the total length, so the overhead
// must be maually accounted for when copying.
data = (uint8_t *) va_arg(argPointer, uint8_t *);
valueLen = 0;
if (cmd == 'L' || cmd == 'S' || cmd == 'b')
{
dataLen = (uint16_t) va_arg(argPointer, int);
if (cmd == 'L')
{
value = dataLen;
valueLen = (value == 0xFFFF ? 0 : 2);
}
else if (cmd == 'S')
{
value = (uint8_t) dataLen;
valueLen = (value == 0xFF ? 0 : 1);
}
else
{
// MISRA requires ..else if.. to have terminating else.
}
}
else if (cmd == 'l')
{
dataLen = static_cast<uint16_t>(emberAfLongStringLength(data) + 2);
}
else if (cmd == 's')
{
dataLen = static_cast<uint16_t>(emberAfStringLength(data) + 1);
}
else if ('0' <= cmd && cmd <= '9')
{
dataLen = static_cast<uint16_t>(cmd - '0');
}
else if ('A' <= cmd && cmd <= 'G')
{
dataLen = static_cast<uint16_t>(cmd - 'A' + 10);
}
else
{
emberAfDebugPrintln("ERR: Unknown format '%c'", cmd);
return 0;
}
}
else
{
// u, v, x, and w are one-, two-, three-, or four-byte integers. u and v
// must be extracted as an int while x and w come through as an uint32_t.
// In all cases, the value is copied to the destination buffer in little-
// endian format.
dataLen = 0;
if (cmd == 'u')
{
valueLen = 1;
}
else if (cmd == 'v')
{
valueLen = 2;
}
else if (cmd == 'x')
{
valueLen = 3;
}
else if (cmd == 'w')
{
valueLen = 4;
}
else
{
emberAfDebugPrintln("ERR: Unknown format '%c'", cmd);
return 0;
}
value = valueLen <= 2 ? static_cast<uint32_t>(va_arg(argPointer, int)) : va_arg(argPointer, uint32_t);
}
// The destination buffer must be at least as large as the running total
// plus the length of the integer value (if applicable) plus the length of
// the data (if applicable).
if (bufferLen < bytes + dataLen + valueLen)
{
emberAfDebugPrintln("ERR: Buffer too short for %d bytes for format '%c'", dataLen + valueLen, cmd);
return 0;
}
// If there is an integer value, add it to destination buffer in little-
// endian format.
for (; 0 < valueLen; valueLen--)
{
buffer[bytes++] = EMBER_LOW_BYTE(value);
value = value >> 8;
}
// Finally, if there is data, add it to the destination buffer as is. If
// the data length is zero, data may actually be NULL. Even if the length
// argument is zero, passing NULL as either the source or destination to
// memcpy is invalid and the behavior is undefined. We avoid that with an
// explicit check.
if (dataLen != 0)
{
if (data == NULL)
{
emberAfDebugPrintln("ERR: Missing data for %d bytes for format '%c'", dataLen, cmd);
return 0;
}
memcpy(buffer + bytes, data, dataLen);
bytes = static_cast<uint16_t>(bytes + dataLen);
}
}
return bytes;
}
////////////////////// Public API ////////////////////////
void emberAfSetExternalBuffer(uint8_t * buffer, uint16_t bufferLen, uint16_t * lenPtr, EmberApsFrame * apsFrame)
{
emAfZclBuffer = buffer;
emAfZclBufferLen = bufferLen;
emAfResponseLengthPtr = lenPtr;
emAfCommandApsFrame = apsFrame;
}
uint16_t emberAfFillExternalManufacturerSpecificBuffer(uint8_t frameControl, ClusterId clusterId, uint16_t manufacturerCode,
CommandId commandId, const char * format, ...)
{
uint16_t returnValue;
va_list argPointer;
va_start(argPointer, format);
returnValue = vFillBuffer(emAfZclBuffer, emAfZclBufferLen, frameControl, manufacturerCode, commandId, format, argPointer);
va_end(argPointer);
*emAfResponseLengthPtr = returnValue;
emAfCommandApsFrame->clusterId = clusterId;
emAfCommandApsFrame->options = EMBER_AF_DEFAULT_APS_OPTIONS;
return returnValue;
}
uint16_t emberAfFillExternalBuffer(uint8_t frameControl, ClusterId clusterId, CommandId commandId, const char * format, ...)
{
uint16_t returnValue;
va_list argPointer;
va_start(argPointer, format);
returnValue =
vFillBuffer(emAfZclBuffer, emAfZclBufferLen, frameControl, EMBER_AF_NULL_MANUFACTURER_CODE, commandId, format, argPointer);
va_end(argPointer);
*emAfResponseLengthPtr = returnValue;
emAfCommandApsFrame->clusterId = clusterId;
emAfCommandApsFrame->options = EMBER_AF_DEFAULT_APS_OPTIONS;
return returnValue;
}
uint16_t emberAfFillBuffer(uint8_t * buffer, uint16_t bufferLen, uint8_t frameControl, CommandId commandId, const char * format,
...)
{
uint16_t returnValue;
va_list argPointer;
va_start(argPointer, format);
returnValue = vFillBuffer(buffer, bufferLen, frameControl, EMBER_AF_NULL_MANUFACTURER_CODE, commandId, format, argPointer);
va_end(argPointer);
return returnValue;
}
EmberStatus emberAfSendCommandUnicastToBindingsWithCallback(EmberAfMessageSentFunction callback)
{
return emberAfSendUnicastToBindingsWithCallback(emAfCommandApsFrame, *emAfResponseLengthPtr, emAfZclBuffer, callback);
}
EmberStatus emberAfSendCommandUnicastToBindings(void)
{
return emberAfSendCommandUnicastToBindingsWithCallback(NULL);
}
// EmberStatus emberAfSendCommandMulticastWithCallback(GroupId multicastId, EmberAfMessageSentFunction callback)
// {
// return emberAfSendMulticastWithCallback(multicastId, emAfCommandApsFrame, *emAfResponseLengthPtr, emAfZclBuffer, callback);
// }
// EmberStatus emberAfSendCommandMulticastWithAliasWithCallback(GroupId multicastId, EmberNodeId alias, uint8_t sequence,
// EmberAfMessageSentFunction callback)
// {
// return emberAfSendMulticastWithAliasWithCallback(multicastId, emAfCommandApsFrame, *emAfResponseLengthPtr, emAfZclBuffer,
// alias,
// sequence, callback);
// }
// EmberStatus emberAfSendCommandMulticast(GroupId multicastId)
// {
// return emberAfSendCommandMulticastWithCallback(multicastId, NULL);
// }
// EmberStatus emberAfSendCommandMulticastWithAlias(GroupId multicastId, EmberNodeId alias, uint8_t sequence)
// {
// return emberAfSendCommandMulticastWithAliasWithCallback(multicastId, alias, sequence, NULL);
// }
EmberStatus emberAfSendCommandMulticastToBindings(void)
{
return emberAfSendMulticastToBindings(emAfCommandApsFrame, *emAfResponseLengthPtr, emAfZclBuffer);
}
// EmberStatus emberAfSendCommandUnicastWithCallback(EmberOutgoingMessageType type, uint16_t indexOrDestination,
// EmberAfMessageSentFunction callback)
// {
// return emberAfSendUnicastWithCallback(type, indexOrDestination, emAfCommandApsFrame, *emAfResponseLengthPtr, emAfZclBuffer,
// callback);
// }
// EmberStatus emberAfSendCommandUnicast(EmberOutgoingMessageType type, uint16_t indexOrDestination)
// {
// return emberAfSendCommandUnicastWithCallback(type, indexOrDestination, NULL);
// }
// EmberStatus emberAfSendCommandBroadcastWithCallback(EmberNodeId destination, EmberAfMessageSentFunction callback)
// {
// return emberAfSendBroadcastWithCallback(destination, emAfCommandApsFrame, *emAfResponseLengthPtr, emAfZclBuffer, callback);
// }
// EmberStatus emberAfSendCommandBroadcastWithAliasWithCallback(EmberNodeId destination, EmberNodeId alias, uint8_t sequence,
// EmberAfMessageSentFunction callback)
// {
// return emberAfSendBroadcastWithAliasWithCallback(destination, emAfCommandApsFrame, *emAfResponseLengthPtr, emAfZclBuffer,
// alias,
// sequence, callback);
// }
// EmberStatus emberAfSendCommandBroadcastWithAlias(EmberNodeId destination, EmberNodeId alias, uint8_t sequence)
// {
// return emberAfSendCommandBroadcastWithAliasWithCallback(destination, alias, sequence, NULL);
// }
// EmberStatus emberAfSendCommandBroadcast(EmberNodeId destination)
// {
// return emberAfSendCommandBroadcastWithCallback(destination, NULL);
// }
// EmberStatus emberAfSendCommandInterPan(EmberPanId panId, const EmberEUI64 destinationLongId, EmberNodeId destinationShortId,
// GroupId multicastId)
// {
// return emberAfSendInterPan(panId, destinationLongId, destinationShortId, multicastId, emAfCommandApsFrame->clusterId,
// *emAfResponseLengthPtr, emAfZclBuffer);
// }
EmberApsFrame * emberAfGetCommandApsFrame(void)
{
return emAfCommandApsFrame;
}
void emberAfSetCommandEndpoints(EndpointId sourceEndpoint, EndpointId destinationEndpoint)
{
emAfCommandApsFrame->sourceEndpoint = sourceEndpoint;
emAfCommandApsFrame->destinationEndpoint = destinationEndpoint;
}