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