Contiki-NG
Loading...
Searching...
No Matches
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
46enum {
47 STATE_WAITING_FOR_HEADER,
48 STATE_WAITING_FOR_CONNECTED,
49 STATE_STEADY_STATE,
50};
51/*---------------------------------------------------------------------------*/
52static void
53send_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/*---------------------------------------------------------------------------*/
86static void
87send_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/*---------------------------------------------------------------------------*/
105static void
106event(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/*---------------------------------------------------------------------------*/
129static int
130parse_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/*---------------------------------------------------------------------------*/
194static int
195input(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/*---------------------------------------------------------------------------*/
221int
222websocket_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) - 1);
233
234 if(file == NULL) {
235 return -1;
236 }
237 strncpy(s->file, file, sizeof(s->file) - 1);
238
239 if(subprotocol == NULL) {
240 return -1;
241 }
242 strncpy(s->subprotocol, subprotocol, sizeof(s->subprotocol) - 1);
243
244 if(header == NULL) {
245 strncpy(s->header, "", sizeof(s->header) - 1);
246 } else {
247 strncpy(s->header, header, sizeof(s->header) - 1);
248 }
249
250 if(port == 0) {
251 s->port = 80;
252 } else {
253 s->port = port;
254 }
255 return 1;
256}
257/*---------------------------------------------------------------------------*/
258int
259websocket_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/*---------------------------------------------------------------------------*/
300int
301websocket_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/*---------------------------------------------------------------------------*/
311int
312websocket_http_client_sendbuflen(struct websocket_http_client_state *s)
313{
314 return tcp_socket_max_sendlen(&s->s);
315}
316/*---------------------------------------------------------------------------*/
317void
318websocket_http_client_close(struct websocket_http_client_state *s)
319{
320 tcp_socket_close(&s->s);
321}
322/*---------------------------------------------------------------------------*/
323const char *
324websocket_http_client_hostname(struct websocket_http_client_state *s)
325{
326 return s->host;
327}
328/*---------------------------------------------------------------------------*/
329void
330websocket_http_client_init(struct websocket_http_client_state *s)
331{
332 uip_create_unspecified(&s->proxy_addr);
333 s->proxy_port = 0;
334}
335/*---------------------------------------------------------------------------*/
336void
337websocket_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/*---------------------------------------------------------------------------*/
344int
345websocket_http_client_queuelen(struct websocket_http_client_state *s)
346{
347 return tcp_socket_queuelen(&s->s);
348}
349/*---------------------------------------------------------------------------*/
#define PT_YIELD(pt)
Yield from the current protothread.
Definition pt.h:455
#define PT_BEGIN(pt)
Declare the start of a protothread inside the C function implementing the protothread.
Definition pt.h:280
#define PT_END(pt)
Declare the end of a protothread.
Definition pt.h:292
#define uip_create_unspecified(a)
set IP address a to unspecified
Definition uip.h:1771
#define uip_ipaddr_copy(dest, src)
Copy an IP address from one place to another.
Definition uip.h:969
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:1263
Header file for the logging system.
uIP DNS resolver code header file.
@ RESOLV_STATUS_CACHED
Hostname is fresh and usable.
Definition resolv.h:54
static uip_ds6_addr_t * addr
Pointer to a nbr cache entry.
Definition uip-nd6.c:107
Header file for the IP address manipulation library.
Representation of an IP address.
Definition uip.h:95