From 4f13d637e666058c89aae6afb4ec1578e6c92ce8 Mon Sep 17 00:00:00 2001 From: Adam Dunkels Date: Mon, 31 Oct 2016 22:07:01 +0100 Subject: [PATCH 01/11] Example code for the Contiki websocket client --- examples/websockets/Makefile | 6 +++ examples/websockets/websocket-example.c | 63 +++++++++++++++++++++++++ 2 files changed, 69 insertions(+) create mode 100644 examples/websockets/Makefile create mode 100644 examples/websockets/websocket-example.c diff --git a/examples/websockets/Makefile b/examples/websockets/Makefile new file mode 100644 index 000000000..49d706070 --- /dev/null +++ b/examples/websockets/Makefile @@ -0,0 +1,6 @@ +all: websocket-example +CONTIKI=../.. + +include $(CONTIKI)/Makefile.include + + diff --git a/examples/websockets/websocket-example.c b/examples/websockets/websocket-example.c new file mode 100644 index 000000000..7214deb39 --- /dev/null +++ b/examples/websockets/websocket-example.c @@ -0,0 +1,63 @@ +#include "contiki.h" + +#include "websocket.h" + +#include + +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(); +} +/*---------------------------------------------------------------------------*/ From 5625a01baf7b0aa1ac6ac04d80701af1454b031a Mon Sep 17 00:00:00 2001 From: Adam Dunkels Date: Mon, 31 Oct 2016 22:07:34 +0100 Subject: [PATCH 02/11] Example node.js websocket server, to be used with the Contiki websocket client --- examples/websockets/node/Makefile | 6 +++ examples/websockets/node/example-server.js | 57 ++++++++++++++++++++++ 2 files changed, 63 insertions(+) create mode 100644 examples/websockets/node/Makefile create mode 100644 examples/websockets/node/example-server.js diff --git a/examples/websockets/node/Makefile b/examples/websockets/node/Makefile new file mode 100644 index 000000000..cde795492 --- /dev/null +++ b/examples/websockets/node/Makefile @@ -0,0 +1,6 @@ +install: + npm install websocket + + +run: + nodejs example-server.js diff --git a/examples/websockets/node/example-server.js b/examples/websockets/node/example-server.js new file mode 100644 index 000000000..c7bac0d44 --- /dev/null +++ b/examples/websockets/node/example-server.js @@ -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); + }); + +}); From a7cbbe496f7d980e63b4a409ae24619e649719d3 Mon Sep 17 00:00:00 2001 From: Adam Dunkels Date: Mon, 31 Oct 2016 22:08:08 +0100 Subject: [PATCH 03/11] Contiki websocket client code --- core/net/ipv6/websocket.c | 719 ++++++++++++++++++++++++++++++++++++++ core/net/ipv6/websocket.h | 115 ++++++ 2 files changed, 834 insertions(+) create mode 100644 core/net/ipv6/websocket.c create mode 100644 core/net/ipv6/websocket.h diff --git a/core/net/ipv6/websocket.c b/core/net/ipv6/websocket.c new file mode 100644 index 000000000..cf37687e7 --- /dev/null +++ b/core/net/ipv6/websocket.c @@ -0,0 +1,719 @@ +/* + * 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 +#include +#include + +#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]; + 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; + } + + 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) +{ + // PRINTF("websocket_send_str %s\n", 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; + + 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); +} +/*---------------------------------------------------------------------------*/ diff --git a/core/net/ipv6/websocket.h b/core/net/ipv6/websocket.h new file mode 100644 index 000000000..ea8247c75 --- /dev/null +++ b/core/net/ipv6/websocket.h @@ -0,0 +1,115 @@ +/* + * 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 */ From bb071a7fba33cdd77c032224b49935478d818d6a Mon Sep 17 00:00:00 2001 From: Adam Dunkels Date: Mon, 31 Oct 2016 22:08:27 +0100 Subject: [PATCH 04/11] Contiki websocket HTTP client code --- core/net/ipv6/websocket-http-client.c | 337 ++++++++++++++++++++++++++ core/net/ipv6/websocket-http-client.h | 123 ++++++++++ 2 files changed, 460 insertions(+) create mode 100644 core/net/ipv6/websocket-http-client.c create mode 100644 core/net/ipv6/websocket-http-client.h diff --git a/core/net/ipv6/websocket-http-client.c b/core/net/ipv6/websocket-http-client.c new file mode 100644 index 000000000..95ed74961 --- /dev/null +++ b/core/net/ipv6/websocket-http-client.c @@ -0,0 +1,337 @@ +/* + * 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 +#include + +#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); + } + 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) { + } +} +/*---------------------------------------------------------------------------*/ +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 consistute the HTTP status code. */ + 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); +} +/*---------------------------------------------------------------------------*/ diff --git a/core/net/ipv6/websocket-http-client.h b/core/net/ipv6/websocket-http-client.h new file mode 100644 index 000000000..56fc34d0a --- /dev/null +++ b/core/net/ipv6/websocket-http-client.h @@ -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_ */ From 6ab9822fc2e9f8192bbe7cf9a8b3eaa2acb4aa14 Mon Sep 17 00:00:00 2001 From: Adam Dunkels Date: Mon, 31 Oct 2016 22:11:15 +0100 Subject: [PATCH 05/11] Make debug printouts be PRINTF() statements to avoid including them in non-debug builds --- core/net/ip/tcp-socket.c | 9 +++++---- 1 file changed, 5 insertions(+), 4 deletions(-) diff --git a/core/net/ip/tcp-socket.c b/core/net/ip/tcp-socket.c index eac008cde..d697ccdd9 100644 --- a/core/net/ip/tcp-socket.c +++ b/core/net/ip/tcp-socket.c @@ -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 #include - 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; From f11d344d4dec84950f09a36de965c0b697f0c1fe Mon Sep 17 00:00:00 2001 From: Adam Dunkels Date: Mon, 31 Oct 2016 22:11:34 +0100 Subject: [PATCH 06/11] Add a tcp_socket_queuelen() function that returns the length of the current TCP output queue --- core/net/ip/tcp-socket.c | 6 ++++++ core/net/ip/tcp-socket.h | 12 ++++++++++++ 2 files changed, 18 insertions(+) diff --git a/core/net/ip/tcp-socket.c b/core/net/ip/tcp-socket.c index d697ccdd9..cd26634a1 100644 --- a/core/net/ip/tcp-socket.c +++ b/core/net/ip/tcp-socket.c @@ -399,3 +399,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; +} +/*---------------------------------------------------------------------------*/ diff --git a/core/net/ip/tcp-socket.h b/core/net/ip/tcp-socket.h index 34cb0d6b7..dd7b5c793 100644 --- a/core/net/ip/tcp-socket.h +++ b/core/net/ip/tcp-socket.h @@ -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 */ From ae4801bba6a5b12ead2cddcbb96cecffeadcdbae Mon Sep 17 00:00:00 2001 From: Adam Dunkels Date: Mon, 31 Oct 2016 22:21:15 +0100 Subject: [PATCH 07/11] Bugfix: make sure the TCP connection is polled immediately after tcp_socket_send() is called so that the TCP segment goes out directly --- core/net/ip/tcp-socket.c | 2 ++ 1 file changed, 2 insertions(+) diff --git a/core/net/ip/tcp-socket.c b/core/net/ip/tcp-socket.c index cd26634a1..f709a3dfb 100644 --- a/core/net/ip/tcp-socket.c +++ b/core/net/ip/tcp-socket.c @@ -357,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; } /*---------------------------------------------------------------------------*/ From c484ee49981107951f117f89defeb343fbc4b347 Mon Sep 17 00:00:00 2001 From: Adam Dunkels Date: Wed, 2 Nov 2016 21:53:31 +0100 Subject: [PATCH 08/11] Added comments --- core/net/ipv6/websocket-http-client.c | 13 ++++++++++++- core/net/ipv6/websocket.c | 9 ++++++++- 2 files changed, 20 insertions(+), 2 deletions(-) diff --git a/core/net/ipv6/websocket-http-client.c b/core/net/ipv6/websocket-http-client.c index 95ed74961..7d4164a04 100644 --- a/core/net/ipv6/websocket-http-client.c +++ b/core/net/ipv6/websocket-http-client.c @@ -62,6 +62,13 @@ send_get(struct websocket_http_client_state *s) 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" @@ -112,6 +119,8 @@ event(struct tcp_socket *tcps, void *ptr, } 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. */ } } /*---------------------------------------------------------------------------*/ @@ -131,7 +140,9 @@ parse_header_byte(struct websocket_http_client_state *s, /* Skip the space that follow the first part */ PT_YIELD(&s->parse_header_pt); - /* Read the first three bytes that consistute the HTTP status code. */ + /* 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'); diff --git a/core/net/ipv6/websocket.c b/core/net/ipv6/websocket.c index cf37687e7..67455eeb4 100644 --- a/core/net/ipv6/websocket.c +++ b/core/net/ipv6/websocket.c @@ -603,7 +603,10 @@ 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]; + 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; @@ -616,6 +619,8 @@ send_data(struct websocket *s, const void *data, 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), @@ -689,6 +694,8 @@ websocket_ping(struct websocket *s) 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; } From 937fddbedf431ad59f243d89cfb12027c522835f Mon Sep 17 00:00:00 2001 From: Adam Dunkels Date: Wed, 2 Nov 2016 21:53:43 +0100 Subject: [PATCH 09/11] Removed blanks --- core/net/ipv6/websocket-http-client.c | 1 - core/net/ipv6/websocket.h | 1 - 2 files changed, 2 deletions(-) diff --git a/core/net/ipv6/websocket-http-client.c b/core/net/ipv6/websocket-http-client.c index 7d4164a04..ab9b75f92 100644 --- a/core/net/ipv6/websocket-http-client.c +++ b/core/net/ipv6/websocket-http-client.c @@ -161,7 +161,6 @@ parse_header_byte(struct websocket_http_client_state *s, } } - /* 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. diff --git a/core/net/ipv6/websocket.h b/core/net/ipv6/websocket.h index ea8247c75..31e5ed797 100644 --- a/core/net/ipv6/websocket.h +++ b/core/net/ipv6/websocket.h @@ -49,7 +49,6 @@ typedef enum { WEBSOCKET_PONG_RECEIVED = 11, } websocket_result_t; - struct websocket; typedef void (* websocket_callback)(struct websocket *s, From 869df3bc369d114452d36774febf9215664934e9 Mon Sep 17 00:00:00 2001 From: Adam Dunkels Date: Wed, 2 Nov 2016 21:54:02 +0100 Subject: [PATCH 10/11] Updated copyright statements --- core/net/ipv6/websocket.c | 1 - core/net/ipv6/websocket.h | 1 - examples/websockets/websocket-example.c | 30 +++++++++++++++++++++++++ 3 files changed, 30 insertions(+), 2 deletions(-) diff --git a/core/net/ipv6/websocket.c b/core/net/ipv6/websocket.c index 67455eeb4..abbf02314 100644 --- a/core/net/ipv6/websocket.c +++ b/core/net/ipv6/websocket.c @@ -10,7 +10,6 @@ * 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. diff --git a/core/net/ipv6/websocket.h b/core/net/ipv6/websocket.h index 31e5ed797..f312d7dbd 100644 --- a/core/net/ipv6/websocket.h +++ b/core/net/ipv6/websocket.h @@ -10,7 +10,6 @@ * 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. diff --git a/examples/websockets/websocket-example.c b/examples/websockets/websocket-example.c index 7214deb39..010712b06 100644 --- a/examples/websockets/websocket-example.c +++ b/examples/websockets/websocket-example.c @@ -1,3 +1,33 @@ +/* + * 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" From 36afb693b0cff45b3ec9f6da23383822e16a12ef Mon Sep 17 00:00:00 2001 From: Adam Dunkels Date: Wed, 2 Nov 2016 21:54:15 +0100 Subject: [PATCH 11/11] Removed left-over debug printout --- core/net/ipv6/websocket.c | 1 - 1 file changed, 1 deletion(-) diff --git a/core/net/ipv6/websocket.c b/core/net/ipv6/websocket.c index abbf02314..92141c9c9 100644 --- a/core/net/ipv6/websocket.c +++ b/core/net/ipv6/websocket.c @@ -674,7 +674,6 @@ send_data(struct websocket *s, const void *data, int websocket_send_str(struct websocket *s, const char *str) { - // PRINTF("websocket_send_str %s\n", str); return send_data(s, str, strlen(str), WEBSOCKET_OPCODE_TEXT); } /*---------------------------------------------------------------------------*/