Contiki-NG
websocket-http-client.c
1 /*
2  * Copyright (c) 2014, Thingsquare, http://www.thingsquare.com/.
3  * All rights reserved.
4  *
5  * Redistribution and use in source and binary forms, with or without
6  * modification, are permitted provided that the following conditions
7  * are met:
8  * 1. Redistributions of source code must retain the above copyright
9  * notice, this list of conditions and the following disclaimer.
10  * 2. Redistributions in binary form must reproduce the above copyright
11  * notice, this list of conditions and the following disclaimer in the
12  * documentation and/or other materials provided with the distribution.
13  * 3. Neither the name of the copyright holder nor the names of its
14  * contributors may be used to endorse or promote products derived
15  * from this software without specific prior written permission.
16  *
17  * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
18  * ``AS IS'' AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
19  * LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS
20  * FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE
21  * COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT,
22  * INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES
23  * (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR
24  * SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION)
25  * HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT,
26  * STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE)
27  * ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED
28  * OF THE POSSIBILITY OF SUCH DAMAGE.
29  *
30  */
31 
32 #include "websocket-http-client.h"
33 #include "net/ipv6/uiplib.h"
34 #include "resolv.h"
35 
36 #include "ipv6/ip64-addr.h"
37 
38 #include <stdio.h>
39 #include <string.h>
40 
41 /* Log configuration */
42 #include "sys/log.h"
43 #define LOG_MODULE "Websocket"
44 #define LOG_LEVEL LOG_LEVEL_NONE
45 
46 enum {
47  STATE_WAITING_FOR_HEADER,
48  STATE_WAITING_FOR_CONNECTED,
49  STATE_STEADY_STATE,
50 };
51 /*---------------------------------------------------------------------------*/
52 static void
53 send_get(struct websocket_http_client_state *s)
54 {
55  struct tcp_socket *tcps;
56 
57  tcps = &s->s;
58  tcp_socket_send_str(tcps, "GET ");
59  tcp_socket_send_str(tcps, s->file);
60  tcp_socket_send_str(tcps, " HTTP/1.1\r\n");
61  tcp_socket_send_str(tcps, "Host: ");
62  tcp_socket_send_str(tcps, s->host);
63  tcp_socket_send_str(tcps, "\r\n");
64  if(strlen(s->header) > 0) {
65  tcp_socket_send_str(tcps, s->header);
66  }
67  /* The Sec-WebSocket-Key: x3JJHMbDL1EzLkh9GBhXDw== header is
68  supposed to be a random value, encoded as base64, that is SHA1
69  hashed by the server and returned in a HTTP header. This is used
70  to make sure that we are not seeing some cached version of this
71  conversation. But we have no SHA1 code by default in Contiki, so
72  we can't check the return value. Therefore we just use a
73  hardcoded value here. */
74  tcp_socket_send_str(tcps,
75  "Connection: Upgrade\r\n"
76  "Upgrade: websocket\r\n"
77  "Sec-WebSocket-Key: x3JJHMbDL1EzLkh9GBhXDw==\r\n"
78  "Sec-WebSocket-Version: 13\r\n"
79  "Sec-WebSocket-Protocol:");
80  tcp_socket_send_str(tcps, s->subprotocol);
81  tcp_socket_send_str(tcps, "\r\n");
82  tcp_socket_send_str(tcps, "\r\n");
83  LOG_INFO("send_get(): output buffer left %d\n", tcp_socket_max_sendlen(tcps));
84 }
85 /*---------------------------------------------------------------------------*/
86 static void
87 send_connect(struct websocket_http_client_state *s)
88 {
89  struct tcp_socket *tcps;
90  char buf[20];
91 
92  tcps = &s->s;
93  tcp_socket_send_str(tcps, "CONNECT ");
94  tcp_socket_send_str(tcps, s->host);
95  tcp_socket_send_str(tcps, ":");
96  sprintf(buf, "%d", s->port);
97  tcp_socket_send_str(tcps, buf);
98  tcp_socket_send_str(tcps, " HTTP/1.1\r\n");
99  tcp_socket_send_str(tcps, "Host: ");
100  tcp_socket_send_str(tcps, s->host);
101  tcp_socket_send_str(tcps, "\r\n");
102  tcp_socket_send_str(tcps, "Proxy-Connection: Keep-Alive\r\n\r\n");
103 }
104 /*---------------------------------------------------------------------------*/
105 static void
106 event(struct tcp_socket *tcps, void *ptr,
107  tcp_socket_event_t e)
108 {
109  struct websocket_http_client_state *s = ptr;
110 
111  if(e == TCP_SOCKET_CONNECTED) {
112  if(s->proxy_port != 0) {
113  send_connect(s);
114  } else {
115  send_get(s);
116  }
117  } else if(e == TCP_SOCKET_CLOSED) {
118  websocket_http_client_closed(s);
119  } else if(e == TCP_SOCKET_TIMEDOUT) {
120  websocket_http_client_timedout(s);
121  } else if(e == TCP_SOCKET_ABORTED) {
122  websocket_http_client_aborted(s);
123  } else if(e == TCP_SOCKET_DATA_SENT) {
124  /* We could feed this information up to the websocket.c layer, but
125  we currently do not do that. */
126  }
127 }
128 /*---------------------------------------------------------------------------*/
129 static int
130 parse_header_byte(struct websocket_http_client_state *s,
131  uint8_t b)
132 {
133  static const char *endmarker = "\r\n\r\n";
134 
135  PT_BEGIN(&s->parse_header_pt);
136 
137  /* Skip the first part of the HTTP response */
138  while(b != ' ') {
139  PT_YIELD(&s->parse_header_pt);
140  }
141 
142  /* Skip the space that follow the first part */
143  PT_YIELD(&s->parse_header_pt);
144 
145  /* Read the first three bytes that constistute the HTTP status
146  code. We store the HTTP status code as an integer in the
147  s->http_status field. */
148  s->http_status = (b - '0');
149  PT_YIELD(&s->parse_header_pt);
150  s->http_status = s->http_status * 10 + (b - '0');
151  PT_YIELD(&s->parse_header_pt);
152  s->http_status = s->http_status * 10 + (b - '0');
153 
154  if((s->proxy_port != 0 && !(s->http_status == 200 || s->http_status == 101)) ||
155  (s->proxy_port == 0 && s->http_status != 101)) {
156  /* This is a websocket request, so the server should have answered
157  with a 101 Switching protocols response. */
158  LOG_WARN("didn't get the 101 status code (got %d), closing connection\n",
159  s->http_status);
160  websocket_http_client_close(s);
161  while(1) {
162  PT_YIELD(&s->parse_header_pt);
163  }
164  }
165 
166  /* Keep eating header bytes until we reach the end of it. The end is
167  indicated by the string "\r\n\r\n". We don't actually look at any
168  of the headers.
169 
170  The s->i variable contains the number of consecutive bytes
171  matched. If we match the total length of the string, we stop.
172  */
173 
174  s->i = 0;
175  do {
176  PT_YIELD(&s->parse_header_pt);
177  if(b == (uint8_t)endmarker[s->i]) {
178  s->i++;
179  } else {
180  s->i = 0;
181  }
182  } while(s->i < strlen(endmarker));
183 
184  if(s->proxy_port != 0 && s->state == STATE_WAITING_FOR_HEADER) {
185  send_get(s);
186  s->state = STATE_WAITING_FOR_CONNECTED;
187  } else {
188  s->state = STATE_STEADY_STATE;
189  websocket_http_client_connected(s);
190  }
191  PT_END(&s->parse_header_pt);
192 }
193 /*---------------------------------------------------------------------------*/
194 static int
195 input(struct tcp_socket *tcps, void *ptr,
196  const uint8_t *inputptr, int inputdatalen)
197 {
198  struct websocket_http_client_state *s = ptr;
199 
200  if(s->state == STATE_WAITING_FOR_HEADER ||
201  s->state == STATE_WAITING_FOR_CONNECTED) {
202  int i;
203  for(i = 0; i < inputdatalen; i++) {
204  parse_header_byte(s, inputptr[i]);
205  if(s->state == STATE_STEADY_STATE) {
206  i++;
207  break;
208  }
209  }
210 
211  if(i < inputdatalen && s->state == STATE_STEADY_STATE) {
212  websocket_http_client_datahandler(s, &inputptr[i], inputdatalen - i);
213  }
214  } else {
215  websocket_http_client_datahandler(s, inputptr, inputdatalen);
216  }
217 
218  return 0; /* all data consumed */
219 }
220 /*---------------------------------------------------------------------------*/
221 int
222 websocket_http_client_register(struct websocket_http_client_state *s,
223  const char *host,
224  uint16_t port,
225  const char *file,
226  const char *subprotocol,
227  const char *header)
228 {
229  if(host == NULL) {
230  return -1;
231  }
232  strncpy(s->host, host, sizeof(s->host));
233 
234  if(file == NULL) {
235  return -1;
236  }
237  strncpy(s->file, file, sizeof(s->file));
238 
239  if(subprotocol == NULL) {
240  return -1;
241  }
242  strncpy(s->subprotocol, subprotocol, sizeof(s->subprotocol));
243 
244  if(header == NULL) {
245  strncpy(s->header, "", sizeof(s->header));
246  } else {
247  strncpy(s->header, header, sizeof(s->header));
248  }
249 
250  if(port == 0) {
251  s->port = 80;
252  } else {
253  s->port = port;
254  }
255  return 1;
256 }
257 /*---------------------------------------------------------------------------*/
258 int
259 websocket_http_client_get(struct websocket_http_client_state *s)
260 {
261  uip_ip4addr_t ip4addr;
262  uip_ip6addr_t ip6addr;
263  uip_ip6addr_t *addr;
264  uint16_t port;
265 
266  LOG_INFO("Get: connecting to %s with file %s subprotocol %s header %s\n",
267  s->host, s->file, s->subprotocol, s->header);
268 
269 
270  s->state = STATE_WAITING_FOR_HEADER;
271 
272  if(tcp_socket_register(&s->s, s,
273  s->inputbuf, sizeof(s->inputbuf),
274  s->outputbuf, sizeof(s->outputbuf),
275  input, event) < 0) {
276  return -1;
277  }
278 
279  port = s->port;
280  if(s->proxy_port != 0) {
281  /* The proxy address should be an IPv6 address. */
282  uip_ipaddr_copy(&ip6addr, &s->proxy_addr);
283  port = s->proxy_port;
284  } else if(uiplib_ip6addrconv(s->host, &ip6addr) == 0) {
285  /* First check if the host is an IP address. */
286  if(uiplib_ip4addrconv(s->host, &ip4addr) != 0) {
287  ip64_addr_4to6(&ip4addr, &ip6addr);
288  } else {
289  /* Try to lookup the hostname. If it fails, we initiate a hostname
290  lookup. */
291  if(resolv_lookup(s->host, &addr) != RESOLV_STATUS_CACHED) {
292  return -1;
293  }
294  return tcp_socket_connect(&s->s, addr, s->port);
295  }
296  }
297  return tcp_socket_connect(&s->s, &ip6addr, port);
298 }
299 /*---------------------------------------------------------------------------*/
300 int
301 websocket_http_client_send(struct websocket_http_client_state *s,
302  const uint8_t *data,
303  uint16_t datalen)
304 {
305  if(s->state == STATE_STEADY_STATE) {
306  return tcp_socket_send(&s->s, data, datalen);
307  }
308  return -1;
309 }
310 /*---------------------------------------------------------------------------*/
311 int
312 websocket_http_client_sendbuflen(struct websocket_http_client_state *s)
313 {
314  return tcp_socket_max_sendlen(&s->s);
315 }
316 /*---------------------------------------------------------------------------*/
317 void
318 websocket_http_client_close(struct websocket_http_client_state *s)
319 {
320  tcp_socket_close(&s->s);
321 }
322 /*---------------------------------------------------------------------------*/
323 const char *
324 websocket_http_client_hostname(struct websocket_http_client_state *s)
325 {
326  return s->host;
327 }
328 /*---------------------------------------------------------------------------*/
329 void
330 websocket_http_client_init(struct websocket_http_client_state *s)
331 {
332  uip_create_unspecified(&s->proxy_addr);
333  s->proxy_port = 0;
334 }
335 /*---------------------------------------------------------------------------*/
336 void
337 websocket_http_client_set_proxy(struct websocket_http_client_state *s,
338  const uip_ipaddr_t *addr, uint16_t port)
339 {
340  uip_ipaddr_copy(&s->proxy_addr, addr);
341  s->proxy_port = port;
342 }
343 /*---------------------------------------------------------------------------*/
344 int
345 websocket_http_client_queuelen(struct websocket_http_client_state *s)
346 {
347  return tcp_socket_queuelen(&s->s);
348 }
349 /*---------------------------------------------------------------------------*/
static uip_ds6_addr_t * addr
Pointer to a nbr cache entry.
Definition: uip-nd6.c:107
uIP DNS resolver code header file.
#define PT_BEGIN(pt)
Declare the start of a protothread inside the C function implementing the protothread.
Definition: pt.h:114
Representation of an IP address.
Definition: uip.h:95
Header file for the IP address manipulation library.
#define PT_END(pt)
Declare the end of a protothread.
Definition: pt.h:126
resolv_status_t resolv_lookup(const char *name, uip_ipaddr_t **ipaddr)
Look up a hostname in the array of known hostnames.
Definition: resolv.c:1277
#define PT_YIELD(pt)
Yield from the current protothread.
Definition: pt.h:289
Hostname is fresh and usable.
Definition: resolv.h:63
#define uip_ipaddr_copy(dest, src)
Copy an IP address from one place to another.
Definition: uip.h:1015
#define uip_create_unspecified(a)
set IP address a to unspecified
Definition: uip.h:1882
Header file for the logging system
static void input(void)
Process a received 6lowpan packet.
Definition: sicslowpan.c:1802