[136] | 1 | /*
|
---|
| 2 | ###############################################################################
|
---|
| 3 | #
|
---|
| 4 | # Temboo Arduino library
|
---|
| 5 | #
|
---|
| 6 | # Copyright 2014, Temboo Inc.
|
---|
| 7 | #
|
---|
| 8 | # Licensed under the Apache License, Version 2.0 (the "License");
|
---|
| 9 | # you may not use this file except in compliance with the License.
|
---|
| 10 | # You may obtain a copy of the License at
|
---|
| 11 | #
|
---|
| 12 | # http://www.apache.org/licenses/LICENSE-2.0
|
---|
| 13 | #
|
---|
| 14 | # Unless required by applicable law or agreed to in writing,
|
---|
| 15 | # software distributed under the License is distributed on an
|
---|
| 16 | # "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND,
|
---|
| 17 | # either express or implied. See the License for the specific
|
---|
| 18 | # language governing permissions and limitations under the License.
|
---|
| 19 | #
|
---|
| 20 | ###############################################################################
|
---|
| 21 | */
|
---|
| 22 |
|
---|
| 23 | #include <string.h>
|
---|
| 24 | #include <stdlib.h>
|
---|
| 25 | #include <avr/pgmspace.h>
|
---|
| 26 | #include "TembooSession.h"
|
---|
| 27 | #include "tmbhmac.h"
|
---|
| 28 | #include "DataFormatter.h"
|
---|
| 29 |
|
---|
| 30 | static const char EOL[] PROGMEM = "\r\n";
|
---|
| 31 | static const char POST[] PROGMEM = "POST ";
|
---|
| 32 | static const char POSTAMBLE[] PROGMEM = " HTTP/1.0"; // Prevent host from using chunked encoding in response.
|
---|
| 33 | static const char HEADER_HOST[] PROGMEM = "Host: ";
|
---|
| 34 | static const char HEADER_ACCEPT[] PROGMEM = "Accept: application/xml";
|
---|
| 35 | static const char HEADER_ORG[] PROGMEM = "x-temboo-domain: /";
|
---|
| 36 | static const char HEADER_DOM[] PROGMEM = "/master";
|
---|
| 37 | static const char HEADER_CONTENT_LENGTH[] PROGMEM = "Content-Length: ";
|
---|
| 38 | static const char HEADER_TIME[] PROGMEM = "x-temboo-time: ";
|
---|
| 39 | static const char BASE_CHOREO_URI[] PROGMEM = "/arcturus-web/api-1.0/ar";
|
---|
| 40 | static const char HEADER_AUTH[] PROGMEM = "x-temboo-authentication: ";
|
---|
| 41 | static const char HEADER_CONTENT_TYPE[] PROGMEM = "Content-Type: text/plain";
|
---|
| 42 | static const char TEMBOO_DOMAIN[] PROGMEM = ".temboolive.com";
|
---|
| 43 | static const char SDK_ID[] PROGMEM = "?source_id=arduinoSDK1";
|
---|
| 44 |
|
---|
| 45 | unsigned long TembooSession::s_timeOffset = 0;
|
---|
| 46 |
|
---|
| 47 | TembooSession::TembooSession(Client& client,
|
---|
| 48 | IPAddress serverAddr,
|
---|
| 49 | uint16_t port) : m_client(client) {
|
---|
| 50 | m_addr = serverAddr;
|
---|
| 51 | m_port = port;
|
---|
| 52 | m_sendQueueDepth = 0;
|
---|
| 53 | }
|
---|
| 54 |
|
---|
| 55 |
|
---|
| 56 | void TembooSession::setTime(unsigned long currentTime) {
|
---|
| 57 | s_timeOffset = currentTime - (millis()/1000);
|
---|
| 58 | }
|
---|
| 59 |
|
---|
| 60 |
|
---|
| 61 | unsigned long TembooSession::getTime() {
|
---|
| 62 | return s_timeOffset + (millis()/1000);
|
---|
| 63 | }
|
---|
| 64 |
|
---|
| 65 |
|
---|
| 66 |
|
---|
| 67 | int TembooSession::executeChoreo(
|
---|
| 68 | const char* accountName,
|
---|
| 69 | const char* appKeyName,
|
---|
| 70 | const char* appKeyValue,
|
---|
| 71 | const char* path,
|
---|
| 72 | const ChoreoInputSet& inputSet,
|
---|
| 73 | const ChoreoOutputSet& outputSet,
|
---|
| 74 | const ChoreoPreset& preset) {
|
---|
| 75 |
|
---|
| 76 | DataFormatter fmt(&inputSet, &outputSet, &preset);
|
---|
| 77 | char auth[HMAC_HEX_SIZE_BYTES + 1];
|
---|
| 78 | char buffer[11];
|
---|
| 79 |
|
---|
| 80 | // We use the current time-of-day as salt on the app key.
|
---|
| 81 | // We keep track of time-of-day by getting the current time
|
---|
| 82 | // from the server and applying an offset (the length of time
|
---|
| 83 | // we've been running.)
|
---|
| 84 | uint32toa((uint32_t)TembooSession::getTime(), buffer);
|
---|
| 85 |
|
---|
| 86 | uint16_t contentLength = getAuth(fmt, appKeyValue, buffer, auth);
|
---|
| 87 |
|
---|
| 88 | m_client.stop();
|
---|
| 89 | m_client.flush();
|
---|
| 90 |
|
---|
| 91 | int connected = 0;
|
---|
| 92 | TEMBOO_TRACE("Connecting: ");
|
---|
| 93 |
|
---|
| 94 | // reserve space for the "host" string sufficient to hold either the
|
---|
| 95 | // (dotted-quad) IP address + port, or the default <account>.temboolive.com
|
---|
| 96 | // host string.
|
---|
| 97 | int hostLen = (m_addr == INADDR_NONE ? (strlen_P(TEMBOO_DOMAIN) + strlen(accountName) + 1):21);
|
---|
| 98 | char host[hostLen];
|
---|
| 99 |
|
---|
| 100 | // If no explicit IP address was specified (the normal case), construct
|
---|
| 101 | // the "host" string from the account name and the temboo domain name.
|
---|
| 102 | if (m_addr == INADDR_NONE) {
|
---|
| 103 | strcpy(host, accountName);
|
---|
| 104 | strcat_P(host, TEMBOO_DOMAIN);
|
---|
| 105 | TEMBOO_TRACELN(host);
|
---|
| 106 | connected = m_client.connect(host, m_port);
|
---|
| 107 | } else {
|
---|
| 108 |
|
---|
| 109 | // If an IP address was explicitly specified (presumably for testing purposes),
|
---|
| 110 | // convert it to a dotted-quad text string.
|
---|
| 111 | host[0] = '\0';
|
---|
| 112 | for(int i = 0; i < 4; i++) {
|
---|
| 113 | uint16toa(m_addr[i], &host[strlen(host)]);
|
---|
| 114 | strcat(host, ".");
|
---|
| 115 | }
|
---|
| 116 |
|
---|
| 117 | // replace the last '.' with ':'
|
---|
| 118 | host[strlen(host)-1] = ':';
|
---|
| 119 |
|
---|
| 120 | // append the port number
|
---|
| 121 | uint16toa(m_port, &host[strlen(host)]);
|
---|
| 122 |
|
---|
| 123 | TEMBOO_TRACELN(host);
|
---|
| 124 | connected = m_client.connect(m_addr, m_port);
|
---|
| 125 | }
|
---|
| 126 |
|
---|
| 127 | if (connected) {
|
---|
| 128 |
|
---|
| 129 | TEMBOO_TRACELN("OK. req:");
|
---|
| 130 | qsendProgmem(POST);
|
---|
| 131 | qsendProgmem(BASE_CHOREO_URI);
|
---|
| 132 | qsend(path);
|
---|
| 133 | qsendProgmem(SDK_ID);
|
---|
| 134 | qsendlnProgmem(POSTAMBLE);
|
---|
| 135 |
|
---|
| 136 | // Send our custom authentication header
|
---|
| 137 | // (app-key-name:hmac)
|
---|
| 138 | qsendProgmem(HEADER_AUTH);
|
---|
| 139 | qsend(appKeyName);
|
---|
| 140 | qsend(":");
|
---|
| 141 | qsendln(auth);
|
---|
| 142 |
|
---|
| 143 | // send the standard host header
|
---|
| 144 | qsendProgmem(HEADER_HOST);
|
---|
| 145 | qsendln(host);
|
---|
| 146 |
|
---|
| 147 | // send the standard accept header
|
---|
| 148 | qsendlnProgmem(HEADER_ACCEPT);
|
---|
| 149 |
|
---|
| 150 | // send our custom account name neader
|
---|
| 151 | qsendProgmem(HEADER_ORG);
|
---|
| 152 | qsend(accountName);
|
---|
| 153 | qsendlnProgmem(HEADER_DOM);
|
---|
| 154 |
|
---|
| 155 | // send the standard content type header
|
---|
| 156 | qsendlnProgmem(HEADER_CONTENT_TYPE);
|
---|
| 157 |
|
---|
| 158 | // send our custom client time header
|
---|
| 159 | qsendProgmem(HEADER_TIME);
|
---|
| 160 | qsendln(buffer);
|
---|
| 161 |
|
---|
| 162 | // send the standard content length header
|
---|
| 163 | qsendProgmem(HEADER_CONTENT_LENGTH);
|
---|
| 164 | qsendln(uint16toa(contentLength, buffer));
|
---|
| 165 |
|
---|
| 166 | qsendProgmem(EOL);
|
---|
| 167 |
|
---|
| 168 | // Format and send the body of the request
|
---|
| 169 | fmt.reset();
|
---|
| 170 | while(fmt.hasNext()) {
|
---|
| 171 | qsend(fmt.next());
|
---|
| 172 | }
|
---|
| 173 |
|
---|
| 174 | qsendProgmem(EOL);
|
---|
| 175 | qflush();
|
---|
| 176 | return 0;
|
---|
| 177 | } else {
|
---|
| 178 | TEMBOO_TRACELN("FAIL");
|
---|
| 179 | return 1;
|
---|
| 180 | }
|
---|
| 181 | }
|
---|
| 182 |
|
---|
| 183 |
|
---|
| 184 | uint16_t TembooSession::getAuth(DataFormatter& fmt, const char* appKeyValue, const char* salt, char* result) const {
|
---|
| 185 |
|
---|
| 186 | // We need the length of the data for other things, and
|
---|
| 187 | // this method is a convenient place to calculate it.
|
---|
| 188 | uint16_t len = 0;
|
---|
| 189 |
|
---|
| 190 | HMAC hmac;
|
---|
| 191 |
|
---|
| 192 | //combine the salt and the key and give it to the HMAC calculator
|
---|
| 193 | size_t keyLength = strlen(appKeyValue) + strlen(salt);
|
---|
| 194 | char key[keyLength + 1];
|
---|
| 195 | strcpy(key, salt);
|
---|
| 196 | strcat(key, appKeyValue);
|
---|
| 197 | hmac.init((uint8_t*)key, keyLength);
|
---|
| 198 |
|
---|
| 199 | // process the data a block at a time.
|
---|
| 200 | uint8_t buffer[HMAC_BLOCK_SIZE_BYTES];
|
---|
| 201 | int blockCount = 0;
|
---|
| 202 | fmt.reset();
|
---|
| 203 | while(fmt.hasNext()) {
|
---|
| 204 | uint8_t c = fmt.next();
|
---|
| 205 | len++;
|
---|
| 206 | buffer[blockCount++] = c;
|
---|
| 207 | if (blockCount == HMAC_BLOCK_SIZE_BYTES) {
|
---|
| 208 | hmac.process(buffer, blockCount);
|
---|
| 209 | blockCount = 0;
|
---|
| 210 | }
|
---|
| 211 | }
|
---|
| 212 | hmac.process(buffer, blockCount);
|
---|
| 213 |
|
---|
| 214 | // Finalize the HMAC calculation and store the (ASCII HEX) value in *result.
|
---|
| 215 | hmac.finishHex(result);
|
---|
| 216 |
|
---|
| 217 | // Return the number of characters processed.
|
---|
| 218 | return len;
|
---|
| 219 | }
|
---|
| 220 |
|
---|
| 221 |
|
---|
| 222 | void TembooSession::qsend(const char* s) {
|
---|
| 223 | char c = *s++;
|
---|
| 224 | while(c != '\0') {
|
---|
| 225 | qsend(c);
|
---|
| 226 | c = *s++;
|
---|
| 227 | }
|
---|
| 228 | }
|
---|
| 229 |
|
---|
| 230 |
|
---|
| 231 | void TembooSession::qsendProgmem(const char* s) {
|
---|
| 232 | char c = pgm_read_byte(s++);
|
---|
| 233 | while(c != '\0') {
|
---|
| 234 | qsend(c);
|
---|
| 235 | c = pgm_read_byte(s++);
|
---|
| 236 | }
|
---|
| 237 | }
|
---|
| 238 |
|
---|
| 239 |
|
---|
| 240 | void TembooSession::qsend(char c) {
|
---|
| 241 | m_sendQueue[m_sendQueueDepth++] = c;
|
---|
| 242 | if (m_sendQueueDepth >= TEMBOO_SEND_QUEUE_SIZE) {
|
---|
| 243 | qflush();
|
---|
| 244 | }
|
---|
| 245 | }
|
---|
| 246 |
|
---|
| 247 |
|
---|
| 248 | void TembooSession::qflush() {
|
---|
| 249 | m_client.write((const uint8_t*)m_sendQueue, m_sendQueueDepth);
|
---|
| 250 | TEMBOO_TRACE_BYTES(m_sendQueue, m_sendQueueDepth);
|
---|
| 251 | m_sendQueueDepth = 0;
|
---|
| 252 | }
|
---|
| 253 |
|
---|
| 254 |
|
---|
| 255 | void TembooSession::qsendln(const char* s) {
|
---|
| 256 | qsend(s);
|
---|
| 257 | qsendProgmem(EOL);
|
---|
| 258 | }
|
---|
| 259 |
|
---|
| 260 |
|
---|
| 261 | void TembooSession::qsendlnProgmem(const char* s) {
|
---|
| 262 | qsendProgmem(s);
|
---|
| 263 | qsendProgmem(EOL);
|
---|
| 264 | }
|
---|
| 265 |
|
---|
| 266 |
|
---|