diff options
author | José Bollo <jose.bollo@iot.bzh> | 2016-03-25 22:33:06 +0100 |
---|---|---|
committer | José Bollo <jose.bollo@iot.bzh> | 2016-03-25 22:33:06 +0100 |
commit | 4d603302535155ffe71208e86de14c7abc4e775d (patch) | |
tree | 303af202ae603f717f6f27ea630a55c8b10046f1 | |
parent | efcba05ca901c277ce44e4be8c475b79595ea0ca (diff) |
websocket: initial (not integrated)
Change-Id: I55943a81101a189d621f37f0a0b2fe21c9fbc215
Signed-off-by: José Bollo <jose.bollo@iot.bzh>
-rw-r--r-- | CMakeLists.txt | 5 | ||||
-rw-r--r-- | src/CMakeLists.txt | 2 | ||||
-rw-r--r-- | src/afb-hreq.c | 5 | ||||
-rw-r--r-- | src/afb-hreq.h | 7 | ||||
-rw-r--r-- | src/afb-websock.c | 195 | ||||
-rw-r--r-- | src/http-svc.c | 1 | ||||
-rw-r--r-- | src/websock.c | 419 | ||||
-rw-r--r-- | src/websock.h | 65 |
8 files changed, 697 insertions, 2 deletions
diff --git a/CMakeLists.txt b/CMakeLists.txt index 9cb17c17..58f82406 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -54,6 +54,7 @@ ENDIF(CMAKE_BUILD_TYPE MATCHES Debug) INCLUDE(FindPkgConfig) PKG_CHECK_MODULES(json-c REQUIRED json-c) PKG_CHECK_MODULES(libmicrohttpd REQUIRED libmicrohttpd) +PKG_CHECK_MODULES(openssl REQUIRED openssl) PKG_CHECK_MODULES(uuid REQUIRED uuid) PKG_CHECK_MODULES(dbus REQUIRED dbus-1) # Optional plugin dependencies @@ -81,8 +82,8 @@ ENDIF(gupnp_FOUND) INCLUDE(FindThreads) FIND_PACKAGE(Threads) -SET(include_dirs ${INCLUDE_DIRS} ${CMAKE_SOURCE_DIR}/include ${json-c_INCLUDE_DIRS} ${libmicrohttpd_INCLUDE_DIRS} ${uuid_INCLUDE_DIRS} ${dbus_INCLUDE_DIRS} ${alsa_INCLUDE_DIRS} ${pulseaudio_INCLUDE_DIRS} ${librtlsdr_INCLUDE_DIRS} ${gupnp_INCLUDE_DIRS}) -SET(link_libraries ${json-c_LIBRARIES} ${libmicrohttpd_LIBRARIES} ${uuid_LIBRARIES} ${dbus_LIBRARIES} ${alsa_LIBRARIES} ${pulseaudio_LIBRARIES} ${librtlsdr_LIBRARIES} ${gupnp_LIBRARIES} ${CMAKE_THREAD_LIBS_INIT} ${libefence_LIBRARIES} -lmagic -lm -ldl) +SET(include_dirs ${INCLUDE_DIRS} ${CMAKE_SOURCE_DIR}/include ${json-c_INCLUDE_DIRS} ${libmicrohttpd_INCLUDE_DIRS} ${uuid_INCLUDE_DIRS} ${dbus_INCLUDE_DIRS} ${alsa_INCLUDE_DIRS} ${pulseaudio_INCLUDE_DIRS} ${librtlsdr_INCLUDE_DIRS} ${gupnp_INCLUDE_DIRS} ${openssl_INCLUDE_DIRS}) +SET(link_libraries ${json-c_LIBRARIES} ${libmicrohttpd_LIBRARIES} ${uuid_LIBRARIES} ${dbus_LIBRARIES} ${alsa_LIBRARIES} ${pulseaudio_LIBRARIES} ${librtlsdr_LIBRARIES} ${gupnp_LIBRARIES} ${CMAKE_THREAD_LIBS_INIT} ${libefence_LIBRARIES} ${openssl_LIBRARIES} -lmagic -lm -ldl) SET(plugin_install_dir ${CMAKE_INSTALL_PREFIX}/${CMAKE_INSTALL_LIBDIR}/afb) ADD_DEFINITIONS(-DPLUGIN_INSTALL_DIR="${plugin_install_dir}") diff --git a/src/CMakeLists.txt b/src/CMakeLists.txt index 19be965d..605a0df1 100644 --- a/src/CMakeLists.txt +++ b/src/CMakeLists.txt @@ -6,6 +6,8 @@ ADD_LIBRARY(src OBJECT rest-api.c afb-method.c afb-hreq.c + afb-websock.c + websock.c helper-api.c) INCLUDE_DIRECTORIES(${include_dirs}) diff --git a/src/afb-hreq.c b/src/afb-hreq.c index 613d4199..7bee69b3 100644 --- a/src/afb-hreq.c +++ b/src/afb-hreq.c @@ -240,6 +240,11 @@ const char *afb_hreq_get_argument(struct afb_hreq *hreq, const char *name) return MHD_lookup_connection_value(hreq->connection, MHD_GET_ARGUMENT_KIND, name); } +const char *afb_hreq_get_header(struct afb_hreq *hreq, const char *name) +{ + return MHD_lookup_connection_value(hreq->connection, MHD_HEADER_KIND, name); +} + struct afb_req_itf afb_hreq_itf = { .get_cookie = (void*)afb_hreq_get_cookie, .get_argument = (void*)afb_hreq_get_argument diff --git a/src/afb-hreq.h b/src/afb-hreq.h index fd2a2a3d..f2362b10 100644 --- a/src/afb-hreq.h +++ b/src/afb-hreq.h @@ -28,6 +28,7 @@ struct afb_hreq { AFB_session *session; struct MHD_Connection *connection; enum afb_method method; + const char *version; const char *url; size_t lenurl; const char *tail; @@ -50,4 +51,10 @@ extern int afb_hreq_reply_file(struct afb_hreq *request, int dirfd, const char * extern int afb_hreq_redirect_to(struct afb_hreq *request, const char *url); +extern const char *afb_hreq_get_cookie(struct afb_hreq *hreq, const char *name); + +extern const char *afb_hreq_get_argument(struct afb_hreq *hreq, const char *name); + +extern const char *afb_hreq_get_header(struct afb_hreq *hreq, const char *name); + extern struct afb_req_itf afb_hreq_itf; diff --git a/src/afb-websock.c b/src/afb-websock.c new file mode 100644 index 00000000..1134ba80 --- /dev/null +++ b/src/afb-websock.c @@ -0,0 +1,195 @@ +/* + * Copyright 2016 IoT.bzh + * Author: José Bollo <jose.bollo@iot.bzh> + * + * Inspired by the work of + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +#define _GNU_SOURCE +#include <microhttpd.h> +#include <assert.h> +#include <errno.h> +#include <sys/uio.h> + +#include <openssl/sha.h> + +#include "websock.h" + +#include "../include/local-def.h" + + +#include "afb-method.h" +#include "afb-hreq.h" + +static const char websocket_s[] = "websocket"; +static const char sec_websocket_key_s[] = "Sec-WebSocket-Key"; +static const char sec_websocket_version_s[] = "Sec-WebSocket-Version"; +static const char sec_websocket_accept_s[] = "Sec-WebSocket-Accept"; +static const char sec_websocket_protocol_s[] = "Sec-WebSocket-Protocol"; +static const char websocket_uuid[] = "258EAFA5-E914-47DA-95CA-C5AB0DC85B11"; + + +struct afb_websock +{ + int fd; + struct MHD_Connection *connection; + struct websock *ws; +}; + +static ssize_t afb_websock_writev(struct afb_websock *ws, const struct iovec *iov, int iovcnt) +{ + ssize_t rc; + do { + rc = writev(ws->fd, iov, iovcnt); + } while(rc == -1 && errno == EINTR); + return rc; +} + +static ssize_t afb_websock_readv(struct afb_websock *ws, const struct iovec *iov, int iovcnt) +{ + ssize_t rc; + do { + rc = readv(ws->fd, iov, iovcnt); + } while(rc == -1 && errno == EINTR); + return rc; +} + +static void afb_websock_disconnect(struct afb_websock *ws) +{ +} + +static void afb_websock_on_close(struct afb_websock *ws, uint16_t code, size_t size) +{ +} + +static void afb_websock_on_content(struct afb_websock *ws, int last, size_t size) +{ +} + +static struct websock_itf afb_websock_itf = { + .writev = (void*)afb_websock_writev, + .readv = (void*)afb_websock_readv, + .disconnect = (void*)afb_websock_disconnect, + + .on_ping = NULL, + .on_pong = NULL, + .on_close = (void*)afb_websock_on_close, + .on_text = (void*)afb_websock_on_content, + .on_binary = (void*)afb_websock_on_content, + .on_continue = (void*)afb_websock_on_content +}; + +static void enc64(unsigned char *in, char *out) +{ + static const char tob64[] = + "ABCDEFGHIJKLMNOPQRSTUVWXYZ" + "abcdefghijklmnopqrstuvwxyz" + "0123456789+/"; + out[0] = tob64[in[0] >> 2]; + out[1] = tob64[((in[0] & 0x03) << 4) | ((in[1] & 0xf0) >> 4)]; + out[2] = tob64[((in[1] & 0x0f) << 2) | ((in[2] & 0xc0) >> 6)]; + out[3] = tob64[in[2] & 0x3f]; +} + +static void make_accept_value(const char *key, char result[29]) +{ + unsigned char md[SHA_DIGEST_LENGTH+1]; + size_t len = strlen(key); + char *buffer = alloca(len + sizeof websocket_uuid - 1); + memcpy(buffer, key, len); + memcpy(buffer + len, websocket_uuid, sizeof websocket_uuid - 1); + SHA1((const unsigned char *)buffer, (unsigned long)(len + sizeof websocket_uuid - 1), md); + assert(SHA_DIGEST_LENGTH == 20); + md[20] = 0; + enc64(&md[0], &result[0]); + enc64(&md[3], &result[4]); + enc64(&md[6], &result[8]); + enc64(&md[9], &result[12]); + enc64(&md[12], &result[16]); + enc64(&md[15], &result[20]); + enc64(&md[18], &result[24]); + result[27] = '='; + result[28] = 0; +} + +static int handshake(struct afb_hreq *hreq, struct afb_websock **ws) +{ + const char *connection, *upgrade, *key, *version, *protocols; + char acceptval[29]; + int vernum; + struct MHD_Response *response; + + upgrade = afb_hreq_get_header(hreq, MHD_HTTP_HEADER_UPGRADE); + if (upgrade == NULL || strcasecmp(upgrade, websocket_s)) + return 0; + + connection = afb_hreq_get_header(hreq, MHD_HTTP_HEADER_CONNECTION); + if (connection == NULL || strcasecmp (connection, MHD_HTTP_HEADER_UPGRADE)) + return 0; + + if(hreq->method != afb_method_get || strcasecmp(hreq->version, MHD_HTTP_VERSION_1_1)) + return 0; + + key = afb_hreq_get_header(hreq, sec_websocket_key_s); + version = afb_hreq_get_header(hreq, sec_websocket_version_s); + if (key == NULL || version == NULL) + return 0; + + vernum = atoi(version); + if (vernum != 13) + return 0; + + if (*ws == NULL) + return 1; + + protocols = afb_hreq_get_header(hreq, sec_websocket_protocol_s); + + if (vernum != 13) { + response = MHD_create_response_from_data(0,NULL,0,0); + MHD_add_response_header (response, sec_websocket_version_s, "13"); + MHD_queue_response (hreq->connection, MHD_HTTP_BAD_REQUEST, response); + MHD_destroy_response (response); + return 2; + } + + make_accept_value(key, acceptval); + response = MHD_create_response_from_data(0,NULL,0,0); + MHD_add_response_header (response, sec_websocket_accept_s, acceptval); + MHD_add_response_header (response, MHD_HTTP_HEADER_CONNECTION, MHD_HTTP_HEADER_UPGRADE); + MHD_add_response_header (response, MHD_HTTP_HEADER_UPGRADE, websocket_s); + MHD_queue_response (hreq->connection, MHD_HTTP_SWITCHING_PROTOCOLS, response); + MHD_destroy_response (response); + + return 1; +} + +int afb_websock_is_handshake(struct afb_hreq *hreq) +{ + return handshake(hreq, NULL); +} + +int afb_websock_open_if(struct afb_hreq *hreq, struct afb_websock **ws) +{ + assert(*ws != NULL); + *ws = NULL; + return handshake(hreq, ws); +} + +int afb_websock_open(struct afb_hreq *hreq, struct afb_websock **ws) +{ + int rc = afb_websock_open_if(hreq, ws); + return rc ? rc : -1; +} + diff --git a/src/http-svc.c b/src/http-svc.c index 7b789c4f..ff09650f 100644 --- a/src/http-svc.c +++ b/src/http-svc.c @@ -245,6 +245,7 @@ static int access_handler( request.session = cls; request.connection = connection; request.method = method; + request.version = version; request.tail = request.url = url; request.lentail = request.lenurl = strlen(url); request.recorder = (struct afb_hreq **)recorder; diff --git a/src/websock.c b/src/websock.c new file mode 100644 index 00000000..dc0b5f44 --- /dev/null +++ b/src/websock.c @@ -0,0 +1,419 @@ +/* + * Copyright 2016 iot.bzh + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +/* + * This work is a far adaptation of apache-websocket: + * origin: https://github.com/disconnect/apache-websocket + * commit: cfaef071223f11ba016bff7e1e4b7c9e5df45b50 + * Copyright 2010-2012 self.disconnect (APACHE-2) + */ + +#include <stdlib.h> +#include <stdint.h> +#include <errno.h> +#include <string.h> +#include <sys/uio.h> + +#include "websock.h" + +#define BLOCK_DATA_SIZE 4096 + +#define FRAME_GET_FIN(BYTE) (((BYTE) >> 7) & 0x01) +#define FRAME_GET_RSV1(BYTE) (((BYTE) >> 6) & 0x01) +#define FRAME_GET_RSV2(BYTE) (((BYTE) >> 5) & 0x01) +#define FRAME_GET_RSV3(BYTE) (((BYTE) >> 4) & 0x01) +#define FRAME_GET_OPCODE(BYTE) ( (BYTE) & 0x0F) +#define FRAME_GET_MASK(BYTE) (((BYTE) >> 7) & 0x01) +#define FRAME_GET_PAYLOAD_LEN(BYTE) ( (BYTE) & 0x7F) + +#define FRAME_SET_FIN(BYTE) (((BYTE) & 0x01) << 7) +#define FRAME_SET_OPCODE(BYTE) ((BYTE) & 0x0F) +#define FRAME_SET_MASK(BYTE) (((BYTE) & 0x01) << 7) +#define FRAME_SET_LENGTH(X64, IDX) (unsigned char)(((X64) >> ((IDX)*8)) & 0xFF) + +#define OPCODE_CONTINUATION 0x0 +#define OPCODE_TEXT 0x1 +#define OPCODE_BINARY 0x2 +#define OPCODE_CLOSE 0x8 +#define OPCODE_PING 0x9 +#define OPCODE_PONG 0xA + +#define STATE_INIT 0 +#define STATE_START 1 +#define STATE_LENGTH 2 +#define STATE_DATA 3 +#define STATE_CLOSED 4 + +struct websock { + int state; + uint64_t maxlength; + int lenhead, szhead; + uint64_t length; + uint32_t mask; + unsigned char header[14]; /* 2 + 8 + 4 */ + const struct websock_itf *itf; + void *closure; +}; + +static ssize_t ws_writev(struct websock *ws, const struct iovec *iov, int iovcnt) +{ + return ws->itf->writev(ws->closure, iov, iovcnt); +} + +static ssize_t ws_readv(struct websock *ws, const struct iovec *iov, int iovcnt) +{ + return ws->itf->readv(ws->closure, iov, iovcnt); +} + +#if 0 +static ssize_t ws_write(struct websock *ws, const void *buffer, size_t buffer_size) +{ + struct iovec iov; + iov.iov_base = (void *)buffer; /* const cast */ + iov.iov_len = buffer_size; + return ws_writev(ws, &iov, 1); +} +#endif + +static ssize_t ws_read(struct websock *ws, void *buffer, size_t buffer_size) +{ + struct iovec iov; + iov.iov_base = buffer; + iov.iov_len = buffer_size; + return ws_readv(ws, &iov, 1); +} + +static ssize_t websock_send(struct websock *ws, unsigned char opcode, + const void *buffer, size_t buffer_size) +{ + struct iovec iov[2]; + size_t pos; + ssize_t rc; + unsigned char header[32]; + + if (ws->state == STATE_CLOSED) + return 0; + + pos = 0; + header[pos++] = (unsigned char)(FRAME_SET_FIN(1) | FRAME_SET_OPCODE(opcode)); + buffer_size = (uint64_t) buffer_size; + if (buffer_size < 126) { + header[pos++] = + FRAME_SET_MASK(0) | FRAME_SET_LENGTH(buffer_size, 0); + } else { + if (buffer_size < 65536) { + header[pos++] = FRAME_SET_MASK(0) | 126; + } else { + header[pos++] = FRAME_SET_MASK(0) | 127; + header[pos++] = FRAME_SET_LENGTH(buffer_size, 7); + header[pos++] = FRAME_SET_LENGTH(buffer_size, 6); + header[pos++] = FRAME_SET_LENGTH(buffer_size, 5); + header[pos++] = FRAME_SET_LENGTH(buffer_size, 4); + header[pos++] = FRAME_SET_LENGTH(buffer_size, 3); + header[pos++] = FRAME_SET_LENGTH(buffer_size, 2); + } + header[pos++] = FRAME_SET_LENGTH(buffer_size, 1); + header[pos++] = FRAME_SET_LENGTH(buffer_size, 0); + } + + iov[0].iov_base = header; + iov[0].iov_len = pos; + iov[1].iov_base = (void *)buffer; /* const cast */ + iov[1].iov_len = buffer_size; + + rc = ws_writev(ws, iov, 1 + !!buffer_size); + + if (opcode == OPCODE_CLOSE) { + ws->length = 0; + ws->state = STATE_CLOSED; + ws->itf->disconnect(ws->closure); + } + return rc; +} + +void websock_close(struct websock *ws) +{ + websock_send(ws, OPCODE_CLOSE, NULL, 0); +} + +void websock_close_code(struct websock *ws, uint16_t code) +{ + unsigned char buffer[2]; + /* Send server-side closing handshake */ + buffer[0] = (unsigned char)((code >> 8) & 0xFF); + buffer[1] = (unsigned char)(code & 0xFF); + websock_send(ws, OPCODE_CLOSE, buffer, 2); +} + +void websock_ping(struct websock *ws) +{ + websock_send(ws, OPCODE_PING, NULL, 0); +} + +void websock_pong(struct websock *ws) +{ + websock_send(ws, OPCODE_PONG, NULL, 0); +} + +void websock_text(struct websock *ws, const char *text, size_t length) +{ + websock_send(ws, OPCODE_TEXT, NULL, 0); +} + +void websock_binary(struct websock *ws, const void *data, size_t length) +{ + websock_send(ws, OPCODE_BINARY, NULL, 0); +} + +static int read_header(struct websock *ws) +{ + if (ws->lenhead < ws->szhead) { + ssize_t rbc = + ws_read(ws, &ws->header[ws->lenhead], (size_t)(ws->szhead - ws->lenhead)); + if (rbc < 0) + return -1; + ws->lenhead += (int)rbc; + } + return 0; +} + +int websock_dispatch(struct websock *ws) +{ + loop: + switch (ws->state) { + case STATE_INIT: + ws->lenhead = 0; + ws->szhead = 2; + ws->state = STATE_START; + + case STATE_START: + /* read the header */ + if (!read_header(ws)) + return -1; + else if (ws->lenhead < ws->szhead) + return 0; + /* sanity checks */ + if (FRAME_GET_RSV1(ws->header[0]) != 0) + goto protocol_error; + if (FRAME_GET_RSV2(ws->header[0]) != 0) + goto protocol_error; + if (FRAME_GET_RSV3(ws->header[0]) != 0) + goto protocol_error; + /* fast track */ + switch (FRAME_GET_OPCODE(ws->header[0])) { + case OPCODE_CONTINUATION: + case OPCODE_TEXT: + case OPCODE_BINARY: + break; + case OPCODE_CLOSE: + if (FRAME_GET_MASK(ws->header[1])) + goto protocol_error; + if (FRAME_GET_PAYLOAD_LEN(ws->header[1]) == 1) + goto protocol_error; + if (FRAME_GET_PAYLOAD_LEN(ws->header[1])) + ws->szhead += 2; + break; + case OPCODE_PING: + if (FRAME_GET_MASK(ws->header[1])) + goto protocol_error; + if (FRAME_GET_PAYLOAD_LEN(ws->header[1]) != 0) + goto protocol_error; + if (ws->itf->on_ping) + ws->itf->on_ping(ws->closure); + else + websock_pong(ws); + ws->state = STATE_INIT; + goto loop; + case OPCODE_PONG: + if (FRAME_GET_MASK(ws->header[1])) + goto protocol_error; + if (FRAME_GET_PAYLOAD_LEN(ws->header[1]) != 0) + goto protocol_error; + if (ws->itf->on_pong) + ws->itf->on_pong(ws->closure); + ws->state = STATE_INIT; + goto loop; + default: + goto protocol_error; + } + /* update heading size */ + switch (FRAME_GET_PAYLOAD_LEN(ws->header[1])) { + case 127: + ws->szhead += 6; + case 126: + ws->szhead += 2; + default: + ws->szhead += 4 * FRAME_GET_MASK(ws->header[1]); + } + ws->state = STATE_LENGTH; + + case STATE_LENGTH: + /* continue to read the header */ + if (!read_header(ws)) + return -1; + else if (ws->lenhead < ws->szhead) + return 0; + /* compute header values */ + switch (FRAME_GET_PAYLOAD_LEN(ws->header[1])) { + case 127: + ws->length = (((uint64_t) ws->header[2]) << 56) + | (((uint64_t) ws->header[3]) << 48) + | (((uint64_t) ws->header[4]) << 40) + | (((uint64_t) ws->header[5]) << 32) + | (((uint64_t) ws->header[6]) << 24) + | (((uint64_t) ws->header[7]) << 16) + | (((uint64_t) ws->header[8]) << 8) + | (uint64_t) ws->header[9]; + break; + case 126: + ws->length = (((uint64_t) ws->header[2]) << 8) + | (uint64_t) ws->header[3]; + break; + default: + ws->length = FRAME_GET_PAYLOAD_LEN(ws->header[1]); + break; + } + if (ws->length > ws->maxlength) + goto too_long_error; + if (FRAME_GET_MASK(ws->header[1])) { + ((unsigned char *)&ws->mask)[0] = ws->header[ws->szhead - 4]; + ((unsigned char *)&ws->mask)[1] = ws->header[ws->szhead - 3]; + ((unsigned char *)&ws->mask)[2] = ws->header[ws->szhead - 2]; + ((unsigned char *)&ws->mask)[3] = ws->header[ws->szhead - 1]; + } else + ws->mask = 0; + ws->state = STATE_DATA; + switch (FRAME_GET_OPCODE(ws->header[0])) { + case OPCODE_CONTINUATION: + ws->itf->on_continue(ws->closure, + FRAME_GET_FIN(ws->header[0]), + (size_t) ws->length); + break; + case OPCODE_TEXT: + ws->itf->on_text(ws->closure, + FRAME_GET_FIN(ws->header[0]), + (size_t) ws->length); + break; + case OPCODE_BINARY: + ws->itf->on_binary(ws->closure, + FRAME_GET_FIN(ws->header[0]), + (size_t) ws->length); + break; + case OPCODE_CLOSE: + ws->state = STATE_CLOSED; + if (ws->length) + ws->itf->on_close(ws->closure, + (uint16_t)((((uint16_t) ws-> header[2]) << 8) | ((uint16_t) ws->header[3])), + (size_t) ws->length); + else + ws->itf->on_close(ws->closure, + STATUS_CODE_UNSET, 0); + ws->itf->disconnect(ws->closure); + return 0; + } + break; + + case STATE_DATA: + if (ws->length) + return 0; + ws->state = STATE_INIT; + break; + + case STATE_CLOSED: + return 0; + } + goto loop; + + too_long_error: + websock_close_code(ws, STATUS_CODE_MESSAGE_TOO_LARGE); + return 0; + + protocol_error: + websock_close_code(ws, STATUS_CODE_PROTOCOL_ERROR); + return 0; +} + +ssize_t websock_read(struct websock * ws, void *buffer, size_t size) +{ + uint32_t mask, *b32; + uint8_t m, *b8; + ssize_t rc; + + if (ws->state != STATE_DATA && ws->state != STATE_CLOSED) + return 0; + + if (size > ws->length) + size = (size_t) ws->length; + + rc = ws_read(ws, buffer, size); + if (rc > 0) { + size = (size_t) rc; + ws->length -= size; + + if (ws->mask) { + mask = ws->mask; + b8 = buffer; + while (size && ((sizeof(uint32_t) - 1) & (uintptr_t) b8)) { + m = ((uint8_t *) & mask)[0]; + ((uint8_t *) & mask)[0] = ((uint8_t *) & mask)[1]; + ((uint8_t *) & mask)[1] = ((uint8_t *) & mask)[2]; + ((uint8_t *) & mask)[2] = ((uint8_t *) & mask)[3]; + ((uint8_t *) & mask)[3] = m; + *b8++ ^= m; + size--; + } + b32 = (uint32_t *) b8; + while (size >= sizeof(uint32_t)) { + *b32++ ^= m; + size -= sizeof(uint32_t); + } + b8 = (uint8_t *) b32; + while (size) { + m = ((uint8_t *) & mask)[0]; + ((uint8_t *) & mask)[0] = ((uint8_t *) & mask)[1]; + ((uint8_t *) & mask)[1] = ((uint8_t *) & mask)[2]; + ((uint8_t *) & mask)[2] = ((uint8_t *) & mask)[3]; + ((uint8_t *) & mask)[3] = m; + *b8++ ^= m; + size--; + } + ws->mask = mask; + } + } + return rc; +} + +void websock_drop(struct websock *ws) +{ + char buffer[4096]; + + while (ws->length && ws_read(ws, buffer, sizeof buffer) >= 0) ; +} + +struct websock *websock_create(const struct websock_itf *itf, void *closure) +{ + struct websock *result = calloc(1, sizeof *result); + if (result) { + result->itf = itf; + result->closure = closure; + } + return result; +} + +void websock_destroy(struct websock *ws) +{ + free(ws); +} diff --git a/src/websock.h b/src/websock.h new file mode 100644 index 00000000..b67f36e1 --- /dev/null +++ b/src/websock.h @@ -0,0 +1,65 @@ +/* + * Copyright 2016 iot.bzh + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +/* + * This work is a far adaptation of apache-websocket: + * origin: https://github.com/disconnect/apache-websocket + * commit: cfaef071223f11ba016bff7e1e4b7c9e5df45b50 + * Copyright 2010-2012 self.disconnect (APACHE-2) + */ + +struct iovec; + +#define STATUS_CODE_UNSET 0 +#define STATUS_CODE_OK 1000 +#define STATUS_CODE_GOING_AWAY 1001 +#define STATUS_CODE_PROTOCOL_ERROR 1002 +#define STATUS_CODE_RESERVED 1004 /* Protocol 8: frame too large */ +#define STATUS_CODE_INVALID_UTF8 1007 +#define STATUS_CODE_POLICY_VIOLATION 1008 +#define STATUS_CODE_MESSAGE_TOO_LARGE 1009 +#define STATUS_CODE_INTERNAL_ERROR 1011 + +struct websock_itf { + ssize_t (*writev) (void *, const struct iovec *, int); + ssize_t (*readv) (void *, const struct iovec *, int); + void (*disconnect) (void *); + + void (*on_ping) (void *); + void (*on_pong) (void *); + void (*on_close) (void *, uint16_t code, size_t size); + void (*on_text) (void *, int last, size_t size); + void (*on_binary) (void *, int last, size_t size); + void (*on_continue) (void *, int last, size_t size); +}; + +struct websock; + +void websock_close(struct websock *ws); +void websock_close_code(struct websock *ws, uint16_t code); + +void websock_ping(struct websock *ws); +void websock_pong(struct websock *ws); +void websock_text(struct websock *ws, const char *text, size_t length); +void websock_binary(struct websock *ws, const void *data, size_t length); + +ssize_t websock_read(struct websock *ws, void *buffer, size_t size); +void websock_drop(struct websock *ws); + +int websock_dispatch(struct websock *ws); + +struct websock *websock_create(const struct websock_itf *itf, void *closure); +void websock_destroy(struct websock *ws); |