From 9d2fe5b0074f3f35a7a8536e7744f269ed959b68 Mon Sep 17 00:00:00 2001 From: TheoryOfNekomata Date: Mon, 13 Jun 2022 08:35:01 +0800 Subject: [PATCH] Implement a more involved example Use minimal server echo example instead. --- CMakeLists.txt | 8 +- src/packages/game/log/IZ_log.c | 24 ++ src/packages/game/log/IZ_log.h | 10 + src/packages/server/IZ_app.c | 57 +++++ src/packages/server/IZ_app.h | 33 +++ src/packages/server/log | 1 + src/packages/server/main.c | 143 ++++++------ src/packages/server/protocol_lws_minimal.c | 143 ------------ .../server/protocol_lws_minimal_server_echo.c | 219 ++++++++++++++++++ 9 files changed, 428 insertions(+), 210 deletions(-) create mode 100644 src/packages/game/log/IZ_log.c create mode 100644 src/packages/game/log/IZ_log.h create mode 100644 src/packages/server/IZ_app.c create mode 100644 src/packages/server/IZ_app.h create mode 120000 src/packages/server/log delete mode 100644 src/packages/server/protocol_lws_minimal.c create mode 100644 src/packages/server/protocol_lws_minimal_server_echo.c diff --git a/CMakeLists.txt b/CMakeLists.txt index 6dffe1f..2a5d754 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -70,7 +70,7 @@ add_executable( src/packages/game/input/IZ_midi.h src/packages/game/data/IZ_list.c src/packages/game/data/IZ_list.h - src/packages/game/network/IZ_wsclient.c src/packages/game/network/IZ_wsclient.h) + src/packages/game/network/IZ_wsclient.c src/packages/game/network/IZ_wsclient.h src/packages/game/log/IZ_log.c src/packages/game/log/IZ_log.h) target_link_libraries( game @@ -170,11 +170,15 @@ add_executable( dependencies/minIni/dev/minIni.h dependencies/minIni/dev/minIni.c src/packages/server/IZ_common.h + src/packages/server/log/IZ_log.h + src/packages/server/log/IZ_log.c src/packages/server/main.c - src/packages/server/protocol_lws_minimal.c) + src/packages/server/protocol_lws_minimal_server_echo.c src/packages/server/IZ_app.c src/packages/server/IZ_app.h) target_link_libraries( server + SDL2main + SDL2 libcrypto libssl websockets diff --git a/src/packages/game/log/IZ_log.c b/src/packages/game/log/IZ_log.c new file mode 100644 index 0000000..8378cc7 --- /dev/null +++ b/src/packages/game/log/IZ_log.c @@ -0,0 +1,24 @@ +#include "IZ_log.h" + +void IZ_LogHandleFromWS(i32 level, const char* line) { + switch (level) { + case LLL_ERR: + SDL_LogError(SDL_LOG_CATEGORY_ERROR, "%s", line); + return; + case LLL_WARN: + SDL_LogWarn(SDL_LOG_CATEGORY_APPLICATION, "%s", line); + return; + case LLL_NOTICE: + SDL_LogInfo(SDL_LOG_CATEGORY_APPLICATION, "%s", line); + return; + case LLL_USER: + default: + break; + } + + SDL_Log("%s", line); +} + +void IZ_LogInterceptWSMessages(i32 level) { + lws_set_log_level(level, IZ_LogHandleFromWS); +} diff --git a/src/packages/game/log/IZ_log.h b/src/packages/game/log/IZ_log.h new file mode 100644 index 0000000..09f586c --- /dev/null +++ b/src/packages/game/log/IZ_log.h @@ -0,0 +1,10 @@ +#ifndef IZ_LOG_H +#define IZ_LOG_H + +#include +#include +#include "../IZ_common.h" + +void IZ_LogInterceptWSMessages(i32); + +#endif diff --git a/src/packages/server/IZ_app.c b/src/packages/server/IZ_app.c new file mode 100644 index 0000000..442cb3a --- /dev/null +++ b/src/packages/server/IZ_app.c @@ -0,0 +1,57 @@ +#include "IZ_app.h" + +void IZ_AppHandleSignal(i32 _signal) { + interrupted = true; +} + +void IZ_AppCreateContext(IZ_App* app) { + struct lws_context_creation_info info; + memset(&info, 0, sizeof info); + info.port = app->config.port; + // TODO initialize protocols + info.options = ( + LWS_SERVER_OPTION_VALIDATE_UTF8 + | LWS_SERVER_OPTION_HTTP_HEADERS_SECURITY_BEST_PRACTICES_ENFORCE + ); +} + +void IZ_AppLoadConfig(IZ_AppConfig* config, u8 argc, const char** argv) { + memcpy_s(config, sizeof(IZ_AppConfig), &IZ_APP_DEFAULT_CONFIG, sizeof(IZ_AppConfig)); + + const char* cmdline_buffer; + if ((cmdline_buffer = lws_cmdline_option(argc, argv, "-d"))) { + config->log_level = atoi(cmdline_buffer); + } + + if ((cmdline_buffer = lws_cmdline_option(argc, argv, "-p"))) { + config->port = atoi(cmdline_buffer); + } + + if (lws_cmdline_option(argc, argv, "-o")) { + // connect once + config->vhost_options |= 1; + } + + if (!lws_cmdline_option(argc, argv, "-n")) { + config->extensions_enabled = true; + } +} + +IZ_ProcedureResult IZ_AppInitialize(IZ_App* app, u8 argc, const char** argv) { + interrupted = false; + signal(SIGINT, IZ_AppHandleSignal); + + IZ_AppLoadConfig(&app->config, argc, argv); + IZ_LogInterceptWSMessages(app->config.log_level); + IZ_AppCreateContext(app); + + return 0; +} + +IZ_ProcedureResult IZ_AppRun(IZ_App* app, u8 argc, const char** argv) { + if (IZ_AppInitialize(app, argc, argv)) { + return 1; + } + + return 0; +} diff --git a/src/packages/server/IZ_app.h b/src/packages/server/IZ_app.h new file mode 100644 index 0000000..e30b664 --- /dev/null +++ b/src/packages/server/IZ_app.h @@ -0,0 +1,33 @@ +#ifndef IZ_APP_H +#define IZ_APP_H + +#include +#include +#include +#include "IZ_common.h" +#include "log/IZ_log.h" + +static bool interrupted; + +typedef struct { + i32 log_level; + u16 port; + i32 vhost_options; + bool extensions_enabled; +} IZ_AppConfig; + +typedef struct { + IZ_AppConfig config; + struct lws_context* context; +} IZ_App; + +static const IZ_AppConfig IZ_APP_DEFAULT_CONFIG = { + .log_level = LLL_USER | LLL_ERR | LLL_WARN | LLL_NOTICE, + .port = 42069, + .vhost_options = 0, + .extensions_enabled = false, +}; + +IZ_ProcedureResult IZ_AppRun(IZ_App*, u8, const char**); + +#endif diff --git a/src/packages/server/log b/src/packages/server/log new file mode 120000 index 0000000..507816d --- /dev/null +++ b/src/packages/server/log @@ -0,0 +1 @@ +E:/Projects/Games/izanagi/src/packages/game/log \ No newline at end of file diff --git a/src/packages/server/main.c b/src/packages/server/main.c index aac4eb2..7d9e83a 100644 --- a/src/packages/server/main.c +++ b/src/packages/server/main.c @@ -1,104 +1,117 @@ #include +#include #include + #include "IZ_common.h" +#include "log/IZ_log.h" #define LWS_PLUGIN_STATIC -#include "protocol_lws_minimal.c" +#include "protocol_lws_minimal_server_echo.c" static struct lws_protocols protocols[] = { - { "http", lws_callback_http_dummy, 0, 0, 0, NULL, 0}, - LWS_PLUGIN_PROTOCOL_MINIMAL, + LWS_PLUGIN_PROTOCOL_MINIMAL_SERVER_ECHO, LWS_PROTOCOL_LIST_TERM }; -static const lws_retry_bo_t retry = { - .secs_since_valid_ping = 3, - .secs_since_valid_hangup = 10, +static int interrupted, port = 7681, options; + +/* pass pointers to shared vars to the protocol */ + +static const struct lws_protocol_vhost_options pvo_options = { + NULL, + NULL, + "options", /* pvo name */ + (void *)&options /* pvo value */ }; -static i32 interrupted; - -static const struct lws_http_mount mount = { - .mount_next = NULL, /* linked-list "next" */ - .mountpoint = "/", /* mountpoint URL */ - .origin = "./mount-origin", /* serve from dir */ - .def = "index.html", /* default filename */ - .protocol = NULL, - .cgienv = NULL, - .extra_mimetypes = NULL, - .interpret = NULL, - .cgi_timeout = 0, - .cache_max_age = 0, - .auth_mask = 0, - .cache_reusable = 0, - .cache_revalidate = 0, - .cache_intermediaries = 0, - .origin_protocol = LWSMPRO_FILE, /* files in a dir */ - .mountpoint_len = 1, /* char count */ - .basic_auth_login_file = NULL, +static const struct lws_protocol_vhost_options pvo_interrupted = { + &pvo_options, + NULL, + "interrupted", /* pvo name */ + (void *)&interrupted /* pvo value */ }; -#if defined(LWS_WITH_PLUGINS) -/* if plugins enabled, only protocols explicitly named in pvo bind to vhost */ -static struct lws_protocol_vhost_options pvo = { NULL, NULL, "lws-minimal", "" }; -#endif +static const struct lws_protocol_vhost_options pvo = { + NULL, /* "next" pvo linked-list */ + &pvo_interrupted, /* "child" pvo linked-list */ + "lws-minimal-server-echo", /* protocol name we belong to on this vhost */ + "" /* ignored */ +}; +static const struct lws_extension extensions[] = { + { + "permessage-deflate", + lws_extension_callback_pm_deflate, + "permessage-deflate" + "; client_no_context_takeover" + "; client_max_window_bits" + }, + { NULL, NULL, NULL /* terminator */ } +}; -void sigint_handler(i32 sig) { +void sigint_handler(int sig) +{ interrupted = 1; } -IZ_ProcedureResult main(i32 arg_count, char* arg_values[]) { - struct lws_context_creation_info info; - struct lws_context* context; - const char* p; - i32 n = 0; - i32 logs = LLL_USER | LLL_ERR | LLL_WARN | LLL_NOTICE; +IZ_ProcedureResult main(i32 argc, const char **argv) +{ + const char *cmdline_buffer; + i32 logs = LLL_USER | LLL_ERR | LLL_WARN | LLL_NOTICE + /* for LLL_ verbosity above NOTICE to be built into lws, + * lws must have been configured and built with + * -DCMAKE_BUILD_TYPE=DEBUG instead of =RELEASE */ + /* | LLL_INFO */ /* | LLL_PARSER */ /* | LLL_HEADER */ + /* | LLL_EXT */ /* | LLL_CLIENT */ /* | LLL_LATENCY */ + /* | LLL_DEBUG */; signal(SIGINT, sigint_handler); + if ((cmdline_buffer = lws_cmdline_option(argc, argv, "-d"))) { + logs = atoi(cmdline_buffer); + } - if ((p = lws_cmdline_option(arg_count, arg_values, "-d"))) - logs = atoi(p); + IZ_LogInterceptWSMessages(logs); + lwsl_user("LWS minimal ws client echo + permessage-deflate + multifragment bulk message\n"); + lwsl_user(" lws-minimal-ws-client-echo [-n (no exts)] [-p port] [-o (once)]\n"); - lws_set_log_level(logs, NULL); - lwsl_user("LWS minimal ws server | visit http://localhost:7681 (-s = use TLS / https)\n"); + if ((cmdline_buffer = lws_cmdline_option(argc, argv, "-p"))) { + port = atoi(cmdline_buffer); + } + if (lws_cmdline_option(argc, argv, "-o")) { + // connect once + options |= 1; + } + + struct lws_context_creation_info info; memset(&info, 0, sizeof info); /* otherwise uninitialized garbage */ - info.port = 6969; - info.mounts = &mount; + info.port = port; info.protocols = protocols; - info.vhost_name = "localhost"; -#if defined(LWS_WITH_PLUGINS) info.pvo = &pvo; -#endif - info.options = - LWS_SERVER_OPTION_HTTP_HEADERS_SECURITY_BEST_PRACTICES_ENFORCE; - -#if defined(LWS_WITH_TLS) - if (lws_cmdline_option(arg_count, arg_values, "-s")) { - lwsl_user("Server using TLS\n"); - info.options |= LWS_SERVER_OPTION_DO_SSL_GLOBAL_INIT; - info.ssl_cert_filepath = "localhost-100y.cert"; - info.ssl_private_key_filepath = "localhost-100y.key"; - } -#endif - - if (lws_cmdline_option(arg_count, arg_values, "-h")) - info.options |= LWS_SERVER_OPTION_VHOST_UPG_STRICT_HOST_CHECK; - - if (lws_cmdline_option(arg_count, arg_values, "-v")) - info.retry_and_idle_policy = &retry; + if (!lws_cmdline_option(argc, argv, "-n")) + info.extensions = extensions; + info.pt_serv_buf_size = 32 * 1024; + info.options = LWS_SERVER_OPTION_VALIDATE_UTF8 | + LWS_SERVER_OPTION_HTTP_HEADERS_SECURITY_BEST_PRACTICES_ENFORCE; + struct lws_context *context; context = lws_create_context(&info); if (!context) { lwsl_err("lws init failed\n"); return 1; } - while (n >= 0 && !interrupted) + i32 n; + while (!interrupted) { n = lws_service(context, 0); + printf("%d\n", n); + if (n < 0) { + break; + } + } lws_context_destroy(context); + lwsl_user("Completed %s\n", interrupted == 2 ? "OK" : "failed"); - return 0; + return interrupted != 2; } diff --git a/src/packages/server/protocol_lws_minimal.c b/src/packages/server/protocol_lws_minimal.c deleted file mode 100644 index a7b1980..0000000 --- a/src/packages/server/protocol_lws_minimal.c +++ /dev/null @@ -1,143 +0,0 @@ - -#if !defined (LWS_PLUGIN_STATIC) -#define LWS_DLL -#define LWS_INTERNAL -#include -#endif - -#include - -/* one of these created for each message */ - -struct msg { - void *payload; /* is malloc'd */ - size_t len; -}; - -/* one of these is created for each client connecting to us */ - -struct per_session_data__minimal { - struct per_session_data__minimal *pss_list; - struct lws *wsi; - int last; /* the last message number we sent */ -}; - -/* one of these is created for each vhost our protocol is used with */ - -struct per_vhost_data__minimal { - struct lws_context *context; - struct lws_vhost *vhost; - const struct lws_protocols *protocol; - - struct per_session_data__minimal *pss_list; /* linked-list of live pss*/ - - struct msg amsg; /* the one pending message... */ - int current; /* the current message number we are caching */ -}; - -/* destroys the message when everyone has had a copy of it */ - -static void -__minimal_destroy_message(void *_msg) -{ - struct msg *msg = _msg; - - free(msg->payload); - msg->payload = NULL; - msg->len = 0; -} - -static int -callback_minimal(struct lws *wsi, enum lws_callback_reasons reason, - void *user, void *in, size_t len) -{ - struct per_session_data__minimal *pss = - (struct per_session_data__minimal *)user; - struct per_vhost_data__minimal *vhd = - (struct per_vhost_data__minimal *) - lws_protocol_vh_priv_get(lws_get_vhost(wsi), - lws_get_protocol(wsi)); - int m; - - switch (reason) { - case LWS_CALLBACK_PROTOCOL_INIT: - vhd = lws_protocol_vh_priv_zalloc(lws_get_vhost(wsi), - lws_get_protocol(wsi), - sizeof(struct per_vhost_data__minimal)); - vhd->context = lws_get_context(wsi); - vhd->protocol = lws_get_protocol(wsi); - vhd->vhost = lws_get_vhost(wsi); - break; - - case LWS_CALLBACK_ESTABLISHED: - /* add ourselves to the list of live pss held in the vhd */ - lws_ll_fwd_insert(pss, pss_list, vhd->pss_list); - pss->wsi = wsi; - pss->last = vhd->current; - break; - - case LWS_CALLBACK_CLOSED: - /* remove our closing pss from the list of live pss */ - lws_ll_fwd_remove(struct per_session_data__minimal, pss_list, - pss, vhd->pss_list); - break; - - case LWS_CALLBACK_SERVER_WRITEABLE: - if (!vhd->amsg.payload) - break; - - if (pss->last == vhd->current) - break; - - /* notice we allowed for LWS_PRE in the payload already */ - m = lws_write(wsi, ((unsigned char *)vhd->amsg.payload) + - LWS_PRE, vhd->amsg.len, LWS_WRITE_TEXT); - if (m < (int)vhd->amsg.len) { - lwsl_err("ERROR %d writing to ws\n", m); - return -1; - } - - pss->last = vhd->current; - break; - - case LWS_CALLBACK_RECEIVE: - if (vhd->amsg.payload) - __minimal_destroy_message(&vhd->amsg); - - vhd->amsg.len = len; - /* notice we over-allocate by LWS_PRE */ - vhd->amsg.payload = malloc(LWS_PRE + len); - if (!vhd->amsg.payload) { - lwsl_user("OOM: dropping\n"); - break; - } - - memcpy((char *)vhd->amsg.payload + LWS_PRE, in, len); - vhd->current++; - - /* - * let everybody know we want to write something on them - * as soon as they are ready - */ - lws_start_foreach_llp(struct per_session_data__minimal **, - ppss, vhd->pss_list) { - lws_callback_on_writable((*ppss)->wsi); - } lws_end_foreach_llp(ppss, pss_list); - break; - - default: - break; - } - - return 0; -} - -#define LWS_PLUGIN_PROTOCOL_MINIMAL \ - { \ - "lws-minimal", \ - callback_minimal, \ - sizeof(struct per_session_data__minimal), \ - 128, \ - 0, NULL, 0 \ - } - diff --git a/src/packages/server/protocol_lws_minimal_server_echo.c b/src/packages/server/protocol_lws_minimal_server_echo.c new file mode 100644 index 0000000..ae8419d --- /dev/null +++ b/src/packages/server/protocol_lws_minimal_server_echo.c @@ -0,0 +1,219 @@ +#ifndef LWS_PLUGIN_STATIC +#define LWS_DLL +#define LWS_INTERNAL + +#include +#endif + +#include + +#define RING_DEPTH 4096 + +/* one of these created for each message */ + +struct msg { + void *payload; /* is malloc'd */ + size_t len; + char binary; + char first; + char final; +}; + +struct per_session_data__minimal_server_echo { + struct lws_ring *ring; + uint32_t msglen; + uint32_t tail; + uint8_t completed: 1; + uint8_t flow_controlled: 1; + uint8_t write_consume_pending: 1; +}; + +struct vhd_minimal_server_echo { + struct lws_context *context; + struct lws_vhost *vhost; + + int *interrupted; + int *options; +}; + +static void +__minimal_destroy_message(void *_msg) { + struct msg *msg = _msg; + + free(msg->payload); + msg->payload = NULL; + msg->len = 0; +} + +static int +callback_minimal_server_echo(struct lws *wsi, enum lws_callback_reasons reason, + void *user, void *in, size_t len) { + struct per_session_data__minimal_server_echo *pss = + (struct per_session_data__minimal_server_echo *) user; + struct vhd_minimal_server_echo *vhd = (struct vhd_minimal_server_echo *) + lws_protocol_vh_priv_get(lws_get_vhost(wsi), + lws_get_protocol(wsi)); + const struct msg *pmsg; + struct msg amsg; + int m, n, flags; + + switch (reason) { + + case LWS_CALLBACK_PROTOCOL_INIT: + vhd = lws_protocol_vh_priv_zalloc(lws_get_vhost(wsi), + lws_get_protocol(wsi), + sizeof(struct vhd_minimal_server_echo)); + if (!vhd) + return -1; + + vhd->context = lws_get_context(wsi); + vhd->vhost = lws_get_vhost(wsi); + + /* get the pointers we were passed in pvo */ + + vhd->interrupted = (int *) lws_pvo_search( + (const struct lws_protocol_vhost_options *) in, + "interrupted")->value; + vhd->options = (int *) lws_pvo_search( + (const struct lws_protocol_vhost_options *) in, + "options")->value; + break; + + case LWS_CALLBACK_ESTABLISHED: + /* generate a block of output before travis times us out */ + lwsl_warn("LWS_CALLBACK_ESTABLISHED\n"); + pss->ring = lws_ring_create(sizeof(struct msg), RING_DEPTH, + __minimal_destroy_message); + if (!pss->ring) + return 1; + pss->tail = 0; + break; + + case LWS_CALLBACK_SERVER_WRITEABLE: + + lwsl_user("LWS_CALLBACK_SERVER_WRITEABLE\n"); + + if (pss->write_consume_pending) { + /* perform the deferred fifo consume */ + lws_ring_consume_single_tail(pss->ring, &pss->tail, 1); + pss->write_consume_pending = 0; + } + + pmsg = lws_ring_get_element(pss->ring, &pss->tail); + if (!pmsg) { + lwsl_user(" (nothing in ring)\n"); + break; + } + + flags = lws_write_ws_flags( + pmsg->binary ? LWS_WRITE_BINARY : LWS_WRITE_TEXT, + pmsg->first, pmsg->final); + + /* notice we allowed for LWS_PRE in the payload already */ + m = lws_write(wsi, ((unsigned char *) pmsg->payload) + + LWS_PRE, pmsg->len, (enum lws_write_protocol) flags); + if (m < (int) pmsg->len) { + lwsl_err("ERROR %d writing to ws socket\n", m); + return -1; + } + + lwsl_user(" wrote %d: flags: 0x%x first: %d final %d\n", + m, flags, pmsg->first, pmsg->final); + /* + * Workaround deferred deflate in pmd extension by only + * consuming the fifo entry when we are certain it has been + * fully deflated at the next WRITABLE callback. You only need + * this if you're using pmd. + */ + pss->write_consume_pending = 1; + lws_callback_on_writable(wsi); + + if (pss->flow_controlled && + (int) lws_ring_get_count_free_elements(pss->ring) > RING_DEPTH - 5) { + lws_rx_flow_control(wsi, 1); + pss->flow_controlled = 0; + } + + if ((*vhd->options & 1) && pmsg && pmsg->final) + pss->completed = 1; + + break; + + case LWS_CALLBACK_RECEIVE: + + lwsl_user("LWS_CALLBACK_RECEIVE: %4d (rpp %5d, first %d, " + "last %d, bin %d, msglen %d (+ %d = %d))\n", + (int) len, (int) lws_remaining_packet_payload(wsi), + lws_is_first_fragment(wsi), + lws_is_final_fragment(wsi), + lws_frame_is_binary(wsi), pss->msglen, (int) len, + (int) pss->msglen + (int) len); + + if (len) { ; + //puts((const char *)in); + //lwsl_hexdump_notice(in, len); + } + + amsg.first = (char) lws_is_first_fragment(wsi); + amsg.final = (char) lws_is_final_fragment(wsi); + amsg.binary = (char) lws_frame_is_binary(wsi); + n = (int) lws_ring_get_count_free_elements(pss->ring); + if (!n) { + lwsl_user("dropping!\n"); + break; + } + + if (amsg.final) + pss->msglen = 0; + else + pss->msglen += (uint32_t) len; + + amsg.len = len; + /* notice we over-allocate by LWS_PRE */ + amsg.payload = malloc(LWS_PRE + len); + if (!amsg.payload) { + lwsl_user("OOM: dropping\n"); + break; + } + + memcpy((char *) amsg.payload + LWS_PRE, in, len); + if (!lws_ring_insert(pss->ring, &amsg, 1)) { + __minimal_destroy_message(&amsg); + lwsl_user("dropping!\n"); + break; + } + lws_callback_on_writable(wsi); + + if (n < 3 && !pss->flow_controlled) { + pss->flow_controlled = 1; + lws_rx_flow_control(wsi, 0); + } + break; + + case LWS_CALLBACK_CLOSED: + lwsl_user("LWS_CALLBACK_CLOSED\n"); + lws_ring_destroy(pss->ring); + + if (*vhd->options & 1) { + if (!*vhd->interrupted) + *vhd->interrupted = 1 + pss->completed; + lws_cancel_service(lws_get_context(wsi)); + } + break; + + default: + break; + } + + return 0; +} + +#define LWS_PLUGIN_PROTOCOL_MINIMAL_SERVER_ECHO \ + { \ + "lws-minimal-server-echo", \ + callback_minimal_server_echo, \ + sizeof(struct per_session_data__minimal_server_echo), \ + 1024, \ + 0, NULL, 0 \ + } +