Peter Ferland / Cayenne-MQTT-mbed-M1

Dependents:   5_Dragonfly_Cayenne_Sprint_IKS01A1

Fork of Cayenne-MQTT-mbed-MTSAS by Peter Ferland

Embed: (wiki syntax)

« Back to documentation index

Show/hide line numbers CayenneUtils.c Source File

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