X-Git-Url: https://gerrit.automotivelinux.org/gerrit/gitweb?a=blobdiff_plain;f=src%2Fwebsock.c;fp=src%2Fwebsock.c;h=dc0b5f44f311b18ad9bce7ac1819038fc1c1c698;hb=4d603302535155ffe71208e86de14c7abc4e775d;hp=0000000000000000000000000000000000000000;hpb=efcba05ca901c277ce44e4be8c475b79595ea0ca;p=src%2Fapp-framework-binder.git 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 +#include +#include +#include +#include + +#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); +}