4 * Author: Markku Rossi <mtr@iki.fi>
6 * Copyright (c) 2011-2012 Markku Rossi
8 * This program is free software: you can redistribute it and/or
9 * modify it under the terms of the GNU General Public License as
10 * published by the Free Software Foundation, either version 3 of the
11 * License, or (at your option) any later version.
13 * This program is distributed in the hope that it will be useful,
14 * but WITHOUT ANY WARRANTY; without even the implied warranty of
15 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
16 * General Public License for more details.
18 * You should have received a copy of the GNU General Public License
19 * along with this program. If not, see
20 * <http://www.gnu.org/licenses/>.
26 const static char hex_table[] PROGMEM = "0123456789ABCDEF";
27 const static char base64_table[] PROGMEM
28 = "ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789+/";
30 const static char s_jan[] PROGMEM = "Jan";
31 const static char s_feb[] PROGMEM = "Feb";
32 const static char s_mar[] PROGMEM = "Mar";
33 const static char s_apr[] PROGMEM = "Apr";
34 const static char s_may[] PROGMEM = "May";
35 const static char s_jun[] PROGMEM = "Jun";
36 const static char s_jul[] PROGMEM = "Jul";
37 const static char s_aug[] PROGMEM = "Aug";
38 const static char s_sep[] PROGMEM = "Sep";
39 const static char s_oct[] PROGMEM = "Oct";
40 const static char s_nov[] PROGMEM = "Nov";
41 const static char s_dec[] PROGMEM = "Dec";
43 const static char *months[] PROGMEM =
45 s_jan, s_feb, s_mar, s_apr, s_may, s_jun,
46 s_jul, s_aug, s_sep, s_oct, s_nov, s_dec,
49 Twitter::Twitter(char *buffer, size_t buffer_len)
54 buffer_len(buffer_len),
62 Twitter::set_twitter_endpoint(const prog_char server[], const prog_char uri[],
63 IPAddress ip, uint16_t port, bool proxy)
65 this->server = server;
70 this->proxy = proxy ? 1 : 0;
74 Twitter::set_client_id(const prog_char consumer_key[],
75 const prog_char consumer_secret[])
77 this->consumer_key = consumer_key;
78 this->consumer_secret = consumer_secret;
82 Twitter::set_account_id(const prog_char access_token[],
83 const prog_char token_secret[])
85 this->access_token.pgm = access_token;
86 this->token_secret.pgm = token_secret;
88 this->access_token_pgm = 1;
92 Twitter::set_account_id(int access_token, int token_secret)
94 this->access_token.eeprom = access_token;
95 this->token_secret.eeprom = token_secret;
97 this->access_token_pgm = 0;
101 Twitter::is_ready(void)
110 Twitter::get_time(void)
112 unsigned long now = millis();
114 if (now < last_millis)
115 /* Our internal clock wrapped around. */
116 basetime += 0xffffffffL / 1000L;
120 return basetime + now / 1000L;
124 Twitter::query_time(void)
128 if (!http.connect(ip, port))
130 println(PSTR("query_time: could not connect to server"));
134 http_print(&http, PSTR("HEAD "));
138 http_print(&http, PSTR("http://"));
139 http_print(&http, server);
142 http_println(&http, PSTR("/ HTTP/1.1"));
144 http_print(&http, PSTR("Host: "));
145 http_print(&http, server);
148 http_println(&http, PSTR("Connection: close"));
152 while (http.connected())
154 if (!read_line(&http, buffer, buffer_len))
157 if (buffer[0] == '\0')
160 if (process_date_header(buffer))
166 return basetime != 0L;
170 Twitter::process_date_header(char *buffer)
172 if ((buffer[0] != 'D' && buffer[0] != 'd')
173 || (buffer[1] != 'A' && buffer[1] != 'a')
174 || (buffer[2] != 'T' && buffer[2] != 't')
175 || (buffer[3] != 'E' && buffer[3] != 'e')
176 || (buffer[4] != ':'))
179 unsigned long now = parse_date(buffer + 5);
183 /* We managed to parse the date header, now update system
185 last_millis = millis();
186 basetime = now - last_millis / 1000L;
193 Twitter::parse_date(char *date)
199 memset(&tm, 0, sizeof(tm));
201 /* We could use sscanf to parse the date string but it adds 26 bytes
202 to our SRAM consumption so let's use this adhoc parser. */
204 for (cp = date; *cp && *cp != ','; cp++)
210 tm.Day = strtol(++cp, &end, 10);
217 for (; *cp && *cp == ' '; cp++)
222 tm.Month = parse_month(cp, &end);
228 tm.Year = strtol(cp, &end, 10) - 1970;
235 tm.Hour = strtol(cp, &end, 10);
242 tm.Minute = strtol(cp, &end, 10);
249 tm.Second = strtol(cp, &end, 10);
258 Twitter::parse_month(char *str, char **end)
263 for (cp = str; *cp && *cp != ' '; cp++)
273 for (i = 0; i < 12; i++)
274 if (strcmp_P(str, (char *) pgm_read_word(&(months[i]))) == 0)
281 Twitter::post_status(const char *message)
286 timestamp = get_time();
289 compute_authorization(message);
291 /* Post message to twitter. */
295 if (!http.connect(ip, port))
297 println(PSTR("Could not connect to server"));
301 http_print(&http, PSTR("POST "));
305 http_print(&http, PSTR("http://"));
306 http_print(&http, server);
309 http_print(&http, uri);
310 http_println(&http, PSTR(" HTTP/1.1"));
312 http_print(&http, PSTR("Host: "));
313 http_print(&http, server);
317 PSTR("Content-Type: application/x-www-form-urlencoded"));
318 http_println(&http, PSTR("Connection: close"));
320 /* Authorization header. */
321 http_print(&http, PSTR("Authorization: OAuth oauth_consumer_key=\""));
323 url_encode_pgm(buffer, consumer_key);
326 http_print(&http, PSTR("\",oauth_signature_method=\"HMAC-SHA1"));
327 http_print(&http, PSTR("\",oauth_timestamp=\""));
329 sprintf(buffer, "%ld", timestamp);
332 http_print(&http, PSTR("\",oauth_nonce=\""));
334 hex_encode(buffer, nonce, sizeof(nonce));
337 http_print(&http, PSTR("\",oauth_version=\"1.0\",oauth_token=\""));
339 if (access_token_pgm)
340 url_encode_pgm(buffer, access_token.pgm);
342 url_encode_eeprom(buffer, access_token.eeprom);
346 http_print(&http, PSTR("\",oauth_signature=\""));
348 cp = base64_encode(buffer, signature, HASH_LENGTH);
349 url_encode(cp + 1, buffer);
353 http_println(&http, PSTR("\""));
355 /* Encode content. */
356 cp = url_encode(buffer, "status");
358 cp = url_encode(cp, message);
360 int content_length = cp - buffer;
361 sprintf(cp + 1, "%d", content_length);
363 http_print(&http, PSTR("Content-Length: "));
367 /* Header-body separator. */
370 /* And finally content. */
373 /* Read response status line. */
374 if (!read_line(&http, buffer, buffer_len) || buffer[0] == '\0')
382 /* HTTP/1.1 200 Success */
383 for (i = 0; buffer[i] && buffer[i] != ' '; i++)
386 response_code = atoi(buffer + i + 1);
390 bool success = (200 <= response_code && response_code < 300);
393 Serial.println(buffer);
398 if (!read_line(&http, buffer, buffer_len))
404 if (buffer[0] == '\0')
407 /* Update our system basetime from the response `Date'
409 process_date_header(buffer);
412 /* Handle content. */
413 while (http.connected())
415 while (http.available() > 0)
417 uint8_t byte = http.read();
434 Twitter::url_encode(char *buffer, char ch)
436 if ('0' <= ch && ch <= '9'
437 || 'a' <= ch && ch <= 'z' || 'A' <= ch && ch <= 'Z'
438 || ch == '-' || ch == '.' || ch == '_' || ch == '~')
444 int val = ((int) ch) & 0xff;
446 snprintf(buffer, 4, "%%%02X", val);
456 Twitter::url_encode(char *buffer, const char *data)
460 while ((ch = *data++))
461 buffer = url_encode(buffer, ch);
467 Twitter::url_encode_pgm(char *buffer, const prog_char data[])
471 while ((ch = pgm_read_byte(data++)))
472 buffer = url_encode(buffer, ch);
478 Twitter::url_encode_eeprom(char *buffer, int address)
482 while ((ch = EEPROM.read(address++)))
483 buffer = url_encode(buffer, ch);
489 Twitter::hex_encode(char *buffer, const uint8_t *data, size_t data_len)
493 for (i = 0; i < data_len; i++)
495 *buffer++ = (char) pgm_read_byte(hex_table + (data[i] >> 4));
496 *buffer++ = (char) pgm_read_byte(hex_table + (data[i] & 0x0f));
505 Twitter::base64_encode(char *buffer, const uint8_t *data, size_t data_len)
509 for (i = 0, len = data_len; len > 0; i += 3, len -= 3)
513 val = (unsigned long) data[i] << 16;
515 val |= (unsigned long) data[i + 1] << 8;
519 *buffer++ = (char) pgm_read_byte(base64_table + (val >> 18));
520 *buffer++ = (char) pgm_read_byte(base64_table + ((val >> 12) & 0x3f));
521 *buffer++ = (char) pgm_read_byte(base64_table + ((val >> 6) & 0x3f));
522 *buffer++ = (char) pgm_read_byte(base64_table + (val & 0x3f));
525 for (; len < 0; len++)
534 Twitter::create_nonce(void)
538 /* Nonce must be unique for the request timestamp value, so let's
539 use the timestamp as our random seed. */
542 for (i = 0; i < sizeof(nonce); i++)
543 nonce[i] = (uint8_t) random();
547 Twitter::compute_authorization(const char *message)
551 /* Compute key and init HMAC. */
553 cp = url_encode_pgm(buffer, consumer_secret);
556 if (access_token_pgm)
557 cp = url_encode_pgm(cp, token_secret.pgm);
559 cp = url_encode_eeprom(cp, token_secret.eeprom);
561 Sha1.initHmac((uint8_t *) buffer, cp - buffer);
563 auth_add_pgm(PSTR("POST&http%3A%2F%2F"));
564 auth_add_pgm(server);
566 url_encode_pgm(buffer, uri);
571 auth_add_pgm(PSTR("oauth_consumer_key"));
572 auth_add_value_separator();
573 url_encode_pgm(buffer, consumer_key);
576 cp = hex_encode(buffer, nonce, sizeof(nonce));
578 auth_add_param(PSTR("oauth_nonce"), buffer, cp + 1);
580 auth_add_param(PSTR("oauth_signature_method"), "HMAC-SHA1", buffer);
582 sprintf(buffer, "%ld", timestamp);
583 auth_add_param(PSTR("oauth_timestamp"), buffer, cp + 1);
585 auth_add_param_separator();
587 auth_add_pgm(PSTR("oauth_token"));
588 auth_add_value_separator();
589 if (access_token_pgm)
590 url_encode_pgm(buffer, access_token.pgm);
592 url_encode_eeprom(buffer, access_token.eeprom);
595 auth_add_param(PSTR("oauth_version"), "1.0", buffer);
597 cp = url_encode(buffer, message);
598 auth_add_param(PSTR("status"), buffer, cp + 1);
600 signature = Sha1.resultHmac();
604 Twitter::auth_add(char ch)
610 Twitter::auth_add(const char *str)
616 Twitter::auth_add_pgm(const prog_char str[])
620 while ((c = pgm_read_byte(str++)))
625 Twitter::auth_add_param(const prog_char key[], const char *value, char *workbuf)
627 /* Add separator. We know that this method is not used to add the
629 auth_add_param_separator();
633 auth_add_value_separator();
635 url_encode(workbuf, value);
640 Twitter::auth_add_param_separator(void)
642 auth_add_pgm(PSTR("%26"));
646 Twitter::auth_add_value_separator(void)
648 auth_add_pgm(PSTR("%3D"));
652 Twitter::http_print(Client *client, const prog_char str[])
659 while ((c = pgm_read_byte(str++)))
664 Twitter::http_println(Client *client, const prog_char str[])
666 http_print(client, str);
667 http_newline(client);
671 Twitter::http_newline(Client *client)
678 Twitter::println(const prog_char str[])
685 while ((c = pgm_read_byte(str++)))
693 Twitter::read_line(Client *client, char *buffer, size_t buflen)
697 while (client->connected())
699 while (client->available() > 0)
701 uint8_t byte = client->read();
708 if (pos > 0 && buffer[pos - 1] == '\r')
715 buffer[buflen - 1] = '\0';
722 buffer[pos++] = byte;