From 59e7f85ca0bbfca266cf85b682d81c9becf78549 Mon Sep 17 00:00:00 2001 From: Jianhui Zhao Date: Tue, 28 Aug 2018 21:11:04 +0800 Subject: [PATCH] Refactoring with libev Signed-off-by: Jianhui Zhao --- cmake/Modules/FindCyaSSL.cmake | 62 ++++ cmake/Modules/FindLibev.cmake | 29 ++ cmake/Modules/FindLibubox.cmake | 17 - cmake/Modules/FindMbedTLS.cmake | 37 ++ example/helloworld.c | 86 +++-- src/CMakeLists.txt | 48 ++- src/base64.c | 285 +++++++++++++++ src/base64.h | 32 ++ src/buffer.c | 307 ++++++++++++++++ src/buffer.h | 243 +++++++++++++ src/config.h.in | 12 +- src/log.c | 98 ++++- src/log.h | 13 +- src/sha1.c | 223 ++++++++++++ src/sha1.h | 37 ++ src/ssl.c | 196 ++++++++++ src/ssl.h | 41 +++ src/utils.c | 93 +++-- src/utils.h | 19 +- src/uwsc.c | 608 ++++++++++++++++---------------- src/uwsc.h | 106 +++--- 21 files changed, 2125 insertions(+), 467 deletions(-) create mode 100644 cmake/Modules/FindCyaSSL.cmake create mode 100644 cmake/Modules/FindLibev.cmake delete mode 100644 cmake/Modules/FindLibubox.cmake create mode 100644 cmake/Modules/FindMbedTLS.cmake create mode 100644 src/base64.c create mode 100644 src/base64.h create mode 100644 src/buffer.c create mode 100644 src/buffer.h create mode 100644 src/sha1.c create mode 100644 src/sha1.h create mode 100644 src/ssl.c create mode 100644 src/ssl.h diff --git a/cmake/Modules/FindCyaSSL.cmake b/cmake/Modules/FindCyaSSL.cmake new file mode 100644 index 0000000..a377734 --- /dev/null +++ b/cmake/Modules/FindCyaSSL.cmake @@ -0,0 +1,62 @@ +if(CYASSL_PREFER_STATIC_LIB) + set(CYASSL_ORIG_CMAKE_FIND_LIBRARY_SUFFIXES ${CMAKE_FIND_LIBRARY_SUFFIXES}) + if(WIN32) + set(CMAKE_FIND_LIBRARY_SUFFIXES .a .lib ${CMAKE_FIND_LIBRARY_SUFFIXES}) + else() + set(CMAKE_FIND_LIBRARY_SUFFIXES .a ${CMAKE_FIND_LIBRARY_SUFFIXES}) + endif() +endif() + +if(UNIX) + find_package(PkgConfig QUIET) + pkg_check_modules(_WOLFSSL QUIET wolfssl) +endif() + +find_path(WOLFSSL_INCLUDE_DIR NAMES wolfssl/version.h HINTS ${_WOLFSSL_INCLUDEDIR}) +find_library(WOLFSSL_LIBRARY NAMES wolfssl HINTS ${_WOLFSSL_LIBDIR}) +if(WOLFSSL_INCLUDE_DIR AND WOLFSSL_LIBRARY) + set(CYASSL_INCLUDE_DIR ${WOLFSSL_INCLUDE_DIR}) + set(CYASSL_LIBRARY ${WOLFSSL_LIBRARY}) + set(CYASSL_VERSION ${_WOLFSSL_VERSION}) + set(CYASSL_IS_WOLFSSL ON) +else() + if(UNIX) + pkg_check_modules(_CYASSL QUIET cyassl) + endif() + + find_path(CYASSL_INCLUDE_DIR NAMES cyassl/version.h HINTS ${_CYASSL_INCLUDEDIR}) + find_library(CYASSL_LIBRARY NAMES cyassl HINTS ${_CYASSL_LIBDIR}) + set(CYASSL_VERSION ${_CYASSL_VERSION}) + set(CYASSL_IS_WOLFSSL OFF) +endif() + +if(NOT CYASSL_VERSION AND CYASSL_INCLUDE_DIR) + if(CYASSL_IS_WOLFSSL) + file(STRINGS "${CYASSL_INCLUDE_DIR}/wolfssl/version.h" CYASSL_VERSION_STR REGEX "^#define[\t ]+LIBWOLFSSL_VERSION_STRING[\t ]+\"[^\"]+\"") + else() + file(STRINGS "${CYASSL_INCLUDE_DIR}/cyassl/version.h" CYASSL_VERSION_STR REGEX "^#define[\t ]+LIBCYASSL_VERSION_STRING[\t ]+\"[^\"]+\"") + endif() + if(CYASSL_VERSION_STR MATCHES "\"([^\"]+)\"") + set(CYASSL_VERSION "${CMAKE_MATCH_1}") + endif() +endif() + +set(CYASSL_INCLUDE_DIRS ${CYASSL_INCLUDE_DIR}) +set(CYASSL_LIBRARIES ${CYASSL_LIBRARY}) + +include(FindPackageHandleStandardArgs) + +find_package_handle_standard_args(CyaSSL + REQUIRED_VARS + CYASSL_LIBRARY + CYASSL_INCLUDE_DIR + VERSION_VAR + CYASSL_VERSION +) + +mark_as_advanced(WOLFSSL_INCLUDE_DIR WOLFSSL_LIBRARY CYASSL_INCLUDE_DIR CYASSL_LIBRARY) + +if(CYASSL_PREFER_STATIC_LIB) + set(CMAKE_FIND_LIBRARY_SUFFIXES ${CYASSL_ORIG_CMAKE_FIND_LIBRARY_SUFFIXES}) + unset(CYASSL_ORIG_CMAKE_FIND_LIBRARY_SUFFIXES) +endif() \ No newline at end of file diff --git a/cmake/Modules/FindLibev.cmake b/cmake/Modules/FindLibev.cmake new file mode 100644 index 0000000..9a75d42 --- /dev/null +++ b/cmake/Modules/FindLibev.cmake @@ -0,0 +1,29 @@ +# - Try to find libev +# Once done this will define +# LIBEV_FOUND - System has libev +# LIBEV_INCLUDE_DIR - The libev include directories +# LIBEV_LIBRARY - The libraries needed to use libev + +find_path(LIBEV_INCLUDE_DIR ev.h) +find_library(LIBEV_LIBRARY ev PATH_SUFFIXES lib64) + +if(LIBEV_INCLUDE_DIR) + file(STRINGS "${LIBEV_INCLUDE_DIR}/ev.h" + LIBEV_VERSION_MAJOR REGEX "^#define[ \t]+EV_VERSION_MAJOR[ \t]+[0-9]+") + file(STRINGS "${LIBEV_INCLUDE_DIR}/ev.h" + LIBEV_VERSION_MINOR REGEX "^#define[ \t]+EV_VERSION_MINOR[ \t]+[0-9]+") + string(REGEX REPLACE "[^0-9]+" "" LIBEV_VERSION_MAJOR "${LIBEV_VERSION_MAJOR}") + string(REGEX REPLACE "[^0-9]+" "" LIBEV_VERSION_MINOR "${LIBEV_VERSION_MINOR}") + set(LIBEV_VERSION "${LIBEV_VERSION_MAJOR}.${LIBEV_VERSION_MINOR}") + unset(LIBEV_VERSION_MINOR) + unset(LIBEV_VERSION_MAJOR) +endif() + +include(FindPackageHandleStandardArgs) +# handle the QUIETLY and REQUIRED arguments and set LIBEV_FOUND to TRUE +# if all listed variables are TRUE and the requested version matches. +find_package_handle_standard_args(Libev REQUIRED_VARS + LIBEV_LIBRARY LIBEV_INCLUDE_DIR + VERSION_VAR LIBEV_VERSION) + +mark_as_advanced(LIBEV_INCLUDE_DIR LIBEV_LIBRARY) diff --git a/cmake/Modules/FindLibubox.cmake b/cmake/Modules/FindLibubox.cmake deleted file mode 100644 index 428a8e2..0000000 --- a/cmake/Modules/FindLibubox.cmake +++ /dev/null @@ -1,17 +0,0 @@ -# - Try to find libubox -# Once done this will define -# LIBUBOX_FOUND - System has libubox -# LIBUBOX_INCLUDE_DIR - The libubox include directories -# LIBUBOX_LIBRARY - The libraries needed to use libubox - -find_path(LIBUBOX_INCLUDE_DIR libubox) -find_library(LIBUBOX_LIBRARY ubox PATH_SUFFIXES lib64) - -include(FindPackageHandleStandardArgs) -# handle the QUIETLY and REQUIRED arguments and set LIBUBOX_FOUND to TRUE -# if all listed variables are TRUE and the requested version matches. -find_package_handle_standard_args(Libubox REQUIRED_VARS - LIBUBOX_LIBRARY LIBUBOX_INCLUDE_DIR - VERSION_VAR LIBUBOX_VERSION) - -mark_as_advanced(LIBUBOX_INCLUDE_DIR LIBUBOX_LIBRARY) diff --git a/cmake/Modules/FindMbedTLS.cmake b/cmake/Modules/FindMbedTLS.cmake new file mode 100644 index 0000000..266f6c1 --- /dev/null +++ b/cmake/Modules/FindMbedTLS.cmake @@ -0,0 +1,37 @@ +find_path(MBEDTLS_INCLUDE_DIR mbedtls/ssl.h) + +find_library(MBEDTLS_LIBRARY mbedtls) +find_library(MBEDX509_LIBRARY mbedx509) +find_library(MBEDCRYPTO_LIBRARY mbedcrypto) + +if(MBEDTLS_INCLUDE_DIR) + file(STRINGS "${MBEDTLS_INCLUDE_DIR}/mbedtls/version.h" + MBEDTLS_VERSION_MAJOR REGEX "^#define[ \t]+MBEDTLS_VERSION_MAJOR[ \t]+[0-9]+") + file(STRINGS "${MBEDTLS_INCLUDE_DIR}/mbedtls/version.h" + MBEDTLS_VERSION_MINOR REGEX "^#define[ \t]+MBEDTLS_VERSION_MINOR[ \t]+[0-9]+") + file(STRINGS "${MBEDTLS_INCLUDE_DIR}/mbedtls/version.h" + MBEDTLS_VERSION_PATCH REGEX "^#define[ \t]+MBEDTLS_VERSION_PATCH[ \t]+[0-9]+") + string(REGEX REPLACE "[^0-9]+" "" MBEDTLS_VERSION_MAJOR "${MBEDTLS_VERSION_MAJOR}") + string(REGEX REPLACE "[^0-9]+" "" MBEDTLS_VERSION_MINOR "${MBEDTLS_VERSION_MINOR}") + string(REGEX REPLACE "[^0-9]+" "" MBEDTLS_VERSION_PATCH "${MBEDTLS_VERSION_PATCH}") + set(MBEDTLS_VERSION "${MBEDTLS_VERSION_MAJOR}.${MBEDTLS_VERSION_MINOR}.${MBEDTLS_VERSION_PATCH}") + unset(MBEDTLS_VERSION_MINOR) + unset(MBEDTLS_VERSION_MAJOR) + unset(MBEDTLS_VERSION_PATCH) +endif() + +set(MBEDTLS_LIBRARIES "${MBEDTLS_LIBRARY}" "${MBEDX509_LIBRARY}" "${MBEDCRYPTO_LIBRARY}") + +include(FindPackageHandleStandardArgs) + +find_package_handle_standard_args(MbedTLS + REQUIRED_VARS + MBEDTLS_LIBRARY + MBEDX509_LIBRARY + MBEDCRYPTO_LIBRARY + MBEDTLS_INCLUDE_DIR + VERSION_VAR + MBEDTLS_VERSION +) + +mark_as_advanced(MBEDTLS_INCLUDE_DIR MBEDTLS_LIBRARY MBEDX509_LIBRARY MBEDCRYPTO_LIBRARY) \ No newline at end of file diff --git a/example/helloworld.c b/example/helloworld.c index 8c1fe0b..8accbdd 100644 --- a/example/helloworld.c +++ b/example/helloworld.c @@ -17,67 +17,80 @@ * USA */ -#include #include #include #include #include -struct uwsc_client *gcl; -struct uloop_fd fd; +#include "uwsc.h" -void fd_handler(struct uloop_fd *u, unsigned int events) +static void stdin_read_cb(struct ev_loop *loop, struct ev_io *w, int revents) { + struct uwsc_client *cl = w->data; char buf[128] = ""; int n; - n = read(u->fd, buf, sizeof(buf)); + n = read(w->fd, buf, sizeof(buf)); if (n > 1) { buf[n - 1] = 0; printf("You input:[%s]\n", buf); - /* Note: the buf to be sent will be modified by the send function */ - gcl->send(gcl, buf, strlen(buf) + 1, WEBSOCKET_OP_TEXT); + cl->send(cl, buf, strlen(buf) + 1, UWSC_OP_TEXT); } } static void uwsc_onopen(struct uwsc_client *cl) { + static struct ev_io stdin_watcher; + uwsc_log_info("onopen\n"); - fd.fd = STDIN_FILENO; - fd.cb = fd_handler; - uloop_fd_add(&fd, ULOOP_READ); + stdin_watcher.data = cl; + + ev_io_init(&stdin_watcher, stdin_read_cb, STDIN_FILENO, EV_READ); + ev_io_start(cl->loop, &stdin_watcher); /* Send Ping per 10s */ cl->set_ping_interval(cl, 10); } -static void uwsc_onmessage(struct uwsc_client *cl, void *data, uint64_t len, enum websocket_op op) +static void uwsc_onmessage(struct uwsc_client *cl, void *data, size_t len, bool binary) { - if (op == WEBSOCKET_OP_BINARY) { - uint64_t i; + printf("Recv:"); + + if (binary) { + size_t i; uint8_t *p = data; + for (i = 0; i < len; i++) { printf("%02hhX ", p[i]); if (i % 16 == 0 && i > 0) puts(""); } puts(""); - } else if (op == WEBSOCKET_OP_TEXT) { - printf("recv:[%s]\n", (char *)data); + } else { + printf("[%.*s]\n", (int)len, (char *)data); } } -static void uwsc_onerror(struct uwsc_client *cl) +static void uwsc_onerror(struct uwsc_client *cl, int err, const char *msg) +{ + uwsc_log_info("onerror:%d: %s\n", err, msg); + ev_break(cl->loop, EVBREAK_ALL); +} + +static void uwsc_onclose(struct uwsc_client *cl, int code, const char *reason) { - uwsc_log_info("onerror:%d\n", cl->error); + uwsc_log_info("onclose:%d: %s\n", code, reason); + ev_break(cl->loop, EVBREAK_ALL); } -static void uwsc_onclose(struct uwsc_client *cl) +static void signal_cb(struct ev_loop *loop, ev_signal *w, int revents) { - uwsc_log_info("onclose\n"); - uloop_end(); + if (w->signum == SIGINT) { + ev_break(loop, EVBREAK_ALL); + uwsc_log_info("Normal quit\n"); + } } static void usage(const char *prog) @@ -94,14 +107,16 @@ static void usage(const char *prog) int main(int argc, char **argv) { int opt; + struct uwsc_client *cl; + struct ev_loop *loop = EV_DEFAULT; const char *url = "ws://localhost:8080/ws"; const char *crt_file = NULL; bool verify = true; bool verbose = false; + struct ev_signal signal_watcher; while ((opt = getopt(argc, argv, "u:nc:v")) != -1) { - switch (opt) - { + switch (opt) { case 'u': url = optarg; break; @@ -122,25 +137,22 @@ int main(int argc, char **argv) if (!verbose) uwsc_log_threshold(LOG_ERR); - uloop_init(); - - gcl = uwsc_new_ssl(url, crt_file, verify); - if (!gcl) { - uloop_done(); + cl = uwsc_new_ssl_v2(url, crt_file, verify, loop); + if (!cl) return -1; - } - gcl->onopen = uwsc_onopen; - gcl->onmessage = uwsc_onmessage; - gcl->onerror = uwsc_onerror; - gcl->onclose = uwsc_onclose; - - uloop_run(); + cl->onopen = uwsc_onopen; + cl->onmessage = uwsc_onmessage; + cl->onerror = uwsc_onerror; + cl->onclose = uwsc_onclose; + + ev_signal_init(&signal_watcher, signal_cb, SIGINT); + ev_signal_start(loop, &signal_watcher); - gcl->send(gcl, NULL, 0, WEBSOCKET_OP_CLOSE); - gcl->free(gcl); + ev_run(loop, 0); - uloop_done(); + cl->send(cl, NULL, 0, UWSC_OP_CLOSE); + free(cl); return 0; } diff --git a/src/CMakeLists.txt b/src/CMakeLists.txt index d5d6b16..f700320 100644 --- a/src/CMakeLists.txt +++ b/src/CMakeLists.txt @@ -7,18 +7,52 @@ set(UWSC_VERSION_MINOR 0) set(UWSC_VERSION_PATCH 5) # Check the third party Libraries -find_package(Libubox REQUIRED) +find_package(Libev REQUIRED) -include_directories(${CMAKE_CURRENT_BINARY_DIR} ${LIBUBOX_INCLUDE_DIR}) +include_directories(${CMAKE_CURRENT_BINARY_DIR} ${LIBEV_INCLUDE_DIR}) -set(EXTRA_LIBS ${LIBUBOX_LIBRARY} dl) -set(SOURCE_FILES uwsc.c log.c utils.c) +set(EXTRA_LIBS ${LIBEV_LIBRARY} dl) +set(SOURCE_FILES uwsc.c log.c utils.c buffer.c sha1.c base64.c ssl.c) set(UWSC_SSL_SUPPORT_CONFIG 1) option(UWSC_SSL_SUPPORT "SSL support" ON) +set(UWSC_SSL_NAME OFF) +set(UWSC_HAVE_OPENSSL_CONFIG 0) +set(UWSC_HAVE_CYASSL_CONFIG 0) +set(UWSC_HAVE_MBEDTLS_CONFIG 0) + if(NOT UWSC_SSL_SUPPORT) set(UWSC_SSL_SUPPORT_CONFIG 0) +else() + find_package(OpenSSL) + find_package(CyaSSL) + find_package(MbedTLS) + + if(OPENSSL_FOUND) + set(UWSC_HAVE_OPENSSL_CONFIG 1) + include_directories(${OPENSSL_INCLUDE_DIR}) + list(APPEND EXTRA_LIBS ${OPENSSL_LIBRARIES}) + set(UWSC_SSL_NAME "OpenSSL") + elseif(CYASSL_FOUND) + set(UWSC_HAVE_CYASSL_CONFIG 1) + include_directories(${CYASSL_INCLUDE_DIR}) + list(APPEND EXTRA_LIBS ${CYASSL_LIBRARIES}) + set(UWSC_SSL_NAME "WolfSSL(CyaSSL)") + elseif(MBEDTLS_FOUND) + set(UWSC_HAVE_MBEDTLS_CONFIG 1) + include_directories(${MBEDTLS_INCLUDE_DIR}) + list(APPEND EXTRA_LIBS ${MBEDTLS_LIBRARIES}) + set(UWSC_SSL_NAME "MbedTLS(PolarSSL)") + else() + set(UWSC_SSL_SUPPORT OFF) + set(UWSC_SSL_SUPPORT_CONFIG 0) + message(WARNING "No available SSL libraries found") + endif() + + if(UWSC_SSL_NAME) + message(STATUS "Select ${UWSC_SSL_NAME} as the SSL backend") + endif() endif() add_library(uwsc SHARED ${SOURCE_FILES}) @@ -33,6 +67,8 @@ install( ${CMAKE_CURRENT_BINARY_DIR}/config.h ${CMAKE_CURRENT_SOURCE_DIR}/log.h ${CMAKE_CURRENT_SOURCE_DIR}/uwsc.h + ${CMAKE_CURRENT_SOURCE_DIR}/buffer.h + ${CMAKE_CURRENT_SOURCE_DIR}/ssl.h DESTINATION include/uwsc ) @@ -42,7 +78,5 @@ install( DESTINATION lib ) -message("") message(STATUS "UWSC_VERSION: ${UWSC_VERSION_MAJOR}.${UWSC_VERSION_MINOR}.${UWSC_VERSION_PATCH}") -message(STATUS "UWSC_SSL_SUPPORT: ${UWSC_SSL_SUPPORT}") -message("") +message(STATUS "UWSC_SSL_SUPPORT: ${UWSC_SSL_NAME}") diff --git a/src/base64.c b/src/base64.c new file mode 100644 index 0000000..0667247 --- /dev/null +++ b/src/base64.c @@ -0,0 +1,285 @@ +/* + * Modified from libubox(https://git.openwrt.org/?p=project/libubox.git) + * + * Copyright (C) 2017 Jianhui Zhao + * + * This program is free software; you can redistribute it and/or + * modify it under the terms of the GNU Lesser General Public + * License as published by the Free Software Foundation; either + * version 2.1 of the License, or (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + * Lesser General Public License for more details. + * + * You should have received a copy of the GNU Lesser General Public + * License along with this library; if not, write to the Free Software + * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 + * USA + */ + +#include +#include +#include +#include +#include + +static const char Base64[] = + "ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789+/"; +static const char Pad64 = '='; + +/* (From RFC1521 and draft-ietf-dnssec-secext-03.txt) + The following encoding technique is taken from RFC 1521 by Borenstein + and Freed. It is reproduced here in a slightly edited form for + convenience. + + A 65-character subset of US-ASCII is used, enabling 6 bits to be + represented per printable character. (The extra 65th character, "=", + is used to signify a special processing function.) + + The encoding process represents 24-bit groups of input bits as output + strings of 4 encoded characters. Proceeding from left to right, a + 24-bit input group is formed by concatenating 3 8-bit input groups. + These 24 bits are then treated as 4 concatenated 6-bit groups, each + of which is translated into a single digit in the base64 alphabet. + + Each 6-bit group is used as an index into an array of 64 printable + characters. The character referenced by the index is placed in the + output string. + + Table 1: The Base64 Alphabet + + Value Encoding Value Encoding Value Encoding Value Encoding + 0 A 17 R 34 i 51 z + 1 B 18 S 35 j 52 0 + 2 C 19 T 36 k 53 1 + 3 D 20 U 37 l 54 2 + 4 E 21 V 38 m 55 3 + 5 F 22 W 39 n 56 4 + 6 G 23 X 40 o 57 5 + 7 H 24 Y 41 p 58 6 + 8 I 25 Z 42 q 59 7 + 9 J 26 a 43 r 60 8 + 10 K 27 b 44 s 61 9 + 11 L 28 c 45 t 62 + + 12 M 29 d 46 u 63 / + 13 N 30 e 47 v + 14 O 31 f 48 w (pad) = + 15 P 32 g 49 x + 16 Q 33 h 50 y + + Special processing is performed if fewer than 24 bits are available + at the end of the data being encoded. A full encoding quantum is + always completed at the end of a quantity. When fewer than 24 input + bits are available in an input group, zero bits are added (on the + right) to form an integral number of 6-bit groups. Padding at the + end of the data is performed using the '=' character. + + Since all base64 input is an integral number of octets, only the + ------------------------------------------------- + following cases can arise: + + (1) the final quantum of encoding input is an integral + multiple of 24 bits; here, the final unit of encoded + output will be an integral multiple of 4 characters + with no "=" padding, + (2) the final quantum of encoding input is exactly 8 bits; + here, the final unit of encoded output will be two + characters followed by two "=" padding characters, or + (3) the final quantum of encoding input is exactly 16 bits; + here, the final unit of encoded output will be three + characters followed by one "=" padding character. + */ + +int b64_encode(const void *_src, size_t srclength, + void *dest, size_t targsize) +{ + const unsigned char *src = _src; + char *target = dest; + size_t datalength = 0; + uint8_t input[3] = {0}; + uint8_t output[4]; + int i; + + while (2 < srclength) { + input[0] = *src++; + input[1] = *src++; + input[2] = *src++; + srclength -= 3; + + output[0] = input[0] >> 2; + output[1] = ((input[0] & 0x03) << 4) + (input[1] >> 4); + output[2] = ((input[1] & 0x0f) << 2) + (input[2] >> 6); + output[3] = input[2] & 0x3f; + + if (datalength + 4 > targsize) + return (-1); + target[datalength++] = Base64[output[0]]; + target[datalength++] = Base64[output[1]]; + target[datalength++] = Base64[output[2]]; + target[datalength++] = Base64[output[3]]; + } + + /* Now we worry about padding. */ + if (0 != srclength) { + /* Get what's left. */ + input[0] = input[1] = input[2] = '\0'; + for (i = 0; i < srclength; i++) + input[i] = *src++; + + output[0] = input[0] >> 2; + output[1] = ((input[0] & 0x03) << 4) + (input[1] >> 4); + output[2] = ((input[1] & 0x0f) << 2) + (input[2] >> 6); + + if (datalength + 4 > targsize) + return (-1); + target[datalength++] = Base64[output[0]]; + target[datalength++] = Base64[output[1]]; + if (srclength == 1) + target[datalength++] = Pad64; + else + target[datalength++] = Base64[output[2]]; + target[datalength++] = Pad64; + } + if (datalength >= targsize) + return (-1); + target[datalength] = '\0'; /* Returned value doesn't count \0. */ + return (datalength); +} + +/* skips all whitespace anywhere. + converts characters, four at a time, starting at (or after) + src from base - 64 numbers into three 8 bit bytes in the target area. + it returns the number of data bytes stored at the target, or -1 on error. + */ + +int b64_decode(const void *_src, void *dest, size_t targsize) +{ + const char *src = _src; + unsigned char *target = dest; + int tarindex, state, ch; + uint8_t nextbyte; + char *pos; + + state = 0; + tarindex = 0; + + while ((ch = (unsigned char)*src++) != '\0') { + if (isspace(ch)) /* Skip whitespace anywhere. */ + continue; + + if (ch == Pad64) + break; + + pos = strchr(Base64, ch); + if (pos == 0) /* A non-base64 character. */ + return (-1); + + switch (state) { + case 0: + if (target) { + if (tarindex >= targsize) + return (-1); + target[tarindex] = (pos - Base64) << 2; + } + state = 1; + break; + case 1: + if (target) { + if (tarindex >= targsize) + return (-1); + target[tarindex] |= (pos - Base64) >> 4; + nextbyte = ((pos - Base64) & 0x0f) << 4; + if (tarindex + 1 < targsize) + target[tarindex+1] = nextbyte; + else if (nextbyte) + return (-1); + } + tarindex++; + state = 2; + break; + case 2: + if (target) { + if (tarindex >= targsize) + return (-1); + target[tarindex] |= (pos - Base64) >> 2; + nextbyte = ((pos - Base64) & 0x03) << 6; + if (tarindex + 1 < targsize) + target[tarindex+1] = nextbyte; + else if (nextbyte) + return (-1); + } + tarindex++; + state = 3; + break; + case 3: + if (target) { + if (tarindex >= targsize) + return (-1); + target[tarindex] |= (pos - Base64); + } + tarindex++; + state = 0; + break; + } + } + + /* + * We are done decoding Base-64 chars. Let's see if we ended + * on a byte boundary, and/or with erroneous trailing characters. + */ + + if (ch == Pad64) { /* We got a pad char. */ + ch = (unsigned char)*src++; /* Skip it, get next. */ + switch (state) { + case 0: /* Invalid = in first position */ + case 1: /* Invalid = in second position */ + return (-1); + + case 2: /* Valid, means one byte of info */ + /* Skip any number of spaces. */ + for (; ch != '\0'; ch = (unsigned char)*src++) + if (!isspace(ch)) + break; + /* Make sure there is another trailing = sign. */ + if (ch != Pad64) + return (-1); + ch = (unsigned char)*src++; /* Skip the = */ + /* Fall through to "single trailing =" case. */ + /* FALLTHROUGH */ + + case 3: /* Valid, means two bytes of info */ + /* + * We know this char is an =. Is there anything but + * whitespace after it? + */ + for (; ch != '\0'; ch = (unsigned char)*src++) + if (!isspace(ch)) + return (-1); + + /* + * Now make sure for cases 2 and 3 that the "extra" + * bits that slopped past the last full byte were + * zeros. If we don't check them, they become a + * subliminal channel. + */ + if (target && tarindex < targsize && + target[tarindex] != 0) + return (-1); + } + } else { + /* + * We ended by seeing the end of the string. Make sure we + * have no partial bytes lying around. + */ + if (state != 0) + return (-1); + } + + /* Null-terminate if we have room left */ + if (tarindex < targsize) + target[tarindex] = 0; + + return (tarindex); +} diff --git a/src/base64.h b/src/base64.h new file mode 100644 index 0000000..c6f247d --- /dev/null +++ b/src/base64.h @@ -0,0 +1,32 @@ +/* + * Modified from libubox(https://git.openwrt.org/?p=project/libubox.git) + * + * Copyright (C) 2017 Jianhui Zhao + * + * This program is free software; you can redistribute it and/or + * modify it under the terms of the GNU Lesser General Public + * License as published by the Free Software Foundation; either + * version 2.1 of the License, or (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + * Lesser General Public License for more details. + * + * You should have received a copy of the GNU Lesser General Public + * License along with this library; if not, write to the Free Software + * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 + * USA + */ + +#ifndef _BASE64_H +#define _BASE64_H + +#include + +int b64_encode(const void *_src, size_t srclength, + void *dest, size_t targsize); + +int b64_decode(const void *_src, void *dest, size_t targsize); + +#endif \ No newline at end of file diff --git a/src/buffer.c b/src/buffer.c new file mode 100644 index 0000000..8504260 --- /dev/null +++ b/src/buffer.c @@ -0,0 +1,307 @@ +/* + * Copyright (C) 2018 Jianhui Zhao + * + * This program is free software; you can redistribute it and/or + * modify it under the terms of the GNU Lesser General Public + * License as published by the Free Software Foundation; either + * version 2.1 of the License, or (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + * Lesser General Public License for more details. + * + * You should have received a copy of the GNU Lesser General Public + * License along with this library; if not, write to the Free Software + * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 + * USA + */ + +#ifndef _GNU_SOURCE +#define _GNU_SOURCE +#endif + +#include +#include +#include +#include +#include +#include +#include + +#include "buffer.h" + +static int buffer_resize(struct buffer *b, size_t size) +{ + uint8_t *head; + size_t new_size = getpagesize(); + int data_len = buffer_length(b); + + while (new_size < size) + new_size <<= 1; + + if (likely(b->head)) { + if (buffer_headroom(b) > 0) { + memmove(b->head, b->data, data_len); + b->data = b->head; + b->tail = b->data + data_len; + } + + head = realloc(b->head, new_size); + } else { + head = malloc(new_size); + } + + if (unlikely(!head)) + return -1; + + b->head = b->data = head; + b->tail = b->data + data_len; + b->end = b->head + new_size; + + if (unlikely(b->tail > b->end)) + b->tail = b->end; + + return 0; +} + +int buffer_init(struct buffer *b, size_t size) +{ + memset(b, 0, sizeof(struct buffer)); + + if (size) + return buffer_resize(b, size); + + return 0; +} + +void buffer_free(struct buffer *b) +{ + if (b->head) { + free(b->head); + memset(b, 0, sizeof(struct buffer)); + } +} + +static inline int buffer_grow(struct buffer *b, size_t len) +{ + return buffer_resize(b, buffer_size(b) + len); +} + +/** + * buffer_put - add data to a buffer + * @b: buffer to use + * @len: amount of data to add + * + * This function extends the used data area of the buffer. A pointer to the + * first byte of the extra data is returned. + * If this would exceed the total buffer size the buffer will grow automatically. + */ +void *buffer_put(struct buffer *b, size_t len) +{ + void *tmp; + + if (buffer_length(b) == 0) + b->tail = b->data = b->head; + + if (buffer_tailroom(b) < len && buffer_grow(b, len) < 0) + return NULL; + + tmp = b->tail; + b->tail += len; + return tmp; +} + +int buffer_put_vprintf(struct buffer *b, const char *fmt, va_list ap) +{ + for (;;) { + int ret; + va_list local_ap; + size_t tail_room = buffer_tailroom(b); + + va_copy(local_ap, ap); + ret = vsnprintf((char *)b->tail, tail_room, fmt, local_ap); + va_end(local_ap); + + if (ret < 0) + return -1; + + if (likely(ret < tail_room)) { + b->tail += ret; + return 0; + } + + if (unlikely(buffer_grow(b, 1) < 0)) + return -1; + } +} + +int buffer_put_printf(struct buffer *b, const char *fmt, ...) +{ + va_list ap; + int ret; + + va_start(ap, fmt); + ret = buffer_put_vprintf(b, fmt, ap); + va_end(ap); + + return ret; +} + +/* +* buffer_put_fd - Append data from a file to the end of a buffer +* @fd: file descriptor +* @len: how much data to read, or -1 to read as much as possible. +* @eof: indicates end of file +* @rd: A customized read function. Generally used for SSL. +* The customized read function should be return: +* P_FD_EOF/P_FD_ERR/P_FD_PENDING or number of bytes read. +* +* Return the number of bytes append +*/ +int buffer_put_fd(struct buffer *b, int fd, ssize_t len, bool *eof, + int (*rd)(int fd, void *buf, size_t count, void *arg), void *arg) +{ + ssize_t remain; + + if (len < 0) + len = INT_MAX; + + remain = len; + *eof = false; + + do { + ssize_t ret; + size_t tail_room = buffer_tailroom(b); + + if (unlikely(!tail_room)) { + if (buffer_grow(b, 1) < 0) + return -1; + tail_room = buffer_tailroom(b); + } + + if (rd) { + ret = rd(fd, b->tail, tail_room, arg); + if (ret == P_FD_ERR) + return -1; + else if (ret == P_FD_PENDING) + break; + } else { + ret = read(fd, b->tail, tail_room); + if (unlikely(ret < 0)) { + if (errno == EINTR) + continue; + + if (errno == EAGAIN || errno == ENOTCONN) + break; + + return -1; + } + } + + if (!ret) { + *eof = true; + break; + } + + b->tail += ret; + remain -= ret; + } while (remain); + + return len - remain; +} + +/** + * buffer_pull - remove data from the start of a buffer + * @b: buffer to use + * @len: amount of data to remove + * + * This function removes data from the start of a buffer, + * returning the actual length removed. + * Just remove the data if the dest is NULL. + */ +size_t buffer_pull(struct buffer *b, void *dest, size_t len) +{ + if (len > buffer_length(b)) + len = buffer_length(b); + + if (dest) + memcpy(dest, b->data, len); + + b->data += len; + + return len; +} + +/* +* buffer_pull_to_fd - remove data from the start of a buffer and write to a file +* @fd: file descriptor +* @len: how much data to remove, or -1 to remove as much as possible. +* @wr: A customized write function. Generally used for SSL. +* The customized write function should be return: +* P_FD_EOF/P_FD_ERR/P_FD_PENDING or number of bytes write. +* +* Return the number of bytes removed +*/ +int buffer_pull_to_fd(struct buffer *b, int fd, size_t len, + int (*wr)(int fd, void *buf, size_t count, void *arg), void *arg) +{ + ssize_t remain; + + if (len < 0) + len = INT_MAX; + + remain = len; + + do { + ssize_t ret; + size_t data_len = buffer_length(b); + + if (!data_len) + break; + + if (wr) { + ret = wr(fd, b->data, data_len, arg); + if (ret == P_FD_ERR) + return -1; + else if (ret == P_FD_PENDING) + break; + } else { + ret = write(fd, b->data, data_len); + if (ret < 0) { + if (errno == EINTR) + continue; + + if (errno == EAGAIN || errno == EWOULDBLOCK || errno == ENOTCONN) + break; + + return -1; + } + } + + remain -= ret; + b->data += ret; + } while (remain); + + return len - remain; +} + +void buffer_hexdump(struct buffer *b, size_t offset, size_t len) +{ + int i; + size_t data_len = buffer_length(b); + uint8_t *data = buffer_data(b); + + if (offset > data_len - 1) + return; + + if (len > data_len) + len = data_len; + + for (i = offset; i < len; i++) { + printf("%02X ", data[i]); + if (i && i % 16 == 0) + printf("\n"); + } + printf("\n"); +} \ No newline at end of file diff --git a/src/buffer.h b/src/buffer.h new file mode 100644 index 0000000..4d411fd --- /dev/null +++ b/src/buffer.h @@ -0,0 +1,243 @@ +/* + * Copyright (C) 2018 Jianhui Zhao + * + * This program is free software; you can redistribute it and/or + * modify it under the terms of the GNU Lesser General Public + * License as published by the Free Software Foundation; either + * version 2.1 of the License, or (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + * Lesser General Public License for more details. + * + * You should have received a copy of the GNU Lesser General Public + * License along with this library; if not, write to the Free Software + * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 + * USA + */ + +#ifndef _BUFFER_H +#define _BUFFER_H + +#include +#include +#include +#include +#include + +/* Test for GCC < 2.96 */ +#if __GNUC__ < 2 || (__GNUC__ == 2 && (__GNUC_MINOR__ < 96)) +#define __builtin_expect(x) (x) +#endif + +#ifndef likely +#define likely(x) __builtin_expect(!!(x), 1) +#endif + +#ifndef unlikely +#define unlikely(x) __builtin_expect(!!(x), 0) +#endif + +enum { + P_FD_EOF = 0, + P_FD_ERR = -1, + P_FD_PENDING = -2 +}; + +struct buffer { + uint8_t *head; /* Head of buffer */ + uint8_t *data; /* Data head pointer */ + uint8_t *tail; /* Data tail pointer */ + uint8_t *end; /* End of buffer */ +}; + +int buffer_init(struct buffer *b, size_t size); +void buffer_free(struct buffer *b); + +/* Actual data Length */ +static inline size_t buffer_length(const struct buffer *b) +{ + return b->tail - b->data; +} + +/* The total buffer size */ +static inline size_t buffer_size(const struct buffer *b) +{ + return b->end - b->head; +} + +static inline size_t buffer_headroom(const struct buffer *b) +{ + return b->data - b->head; +} + +static inline size_t buffer_tailroom(const struct buffer *b) +{ + return b->end - b->tail; +} + +static inline void *buffer_data(const struct buffer *b) +{ + return b->data; +} + +void *buffer_put(struct buffer *b, size_t len); + +static inline void *buffer_put_zero(struct buffer *b, size_t len) +{ + void *tmp = buffer_put(b, len); + + if (likely(tmp)) + memset(tmp, 0, len); + return tmp; +} + +static inline void *buffer_put_data(struct buffer *b, const void *data, size_t len) +{ + void *tmp = buffer_put(b, len); + + if (likely(tmp)) + memcpy(tmp, data, len); + return tmp; +} + + +static inline int buffer_put_u8(struct buffer *b, uint8_t val) +{ + uint8_t *p = buffer_put(b, 1); + + if (likely(p)) { + *p = val; + return 0; + } + + return -1; +} + +static inline int buffer_put_u16(struct buffer *b, uint16_t val) +{ + uint16_t *p = buffer_put(b, 2); + + if (likely(p)) { + *p = val; + return 0; + } + + return -1; +} + +static inline int buffer_put_u32(struct buffer *b, uint32_t val) +{ + uint32_t *p = buffer_put(b, 4); + + if (likely(p)) { + *p = val; + return 0; + } + + return -1; +} + +static inline int buffer_put_u64(struct buffer *b, uint64_t val) +{ + uint64_t *p = buffer_put(b, 8); + + if (likely(p)) { + *p = val; + return 0; + } + + return -1; +} + +static inline int buffer_put_string(struct buffer *b, const char *s) +{ + size_t len = strlen(s); + char *p = buffer_put(b, len); + + if (likely(p)) { + memcpy(p, s, len); + return 0; + } + + return -1; +} + +int buffer_put_vprintf(struct buffer *b, const char *fmt, va_list ap) __attribute__((format(printf, 2, 0))); +int buffer_put_printf(struct buffer *b, const char *fmt, ...) __attribute__((format(printf, 2, 3))); + +int buffer_put_fd(struct buffer *b, int fd, ssize_t len, bool *eof, + int (*rd)(int fd, void *buf, size_t count, void *arg), void *arg); + +/** + * buffer_truncate - remove end from a buffer + * @b: buffer to alter + * @len: new length + * + * Cut the length of a buffer down by removing data from the tail. If + * the buffer is already under the length specified it is not modified. + */ +static inline void buffer_truncate(struct buffer *b, size_t len) +{ + if (buffer_length(b) > len) + b->tail = b->data + len; +} + + +size_t buffer_pull(struct buffer *b, void *dest, size_t len); + +static inline uint8_t buffer_pull_u8(struct buffer *b) +{ + uint8_t val = 0; + + if (likely(buffer_length(b) > 0)) { + val = b->data[0]; + b->data += 1; + } + + return val; +} + +static inline uint16_t buffer_pull_u16(struct buffer *b) +{ + uint16_t val = 0; + + if (likely(buffer_length(b) > 1)) { + val = *((uint16_t *)b->data); + b->data += 2; + } + + return val; +} + +static inline uint32_t buffer_pull_u32(struct buffer *b) +{ + uint32_t val = 0; + + if (likely(buffer_length(b) > 3)) { + val = *((uint32_t *)b->data); + b->data += 4; + } + + return val; +} + +static inline uint64_t buffer_pull_u64(struct buffer *b) +{ + uint64_t val = 0; + + if (likely(buffer_length(b) > 7)) { + val = *((uint64_t *)b->data); + b->data += 8; + } + + return val; +} + +int buffer_pull_to_fd(struct buffer *b, int fd, size_t len, + int (*wr)(int fd, void *buf, size_t count, void *arg), void *arg); + +void buffer_hexdump(struct buffer *b, size_t offset, size_t len); + +#endif diff --git a/src/config.h.in b/src/config.h.in index 6b8e3a0..8947254 100644 --- a/src/config.h.in +++ b/src/config.h.in @@ -20,11 +20,15 @@ #ifndef _UWSC_CONFIG_H #define _UWSC_CONFIG_H -#define UWSC_VERSION_MAJOR @UWSC_VERSION_MAJOR@ -#define UWSC_VERSION_MINOR @UWSC_VERSION_MINOR@ -#define UWSC_VERSION_PATCH @UWSC_VERSION_PATCH@ +#define UWSC_VERSION_MAJOR @UWSC_VERSION_MAJOR@ +#define UWSC_VERSION_MINOR @UWSC_VERSION_MINOR@ +#define UWSC_VERSION_PATCH @UWSC_VERSION_PATCH@ #define UWSC_VERSION_STRING "@UWSC_VERSION_MAJOR@.@UWSC_VERSION_MINOR@.@UWSC_VERSION_PATCH@" -#define UWSC_SSL_SUPPORT @UWSC_SSL_SUPPORT_CONFIG@ +#define UWSC_SSL_SUPPORT @UWSC_SSL_SUPPORT_CONFIG@ + +#define UWSC_HAVE_OPENSSL @UWSC_HAVE_OPENSSL_CONFIG@ +#define UWSC_HAVE_CYASSL @UWSC_HAVE_CYASSL_CONFIG@ +#define UWSC_HAVE_MBEDTLS @UWSC_HAVE_MBEDTLS_CONFIG@ #endif diff --git a/src/log.c b/src/log.c index c8f820d..7d4ca60 100644 --- a/src/log.c +++ b/src/log.c @@ -18,28 +18,98 @@ */ #include -#include #include -#include +#include +#include +#include +#include #include "log.h" -void __uwsc_log(const char *filename, int line, int priority, const char *fmt, ...) +static int log_threshold = LOG_DEBUG; +static bool log_initialized; +static const char *ident; + +void (*log_write)(int priority, const char *fmt, va_list ap); + +static const char *log_ident() { - va_list ap; - static char buf[128]; + FILE *self; + static char line[64]; + char *p = NULL; + char *sbuf; - snprintf(buf, sizeof(buf), "(%s:%d) ", filename, line); - - va_start(ap, fmt); - vsnprintf(buf + strlen(buf), sizeof(buf) - strlen(buf), fmt, ap); - va_end(ap); + if ((self = fopen("/proc/self/status", "r")) != NULL) { + while (fgets(line, sizeof(line), self)) { + if (!strncmp(line, "Name:", 5)) { + strtok_r(line, "\t\n", &sbuf); + p = strtok_r(NULL, "\t\n", &sbuf); + break; + } + } + fclose(self); + } + + return p; +} + +static inline void log_write_stdout(int priority, const char *fmt, va_list ap) +{ + vfprintf(stderr, fmt, ap); +} + +static inline void log_write_syslog(int priority, const char *fmt, va_list ap) +{ + vsyslog(priority, fmt, ap); +} + +static inline void log_init() +{ + if (log_initialized) + return; + + ident = log_ident(); - if (priority == LOG_ERR && errno > 0) { - snprintf(buf + strlen(buf), sizeof(buf) - strlen(buf), ":%s", strerror(errno)); - errno = 0; + if (isatty(STDOUT_FILENO)) { + log_write = log_write_stdout; + } else { + log_write = log_write_syslog; + + openlog(ident, 0, LOG_DAEMON); } - ulog(priority, "%s\n", buf); + log_initialized = true; +} + + +void uwsc_log_threshold(int threshold) +{ + log_threshold = threshold; +} + +void uwsc_log_close() +{ + if (!log_initialized) + return; + + closelog(); + + log_initialized = 0; } +void __uwsc_log(const char *filename, int line, int priority, const char *fmt, ...) +{ + static char new_fmt[256]; + va_list ap; + + if (priority > log_threshold) + return; + + log_init(); + + snprintf(new_fmt, sizeof(new_fmt), "(%s:%d) %s", filename, line, fmt); + + va_start(ap, fmt); + log_write(priority, new_fmt, ap); + va_end(ap); +} diff --git a/src/log.h b/src/log.h index cf24886..8f28002 100644 --- a/src/log.h +++ b/src/log.h @@ -17,20 +17,17 @@ * USA */ -#ifndef _LOG_H -#define _LOG_H +#ifndef _UWSC_LOG_H +#define _UWSC_LOG_H +#include #include -#include +void uwsc_log_threshold(int threshold); +void uwsc_log_close(); #define __FILENAME__ (strrchr(__FILE__, '/') ? strrchr(__FILE__, '/') + 1 : __FILE__) -#define uwsc_log_threshold(priority) ulog_threshold(priority) - -/* - * Use the syslog output log and include the name and number of rows at the call - */ #define uwsc_log(priority, fmt...) __uwsc_log(__FILENAME__, __LINE__, priority, fmt) #define uwsc_log_debug(fmt...) uwsc_log(LOG_DEBUG, fmt) diff --git a/src/sha1.c b/src/sha1.c new file mode 100644 index 0000000..48d9f32 --- /dev/null +++ b/src/sha1.c @@ -0,0 +1,223 @@ +/* + * Modified from mongoose(https://github.com/cesanta/mongoose) + * + * Copyright (C) 2017 Jianhui Zhao + * + * This program is free software; you can redistribute it and/or + * modify it under the terms of the GNU Lesser General Public + * License as published by the Free Software Foundation; either + * version 2.1 of the License, or (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + * Lesser General Public License for more details. + * + * You should have received a copy of the GNU Lesser General Public + * License along with this library; if not, write to the Free Software + * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 + * USA + */ + +#include + +#include "sha1.h" + +union char64long16 { + uint8_t c[64]; + uint32_t l[16]; +}; + +#define rol(value, bits) (((value) << (bits)) | ((value) >> (32 - (bits)))) + +static uint32_t blk0(union char64long16 *block, int i) +{ + /* Forrest: SHA expect BIG_ENDIAN, swap if LITTLE_ENDIAN */ +#if BYTE_ORDER == LITTLE_ENDIAN + block->l[i] = (rol(block->l[i], 24) & 0xFF00FF00) | (rol(block->l[i], 8) & 0x00FF00FF); +#endif + return block->l[i]; +} + +#define blk(i) \ + (block->l[i & 15] = rol(block->l[(i + 13) & 15] ^ block->l[(i + 8) & 15] ^ \ + block->l[(i + 2) & 15] ^ block->l[i & 15], \ + 1)) + +#define R0(v, w, x, y, z, i) \ + z += ((w & (x ^ y)) ^ y) + blk0(block, i) + 0x5A827999 + rol(v, 5); \ + w = rol(w, 30); +#define R1(v, w, x, y, z, i) \ + z += ((w & (x ^ y)) ^ y) + blk(i) + 0x5A827999 + rol(v, 5); \ + w = rol(w, 30); +#define R2(v, w, x, y, z, i) \ + z += (w ^ x ^ y) + blk(i) + 0x6ED9EBA1 + rol(v, 5); \ + w = rol(w, 30); +#define R3(v, w, x, y, z, i) \ + z += (((w | x) & y) | (w & x)) + blk(i) + 0x8F1BBCDC + rol(v, 5); \ + w = rol(w, 30); +#define R4(v, w, x, y, z, i) \ + z += (w ^ x ^ y) + blk(i) + 0xCA62C1D6 + rol(v, 5); \ + w = rol(w, 30); + +static void sha1_transform(uint32_t state[5], const uint8_t buffer[64]) +{ + uint32_t a, b, c, d, e; + union char64long16 block[1]; + + memcpy(block, buffer, 64); + a = state[0]; + b = state[1]; + c = state[2]; + d = state[3]; + e = state[4]; + R0(a, b, c, d, e, 0); + R0(e, a, b, c, d, 1); + R0(d, e, a, b, c, 2); + R0(c, d, e, a, b, 3); + R0(b, c, d, e, a, 4); + R0(a, b, c, d, e, 5); + R0(e, a, b, c, d, 6); + R0(d, e, a, b, c, 7); + R0(c, d, e, a, b, 8); + R0(b, c, d, e, a, 9); + R0(a, b, c, d, e, 10); + R0(e, a, b, c, d, 11); + R0(d, e, a, b, c, 12); + R0(c, d, e, a, b, 13); + R0(b, c, d, e, a, 14); + R0(a, b, c, d, e, 15); + R1(e, a, b, c, d, 16); + R1(d, e, a, b, c, 17); + R1(c, d, e, a, b, 18); + R1(b, c, d, e, a, 19); + R2(a, b, c, d, e, 20); + R2(e, a, b, c, d, 21); + R2(d, e, a, b, c, 22); + R2(c, d, e, a, b, 23); + R2(b, c, d, e, a, 24); + R2(a, b, c, d, e, 25); + R2(e, a, b, c, d, 26); + R2(d, e, a, b, c, 27); + R2(c, d, e, a, b, 28); + R2(b, c, d, e, a, 29); + R2(a, b, c, d, e, 30); + R2(e, a, b, c, d, 31); + R2(d, e, a, b, c, 32); + R2(c, d, e, a, b, 33); + R2(b, c, d, e, a, 34); + R2(a, b, c, d, e, 35); + R2(e, a, b, c, d, 36); + R2(d, e, a, b, c, 37); + R2(c, d, e, a, b, 38); + R2(b, c, d, e, a, 39); + R3(a, b, c, d, e, 40); + R3(e, a, b, c, d, 41); + R3(d, e, a, b, c, 42); + R3(c, d, e, a, b, 43); + R3(b, c, d, e, a, 44); + R3(a, b, c, d, e, 45); + R3(e, a, b, c, d, 46); + R3(d, e, a, b, c, 47); + R3(c, d, e, a, b, 48); + R3(b, c, d, e, a, 49); + R3(a, b, c, d, e, 50); + R3(e, a, b, c, d, 51); + R3(d, e, a, b, c, 52); + R3(c, d, e, a, b, 53); + R3(b, c, d, e, a, 54); + R3(a, b, c, d, e, 55); + R3(e, a, b, c, d, 56); + R3(d, e, a, b, c, 57); + R3(c, d, e, a, b, 58); + R3(b, c, d, e, a, 59); + R4(a, b, c, d, e, 60); + R4(e, a, b, c, d, 61); + R4(d, e, a, b, c, 62); + R4(c, d, e, a, b, 63); + R4(b, c, d, e, a, 64); + R4(a, b, c, d, e, 65); + R4(e, a, b, c, d, 66); + R4(d, e, a, b, c, 67); + R4(c, d, e, a, b, 68); + R4(b, c, d, e, a, 69); + R4(a, b, c, d, e, 70); + R4(e, a, b, c, d, 71); + R4(d, e, a, b, c, 72); + R4(c, d, e, a, b, 73); + R4(b, c, d, e, a, 74); + R4(a, b, c, d, e, 75); + R4(e, a, b, c, d, 76); + R4(d, e, a, b, c, 77); + R4(c, d, e, a, b, 78); + R4(b, c, d, e, a, 79); + state[0] += a; + state[1] += b; + state[2] += c; + state[3] += d; + state[4] += e; + /* Erase working structures. The order of operations is important, + * used to ensure that compiler doesn't optimize those out. */ + memset(block, 0, sizeof(block)); + a = b = c = d = e = 0; +} + +void sha1_init(struct sha1_ctx *ctx) +{ + ctx->state[0] = 0x67452301; + ctx->state[1] = 0xEFCDAB89; + ctx->state[2] = 0x98BADCFE; + ctx->state[3] = 0x10325476; + ctx->state[4] = 0xC3D2E1F0; + ctx->count[0] = ctx->count[1] = 0; +} + +void sha1_update(struct sha1_ctx *ctx, const void *data, size_t len) +{ + size_t i, j; + + j = ctx->count[0]; + ctx->count[0] += len << 3; + + if (ctx->count[0] < j) + ctx->count[1]++; + + ctx->count[1] += (len >> 29); + j = (j >> 3) & 63; + + if ((j + len) > 63) { + i = 64 - j; + memcpy(ctx->buffer + j, data, i); + sha1_transform(ctx->state, ctx->buffer); + for (; i + 63 < len; i += 64) + sha1_transform(ctx->state, data + i); + j = 0; + } else { + i = 0; + } + + memcpy(ctx->buffer + j, data + i, len - i); +} + +void sha1_final(struct sha1_ctx *ctx, uint8_t digest[20]) +{ + unsigned i; + unsigned char finalcount[8], c; + + for (i = 0; i < 8; i++) { + finalcount[i] = (unsigned char) ((ctx->count[(i >= 4 ? 0 : 1)] >> ((3 - (i & 3)) * 8)) & 255); + } + + c = 0200; + sha1_update(ctx, &c, 1); + while ((ctx->count[0] & 504) != 448) { + c = 0000; + sha1_update(ctx, &c, 1); + } + sha1_update(ctx, finalcount, 8); + for (i = 0; i < 20; i++) { + digest[i] = (unsigned char) ((ctx->state[i >> 2] >> ((3 - (i & 3)) * 8)) & 255); + } + memset(ctx, '\0', sizeof(*ctx)); + memset(&finalcount, '\0', sizeof(finalcount)); +} diff --git a/src/sha1.h b/src/sha1.h new file mode 100644 index 0000000..ac7a1c0 --- /dev/null +++ b/src/sha1.h @@ -0,0 +1,37 @@ +/* + * Modified from mongoose(https://github.com/cesanta/mongoose) + * + * Copyright (C) 2017 Jianhui Zhao + * + * This program is free software; you can redistribute it and/or + * modify it under the terms of the GNU Lesser General Public + * License as published by the Free Software Foundation; either + * version 2.1 of the License, or (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + * Lesser General Public License for more details. + * + * You should have received a copy of the GNU Lesser General Public + * License along with this library; if not, write to the Free Software + * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 + * USA + */ + +#ifndef _SHA1_H +#define _SHA1_H + +#include + +struct sha1_ctx { + uint32_t state[5]; + size_t count[2]; + uint8_t buffer[64]; +}; + +void sha1_init(struct sha1_ctx *ctx); +void sha1_update(struct sha1_ctx *ctx, const void *data, size_t len); +void sha1_final(struct sha1_ctx *ctx, uint8_t digest[20]); + +#endif \ No newline at end of file diff --git a/src/ssl.c b/src/ssl.c new file mode 100644 index 0000000..abb75b1 --- /dev/null +++ b/src/ssl.c @@ -0,0 +1,196 @@ +/* + * Copyright (C) 2017 Jianhui Zhao + * + * This program is free software; you can redistribute it and/or + * modify it under the terms of the GNU Lesser General Public + * License as published by the Free Software Foundation; either + * version 2.1 of the License, or (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + * Lesser General Public License for more details. + * + * You should have received a copy of the GNU Lesser General Public + * License along with this library; if not, write to the Free Software + * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 + * USA + */ + +#include + +#include "ssl.h" +#include "buffer.h" + +#if UWSC_SSL_SUPPORT + +#if UWSC_HAVE_MBEDTLS +#include +#include +#include +#include + +struct uwsc_ssl_ctx { + mbedtls_net_context net; + mbedtls_ssl_context ssl; + mbedtls_ssl_config cfg; + mbedtls_ctr_drbg_context drbg; + mbedtls_entropy_context etpy; + mbedtls_x509_crt x509; + bool last_read_ok; +}; + +#else + +#if UWSC_HAVE_OPENSSL +#include +#elif UWSC_HAVE_CYASSL +#define WC_NO_HARDEN +#include +#endif + +struct uwsc_ssl_ctx { + SSL_CTX *ctx; + SSL *ssl; +}; + +#endif + +int uwsc_ssl_init(struct uwsc_ssl_ctx **ctx, int sock) +{ + struct uwsc_ssl_ctx *c = calloc(1, sizeof(struct uwsc_ssl_ctx)); + + if (!ctx) { + uwsc_log_err("calloc failed: %s\n", strerror(errno)); + return -1; + } + +#if UWSC_HAVE_MBEDTLS + mbedtls_net_init(&c->net); + mbedtls_ssl_init(&c->ssl); + mbedtls_ssl_config_init(&c->cfg); + mbedtls_ctr_drbg_init(&c->drbg); + mbedtls_x509_crt_init(&c->x509); + + mbedtls_entropy_init(&c->etpy); + mbedtls_ctr_drbg_seed(&c->drbg, mbedtls_entropy_func, &c->etpy, NULL, 0); + + mbedtls_ssl_config_defaults(&c->cfg, MBEDTLS_SSL_IS_CLIENT, + MBEDTLS_SSL_TRANSPORT_STREAM, MBEDTLS_SSL_PRESET_DEFAULT); + + mbedtls_ssl_conf_authmode(&c->cfg, MBEDTLS_SSL_VERIFY_OPTIONAL); + mbedtls_ssl_conf_ca_chain(&c->cfg, &c->x509, NULL); + mbedtls_ssl_conf_rng(&c->cfg, mbedtls_ctr_drbg_random, &c->drbg); + + mbedtls_ssl_set_bio(&c->ssl, &c->net, mbedtls_net_send, + mbedtls_net_recv, mbedtls_net_recv_timeout); + + mbedtls_ssl_setup(&c->ssl, &c->cfg); + + c->net.fd = sock; +#else + SSL_library_init(); + SSL_load_error_strings(); + + c->ctx = SSL_CTX_new(SSLv23_client_method()); + SSL_CTX_set_verify(c->ctx, SSL_VERIFY_NONE, 0); + + c->ssl = SSL_new(c->ctx); + SSL_set_fd(c->ssl, sock); +#endif + + *ctx = c; + return 0; +} + +int uwsc_ssl_handshake(struct uwsc_ssl_ctx *ctx) +{ +#if UWSC_HAVE_MBEDTLS + int ret = mbedtls_ssl_handshake(&ctx->ssl); + if (ret == MBEDTLS_ERR_SSL_WANT_READ || ret == MBEDTLS_ERR_SSL_WANT_WRITE) + return 0; + if (ret == 0) + return 1; + return -1; +#else + int ret = SSL_connect(ctx->ssl); + if (ret == 1) { + return 1; + } else { + int err = SSL_get_error(ctx->ssl, ret); + if (err == SSL_ERROR_WANT_READ || err == SSL_ERROR_WANT_WRITE) + return 0; + return -1; + } +#endif +} + +void uwsc_ssl_free(struct uwsc_ssl_ctx *ctx) +{ + if (!ctx) + return; + +#if UWSC_HAVE_MBEDTLS + mbedtls_ssl_free(&ctx->ssl); + mbedtls_ssl_config_free(&ctx->cfg); +#else + SSL_shutdown(ctx->ssl); + SSL_CTX_free(ctx->ctx); +#endif + free(ctx); +} + +int uwsc_ssl_read(int fd, void *buf, size_t count, void *arg) +{ + struct uwsc_ssl_ctx *ctx = arg; + +#if UWSC_HAVE_MBEDTLS + int ret; + + if (ctx->last_read_ok) { + ctx->last_read_ok = false; + return P_FD_PENDING; + } + + ret = mbedtls_ssl_read(&ctx->ssl, buf, count); + if (ret == MBEDTLS_ERR_SSL_WANT_READ) + return P_FD_PENDING; + if (ret == 0) + return P_FD_ERR; + if (ret > 0) + ctx->last_read_ok = true; +#else + int ret = SSL_read(ctx->ssl, buf, count); + if (ret < 0) { + int err = SSL_get_error(ctx->ssl, ret); + if (err == SSL_ERROR_WANT_READ) + return P_FD_PENDING; + return P_FD_ERR; + } +#endif + return ret; +} + +int uwsc_ssl_write(int fd, void *buf, size_t count, void *arg) +{ + struct uwsc_ssl_ctx *ctx = arg; + +#if UWSC_HAVE_MBEDTLS + int ret = mbedtls_ssl_write(&ctx->ssl, buf, count); + if (ret == MBEDTLS_ERR_SSL_WANT_WRITE) + return P_FD_PENDING; + if (ret == 0) + return P_FD_ERR; +#else + int ret = SSL_write(ctx->ssl, buf, count); + if (ret < 0) { + int err = SSL_get_error(ctx->ssl, ret); + if (err == SSL_ERROR_WANT_WRITE) + return P_FD_PENDING; + return P_FD_ERR; + } +#endif + return ret; +} + +#endif diff --git a/src/ssl.h b/src/ssl.h new file mode 100644 index 0000000..defb8a0 --- /dev/null +++ b/src/ssl.h @@ -0,0 +1,41 @@ +/* + * Copyright (C) 2017 Jianhui Zhao + * + * This program is free software; you can redistribute it and/or + * modify it under the terms of the GNU Lesser General Public + * License as published by the Free Software Foundation; either + * version 2.1 of the License, or (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + * Lesser General Public License for more details. + * + * You should have received a copy of the GNU Lesser General Public + * License along with this library; if not, write to the Free Software + * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 + * USA + */ + +#ifndef _UWSC_SSL_H +#define _UWSC_SSL_H + +#include +#include + +#include "uwsc.h" + +#if UWSC_SSL_SUPPORT + +struct uwsc_ssl_ctx; + +int uwsc_ssl_init(struct uwsc_ssl_ctx **ctx, int sock); +int uwsc_ssl_handshake(struct uwsc_ssl_ctx *ctx); +void uwsc_ssl_free(struct uwsc_ssl_ctx *ctx); + +int uwsc_ssl_read(int fd, void *buf, size_t count, void *arg); +int uwsc_ssl_write(int fd, void *buf, size_t count, void *arg); + +#endif + +#endif diff --git a/src/utils.c b/src/utils.c index a5c6a73..01cb559 100644 --- a/src/utils.c +++ b/src/utils.c @@ -20,6 +20,11 @@ #include #include #include +#include +#include +#include +#include +#include #include "log.h" #include "utils.h" @@ -39,11 +44,12 @@ int get_nonce(uint8_t *dest, int len) return -1; } -int parse_url(const char *url, char **host, int *port, const char **path, bool *ssl) +int parse_url(const char *url, char *host, int host_len, + int *port, const char **path, bool *ssl) { char *p; const char *host_pos; - int host_len = 0; + int hl = 0; if (!strncmp(url, "ws://", 5)) { *ssl = false; @@ -61,7 +67,7 @@ int parse_url(const char *url, char **host, int *port, const char **path, bool * p = strchr(url, ':'); if (p) { - host_len = p - url; + hl = p - url; url = p + 1; *port = atoi(url); } @@ -69,36 +75,81 @@ int parse_url(const char *url, char **host, int *port, const char **path, bool * p = strchr(url, '/'); if (p) { *path = p; - if (host_len == 0) - host_len = p - host_pos; + if (hl == 0) + hl = p - host_pos; } - if (host_len == 0) - host_len = strlen(host_pos); + if (hl == 0) + hl = strlen(host_pos); - *host = strndup(host_pos, host_len); + if (hl > host_len - 1) + hl = host_len - 1; + + memcpy(host, host_pos, hl); return 0; } -#if (UWSC_SSL_SUPPORT) -const struct ustream_ssl_ops *init_ustream_ssl() +static const char *port2str(int port) { - void *dlh; - struct ustream_ssl_ops *ops; + static char buffer[sizeof("65535\0")]; - dlh = dlopen("libustream-ssl.so", RTLD_LAZY | RTLD_LOCAL); - if (!dlh) { - uwsc_log_err("Failed to load ustream-ssl library: %s", dlerror()); + if (port < 0 || port > 65535) return NULL; + + snprintf(buffer, sizeof(buffer), "%u", port); + + return buffer; +} + +int tcp_connect(const char *host, int port, int flags, bool *inprogress, int *eai) +{ + int ret; + int sock = -1; + int addr_len; + struct sockaddr *addr = NULL; + struct addrinfo *result, *rp; + struct addrinfo hints = { + .ai_family = AF_INET, + .ai_socktype = SOCK_STREAM, + .ai_flags = AI_ADDRCONFIG + }; + + *inprogress = false; + + ret = getaddrinfo(host, port2str(port), &hints, &result); + if (ret) { + if (ret == EAI_SYSTEM) + return -1; + *eai = ret; + return 0; } - ops = dlsym(dlh, "ustream_ssl_ops"); - if (!ops) { - uwsc_log_err("Could not find required symbol 'ustream_ssl_ops' in ustream-ssl library"); - return NULL; + for (rp = result; rp != NULL; rp = rp->ai_next) { + if (rp->ai_family == AF_INET) { + addr = rp->ai_addr; + addr_len = rp->ai_addrlen; + break; + } + } + + if (!addr) + goto free_addrinfo; + + sock = socket(AF_INET, SOCK_STREAM | flags, 0); + if (sock < 0) + goto free_addrinfo; + + if (connect(sock, addr, addr_len) < 0) { + if (errno != EINPROGRESS) { + close(sock); + sock = -1; + } else { + *inprogress = true; + } } - return ops; +free_addrinfo: + freeaddrinfo(result); + return sock; } -#endif diff --git a/src/utils.h b/src/utils.h index 526a267..0b4bb2a 100644 --- a/src/utils.h +++ b/src/utils.h @@ -20,21 +20,24 @@ #ifndef _UTILS_H #define _UTILS_H -#include +#include #include +#include #include "config.h" -#if (UWSC_SSL_SUPPORT) -#include -#include +#ifndef container_of +#define container_of(ptr, type, member) \ + ({ \ + const __typeof__(((type *) NULL)->member) *__mptr = (ptr); \ + (type *) ((char *) __mptr - offsetof(type, member)); \ + }) #endif int get_nonce(uint8_t *dest, int len); -int parse_url(const char *url, char **host, int *port, const char **path, bool *ssl); +int parse_url(const char *url, char *host, int host_len, + int *port, const char **path, bool *ssl); -#if (UWSC_SSL_SUPPORT) -const struct ustream_ssl_ops *init_ustream_ssl(); -#endif +int tcp_connect(const char *host, int port, int flags, bool *inprogress, int *eai); #endif diff --git a/src/uwsc.c b/src/uwsc.c index 4253d12..86756b5 100644 --- a/src/uwsc.c +++ b/src/uwsc.c @@ -19,39 +19,56 @@ #include #include -#include #include -#include #include +#include +#include +#include #include -#include -#include +#include "ssl.h" #include "uwsc.h" -#include "log.h" +#include "sha1.h" #include "utils.h" +#include "base64.h" static void uwsc_free(struct uwsc_client *cl) { - uloop_timeout_cancel(&cl->ping_timer); - ustream_free(&cl->sfd.stream); - shutdown(cl->sfd.fd.fd, SHUT_RDWR); - close(cl->sfd.fd.fd); -#if (UWSC_SSL_SUPPORT) - ustream_free(&cl->ussl.stream); - if (cl->ssl_ops && cl->ssl_ctx) - cl->ssl_ops->context_free(cl->ssl_ctx); + ev_timer_stop(cl->loop, &cl->timer); + ev_io_stop(cl->loop, &cl->ior); + ev_io_stop(cl->loop, &cl->iow); + buffer_free(&cl->rb); + buffer_free(&cl->wb); + +#if UWSC_SSL_SUPPORT + uwsc_ssl_free(cl->ssl); #endif - free(cl); + + if (cl->sock > 0) + close(cl->sock); +} + +static inline void uwsc_error(struct uwsc_client *cl, int err, const char *msg) +{ + uwsc_free(cl); + + if (cl->onerror) { + if (!msg) + msg = ""; + cl->onerror(cl, err, msg); + } } -static inline void uwsc_error(struct uwsc_client *cl, int error) +static int uwsc_send_close(struct uwsc_client *cl, int code, const char *reason) { - cl->us->eof = true; - cl->error = error; + char buf[128] = ""; + + buf[1] = code & 0xFF; + buf[0] = (code >> 8)& 0xFF; - cl->send(cl, NULL, 0, WEBSOCKET_OP_CLOSE); - ustream_state_change(cl->us); + strncpy(&buf[2], reason, sizeof(buf) - 3); + + return cl->send(cl, buf, strlen(buf + 2) + 2, UWSC_OP_CLOSE); } static void dispach_message(struct uwsc_client *cl) @@ -59,47 +76,66 @@ static void dispach_message(struct uwsc_client *cl) struct uwsc_frame *frame = &cl->frame; switch (frame->opcode) { - case WEBSOCKET_OP_TEXT: - case WEBSOCKET_OP_BINARY: + case UWSC_OP_TEXT: + case UWSC_OP_BINARY: if (cl->onmessage) - cl->onmessage(cl, frame->payload, frame->payloadlen, frame->opcode); + cl->onmessage(cl, frame->payload, frame->payloadlen, frame->opcode == UWSC_OP_BINARY); break; - case WEBSOCKET_OP_PING: - cl->send(cl, frame->payload, frame->payloadlen, WEBSOCKET_OP_PONG); + + case UWSC_OP_PING: + cl->send(cl, frame->payload, frame->payloadlen, UWSC_OP_PONG); break; - case WEBSOCKET_OP_PONG: - cl->wait_pingresp = false; - uloop_timeout_set(&cl->ping_timer, cl->ping_interval * 1000); + + case UWSC_OP_PONG: + cl->wait_pong = false; break; - case WEBSOCKET_OP_CLOSE: - uwsc_error(cl, UWSC_ERROR_CLOSED_BY_SERVER); + + case UWSC_OP_CLOSE: + if (cl->onclose) { + int code = buffer_pull_u16(&cl->rb); + char reason[128] = ""; + + frame->payloadlen -= 2; + buffer_pull(&cl->rb, reason, frame->payloadlen); + cl->onclose(cl, ntohs(code), reason); + } + + uwsc_free(cl); break; + default: - uwsc_log_err("dispach_message: Invalid opcode - %d\n", frame->opcode); + uwsc_log_err("unknown opcode - %d\n", frame->opcode); + uwsc_send_close(cl, UWSC_CLOSE_STATUS_PROTOCOL_ERR, "unknown opcode"); break; } + + if (frame->payloadlen > 0) { + buffer_pull(&cl->rb, NULL, frame->payloadlen); + frame->payloadlen = 0; + } } -static bool parse_header_len(struct uwsc_client *cl, uint8_t *data, uint64_t len, - uint64_t *payloadlen, int *payloadlen_size) +static bool parse_header_len(struct uwsc_client *cl, uint64_t *payloadlen, + int *payloadlen_size) { struct uwsc_frame *frame = &cl->frame; + struct buffer *rb = &cl->rb; + uint8_t *data = buffer_data(rb); bool fin; - if (len < 2) + + if (buffer_length(rb) < 2) return false; fin = (data[0] & 0x80) ? true : false; frame->opcode = data[0] & 0x0F; - if (!fin || frame->opcode == WEBSOCKET_OP_CONTINUE) { - uwsc_log_err("Not support fragment\n"); - uwsc_error(cl, UWSC_ERROR_NOT_SUPPORT); + if (!fin || frame->opcode == UWSC_OP_CONTINUE) { + uwsc_error(cl, UWSC_ERROR_NOT_SUPPORT, "Not support fragment"); return false; } if (data[1] & 0x80) { - uwsc_log_err("Masked error"); - uwsc_error(cl, UWSC_ERROR_SERVER_MASKED); + uwsc_error(cl, UWSC_ERROR_SERVER_MASKED, "Masked error"); return false; } @@ -108,16 +144,14 @@ static bool parse_header_len(struct uwsc_client *cl, uint8_t *data, uint64_t len switch (*payloadlen) { case 126: - if (len < 4) + if (buffer_length(rb) < 4) return false; *payloadlen = ntohs(*(uint16_t *)&data[2]); *payloadlen_size += 2; break; case 127: - if (len < 10) - return false; - *payloadlen = (((uint64_t)ntohl(*(uint32_t *)&data[2])) << 32) + ntohl(*(uint32_t *)&data[6]); - *payloadlen_size += 8; + uwsc_error(cl, UWSC_ERROR_NOT_SUPPORT, "Payload too large"); + uwsc_send_close(cl, UWSC_CLOSE_STATUS_MESSAGE_TOO_LARGE, ""); break; default: break; @@ -126,70 +160,31 @@ static bool parse_header_len(struct uwsc_client *cl, uint8_t *data, uint64_t len return true; } -static bool parse_frame(struct uwsc_client *cl, uint8_t *data, uint64_t len) +static bool parse_frame(struct uwsc_client *cl) { struct uwsc_frame *frame = &cl->frame; + struct buffer *rb = &cl->rb; uint64_t payloadlen; int payloadlen_size; - uint8_t *payload; - - if (frame->wait) { - uint64_t more = frame->payloadlen - frame->buffer_len; - - if (more > len) - more = len; - - memcpy(frame->payload + frame->buffer_len, data, more); - frame->buffer_len += more; - - ustream_consume(cl->us, more); - if (frame->buffer_len < frame->payloadlen) - return false; - - if (frame->opcode == WEBSOCKET_OP_TEXT) - frame->payload[frame->payloadlen] = 0; - - dispach_message(cl); - - free(frame->payload); - frame->payload = NULL; - frame->wait = false; - } else { - if (!parse_header_len(cl, data, len, &payloadlen, &payloadlen_size)) - return false; + if (!parse_header_len(cl, &payloadlen, &payloadlen_size)) + return false; - payload = data + payloadlen_size + 1; + if (buffer_length(rb) < 1 + payloadlen_size + payloadlen) + return false; - if (len < 1 + payloadlen_size + payloadlen) { - if (1 + payloadlen_size + payloadlen > cl->us->r.buffer_len) { - frame->payload = malloc(payloadlen + 1); - if (!frame->payload) { - uwsc_log_err("No mem"); - uwsc_error(cl, UWSC_ERROR_NOMEM); - return false; - } + frame->payloadlen = payloadlen; - memcpy(frame->payload, payload, len - 1 - payloadlen_size); - ustream_consume(cl->us, len); + buffer_pull(rb, NULL, payloadlen_size + 1); - frame->wait = true; - frame->payloadlen = payloadlen; - frame->buffer_len = len - 1 - payloadlen_size; - } - return false; - } + frame->payload = buffer_data(rb); - frame->payload = payload; - frame->payloadlen = payloadlen; + dispach_message(cl); - dispach_message(cl); - ustream_consume(cl->us, 1 + payloadlen_size + payloadlen); - } return true; } -static int parse_header(struct uwsc_client *cl, char *data) +static int parse_http_header(struct uwsc_client *cl) { char *k, *v; bool has_upgrade = false; @@ -216,369 +211,366 @@ static int parse_header(struct uwsc_client *cl, char *data) if (!strcasecmp(k, "Connection") && !strcasecmp(v, "upgrade")) has_connection = true; - if (!strcasecmp(k, "Sec-WebSocket-Accept")) - has_sec_webSocket_accept = true; + if (!strcasecmp(k, "Sec-WebSocket-Accept")) { + static const char *magic = "258EAFA5-E914-47DA-95CA-C5AB0DC85B11"; + struct sha1_ctx ctx; + unsigned char sha[20]; + char my[512] = ""; + + sha1_init(&ctx); + sha1_update(&ctx, (uint8_t *)cl->key, strlen(cl->key)); + sha1_update(&ctx, (uint8_t *)magic, strlen(magic)); + sha1_final(&ctx, sha); - /* TODO: verify the value of Sec-WebSocket-Accept */ + b64_encode(sha, sizeof(sha), my, sizeof(my)); + + /* verify the value of Sec-WebSocket-Accept */ + if (strcmp(v, my)) { + uwsc_log_err("verify Sec-WebSocket-Accept failed\n"); + return -1; + } + has_sec_webSocket_accept = true; + } } - if (!has_upgrade || !has_connection || !has_sec_webSocket_accept) { - uwsc_log_err("Invalid header"); + if (!has_upgrade || !has_connection || !has_sec_webSocket_accept) return -1; - } return 0; } -static void __uwsc_notify_read(struct uwsc_client *cl, struct ustream *s) +static void uwsc_parse(struct uwsc_client *cl) { - char *data; - int len; + struct buffer *rb = &cl->rb; + int err = 0; do { - data = ustream_get_read_buf(s, &len); - if (!data || !len) + int data_len = buffer_length(rb); + if (data_len == 0) return; if (cl->state == CLIENT_STATE_HANDSHAKE) { - char *p, *version, *status_code, *summary; + char *version, *status_code, *summary; + char *p, *data = buffer_data(rb); - p = strstr(data, "\r\n\r\n"); + p = memmem(data, data_len, "\r\n\r\n", 4); if (!p) return; - - p[2] = 0; + p[0] = '\0'; version = strtok(data, " "); status_code = strtok(NULL, " "); summary = strtok(NULL, "\r\n"); if (!version || strcmp(version, "HTTP/1.1")) { - uwsc_log_err("Invalid version"); - cl->error = UWSC_ERROR_INVALID_HEADER; + err = UWSC_ERROR_INVALID_HEADER; break; } if (!status_code || atoi(status_code) != 101) { - uwsc_log_err("Invalid status code"); - cl->error = UWSC_ERROR_INVALID_HEADER; + err = UWSC_ERROR_INVALID_HEADER; break; } if (!summary) { - uwsc_log_err("Invalid summary"); - cl->error = UWSC_ERROR_INVALID_HEADER; + err = UWSC_ERROR_INVALID_HEADER; break; } - if (parse_header(cl, data)) { - cl->error = UWSC_ERROR_INVALID_HEADER; + if (parse_http_header(cl)) { + err = UWSC_ERROR_INVALID_HEADER; break; } - ustream_consume(cl->us, p + 4 - data); + buffer_pull(rb, NULL, p - data + 4); if (cl->onopen) cl->onopen(cl); cl->state = CLIENT_STATE_MESSAGE; } else if (cl->state == CLIENT_STATE_MESSAGE) { - if (!parse_frame(cl, (uint8_t *)data, len)) + if (!parse_frame(cl)) break; } else { uwsc_log_err("Invalid state\n"); } - } while(!cl->error); - - if (cl->error) - uwsc_error(cl, cl->error); -} + } while(!err); -static inline void uwsc_notify_read(struct ustream *s, int bytes) -{ - struct uwsc_client *cl = container_of(s, struct uwsc_client, sfd.stream); - __uwsc_notify_read(cl, s); + if (err) + uwsc_error(cl, err, "Invalid header"); } -static void __uwsc_notify_state(struct uwsc_client *cl, struct ustream *s) +static void uwsc_io_read_cb(struct ev_loop *loop, struct ev_io *w, int revents) { - errno = 0; - - if (!cl->error && s->write_error) - cl->error = UWSC_ERROR_WRITE; - - if (!cl->error) { - if (!s->eof || s->w.data_bytes) + struct uwsc_client *cl = container_of(w, struct uwsc_client, ior); + struct buffer *rb = &cl->rb; + bool eof; + int ret; + + if (cl->state == CLIENT_STATE_CONNECTING) { + int err; + socklen_t optlen = sizeof(err); + + getsockopt(w->fd, SOL_SOCKET, SO_ERROR, &err, &optlen); + if (err) { + uwsc_error(cl, UWSC_ERROR_CONNECT, strerror(err)); return; + } + cl->state = CLIENT_STATE_HANDSHAKE; + return; } - if (cl->error && cl->onerror) - cl->onerror(cl); - - if (cl->onclose) - cl->onclose(cl); -} - -static inline void uwsc_notify_state(struct ustream *s) -{ - struct uwsc_client *cl = container_of(s, struct uwsc_client, sfd.stream); - __uwsc_notify_state(cl, s); -} - -#if (UWSC_SSL_SUPPORT) -static inline void uwsc_ssl_notify_read(struct ustream *s, int bytes) -{ - struct uwsc_client *cl = container_of(s, struct uwsc_client, ussl.stream); - __uwsc_notify_read(cl, s); -} - -static inline void uwsc_ssl_notify_state(struct ustream *s) -{ - struct uwsc_client *cl = container_of(s, struct uwsc_client, ussl.stream); - __uwsc_notify_state(cl, s); -} - -static void uwsc_ssl_notify_error(struct ustream_ssl *ssl, int error, const char *str) -{ - struct uwsc_client *cl = container_of(ssl, struct uwsc_client, ussl); +#if UWSC_SSL_SUPPORT + if (cl->ssl) + ret = buffer_put_fd(rb, w->fd, -1, &eof, uwsc_ssl_read, cl->ssl); + else +#endif + ret = buffer_put_fd(rb, w->fd, -1, &eof, NULL, NULL); - uwsc_error(cl, UWSC_ERROR_SSL); - uwsc_log_err("ssl error:%d:%s", error, str); -} + if (ret < 0) { + uwsc_error(cl, UWSC_ERROR_IO, "read error"); + return; + } -static void uwsc_ssl_notify_verify_error(struct ustream_ssl *ssl, int error, const char *str) -{ - struct uwsc_client *cl = container_of(ssl, struct uwsc_client, ussl); + if (eof) { + uwsc_free(cl); - if (!cl->ssl_require_validation) + if (cl->onclose) + cl->onclose(cl, UWSC_CLOSE_STATUS_ABNORMAL_CLOSE, "unexpected EOF"); return; + } - uwsc_error(cl, UWSC_ERROR_SSL_INVALID_CERT); - uwsc_log_err("ssl error:%d:%s", error, str); + uwsc_parse(cl); } -static void uwsc_ssl_notify_connected(struct ustream_ssl *ssl) +static void uwsc_io_write_cb(struct ev_loop *loop, struct ev_io *w, int revents) { - struct uwsc_client *cl = container_of(ssl, struct uwsc_client, ussl); + struct uwsc_client *cl = container_of(w, struct uwsc_client, iow); + int ret; + + if (unlikely(cl->state == CLIENT_STATE_CONNECTING)) { +#if UWSC_SSL_SUPPORT + if (cl->ssl) + cl->state = CLIENT_STATE_SSL_HANDSHAKE; + else +#endif + cl->state = CLIENT_STATE_HANDSHAKE; + } - if (!cl->ssl_require_validation) +#if UWSC_SSL_SUPPORT + if (unlikely(cl->state == CLIENT_STATE_SSL_HANDSHAKE)) { + ret = uwsc_ssl_handshake(cl->ssl); + if (ret == 1) + cl->state = CLIENT_STATE_HANDSHAKE; + else if (ret == -1) + uwsc_error(cl, UWSC_ERROR_SSL_HANDSHAKE, "ssl handshake failed"); return; - - if (!cl->ussl.valid_cn) { - uwsc_error(cl, UWSC_ERROR_SSL_CN_MISMATCH); - uwsc_log_err("ssl error: cn mismatch"); } -} - #endif -static int uwsc_send(struct uwsc_client *cl, void *data, int len, enum websocket_op op) -{ - char *head, *p; - uint8_t mask_key[4]; - int i, head_size; +#if UWSC_SSL_SUPPORT + if (cl->ssl) + ret = buffer_pull_to_fd(&cl->wb, w->fd, buffer_length(&cl->wb), uwsc_ssl_write, cl->ssl); + else +#endif + ret = buffer_pull_to_fd(&cl->wb, w->fd, buffer_length(&cl->wb), NULL, NULL); - if (len > INT_MAX - 14) { - uwsc_log_err("Payload too big"); - return -1; + if (ret < 0) { + uwsc_error(cl, UWSC_ERROR_IO, "write error"); + return; } - head = malloc(14); - if (!head) { - uwsc_log_err("NO mem"); - return -1; - } + if (buffer_length(&cl->wb) < 1) + ev_io_stop(loop, w); +} - get_nonce(mask_key, 4); +static int uwsc_send(struct uwsc_client *cl, const void *data, size_t len, int op) +{ + struct buffer *wb = &cl->wb; + const uint8_t *p; + uint8_t mk[4]; + int i; + + get_nonce(mk, 4); - p = head; - *p++ = 0x80 | op; /* FIN and opcode */ + buffer_put_u8(wb, 0x80 | op); if (len < 126) { - *p++ = 0x80 | len; - head_size = 6; - } else if (len < 0x10000) { - *p++ = 0x80 | 126; - *p++ = (len >> 8) & 0xFF; - *p++ = len & 0xFF; - head_size = 8; + buffer_put_u8(wb, 0x80 | len); + } else if (len < 65536) { + buffer_put_u8(wb, 0x80 | 126); + buffer_put_u8(wb, (len >> 8) & 0xFF); + buffer_put_u8(wb, len & 0xFF); } else { - *p++ = 0x80 | 127; - *p++ = 0; - *p++ = 0; - *p++ = 0; - *p++ = 0; - *p++ = (len >> 24) & 0xFF; - *p++ = (len >> 16) & 0xFF; - *p++ = (len >> 8) & 0xFF; - *p++ = len & 0xFF; - head_size = 14; + uwsc_log_err("Payload too large"); + return -1; } - memcpy(p, mask_key, 4); + buffer_put_data(wb, mk, 4); + p = data; - for (i = 0; i < len; i++) { - p[i] ^= mask_key[i % 4]; - } + for (i = 0; i < len; i++) + buffer_put_u8(wb, p[i] ^ mk[i % 4]); - ustream_write(cl->us, head, head_size, false); - ustream_write(cl->us, data, len, false); - free(head); + ev_io_start(cl->loop, &cl->iow); return 0; } static inline void uwsc_ping(struct uwsc_client *cl) { - cl->send(cl, NULL, 0, WEBSOCKET_OP_PING); + const char *msg = "libuwsc"; + cl->send(cl, msg, strlen(msg), UWSC_OP_PING); } static void uwsc_handshake(struct uwsc_client *cl, const char *host, int port, const char *path) { + struct buffer *wb = &cl->wb; uint8_t nonce[16]; - char websocket_key[256] = ""; get_nonce(nonce, sizeof(nonce)); - b64_encode(nonce, sizeof(nonce), websocket_key, sizeof(websocket_key)); + b64_encode(nonce, sizeof(nonce), cl->key, sizeof(cl->key)); - ustream_printf(cl->us, "GET %s HTTP/1.1\r\n", path); - ustream_printf(cl->us, "Upgrade: websocket\r\n"); - ustream_printf(cl->us, "Connection: Upgrade\r\n"); - ustream_printf(cl->us, "Sec-WebSocket-Key: %s\r\n", websocket_key); - ustream_printf(cl->us, "Sec-WebSocket-Version: 13\r\n"); + buffer_put_printf(wb, "GET %s HTTP/1.1\r\n", path); + buffer_put_string(wb, "Upgrade: websocket\r\n"); + buffer_put_string(wb, "Connection: Upgrade\r\n"); + buffer_put_printf(wb, "Sec-WebSocket-Key: %s\r\n", cl->key); + buffer_put_string(wb, "Sec-WebSocket-Version: 13\r\n"); - ustream_printf(cl->us, "Host: %s", host); + buffer_put_printf(wb, "Host: %s", host); if (port == 80) - ustream_printf(cl->us, "\r\n"); + buffer_put_string(wb, "\r\n"); else - ustream_printf(cl->us, ":%d\r\n", port); + buffer_put_printf(wb, ":%d\r\n", port); - ustream_printf(cl->us, "\r\n"); + buffer_put_string(wb, "\r\n"); + + ev_io_start(cl->loop, &cl->iow); } -static void uwsc_ping_cb(struct uloop_timeout *timeout) +static void uwsc_timer_cb(struct ev_loop *loop, struct ev_timer *w, int revents) { - struct uwsc_client *cl = container_of(timeout, struct uwsc_client, ping_timer); + struct uwsc_client *cl = container_of(w, struct uwsc_client, timer); + static time_t connect_time; + static time_t last_ping; + static int ntimeout; + time_t now = time(NULL); + + if (unlikely(cl->state == CLIENT_STATE_CONNECTING)) { + if (connect_time == 0) { + connect_time = now; + return; + } + + if (now - connect_time > 5) { + uwsc_error(cl, UWSC_ERROR_CONNECT, "Connect timeout"); + return; + } + } - if (cl->wait_pingresp) { - uwsc_error(cl, UWSC_ERROR_PING_TIMEOUT); + if (unlikely(cl->state != CLIENT_STATE_MESSAGE)) return; + + if (cl->ping_interval == 0) + return; + + if (unlikely(cl->wait_pong)) { + if (now - last_ping < 3) + return; + + uwsc_log_err("ping timeout %d\n", ++ntimeout); + if (ntimeout > 2) { + uwsc_error(cl, UWSC_ERROR_PING_TIMEOUT, "ping timeout"); + return; + } + } else { + ntimeout = 0; } + + if (now - last_ping < cl->ping_interval) + return; + last_ping = now; + cl->ping(cl); - cl->wait_pingresp = true; - uloop_timeout_set(&cl->ping_timer, 1 * 1000); + cl->wait_pong = true; } static void uwsc_set_ping_interval(struct uwsc_client *cl, int interval) { cl->ping_interval = interval; - - uloop_timeout_cancel(&cl->ping_timer); - - if (interval > 0) - uloop_timeout_set(&cl->ping_timer, interval * 1000); } -struct uwsc_client *uwsc_new_ssl(const char *url, const char *ca_crt_file, bool verify) +struct uwsc_client *uwsc_new_ssl_v2(const char *url, const char *ca_crt_file, + bool verify, struct ev_loop *loop) { struct uwsc_client *cl = NULL; - char *host = NULL; const char *path = "/"; + char host[256] = ""; + bool inprogress; + int sock = -1; int port; - int sock; bool ssl; + int eai; - if (parse_url(url, &host, &port, &path, &ssl) < 0) { - uwsc_log_err("Invalid url"); + if (parse_url(url, host, sizeof(host), &port, &path, &ssl) < 0) { + uwsc_log_err("Invalid url\n"); return NULL; } - sock = usock(USOCK_TCP | USOCK_NOCLOEXEC, host, usock_port(port)); + sock = tcp_connect(host, port, SOCK_NONBLOCK | SOCK_CLOEXEC, &inprogress, &eai); if (sock < 0) { - uwsc_log_err("usock"); - goto err; + uwsc_log_err("tcp_connect failed: %s\n", strerror(errno)); + return NULL; + } else if (sock == 0) { + uwsc_log_err("tcp_connect failed: %s\n", gai_strerror(eai)); + return NULL; } cl = calloc(1, sizeof(struct uwsc_client)); if (!cl) { - uwsc_log_err("calloc"); + uwsc_log_err("calloc failed: %s\n", strerror(errno)); goto err; } - cl->free = uwsc_free; + if (!inprogress) + cl->state = CLIENT_STATE_HANDSHAKE; + + cl->loop = loop; + cl->sock = sock; cl->send = uwsc_send; cl->ping = uwsc_ping; cl->set_ping_interval = uwsc_set_ping_interval; - cl->ping_timer.cb = uwsc_ping_cb; - ustream_fd_init(&cl->sfd, sock); if (ssl) { #if (UWSC_SSL_SUPPORT) - cl->ssl_ops = init_ustream_ssl(); - if (!cl->ssl_ops) { - uwsc_log_err("SSL support not available,please install one of the libustream-ssl-* libraries"); - goto err; - } - - cl->ssl_ctx = cl->ssl_ops->context_new(false); - if (!cl->ssl_ctx) { - uwsc_log_err("ustream_ssl_context_new"); - goto err; - } - - if (ca_crt_file) { - if (cl->ssl_ops->context_add_ca_crt_file(cl->ssl_ctx, ca_crt_file)) { - uwsc_log_err("Load CA certificates failed"); - goto err; - } - } else if (verify) { - int i; - glob_t gl; - - cl->ssl_require_validation = true; - - if (!glob("/etc/ssl/certs/*.crt", 0, NULL, &gl)) { - for (i = 0; i < gl.gl_pathc; i++) - cl->ssl_ops->context_add_ca_crt_file(cl->ssl_ctx, gl.gl_pathv[i]); - globfree(&gl); - } - } - - cl->us = &cl->ussl.stream; - cl->us->string_data = true; - cl->us->notify_read = uwsc_ssl_notify_read; - cl->us->notify_state = uwsc_ssl_notify_state; - cl->ussl.notify_error = uwsc_ssl_notify_error; - cl->ussl.notify_verify_error = uwsc_ssl_notify_verify_error; - cl->ussl.notify_connected = uwsc_ssl_notify_connected; - cl->ussl.server_name = host; - cl->ssl_ops->init(&cl->ussl, &cl->sfd.stream, cl->ssl_ctx, false); - cl->ssl_ops->set_peer_cn(&cl->ussl, host); + uwsc_ssl_init((struct uwsc_ssl_ctx **)&cl->ssl, cl->sock); #else - uwsc_log_err("SSL support not available"); + uwsc_log_err("SSL is not enabled at compile\n"); goto err; #endif - } else { - cl->us = &cl->sfd.stream; - cl->us->string_data = true; - cl->us->notify_read = uwsc_notify_read; - cl->us->notify_state = uwsc_notify_state; } + ev_io_init(&cl->iow, uwsc_io_write_cb, sock, EV_WRITE); + + ev_io_init(&cl->ior, uwsc_io_read_cb, sock, EV_READ); + ev_io_start(loop, &cl->ior); + + ev_timer_init(&cl->timer, uwsc_timer_cb, 0.0, 1.0); + ev_timer_start(cl->loop, &cl->timer); + uwsc_handshake(cl, host, port, path); - free(host); return cl; err: - if (host) - free(host); + if (sock > 0) + close(sock); if (cl) - cl->free(cl); + free(cl); - return NULL; + return NULL; } diff --git a/src/uwsc.h b/src/uwsc.h index 437c25f..ae14ab9 100644 --- a/src/uwsc.h +++ b/src/uwsc.h @@ -20,84 +20,104 @@ #ifndef _UWSC_H #define _UWSC_H -#include -#include +#include #include "log.h" #include "config.h" +#include "buffer.h" -#if (UWSC_SSL_SUPPORT) -#include -#endif +#define HTTP_HEAD_LIMIT 4096 + +/* WebSocket close status codes defined in RFC 6455, section 11.7 */ +enum { + UWSC_CLOSE_STATUS_NORMAL = 1000, + UWSC_CLOSE_STATUS_GOINGAWAY = 1001, + UWSC_CLOSE_STATUS_PROTOCOL_ERR = 1002, + UWSC_CLOSE_STATUS_UNACCEPTABLE_OPCODE = 1003, + UWSC_CLOSE_STATUS_RESERVED = 1004, + UWSC_CLOSE_STATUS_NO_STATUS = 1005, + UWSC_CLOSE_STATUS_ABNORMAL_CLOSE = 1006, + UWSC_CLOSE_STATUS_INVALID_PAYLOAD = 1007, + UWSC_CLOSE_STATUS_POLICY_VIOLATION = 1008, + UWSC_CLOSE_STATUS_MESSAGE_TOO_LARGE = 1009, + UWSC_CLOSE_STATUS_EXTENSION_REQUIRED = 1010, + UWSC_CLOSE_STATUS_UNEXPECTED_CONDITION = 1011, + UWSC_CLOSE_STATUS_TLS_FAILURE = 1015 +}; -enum uwsc_error_code { - UWSC_ERROR_WRITE = 1, +enum { + UWSC_ERROR_IO = 1, UWSC_ERROR_INVALID_HEADER, - UWSC_ERROR_SSL, - UWSC_ERROR_SSL_INVALID_CERT, - UWSC_ERROR_SSL_CN_MISMATCH, UWSC_ERROR_SERVER_MASKED, - UWSC_ERROR_NOMEM, UWSC_ERROR_NOT_SUPPORT, UWSC_ERROR_PING_TIMEOUT, - UWSC_ERROR_CLOSED_BY_SERVER + UWSC_ERROR_CONNECT, + UWSC_ERROR_SSL_HANDSHAKE }; -enum client_state { +enum { + CLIENT_STATE_CONNECTING, + CLIENT_STATE_SSL_HANDSHAKE, CLIENT_STATE_HANDSHAKE, CLIENT_STATE_MESSAGE }; -enum websocket_op { - WEBSOCKET_OP_CONTINUE = 0x0, - WEBSOCKET_OP_TEXT = 0x1, - WEBSOCKET_OP_BINARY = 0x2, - WEBSOCKET_OP_CLOSE = 0x8, - WEBSOCKET_OP_PING = 0x9, - WEBSOCKET_OP_PONG = 0xA +enum { + UWSC_OP_CONTINUE = 0x0, + UWSC_OP_TEXT = 0x1, + UWSC_OP_BINARY = 0x2, + UWSC_OP_CLOSE = 0x8, + UWSC_OP_PING = 0x9, + UWSC_OP_PONG = 0xA }; struct uwsc_frame { uint8_t opcode; - bool wait; /* Wait more data */ - uint8_t *buffer; - uint64_t buffer_len; - uint64_t payloadlen; + size_t payloadlen; uint8_t *payload; }; struct uwsc_client { - struct ustream *us; - struct ustream_fd sfd; - enum client_state state; + int sock; + int state; + struct ev_loop *loop; + struct ev_io ior; + struct ev_io iow; + struct buffer rb; + struct buffer wb; struct uwsc_frame frame; - struct uloop_timeout ping_timer; - bool wait_pingresp; + struct ev_timer timer; + bool wait_pong; int ping_interval; - enum uwsc_error_code error; - -#if (UWSC_SSL_SUPPORT) - bool ssl_require_validation; - struct ustream_ssl ussl; - struct ustream_ssl_ctx *ssl_ctx; - const struct ustream_ssl_ops *ssl_ops; -#endif + char key[256]; /* Sec-WebSocket-Key */ + void *ssl; void (*onopen)(struct uwsc_client *cl); void (*set_ping_interval)(struct uwsc_client *cl, int interval); - void (*onmessage)(struct uwsc_client *cl, void *data, uint64_t len, enum websocket_op op); - void (*onerror)(struct uwsc_client *cl); - void (*onclose)(struct uwsc_client *cl); - int (*send)(struct uwsc_client *cl, void *data, int len, enum websocket_op op); + void (*onmessage)(struct uwsc_client *cl, void *data, size_t len, bool binary); + void (*onerror)(struct uwsc_client *cl, int err, const char *msg); + void (*onclose)(struct uwsc_client *cl, int code, const char *reason); + int (*send)(struct uwsc_client *cl, const void *data, size_t len, int op); void (*ping)(struct uwsc_client *cl); - void (*free)(struct uwsc_client *cl); }; -struct uwsc_client *uwsc_new_ssl(const char *url, const char *ca_crt_file, bool verify); +struct uwsc_client *uwsc_new_ssl_v2(const char *url, const char *ca_crt_file, bool verify, + struct ev_loop *loop); + +static inline struct uwsc_client *uwsc_new_ssl(const char *url, const char *ca_crt_file, + bool verify) +{ + return uwsc_new_ssl_v2(url, ca_crt_file, verify, EV_DEFAULT); +} static inline struct uwsc_client *uwsc_new(const char *url) { return uwsc_new_ssl(url, NULL, false); } +static inline struct uwsc_client *uwsc_new_v2(const char *url, struct ev_loop *loop) +{ + return uwsc_new_ssl_v2(url, NULL, false, loop); +} + #endif