| /** |
| * |
| * 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; |
| } |