diff --git a/.github/workflows/ci.yml b/.github/workflows/ci.yml index 7c259ec..d87b12b 100644 --- a/.github/workflows/ci.yml +++ b/.github/workflows/ci.yml @@ -128,3 +128,19 @@ jobs: cmake .. -G "MinGW Makefiles" -DBUILD_TESTING=ON -DBUILD_TESTING_INTEGRATION=ON -DC_WARNINGS_AS_ERRORS=ON -DCPP_WARNINGS_AS_ERRORS=ON cmake --build . ctest --verbose -R "allocator|value|example_basic|integration_basic" + + build_and_test_linux_wasm: + strategy: + matrix: + platform: [ubuntu-18.04] + runs-on: ${{ matrix.platform }} + steps: + - name: Set-up repository + uses: actions/checkout@v2 + + - name: Build with clang + run: | + mkdir build + cd build + cmake .. -DCMAKE_BUILD_TYPE=Release -DC_WARNINGS_AS_ERRORS=ON -DWASM=ON + make diff --git a/.gitignore b/.gitignore index d8991a0..7700465 100644 --- a/.gitignore +++ b/.gitignore @@ -13,3 +13,6 @@ tool/generated/ # Doxygen output files. html/ + +# emsdk folder pulled from wasm/install_deps.sh +wasm/emsdk diff --git a/CMakeLists.txt b/CMakeLists.txt index 506f82e..9741107 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -14,13 +14,23 @@ cmake_minimum_required(VERSION 3.8) -project(mgclient VERSION 1.3.0) +if(WASM) + execute_process(COMMAND ${CMAKE_SOURCE_DIR}/wasm/install_deps.sh) + set(CMAKE_TOOLCHAIN_FILE "${CMAKE_SOURCE_DIR}/wasm/emsdk/upstream/emscripten/cmake/Modules/Platform/Emscripten.cmake") +endif() + +project(mgclient VERSION 1.2.0) # Minor version increase can also mean ABI incompatibility with previous # versions. IMPORTANT: Take care of the SO version manually. set(mgclient_SOVERSION 2) +set(WASM OFF CACHE BOOL "Compile mgclient for wasm") add_definitions(-DMGCLIENT_VERSION="${mgclient_VERSION}") # Deal with the operating system. +if((NOT UNIX) AND EMSCRIPTEN) + message(FATAL_ERROR "WASM build is only supported in Linux") +endif() + if(WIN32) message(STATUS "ON WINDOWS BUILD") set(MGCLIENT_ON_WINDOWS TRUE) @@ -29,14 +39,16 @@ if(WIN32) # "lib;" breaks gtest lib referencing. set(MGCLIENT_FIND_LIBRARY_PREFIXES "lib") elseif(UNIX AND NOT APPLE) - if(CMAKE_SYSTEM_NAME STREQUAL "Linux") + if(EMSCRIPTEN) + message(STATUS "ON LINUX WASM BUILD") + elseif(CMAKE_SYSTEM_NAME STREQUAL "Linux") message(STATUS "ON LINUX BUILD") - set(MGCLIENT_ON_LINUX TRUE) - add_definitions(-DMGCLIENT_ON_LINUX) - set(MGCLIENT_FIND_LIBRARY_PREFIXES "${CMAKE_FIND_LIBRARY_PREFIXES}") - else() + else() message(FATAL_ERROR "Unsupported operating system. Please create issue or contribute!") endif() + set(MGCLIENT_ON_LINUX TRUE) + add_definitions(-DMGCLIENT_ON_LINUX) + set(MGCLIENT_FIND_LIBRARY_PREFIXES "${CMAKE_FIND_LIBRARY_PREFIXES}") elseif(APPLE) message(STATUS "ON APPLE BUILD") set(MGCLIENT_ON_APPLE TRUE) @@ -79,6 +91,14 @@ set_project_c_warnings(project_c_warnings) add_library(project_cpp_warnings INTERFACE) set_project_cpp_warnings(project_cpp_warnings) +if(EMSCRIPTEN) + # same as project_c_warnings but without -O3. In the future we should + # experiment and switch to O3. because it reduces the js output size + # significantly. + set(CMAKE_C_FLAGS_DEBUG "-g -O0 -Wall -Wextra -Wpedantic -std=gnu11") + set(CMAKE_C_FLAGS_RELEASE "-O2 -DNDEBUG -Wall -Wextra -Wpedantic -std=gnu11") +endif() + # Set default build type to 'Release' if(NOT CMAKE_BUILD_TYPE) set(CMAKE_BUILD_TYPE "Release") diff --git a/README.md b/README.md index 80b3c62..b0e5dbf 100644 --- a/README.md +++ b/README.md @@ -126,6 +126,12 @@ cd build cmake .. -G "MinGW Makefiles" cmake --build . --target install ``` +## Building WASM (linux only) +Compiling `mgclient` for wasm requires the Emscripten sdk. This is automated in the following steps: + 1. mkdir build && cd build + 2. cmake .. -DWASM=ON + 3. make +Now there should be an `mgclient.js` and an `mgclient.wasm` found in `mgclient/build/` ## Using the library diff --git a/include/mgclient.h b/include/mgclient.h index 20c6fdc..b916a6c 100644 --- a/include/mgclient.h +++ b/include/mgclient.h @@ -15,7 +15,12 @@ #ifndef MGCLIENT_MGCLIENT_H #define MGCLIENT_MGCLIENT_H +#ifdef __EMSCRIPTEN__ +#define MGCLIENT_EXPORT EMSCRIPTEN_KEEPALIVE +#include "emscripten.h" +#else #include "mgclient-export.h" +#endif #ifdef __cplusplus extern "C" { @@ -1127,7 +1132,9 @@ MGCLIENT_EXPORT void mg_point_3d_destroy(mg_point_3d *point_3d); /// the server. enum mg_sslmode { MG_SSLMODE_DISABLE, ///< Only try a non-SSL connection. +#ifndef __EMSCRIPTEN__ MG_SSLMODE_REQUIRE, ///< Only try a SSL connection. +#endif }; /// An object encapsulating a Bolt session. diff --git a/src/CMakeLists.txt b/src/CMakeLists.txt index 5723526..e0a8dd9 100644 --- a/src/CMakeLists.txt +++ b/src/CMakeLists.txt @@ -35,58 +35,84 @@ else() message(FATAL_ERROR "Operating system undefined.") endif() -find_package(OpenSSL REQUIRED) +if(EMSCRIPTEN) + list(APPEND mgclient_src_files mgwasm.c) + add_executable(mgclient ${mgclient_src_files}) + set_target_properties(mgclient PROPERTIES LINK_FLAGS "-s ASYNCIFY=1 -s MODULARIZE -s EXPORT_NAME=\"load_mgclient\" --shared-memory --no-entry -s USE_PTHREADS=1 -s WEBSOCKET_SUBPROTOCOL=\"binary\" -s EXPORTED_RUNTIME_METHODS=\"ccall, cwrap, getValue, setValue, UTF8ToString, allocateUTF8\"") -include(GenerateExportHeader) + target_include_directories(mgclient + PRIVATE + "${PROJECT_SOURCE_DIR}/src" + PUBLIC + "${PROJECT_SOURCE_DIR}/include" + "${CMAKE_CURRENT_BINARY_DIR}") +else() + find_package(OpenSSL REQUIRED) + include(GenerateExportHeader) -add_library(mgclient-static STATIC ${mgclient_src_files}) -set_target_properties(mgclient-static PROPERTIES - OUTPUT_NAME mgclient) -target_compile_definitions(mgclient-static PUBLIC MGCLIENT_STATIC_DEFINE) -target_include_directories(mgclient-static - PRIVATE - "${PROJECT_SOURCE_DIR}/src" - PUBLIC - "${PROJECT_SOURCE_DIR}/include" - "${CMAKE_CURRENT_BINARY_DIR}" - "${OPENSSL_INCLUDE_DIR}") -target_link_libraries(mgclient-static - PRIVATE - ${OPENSSL_LIBRARIES} project_options project_c_warnings) -if(MGCLIENT_ON_WINDOWS) - target_link_libraries(mgclient-static PUBLIC ws2_32) -endif() + add_library(mgclient-static STATIC ${mgclient_src_files}) -add_library(mgclient-shared SHARED ${mgclient_src_files}) -set_target_properties(mgclient-shared PROPERTIES - OUTPUT_NAME mgclient - SOVERSION ${mgclient_SOVERSION} - C_VISIBILITY_PRESET hidden) -target_include_directories(mgclient-shared - PRIVATE - "${PROJECT_SOURCE_DIR}/src" - PUBLIC - "${PROJECT_SOURCE_DIR}/include" - "${CMAKE_CURRENT_BINARY_DIR}" - "${OPENSSL_INCLUDE_DIR}") -target_link_libraries(mgclient-shared PRIVATE ${OPENSSL_LIBRARIES}) -if(MGCLIENT_ON_WINDOWS) - target_link_libraries(mgclient-shared PUBLIC ws2_32) -endif() + generate_export_header(mgclient-static + BASE_NAME "mgclient" + EXPORT_FILE_NAME "mgclient-export.h") + + set_target_properties(mgclient-static PROPERTIES + OUTPUT_NAME mgclient) + target_compile_definitions(mgclient-static PUBLIC MGCLIENT_STATIC_DEFINE) + target_include_directories(mgclient-static + PRIVATE + "${PROJECT_SOURCE_DIR}/src" + PUBLIC + "${PROJECT_SOURCE_DIR}/include" + "${CMAKE_CURRENT_BINARY_DIR}" + "${OPENSSL_INCLUDE_DIR}") + target_link_libraries(mgclient-static + PRIVATE + ${OPENSSL_LIBRARIES} project_options project_c_warnings) + + if(MGCLIENT_ON_WINDOWS) + target_link_libraries(mgclient-static PUBLIC ws2_32) + endif() -generate_export_header(mgclient-shared - BASE_NAME "mgclient" - EXPORT_FILE_NAME "mgclient-export.h") + add_library(mgclient-shared SHARED ${mgclient_src_files}) -include(GNUInstallDirs) + generate_export_header(mgclient-shared + BASE_NAME "mgclient" + EXPORT_FILE_NAME "mgclient-export.h") -install(TARGETS mgclient-static mgclient-shared - ARCHIVE DESTINATION ${CMAKE_INSTALL_LIBDIR} - LIBRARY DESTINATION ${CMAKE_INSTALL_LIBDIR} - RUNTIME DESTINATION ${CMAKE_INSTALL_LIBDIR}) -install(DIRECTORY - "${PROJECT_SOURCE_DIR}/include/" - DESTINATION ${CMAKE_INSTALL_INCLUDEDIR}) -install(FILES - "${CMAKE_CURRENT_BINARY_DIR}/mgclient-export.h" - DESTINATION ${CMAKE_INSTALL_INCLUDEDIR}) + set_target_properties(mgclient-shared PROPERTIES + OUTPUT_NAME mgclient + SOVERSION ${mgclient_SOVERSION} + C_VISIBILITY_PRESET hidden) + target_include_directories(mgclient-shared + PRIVATE + "${PROJECT_SOURCE_DIR}/src" + PUBLIC + "${PROJECT_SOURCE_DIR}/include" + "${CMAKE_CURRENT_BINARY_DIR}" + "${OPENSSL_INCLUDE_DIR}") + target_link_libraries(mgclient-shared + PRIVATE + ${OPENSSL_LIBRARIES} project_options project_c_warnings) + + if(MGCLIENT_ON_WINDOWS) + target_link_libraries(mgclient-shared PUBLIC ws2_32) + endif() + + generate_export_header(mgclient-shared + BASE_NAME "mgclient" + EXPORT_FILE_NAME "mgclient-export.h") + + include(GNUInstallDirs) + + install(TARGETS mgclient-static mgclient-shared + ARCHIVE DESTINATION ${CMAKE_INSTALL_LIBDIR} + LIBRARY DESTINATION ${CMAKE_INSTALL_LIBDIR} + RUNTIME DESTINATION ${CMAKE_INSTALL_LIBDIR}) + install(DIRECTORY + "${PROJECT_SOURCE_DIR}/include/" + DESTINATION ${CMAKE_INSTALL_INCLUDEDIR}) + install(FILES + "${CMAKE_CURRENT_BINARY_DIR}/mgclient-export.h" + DESTINATION ${CMAKE_INSTALL_INCLUDEDIR}) +endif() diff --git a/src/linux/mgsocket.c b/src/linux/mgsocket.c index d2ae9a0..d7ba7db 100644 --- a/src/linux/mgsocket.c +++ b/src/linux/mgsocket.c @@ -12,18 +12,25 @@ // See the License for the specific language governing permissions and // limitations under the License. +#include "mgsocket.h" + +#include #include #include "mgcommon.h" -#include "mgsocket.h" -#define MG_RETRY_ON_EINTR(expression) \ - __extension__({ \ - long result; \ - do { \ - result = (long)(expression); \ - } while (result == -1L && errno == EINTR); \ - result; \ +#ifdef __EMSCRIPTEN__ +#include "emscripten.h" +#include "mgwasm.h" +#endif + +#define MG_RETRY_ON_EINTR(expression) \ + __extension__({ \ + long result; \ + do { \ + result = (long)(expression); \ + } while (result == -1L && (errno == EINTR)); \ + result; \ }) // Please refer to https://man7.org/linux/man-pages/man2 for more details about @@ -47,14 +54,32 @@ int mg_socket_create_handle_error(int sock, mg_session *session) { return MG_SUCCESS; } +static int mg_socket_test_status_is_error(long status) { +#ifdef __EMSCRIPTEN__ + if (status == -1L && errno != EINPROGRESS) { +#else + if (status == -1L) { +#endif + return 1; + } + return 0; +} + int mg_socket_connect(int sock, const struct sockaddr *addr, socklen_t addrlen) { long status = MG_RETRY_ON_EINTR(connect(sock, addr, addrlen)); - if (status == -1L) { + if (mg_socket_test_status_is_error(status)) { + return MG_ERROR_SOCKET; + } +#ifdef __EMSCRIPTEN__ + if (mg_wasm_suspend_until_ready_to_write(sock) == -1) { return MG_ERROR_SOCKET; } +#endif + return MG_SUCCESS; } + int mg_socket_connect_handle_error(int *sock, int status, mg_session *session) { if (status != MG_SUCCESS) { mg_session_set_error(session, "couldn't connect to host: %s", @@ -69,6 +94,11 @@ int mg_socket_connect_handle_error(int *sock, int status, mg_session *session) { } int mg_socket_options(int sock, mg_session *session) { +#ifdef __EMSCRIPTEN__ + (void)sock; + (void)session; + return MG_SUCCESS; +#else struct { int level; int optname; @@ -100,6 +130,7 @@ int mg_socket_options(int sock, mg_session *session) { } } return MG_SUCCESS; +#endif } ssize_t mg_socket_send(int sock, const void *buf, int len) { diff --git a/src/mgclient.c b/src/mgclient.c index 7ce41a0..6e8c721 100644 --- a/src/mgclient.c +++ b/src/mgclient.c @@ -26,6 +26,7 @@ #include "mgmessage.h" #include "mgsession.h" #include "mgsocket.h" +#include "mgtransport.h" #include "mgvalue.h" const char *mg_client_version() { return MGCLIENT_VERSION; } @@ -204,6 +205,7 @@ static int mg_bolt_handshake(mg_session *session) { const uint32_t VERSION_NONE = htobe32(0); const uint32_t VERSION_1 = htobe32(1); const uint32_t VERSION_4_1 = htobe32(0x0104); + mg_transport_suspend_until_ready_to_write(session->transport); if (mg_transport_send(session->transport, MG_HANDSHAKE_MAGIC, strlen(MG_HANDSHAKE_MAGIC)) != 0 || mg_transport_send(session->transport, (char *)&VERSION_4_1, 4) != 0 || @@ -213,12 +215,13 @@ static int mg_bolt_handshake(mg_session *session) { mg_session_set_error(session, "failed to send handshake data"); return MG_ERROR_SEND_FAILED; } + uint32_t server_version; + mg_transport_suspend_until_ready_to_read(session->transport); if (mg_transport_recv(session->transport, (char *)&server_version, 4) != 0) { mg_session_set_error(session, "failed to receive handshake response"); return MG_ERROR_RECV_FAILED; } - if (server_version == VERSION_1) { session->version = 1; } else if (server_version == VERSION_4_1) { @@ -429,7 +432,6 @@ static int init_tcp_connection(const mg_session_params *params, int *sockfd, char portstr[6]; sprintf(portstr, "%" PRIu16, params->port); - int getaddrinfo_status; if (params->host) { getaddrinfo_status = getaddrinfo(params->host, portstr, &hints, &addr_list); @@ -441,8 +443,14 @@ static int init_tcp_connection(const mg_session_params *params, int *sockfd, abort(); } if (getaddrinfo_status != 0) { +#ifdef __EMSCRIPTEN__ + mg_session_set_error(session, "getaddrinfo failed: %d", getaddrinfo_status); + // Not supported by emscripten: + // gai_strerror(getaddrinfo_status)); +#else mg_session_set_error(session, "getaddrinfo failed: %s", gai_strerror(getaddrinfo_status)); +#endif return MG_ERROR_NETWORK_FAILURE; } @@ -479,6 +487,7 @@ static int init_tcp_connection(const mg_session_params *params, int *sockfd, return 0; } +#ifndef __EMSCRIPTEN__ static int get_hostname_and_ip(const struct sockaddr *peer_addr, char *hostname, char *ip, mg_session *session) { // Populate the ip. @@ -519,6 +528,7 @@ static int get_hostname_and_ip(const struct sockaddr *peer_addr, char *hostname, } return 0; } +#endif int mg_connect_ca(const mg_session_params *params, mg_session **session, mg_allocator *allocator) { @@ -541,7 +551,6 @@ int mg_connect_ca(const mg_session_params *params, mg_session **session, if (status != 0) { goto cleanup; } - switch (params->sslmode) { case MG_SSLMODE_DISABLE: status = mg_raw_transport_init( @@ -551,6 +560,7 @@ int mg_connect_ca(const mg_session_params *params, mg_session **session, goto cleanup; } break; +#ifndef __EMSCRIPTEN__ case MG_SSLMODE_REQUIRE: { mg_secure_transport *ttransport; status = mg_secure_transport_init(sockfd, params->sslcert, params->sslkey, @@ -580,6 +590,7 @@ int mg_connect_ca(const mg_session_params *params, mg_session **session, } break; } +#endif default: // Should not get here. abort(); @@ -587,12 +598,10 @@ int mg_connect_ca(const mg_session_params *params, mg_session **session, // mg_transport object took ownership of the socket. sockfd = -1; - status = mg_bolt_handshake(tsession); if (status != 0) { goto cleanup; } - status = mg_bolt_init(tsession, params); if (status != 0) { goto cleanup; @@ -688,6 +697,7 @@ int mg_session_run(mg_session *session, const char *query, const mg_map *params, goto fatal_failure; } + mg_transport_suspend_until_ready_to_read(session->transport); status = mg_session_receive_message(session); if (status != 0) { goto fatal_failure; diff --git a/src/mgsession.c b/src/mgsession.c index dc9fb8b..f2043e7 100644 --- a/src/mgsession.c +++ b/src/mgsession.c @@ -27,6 +27,7 @@ #include "mgcommon.h" #include "mgconstants.h" #include "mgsession.h" +#include "mgtransport.h" int mg_session_status(const mg_session *session) { if (!session) { @@ -227,6 +228,7 @@ int mg_session_ensure_space_for_chunk(mg_session *session, size_t chunk_size) { int mg_session_read_chunk(mg_session *session) { uint16_t chunk_size; + mg_transport_suspend_until_ready_to_read(session->transport); if (mg_transport_recv(session->transport, (char *)&chunk_size, 2) != 0) { mg_session_set_error(session, "failed to receive chunk size"); return MG_ERROR_RECV_FAILED; @@ -241,6 +243,7 @@ int mg_session_read_chunk(mg_session *session) { return status; } } + mg_transport_suspend_until_ready_to_read(session->transport); if (mg_transport_recv(session->transport, session->in_buffer + session->in_end, chunk_size) != 0) { diff --git a/src/mgsocket.h b/src/mgsocket.h index 98d80a1..14c3b7e 100644 --- a/src/mgsocket.h +++ b/src/mgsocket.h @@ -30,6 +30,7 @@ extern "C" { #ifdef MGCLIENT_ON_LINUX #include +#include #include #include #include diff --git a/src/mgtransport.c b/src/mgtransport.c index 7635c9f..03980af 100644 --- a/src/mgtransport.c +++ b/src/mgtransport.c @@ -19,13 +19,18 @@ #include #include #ifdef MGCLIENT_ON_LINUX +#ifndef __EMSCRIPTEN__ #include +#endif #endif // MGCLIENT_ON_LINUX #include "mgallocator.h" #include "mgclient.h" #include "mgcommon.h" #include "mgsocket.h" +#ifdef __EMSCRIPTEN__ +#include "mgwasm.h" +#endif int mg_init_ssl = 1; @@ -41,6 +46,18 @@ void mg_transport_destroy(mg_transport *transport) { transport->destroy(transport); } +void mg_transport_suspend_until_ready_to_read(struct mg_transport *transport) { + if (transport->suspend_until_ready_to_read) { + transport->suspend_until_ready_to_read(transport); + } +} + +void mg_transport_suspend_until_ready_to_write(struct mg_transport *transport) { + if (transport->suspend_until_ready_to_write) { + transport->suspend_until_ready_to_write(transport); + } +} + int mg_raw_transport_init(int sockfd, mg_raw_transport **transport, mg_allocator *allocator) { mg_raw_transport *ttransport = @@ -52,6 +69,10 @@ int mg_raw_transport_init(int sockfd, mg_raw_transport **transport, ttransport->send = mg_raw_transport_send; ttransport->recv = mg_raw_transport_recv; ttransport->destroy = mg_raw_transport_destroy; + ttransport->suspend_until_ready_to_read = + mg_raw_transport_suspend_until_ready_to_read; + ttransport->suspend_until_ready_to_write = + mg_raw_transport_suspend_until_ready_to_write; ttransport->allocator = allocator; *transport = ttransport; return 0; @@ -103,6 +124,27 @@ void mg_raw_transport_destroy(struct mg_transport *transport) { mg_allocator_free(self->allocator, transport); } +void mg_raw_transport_suspend_until_ready_to_read( + struct mg_transport *transport) { +#ifdef __EMSCRIPTEN__ + const int sock = ((mg_raw_transport *)transport)->sockfd; + mg_wasm_suspend_until_ready_to_read(sock); +#else + (void)transport; +#endif +} + +void mg_raw_transport_suspend_until_ready_to_write( + struct mg_transport *transport) { +#ifdef __EMSCRIPTEN__ + const int sock = ((mg_raw_transport *)transport)->sockfd; + mg_wasm_suspend_until_ready_to_write(sock); +#else + (void)transport; +#endif +} + +#ifndef __EMSCRIPTEN__ static int print_ssl_error(const char *str, size_t len, void *u) { (void)len; fprintf(stderr, "%s: %s", (char *)u, str); @@ -228,6 +270,8 @@ int mg_secure_transport_init(int sockfd, const char *cert_file, hex_encode(peer_pubkey_fp, peer_pubkey_fp_len, allocator); ttransport->send = mg_secure_transport_send; ttransport->recv = mg_secure_transport_recv; + ttransport->suspend_until_ready_to_read = NULL; + ttransport->suspend_until_ready_to_write = NULL; ttransport->destroy = mg_secure_transport_destroy; ttransport->allocator = allocator; *transport = ttransport; @@ -319,3 +363,4 @@ void mg_secure_transport_destroy(mg_transport *transport) { mg_allocator_free(self->allocator, self->peer_pubkey_fp); mg_allocator_free(self->allocator, self); } +#endif diff --git a/src/mgtransport.h b/src/mgtransport.h index 95f0615..615b0f5 100644 --- a/src/mgtransport.h +++ b/src/mgtransport.h @@ -19,10 +19,14 @@ extern "C" { #endif +#ifndef __EMSCRIPTEN__ #include #include #include +#endif + #include +#include #include "mgallocator.h" #include "mgcommon.h" @@ -31,26 +35,34 @@ typedef struct mg_transport { int (*send)(struct mg_transport *, const char *buf, size_t len); int (*recv)(struct mg_transport *, char *buf, size_t len); void (*destroy)(struct mg_transport *); + void (*suspend_until_ready_to_read)(struct mg_transport *); + void (*suspend_until_ready_to_write)(struct mg_transport *); } mg_transport; typedef struct mg_raw_transport { int (*send)(struct mg_transport *, const char *buf, size_t len); int (*recv)(struct mg_transport *, char *buf, size_t len); void (*destroy)(struct mg_transport *); + void (*suspend_until_ready_to_read)(struct mg_transport *); + void (*suspend_until_ready_to_write)(struct mg_transport *); int sockfd; mg_allocator *allocator; } mg_raw_transport; +#ifndef __EMSCRIPTEN__ typedef struct mg_secure_transport { int (*send)(struct mg_transport *, const char *buf, size_t len); int (*recv)(struct mg_transport *, char *buf, size_t len); void (*destroy)(struct mg_transport *); + void (*suspend_until_ready_to_read)(struct mg_transport *); + void (*suspend_until_ready_to_write)(struct mg_transport *); SSL *ssl; BIO *bio; const char *peer_pubkey_type; char *peer_pubkey_fp; mg_allocator *allocator; } mg_secure_transport; +#endif int mg_transport_send(mg_transport *transport, const char *buf, size_t len); @@ -58,6 +70,10 @@ int mg_transport_recv(mg_transport *transport, char *buf, size_t len); void mg_transport_destroy(mg_transport *transport); +void mg_transport_suspend_until_ready_to_read(struct mg_transport *); + +void mg_transport_suspend_until_ready_to_write(struct mg_transport *); + int mg_raw_transport_init(int sockfd, mg_raw_transport **transport, mg_allocator *allocator); @@ -67,6 +83,11 @@ int mg_raw_transport_recv(struct mg_transport *, char *buf, size_t len); void mg_raw_transport_destroy(struct mg_transport *); +void mg_raw_transport_suspend_until_ready_to_read(struct mg_transport *); + +void mg_raw_transport_suspend_until_ready_to_write(struct mg_transport *); + +#ifndef __EMSCRIPTEN__ // This function is mocked in tests during linking by using --wrap. ON_APPLE // there is no --wrap. An alternative is to use -alias but if a symbol is // strong linking fails. @@ -81,6 +102,7 @@ int mg_secure_transport_send(mg_transport *, const char *buf, size_t len); int mg_secure_transport_recv(mg_transport *, char *buf, size_t len); void mg_secure_transport_destroy(mg_transport *); +#endif #ifdef __cplusplus } diff --git a/src/mgwasm.c b/src/mgwasm.c new file mode 100644 index 0000000..78ffdea --- /dev/null +++ b/src/mgwasm.c @@ -0,0 +1,59 @@ +#include "mgwasm.h" + +#include +#include + +#include "emscripten.h" + +int read_loop(const int sock) { + fd_set fdr; + FD_ZERO(&fdr); + FD_SET(sock, &fdr); + int poll = select(sock + 1, &fdr, NULL, NULL, NULL); + if (poll == -1) { + return -1; + } + if (!FD_ISSET(sock, &fdr)) { + return -100; + } + return 1; +} + +int write_loop(const int sock) { + fd_set fdw; + FD_ZERO(&fdw); + FD_SET(sock, &fdw); + const int poll = select(sock + 1, NULL, &fdw, NULL, NULL); + if (poll == -1) { + return -1; + } + if (!FD_ISSET(sock, &fdw)) { + return -100; + } + return 1; +} + +static const size_t DELAY_MS = 10; + +int mg_wasm_suspend_until_ready_to_read(const int sock) { + while (1) { + const int res = read_loop(sock); + if (res == 1 || res == -1) { + return res; + } + emscripten_sleep(DELAY_MS); + } +} + +int mg_wasm_suspend_until_ready_to_write(const int sock) { + while (1) { + const int res = write_loop(sock); + if (res == 1 || res == -1) { + return res; + } + if (res == -1) { + return -1; + } + emscripten_sleep(DELAY_MS); + } +} diff --git a/src/mgwasm.h b/src/mgwasm.h new file mode 100644 index 0000000..54fdb66 --- /dev/null +++ b/src/mgwasm.h @@ -0,0 +1,7 @@ +#ifndef MGCLIENT_MGWASM_H +#define MGCLIENT_MGWASM_H + +int mg_wasm_suspend_until_ready_to_read(int sock); +int mg_wasm_suspend_until_ready_to_write(int sock); + +#endif /* MGCLIENT_MGWASM_H */ diff --git a/tests/client.cpp b/tests/client.cpp index 997127e..9cdc813 100644 --- a/tests/client.cpp +++ b/tests/client.cpp @@ -12,18 +12,17 @@ // See the License for the specific language governing permissions and // limitations under the License. +#include +#include + #include #include #include -#include -#include - #include "mgclient.h" #include "mgcommon.h" #include "mgsession.h" #include "mgsocket.h" - #include "test-common.hpp" using namespace std::string_literals; @@ -70,6 +69,8 @@ struct test_transport { int (*send)(struct mg_transport *, const char *buf, size_t len); int (*recv)(struct mg_transport *, char *buf, size_t len); void (*destroy)(struct mg_transport *); + void (*suspend_until_ready_to_read)(struct mg_transport *); + void (*suspend_until_ready_to_write)(struct mg_transport *); union { struct { SSL *ssl; @@ -98,6 +99,8 @@ int __wrap_mg_secure_transport_init(int sockfd, const char *cert, ttransport->send = mg_raw_transport_send; ttransport->recv = mg_raw_transport_recv; ttransport->destroy = test_transport_destroy; + ttransport->suspend_until_ready_to_read = NULL; + ttransport->suspend_until_ready_to_write = NULL; ttransport->peer_pubkey_type = "rsaEncryption"; ttransport->peer_pubkey_fp = TEST_KEY_FP; *transport = (mg_secure_transport *)ttransport; diff --git a/wasm/install_deps.sh b/wasm/install_deps.sh new file mode 100755 index 0000000..76c948a --- /dev/null +++ b/wasm/install_deps.sh @@ -0,0 +1,6 @@ +#!/bin/bash + +DIR="$( cd "$( dirname "${BASH_SOURCE[0]}" )" && pwd )" +git clone https://github.com/emscripten-core/emsdk.git ${DIR}/emsdk +${DIR}/emsdk/emsdk install latest +${DIR}/emsdk/emsdk activate latest