X-Git-Url: https://gerrit.automotivelinux.org/gerrit/gitweb?a=blobdiff_plain;f=src%2Fwebsock.c;h=2632260910f4ea3137142ce2199e7ba6f375a9d3;hb=65353dce81a629e042800bb7b86fcd869a76727e;hp=41e47a0a4eb1880608a7da09a2d376d1aacadad0;hpb=cc4b56b6710624c069642d1a510d0060949fe5b9;p=src%2Fapp-framework-binder.git diff --git a/src/websock.c b/src/websock.c index 41e47a0a..26322609 100644 --- a/src/websock.c +++ b/src/websock.c @@ -1,5 +1,5 @@ /* - * Copyright 2016 iot.bzh + * Copyright (C) 2015-2020 "IoT.bzh" * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. @@ -29,7 +29,9 @@ #include "websock.h" -#define BLOCK_DATA_SIZE 4096 +#if !defined(WEBSOCKET_DEFAULT_MAXLENGTH) +# define WEBSOCKET_DEFAULT_MAXLENGTH 1048500 /* 76 less than 1M, probably enougth for headers */ +#endif #define FRAME_GET_FIN(BYTE) (((BYTE) >> 7) & 0x01) #define FRAME_GET_RSV1(BYTE) (((BYTE) >> 6) & 0x01) @@ -40,9 +42,12 @@ #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_RSV1(BYTE) (((BYTE) & 0x01) << 6) +#define FRAME_SET_RSV2(BYTE) (((BYTE) & 0x01) << 5) +#define FRAME_SET_RSV3(BYTE) (((BYTE) & 0x01) << 4) +#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 FRAME_SET_LENGTH(X64, IDX) (unsigned char)((sizeof(X64)) <= (IDX) ? 0 : (((X64) >> ((IDX)*8)) & 0xFF)) #define OPCODE_CONTINUATION 0x0 #define OPCODE_TEXT 0x1 @@ -55,7 +60,8 @@ #define STATE_START 1 #define STATE_LENGTH 2 #define STATE_DATA 3 -#define STATE_CLOSED 4 + +static size_t default_maxlength = WEBSOCKET_DEFAULT_MAXLENGTH; struct websock { int state; @@ -78,16 +84,6 @@ 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; @@ -96,86 +92,181 @@ static ssize_t ws_read(struct websock *ws, void *buffer, size_t 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) +static int websock_send_internal_v(struct websock *ws, unsigned char first, const struct iovec *iovec, int count) { - struct iovec iov[2]; - size_t pos; + struct iovec iov[32]; + int i, j; + size_t pos, size, len; ssize_t rc; unsigned char header[32]; - if (ws->state == STATE_CLOSED) - return 0; + /* checks count */ + if (count < 0 || (count + 1) > (int)(sizeof iov / sizeof * iov)) { + errno = EINVAL; + return -1; + } + + /* computes the size */ + size = 0; + i = 1; + for (j = 0 ; j < count ; j++) { + iov[i].iov_base = iovec[j].iov_base; + len = iovec[j].iov_len; + if (len != 0) { + iov[i].iov_len = len; + size += len; + i++; + } + } + /* makes the header */ 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); + header[pos++] = first; + size = (uint64_t) size; + if (size < 126) { + header[pos++] = FRAME_SET_MASK(0) | FRAME_SET_LENGTH(size, 0); } else { - if (buffer_size < 65536) { + if (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(size, 7); + header[pos++] = FRAME_SET_LENGTH(size, 6); + header[pos++] = FRAME_SET_LENGTH(size, 5); + header[pos++] = FRAME_SET_LENGTH(size, 4); + header[pos++] = FRAME_SET_LENGTH(size, 3); + header[pos++] = FRAME_SET_LENGTH(size, 2); } - header[pos++] = FRAME_SET_LENGTH(buffer_size, 1); - header[pos++] = FRAME_SET_LENGTH(buffer_size, 0); + header[pos++] = FRAME_SET_LENGTH(size, 1); + header[pos++] = FRAME_SET_LENGTH(size, 0); } + /* allocates the vec */ 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, i); - rc = ws_writev(ws, iov, 1 + !!buffer_size); + return rc < 0 ? -1 : 0; +} - if (opcode == OPCODE_CLOSE) { - ws->length = 0; - ws->state = STATE_CLOSED; - ws->itf->disconnect(ws->closure); - } - return rc; +static int websock_send_internal(struct websock *ws, unsigned char first, const void *buffer, size_t size) +{ + struct iovec iov; + + iov.iov_base = (void *)buffer; + iov.iov_len = size; + return websock_send_internal_v(ws, first, &iov, 1); +} + +static inline int websock_send_v(struct websock *ws, int last, int rsv1, int rsv2, int rsv3, int opcode, const struct iovec *iovec, int count) +{ + unsigned char first = (unsigned char)(FRAME_SET_FIN(last) + | FRAME_SET_RSV1(rsv1) + | FRAME_SET_RSV1(rsv2) + | FRAME_SET_RSV1(rsv3) + | FRAME_SET_OPCODE(opcode)); + return websock_send_internal_v(ws, first, iovec, count); +} + +static inline int websock_send(struct websock *ws, int last, int rsv1, int rsv2, int rsv3, int opcode, const void *buffer, size_t size) +{ + unsigned char first = (unsigned char)(FRAME_SET_FIN(last) + | FRAME_SET_RSV1(rsv1) + | FRAME_SET_RSV1(rsv2) + | FRAME_SET_RSV1(rsv3) + | FRAME_SET_OPCODE(opcode)); + return websock_send_internal(ws, first, buffer, size); } -void websock_close(struct websock *ws) +int websock_close_empty(struct websock *ws) { - websock_send(ws, OPCODE_CLOSE, NULL, 0); + return websock_close(ws, WEBSOCKET_CODE_NOT_SET, NULL, 0); } -void websock_close_code(struct websock *ws, uint16_t code) +int websock_close(struct websock *ws, uint16_t code, const void *data, size_t length) { unsigned char buffer[2]; - /* Send server-side closing handshake */ + struct iovec iov[2]; + + if (code == WEBSOCKET_CODE_NOT_SET && length == 0) + return websock_send(ws, 1, 0, 0, 0, OPCODE_CLOSE, NULL, 0); + + /* checks the length */ + if (length > 123) { + errno = EINVAL; + return -1; + } + + /* prepare the buffer */ buffer[0] = (unsigned char)((code >> 8) & 0xFF); buffer[1] = (unsigned char)(code & 0xFF); - websock_send(ws, OPCODE_CLOSE, buffer, 2); + + /* Send server-side closing handshake */ + iov[0].iov_base = (void *)buffer; + iov[0].iov_len = 2; + iov[1].iov_base = (void *)data; + iov[1].iov_len = length; + return websock_send_v(ws, 1, 0, 0, 0, OPCODE_CLOSE, iov, 2); +} + +int websock_ping(struct websock *ws, const void *data, size_t length) +{ + /* checks the length */ + if (length > 125) { + errno = EINVAL; + return -1; + } + + return websock_send(ws, 1, 0, 0, 0, OPCODE_PING, data, length); +} + +int websock_pong(struct websock *ws, const void *data, size_t length) +{ + /* checks the length */ + if (length > 125) { + errno = EINVAL; + return -1; + } + + return websock_send(ws, 1, 0, 0, 0, OPCODE_PONG, data, length); +} + +int websock_text(struct websock *ws, int last, const void *text, size_t length) +{ + return websock_send(ws, last, 0, 0, 0, OPCODE_TEXT, text, length); +} + +int websock_text_v(struct websock *ws, int last, const struct iovec *iovec, int count) +{ + return websock_send_v(ws, last, 0, 0, 0, OPCODE_TEXT, iovec, count); } -void websock_ping(struct websock *ws) +int websock_binary(struct websock *ws, int last, const void *data, size_t length) { - websock_send(ws, OPCODE_PING, NULL, 0); + return websock_send(ws, last, 0, 0, 0, OPCODE_BINARY, data, length); } -void websock_pong(struct websock *ws) +int websock_binary_v(struct websock *ws, int last, const struct iovec *iovec, int count) { - websock_send(ws, OPCODE_PONG, NULL, 0); + return websock_send_v(ws, last, 0, 0, 0, OPCODE_BINARY, iovec, count); } -void websock_text(struct websock *ws, const char *text, size_t length) +int websock_continue(struct websock *ws, int last, const void *data, size_t length) { - websock_send(ws, OPCODE_TEXT, NULL, 0); + return websock_send(ws, last, 0, 0, 0, OPCODE_CONTINUATION, data, length); } -void websock_binary(struct websock *ws, const void *data, size_t length) +int websock_continue_v(struct websock *ws, int last, const struct iovec *iovec, int count) { - websock_send(ws, OPCODE_BINARY, NULL, 0); + return websock_send_v(ws, last, 0, 0, 0, OPCODE_CONTINUATION, iovec, count); +} + +int websock_error(struct websock *ws, uint16_t code, const void *data, size_t size) +{ + int rc = websock_close(ws, code, data, size); + if (ws->itf->on_error != NULL) + ws->itf->on_error(ws->closure, code, data, size); + return rc; } static int read_header(struct websock *ws) @@ -190,28 +281,39 @@ static int read_header(struct websock *ws) return 0; } -int websock_dispatch(struct websock *ws) +static int check_control_header(struct websock *ws) +{ + /* sanity checks */ + if (FRAME_GET_RSV1(ws->header[0]) != 0) + return 0; + if (FRAME_GET_RSV2(ws->header[0]) != 0) + return 0; + if (FRAME_GET_RSV3(ws->header[0]) != 0) + return 0; + if (FRAME_GET_PAYLOAD_LEN(ws->header[1]) > 125) + return 0; + if (FRAME_GET_OPCODE(ws->header[0]) == OPCODE_CLOSE) + return FRAME_GET_PAYLOAD_LEN(ws->header[1]) != 1; + return 1; +} + +int websock_dispatch(struct websock *ws, int loop) { - loop: + uint16_t code; +loop: switch (ws->state) { case STATE_INIT: ws->lenhead = 0; ws->szhead = 2; ws->state = STATE_START; + /*@fallthrough@*/ case STATE_START: /* read the header */ - if (!read_header(ws)) + 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: @@ -219,54 +321,40 @@ int websock_dispatch(struct websock *ws) 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) + if (!check_control_header(ws)) 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])) + if (!check_control_header(ws)) 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; + break; } /* update heading size */ switch (FRAME_GET_PAYLOAD_LEN(ws->header[1])) { case 127: ws->szhead += 6; + /*@fallthrough@*/ case 126: ws->szhead += 2; + /*@fallthrough@*/ default: ws->szhead += 4 * FRAME_GET_MASK(ws->header[1]); } ws->state = STATE_LENGTH; + /*@fallthrough@*/ case STATE_LENGTH: /* continue to read the header */ - if (!read_header(ws)) + if (read_header(ws)) return -1; else if (ws->lenhead < ws->szhead) return 0; - /* compute header values */ + + /* compute length */ switch (FRAME_GET_PAYLOAD_LEN(ws->header[1])) { case 127: ws->length = (((uint64_t) ws->header[2]) << 56) @@ -286,8 +374,12 @@ int websock_dispatch(struct websock *ws) ws->length = FRAME_GET_PAYLOAD_LEN(ws->header[1]); break; } + if (FRAME_GET_OPCODE(ws->header[0]) == OPCODE_CLOSE && ws->length != 0) + ws->length -= 2; if (ws->length > ws->maxlength) goto too_long_error; + + /* compute mask */ 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]; @@ -295,34 +387,84 @@ int websock_dispatch(struct websock *ws) ((unsigned char *)&ws->mask)[3] = ws->header[ws->szhead - 1]; } else ws->mask = 0; + + /* all heading fields are known, process */ ws->state = STATE_DATA; + if (ws->itf->on_extension != NULL) { + if (ws->itf->on_extension(ws->closure, + FRAME_GET_FIN(ws->header[0]), + FRAME_GET_RSV1(ws->header[0]), + FRAME_GET_RSV2(ws->header[0]), + FRAME_GET_RSV3(ws->header[0]), + FRAME_GET_OPCODE(ws->header[0]), + (size_t) ws->length)) { + return 0; + } + } + + /* not an extension case */ + 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; + + /* handle */ 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); + if (!loop) + return 0; break; case OPCODE_TEXT: ws->itf->on_text(ws->closure, FRAME_GET_FIN(ws->header[0]), (size_t) ws->length); + if (!loop) + return 0; break; case OPCODE_BINARY: ws->itf->on_binary(ws->closure, FRAME_GET_FIN(ws->header[0]), (size_t) ws->length); + if (!loop) + return 0; 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); + if (ws->length == 0) + code = WEBSOCKET_CODE_NOT_SET; + else { + code = (uint16_t)(ws->header[ws->szhead - 2] & 0xff); + code = (uint16_t)(code << 8); + code = (uint16_t)(code | (uint16_t)(ws->header[ws->szhead - 1] & 0xff)); + } + ws->itf->on_close(ws->closure, code, (size_t) ws->length); return 0; + case OPCODE_PING: + if (ws->itf->on_ping) + ws->itf->on_ping(ws->closure, ws->length); + else { + websock_drop(ws); + websock_pong(ws, NULL, 0); + } + ws->state = STATE_INIT; + if (!loop) + return 0; + break; + case OPCODE_PONG: + if (ws->itf->on_pong) + ws->itf->on_pong(ws->closure, ws->length); + else + websock_drop(ws); + ws->state = STATE_INIT; + if (!loop) + return 0; + break; + default: + goto protocol_error; } break; @@ -331,28 +473,57 @@ int websock_dispatch(struct websock *ws) 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); + websock_error(ws, WEBSOCKET_CODE_MESSAGE_TOO_LARGE, NULL, 0); return 0; protocol_error: - websock_close_code(ws, STATUS_CODE_PROTOCOL_ERROR); + websock_error(ws, WEBSOCKET_CODE_PROTOCOL_ERROR, NULL, 0); return 0; } -ssize_t websock_read(struct websock * ws, void *buffer, size_t size) +static void unmask(struct websock * ws, void *buffer, size_t size) { uint32_t mask, *b32; uint8_t m, *b8; + + 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++ ^= mask; + 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; +} + +ssize_t websock_read(struct websock * ws, void *buffer, size_t size) +{ ssize_t rc; - if (ws->state != STATE_DATA && ws->state != STATE_CLOSED) + if (ws->state != STATE_DATA) return 0; if (size > ws->length) @@ -363,52 +534,29 @@ ssize_t websock_read(struct websock * ws, void *buffer, size_t size) 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++ ^= mask; - 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; - } + if (ws->mask != 0) + unmask(ws, buffer, size); } return rc; } -void websock_drop(struct websock *ws) +int websock_drop(struct websock *ws) { - char buffer[4096]; + char buffer[8000]; - while (ws->length && ws_read(ws, buffer, sizeof buffer) >= 0) ; + while (ws->length) + if (websock_read(ws, buffer, sizeof buffer) < 0) + return -1; + return 0; } -struct websock *websock_create(const struct websock_itf *itf, void *closure) +struct websock *websock_create_v13(const struct websock_itf *itf, void *closure) { struct websock *result = calloc(1, sizeof *result); if (result) { result->itf = itf; result->closure = closure; + result->maxlength = default_maxlength; } return result; } @@ -417,3 +565,34 @@ void websock_destroy(struct websock *ws) { free(ws); } + +void websock_set_default_max_length(size_t maxlen) +{ + default_maxlength = maxlen; +} + +void websock_set_max_length(struct websock *ws, size_t maxlen) +{ + ws->maxlength = (uint64_t)maxlen; +} + +const char *websocket_explain_error(uint16_t code) +{ + static const char *msgs[] = { + "OK", /* 1000 */ + "GOING_AWAY", /* 1001 */ + "PROTOCOL_ERROR", /* 1002 */ + "CANT_ACCEPT", /* 1003 */ + "RESERVED", /* 1004 */ + "NOT_SET", /* 1005 */ + "ABNORMAL", /* 1006 */ + "INVALID_UTF8", /* 1007 */ + "POLICY_VIOLATION", /* 1008 */ + "MESSAGE_TOO_LARGE", /* 1009 */ + "EXPECT_EXTENSION", /* 1010 */ + "INTERNAL_ERROR", /* 1011 */ + }; + if (code < 1000 || (code - 1000) >= (sizeof msgs / sizeof *msgs)) + return "?"; + return msgs[code - 1000]; +}