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 |
|
---|