Important changes to repositories hosted on mbed.com
Mbed hosted mercurial repositories are deprecated and are due to be permanently deleted in July 2026.
To keep a copy of this software download the repository Zip archive or clone locally using Mercurial.
It is also possible to export all your personal repositories from the account settings page.
Dependents: 5_Dragonfly_Cayenne_Sprint_IKS01A1
Fork of Cayenne-MQTT-mbed-MTSAS by
CayenneUtils.c
00001 /* 00002 The MIT License(MIT) 00003 00004 Cayenne MQTT Client Library 00005 Copyright (c) 2016 myDevices 00006 00007 Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated 00008 documentation files(the "Software"), to deal in the Software without restriction, including without limitation 00009 the rights to use, copy, modify, merge, publish, distribute, sublicense, and / or sell copies of the Software, 00010 and to permit persons to whom the Software is furnished to do so, subject to the following conditions : 00011 The above copyright notice and this permission notice shall be included in all copies or substantial portions of the Software. 00012 THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE 00013 WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT.IN NO EVENT SHALL THE AUTHORS OR 00014 COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, 00015 ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. 00016 00017 */ 00018 00019 #include <stdlib.h> 00020 #include <string.h> 00021 #include "CayenneUtils.h" 00022 00023 #ifdef DIGITAL_AND_ANALOG_SUPPORT 00024 #ifdef PARSE_INFO_PAYLOADS 00025 #define PARSE_TOPICS_COUNT 13 00026 #else 00027 #define PARSE_TOPICS_COUNT 6 00028 #endif 00029 #else 00030 #ifdef PARSE_INFO_PAYLOADS 00031 #define PARSE_TOPICS_COUNT 7 00032 #else 00033 #define PARSE_TOPICS_COUNT 2 00034 #endif 00035 #endif 00036 00037 #define THINGS_STRING CAYENNE_PSTR("/things/") 00038 00039 typedef struct TopicChannel 00040 { 00041 CayenneTopic topic; 00042 unsigned int channel; 00043 } TopicChannel; 00044 00045 /** 00046 * Build a specified topic string. 00047 * @param[out] topic Returned topic string 00048 * @param[in] length CayenneTopic buffer length 00049 * @param[in] username Cayenne username 00050 * @param[in] clientID Cayennne client ID 00051 * @param[in] suffix The topic suffix 00052 * @return CAYENNE_SUCCESS if topic string was created, error code otherwise 00053 */ 00054 int buildTopic(char* topic, size_t length, const char* username, const char* clientID, const char* suffix) { 00055 size_t topicLength = 0; 00056 if (!topic || !username || !clientID || !suffix) 00057 return CAYENNE_FAILURE; 00058 topicLength = strlen(username) + strlen(clientID) + strlen(suffix) + 11; 00059 if (topicLength > length) 00060 return CAYENNE_BUFFER_OVERFLOW; 00061 00062 topic[0] = '\0'; 00063 strcat(topic, CAYENNE_VERSION); 00064 strcat(topic, "/"); 00065 strcat(topic, username); 00066 CAYENNE_STRCAT(topic, THINGS_STRING); 00067 strcat(topic, clientID); 00068 strcat(topic, "/"); 00069 strcat(topic, suffix); 00070 return CAYENNE_SUCCESS; 00071 } 00072 00073 /** 00074 * Build a specified topic suffix string. 00075 * @param[out] suffix Returned suffix string 00076 * @param[in] length Suffix buffer length 00077 * @param[in] topic Cayenne topic 00078 * @param[in] channel The topic channel, CAYENNE_NO_CHANNEL for none, CAYENNE_ALL_CHANNELS for all 00079 * @return CAYENNE_SUCCESS if suffix string was created, error code otherwise 00080 */ 00081 int buildSuffix(char* suffix, size_t length, const CayenneTopic topic, unsigned int channel) { 00082 char* topicString = NULL; 00083 if (!suffix) 00084 return CAYENNE_FAILURE; 00085 switch (topic) 00086 { 00087 case COMMAND_TOPIC: 00088 topicString = COMMAND_STRING; 00089 break; 00090 case CONFIG_TOPIC: 00091 topicString = CONFIG_STRING; 00092 break; 00093 case DATA_TOPIC: 00094 topicString = DATA_STRING; 00095 break; 00096 case RESPONSE_TOPIC: 00097 topicString = RESPONSE_STRING; 00098 break; 00099 case SYS_MODEL_TOPIC: 00100 topicString = SYS_MODEL_STRING; 00101 break; 00102 case SYS_VERSION_TOPIC: 00103 topicString = SYS_VERSION_STRING; 00104 break; 00105 case SYS_CPU_MODEL_TOPIC: 00106 topicString = SYS_CPU_MODEL_STRING; 00107 break; 00108 case SYS_CPU_SPEED_TOPIC: 00109 topicString = SYS_CPU_SPEED_STRING; 00110 break; 00111 #ifdef DIGITAL_AND_ANALOG_SUPPORT 00112 case DIGITAL_TOPIC: 00113 topicString = DIGITAL_STRING; 00114 break; 00115 case DIGITAL_COMMAND_TOPIC: 00116 topicString = DIGITAL_COMMAND_STRING; 00117 break; 00118 case DIGITAL_CONFIG_TOPIC: 00119 topicString = DIGITAL_CONFIG_STRING; 00120 break; 00121 case ANALOG_TOPIC: 00122 topicString = ANALOG_STRING; 00123 break; 00124 case ANALOG_COMMAND_TOPIC: 00125 topicString = ANALOG_COMMAND_STRING; 00126 break; 00127 case ANALOG_CONFIG_TOPIC: 00128 topicString = ANALOG_CONFIG_STRING; 00129 break; 00130 #endif 00131 default: 00132 return CAYENNE_FAILURE; 00133 } 00134 00135 if (!topicString) 00136 return CAYENNE_FAILURE; 00137 if (CAYENNE_STRLEN(topicString) >= length) 00138 return CAYENNE_BUFFER_OVERFLOW; 00139 00140 suffix[0] = '\0'; 00141 CAYENNE_STRCAT(suffix, topicString); 00142 if (channel != CAYENNE_NO_CHANNEL) { 00143 strcat(suffix, "/"); 00144 if (channel == CAYENNE_ALL_CHANNELS) { 00145 strcat(suffix, "+"); 00146 } 00147 else { 00148 #if defined(__AVR__) || defined (ARDUINO_ARCH_ARC32) 00149 itoa(channel, &suffix[strlen(suffix)], 10); 00150 #else 00151 snprintf(&suffix[strlen(suffix)], length - strlen(suffix), "%u", channel); 00152 #endif 00153 } 00154 } 00155 return CAYENNE_SUCCESS; 00156 } 00157 00158 /** 00159 * Check if topic matches. 00160 * @param[in] filter Filter to check topic against 00161 * @param[in] topicName CayenneTopic name 00162 * @param[in] topicNameLen CayenneTopic name length 00163 * return true if topic matches, false otherwise 00164 */ 00165 int topicMatches(char* filter, char* topicName, unsigned int topicNameLen) 00166 { 00167 char* curf = filter; 00168 char* curn = topicName; 00169 char* curn_end = topicName + topicNameLen; 00170 00171 while (*curf && curn < curn_end) 00172 { 00173 if (*curn == '/' && *curf != '/') 00174 break; 00175 if (*curf != '+' && *curf != '#' && *curf != *curn) 00176 break; 00177 if (*curf == '+') 00178 { // skip until we meet the next separator, or end of string 00179 char* nextpos = curn + 1; 00180 while (nextpos < curn_end && *nextpos != '/') 00181 nextpos = ++curn + 1; 00182 } 00183 else if (*curf == '#') 00184 curn = curn_end - 1; // skip until end of string 00185 curf++; 00186 curn++; 00187 }; 00188 00189 return ((curn == curn_end) && (*curf == '\0')); 00190 } 00191 00192 /** 00193 * Get the count of values in a message. 00194 * @param[out] count Returned number of values found in message 00195 * @param[in] payload Payload string, must be null terminated 00196 * @param[in] token Character token for splitting "unit=value" payloads, 0 to just parse first comma delimited value 00197 * @return CAYENNE_SUCCESS if value count succeeded, error code otherwise 00198 */ 00199 int getValueCount(size_t* count, char* payload, char token) { 00200 char* index = payload; 00201 size_t unitCount = 0; 00202 size_t valueCount = 0; 00203 int countingValues = 0; 00204 00205 if (token == 0) { 00206 //Currently there can only be one value in payload if this isn't a "unit=value" payload. 00207 *count = 1; 00208 return CAYENNE_SUCCESS; 00209 } 00210 00211 *count = 0; 00212 while (*index && index != '\0') { 00213 if ((*index == ',') || (*index == token)) { 00214 if (*index == ',') { 00215 if (countingValues) { 00216 valueCount++; 00217 } 00218 else { 00219 unitCount++; 00220 } 00221 } 00222 else if (*index == token) { 00223 countingValues = 1; 00224 valueCount++; 00225 } 00226 } 00227 index++; 00228 } 00229 00230 if (countingValues) { 00231 if ((valueCount != unitCount) && !(unitCount == 0 && valueCount == 1)) { 00232 return CAYENNE_FAILURE; 00233 } 00234 } 00235 else { 00236 valueCount = 1; 00237 } 00238 *count = valueCount; 00239 return CAYENNE_SUCCESS; 00240 } 00241 00242 /** 00243 * Parse a null terminated payload string in place. This may modify the payload string. 00244 * @param[out] values Returned payload data unit & value array 00245 * @param[in,out] valuesSize Size of values array, returns the count of values in the array 00246 * @param[out] type Returned type, NULL if there is none 00247 * @param[in] payload Payload string, must be null terminated 00248 * @param[in] token Character token for splitting "unit=value" payloads, 0 to just parse first comma delimited value 00249 * @return CAYENNE_SUCCESS if value and id were parsed, error code otherwise 00250 */ 00251 int parsePayload(CayenneValuePair* values, size_t* valuesSize, const char** type, char* payload, char token) { 00252 char* index = payload; 00253 size_t count = 0; 00254 int parsingValues = 0; 00255 size_t valueIndex = 0; 00256 #ifdef PARSE_INFO_PAYLOADS 00257 int result = getValueCount(&count, payload, token); 00258 if (result != CAYENNE_SUCCESS) { 00259 *valuesSize = 0; 00260 return result; 00261 } 00262 #else 00263 count = 1; 00264 #endif 00265 00266 if(token == 0) 00267 parsingValues = 1; //Don't need to parse units if there is no unit/value separator 00268 00269 values[0].value = NULL; 00270 values[0].unit = NULL; 00271 *type = NULL; 00272 while (*index && index != '\0') { 00273 if ((*index == ',') || (*index == token)) { 00274 if (*index == ',') { 00275 *type = payload; 00276 if (valueIndex < *valuesSize) { 00277 if (parsingValues) { 00278 values[valueIndex].value = index + 1; 00279 } 00280 else { 00281 values[valueIndex].unit = index + 1; 00282 } 00283 } 00284 *index = '\0'; 00285 valueIndex++; 00286 if (token == 0) 00287 break; 00288 } 00289 else if (*index == token && !parsingValues) { 00290 parsingValues = 1; 00291 valueIndex = 0; 00292 *type = payload; 00293 values[valueIndex].value = index + 1; 00294 *index = '\0'; 00295 valueIndex++; 00296 if (count == valueIndex) 00297 break; 00298 } 00299 } 00300 index++; 00301 }; 00302 *valuesSize = count; 00303 return CAYENNE_SUCCESS; 00304 } 00305 00306 /** 00307 * Build a specified topic string. 00308 * @param[out] topicName Returned topic string 00309 * @param[in] length CayenneTopic buffer length 00310 * @param[in] username Cayenne username 00311 * @param[in] clientID Cayennne client ID 00312 * @param[in] topic Cayenne topic 00313 * @param[in] channel The topic channel, CAYENNE_NO_CHANNEL for none, CAYENNE_ALL_CHANNELS for all 00314 * @return CAYENNE_SUCCESS if topic string was created, error code otherwise 00315 */ 00316 int CayenneBuildTopic(char* topicName, size_t length, const char* username, const char* clientID, CayenneTopic topic, unsigned int channel) { 00317 char channelSuffix[20] = {0}; 00318 int result = buildSuffix(channelSuffix, sizeof(channelSuffix), topic, channel); 00319 if (result != CAYENNE_SUCCESS) 00320 return result; 00321 return buildTopic(topicName, length, username, clientID, channelSuffix); 00322 } 00323 00324 /** 00325 * Build a specified data payload. 00326 * @param[out] payload Returned payload 00327 * @param[in,out] length Payload buffer length 00328 * @param[in] type Optional type to use for type,unit=value payload, can be NULL 00329 * @param[in] values Unit/value array 00330 * @param[in] valueCount Number of values 00331 * @return CAYENNE_SUCCESS if topic string was created, error code otherwise 00332 */ 00333 int CayenneBuildDataPayload(char* payload, size_t* length, const char* type, const CayenneValuePair* values, size_t valueCount) { 00334 int i; 00335 size_t payloadLength = 0; 00336 for (i = 0; i < valueCount; ++i) { 00337 if (values[i].unit) { 00338 payloadLength += strlen(values[i].unit) + 1; 00339 } 00340 else if (type) { 00341 // If type exists but unit does not, use UNIT_UNDEFINED for the unit. 00342 payloadLength += strlen(UNIT_UNDEFINED) + 1; 00343 } 00344 payloadLength += values[i].value ? strlen(values[i].value) + 1 : 0; 00345 } 00346 payloadLength += type ? strlen(type) + 1 : 0; 00347 //If payload can't fit the payload plus a terminating null byte return. 00348 if (payloadLength > *length) { 00349 return CAYENNE_BUFFER_OVERFLOW; 00350 } 00351 00352 payload[0] = '\0'; 00353 if (type) { 00354 strcat(payload, type); 00355 } 00356 for (i = 0; i < valueCount; ++i) { 00357 if (payload[0] != '\0') 00358 strcat(payload, ","); 00359 if (values[i].unit) 00360 strcat(payload, values[i].unit); 00361 else if (type) 00362 strcat(payload, UNIT_UNDEFINED); 00363 } 00364 if (payload[0] != '\0' && valueCount > 0 && values[0].value) 00365 strcat(payload, "="); 00366 for (i = 0; i < valueCount && values[i].value; ++i) { 00367 strcat(payload, values[i].value); 00368 if (i + 1 < valueCount) 00369 strcat(payload, ","); 00370 } 00371 *length = --payloadLength; //Subtract terminating null 00372 return CAYENNE_SUCCESS; 00373 } 00374 00375 /** 00376 * Build a specified response payload. 00377 * @param[out] payload Returned payload 00378 * @param[in,out] length Payload buffer length 00379 * @param[in] id ID of message the response is for 00380 * @param[in] error Optional error message, NULL for success 00381 * @return CAYENNE_SUCCESS if payload string was created, error code otherwise 00382 */ 00383 int CayenneBuildResponsePayload(char* payload, size_t* length, const char* id, const char* error) { 00384 CayenneValuePair values[1]; 00385 values[0].unit = id; 00386 values[0].value = error; 00387 if (error) { 00388 return CayenneBuildDataPayload(payload, length, "error", values, 1); 00389 } 00390 return CayenneBuildDataPayload(payload, length, "ok", values, 1); 00391 } 00392 00393 /** 00394 * Parse a topic string in place. This may modify the topic string. 00395 * @param[out] topic Returned Cayenne topic 00396 * @param[out] channel Returned channel, CAYENNE_NO_CHANNEL if there is none 00397 * @param[out] clientID Returned client ID 00398 * @param[in] username Cayenne username 00399 * @param[in] topicName Topic name string 00400 * @param[in] length Topic name string length 00401 * @return CAYENNE_SUCCESS if topic was parsed, error code otherwise 00402 */ 00403 int CayenneParseTopic(CayenneTopic* topic, unsigned int* channel, const char** clientID, const char* username, char* topicName, unsigned int length) { 00404 char* index = NULL; 00405 int i = 0; 00406 TopicChannel parseTopics[PARSE_TOPICS_COUNT] = { { COMMAND_TOPIC, CAYENNE_ALL_CHANNELS },{ CONFIG_TOPIC, CAYENNE_ALL_CHANNELS }, 00407 #ifdef DIGITAL_AND_ANALOG_SUPPORT 00408 { ANALOG_COMMAND_TOPIC, CAYENNE_ALL_CHANNELS },{ ANALOG_CONFIG_TOPIC, CAYENNE_ALL_CHANNELS },{ DIGITAL_COMMAND_TOPIC, CAYENNE_ALL_CHANNELS },{ DIGITAL_CONFIG_TOPIC, CAYENNE_ALL_CHANNELS }, 00409 #ifdef PARSE_INFO_PAYLOADS 00410 { DIGITAL_TOPIC, CAYENNE_ALL_CHANNELS },{ ANALOG_TOPIC, CAYENNE_ALL_CHANNELS }, 00411 #endif 00412 #endif 00413 #ifdef PARSE_INFO_PAYLOADS 00414 { DATA_TOPIC, CAYENNE_ALL_CHANNELS },{ SYS_MODEL_TOPIC, CAYENNE_NO_CHANNEL },{ SYS_VERSION_TOPIC, CAYENNE_NO_CHANNEL },{ SYS_CPU_MODEL_TOPIC, CAYENNE_NO_CHANNEL },{ SYS_CPU_SPEED_TOPIC, CAYENNE_NO_CHANNEL } 00415 #endif 00416 }; 00417 00418 if (!topic || !channel || !topicName) 00419 { 00420 return CAYENNE_FAILURE; 00421 } 00422 if (length > CAYENNE_MAX_MESSAGE_SIZE) 00423 { 00424 return CAYENNE_BUFFER_OVERFLOW; 00425 } 00426 if (strncmp(CAYENNE_VERSION, topicName, strlen(CAYENNE_VERSION)) != 0) 00427 return CAYENNE_FAILURE; 00428 index = topicName + strlen(CAYENNE_VERSION) + 1; 00429 if (strncmp(username, index, strlen(username)) != 0) 00430 return CAYENNE_FAILURE; 00431 index += strlen(username); 00432 if (CAYENNE_STRNCMP(index, THINGS_STRING, CAYENNE_STRLEN(THINGS_STRING)) != 0) 00433 return CAYENNE_FAILURE; 00434 index += CAYENNE_STRLEN(THINGS_STRING); 00435 char* deviceIDEnd = strchr(index, '/'); 00436 if (!deviceIDEnd) 00437 return CAYENNE_FAILURE; 00438 *clientID = index; 00439 *deviceIDEnd = '\0'; 00440 00441 index = deviceIDEnd + 1; 00442 *topic = UNDEFINED_TOPIC; 00443 *channel = CAYENNE_NO_CHANNEL; 00444 length -= (index - topicName); 00445 for (i = 0; i < PARSE_TOPICS_COUNT; ++i) 00446 { 00447 char channelSuffix[32] = { 0 }; 00448 if (buildSuffix(channelSuffix, sizeof(channelSuffix), parseTopics[i].topic, parseTopics[i].channel) == CAYENNE_SUCCESS && topicMatches(channelSuffix, index, length)) { 00449 *topic = parseTopics[i].topic; 00450 break; 00451 } 00452 } 00453 00454 if (*topic == UNDEFINED_TOPIC) 00455 return CAYENNE_FAILURE; 00456 00457 if (parseTopics[i].channel != CAYENNE_NO_CHANNEL) { 00458 if (length == 0 || length > 31) 00459 return CAYENNE_FAILURE; 00460 char* channelIndex = NULL; 00461 char buffer[32] = { 0 }; 00462 memcpy(buffer, index, length); 00463 buffer[length] = '\0'; 00464 channelIndex = strrchr(buffer, '/'); 00465 if (channelIndex && ++channelIndex) { 00466 char* indexEnd = NULL; 00467 unsigned int channelNumber = strtoul(channelIndex, &indexEnd, 10); 00468 if (indexEnd && *indexEnd == '\0') { 00469 if (((channelNumber != 0) && (*channelIndex != '0')) || ((channelNumber == 0) && (*channelIndex == '0') && (channelIndex + 1 == indexEnd))) { 00470 *channel = channelNumber; 00471 } 00472 } 00473 } 00474 } 00475 00476 return CAYENNE_SUCCESS; 00477 } 00478 00479 /** 00480 * Parse a null terminated payload in place. This may modify the payload string. 00481 * @param[out] values Returned payload data unit & value array 00482 * @param[in,out] valuesSize Size of values array, returns the count of values in the array 00483 * @param[out] type Returned type, NULL if there is none 00484 * @param[out] id Returned message id, empty string if there is none 00485 * @param[in] topic Cayenne topic 00486 * @param[in] payload Payload string, must be null terminated. 00487 * @return CAYENNE_SUCCESS if topic string was created, error code otherwise 00488 */ 00489 int CayenneParsePayload(CayenneValuePair* values, size_t* valuesSize, const char** type, const char** id, CayenneTopic topic, char* payload) { 00490 int i; 00491 if (!payload || !valuesSize || *valuesSize == 0) 00492 return CAYENNE_FAILURE; 00493 00494 *type = NULL; 00495 *id = NULL; 00496 for(i = 0; i < *valuesSize; i++) { 00497 values[i].unit = NULL; 00498 values[i].value = NULL; 00499 } 00500 switch (topic) 00501 { 00502 #ifdef PARSE_INFO_PAYLOADS 00503 case DATA_TOPIC: 00504 parsePayload(values, valuesSize, type, payload, '='); 00505 if (!values[0].value) 00506 return CAYENNE_FAILURE; 00507 break; 00508 #endif 00509 #ifdef DIGITAL_AND_ANALOG_SUPPORT 00510 #ifdef PARSE_INFO_PAYLOADS 00511 case ANALOG_TOPIC: 00512 parsePayload(values, valuesSize, type, payload, 0); 00513 values[0].unit = values[0].value; //Use unit to store resolution 00514 values[0].value = *type; 00515 *type = NULL; 00516 if (!values[0].value) 00517 return CAYENNE_FAILURE; 00518 break; 00519 #endif 00520 case DIGITAL_COMMAND_TOPIC: 00521 case ANALOG_COMMAND_TOPIC: 00522 #endif 00523 case COMMAND_TOPIC: 00524 parsePayload(values, valuesSize, type, payload, 0); 00525 *id = *type; 00526 *type = NULL; 00527 if (!values[0].value) 00528 return CAYENNE_FAILURE; 00529 break; 00530 default: 00531 break; 00532 } 00533 00534 if (!values[0].value) { 00535 values[0].value = payload; 00536 values[0].unit = NULL; 00537 *type = NULL; 00538 *id = NULL; 00539 *valuesSize = 1; 00540 } 00541 00542 return CAYENNE_SUCCESS; 00543 } 00544 00545 00546
Generated on Wed Jul 13 2022 08:52:00 by
 1.7.2
 1.7.2 
    