diff --git a/examples/blink/Blink.ino b/examples/Arduino Yun/blink/Blink.ino similarity index 100% rename from examples/blink/Blink.ino rename to examples/Arduino Yun/blink/Blink.ino diff --git a/examples/quickstart/QuickStart.ino b/examples/Arduino Yun/quickstart/QuickStart.ino similarity index 100% rename from examples/quickstart/QuickStart.ino rename to examples/Arduino Yun/quickstart/QuickStart.ino diff --git a/examples/setup/Setup.ino b/examples/Arduino Yun/setup/Setup.ino similarity index 93% rename from examples/setup/Setup.ino rename to examples/Arduino Yun/setup/Setup.ino index 822814e..b3ed8cc 100644 --- a/examples/setup/Setup.ino +++ b/examples/Arduino Yun/setup/Setup.ino @@ -1,7 +1,7 @@ #include -String revision = "1.0.2-1_ar71xx"; -String location = "https://raw.githubusercontent.com/ParsePlatform/parse-embedded-sdks/1.0.2/yun/linux_package/"; +String revision = "1.0.3-1_ar71xx"; +String location = "https://raw.githubusercontent.com/ParsePlatform/parse-embedded-sdks/1.0.3/yun/linux_package/"; void downloadPackage(String file) { Serial.println("Download: " + location + file + revision + ".ipk"); diff --git a/examples/test/Temperature.json b/examples/Arduino Yun/test/Temperature.json similarity index 100% rename from examples/test/Temperature.json rename to examples/Arduino Yun/test/Temperature.json diff --git a/examples/test/Test.ino b/examples/Arduino Yun/test/Test.ino similarity index 100% rename from examples/test/Test.ino rename to examples/Arduino Yun/test/Test.ino diff --git a/examples/Arduino Zero + WiFi101/quickstart/QuickStart.ino b/examples/Arduino Zero + WiFi101/quickstart/QuickStart.ino new file mode 100644 index 0000000..a771507 --- /dev/null +++ b/examples/Arduino Zero + WiFi101/quickstart/QuickStart.ino @@ -0,0 +1,38 @@ +#include +#include +#include + +char ssid[] = ""; // your network SSID (name) +char pass[] = ""; // your network password (use for WPA, or use as key for WEP) + +int status = WL_IDLE_STATUS; + +void setup() { + //Initialize serial and wait for port to open: + Serial.begin(115200); + while (!Serial) { + ; // wait for serial port to connect. Needed for Leonardo only + } + + // check for the presence of the shield: + if (WiFi.status() == WL_NO_SHIELD) { + Serial.println("WiFi shield not present"); + // don't continue: + while (true); + } + + // attempt to connect to Wifi network: + while (status != WL_CONNECTED) { + Serial.print("Attempting to connect to SSID: "); + Serial.println(ssid); + // Connect to WPA/WPA2 network. Change this line if using open or WEP network: + status = WiFi.begin(ssid, pass); + // wait 10 seconds for connection: + delay(10000); + } + +} + +void loop() { + +} diff --git a/library.properties b/library.properties index e212da6..6575c3f 100644 --- a/library.properties +++ b/library.properties @@ -1,8 +1,8 @@ name=Parse Arduino SDK -version=1.0.2 +version=1.0.3 author=Parse, LLC. maintainer=Parse, LLC. sentence=A library that provides access to Parse -paragraph=Provides convenience methods to access the REST API on Parse.com from Arduino -url=https://github.com/ParsePlatform/Parse-SDK-Arduino -architectures=avr +paragraph=Provides convenience methods to access the REST API on Parse.com from Arduino. +url=https://github.com/ParsePlatform/parse-embedded-sdks +architectures=avr,samd diff --git a/src/external/FlashStorage/FlashStorage.cpp b/src/external/FlashStorage/FlashStorage.cpp new file mode 100644 index 0000000..ae3fae9 --- /dev/null +++ b/src/external/FlashStorage/FlashStorage.cpp @@ -0,0 +1,103 @@ +/* + Copyright (c) 2015 Arduino LLC. All right reserved. + + This library is free software; you can redistribute it and/or + modify it under the terms of the GNU Lesser General Public + License as published by the Free Software Foundation; either + version 2.1 of the License, or (at your option) any later version. + + This library is distributed in the hope that it will be useful, + but WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. + See the GNU Lesser General Public License for more details. + + You should have received a copy of the GNU Lesser General Public + License along with this library; if not, write to the Free Software + Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA +*/ + +#if defined (ARDUINO_SAMD_ZERO) + +#include "FlashStorage.h" + +static const uint32_t pageSizes[] = { 8, 16, 32, 64, 128, 256, 512, 1024 }; + +FlashClass::FlashClass(const void *flash_addr, uint32_t size) : + PAGE_SIZE(pageSizes[NVMCTRL->PARAM.bit.PSZ]), + PAGES(NVMCTRL->PARAM.bit.NVMP), + MAX_FLASH(PAGE_SIZE * PAGES), + ROW_SIZE(PAGE_SIZE * 4), + flash_address((volatile void *)flash_addr), + flash_size(size) +{ +} + +static inline uint32_t read_unaligned_uint32(const void *data) +{ + union { + uint32_t u32; + uint8_t u8[4]; + } res; + const uint8_t *d = (const uint8_t *)data; + res.u8[0] = d[0]; + res.u8[1] = d[1]; + res.u8[2] = d[2]; + res.u8[3] = d[3]; + return res.u32; +} + +void FlashClass::write(const volatile void *flash_ptr, const void *data, uint32_t size) +{ + // Calculate data boundaries + size = (size + 3) / 4; + volatile uint32_t *dst_addr = (volatile uint32_t *)flash_ptr; + const uint8_t *src_addr = (uint8_t *)data; + + // Disable automatic page write + NVMCTRL->CTRLB.bit.MANW = 1; + + // Do writes in pages + while (size) { + // Execute "PBC" Page Buffer Clear + NVMCTRL->CTRLA.reg = NVMCTRL_CTRLA_CMDEX_KEY | NVMCTRL_CTRLA_CMD_PBC; + while (NVMCTRL->INTFLAG.bit.READY == 0) { } + + // Fill page buffer + uint32_t i; + for (i=0; i<(PAGE_SIZE/4) && size; i++) { + *dst_addr = read_unaligned_uint32(src_addr); + src_addr += 4; + dst_addr++; + size--; + } + + // Execute "WP" Write Page + NVMCTRL->CTRLA.reg = NVMCTRL_CTRLA_CMDEX_KEY | NVMCTRL_CTRLA_CMD_WP; + while (NVMCTRL->INTFLAG.bit.READY == 0) { } + } +} + +void FlashClass::erase(const volatile void *flash_ptr, uint32_t size) +{ + const uint8_t *ptr = (const uint8_t *)flash_ptr; + while (size > ROW_SIZE) { + erase(ptr); + ptr += ROW_SIZE; + size -= ROW_SIZE; + } + erase(ptr); +} + +void FlashClass::erase(const volatile void *flash_ptr) +{ + NVMCTRL->ADDR.reg = ((uint32_t)flash_ptr) / 2; + NVMCTRL->CTRLA.reg = NVMCTRL_CTRLA_CMDEX_KEY | NVMCTRL_CTRLA_CMD_ER; + while (!NVMCTRL->INTFLAG.bit.READY) { } +} + +void FlashClass::read(const volatile void *flash_ptr, void *data, uint32_t size) +{ + memcpy(data, (const void *)flash_ptr, size); +} + +#endif // defined (ARDUINO_SAMD_ZERO) diff --git a/src/external/FlashStorage/FlashStorage.h b/src/external/FlashStorage/FlashStorage.h new file mode 100644 index 0000000..a15d2e7 --- /dev/null +++ b/src/external/FlashStorage/FlashStorage.h @@ -0,0 +1,76 @@ +/* + Copyright (c) 2015 Arduino LLC. All right reserved. + + This library is free software; you can redistribute it and/or + modify it under the terms of the GNU Lesser General Public + License as published by the Free Software Foundation; either + version 2.1 of the License, or (at your option) any later version. + + This library is distributed in the hope that it will be useful, + but WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. + See the GNU Lesser General Public License for more details. + + You should have received a copy of the GNU Lesser General Public + License along with this library; if not, write to the Free Software + Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA +*/ + +#pragma once + +#include + +// Concatenate after macro expansion +#define PPCAT_NX(A, B) A ## B +#define PPCAT(A, B) PPCAT_NX(A, B) + +#define Flash(name, size) \ + __attribute__((__aligned__(256))) \ + static const uint8_t PPCAT(_data,name)[(size+255)/256*256] = { }; \ + FlashClass name(PPCAT(_data,name), size); + +#define FlashStorage(name, T) \ + __attribute__((__aligned__(256))) \ + static const uint8_t PPCAT(_data,name)[(sizeof(T)+255)/256*256] = { }; \ + FlashStorageClass name(PPCAT(_data,name)); + +class FlashClass { +public: + FlashClass(const void *flash_addr = NULL, uint32_t size = 0); + + void write(const void *data) { write(flash_address, data, flash_size); } + void erase() { erase(flash_address, flash_size); } + void read(void *data) { read(flash_address, data, flash_size); } + + void write(const volatile void *flash_ptr, const void *data, uint32_t size); + void erase(const volatile void *flash_ptr, uint32_t size); + void read(const volatile void *flash_ptr, void *data, uint32_t size); + +private: + void erase(const volatile void *flash_ptr); + + const uint32_t PAGE_SIZE, PAGES, MAX_FLASH, ROW_SIZE; + const volatile void *flash_address; + const uint32_t flash_size; +}; + +template +class FlashStorageClass { +public: + FlashStorageClass(const void *flash_addr) : flash(flash_addr, sizeof(T)) { }; + + // Write data into flash memory. + // Compiler is able to optimize parameter copy. + inline void write(T data) { flash.erase(); flash.write(&data); } + + // Read data from flash into variable. + inline void read(T *data) { flash.read(data); } + + // Overloaded version of read. + // Compiler is able to optimize copy-on-return. + inline T read() { T data; read(&data); return data; } + +private: + FlashClass flash; +}; + diff --git a/src/internal/ConnectionClient.h b/src/internal/ConnectionClient.h new file mode 100644 index 0000000..524405f --- /dev/null +++ b/src/internal/ConnectionClient.h @@ -0,0 +1,19 @@ +#ifndef __CONNECTION_CLIENT_H__ +#define __CONNECTION_CLIENT_H__ + + +#include + +#ifdef ARDUINO_SAMD_ZERO + +#include +typedef WiFiClient ConnectionClient; + +#elif ARDUINO_AVR_YUN + +#include +typedef Process ConnectionClient; + +#endif + +#endif //__CONNECTION_CLIENT_H__ diff --git a/src/internal/ParseClient.h b/src/internal/ParseClient.h index 00aefc5..979ef5f 100644 --- a/src/internal/ParseClient.h +++ b/src/internal/ParseClient.h @@ -22,11 +22,11 @@ #ifndef ParseClient_h #define ParseClient_h -#if defined (ARDUINO_AVR_YUN) || defined (ARDUINO_AVR_TRE) +//#if defined (ARDUINO_AVR_YUN) || defined (ARDUINO_AVR_TRE) +#include "ConnectionClient.h" #include "ParseResponse.h" #include "ParsePush.h" -#include /*! \file ParseClient.h * \brief ParseClient object for the Yun @@ -35,17 +35,30 @@ /*! \class ParseClient * \brief Class responsible for Parse connection - * - * NOTE: A global ParseClient object with the name "Parse" is defined in the SDK, - * use it in your sketch instead of defining your own ParseClient object. */ class ParseClient { private: - const char* installationId; - const char* sessionToken; - void read(Process* client, char* buf, int size); - Process pushClient; - Process requestClient; + char applicationId[41]; // APPLICATION_ID_MAX_LEN + char clientKey[41]; // CLIENT_KEY_MAX_LEN + char installationId[37]; // INSTALLATION_ID_MAX_LEN + char sessionToken[41]; // SESSION_TOKEN_MAX_LEN + + ConnectionClient client; + ConnectionClient pushClient; + + unsigned long lastHeartbeat; + + void read(ConnectionClient* client, char* buf, int len); + +#if defined (ARDUINO_SAMD_ZERO) + char lastPushTime[41]; // PUSH_TIME_MAX_LEN + bool dataIsDirty; + char pushBuff[5]; + + void saveKeys(); + void restoreKeys(); + void saveLastPushTime(char *time); +#endif public: /*! \fn ParseClient() @@ -57,6 +70,7 @@ class ParseClient { */ ~ParseClient(); + /*! \fn begin(const char *applicationId, const char *clientKey) * \brief Initialize the Parse client and user session * @@ -216,5 +230,5 @@ class ParseClient { */ extern ParseClient Parse; -#endif //ARDUINO_AVR_YUN +//#endif //ARDUINO_AVR_YUN #endif diff --git a/src/internal/ParseCloudFunction.cpp b/src/internal/ParseCloudFunction.cpp index 436a9f6..1d7ede1 100644 --- a/src/internal/ParseCloudFunction.cpp +++ b/src/internal/ParseCloudFunction.cpp @@ -19,7 +19,6 @@ * */ -#include "ParseClient.h" #include "ParseCloudFunction.h" ParseCloudFunction::ParseCloudFunction() : ParseObjectCreate() { diff --git a/src/internal/ParseInternal.h b/src/internal/ParseInternal.h new file mode 100644 index 0000000..71ceb57 --- /dev/null +++ b/src/internal/ParseInternal.h @@ -0,0 +1,7 @@ +#ifndef __PARSE_INTERNAL_H__ +#define __PARSE_INTERNAL_H__ + +#include +#include "ParseUtils.h" + +#endif //__PARSE_INTERNAL_H__ diff --git a/src/internal/ParseObjectCreate.cpp b/src/internal/ParseObjectCreate.cpp index 3bb236a..f54777f 100644 --- a/src/internal/ParseObjectCreate.cpp +++ b/src/internal/ParseObjectCreate.cpp @@ -19,6 +19,7 @@ * */ +#include "ParseInternal.h" #include "ParseClient.h" #include "ParseObjectCreate.h" diff --git a/src/internal/ParseObjectDelete.cpp b/src/internal/ParseObjectDelete.cpp index c16e0f3..1cf75f5 100644 --- a/src/internal/ParseObjectDelete.cpp +++ b/src/internal/ParseObjectDelete.cpp @@ -19,6 +19,7 @@ * */ +#include "ParseInternal.h" #include "ParseClient.h" #include "ParseObjectDelete.h" diff --git a/src/internal/ParseObjectDelete.h b/src/internal/ParseObjectDelete.h index f7a9fc9..fe2e5c7 100644 --- a/src/internal/ParseObjectDelete.h +++ b/src/internal/ParseObjectDelete.h @@ -44,7 +44,7 @@ class ParseObjectDelete : public ParseRequest { * * \return response of request. */ - ParseResponse send() override; + ParseResponse send(); }; #endif diff --git a/src/internal/ParseObjectGet.cpp b/src/internal/ParseObjectGet.cpp index a41081d..116ff93 100644 --- a/src/internal/ParseObjectGet.cpp +++ b/src/internal/ParseObjectGet.cpp @@ -19,6 +19,7 @@ * */ +#include "ParseInternal.h" #include "ParseClient.h" #include "ParseObjectGet.h" diff --git a/src/internal/ParseObjectGet.h b/src/internal/ParseObjectGet.h index 926d011..b447d39 100644 --- a/src/internal/ParseObjectGet.h +++ b/src/internal/ParseObjectGet.h @@ -44,7 +44,7 @@ class ParseObjectGet : public ParseRequest { * * \result response of request */ - ParseResponse send() override; + ParseResponse send(); }; #endif diff --git a/src/internal/ParseObjectUpdate.cpp b/src/internal/ParseObjectUpdate.cpp index c5bbeb2..33ce319 100644 --- a/src/internal/ParseObjectUpdate.cpp +++ b/src/internal/ParseObjectUpdate.cpp @@ -19,6 +19,7 @@ * */ +#include "ParseInternal.h" #include "ParseClient.h" #include "ParseObjectUpdate.h" diff --git a/src/internal/ParseObjectUpdate.h b/src/internal/ParseObjectUpdate.h index 7a5bd52..d2fa3b0 100644 --- a/src/internal/ParseObjectUpdate.h +++ b/src/internal/ParseObjectUpdate.h @@ -44,7 +44,7 @@ class ParseObjectUpdate : public ParseObjectCreate { * * \result the response */ - ParseResponse send() override; + ParseResponse send(); }; #endif diff --git a/src/internal/ParsePlatformSupport.h b/src/internal/ParsePlatformSupport.h new file mode 100644 index 0000000..0a90ae5 --- /dev/null +++ b/src/internal/ParsePlatformSupport.h @@ -0,0 +1,12 @@ +#ifndef __PARSE_PLATFORM_SUPPORT_H__ +#define __PARSE_PLATFORM_SUPPORT_H__ + +#include +#include "ConnectionClient.h" + +class ParsePlatformSupport { +public: + static int read(ConnectionClient* client, char* buf, int len); +}; + +#endif //__PARSE_PLATFORM_SUPPORT_H__ diff --git a/src/internal/ParsePush.h b/src/internal/ParsePush.h index d660fe8..9b6355c 100644 --- a/src/internal/ParsePush.h +++ b/src/internal/ParsePush.h @@ -22,6 +22,7 @@ #ifndef ParsePush_h #define ParsePush_h +#include "ConnectionClient.h" #include "ParseResponse.h" /*! \file ParsePush.h @@ -34,7 +35,14 @@ */ class ParsePush : public ParseResponse { protected: - ParsePush(Process* pushClient); + ParsePush(ConnectionClient* pushClient); + +#if defined (ARDUINO_SAMD_ZERO) + char lookahead[5]; + void setLookahead(const char *read_data); + void read(); +#endif // defined (ARDUINO_SAMD_ZERO) + public: /*! \fn void close() diff --git a/src/internal/ParseQuery.cpp b/src/internal/ParseQuery.cpp index f8f292f..e45c44f 100644 --- a/src/internal/ParseQuery.cpp +++ b/src/internal/ParseQuery.cpp @@ -19,9 +19,9 @@ * */ +#include "ParseInternal.h" #include "ParseClient.h" #include "ParseQuery.h" -#include "ParseUtils.h" ParseQuery::ParseQuery() : ParseRequest() { whereClause = ""; @@ -31,6 +31,11 @@ ParseQuery::ParseQuery() : ParseRequest() { returnedFields = ""; } +long getDecimal(double v) { + long decimal = 1000 * (v - int(v)); + return decimal > 0 ? decimal : -1 * decimal; +} + void ParseQuery::addConditionKey(const char* key) { if (whereClause == "") { whereClause += "{"; @@ -46,14 +51,15 @@ void ParseQuery::addConditionKey(const char* key) { void ParseQuery::addConditionNum(const char* key, const char* comparator, double v) { addConditionKey(key); + String stringVal = String(int(v)) + "." + String(getDecimal(v)); if (comparator == "$=") { - whereClause += v; + whereClause += stringVal; } else { whereClause += "{\""; whereClause += comparator; whereClause += "\":"; - whereClause += v; + whereClause += stringVal; whereClause += "}"; } } diff --git a/src/internal/ParseQuery.h b/src/internal/ParseQuery.h index 02da433..cfa3da1 100644 --- a/src/internal/ParseQuery.h +++ b/src/internal/ParseQuery.h @@ -238,7 +238,7 @@ class ParseQuery : public ParseRequest { * * \result response of the request. */ - ParseResponse send() override; + ParseResponse send(); }; #endif diff --git a/src/internal/ParseRequest.cpp b/src/internal/ParseRequest.cpp index 1dfd255..e9eaf84 100644 --- a/src/internal/ParseRequest.cpp +++ b/src/internal/ParseRequest.cpp @@ -19,7 +19,7 @@ * */ -#include "ParseClient.h" +#include "ParseInternal.h" #include "ParseRequest.h" ParseRequest::ParseRequest() { diff --git a/src/internal/ParseRequest.h b/src/internal/ParseRequest.h index d6a5a61..0300ad5 100644 --- a/src/internal/ParseRequest.h +++ b/src/internal/ParseRequest.h @@ -22,6 +22,9 @@ #ifndef ParseRequest_h #define ParseRequest_h +#include "ParseInternal.h" +#include "ParseResponse.h" + /*! \file ParseRequest.h * \brief ParseRequest object for the Yun * include Parse.h, not this file diff --git a/src/internal/ParseResponse.h b/src/internal/ParseResponse.h index 8bb0d7d..801a996 100644 --- a/src/internal/ParseResponse.h +++ b/src/internal/ParseResponse.h @@ -22,7 +22,7 @@ #ifndef ParseResponse_h #define ParseResponse_h -#include +#include "ConnectionClient.h" /*! \file ParseResponse.h * \brief ParseResponse object for the Yun @@ -40,14 +40,35 @@ class ParseResponse { char* buf; char* tmpBuf; bool isUserBuffer; - int p = 0; - int resultCount = -1; - Process* client; - void read(); + int p; + int resultCount; +#if defined (ARDUINO_SAMD_ZERO) + long responseLength; + bool isChunked; + bool firstObject; + bool dataDone; + char chunkedBuffer[1024]; + int bufferPos; + int lastRead; +#endif + ConnectionClient* client; + + virtual void read(); void readWithTimeout(int maxSec); + +#if defined (ARDUINO_SAMD_ZERO) + // Zero functions only - do nothing on Yun + void readLine(char *buff, int sz); + bool readJson(char *buff, int sz); + bool readJsonInternal(char *buff, int sz, int *read_bytes, char started); + int readChunkedData(int timeout); + // End Zero only functions +#endif + int available(); void freeBuffer(); - ParseResponse(Process* client); + + ParseResponse(ConnectionClient* client); public: /*! \fn ParseResponse() @@ -126,6 +147,10 @@ class ParseResponse { * * NOTE: if the query resutls exceed 2048 bytes(query result buffer size), * it will only return the count of objects in the buffer. + * NOTE2(Zero only): the returned count is approximation of the number of + * returned objects. If the server sends results multi-chunked, the + * count is an approximation of the objects in the first chunk + * (usually 4K-16K) * \result number of objects in the result */ int count(); diff --git a/src/internal/ParseTrackEvent.cpp b/src/internal/ParseTrackEvent.cpp index 4dbf25a..d55d656 100644 --- a/src/internal/ParseTrackEvent.cpp +++ b/src/internal/ParseTrackEvent.cpp @@ -19,7 +19,7 @@ * */ -#include "ParseClient.h" +#include "ParseInternal.h" #include "ParseTrackEvent.h" ParseTrackEvent::ParseTrackEvent() : ParseObjectCreate() { diff --git a/src/internal/ParseClient.cpp b/src/internal/yun/ParseClient.cpp similarity index 68% rename from src/internal/ParseClient.cpp rename to src/internal/yun/ParseClient.cpp index f825989..9d02935 100644 --- a/src/internal/ParseClient.cpp +++ b/src/internal/yun/ParseClient.cpp @@ -19,10 +19,18 @@ * */ -#include "ParseClient.h" -#include "ParseUtils.h" + +#if defined (ARDUINO_AVR_YUN) + +#include "../ParseClient.h" +#include "../ParseUtils.h" +#include "../ParsePlatformSupport.h" ParseClient::ParseClient() { + applicationId[0] = '\0'; + clientKey[0] = '\0'; + installationId[0] = '\0'; + sessionToken[0] = '\0'; } ParseClient::~ParseClient() { @@ -42,7 +50,7 @@ void ParseClient::begin(const char *applicationId, const char *clientKey) { } void ParseClient::setInstallationId(const char *installationId) { - this->installationId = installationId; + strncpy(this->installationId, installationId, sizeof(this->installationId)); if (installationId) { Bridge.put("installationId", installationId); } else { @@ -51,24 +59,22 @@ void ParseClient::setInstallationId(const char *installationId) { } const char* ParseClient::getInstallationId() { - if (!this->installationId) { - char *buf = new char[37]; - - requestClient.begin("parse_request"); - requestClient.addParameter("-i"); - requestClient.run(); - read(&requestClient, buf, 37); - this->installationId = buf; + if (this->installationId[0] == '\0') { + client.begin("parse_request"); + client.addParameter("-i"); + client.run(); + ParsePlatformSupport::read(&client, this->installationId, sizeof(installationId)); } return this->installationId; } void ParseClient::setSessionToken(const char *sessionToken) { - if ((sessionToken != NULL) && (strlen(sessionToken) > 0 )){ - this->sessionToken = sessionToken; + if ((sessionToken != NULL) && (sessionToken[0] != '\0')){ + strncpy(this->sessionToken, sessionToken, sizeof(this->sessionToken)); Bridge.put("sessionToken", sessionToken); } else { Bridge.put("sessionToken", ""); + this->sessionToken[0] = '\0'; } } @@ -80,11 +86,11 @@ const char* ParseClient::getSessionToken() { if (!this->sessionToken) { char *buf = new char[33]; - requestClient.begin("parse_request"); - requestClient.addParameter("-s"); - requestClient.run(); - read(&requestClient, buf, 33); - this->sessionToken = buf; + client.begin("parse_request"); + client.addParameter("-s"); + client.run(); + ParsePlatformSupport::read(&client, buf, 33); + strncpy(this->sessionToken, buf, sizeof(this->sessionToken)); } return this->sessionToken; } @@ -94,30 +100,33 @@ ParseResponse ParseClient::sendRequest(const char* httpVerb, const char* httpPat } ParseResponse ParseClient::sendRequest(const String& httpVerb, const String& httpPath, const String& requestBody, const String& urlParams) { - requestClient.begin("parse_request"); // start a process that launch the "parse_request" command + while(client.available()) { + client.read(); // flush out the buffer in case there is any leftover data there + } + client.begin("parse_request"); // start a process that launch the "parse_request" command - if( ParseUtils::isSanitizedString(httpVerb) + if(ParseUtils::isSanitizedString(httpVerb) && ParseUtils::isSanitizedString(httpPath) && ParseUtils::isSanitizedString(requestBody) && ParseUtils::isSanitizedString(urlParams)) { - requestClient.addParameter("-v"); - requestClient.addParameter(httpVerb); - requestClient.addParameter("-e"); - requestClient.addParameter(httpPath); + client.addParameter("-v"); + client.addParameter(httpVerb); + client.addParameter("-e"); + client.addParameter(httpPath); if (requestBody != "") { - requestClient.addParameter("-d"); - requestClient.addParameter(requestBody); + client.addParameter("-d"); + client.addParameter(requestBody); } if (urlParams != "") { - requestClient.addParameter("-p"); - requestClient.addParameter(urlParams); - requestClient.runAsynchronously(); + client.addParameter("-p"); + client.addParameter(urlParams); + client.runAsynchronously(); } else { - requestClient.run(); // Run the process and wait for its termination + client.run(); // Run the process and wait for its termination } } - ParseResponse response(&requestClient); + ParseResponse response(&client); return response; } @@ -161,29 +170,16 @@ void ParseClient::stopPushService() { } void ParseClient::end() { - if(installationId) { - delete[] installationId; - installationId = NULL; + if(installationId[0] != '\0') { + installationId[0] = '\0'; } - if(sessionToken) { - delete[] sessionToken; - sessionToken = NULL; + if(sessionToken[0] != '\0') { + sessionToken[0] = '\0'; } stopPushService(); } -void ParseClient::read(Process* client, char* buf, int len) { - memset(buf, 0, len); - int p = 0; - while(1) { - if(client->available()) { - while (p < (len-1) && client->available()) { - buf[p++] = client->read(); - } - break; - } - } -} - ParseClient Parse; + +#endif diff --git a/src/internal/yun/ParsePlatformSupport.cpp b/src/internal/yun/ParsePlatformSupport.cpp new file mode 100644 index 0000000..6551668 --- /dev/null +++ b/src/internal/yun/ParsePlatformSupport.cpp @@ -0,0 +1,41 @@ +/* + * Copyright (c) 2015, Parse, LLC. All rights reserved. + * + * You are hereby granted a non-exclusive, worldwide, royalty-free license to use, + * copy, modify, and distribute this software in source code or binary form for use + * in connection with the web services and APIs provided by Parse. + * + * As with any software that integrates with the Parse platform, your use of + * this software is subject to the Parse Terms of Service + * [https://www.parse.com/about/terms]. This copyright notice shall be + * included in all copies or substantial portions of the software. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR + * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS + * FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR + * COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER + * IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN + * CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. + * + */ + + +#if defined (ARDUINO_AVR_YUN) + +#include "../ConnectionClient.h" +#include "../ParsePlatformSupport.h" + +int ParsePlatformSupport::read(ConnectionClient* client, char* buf, int len) { + int p = 0; + while (p < (len-1) && client->available()) { + buf[p++] = client->read(); + } + if (p > 0 && buf[p - 1] == '\n') { + buf[p - 1] = '\0'; + } else { + buf[p] = '\0'; + } + return p; +} + +#endif diff --git a/src/internal/ParsePush.cpp b/src/internal/yun/ParsePush.cpp similarity index 93% rename from src/internal/ParsePush.cpp rename to src/internal/yun/ParsePush.cpp index cf77de8..ae9847a 100644 --- a/src/internal/ParsePush.cpp +++ b/src/internal/yun/ParsePush.cpp @@ -19,8 +19,10 @@ * */ -#include "ParseClient.h" -#include "ParsePush.h" +#ifdef ARDUINO_AVR_YUN + +#include "../ParseClient.h" +#include "../ParsePush.h" ParsePush::ParsePush(Process* pushClient) : ParseResponse(pushClient) { } @@ -31,3 +33,5 @@ void ParsePush::close() { client->write('n'); freeBuffer(); } + +#endif diff --git a/src/internal/ParseResponse.cpp b/src/internal/yun/ParseResponse.cpp similarity index 90% rename from src/internal/ParseResponse.cpp rename to src/internal/yun/ParseResponse.cpp index 376ac14..e26ca6a 100644 --- a/src/internal/ParseResponse.cpp +++ b/src/internal/yun/ParseResponse.cpp @@ -19,11 +19,15 @@ * */ -#include "ParseClient.h" -#include "ParseResponse.h" -#include "ParseUtils.h" +#if defined (ARDUINO_AVR_YUN) + +#include "../ParseInternal.h" +#include "../ParseClient.h" +#include "../ParseResponse.h" +#include "../ParsePlatformSupport.h" ParseResponse::ParseResponse(Process* client) { + p = 0; buf = NULL; tmpBuf = NULL; bufSize = 0; @@ -54,19 +58,8 @@ void ParseResponse::read() { if(buf == NULL) { bufSize = BUFSIZE; buf = new char[bufSize]; - memset(buf, 0, bufSize); - } - - if (p == bufSize - 1) { - return; - } - while (p < bufSize-1 && available()) { - buf[p++] = client->read(); } - while(available()) { - client->read(); //discard exccessive data which buffer cannot contain - } - buf[bufSize-1] = 0; + p += ParsePlatformSupport::read(client, buf + p, bufSize - p); } int ParseResponse::getErrorCode() { @@ -164,3 +157,5 @@ void ParseResponse::close() { } freeBuffer(); } + +#endif diff --git a/src/internal/zero/ParseClient.cpp b/src/internal/zero/ParseClient.cpp new file mode 100644 index 0000000..4d6258b --- /dev/null +++ b/src/internal/zero/ParseClient.cpp @@ -0,0 +1,389 @@ +/* + * Copyright (c) 2015, Parse, LLC. All rights reserved. + * + * You are hereby granted a non-exclusive, worldwide, royalty-free license to use, + * copy, modify, and distribute this software in source code or binary form for use + * in connection with the web services and APIs provided by Parse. + * + * As with any software that integrates with the Parse platform, your use of + * this software is subject to the Parse Terms of Service + * [https://www.parse.com/about/terms]. This copyright notice shall be + * included in all copies or substantial portions of the software. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR + * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS + * FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR + * COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER + * IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN + * CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. + * + */ + +#if defined (ARDUINO_SAMD_ZERO) + +#include "../ParseClient.h" +#include "../../external/FlashStorage/FlashStorage.h" +#include + +// Set DEBUG to true to see serial debug output for the main stages +// of the Parse client. +const bool DEBUG = false; +const char* PARSE_API = "api.parse.com"; +const char* USER_AGENT = "arduino-zero.v.1.0.1"; +const char* CLIENT_VERSION = "1.0.3"; +const char* PARSE_PUSH = "push.parse.com"; +const unsigned short SSL_PORT = 443; + +struct KeysInternalStorage { + bool assigned; + char installationId[37]; + char sessionToken[41]; + char lastPushTime[41]; +}; + +// Reserve a portion of flash memory to store a "KeysInternalStorage" variable +// and call it "parse_flash_store". +FlashStorage(parse_flash_store, KeysInternalStorage); + +/* + * !!! IMPORTANT !!! + * This function does not conform to RFC 4122, even though it returns + * a string formatted as an UUID. Do not use this to generate proper UUID! + */ +static String createNewInstallationId(void) { + static bool randInitialized = false; + char buff[40]; + + if (!randInitialized) { + struct timeval tm; + gettimeofday(&tm, NULL); + // Terrible choice for initialization, prone to collision + srand((unsigned)tm.tv_sec + (unsigned)tm.tv_usec); + randInitialized = true; + } + + snprintf(buff, sizeof(buff), + "%1x%1x%1x%1x%1x%1x%1x%1x-%1x%1x%1x%1x-%1x%1x%1x%1x-%1x%1x%1x%1x-%1x%1x%1x%1x%1x%1x%1x%1x%1x%1x%1x%1x", + rand()%16, rand()%16, rand()%16, rand()%16, rand()%16, rand()%16, rand()%16, rand()%16, + rand()%16, rand()%16, rand()%16, rand()%16, + rand()%16, rand()%16, rand()%16, rand()%16, + rand()%16, rand()%16, rand()%16, rand()%16, + rand()%16, rand()%16, rand()%16, rand()%16, rand()%16, rand()%16, rand()%16, rand()%16, rand()%16, rand()%16, rand()%16, rand()%16 + ); + return String(buff); +} + +static void sendAndEchoToSerial(WiFiClient& client, const char *line) { + client.print(line); + if (Serial && DEBUG) + Serial.print(line); +} + +ParseClient::ParseClient() { + memset(applicationId, 0, sizeof(applicationId)); + memset(clientKey, 0, sizeof(clientKey)); + memset(installationId, 0, sizeof(installationId)); + memset(sessionToken, 0, sizeof(sessionToken)); + memset(lastPushTime, 0, sizeof(lastPushTime)); + lastHeartbeat = 0; + dataIsDirty = false; +} + +ParseClient::~ParseClient() { + end(); +} + +void ParseClient::begin(const char *applicationId, const char *clientKey) { + if (Serial && DEBUG) { + Serial.print("begin("); + Serial.print(applicationId ? applicationId : "NULL"); + Serial.print(", "); + Serial.print(clientKey ? clientKey : "NULL"); + Serial.println(")"); + } + + if(applicationId) { + strncpy(this->applicationId, applicationId, sizeof(this->applicationId)); + } + if (clientKey) { + strncpy(this->clientKey, clientKey, sizeof(this->clientKey)); + } + restoreKeys(); +} + +void ParseClient::setInstallationId(const char *installationId) { + if (installationId) { + if (strcmp(this->installationId, installationId)) + dataIsDirty = true; + strncpy(this->installationId, installationId, sizeof(this->installationId)); + } else { + if (this->installationId[0]) + dataIsDirty = true; + strncpy(this->installationId, "", sizeof(this->installationId)); + } +} + +const char* ParseClient::getInstallationId() { + if (!strlen(installationId)) { + setInstallationId(createNewInstallationId().c_str()); + char buff[40]; + + if (Serial && DEBUG) { + Serial.print("creating new installationId:"); + Serial.println(installationId); + } + + char content[120]; + snprintf(content, sizeof(content), "{\"installationId\": \"%s\", \"deviceType\": \"embedded\", \"parseVersion\": \"1.0.0\"}", installationId); + + ParseResponse response = sendRequest("POST", "/1/installations", content, ""); + if (Serial && DEBUG) { + Serial.print("response:"); + Serial.println(response.getJSONBody()); + } + } + saveKeys(); + return installationId; +} + +void ParseClient::setSessionToken(const char *sessionToken) { + if ((sessionToken != NULL) && (strlen(sessionToken) > 0 )) { + if (strcmp(this->sessionToken, sessionToken)) + dataIsDirty = true; + strncpy(this->sessionToken, sessionToken, sizeof(this->sessionToken)); + if (Serial && DEBUG) { + Serial.print("setting the session for installation:"); + Serial.println(installationId); + } + getInstallationId(); + ParseResponse response = sendRequest("GET", "/1/sessions/me", "", ""); + String installation = response.getString("installationId"); + if (!installation.length() && strlen(installationId)) { + sendRequest("PUT", "/1/sessions/me", "{}\r\n", ""); + } + } else { + if (sessionToken[0]) + dataIsDirty = true; + this->sessionToken[0] = 0; + } +} + +void ParseClient::clearSessionToken() { + setSessionToken(NULL); +} + +const char* ParseClient::getSessionToken() { + if (!strlen(sessionToken)) + return NULL; + return sessionToken; +} + +ParseResponse ParseClient::sendRequest(const char* httpVerb, const char* httpPath, const char* requestBody, const char* urlParams) { + return sendRequest(String(httpVerb), String(httpPath), String(requestBody), String(urlParams)); +} + +ParseResponse ParseClient::sendRequest(const String& httpVerb, const String& httpPath, const String& requestBody, const String& urlParams) { + char buff[91] = {0}; + client.stop(); + + saveKeys(); + + if (Serial && DEBUG) { + Serial.print("sendRequest(\""); + Serial.print(httpVerb.c_str()); + Serial.print("\", \""); + Serial.print(httpPath.c_str()); + Serial.print("\", \""); + Serial.print(requestBody.c_str()); + Serial.print("\", \""); + Serial.print(urlParams.c_str()); + Serial.println("\")"); + } + + int retry = 3; + bool connected; + + while(!(connected = client.connectSSL(PARSE_API, SSL_PORT)) && retry--); + + if (connected) { + if (Serial && DEBUG) { + Serial.println("connected to server"); + Serial.println(applicationId); + Serial.println(clientKey); + Serial.println(installationId); + } + if (urlParams.length() > 0) { + snprintf(buff, sizeof(buff) - 1, "%s %s?%s HTTP/1.1\r\n", httpVerb.c_str(), httpPath.c_str(), urlParams.c_str()); + } else { + snprintf(buff, sizeof(buff) - 1, "%s %s HTTP/1.1\r\n", httpVerb.c_str(), httpPath.c_str()); + } + sendAndEchoToSerial(client, buff); + snprintf(buff, sizeof(buff) - 1, "Host: %s\r\n", PARSE_API); + sendAndEchoToSerial(client, buff); + snprintf(buff, sizeof(buff) - 1, "X-Parse-Client-Version: %s\r\n", CLIENT_VERSION); + sendAndEchoToSerial(client, buff); + snprintf(buff, sizeof(buff) - 1, "X-Parse-Application-Id: %s\r\n", applicationId); + sendAndEchoToSerial(client, buff); + snprintf(buff, sizeof(buff) - 1, "X-Parse-Client-Key: %s\r\n", clientKey); + sendAndEchoToSerial(client, buff); + + if (strlen(installationId) > 0) { + snprintf(buff, sizeof(buff) - 1, "X-Parse-Installation-Id: %s\r\n", installationId); + sendAndEchoToSerial(client, buff); + } + if (strlen(sessionToken) > 0) { + snprintf(buff, sizeof(buff) - 1, "X-Parse-Session-Token: %s\r\n", sessionToken); + sendAndEchoToSerial(client, buff); + } + if (requestBody.length() > 0 && httpVerb != "GET") { + requestBody; + sendAndEchoToSerial(client, "Content-Type: application/json; charset=utf-8\r\n"); + } else if (urlParams.length() > 0) { + sendAndEchoToSerial(client, "Content-Type: html/text\r\n"); + } + if (requestBody.length() > 0 && httpVerb != "GET") { + snprintf(buff, sizeof(buff) - 1, "Content-Length: %d\r\n", requestBody.length()); + sendAndEchoToSerial(client, buff); + } + sendAndEchoToSerial(client, "Connection: close\r\n"); + sendAndEchoToSerial(client, "\r\n"); + if (requestBody.length() > 0) { + sendAndEchoToSerial(client, requestBody.c_str()); + } + } else { + if (Serial && DEBUG) + Serial.println("failed to connect to server"); + } + ParseResponse response(&client); + return response; +} + +bool ParseClient::startPushService() { + pushClient.stop(); + + saveKeys(); + + if (Serial && DEBUG) + Serial.println("start push"); + + int retry = 3; + bool connected; + + while(!(connected = pushClient.connectSSL(PARSE_PUSH, SSL_PORT)) && retry--); + + if (connected) { + if (Serial && DEBUG) + Serial.println("push started"); + char buff[256] = {0}; + snprintf(buff, sizeof(buff), + "{\"installation_id\":\"%s\", \"oauth_key\":\"%s\", " + "\"v\":\"e1.0.0\", \"last\":%s%s%s}\r\n{}\r\n", + installationId, + applicationId, + lastPushTime[0] ? "\"" : "", + lastPushTime[0] ? lastPushTime : "null", + lastPushTime[0] ? "\"" : ""); + sendAndEchoToSerial(pushClient, buff); + } else { + if (Serial && DEBUG) + Serial.println("failed to connect to push server"); + } +} + +ParsePush ParseClient::nextPush() { + ParsePush push(&pushClient); + if (pushBuff[0]) { + push.setLookahead(pushBuff); + memset(pushBuff, 0, sizeof(pushBuff)); + } + return push; +} + +bool ParseClient::pushAvailable() { + bool available = (pushClient.available() > 0); + // send keepalive if connected. + if (!available && pushClient.connected() && millis() - lastHeartbeat > 1000 * 60 * 12) { + pushClient.println("{}"); + lastHeartbeat = millis(); + } + if (pushBuff[0]) { + available = true; + } else { + for (int i = 0; i < sizeof(pushBuff) && (pushClient.available() > 0); ++i) { + pushBuff[i] = pushClient.read(); + } + if (!strncmp(pushBuff, "{}", 2)) { + int i = 2; + for (; pushBuff[i] == '\r' || pushBuff[i] == '\n'; ++i); + if (pushBuff[i]) { + int j = i;; + for (; pushBuff[i]; ++i) + pushBuff[i - j] = pushBuff[i]; + pushBuff[i - j] = 0; + } else { + memset(pushBuff, 0, sizeof(pushBuff)); + available = (pushClient.available() > 0); + } + } + } + return available; +} + +void ParseClient::stopPushService() { + pushClient.stop(); +} + +void ParseClient::end() { + stopPushService(); +} + +void ParseClient::saveKeys() { + if (dataIsDirty) { + KeysInternalStorage stored_keys = parse_flash_store.read(); + + stored_keys.assigned = true; + strcpy(stored_keys.installationId, installationId); + strcpy(stored_keys.sessionToken, sessionToken); + strcpy(stored_keys.lastPushTime, lastPushTime); + parse_flash_store.write(stored_keys); + if (Serial && DEBUG) { + Serial.println("ParseClient::saveKeys() : done."); + } + dataIsDirty = false; + } else { + if (Serial && DEBUG) { + Serial.println("ParseClient::saveKeys() : keys are not changed - skipping..."); + } + } +} + +void ParseClient::restoreKeys() { + KeysInternalStorage stored_keys = parse_flash_store.read(); + + if (stored_keys.assigned) { + strcpy(installationId, stored_keys.installationId); + strcpy(sessionToken, stored_keys.sessionToken); + strcpy(lastPushTime, stored_keys.lastPushTime); + if (Serial && DEBUG) { + Serial.println("ParseClient::restoreKeys() : done:"); + Serial.println(installationId); + Serial.println(sessionToken); + Serial.println(lastPushTime); + } + } else { + if (Serial && DEBUG) { + Serial.println("ParseClient::restoreKeys() : nothing is stored."); + } + } +} + +void ParseClient::saveLastPushTime(char *time) { + dataIsDirty = true; + strcpy(lastPushTime, time); + saveKeys(); +} + + +ParseClient Parse; + +#endif // ARDUINO_SAMD_ZERO diff --git a/src/internal/zero/ParsePush.cpp b/src/internal/zero/ParsePush.cpp new file mode 100644 index 0000000..f63cd18 --- /dev/null +++ b/src/internal/zero/ParsePush.cpp @@ -0,0 +1,77 @@ +/* + * Copyright (c) 2015, Parse, LLC. All rights reserved. + * + * You are hereby granted a non-exclusive, worldwide, royalty-free license to use, + * copy, modify, and distribute this software in source code or binary form for use + * in connection with the web services and APIs provided by Parse. + * + * As with any software that integrates with the Parse platform, your use of + * this software is subject to the Parse Terms of Service + * [https://www.parse.com/about/terms]. This copyright notice shall be + * included in all copies or substantial portions of the software. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR + * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS + * FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR + * COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER + * IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN + * CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. + * + */ + +#if defined (ARDUINO_SAMD_ZERO) + +#include "../ParsePush.h" +#include "../ParseClient.h" +#include "../ParseUtils.h" + +ParsePush::ParsePush(WiFiClient* pushClient) : ParseResponse(pushClient) { + memset(lookahead, 0, sizeof(lookahead)); +} + +void ParsePush::close() { + freeBuffer(); +} + +void ParsePush::setLookahead(const char *read_data) { + strncpy(lookahead, read_data, sizeof(lookahead)); +} + +void ParsePush::read() { + if (buf == NULL) { + bufSize = BUFSIZE; + buf = new char[bufSize]; + } + + if (p == bufSize - 1) { + return; + } + + if (p == 0) { + memset(buf, 0, bufSize); + for (; lookahead[p]; ++p) + buf[p] = lookahead[p]; + lookahead[0] = 0; + } + + char c; + while (client->connected()) { + if (client->available()) { + c = client->read(); + if (c == '\n') c = '\0'; + if (p < bufSize - 1) { + *(buf + p) = c; + p++; + } + if (c == '\0') break; + } + } + p = 0; + char time[41]; + if (ParseUtils::getStringFromJSON(buf, "time", time, sizeof(time))) { + Parse.saveLastPushTime(time); + } +} + +#endif // ARDUINO_SAMD_ZERO + diff --git a/src/internal/zero/ParseResponse.cpp b/src/internal/zero/ParseResponse.cpp new file mode 100644 index 0000000..36ab1fe --- /dev/null +++ b/src/internal/zero/ParseResponse.cpp @@ -0,0 +1,447 @@ +/* + * Copyright (c) 2015, Parse, LLC. All rights reserved. + * + * You are hereby granted a non-exclusive, worldwide, royalty-free license to use, + * copy, modify, and distribute this software in source code or binary form for use + * in connection with the web services and APIs provided by Parse. + * + * As with any software that integrates with the Parse platform, your use of + * this software is subject to the Parse Terms of Service + * [https://www.parse.com/about/terms]. This copyright notice shall be + * included in all copies or substantial portions of the software. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR + * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS + * FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR + * COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER + * IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN + * CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. + * + */ + +#if defined (ARDUINO_SAMD_ZERO) + +#include "../ParseResponse.h" +#include "../ParseInternal.h" + +static const char kHttpOK[] = "HTTP/1.1 200 OK"; +static const char kContentLength[] = "Content-Length:"; +static const char kChunkedEncoding[] = "transfer-encoding: chunked"; +// Fortunately we do not need to support *any* JSON, only the one generated by Parse. +static const char kResultsStart[] = "{\"results\":["; +static const int kJsonResponseMaxSize = 256; +static const int kQueryTimeout = 5000; +static const int kBufferSize = 1024; + +// Uncomment following line if you want to debug query response with serial output. +// #define DEBUG_RESPONSE + +ParseResponse::ParseResponse(ConnectionClient* client) { + buf = NULL; + tmpBuf = NULL; + p = 0; + resultCount = -1; + bufSize = 0; + isUserBuffer = false; + isChunked = false; + responseLength = 0; + dataDone = false; + this->client = client; + bufferPos = kBufferSize; + lastRead = -1; +} + +ParseResponse::~ParseResponse() { + close(); +} + +void ParseResponse::setBuffer(char* buffer, int size) { + if (!buffer || size <= 0) { + return; + } + + buf = buffer; + bufSize = size; + isUserBuffer = true; + memset(buf, 0, bufSize); +} + +int ParseResponse::available() { + return client->available(); +} + +void ParseResponse::read() { + if (dataDone) + return; + if (buf == NULL) { + bufSize = BUFSIZE; + buf = new char[bufSize]; + memset(buf, 0, bufSize); + } + + if (p == bufSize - 1) { + return; + } + + memset(buf + p, 0, bufSize - p); + + char c; + int i; + + bool data = false; + int count = 0; + + while (client->connected()) { + delay(1); + while (client->available()) { + c = client->read(); + if (c != '\r') { // filter out '\r' character + if (data) { + if (p < bufSize - 1) { + *(buf + p) = c; + p++; + } + } + // filter out the headers + if (c == '\n') count++; else count = 0; + if (count >= 2) data = true; + } + } + } + dataDone = 1; +} + +void ParseResponse::readLine(char *buff, int sz) { + memset(buff, 0, sz); +#ifdef DEBUG_RESPONSE + Serial.print("Read line:"); +#endif + for (int i = 0; client->available(); ++i) { + char c = client->read(); + if (c == '\r') { + client->read(); // read /n + return; + } + if (c == '\n') { + return; + } +#ifdef DEBUG_RESPONSE + Serial.print(c); +#endif + if (i < sz - 1) + buff[i] = c; + } +#ifdef DEBUG_RESPONSE + Serial.println(""); +#endif +} + +bool ParseResponse::readJson(char *buff, int sz) { + memset(buff, 0, sz); + + int read_bytes = 0; + + bool res = readJsonInternal(buff, sz, &read_bytes, '\0'); + if (!res) { +#ifdef DEBUG_RESPONSE + Serial.print("Failed"); + Serial.println(buff); +#endif + } + return res; +} + +bool ParseResponse::readJsonInternal(char *buff, int sz, int* read_bytes, char started) { + // It is our own JSON, so we can be *very* strict in regars to format. + char in_string = '\0'; + int ch; + int i; + + char tmp[32]; + + for (i = 0; ; ++i) { + bool skip = false; + if ((ch = readChunkedData(kQueryTimeout)) < 0) { + *read_bytes = i; + return false; + } + switch (ch) { + case '\"': + case '\'': + if (in_string == ch) + in_string = '\0'; + else if (in_string == '\0') + in_string = ch; + break; + case ',': + if (in_string) + break; + if (!started) { + --i; // skip + skip = true; + } + break; + case '[': + case '{': + { + if (in_string) + break; + if (!started) { + started = ch; + break; + } + int added = 0; + if (i < sz) + buff[i] = ch; + bool success = readJsonInternal(buff + i + 1, sz - i - 1, &added, ch); + if (!success) + return false; + i += added; + skip = true; + } break; + case '}': + case ']': + { + if (in_string) + break; + if (!started) + return false; + if ((started == '[' && ch != ']') || (started == '{' && ch != '}')) + return false; + if (i < sz) + buff[i] = ch; + *read_bytes = i + 1; + return true; + } break; + } + if (!skip && i < sz - 1) + buff[i] = ch; + } +} + +#ifdef DEBUG_RESPONSE +void printData(char *d, int sz, int offset) { + char t1[32]; + sprintf(t1,"\r\n%d %04x[->", sz, offset); + Serial.print(t1); + char tmp[4] = {0}; + for (int i = 0; i < sz; ++i) { + if (d[i] >= 32 && d[i] <= 127) + tmp[0] = d[i]; + else + tmp[0] = '?'; + Serial.print(tmp); + } + Serial.println("<-]"); +} +#endif + +int ParseResponse::readChunkedData(int timeout) { + + char snum[16]; + + if (bufferPos == kBufferSize || (lastRead > 0 && bufferPos == lastRead)) { + for (int i = 0; i < timeout && !client->available(); ++i) { + delay(1); + } + + if (responseLength <= 0) { + readLine(snum, sizeof(snum)); + readLine(snum, sizeof(snum)); + char *tmp; +#ifdef DEBUG_RESPONSE + Serial.println(""); + Serial.print("Next chunk:"); + Serial.println(snum); +#endif + responseLength = strtol(snum, &tmp, 16); + if (!responseLength) + return -1; + } + if (client->available()) { + int to_read = responseLength > kBufferSize ? kBufferSize : responseLength; + bufferPos = 0; + int sz = client->read((uint8_t *)chunkedBuffer, to_read); + lastRead = sz; +#ifdef DEBUG_RESPONSE + Serial.println(); + Serial.print("Read: "); + Serial.println(sz); + Serial.println(); + Serial.print("bufferPos: "); + Serial.println(bufferPos); + Serial.println(); + Serial.print("to_read: "); + Serial.println(to_read); + Serial.println(); + Serial.print("responseLength: "); + Serial.println(responseLength); + printData(chunkedBuffer, sz, bufferPos); +#endif + if (sz <= 0) + return -1; + responseLength -= sz; + } + } + int ch = chunkedBuffer[bufferPos++]; + return ch; +} + +int ParseResponse::getErrorCode() { + return getInt("code"); +} + +const char* ParseResponse::getJSONBody() { + read(); + return buf; +} + +const char* ParseResponse::getString(const char* key) { + read(); + if (!tmpBuf) { + tmpBuf = new char[64]; + } + memset(tmpBuf, 0, 64); + ParseUtils::getStringFromJSON(buf, key, tmpBuf, 64); + return tmpBuf; +} + +int ParseResponse::getInt(const char* key) { + read(); + return ParseUtils::getIntFromJSON(buf, key); +} + +double ParseResponse::getDouble(const char* key) { + read(); + return ParseUtils::getFloatFromJSON(buf, key); +} + +bool ParseResponse::getBoolean(const char* key) { + read(); + return ParseUtils::getBooleanFromJSON(buf, key); +} + +void ParseResponse::readWithTimeout(int maxSec) { + while((!available()) && (maxSec--)) { // wait till response + delay(1000); + } + read(); +} + +bool ParseResponse::nextObject() { + if(resultCount <= 0) { + count(); + } + + if(resultCount <= 0) { + return false; + } + + if (firstObject) { + firstObject = false; + return true; + } else { + return readJson(buf, bufSize); + } +} + +int ParseResponse::count() { + if (resultCount != -1) + return resultCount; + char buff[128]; + + resultCount = 0; + + bool first_line = true; + bool ok = false; + bool done = false; + char *ptr; + long len = 0; + while (client->connected() && !done) { + delay(1); + while (client->available()) { + readLine(buff, sizeof(buff)); + if (first_line) { + if (!strcmp(kHttpOK, buff)) + ok = true; + first_line = false; + } +#ifdef DEBUG_RESPONSE + Serial.print("H->"); + Serial.println(buff); +#endif + if (!strcmp(kChunkedEncoding, buff)) { + isChunked = true; + } else if (!strncmp(kContentLength, buff, sizeof(kContentLength))) { + responseLength = strtol(buff + sizeof(kContentLength), &ptr, 10); + } else if (!buff[0]) { + if (isChunked && client->available()) { + readLine(buff, sizeof(buff)); + responseLength = strtol(buff, &ptr, 16); +#ifdef DEBUG_RESPONSE + Serial.print("First chunk->"); + Serial.println(buff); +#endif + } + done = true; + break; + } + } + } + long persistentResponseLength = responseLength; // responseLength is modified by calls to readChunkedData +#ifdef DEBUG_RESPONSE + sprintf(buff, "Ok:%s Length:%d Chunked:%s", ok ? "y" : "n", responseLength, isChunked ? "y" : "n"); + Serial.println(buff); +#endif + done = false; + freeBuffer(); + setBuffer(new char[kJsonResponseMaxSize], kJsonResponseMaxSize); + dataDone = true; + + for (int i = 0; i < sizeof(kResultsStart) - 1; ++i) { + char c = readChunkedData(kQueryTimeout); + if (c != kResultsStart[i]) { +#ifdef DEBUG_RESPONSE + Serial.println("Malformed response!"); +#endif + resultCount = -1; + return -1; + } + } + + if (!readJson(buf, bufSize)) { +#ifdef DEBUG_RESPONSE + Serial.println("no results"); +#endif + resultCount = 0; + return 0; + } + + int tmplen = strlen(buf); + firstObject = true; + if (tmplen > 0) + resultCount = persistentResponseLength / tmplen; + if (isChunked) + resultCount *= 2; + if (!resultCount) + resultCount = 1; + + return resultCount; +} + +void ParseResponse::freeBuffer() { + if (!isUserBuffer) { // only free non-user buffer + delete[] buf; + buf = NULL; + } + if (tmpBuf) { + delete[] tmpBuf; + tmpBuf = NULL; + } +} + +void ParseResponse::close() { + freeBuffer(); +} + +#endif // ARDUINO_SAMD_ZERO