Merge pull request #1918 from adamdunkels/pr-websocket-2
Contiki websocket client
This commit is contained in:
commit
95f69d92a8
|
@ -29,6 +29,9 @@
|
||||||
*
|
*
|
||||||
*/
|
*/
|
||||||
|
|
||||||
|
#define DEBUG DEBUG_NONE
|
||||||
|
#include "net/ip/uip-debug.h"
|
||||||
|
|
||||||
#include "contiki.h"
|
#include "contiki.h"
|
||||||
#include "sys/cc.h"
|
#include "sys/cc.h"
|
||||||
#include "contiki-net.h"
|
#include "contiki-net.h"
|
||||||
|
@ -37,10 +40,8 @@
|
||||||
|
|
||||||
#include "tcp-socket.h"
|
#include "tcp-socket.h"
|
||||||
|
|
||||||
#include <stdio.h>
|
|
||||||
#include <string.h>
|
#include <string.h>
|
||||||
|
|
||||||
|
|
||||||
static void relisten(struct tcp_socket *s);
|
static void relisten(struct tcp_socket *s);
|
||||||
|
|
||||||
LIST(socketlist);
|
LIST(socketlist);
|
||||||
|
@ -80,7 +81,7 @@ acked(struct tcp_socket *s)
|
||||||
s->output_data_maxlen - s->output_data_send_nxt);
|
s->output_data_maxlen - s->output_data_send_nxt);
|
||||||
}
|
}
|
||||||
if(s->output_data_len < s->output_data_send_nxt) {
|
if(s->output_data_len < s->output_data_send_nxt) {
|
||||||
printf("tcp: acked assertion failed s->output_data_len (%d) < s->output_data_send_nxt (%d)\n",
|
PRINTF("tcp: acked assertion failed s->output_data_len (%d) < s->output_data_send_nxt (%d)\n",
|
||||||
s->output_data_len,
|
s->output_data_len,
|
||||||
s->output_data_send_nxt);
|
s->output_data_send_nxt);
|
||||||
tcp_markconn(uip_conn, NULL);
|
tcp_markconn(uip_conn, NULL);
|
||||||
|
@ -121,7 +122,7 @@ newdata(struct tcp_socket *s)
|
||||||
bytesleft = 0;
|
bytesleft = 0;
|
||||||
}
|
}
|
||||||
if(bytesleft > 0) {
|
if(bytesleft > 0) {
|
||||||
printf("tcp: newdata, bytesleft > 0 (%d) not implemented\n", bytesleft);
|
PRINTF("tcp: newdata, bytesleft > 0 (%d) not implemented\n", bytesleft);
|
||||||
}
|
}
|
||||||
dataptr += copylen;
|
dataptr += copylen;
|
||||||
len -= copylen;
|
len -= copylen;
|
||||||
|
@ -356,6 +357,8 @@ tcp_socket_send(struct tcp_socket *s,
|
||||||
s->output_senddata_len = s->output_data_len;
|
s->output_senddata_len = s->output_data_len;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
tcpip_poll_tcp(s->c);
|
||||||
|
|
||||||
return len;
|
return len;
|
||||||
}
|
}
|
||||||
/*---------------------------------------------------------------------------*/
|
/*---------------------------------------------------------------------------*/
|
||||||
|
@ -398,3 +401,9 @@ tcp_socket_max_sendlen(struct tcp_socket *s)
|
||||||
return s->output_data_maxlen - s->output_data_len;
|
return s->output_data_maxlen - s->output_data_len;
|
||||||
}
|
}
|
||||||
/*---------------------------------------------------------------------------*/
|
/*---------------------------------------------------------------------------*/
|
||||||
|
int
|
||||||
|
tcp_socket_queuelen(struct tcp_socket *s)
|
||||||
|
{
|
||||||
|
return s->output_data_len;
|
||||||
|
}
|
||||||
|
/*---------------------------------------------------------------------------*/
|
||||||
|
|
|
@ -284,4 +284,16 @@ int tcp_socket_unregister(struct tcp_socket *s);
|
||||||
*/
|
*/
|
||||||
int tcp_socket_max_sendlen(struct tcp_socket *s);
|
int tcp_socket_max_sendlen(struct tcp_socket *s);
|
||||||
|
|
||||||
|
/**
|
||||||
|
* \brief The number of bytes waiting to be sent
|
||||||
|
* \param s A pointer to a TCP socket
|
||||||
|
* \return The number of bytes that have not yet been acknowledged by the receiver.
|
||||||
|
*
|
||||||
|
* This function queries the TCP socket and returns the
|
||||||
|
* number of bytes that are currently not yet known to
|
||||||
|
* have been successfully received by the receiver.
|
||||||
|
*
|
||||||
|
*/
|
||||||
|
int tcp_socket_queuelen(struct tcp_socket *s);
|
||||||
|
|
||||||
#endif /* TCP_SOCKET_H */
|
#endif /* TCP_SOCKET_H */
|
||||||
|
|
347
core/net/ipv6/websocket-http-client.c
Normal file
347
core/net/ipv6/websocket-http-client.c
Normal file
|
@ -0,0 +1,347 @@
|
||||||
|
/*
|
||||||
|
* Copyright (c) 2014, Thingsquare, http://www.thingsquare.com/.
|
||||||
|
* All rights reserved.
|
||||||
|
*
|
||||||
|
* Redistribution and use in source and binary forms, with or without
|
||||||
|
* modification, are permitted provided that the following conditions
|
||||||
|
* are met:
|
||||||
|
* 1. Redistributions of source code must retain the above copyright
|
||||||
|
* notice, this list of conditions and the following disclaimer.
|
||||||
|
* 2. Redistributions in binary form must reproduce the above copyright
|
||||||
|
* notice, this list of conditions and the following disclaimer in the
|
||||||
|
* documentation and/or other materials provided with the distribution.
|
||||||
|
* 3. Neither the name of the copyright holder nor the names of its
|
||||||
|
* contributors may be used to endorse or promote products derived
|
||||||
|
* from this software without specific prior written permission.
|
||||||
|
*
|
||||||
|
* THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
|
||||||
|
* ``AS IS'' AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
|
||||||
|
* LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS
|
||||||
|
* FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE
|
||||||
|
* COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT,
|
||||||
|
* INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES
|
||||||
|
* (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR
|
||||||
|
* SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION)
|
||||||
|
* HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT,
|
||||||
|
* STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE)
|
||||||
|
* ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED
|
||||||
|
* OF THE POSSIBILITY OF SUCH DAMAGE.
|
||||||
|
*
|
||||||
|
*/
|
||||||
|
|
||||||
|
#include "websocket-http-client.h"
|
||||||
|
#include "net/ip/uiplib.h"
|
||||||
|
#include "net/ip/resolv.h"
|
||||||
|
|
||||||
|
#include "ip64-addr.h"
|
||||||
|
|
||||||
|
#include <stdio.h>
|
||||||
|
#include <string.h>
|
||||||
|
|
||||||
|
#define DEBUG DEBUG_NONE
|
||||||
|
#include "net/ip/uip-debug.h"
|
||||||
|
|
||||||
|
enum {
|
||||||
|
STATE_WAITING_FOR_HEADER,
|
||||||
|
STATE_WAITING_FOR_CONNECTED,
|
||||||
|
STATE_STEADY_STATE,
|
||||||
|
};
|
||||||
|
/*---------------------------------------------------------------------------*/
|
||||||
|
static void
|
||||||
|
send_get(struct websocket_http_client_state *s)
|
||||||
|
{
|
||||||
|
struct tcp_socket *tcps;
|
||||||
|
|
||||||
|
tcps = &s->s;
|
||||||
|
tcp_socket_send_str(tcps, "GET ");
|
||||||
|
tcp_socket_send_str(tcps, s->file);
|
||||||
|
tcp_socket_send_str(tcps, " HTTP/1.1\r\n");
|
||||||
|
tcp_socket_send_str(tcps, "Host: ");
|
||||||
|
tcp_socket_send_str(tcps, s->host);
|
||||||
|
tcp_socket_send_str(tcps, "\r\n");
|
||||||
|
if(strlen(s->header) > 0) {
|
||||||
|
tcp_socket_send_str(tcps, s->header);
|
||||||
|
}
|
||||||
|
/* The Sec-WebSocket-Key: x3JJHMbDL1EzLkh9GBhXDw== header is
|
||||||
|
supposed to be a random value, encoded as base64, that is SHA1
|
||||||
|
hashed by the server and returned in a HTTP header. This is used
|
||||||
|
to make sure that we are not seeing some cached version of this
|
||||||
|
conversation. But we have no SHA1 code by default in Contiki, so
|
||||||
|
we can't check the return value. Therefore we just use a
|
||||||
|
hardcoded value here. */
|
||||||
|
tcp_socket_send_str(tcps,
|
||||||
|
"Connection: Upgrade\r\n"
|
||||||
|
"Upgrade: websocket\r\n"
|
||||||
|
"Sec-WebSocket-Key: x3JJHMbDL1EzLkh9GBhXDw==\r\n"
|
||||||
|
"Sec-WebSocket-Version: 13\r\n"
|
||||||
|
"Sec-WebSocket-Protocol:");
|
||||||
|
tcp_socket_send_str(tcps, s->subprotocol);
|
||||||
|
tcp_socket_send_str(tcps, "\r\n");
|
||||||
|
tcp_socket_send_str(tcps, "\r\n");
|
||||||
|
PRINTF("websocket-http-client: send_get(): output buffer left %d\n", tcp_socket_max_sendlen(tcps));
|
||||||
|
}
|
||||||
|
/*---------------------------------------------------------------------------*/
|
||||||
|
static void
|
||||||
|
send_connect(struct websocket_http_client_state *s)
|
||||||
|
{
|
||||||
|
struct tcp_socket *tcps;
|
||||||
|
char buf[20];
|
||||||
|
|
||||||
|
tcps = &s->s;
|
||||||
|
tcp_socket_send_str(tcps, "CONNECT ");
|
||||||
|
tcp_socket_send_str(tcps, s->host);
|
||||||
|
tcp_socket_send_str(tcps, ":");
|
||||||
|
sprintf(buf, "%d", s->port);
|
||||||
|
tcp_socket_send_str(tcps, buf);
|
||||||
|
tcp_socket_send_str(tcps, " HTTP/1.1\r\n");
|
||||||
|
tcp_socket_send_str(tcps, "Host: ");
|
||||||
|
tcp_socket_send_str(tcps, s->host);
|
||||||
|
tcp_socket_send_str(tcps, "\r\n");
|
||||||
|
tcp_socket_send_str(tcps, "Proxy-Connection: Keep-Alive\r\n\r\n");
|
||||||
|
}
|
||||||
|
/*---------------------------------------------------------------------------*/
|
||||||
|
static void
|
||||||
|
event(struct tcp_socket *tcps, void *ptr,
|
||||||
|
tcp_socket_event_t e)
|
||||||
|
{
|
||||||
|
struct websocket_http_client_state *s = ptr;
|
||||||
|
|
||||||
|
if(e == TCP_SOCKET_CONNECTED) {
|
||||||
|
if(s->proxy_port != 0) {
|
||||||
|
send_connect(s);
|
||||||
|
} else {
|
||||||
|
send_get(s);
|
||||||
|
}
|
||||||
|
} else if(e == TCP_SOCKET_CLOSED) {
|
||||||
|
websocket_http_client_closed(s);
|
||||||
|
} else if(e == TCP_SOCKET_TIMEDOUT) {
|
||||||
|
websocket_http_client_timedout(s);
|
||||||
|
} else if(e == TCP_SOCKET_ABORTED) {
|
||||||
|
websocket_http_client_aborted(s);
|
||||||
|
} else if(e == TCP_SOCKET_DATA_SENT) {
|
||||||
|
/* We could feed this information up to the websocket.c layer, but
|
||||||
|
we currently do not do that. */
|
||||||
|
}
|
||||||
|
}
|
||||||
|
/*---------------------------------------------------------------------------*/
|
||||||
|
static int
|
||||||
|
parse_header_byte(struct websocket_http_client_state *s,
|
||||||
|
uint8_t b)
|
||||||
|
{
|
||||||
|
static const char *endmarker = "\r\n\r\n";
|
||||||
|
|
||||||
|
PT_BEGIN(&s->parse_header_pt);
|
||||||
|
|
||||||
|
/* Skip the first part of the HTTP response */
|
||||||
|
while(b != ' ') {
|
||||||
|
PT_YIELD(&s->parse_header_pt);
|
||||||
|
}
|
||||||
|
|
||||||
|
/* Skip the space that follow the first part */
|
||||||
|
PT_YIELD(&s->parse_header_pt);
|
||||||
|
|
||||||
|
/* Read the first three bytes that constistute the HTTP status
|
||||||
|
code. We store the HTTP status code as an integer in the
|
||||||
|
s->http_status field. */
|
||||||
|
s->http_status = (b - '0');
|
||||||
|
PT_YIELD(&s->parse_header_pt);
|
||||||
|
s->http_status = s->http_status * 10 + (b - '0');
|
||||||
|
PT_YIELD(&s->parse_header_pt);
|
||||||
|
s->http_status = s->http_status * 10 + (b - '0');
|
||||||
|
|
||||||
|
if((s->proxy_port != 0 && !(s->http_status == 200 || s->http_status == 101)) ||
|
||||||
|
(s->proxy_port == 0 && s->http_status != 101)) {
|
||||||
|
/* This is a websocket request, so the server should have answered
|
||||||
|
with a 101 Switching protocols response. */
|
||||||
|
PRINTF("Websocket HTTP client didn't get the 101 status code (got %d), closing connection\n",
|
||||||
|
s->http_status);
|
||||||
|
websocket_http_client_close(s);
|
||||||
|
while(1) {
|
||||||
|
PT_YIELD(&s->parse_header_pt);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/* Keep eating header bytes until we reach the end of it. The end is
|
||||||
|
indicated by the string "\r\n\r\n". We don't actually look at any
|
||||||
|
of the headers.
|
||||||
|
|
||||||
|
The s->i variable contains the number of consecutive bytes
|
||||||
|
matched. If we match the total length of the string, we stop.
|
||||||
|
*/
|
||||||
|
|
||||||
|
s->i = 0;
|
||||||
|
do {
|
||||||
|
PT_YIELD(&s->parse_header_pt);
|
||||||
|
if(b == (uint8_t)endmarker[s->i]) {
|
||||||
|
s->i++;
|
||||||
|
} else {
|
||||||
|
s->i = 0;
|
||||||
|
}
|
||||||
|
} while(s->i < strlen(endmarker));
|
||||||
|
|
||||||
|
if(s->proxy_port != 0 && s->state == STATE_WAITING_FOR_HEADER) {
|
||||||
|
send_get(s);
|
||||||
|
s->state = STATE_WAITING_FOR_CONNECTED;
|
||||||
|
} else {
|
||||||
|
s->state = STATE_STEADY_STATE;
|
||||||
|
websocket_http_client_connected(s);
|
||||||
|
}
|
||||||
|
PT_END(&s->parse_header_pt);
|
||||||
|
}
|
||||||
|
/*---------------------------------------------------------------------------*/
|
||||||
|
static int
|
||||||
|
input(struct tcp_socket *tcps, void *ptr,
|
||||||
|
const uint8_t *inputptr, int inputdatalen)
|
||||||
|
{
|
||||||
|
struct websocket_http_client_state *s = ptr;
|
||||||
|
|
||||||
|
if(s->state == STATE_WAITING_FOR_HEADER ||
|
||||||
|
s->state == STATE_WAITING_FOR_CONNECTED) {
|
||||||
|
int i;
|
||||||
|
for(i = 0; i < inputdatalen; i++) {
|
||||||
|
parse_header_byte(s, inputptr[i]);
|
||||||
|
if(s->state == STATE_STEADY_STATE) {
|
||||||
|
i++;
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
if(i < inputdatalen && s->state == STATE_STEADY_STATE) {
|
||||||
|
websocket_http_client_datahandler(s, &inputptr[i], inputdatalen - i);
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
websocket_http_client_datahandler(s, inputptr, inputdatalen);
|
||||||
|
}
|
||||||
|
|
||||||
|
return 0; /* all data consumed */
|
||||||
|
}
|
||||||
|
/*---------------------------------------------------------------------------*/
|
||||||
|
int
|
||||||
|
websocket_http_client_register(struct websocket_http_client_state *s,
|
||||||
|
const char *host,
|
||||||
|
uint16_t port,
|
||||||
|
const char *file,
|
||||||
|
const char *subprotocol,
|
||||||
|
const char *header)
|
||||||
|
{
|
||||||
|
if(host == NULL) {
|
||||||
|
return -1;
|
||||||
|
}
|
||||||
|
strncpy(s->host, host, sizeof(s->host));
|
||||||
|
|
||||||
|
if(file == NULL) {
|
||||||
|
return -1;
|
||||||
|
}
|
||||||
|
strncpy(s->file, file, sizeof(s->file));
|
||||||
|
|
||||||
|
if(subprotocol == NULL) {
|
||||||
|
return -1;
|
||||||
|
}
|
||||||
|
strncpy(s->subprotocol, subprotocol, sizeof(s->subprotocol));
|
||||||
|
|
||||||
|
if(header == NULL) {
|
||||||
|
strncpy(s->header, "", sizeof(s->header));
|
||||||
|
} else {
|
||||||
|
strncpy(s->header, header, sizeof(s->header));
|
||||||
|
}
|
||||||
|
|
||||||
|
if(port == 0) {
|
||||||
|
s->port = 80;
|
||||||
|
} else {
|
||||||
|
s->port = port;
|
||||||
|
}
|
||||||
|
return 1;
|
||||||
|
}
|
||||||
|
/*---------------------------------------------------------------------------*/
|
||||||
|
int
|
||||||
|
websocket_http_client_get(struct websocket_http_client_state *s)
|
||||||
|
{
|
||||||
|
uip_ip4addr_t ip4addr;
|
||||||
|
uip_ip6addr_t ip6addr;
|
||||||
|
uip_ip6addr_t *addr;
|
||||||
|
uint16_t port;
|
||||||
|
|
||||||
|
PRINTF("websocket_http_client_get: connecting to %s with file %s subprotocol %s header %s\n",
|
||||||
|
s->host, s->file, s->subprotocol, s->header);
|
||||||
|
|
||||||
|
|
||||||
|
s->state = STATE_WAITING_FOR_HEADER;
|
||||||
|
|
||||||
|
if(tcp_socket_register(&s->s, s,
|
||||||
|
s->inputbuf, sizeof(s->inputbuf),
|
||||||
|
s->outputbuf, sizeof(s->outputbuf),
|
||||||
|
input, event) < 0) {
|
||||||
|
return -1;
|
||||||
|
}
|
||||||
|
|
||||||
|
port = s->port;
|
||||||
|
if(s->proxy_port != 0) {
|
||||||
|
/* The proxy address should be an IPv6 address. */
|
||||||
|
uip_ipaddr_copy(&ip6addr, &s->proxy_addr);
|
||||||
|
port = s->proxy_port;
|
||||||
|
} else if(uiplib_ip6addrconv(s->host, &ip6addr) == 0) {
|
||||||
|
/* First check if the host is an IP address. */
|
||||||
|
if(uiplib_ip4addrconv(s->host, &ip4addr) != 0) {
|
||||||
|
ip64_addr_4to6(&ip4addr, &ip6addr);
|
||||||
|
} else {
|
||||||
|
/* Try to lookup the hostname. If it fails, we initiate a hostname
|
||||||
|
lookup. */
|
||||||
|
if(resolv_lookup(s->host, &addr) != RESOLV_STATUS_CACHED) {
|
||||||
|
return -1;
|
||||||
|
}
|
||||||
|
return tcp_socket_connect(&s->s, addr, s->port);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return tcp_socket_connect(&s->s, &ip6addr, port);
|
||||||
|
}
|
||||||
|
/*---------------------------------------------------------------------------*/
|
||||||
|
int
|
||||||
|
websocket_http_client_send(struct websocket_http_client_state *s,
|
||||||
|
const uint8_t *data,
|
||||||
|
uint16_t datalen)
|
||||||
|
{
|
||||||
|
if(s->state == STATE_STEADY_STATE) {
|
||||||
|
return tcp_socket_send(&s->s, data, datalen);
|
||||||
|
}
|
||||||
|
return -1;
|
||||||
|
}
|
||||||
|
/*---------------------------------------------------------------------------*/
|
||||||
|
int
|
||||||
|
websocket_http_client_sendbuflen(struct websocket_http_client_state *s)
|
||||||
|
{
|
||||||
|
return tcp_socket_max_sendlen(&s->s);
|
||||||
|
}
|
||||||
|
/*---------------------------------------------------------------------------*/
|
||||||
|
void
|
||||||
|
websocket_http_client_close(struct websocket_http_client_state *s)
|
||||||
|
{
|
||||||
|
tcp_socket_close(&s->s);
|
||||||
|
}
|
||||||
|
/*---------------------------------------------------------------------------*/
|
||||||
|
const char *
|
||||||
|
websocket_http_client_hostname(struct websocket_http_client_state *s)
|
||||||
|
{
|
||||||
|
return s->host;
|
||||||
|
}
|
||||||
|
/*---------------------------------------------------------------------------*/
|
||||||
|
void
|
||||||
|
websocket_http_client_init(struct websocket_http_client_state *s)
|
||||||
|
{
|
||||||
|
uip_create_unspecified(&s->proxy_addr);
|
||||||
|
s->proxy_port = 0;
|
||||||
|
}
|
||||||
|
/*---------------------------------------------------------------------------*/
|
||||||
|
void
|
||||||
|
websocket_http_client_set_proxy(struct websocket_http_client_state *s,
|
||||||
|
const uip_ipaddr_t *addr, uint16_t port)
|
||||||
|
{
|
||||||
|
uip_ipaddr_copy(&s->proxy_addr, addr);
|
||||||
|
s->proxy_port = port;
|
||||||
|
}
|
||||||
|
/*---------------------------------------------------------------------------*/
|
||||||
|
int
|
||||||
|
websocket_http_client_queuelen(struct websocket_http_client_state *s)
|
||||||
|
{
|
||||||
|
return tcp_socket_queuelen(&s->s);
|
||||||
|
}
|
||||||
|
/*---------------------------------------------------------------------------*/
|
123
core/net/ipv6/websocket-http-client.h
Normal file
123
core/net/ipv6/websocket-http-client.h
Normal file
|
@ -0,0 +1,123 @@
|
||||||
|
/*
|
||||||
|
* Copyright (c) 2014, Thingsquare, http://www.thingsquare.com/.
|
||||||
|
* All rights reserved.
|
||||||
|
*
|
||||||
|
* Redistribution and use in source and binary forms, with or without
|
||||||
|
* modification, are permitted provided that the following conditions
|
||||||
|
* are met:
|
||||||
|
* 1. Redistributions of source code must retain the above copyright
|
||||||
|
* notice, this list of conditions and the following disclaimer.
|
||||||
|
* 2. Redistributions in binary form must reproduce the above copyright
|
||||||
|
* notice, this list of conditions and the following disclaimer in the
|
||||||
|
* documentation and/or other materials provided with the distribution.
|
||||||
|
* 3. Neither the name of the copyright holder nor the names of its
|
||||||
|
* contributors may be used to endorse or promote products derived
|
||||||
|
* from this software without specific prior written permission.
|
||||||
|
*
|
||||||
|
* THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
|
||||||
|
* ``AS IS'' AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
|
||||||
|
* LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS
|
||||||
|
* FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE
|
||||||
|
* COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT,
|
||||||
|
* INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES
|
||||||
|
* (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR
|
||||||
|
* SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION)
|
||||||
|
* HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT,
|
||||||
|
* STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE)
|
||||||
|
* ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED
|
||||||
|
* OF THE POSSIBILITY OF SUCH DAMAGE.
|
||||||
|
*
|
||||||
|
*/
|
||||||
|
#ifndef WEBSOCKET_HTTP_CLIENT_H_
|
||||||
|
#define WEBSOCKET_HTTP_CLIENT_H_
|
||||||
|
|
||||||
|
#include "contiki.h"
|
||||||
|
#include "tcp-socket.h"
|
||||||
|
|
||||||
|
#ifdef WEBSOCKET_HTTP_CLIENT_CONF_INPUTBUFSIZE
|
||||||
|
#define WEBSOCKET_HTTP_CLIENT_INPUTBUFSIZE WEBSOCKET_HTTP_CLIENT_CONF_INPUTBUFSIZE
|
||||||
|
#else /* WEBSOCKET_HTTP_CLIENT_CONF_INPUTBUFSIZE */
|
||||||
|
#define WEBSOCKET_HTTP_CLIENT_INPUTBUFSIZE 100
|
||||||
|
#endif /* WEBSOCKET_HTTP_CLIENT_CONF_INPUTBUFSIZE */
|
||||||
|
|
||||||
|
#ifdef WEBSOCKET_HTTP_CLIENT_CONF_OUTPUTBUFSIZE
|
||||||
|
#define WEBSOCKET_HTTP_CLIENT_OUTPUTBUFSIZE WEBSOCKET_HTTP_CLIENT_CONF_OUTPUTBUFSIZE
|
||||||
|
#else /* WEBSOCKET_HTTP_CLIENT_CONF_OUTPUTBUFSIZE */
|
||||||
|
#define WEBSOCKET_HTTP_CLIENT_OUTPUTBUFSIZE 300
|
||||||
|
#endif /* WEBSOCKET_HTTP_CLIENT_CONF_OUTPUTBUFSIZE */
|
||||||
|
|
||||||
|
#ifdef WEBSOCKET_HTTP_CLIENT_CONF_MAX_HOSTLEN
|
||||||
|
#define WEBSOCKET_HTTP_CLIENT_MAX_HOSTLEN WEBSOCKET_HTTP_CLIENT_CONF_MAX_HOSTLEN
|
||||||
|
#else /* WEBSOCKET_HTTP_CLIENT_CONF_MAX_HOSTLEN */
|
||||||
|
#define WEBSOCKET_HTTP_CLIENT_MAX_HOSTLEN 32
|
||||||
|
#endif /* WEBSOCKET_HTTP_CLIENT_CONF_MAX_HOSTLEN */
|
||||||
|
|
||||||
|
#ifdef WEBSOCKET_HTTP_CLIENT_CONF_MAX_FILELEN
|
||||||
|
#define WEBSOCKET_HTTP_CLIENT_MAX_FILELEN WEBSOCKET_HTTP_CLIENT_CONF_MAX_FILELEN
|
||||||
|
#else /* WEBSOCKET_HTTP_CLIENT_CONF_MAX_FILELEN */
|
||||||
|
#define WEBSOCKET_HTTP_CLIENT_MAX_FILELEN 32
|
||||||
|
#endif /* WEBSOCKET_HTTP_CLIENT_CONF_MAX_FILELEN */
|
||||||
|
|
||||||
|
#ifdef WEBSOCKET_HTTP_CLIENT_CONF_MAX_SUBPROTOCOLLEN
|
||||||
|
#define WEBSOCKET_HTTP_CLIENT_MAX_SUBPROTOCOLLEN WEBSOCKET_HTTP_CLIENT_CONF_MAX_SUBPROTOCOLLEN
|
||||||
|
#else /* WEBSOCKET_HTTP_CLIENT_CONF_MAX_SUBPROTOCOLLEN */
|
||||||
|
#define WEBSOCKET_HTTP_CLIENT_MAX_SUBPROTOCOLLEN 24
|
||||||
|
#endif /* WEBSOCKET_HTTP_CLIENT_CONF_MAX_SUBPROTOCOLLEN */
|
||||||
|
|
||||||
|
#ifdef WEBSOCKET_HTTP_CLIENT_CONF_MAX_HEADERLEN
|
||||||
|
#define WEBSOCKET_HTTP_CLIENT_MAX_HEADERLEN WEBSOCKET_HTTP_CLIENT_CONF_MAX_HEADERLEN
|
||||||
|
#else /* WEBSOCKET_HTTP_CLIENT_CONF_MAX_HEADERLEN */
|
||||||
|
#define WEBSOCKET_HTTP_CLIENT_MAX_HEADERLEN 128
|
||||||
|
#endif /* WEBSOCKET_HTTP_CLIENT_CONF_MAX_HEADERLEN */
|
||||||
|
|
||||||
|
struct websocket_http_client_state {
|
||||||
|
struct tcp_socket s;
|
||||||
|
uint8_t inputbuf[WEBSOCKET_HTTP_CLIENT_INPUTBUFSIZE];
|
||||||
|
uint8_t outputbuf[WEBSOCKET_HTTP_CLIENT_OUTPUTBUFSIZE];
|
||||||
|
char host[WEBSOCKET_HTTP_CLIENT_MAX_HOSTLEN];
|
||||||
|
char file[WEBSOCKET_HTTP_CLIENT_MAX_FILELEN];
|
||||||
|
char subprotocol[WEBSOCKET_HTTP_CLIENT_MAX_SUBPROTOCOLLEN];
|
||||||
|
char header[WEBSOCKET_HTTP_CLIENT_MAX_HEADERLEN];
|
||||||
|
uint16_t port;
|
||||||
|
|
||||||
|
int state;
|
||||||
|
struct pt parse_header_pt;
|
||||||
|
int http_status;
|
||||||
|
int i;
|
||||||
|
|
||||||
|
uip_ipaddr_t proxy_addr;
|
||||||
|
uint16_t proxy_port;
|
||||||
|
};
|
||||||
|
|
||||||
|
void websocket_http_client_init(struct websocket_http_client_state *s);
|
||||||
|
void websocket_http_client_set_proxy(struct websocket_http_client_state *s,
|
||||||
|
const uip_ipaddr_t *addr, uint16_t port);
|
||||||
|
|
||||||
|
int websocket_http_client_register(struct websocket_http_client_state *s,
|
||||||
|
const char *host,
|
||||||
|
uint16_t port,
|
||||||
|
const char *file,
|
||||||
|
const char *subprotocol,
|
||||||
|
const char *hdr);
|
||||||
|
int websocket_http_client_get(struct websocket_http_client_state *s);
|
||||||
|
int websocket_http_client_send(struct websocket_http_client_state *s,
|
||||||
|
const uint8_t *data,
|
||||||
|
uint16_t datalen);
|
||||||
|
int websocket_http_client_sendbuflen(struct websocket_http_client_state *s);
|
||||||
|
|
||||||
|
void websocket_http_client_close(struct websocket_http_client_state *s);
|
||||||
|
|
||||||
|
const char *websocket_http_client_hostname(struct websocket_http_client_state *s);
|
||||||
|
|
||||||
|
int websocket_http_client_queuelen(struct websocket_http_client_state *s);
|
||||||
|
|
||||||
|
/* Callback functions that have to be implemented by the application
|
||||||
|
program. */
|
||||||
|
void websocket_http_client_datahandler(struct websocket_http_client_state *s,
|
||||||
|
const uint8_t *data, uint16_t len);
|
||||||
|
void websocket_http_client_connected(struct websocket_http_client_state *s);
|
||||||
|
void websocket_http_client_timedout(struct websocket_http_client_state *s);
|
||||||
|
void websocket_http_client_aborted(struct websocket_http_client_state *s);
|
||||||
|
void websocket_http_client_closed(struct websocket_http_client_state *s);
|
||||||
|
|
||||||
|
#endif /* WEBSOCKET_HTTP_CLIENT_H_ */
|
724
core/net/ipv6/websocket.c
Normal file
724
core/net/ipv6/websocket.c
Normal file
|
@ -0,0 +1,724 @@
|
||||||
|
/*
|
||||||
|
* Copyright (c) 2012, Thingsquare, http://www.thingsquare.com/.
|
||||||
|
* All rights reserved.
|
||||||
|
*
|
||||||
|
* Redistribution and use in source and binary forms, with or without
|
||||||
|
* modification, are permitted provided that the following conditions
|
||||||
|
* are met:
|
||||||
|
* 1. Redistributions of source code must retain the above copyright
|
||||||
|
* notice, this list of conditions and the following disclaimer.
|
||||||
|
* 2. Redistributions in binary form must reproduce the above copyright
|
||||||
|
* notice, this list of conditions and the following disclaimer in the
|
||||||
|
* documentation and/or other materials provided with the distribution.
|
||||||
|
* 3. Neither the name of the copyright holder nor the names of its
|
||||||
|
* contributors may be used to endorse or promote products derived
|
||||||
|
* from this software without specific prior written permission.
|
||||||
|
*
|
||||||
|
* THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
|
||||||
|
* ``AS IS'' AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
|
||||||
|
* LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS
|
||||||
|
* FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE
|
||||||
|
* COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT,
|
||||||
|
* INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES
|
||||||
|
* (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR
|
||||||
|
* SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION)
|
||||||
|
* HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT,
|
||||||
|
* STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE)
|
||||||
|
* ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED
|
||||||
|
* OF THE POSSIBILITY OF SUCH DAMAGE.
|
||||||
|
*
|
||||||
|
*/
|
||||||
|
|
||||||
|
#include <stdio.h>
|
||||||
|
#include <stddef.h>
|
||||||
|
#include <string.h>
|
||||||
|
|
||||||
|
#include "contiki-net.h"
|
||||||
|
#include "lib/petsciiconv.h"
|
||||||
|
|
||||||
|
#include "websocket.h"
|
||||||
|
|
||||||
|
PROCESS(websocket_process, "Websockets process");
|
||||||
|
|
||||||
|
#define MAX_HOSTLEN 64
|
||||||
|
#define MAX_PATHLEN 100
|
||||||
|
|
||||||
|
LIST(websocketlist);
|
||||||
|
|
||||||
|
#define WEBSOCKET_FIN_BIT 0x80
|
||||||
|
|
||||||
|
#define WEBSOCKET_OPCODE_MASK 0x0f
|
||||||
|
#define WEBSOCKET_OPCODE_CONT 0x00
|
||||||
|
#define WEBSOCKET_OPCODE_TEXT 0x01
|
||||||
|
#define WEBSOCKET_OPCODE_BIN 0x02
|
||||||
|
#define WEBSOCKET_OPCODE_CLOSE 0x08
|
||||||
|
#define WEBSOCKET_OPCODE_PING 0x09
|
||||||
|
#define WEBSOCKET_OPCODE_PONG 0x0a
|
||||||
|
|
||||||
|
#define WEBSOCKET_MASK_BIT 0x80
|
||||||
|
#define WEBSOCKET_LEN_MASK 0x7f
|
||||||
|
struct websocket_frame_hdr {
|
||||||
|
uint8_t opcode;
|
||||||
|
uint8_t len;
|
||||||
|
uint8_t extlen[4];
|
||||||
|
};
|
||||||
|
|
||||||
|
struct websocket_frame_mask {
|
||||||
|
uint8_t mask[4];
|
||||||
|
};
|
||||||
|
|
||||||
|
#define DEBUG DEBUG_NONE
|
||||||
|
#include "net/ip/uip-debug.h"
|
||||||
|
|
||||||
|
/*---------------------------------------------------------------------------*/
|
||||||
|
static int
|
||||||
|
parse_url(const char *url, char *host, uint16_t *portptr, char *path)
|
||||||
|
{
|
||||||
|
const char *urlptr;
|
||||||
|
int i;
|
||||||
|
const char *file;
|
||||||
|
uint16_t port;
|
||||||
|
|
||||||
|
if(url == NULL) {
|
||||||
|
return 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
/* Don't even try to go further if the URL is empty. */
|
||||||
|
if(strlen(url) == 0) {
|
||||||
|
return 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
/* See if the URL starts with http:// or ws:// and remove it. */
|
||||||
|
if(strncmp(url, "http://", strlen("http://")) == 0) {
|
||||||
|
urlptr = url + strlen("http://");
|
||||||
|
} else if(strncmp(url, "ws://", strlen("ws://")) == 0) {
|
||||||
|
urlptr = url + strlen("ws://");
|
||||||
|
} else {
|
||||||
|
urlptr = url;
|
||||||
|
}
|
||||||
|
|
||||||
|
/* Find host part of the URL. */
|
||||||
|
for(i = 0; i < MAX_HOSTLEN; ++i) {
|
||||||
|
if(*urlptr == 0 ||
|
||||||
|
*urlptr == '/' ||
|
||||||
|
*urlptr == ' ' ||
|
||||||
|
*urlptr == ':') {
|
||||||
|
if(host != NULL) {
|
||||||
|
host[i] = 0;
|
||||||
|
}
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
if(host != NULL) {
|
||||||
|
host[i] = *urlptr;
|
||||||
|
}
|
||||||
|
++urlptr;
|
||||||
|
}
|
||||||
|
|
||||||
|
/* Find the port. Default is 0, which lets the underlying transport
|
||||||
|
select its default port. */
|
||||||
|
port = 0;
|
||||||
|
if(*urlptr == ':') {
|
||||||
|
port = 0;
|
||||||
|
do {
|
||||||
|
++urlptr;
|
||||||
|
if(*urlptr >= '0' && *urlptr <= '9') {
|
||||||
|
port = (10 * port) + (*urlptr - '0');
|
||||||
|
}
|
||||||
|
} while(*urlptr >= '0' &&
|
||||||
|
*urlptr <= '9');
|
||||||
|
}
|
||||||
|
if(portptr != NULL) {
|
||||||
|
*portptr = port;
|
||||||
|
}
|
||||||
|
/* Find file part of the URL. */
|
||||||
|
while(*urlptr != '/' && *urlptr != 0) {
|
||||||
|
++urlptr;
|
||||||
|
}
|
||||||
|
if(*urlptr == '/') {
|
||||||
|
file = urlptr;
|
||||||
|
} else {
|
||||||
|
file = "/";
|
||||||
|
}
|
||||||
|
if(path != NULL) {
|
||||||
|
strncpy(path, file, MAX_PATHLEN);
|
||||||
|
}
|
||||||
|
return 1;
|
||||||
|
}
|
||||||
|
/*---------------------------------------------------------------------------*/
|
||||||
|
static int
|
||||||
|
start_get(struct websocket *s)
|
||||||
|
{
|
||||||
|
if(websocket_http_client_get(&(s->s)) == 0) {
|
||||||
|
PRINTF("Out of memory error\n");
|
||||||
|
s->state = WEBSOCKET_STATE_CLOSED;
|
||||||
|
return WEBSOCKET_ERR;
|
||||||
|
} else {
|
||||||
|
PRINTF("Connecting...\n");
|
||||||
|
s->state = WEBSOCKET_STATE_HTTP_REQUEST_SENT;
|
||||||
|
return WEBSOCKET_OK;
|
||||||
|
}
|
||||||
|
return WEBSOCKET_ERR;
|
||||||
|
}
|
||||||
|
/*---------------------------------------------------------------------------*/
|
||||||
|
void
|
||||||
|
call(struct websocket *s, websocket_result_t r,
|
||||||
|
const uint8_t *data, uint16_t datalen)
|
||||||
|
{
|
||||||
|
if(s != NULL && s->callback != NULL) {
|
||||||
|
s->callback(s, r, data, datalen);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
/*---------------------------------------------------------------------------*/
|
||||||
|
PROCESS_THREAD(websocket_process, ev, data)
|
||||||
|
{
|
||||||
|
PROCESS_BEGIN();
|
||||||
|
|
||||||
|
while(1) {
|
||||||
|
|
||||||
|
PROCESS_WAIT_EVENT();
|
||||||
|
|
||||||
|
if(ev == resolv_event_found && data != NULL) {
|
||||||
|
int ret;
|
||||||
|
struct websocket *s;
|
||||||
|
const char *name = data;
|
||||||
|
/* Either found a hostname, or not. We need to go through the
|
||||||
|
list of websocketsand figure out to which connection this
|
||||||
|
reply corresponds, then either restart the HTTP get, or kill
|
||||||
|
it (if no hostname was found). */
|
||||||
|
for(s = list_head(websocketlist);
|
||||||
|
s != NULL;
|
||||||
|
s = list_item_next(s)) {
|
||||||
|
if(strcmp(name, websocket_http_client_hostname(&s->s)) == 0) {
|
||||||
|
ret = resolv_lookup(name, NULL);
|
||||||
|
if(ret == RESOLV_STATUS_CACHED) {
|
||||||
|
/* Hostname found, restart get. */
|
||||||
|
if(s->state == WEBSOCKET_STATE_DNS_REQUEST_SENT) {
|
||||||
|
PRINTF("Restarting get\n");
|
||||||
|
start_get(s);
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
if(s->state == WEBSOCKET_STATE_DNS_REQUEST_SENT) {
|
||||||
|
/* Hostname not found, kill connection. */
|
||||||
|
/* PRINTF("XXX killing connection\n");*/
|
||||||
|
call(s, WEBSOCKET_HOSTNAME_NOT_FOUND, NULL, 0);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
PROCESS_END();
|
||||||
|
}
|
||||||
|
/*---------------------------------------------------------------------------*/
|
||||||
|
/* Callback function. Called from the webclient when the HTTP
|
||||||
|
* connection was abruptly aborted.
|
||||||
|
*/
|
||||||
|
void
|
||||||
|
websocket_http_client_aborted(struct websocket_http_client_state *client_state)
|
||||||
|
{
|
||||||
|
if(client_state != NULL) {
|
||||||
|
struct websocket *s = (struct websocket *)
|
||||||
|
((char *)client_state - offsetof(struct websocket, s));
|
||||||
|
PRINTF("Websocket reset\n");
|
||||||
|
s->state = WEBSOCKET_STATE_CLOSED;
|
||||||
|
call(s, WEBSOCKET_RESET, NULL, 0);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
/*---------------------------------------------------------------------------*/
|
||||||
|
/* Callback function. Called from the webclient when the HTTP
|
||||||
|
* connection timed out.
|
||||||
|
*/
|
||||||
|
void
|
||||||
|
websocket_http_client_timedout(struct websocket_http_client_state *client_state)
|
||||||
|
{
|
||||||
|
if(client_state != NULL) {
|
||||||
|
struct websocket *s = (struct websocket *)
|
||||||
|
((char *)client_state - offsetof(struct websocket, s));
|
||||||
|
PRINTF("Websocket timed out\n");
|
||||||
|
s->state = WEBSOCKET_STATE_CLOSED;
|
||||||
|
call(s, WEBSOCKET_TIMEDOUT, NULL, 0);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
/*---------------------------------------------------------------------------*/
|
||||||
|
/* Callback function. Called from the webclient when the HTTP
|
||||||
|
* connection was closed after a request from the "websocket_http_client_close()"
|
||||||
|
* function. .
|
||||||
|
*/
|
||||||
|
void
|
||||||
|
websocket_http_client_closed(struct websocket_http_client_state *client_state)
|
||||||
|
{
|
||||||
|
if(client_state != NULL) {
|
||||||
|
struct websocket *s = (struct websocket *)
|
||||||
|
((char *)client_state - offsetof(struct websocket, s));
|
||||||
|
PRINTF("Websocket closed.\n");
|
||||||
|
s->state = WEBSOCKET_STATE_CLOSED;
|
||||||
|
call(s, WEBSOCKET_CLOSED, NULL, 0);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
/*---------------------------------------------------------------------------*/
|
||||||
|
/* Callback function. Called from the webclient when the HTTP
|
||||||
|
* connection is connected.
|
||||||
|
*/
|
||||||
|
void
|
||||||
|
websocket_http_client_connected(struct websocket_http_client_state *client_state)
|
||||||
|
{
|
||||||
|
struct websocket *s = (struct websocket *)
|
||||||
|
((char *)client_state - offsetof(struct websocket, s));
|
||||||
|
|
||||||
|
PRINTF("Websocket connected\n");
|
||||||
|
s->state = WEBSOCKET_STATE_WAITING_FOR_HEADER;
|
||||||
|
call(s, WEBSOCKET_CONNECTED, NULL, 0);
|
||||||
|
}
|
||||||
|
/*---------------------------------------------------------------------------*/
|
||||||
|
/* The websocket header may potentially be split into multiple TCP
|
||||||
|
segments. This function eats one byte each, puts it into
|
||||||
|
s->headercache, and checks whether or not the full header has been
|
||||||
|
received. */
|
||||||
|
static int
|
||||||
|
receive_header_byte(struct websocket *s, uint8_t byte)
|
||||||
|
{
|
||||||
|
int len;
|
||||||
|
int expected_len;
|
||||||
|
struct websocket_frame_hdr *hdr;
|
||||||
|
|
||||||
|
/* Take the next byte of data and place it in the header cache. */
|
||||||
|
if(s->state == WEBSOCKET_STATE_RECEIVING_HEADER) {
|
||||||
|
s->headercache[s->headercacheptr] = byte;
|
||||||
|
s->headercacheptr++;
|
||||||
|
if(s->headercacheptr >= sizeof(s->headercache)) {
|
||||||
|
/* Something bad happened: we ad read 10 bytes and had not yet
|
||||||
|
found a reasonable header, so we close the socket. */
|
||||||
|
websocket_close(s);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
len = s->headercacheptr;
|
||||||
|
hdr = (struct websocket_frame_hdr *)s->headercache;
|
||||||
|
|
||||||
|
/* Check the header that we have received to see if it is long
|
||||||
|
enough. */
|
||||||
|
|
||||||
|
/* We start with expecting a length of at least two bytes (opcode +
|
||||||
|
1 length byte). */
|
||||||
|
expected_len = 2;
|
||||||
|
|
||||||
|
if(len >= expected_len) {
|
||||||
|
|
||||||
|
/* We check how many more bytes we should expect to see. The
|
||||||
|
length byte determines how many length bytes are included in
|
||||||
|
the header. */
|
||||||
|
if((hdr->len & WEBSOCKET_LEN_MASK) == 126) {
|
||||||
|
expected_len += 2;
|
||||||
|
} else if((hdr->len & WEBSOCKET_LEN_MASK) == 127) {
|
||||||
|
expected_len += 4;
|
||||||
|
}
|
||||||
|
|
||||||
|
/* If the option has the mask bit set, we should expect to see 4
|
||||||
|
mask bytes at the end of the header. */
|
||||||
|
if((hdr->len & WEBSOCKET_MASK_BIT ) != 0) {
|
||||||
|
expected_len += 4;
|
||||||
|
}
|
||||||
|
|
||||||
|
/* Now we know how long our header if expected to be. If it is
|
||||||
|
this long, we are done and we set the state to reflect this. */
|
||||||
|
if(len == expected_len) {
|
||||||
|
s->state = WEBSOCKET_STATE_HEADER_RECEIVED;
|
||||||
|
return 1;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return 0;
|
||||||
|
}
|
||||||
|
/*---------------------------------------------------------------------------*/
|
||||||
|
/* Callback function. Called from the webclient module when HTTP data
|
||||||
|
* has arrived.
|
||||||
|
*/
|
||||||
|
void
|
||||||
|
websocket_http_client_datahandler(struct websocket_http_client_state *client_state,
|
||||||
|
const uint8_t *data, uint16_t datalen)
|
||||||
|
{
|
||||||
|
struct websocket *s = (struct websocket *)
|
||||||
|
((char *)client_state - offsetof(struct websocket, s));
|
||||||
|
struct websocket_frame_hdr *hdr;
|
||||||
|
struct websocket_frame_mask *maskptr;
|
||||||
|
|
||||||
|
if(data == NULL) {
|
||||||
|
call(s, WEBSOCKET_CLOSED, NULL, 0);
|
||||||
|
} else {
|
||||||
|
/* This function is a state machine that does different things
|
||||||
|
depending on the state. If we are waiting for header (the
|
||||||
|
default state), we change to the RECEIVING_HEADER state when we
|
||||||
|
get the first byte. If we are receiving header, we put all
|
||||||
|
bytes we have into a header buffer until the full header has
|
||||||
|
been received. If we have received the header, we parse it. If
|
||||||
|
we have received and parsed the header, we are ready to receive
|
||||||
|
data. Finally, if there is data left in the incoming packet, we
|
||||||
|
repeat the process. */
|
||||||
|
|
||||||
|
if(s->state == WEBSOCKET_STATE_WAITING_FOR_HEADER) {
|
||||||
|
s->state = WEBSOCKET_STATE_RECEIVING_HEADER;
|
||||||
|
s->headercacheptr = 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
if(s->state == WEBSOCKET_STATE_RECEIVING_HEADER) {
|
||||||
|
while(datalen > 0 && s->state == WEBSOCKET_STATE_RECEIVING_HEADER) {
|
||||||
|
receive_header_byte(s, data[0]);
|
||||||
|
data++;
|
||||||
|
datalen--;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
if(s->state == WEBSOCKET_STATE_HEADER_RECEIVED) {
|
||||||
|
/* If this is the start of an incoming websocket data frame, we
|
||||||
|
decode the header and check if we should act on in. If not, we
|
||||||
|
pipe the data to the application through a callback handler. If
|
||||||
|
data arrives in multiple packets, it is up to the application to
|
||||||
|
put it back together again. */
|
||||||
|
|
||||||
|
/* The websocket header is at the start of the incoming data. */
|
||||||
|
hdr = (struct websocket_frame_hdr *)s->headercache;
|
||||||
|
|
||||||
|
/* The s->left field holds the length of the application data
|
||||||
|
* chunk that we are about to receive. */
|
||||||
|
s->len = s->left = 0;
|
||||||
|
|
||||||
|
/* The s->mask field holds the bitmask of the data chunk, if
|
||||||
|
* any. */
|
||||||
|
memset(s->mask, 0, sizeof(s->mask));
|
||||||
|
|
||||||
|
/* We first read out the length of the application data
|
||||||
|
chunk. The length may be encoded over multiple bytes. If the
|
||||||
|
length is >= 126 bytes, it is encoded as two or more
|
||||||
|
bytes. The first length field determines if it is in 2 or 4
|
||||||
|
bytes. We also keep track of where the bitmask is held - its
|
||||||
|
place also differs depending on how the length is encoded. */
|
||||||
|
maskptr = (struct websocket_frame_mask *)hdr->extlen;
|
||||||
|
if((hdr->len & WEBSOCKET_LEN_MASK) < 126) {
|
||||||
|
s->len = s->left = hdr->len & WEBSOCKET_LEN_MASK;
|
||||||
|
} else if(hdr->len == 126) {
|
||||||
|
s->len = s->left = (hdr->extlen[0] << 8) + hdr->extlen[1];
|
||||||
|
maskptr = (struct websocket_frame_mask *)&hdr->extlen[2];
|
||||||
|
} else if(hdr->len == 127) {
|
||||||
|
s->len = s->left = ((uint32_t)hdr->extlen[0] << 24) +
|
||||||
|
((uint32_t)hdr->extlen[1] << 16) +
|
||||||
|
((uint32_t)hdr->extlen[2] << 8) +
|
||||||
|
hdr->extlen[3];
|
||||||
|
maskptr = (struct websocket_frame_mask *)&hdr->extlen[4];
|
||||||
|
}
|
||||||
|
|
||||||
|
/* Set user_data to point to the first byte of application data.
|
||||||
|
See if the application data chunk is masked or not. If it is,
|
||||||
|
we copy the bitmask into the s->mask field. */
|
||||||
|
if((hdr->len & WEBSOCKET_MASK_BIT) == 0) {
|
||||||
|
/* PRINTF("No mask\n");*/
|
||||||
|
} else {
|
||||||
|
memcpy(s->mask, &maskptr->mask, sizeof(s->mask));
|
||||||
|
/* PRINTF("There was a mask, %02x %02x %02x %02x\n",
|
||||||
|
s->mask[0], s->mask[1], s->mask[2], s->mask[3]);*/
|
||||||
|
}
|
||||||
|
|
||||||
|
/* Remember the opcode of the application chunk, put it in the
|
||||||
|
* s->opcode field. */
|
||||||
|
s->opcode = hdr->opcode & WEBSOCKET_OPCODE_MASK;
|
||||||
|
|
||||||
|
if(s->opcode == WEBSOCKET_OPCODE_PING) {
|
||||||
|
/* If the opcode is ping, we change the opcode to a pong, and
|
||||||
|
* send the data back. */
|
||||||
|
hdr->opcode = (hdr->opcode & (~WEBSOCKET_OPCODE_MASK)) |
|
||||||
|
WEBSOCKET_OPCODE_PONG;
|
||||||
|
websocket_http_client_send(&s->s, (const uint8_t*)hdr, 2);
|
||||||
|
if(s->left > 0) {
|
||||||
|
websocket_http_client_send(&s->s, (const uint8_t*)data, s->left);
|
||||||
|
}
|
||||||
|
PRINTF("Got ping\n");
|
||||||
|
call(s, WEBSOCKET_PINGED, NULL, 0);
|
||||||
|
s->state = WEBSOCKET_STATE_WAITING_FOR_HEADER;
|
||||||
|
} else if(s->opcode == WEBSOCKET_OPCODE_PONG) {
|
||||||
|
/* If the opcode is pong, we call the application to let it
|
||||||
|
know we got a pong. */
|
||||||
|
PRINTF("Got pong\n");
|
||||||
|
call(s, WEBSOCKET_PONG_RECEIVED, NULL, 0);
|
||||||
|
s->state = WEBSOCKET_STATE_WAITING_FOR_HEADER;
|
||||||
|
} else if(s->opcode == WEBSOCKET_OPCODE_CLOSE) {
|
||||||
|
/* If the opcode is a close, we send a close frame back. */
|
||||||
|
hdr->opcode = (hdr->opcode & (~WEBSOCKET_OPCODE_MASK)) |
|
||||||
|
WEBSOCKET_OPCODE_CLOSE;
|
||||||
|
websocket_http_client_send(&s->s, (const uint8_t*)hdr, 2);
|
||||||
|
if(s->left > 0) {
|
||||||
|
websocket_http_client_send(&s->s, (const uint8_t*)data, s->left);
|
||||||
|
}
|
||||||
|
PRINTF("websocket: got close, sending close\n");
|
||||||
|
s->state = WEBSOCKET_STATE_WAITING_FOR_HEADER;
|
||||||
|
websocket_http_client_close(&s->s);
|
||||||
|
} else if(s->opcode == WEBSOCKET_OPCODE_BIN ||
|
||||||
|
s->opcode == WEBSOCKET_OPCODE_TEXT) {
|
||||||
|
|
||||||
|
/* If the opcode is bin or text, and there is application
|
||||||
|
* layer data in the packet, we call the application to
|
||||||
|
* process it. */
|
||||||
|
if(s->left > 0) {
|
||||||
|
s->state = WEBSOCKET_STATE_RECEIVING_DATA;
|
||||||
|
if(datalen > 0) {
|
||||||
|
int len;
|
||||||
|
|
||||||
|
len = MIN(s->left, datalen);
|
||||||
|
/* XXX todo: mask if needed. */
|
||||||
|
call(s, WEBSOCKET_DATA, data, len);
|
||||||
|
data += len;
|
||||||
|
s->left -= len;
|
||||||
|
datalen -= len;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
if(s->left == 0) {
|
||||||
|
call(s, WEBSOCKET_DATA_RECEIVED, NULL, s->len);
|
||||||
|
s->state = WEBSOCKET_STATE_WAITING_FOR_HEADER;
|
||||||
|
|
||||||
|
/* Need to keep parsing the incoming data to check for more
|
||||||
|
frames, if the incoming datalen is > than s->left. */
|
||||||
|
if(datalen > 0) {
|
||||||
|
PRINTF("XXX 1 again\n");
|
||||||
|
websocket_http_client_datahandler(client_state,
|
||||||
|
data, datalen);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
} else if(s->state == WEBSOCKET_STATE_RECEIVING_DATA) {
|
||||||
|
/* XXX todo: mask if needed. */
|
||||||
|
/* PRINTF("Calling with s->left %d datalen %d\n",
|
||||||
|
s->left, datalen);*/
|
||||||
|
if(datalen > 0) {
|
||||||
|
if(datalen < s->left) {
|
||||||
|
call(s, WEBSOCKET_DATA, data, datalen);
|
||||||
|
s->left -= datalen;
|
||||||
|
data += datalen;
|
||||||
|
datalen = 0;
|
||||||
|
} else {
|
||||||
|
call(s, WEBSOCKET_DATA, data, s->left);
|
||||||
|
data += s->left;
|
||||||
|
datalen -= s->left;
|
||||||
|
s->left = 0;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
if(s->left == 0) {
|
||||||
|
call(s, WEBSOCKET_DATA_RECEIVED, NULL, s->len);
|
||||||
|
s->state = WEBSOCKET_STATE_WAITING_FOR_HEADER;
|
||||||
|
/* Need to keep parsing the incoming data to check for more
|
||||||
|
frames, if the incoming datalen is > than len. */
|
||||||
|
if(datalen > 0) {
|
||||||
|
PRINTF("XXX 2 again (datalen %d s->left %d)\n", datalen, (int)s->left);
|
||||||
|
websocket_http_client_datahandler(client_state,
|
||||||
|
data, datalen);
|
||||||
|
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
/*---------------------------------------------------------------------------*/
|
||||||
|
static void
|
||||||
|
init(void)
|
||||||
|
{
|
||||||
|
static uint8_t inited = 0;
|
||||||
|
if(!inited) {
|
||||||
|
process_start(&websocket_process, NULL);
|
||||||
|
list_init(websocketlist);
|
||||||
|
inited = 1;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
/*---------------------------------------------------------------------------*/
|
||||||
|
void
|
||||||
|
websocket_init(struct websocket *s)
|
||||||
|
{
|
||||||
|
init();
|
||||||
|
websocket_http_client_init(&s->s);
|
||||||
|
}
|
||||||
|
/*---------------------------------------------------------------------------*/
|
||||||
|
void
|
||||||
|
websocket_set_proxy(struct websocket *s,
|
||||||
|
const uip_ipaddr_t *addr, uint16_t port)
|
||||||
|
{
|
||||||
|
websocket_http_client_set_proxy(&s->s, addr, port);
|
||||||
|
}
|
||||||
|
/*---------------------------------------------------------------------------*/
|
||||||
|
websocket_result_t
|
||||||
|
websocket_open(struct websocket *s, const char *url,
|
||||||
|
const char *subprotocol, const char *hdr,
|
||||||
|
websocket_callback c)
|
||||||
|
{
|
||||||
|
int ret;
|
||||||
|
char host[MAX_HOSTLEN];
|
||||||
|
char path[MAX_PATHLEN];
|
||||||
|
uint16_t port;
|
||||||
|
uip_ipaddr_t addr;
|
||||||
|
|
||||||
|
init();
|
||||||
|
|
||||||
|
if(s == NULL) {
|
||||||
|
return WEBSOCKET_ERR;
|
||||||
|
}
|
||||||
|
|
||||||
|
if(s->state != WEBSOCKET_STATE_CLOSED) {
|
||||||
|
PRINTF("websocket_open: closing websocket before opening it again.\n");
|
||||||
|
websocket_close(s);
|
||||||
|
}
|
||||||
|
s->callback = c;
|
||||||
|
|
||||||
|
if(parse_url(url, host, &port, path)) {
|
||||||
|
list_add(websocketlist, s);
|
||||||
|
websocket_http_client_register(&s->s, host, port, path, subprotocol, hdr);
|
||||||
|
|
||||||
|
/* First check if the host is an IP address. */
|
||||||
|
if(uiplib_ip4addrconv(host, (uip_ip4addr_t *)&addr) == 0 &&
|
||||||
|
uiplib_ip6addrconv(host, (uip_ip6addr_t *)&addr) == 0) {
|
||||||
|
/* Try to lookup the hostname. If it fails, we initiate a hostname
|
||||||
|
lookup and print out an informative message on the
|
||||||
|
statusbar. */
|
||||||
|
ret = resolv_lookup(host, NULL);
|
||||||
|
if(ret != RESOLV_STATUS_CACHED) {
|
||||||
|
resolv_query(host);
|
||||||
|
s->state = WEBSOCKET_STATE_DNS_REQUEST_SENT;
|
||||||
|
PRINTF("Resolving host...\n");
|
||||||
|
return WEBSOCKET_OK;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
PROCESS_CONTEXT_BEGIN(&websocket_process);
|
||||||
|
ret = start_get(s);
|
||||||
|
PROCESS_CONTEXT_END();
|
||||||
|
return ret;
|
||||||
|
}
|
||||||
|
return -1;
|
||||||
|
}
|
||||||
|
/*---------------------------------------------------------------------------*/
|
||||||
|
void
|
||||||
|
websocket_close(struct websocket *s)
|
||||||
|
{
|
||||||
|
websocket_http_client_close(&s->s);
|
||||||
|
s->state = WEBSOCKET_STATE_CLOSED;
|
||||||
|
}
|
||||||
|
/*---------------------------------------------------------------------------*/
|
||||||
|
static int
|
||||||
|
send_data(struct websocket *s, const void *data,
|
||||||
|
uint16_t datalen, uint8_t data_type_opcode)
|
||||||
|
{
|
||||||
|
uint8_t buf[WEBSOCKET_MAX_MSGLEN + 4 + 4]; /* The extra + 4 + 4 here
|
||||||
|
comes from the size of
|
||||||
|
the websocket framing
|
||||||
|
header. */
|
||||||
|
struct websocket_frame_hdr *hdr;
|
||||||
|
struct websocket_frame_mask *mask;
|
||||||
|
|
||||||
|
PRINTF("websocket send data len %d %.*s\n", datalen, datalen, (char *)data);
|
||||||
|
if(s->state == WEBSOCKET_STATE_CLOSED ||
|
||||||
|
s->state == WEBSOCKET_STATE_DNS_REQUEST_SENT ||
|
||||||
|
s->state == WEBSOCKET_STATE_HTTP_REQUEST_SENT) {
|
||||||
|
/* Trying to send data on a non-connected websocket. */
|
||||||
|
PRINTF("websocket send fail: not connected\n");
|
||||||
|
return -1;
|
||||||
|
}
|
||||||
|
|
||||||
|
/* We need to have 4 + 4 additional bytes for the websocket framing
|
||||||
|
header. */
|
||||||
|
if(4 + 4 + datalen > websocket_http_client_sendbuflen(&s->s)) {
|
||||||
|
PRINTF("websocket: too few bytes left (%d left, %d needed)\n",
|
||||||
|
websocket_http_client_sendbuflen(&s->s),
|
||||||
|
4 + 4 + datalen);
|
||||||
|
return -1;
|
||||||
|
}
|
||||||
|
|
||||||
|
if(datalen > sizeof(buf) - 4 - 4) {
|
||||||
|
PRINTF("websocket: trying to send too large data chunk %d > %d\n",
|
||||||
|
datalen, sizeof(buf) - 4 - 4);
|
||||||
|
return -1;
|
||||||
|
}
|
||||||
|
|
||||||
|
hdr = (struct websocket_frame_hdr *)&buf[0];
|
||||||
|
hdr->opcode = WEBSOCKET_FIN_BIT | data_type_opcode;
|
||||||
|
|
||||||
|
/* If the datalen is larger than 125 bytes, we need to send the data
|
||||||
|
length as two bytes. If the data length would be larger than 64k,
|
||||||
|
we should send the length as 4 bytes, but since we specify the
|
||||||
|
datalen as an unsigned 16-bit int, we do not handle the 64k case
|
||||||
|
here. */
|
||||||
|
if(datalen > 125) {
|
||||||
|
/* Data from client must always have the mask bit set, and a data
|
||||||
|
mask sent right after the header. */
|
||||||
|
hdr->len = 126 | WEBSOCKET_MASK_BIT;
|
||||||
|
hdr->extlen[0] = datalen >> 8;
|
||||||
|
hdr->extlen[1] = datalen & 0xff;
|
||||||
|
|
||||||
|
mask = (struct websocket_frame_mask *)&buf[4];
|
||||||
|
mask->mask[0] =
|
||||||
|
mask->mask[1] =
|
||||||
|
mask->mask[2] =
|
||||||
|
mask->mask[3] = 0;
|
||||||
|
memcpy(&buf[8], data, datalen);
|
||||||
|
return websocket_http_client_send(&s->s, buf, 8 + datalen);
|
||||||
|
} else {
|
||||||
|
/* Data from client must always have the mask bit set, and a data
|
||||||
|
mask sent right after the header. */
|
||||||
|
hdr->len = datalen | WEBSOCKET_MASK_BIT;
|
||||||
|
|
||||||
|
mask = (struct websocket_frame_mask *)&buf[2];
|
||||||
|
mask->mask[0] =
|
||||||
|
mask->mask[1] =
|
||||||
|
mask->mask[2] =
|
||||||
|
mask->mask[3] = 0;
|
||||||
|
memcpy(&buf[6], data, datalen);
|
||||||
|
return websocket_http_client_send(&s->s, buf, 6 + datalen);
|
||||||
|
}
|
||||||
|
return -1;
|
||||||
|
}
|
||||||
|
/*---------------------------------------------------------------------------*/
|
||||||
|
int
|
||||||
|
websocket_send_str(struct websocket *s, const char *str)
|
||||||
|
{
|
||||||
|
return send_data(s, str, strlen(str), WEBSOCKET_OPCODE_TEXT);
|
||||||
|
}
|
||||||
|
/*---------------------------------------------------------------------------*/
|
||||||
|
int
|
||||||
|
websocket_send(struct websocket *s, const uint8_t *data,
|
||||||
|
uint16_t datalen)
|
||||||
|
{
|
||||||
|
return send_data(s, data, datalen, WEBSOCKET_OPCODE_BIN);
|
||||||
|
}
|
||||||
|
/*---------------------------------------------------------------------------*/
|
||||||
|
int
|
||||||
|
websocket_ping(struct websocket *s)
|
||||||
|
{
|
||||||
|
uint8_t buf[sizeof(struct websocket_frame_hdr) +
|
||||||
|
sizeof(struct websocket_frame_mask)];
|
||||||
|
struct websocket_frame_hdr *hdr;
|
||||||
|
struct websocket_frame_mask *mask;
|
||||||
|
|
||||||
|
/* We need 2 + 4 additional bytes for the websocket framing
|
||||||
|
header. */
|
||||||
|
if(2 + 4 > websocket_http_client_sendbuflen(&s->s)) {
|
||||||
|
return -1;
|
||||||
|
}
|
||||||
|
|
||||||
|
hdr = (struct websocket_frame_hdr *)&buf[0];
|
||||||
|
mask = (struct websocket_frame_mask *)&buf[2];
|
||||||
|
hdr->opcode = WEBSOCKET_FIN_BIT | WEBSOCKET_OPCODE_PING;
|
||||||
|
|
||||||
|
/* Data from client must always have the mask bit set, and a data
|
||||||
|
mask sent right after the header. */
|
||||||
|
hdr->len = 0 | WEBSOCKET_MASK_BIT;
|
||||||
|
|
||||||
|
/* XXX: We just set a dummy mask of 0 for now and hope that this
|
||||||
|
works. */
|
||||||
|
mask->mask[0] =
|
||||||
|
mask->mask[1] =
|
||||||
|
mask->mask[2] =
|
||||||
|
mask->mask[3] = 0;
|
||||||
|
websocket_http_client_send(&s->s, buf, 2 + 4);
|
||||||
|
return 1;
|
||||||
|
}
|
||||||
|
/*---------------------------------------------------------------------------*/
|
||||||
|
int
|
||||||
|
websocket_queuelen(struct websocket *s)
|
||||||
|
{
|
||||||
|
return websocket_http_client_queuelen(&s->s);
|
||||||
|
}
|
||||||
|
/*---------------------------------------------------------------------------*/
|
113
core/net/ipv6/websocket.h
Normal file
113
core/net/ipv6/websocket.h
Normal file
|
@ -0,0 +1,113 @@
|
||||||
|
/*
|
||||||
|
* Copyright (c) 2012, Thingsquare, http://www.thingsquare.com/.
|
||||||
|
* All rights reserved.
|
||||||
|
*
|
||||||
|
* Redistribution and use in source and binary forms, with or without
|
||||||
|
* modification, are permitted provided that the following conditions
|
||||||
|
* are met:
|
||||||
|
* 1. Redistributions of source code must retain the above copyright
|
||||||
|
* notice, this list of conditions and the following disclaimer.
|
||||||
|
* 2. Redistributions in binary form must reproduce the above copyright
|
||||||
|
* notice, this list of conditions and the following disclaimer in the
|
||||||
|
* documentation and/or other materials provided with the distribution.
|
||||||
|
* 3. Neither the name of the copyright holder nor the names of its
|
||||||
|
* contributors may be used to endorse or promote products derived
|
||||||
|
* from this software without specific prior written permission.
|
||||||
|
*
|
||||||
|
* THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
|
||||||
|
* ``AS IS'' AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
|
||||||
|
* LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS
|
||||||
|
* FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE
|
||||||
|
* COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT,
|
||||||
|
* INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES
|
||||||
|
* (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR
|
||||||
|
* SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION)
|
||||||
|
* HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT,
|
||||||
|
* STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE)
|
||||||
|
* ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED
|
||||||
|
* OF THE POSSIBILITY OF SUCH DAMAGE.
|
||||||
|
*
|
||||||
|
*/
|
||||||
|
#ifndef WEBSOCKET_H
|
||||||
|
#define WEBSOCKET_H
|
||||||
|
|
||||||
|
#include "websocket-http-client.h"
|
||||||
|
|
||||||
|
typedef enum {
|
||||||
|
WEBSOCKET_ERR = 0,
|
||||||
|
WEBSOCKET_OK = 1,
|
||||||
|
WEBSOCKET_IN_PROGRESS = 2,
|
||||||
|
WEBSOCKET_HOSTNAME_NOT_FOUND = 3,
|
||||||
|
WEBSOCKET_CONNECTED = 4,
|
||||||
|
WEBSOCKET_DATA = 5,
|
||||||
|
WEBSOCKET_RESET = 6,
|
||||||
|
WEBSOCKET_TIMEDOUT = 7,
|
||||||
|
WEBSOCKET_CLOSED = 8,
|
||||||
|
WEBSOCKET_PINGED = 9,
|
||||||
|
WEBSOCKET_DATA_RECEIVED = 10,
|
||||||
|
WEBSOCKET_PONG_RECEIVED = 11,
|
||||||
|
} websocket_result_t;
|
||||||
|
|
||||||
|
struct websocket;
|
||||||
|
|
||||||
|
typedef void (* websocket_callback)(struct websocket *s,
|
||||||
|
websocket_result_t result,
|
||||||
|
const uint8_t *data,
|
||||||
|
uint16_t datalen);
|
||||||
|
#ifdef WEBSOCKET_CONF_MAX_MSGLEN
|
||||||
|
#define WEBSOCKET_MAX_MSGLEN WEBSOCKET_CONF_MAX_MSGLEN
|
||||||
|
#else /* WEBSOCKET_CONF_MAX_MSGLEN */
|
||||||
|
#define WEBSOCKET_MAX_MSGLEN 200
|
||||||
|
#endif /* WEBSOCKET_CONF_MAX_MSGLEN */
|
||||||
|
|
||||||
|
struct websocket {
|
||||||
|
struct websocket *next; /* Must be first. */
|
||||||
|
struct websocket_http_client_state s;
|
||||||
|
websocket_callback callback;
|
||||||
|
|
||||||
|
uint8_t mask[4];
|
||||||
|
uint32_t left, len;
|
||||||
|
uint8_t opcode;
|
||||||
|
|
||||||
|
uint8_t state;
|
||||||
|
|
||||||
|
uint8_t headercacheptr;
|
||||||
|
uint8_t headercache[10]; /* The maximum websocket header + mask is 6
|
||||||
|
+ 4 bytes long */
|
||||||
|
};
|
||||||
|
|
||||||
|
enum {
|
||||||
|
WEBSOCKET_STATE_CLOSED = 0,
|
||||||
|
WEBSOCKET_STATE_DNS_REQUEST_SENT = 1,
|
||||||
|
WEBSOCKET_STATE_HTTP_REQUEST_SENT = 2,
|
||||||
|
WEBSOCKET_STATE_WAITING_FOR_HEADER = 3,
|
||||||
|
WEBSOCKET_STATE_RECEIVING_HEADER = 4,
|
||||||
|
WEBSOCKET_STATE_HEADER_RECEIVED = 5,
|
||||||
|
WEBSOCKET_STATE_RECEIVING_DATA = 6,
|
||||||
|
};
|
||||||
|
|
||||||
|
|
||||||
|
void websocket_init(struct websocket *s);
|
||||||
|
|
||||||
|
void websocket_set_proxy(struct websocket *s,
|
||||||
|
const uip_ipaddr_t *addr, uint16_t port);
|
||||||
|
|
||||||
|
websocket_result_t websocket_open(struct websocket *s,
|
||||||
|
const char *url,
|
||||||
|
const char *subprotocol,
|
||||||
|
const char *hdr,
|
||||||
|
websocket_callback c);
|
||||||
|
|
||||||
|
int websocket_send(struct websocket *s,
|
||||||
|
const uint8_t *data, uint16_t datalen);
|
||||||
|
|
||||||
|
int websocket_send_str(struct websocket *s,
|
||||||
|
const char *strptr);
|
||||||
|
|
||||||
|
void websocket_close(struct websocket *s);
|
||||||
|
|
||||||
|
int websocket_ping(struct websocket *s);
|
||||||
|
|
||||||
|
int websocket_queuelen(struct websocket *s);
|
||||||
|
|
||||||
|
#endif /* WEBSOCKET_H */
|
6
examples/websockets/Makefile
Normal file
6
examples/websockets/Makefile
Normal file
|
@ -0,0 +1,6 @@
|
||||||
|
all: websocket-example
|
||||||
|
CONTIKI=../..
|
||||||
|
|
||||||
|
include $(CONTIKI)/Makefile.include
|
||||||
|
|
||||||
|
|
6
examples/websockets/node/Makefile
Normal file
6
examples/websockets/node/Makefile
Normal file
|
@ -0,0 +1,6 @@
|
||||||
|
install:
|
||||||
|
npm install websocket
|
||||||
|
|
||||||
|
|
||||||
|
run:
|
||||||
|
nodejs example-server.js
|
57
examples/websockets/node/example-server.js
Normal file
57
examples/websockets/node/example-server.js
Normal file
|
@ -0,0 +1,57 @@
|
||||||
|
"use strict";
|
||||||
|
|
||||||
|
var serverPort = 8080;
|
||||||
|
|
||||||
|
var websocket = require('websocket').server;
|
||||||
|
var http = require('http');
|
||||||
|
|
||||||
|
var server = http.createServer(function(request, response) {
|
||||||
|
response.writeHead(200, {'Content-Type': 'text/plain'});
|
||||||
|
response.write('This is a websocket server, not intended for http\n');
|
||||||
|
response.end();
|
||||||
|
});
|
||||||
|
|
||||||
|
server.listen(serverPort, function() {
|
||||||
|
console.log('Server is listening on port ' + serverPort);
|
||||||
|
});
|
||||||
|
|
||||||
|
var wsServer = new websocket({
|
||||||
|
httpServer: server
|
||||||
|
});
|
||||||
|
|
||||||
|
|
||||||
|
var connections = [];
|
||||||
|
|
||||||
|
function broadcastMessage(message) {
|
||||||
|
for (var i = 0; i < connections.length; i++) {
|
||||||
|
connections[i].sendUTF(message);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
wsServer.on('request', function(request) {
|
||||||
|
/* Save the connection */
|
||||||
|
var connection = request.accept(null, request.origin);
|
||||||
|
|
||||||
|
/* Store the connection in the list of connections */
|
||||||
|
var connectionIndex = connections.push(connection) - 1;
|
||||||
|
|
||||||
|
console.log('Connection from ' + connection.remoteAddress + '.');
|
||||||
|
|
||||||
|
broadcastMessage('Connection from ' + connection.remoteAddress + '.');
|
||||||
|
|
||||||
|
connection.on('message', function(message) {
|
||||||
|
if (message.type === 'utf8') {
|
||||||
|
console.log((new Date()) + ' Message received: ' +
|
||||||
|
message.utf8Data);
|
||||||
|
broadcastMessage(message.utf8Data);
|
||||||
|
}
|
||||||
|
});
|
||||||
|
|
||||||
|
// user disconnected
|
||||||
|
connection.on('close', function(connection) {
|
||||||
|
console.log((new Date()) + ' Connection lost: ' +
|
||||||
|
connection.remoteAddress);
|
||||||
|
connections.splice(connectionIndex, 1);
|
||||||
|
});
|
||||||
|
|
||||||
|
});
|
93
examples/websockets/websocket-example.c
Normal file
93
examples/websockets/websocket-example.c
Normal file
|
@ -0,0 +1,93 @@
|
||||||
|
/*
|
||||||
|
* Copyright (c) 2012, Thingsquare, http://www.thingsquare.com/.
|
||||||
|
* All rights reserved.
|
||||||
|
*
|
||||||
|
* Redistribution and use in source and binary forms, with or without
|
||||||
|
* modification, are permitted provided that the following conditions
|
||||||
|
* are met:
|
||||||
|
* 1. Redistributions of source code must retain the above copyright
|
||||||
|
* notice, this list of conditions and the following disclaimer.
|
||||||
|
* 2. Redistributions in binary form must reproduce the above copyright
|
||||||
|
* notice, this list of conditions and the following disclaimer in the
|
||||||
|
* documentation and/or other materials provided with the distribution.
|
||||||
|
* 3. Neither the name of the copyright holder nor the names of its
|
||||||
|
* contributors may be used to endorse or promote products derived
|
||||||
|
* from this software without specific prior written permission.
|
||||||
|
*
|
||||||
|
* THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
|
||||||
|
* ``AS IS'' AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
|
||||||
|
* LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS
|
||||||
|
* FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE
|
||||||
|
* COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT,
|
||||||
|
* INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES
|
||||||
|
* (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR
|
||||||
|
* SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION)
|
||||||
|
* HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT,
|
||||||
|
* STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE)
|
||||||
|
* ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED
|
||||||
|
* OF THE POSSIBILITY OF SUCH DAMAGE.
|
||||||
|
*
|
||||||
|
*/
|
||||||
|
#include "contiki.h"
|
||||||
|
|
||||||
|
#include "websocket.h"
|
||||||
|
|
||||||
|
#include <stdio.h>
|
||||||
|
|
||||||
|
static struct websocket s;
|
||||||
|
|
||||||
|
static void callback(struct websocket *s, websocket_result_t r,
|
||||||
|
const uint8_t *data, uint16_t datalen);
|
||||||
|
|
||||||
|
#define RECONNECT_INTERVAL 10 * CLOCK_SECOND
|
||||||
|
static struct ctimer reconnect_timer;
|
||||||
|
|
||||||
|
/*---------------------------------------------------------------------------*/
|
||||||
|
PROCESS(websocket_example_process, "Websocket Example");
|
||||||
|
AUTOSTART_PROCESSES(&websocket_example_process);
|
||||||
|
/*---------------------------------------------------------------------------*/
|
||||||
|
static void
|
||||||
|
reconnect_callback(void *ptr)
|
||||||
|
{
|
||||||
|
websocket_open(&s, "ws://172.16.0.1:8080/",
|
||||||
|
"contiki", NULL, callback);
|
||||||
|
}
|
||||||
|
/*---------------------------------------------------------------------------*/
|
||||||
|
static void
|
||||||
|
callback(struct websocket *s, websocket_result_t r,
|
||||||
|
const uint8_t *data, uint16_t datalen)
|
||||||
|
{
|
||||||
|
if(r == WEBSOCKET_CLOSED ||
|
||||||
|
r == WEBSOCKET_RESET ||
|
||||||
|
r == WEBSOCKET_HOSTNAME_NOT_FOUND ||
|
||||||
|
r == WEBSOCKET_TIMEDOUT) {
|
||||||
|
ctimer_set(&reconnect_timer, RECONNECT_INTERVAL, reconnect_callback, s);
|
||||||
|
} else if(r == WEBSOCKET_CONNECTED) {
|
||||||
|
websocket_send_str(s, "Connected");
|
||||||
|
} else if(r == WEBSOCKET_DATA) {
|
||||||
|
printf("websocket-example: Received data '%.*s' (len %d)\n", datalen,
|
||||||
|
data, datalen);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
/*---------------------------------------------------------------------------*/
|
||||||
|
PROCESS_THREAD(websocket_example_process, ev, data)
|
||||||
|
{
|
||||||
|
static struct etimer et;
|
||||||
|
PROCESS_BEGIN();
|
||||||
|
|
||||||
|
ctimer_set(&reconnect_timer, RECONNECT_INTERVAL, reconnect_callback, &s);
|
||||||
|
|
||||||
|
websocket_init(&s);
|
||||||
|
while(1) {
|
||||||
|
etimer_set(&et, CLOCK_SECOND / 8);
|
||||||
|
PROCESS_WAIT_EVENT_UNTIL(etimer_expired(&et));
|
||||||
|
char buf[] = "012345678";
|
||||||
|
static int count;
|
||||||
|
buf[0] = (count % 9) + '0';
|
||||||
|
count++;
|
||||||
|
websocket_send_str(&s, buf);
|
||||||
|
}
|
||||||
|
|
||||||
|
PROCESS_END();
|
||||||
|
}
|
||||||
|
/*---------------------------------------------------------------------------*/
|
Loading…
Reference in a new issue