Merge pull request #1918 from adamdunkels/pr-websocket-2

Contiki websocket client
This commit is contained in:
Antonio Lignan 2016-12-05 06:46:43 +01:00 committed by GitHub
commit 95f69d92a8
10 changed files with 1494 additions and 4 deletions

View file

@ -29,6 +29,9 @@
*
*/
#define DEBUG DEBUG_NONE
#include "net/ip/uip-debug.h"
#include "contiki.h"
#include "sys/cc.h"
#include "contiki-net.h"
@ -37,10 +40,8 @@
#include "tcp-socket.h"
#include <stdio.h>
#include <string.h>
static void relisten(struct tcp_socket *s);
LIST(socketlist);
@ -80,7 +81,7 @@ acked(struct tcp_socket *s)
s->output_data_maxlen - 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_send_nxt);
tcp_markconn(uip_conn, NULL);
@ -121,7 +122,7 @@ newdata(struct tcp_socket *s)
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;
len -= copylen;
@ -356,6 +357,8 @@ tcp_socket_send(struct tcp_socket *s,
s->output_senddata_len = s->output_data_len;
}
tcpip_poll_tcp(s->c);
return len;
}
/*---------------------------------------------------------------------------*/
@ -398,3 +401,9 @@ tcp_socket_max_sendlen(struct tcp_socket *s)
return s->output_data_maxlen - s->output_data_len;
}
/*---------------------------------------------------------------------------*/
int
tcp_socket_queuelen(struct tcp_socket *s)
{
return s->output_data_len;
}
/*---------------------------------------------------------------------------*/

View file

@ -284,4 +284,16 @@ int tcp_socket_unregister(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 */

View 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);
}
/*---------------------------------------------------------------------------*/

View 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
View 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
View 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 */

View file

@ -0,0 +1,6 @@
all: websocket-example
CONTIKI=../..
include $(CONTIKI)/Makefile.include

View file

@ -0,0 +1,6 @@
install:
npm install websocket
run:
nodejs example-server.js

View 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);
});
});

View 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();
}
/*---------------------------------------------------------------------------*/