From 7742dffae4192e73c258493a7320eb11b6437b8a Mon Sep 17 00:00:00 2001 From: Shane Bryldt Date: Thu, 16 Mar 2017 13:13:26 -0500 Subject: [PATCH] FS-9952: Initial implementation of a very basic text chat system which introduced a number of supporting subsystems --- libs/libblade/Makefile.am | 2 +- libs/libblade/src/blade_connection.c | 1 + libs/libblade/src/blade_module.c | 21 +- libs/libblade/src/blade_module_chat.c | 432 +++++++++++++++++++++ libs/libblade/src/blade_module_wss.c | 17 +- libs/libblade/src/blade_protocol.c | 81 +++- libs/libblade/src/blade_session.c | 227 ++++++++--- libs/libblade/src/blade_space.c | 11 +- libs/libblade/src/blade_stack.c | 214 +++++++++- libs/libblade/src/include/blade_module.h | 18 +- libs/libblade/src/include/blade_protocol.h | 3 + libs/libblade/src/include/blade_session.h | 7 + libs/libblade/src/include/blade_space.h | 3 +- libs/libblade/src/include/blade_stack.h | 8 + libs/libblade/src/include/blade_types.h | 26 +- libs/libblade/test/Makefile.am | 5 + libs/libblade/test/bladec.c | 156 +++++--- libs/libblade/test/bladec.cfg | 19 +- libs/libblade/test/bladec2.cfg | 11 - libs/libblade/test/blades.c | 207 ++++++++++ libs/libblade/test/blades.cfg | 28 ++ 21 files changed, 1325 insertions(+), 172 deletions(-) create mode 100644 libs/libblade/src/blade_module_chat.c delete mode 100644 libs/libblade/test/bladec2.cfg create mode 100644 libs/libblade/test/blades.c create mode 100644 libs/libblade/test/blades.cfg diff --git a/libs/libblade/Makefile.am b/libs/libblade/Makefile.am index 9346c3249a..f3ad18d3e0 100644 --- a/libs/libblade/Makefile.am +++ b/libs/libblade/Makefile.am @@ -15,7 +15,7 @@ libblade_la_SOURCES = src/blade.c src/blade_stack.c libblade_la_SOURCES += src/blade_datastore.c libblade_la_SOURCES += src/blade_identity.c src/blade_module.c src/blade_connection.c libblade_la_SOURCES += src/blade_session.c src/blade_protocol.c src/blade_space.c src/blade_method.c -libblade_la_SOURCES += src/blade_module_wss.c +libblade_la_SOURCES += src/blade_module_wss.c src/blade_module_chat.c libblade_la_CFLAGS = $(AM_CFLAGS) $(AM_CPPFLAGS) libblade_la_LDFLAGS = -version-info 0:1:0 -lncurses -lpthread -lm -lconfig $(AM_LDFLAGS) libblade_la_LIBADD = libunqlite.la diff --git a/libs/libblade/src/blade_connection.c b/libs/libblade/src/blade_connection.c index f8cfaead54..d757845b6b 100644 --- a/libs/libblade/src/blade_connection.c +++ b/libs/libblade/src/blade_connection.c @@ -532,6 +532,7 @@ ks_status_t blade_connection_state_on_ready(blade_connection_t *bc) if (callback) hook = callback(bc, BLADE_CONNECTION_STATE_CONDITION_POST); if (hook == BLADE_CONNECTION_STATE_HOOK_DISCONNECT) blade_connection_disconnect(bc); + else ks_sleep_ms(1); return KS_STATUS_SUCCESS; } diff --git a/libs/libblade/src/blade_module.c b/libs/libblade/src/blade_module.c index a874e45ffc..403748b0fc 100644 --- a/libs/libblade/src/blade_module.c +++ b/libs/libblade/src/blade_module.c @@ -1,23 +1,23 @@ /* * Copyright (c) 2017, Shane Bryldt * All rights reserved. - * + * * Redistribution and use in source and binary forms, with or without * modification, are permitted provided that the following conditions * are met: - * + * * * Redistributions of source code must retain the above copyright * notice, this list of conditions and the following disclaimer. - * + * * * Redistributions in binary form must reproduce the above copyright * notice, this list of conditions and the following disclaimer in the * documentation and/or other materials provided with the distribution. - * + * * * Neither the name of the original author; nor the names of any contributors * may be used to endorse or promote products derived from this software * without specific prior written permission. - * - * + * + * * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS * "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT * LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR @@ -78,6 +78,13 @@ KS_DECLARE(ks_status_t) blade_module_destroy(blade_module_t **bmP) return KS_STATUS_SUCCESS; } +KS_DECLARE(blade_handle_t *) blade_module_handle_get(blade_module_t *bm) +{ + ks_assert(bm); + + return bm->handle; +} + KS_DECLARE(void *) blade_module_data_get(blade_module_t *bm) { ks_assert(bm); @@ -85,7 +92,7 @@ KS_DECLARE(void *) blade_module_data_get(blade_module_t *bm) return bm->module_data; } - + /* For Emacs: * Local Variables: * mode:c diff --git a/libs/libblade/src/blade_module_chat.c b/libs/libblade/src/blade_module_chat.c new file mode 100644 index 0000000000..a9fbfc3b3a --- /dev/null +++ b/libs/libblade/src/blade_module_chat.c @@ -0,0 +1,432 @@ +/* + * Copyright (c) 2017, Shane Bryldt + * All rights reserved. + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions + * are met: + * + * * Redistributions of source code must retain the above copyright + * notice, this list of conditions and the following disclaimer. + * + * * Redistributions in binary form must reproduce the above copyright + * notice, this list of conditions and the following disclaimer in the + * documentation and/or other materials provided with the distribution. + * + * * Neither the name of the original author; nor the names of any contributors + * may be used to endorse or promote products derived from this software + * without specific prior written permission. + * + * + * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS + * "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT + * LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR + * A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER + * OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, + * EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, + * PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR + * PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF + * LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING + * NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS + * SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. + */ + +#include "blade.h" + +typedef struct blade_module_chat_s blade_module_chat_t; + +struct blade_module_chat_s { + blade_handle_t *handle; + ks_pool_t *pool; + ks_thread_pool_t *tpool; + blade_module_t *module; + blade_module_callbacks_t *module_callbacks; + + const char *session_state_callback_id; + list_t participants; +}; + + +ks_status_t blade_module_chat_create(blade_module_chat_t **bm_chatP, blade_handle_t *bh); +ks_status_t blade_module_chat_destroy(blade_module_chat_t **bm_chatP); + +// @todo remove exporting this, it's only temporary until DSO loading is in place so wss module can be loaded +KS_DECLARE(ks_status_t) blade_module_chat_on_load(blade_module_t **bmP, blade_handle_t *bh); +KS_DECLARE(ks_status_t) blade_module_chat_on_unload(blade_module_t *bm); +KS_DECLARE(ks_status_t) blade_module_chat_on_startup(blade_module_t *bm, config_setting_t *config); +KS_DECLARE(ks_status_t) blade_module_chat_on_shutdown(blade_module_t *bm); + +void blade_module_chat_on_session_state(blade_session_t *bs, blade_session_state_condition_t condition, void *data); + +ks_bool_t blade_chat_join_request_handler(blade_module_t *bm, blade_request_t *breq); +ks_bool_t blade_chat_leave_request_handler(blade_module_t *bm, blade_request_t *breq); +ks_bool_t blade_chat_send_request_handler(blade_module_t *bm, blade_request_t *breq); + +static blade_module_callbacks_t g_module_chat_callbacks = +{ + blade_module_chat_on_load, + blade_module_chat_on_unload, + blade_module_chat_on_startup, + blade_module_chat_on_shutdown, +}; + + + +ks_status_t blade_module_chat_create(blade_module_chat_t **bm_chatP, blade_handle_t *bh) +{ + blade_module_chat_t *bm_chat = NULL; + ks_pool_t *pool = NULL; + + ks_assert(bm_chatP); + ks_assert(bh); + + pool = blade_handle_pool_get(bh); + + bm_chat = ks_pool_alloc(pool, sizeof(blade_module_chat_t)); + bm_chat->handle = bh; + bm_chat->pool = pool; + bm_chat->tpool = blade_handle_tpool_get(bh); + bm_chat->session_state_callback_id = NULL; + list_init(&bm_chat->participants); + + blade_module_create(&bm_chat->module, bh, bm_chat, &g_module_chat_callbacks); + bm_chat->module_callbacks = &g_module_chat_callbacks; + + *bm_chatP = bm_chat; + + ks_log(KS_LOG_DEBUG, "Created\n"); + + return KS_STATUS_SUCCESS; +} + +ks_status_t blade_module_chat_destroy(blade_module_chat_t **bm_chatP) +{ + blade_module_chat_t *bm_chat = NULL; + + ks_assert(bm_chatP); + ks_assert(*bm_chatP); + + bm_chat = *bm_chatP; + + blade_module_chat_on_shutdown(bm_chat->module); + + list_destroy(&bm_chat->participants); + + blade_module_destroy(&bm_chat->module); + + ks_pool_free(bm_chat->pool, bm_chatP); + + ks_log(KS_LOG_DEBUG, "Destroyed\n"); + + return KS_STATUS_SUCCESS; +} + +KS_DECLARE(ks_status_t) blade_module_chat_on_load(blade_module_t **bmP, blade_handle_t *bh) +{ + blade_module_chat_t *bm_chat = NULL; + + ks_assert(bmP); + ks_assert(bh); + + blade_module_chat_create(&bm_chat, bh); + ks_assert(bm_chat); + + *bmP = bm_chat->module; + + ks_log(KS_LOG_DEBUG, "Loaded\n"); + + return KS_STATUS_SUCCESS; +} + +KS_DECLARE(ks_status_t) blade_module_chat_on_unload(blade_module_t *bm) +{ + blade_module_chat_t *bm_chat = NULL; + + ks_assert(bm); + + bm_chat = blade_module_data_get(bm); + + blade_module_chat_destroy(&bm_chat); + + ks_log(KS_LOG_DEBUG, "Unloaded\n"); + + return KS_STATUS_SUCCESS; +} + +ks_status_t blade_module_chat_config(blade_module_chat_t *bm_chat, config_setting_t *config) +{ + config_setting_t *chat = NULL; + + ks_assert(bm_chat); + ks_assert(config); + + if (!config_setting_is_group(config)) { + ks_log(KS_LOG_DEBUG, "!config_setting_is_group(config)\n"); + return KS_STATUS_FAIL; + } + + chat = config_setting_get_member(config, "chat"); + if (chat) { + } + + + // Configuration is valid, now assign it to the variables that are used + // If the configuration was invalid, then this does not get changed + + ks_log(KS_LOG_DEBUG, "Configured\n"); + + return KS_STATUS_SUCCESS; +} + +KS_DECLARE(ks_status_t) blade_module_chat_on_startup(blade_module_t *bm, config_setting_t *config) +{ + blade_module_chat_t *bm_chat = NULL; + blade_space_t *space = NULL; + blade_method_t *method = NULL; + + ks_assert(bm); + ks_assert(config); + + bm_chat = (blade_module_chat_t *)blade_module_data_get(bm); + + if (blade_module_chat_config(bm_chat, config) != KS_STATUS_SUCCESS) { + ks_log(KS_LOG_DEBUG, "blade_module_chat_config failed\n"); + return KS_STATUS_FAIL; + } + + blade_space_create(&space, bm_chat->handle, bm, "blade.chat"); + ks_assert(space); + + blade_method_create(&method, space, "join", blade_chat_join_request_handler); + ks_assert(method); + blade_space_methods_add(space, method); + + blade_method_create(&method, space, "leave", blade_chat_leave_request_handler); + ks_assert(method); + blade_space_methods_add(space, method); + + blade_method_create(&method, space, "send", blade_chat_send_request_handler); + ks_assert(method); + blade_space_methods_add(space, method); + + blade_handle_space_register(space); + + blade_handle_session_state_callback_register(blade_module_handle_get(bm), bm, blade_module_chat_on_session_state, &bm_chat->session_state_callback_id); + + ks_log(KS_LOG_DEBUG, "Started\n"); + + return KS_STATUS_SUCCESS; +} + +KS_DECLARE(ks_status_t) blade_module_chat_on_shutdown(blade_module_t *bm) +{ + blade_module_chat_t *bm_chat = NULL; + + ks_assert(bm); + + bm_chat = (blade_module_chat_t *)blade_module_data_get(bm); + ks_assert(bm_chat); + + if (bm_chat->session_state_callback_id) blade_handle_session_state_callback_unregister(blade_module_handle_get(bm), bm_chat->session_state_callback_id); + bm_chat->session_state_callback_id = NULL; + + ks_log(KS_LOG_DEBUG, "Stopped\n"); + + return KS_STATUS_SUCCESS; +} + +void blade_module_chat_on_session_state(blade_session_t *bs, blade_session_state_condition_t condition, void *data) +{ + blade_module_t *bm = NULL; + blade_module_chat_t *bm_chat = NULL; + + ks_assert(bs); + ks_assert(data); + + bm = (blade_module_t *)data; + bm_chat = (blade_module_chat_t *)blade_module_data_get(bm); + ks_assert(bm_chat); + + if (blade_session_state_get(bs) == BLADE_SESSION_STATE_HANGUP && condition == BLADE_SESSION_STATE_CONDITION_PRE) { + cJSON *props = NULL; + + ks_log(KS_LOG_DEBUG, "Removing session from chat participants if present\n"); + + props = blade_session_properties_get(bs); + ks_assert(props); + + cJSON_DeleteItemFromObject(props, "blade.chat.participant"); + + list_delete(&bm_chat->participants, blade_session_id_get(bs)); // @todo make copy of session id instead and search manually, also free the id + } +} + +ks_bool_t blade_chat_join_request_handler(blade_module_t *bm, blade_request_t *breq) +{ + blade_module_chat_t *bm_chat = NULL; + blade_session_t *bs = NULL; + cJSON *res = NULL; + cJSON *props = NULL; + cJSON *props_participant = NULL; + + ks_assert(bm); + ks_assert(breq); + + ks_log(KS_LOG_DEBUG, "Request Received!\n"); + + bm_chat = (blade_module_chat_t *)blade_module_data_get(bm); + ks_assert(bm_chat); + + bs = blade_handle_sessions_get(breq->handle, breq->session_id); + ks_assert(bs); + + // @todo properties only used to demonstrate a flexible container for session data, should just rely on the participants list/hash + blade_session_properties_write_lock(bs, KS_TRUE); + + props = blade_session_properties_get(bs); + ks_assert(props); + + props_participant = cJSON_GetObjectItem(props, "blade.chat.participant"); + if (props_participant && props_participant->type == cJSON_True) { + ks_log(KS_LOG_DEBUG, "Session (%s) attempted to join chat but is already a participant\n", blade_session_id_get(bs)); + blade_rpc_error_create(breq->pool, &res, NULL, breq->message_id, -10000, "Already a participant of chat"); + } else { + ks_log(KS_LOG_DEBUG, "Session (%s) joined chat\n", blade_session_id_get(bs)); + + if (props_participant) props_participant->type = cJSON_True; + else cJSON_AddTrueToObject(props, "blade.chat.participant"); + + list_append(&bm_chat->participants, blade_session_id_get(bs)); // @todo make copy of session id instead and cleanup when removed + + blade_rpc_response_create(breq->pool, &res, NULL, breq->message_id); + + // @todo create an event to send to participants when a session joins and leaves, send after main response though + } + + blade_session_properties_write_unlock(bs); + + blade_session_send(bs, res, NULL); + + blade_session_read_unlock(bs); + + cJSON_Delete(res); + + return KS_FALSE; +} + +ks_bool_t blade_chat_leave_request_handler(blade_module_t *bm, blade_request_t *breq) +{ + blade_module_chat_t *bm_chat = NULL; + blade_session_t *bs = NULL; + cJSON *res = NULL; + cJSON *props = NULL; + cJSON *props_participant = NULL; + + ks_assert(bm); + ks_assert(breq); + + ks_log(KS_LOG_DEBUG, "Request Received!\n"); + + bm_chat = (blade_module_chat_t *)blade_module_data_get(bm); + ks_assert(bm_chat); + + bs = blade_handle_sessions_get(breq->handle, breq->session_id); + ks_assert(bs); + + blade_session_properties_write_lock(bs, KS_TRUE); + + props = blade_session_properties_get(bs); + ks_assert(props); + + props_participant = cJSON_GetObjectItem(props, "blade.chat.participant"); + if (!props_participant || props_participant->type == cJSON_False) { + ks_log(KS_LOG_DEBUG, "Session (%s) attempted to leave chat but is not a participant\n", blade_session_id_get(bs)); + blade_rpc_error_create(breq->pool, &res, NULL, breq->message_id, -10000, "Not a participant of chat"); + } else { + ks_log(KS_LOG_DEBUG, "Session (%s) left chat\n", blade_session_id_get(bs)); + + cJSON_DeleteItemFromObject(props, "blade.chat.participant"); + + list_delete(&bm_chat->participants, blade_session_id_get(bs)); // @todo make copy of session id instead and search manually, also free the id + + blade_rpc_response_create(breq->pool, &res, NULL, breq->message_id); + + // @todo create an event to send to participants when a session joins and leaves, send after main response though + } + + blade_session_properties_write_unlock(bs); + + blade_session_send(bs, res, NULL); + + blade_session_read_unlock(bs); + + cJSON_Delete(res); + + return KS_FALSE; +} + +ks_bool_t blade_chat_send_request_handler(blade_module_t *bm, blade_request_t *breq) +{ + blade_module_chat_t *bm_chat = NULL; + blade_session_t *bs = NULL; + cJSON *params = NULL; + cJSON *res = NULL; + cJSON *event = NULL; + const char *message = NULL; + ks_bool_t sendevent = KS_FALSE; + + ks_assert(bm); + ks_assert(breq); + + ks_log(KS_LOG_DEBUG, "Request Received!\n"); + + bm_chat = (blade_module_chat_t *)blade_module_data_get(bm); + ks_assert(bm_chat); + + params = cJSON_GetObjectItem(breq->message, "params"); // @todo cache this in blade_request_t for quicker/easier access + if (!params) { + ks_log(KS_LOG_DEBUG, "Session (%s) attempted to send chat message with no 'params' object\n", blade_session_id_get(bs)); + blade_rpc_error_create(breq->pool, &res, NULL, breq->message_id, -32602, "Missing params object"); + } else if (!(message = cJSON_GetObjectCstr(params, "message"))) { + ks_log(KS_LOG_DEBUG, "Session (%s) attempted to send chat message with no 'message'\n", blade_session_id_get(bs)); + blade_rpc_error_create(breq->pool, &res, NULL, breq->message_id, -32602, "Missing params message string"); + } + + bs = blade_handle_sessions_get(breq->handle, breq->session_id); + ks_assert(bs); + + if (!res) { + blade_rpc_response_create(breq->pool, &res, NULL, breq->message_id); + sendevent = KS_TRUE; + } + blade_session_send(bs, res, NULL); + + blade_session_read_unlock(bs); + + cJSON_Delete(res); + + if (sendevent) { + blade_rpc_event_create(breq->pool, &event, &res, "blade.chat.message"); + ks_assert(event); + cJSON_AddStringToObject(res, "from", breq->session_id); // @todo should really be the identity, but we don't have that in place yet + cJSON_AddStringToObject(res, "message", message); + + blade_handle_sessions_send(breq->handle, &bm_chat->participants, NULL, event); + + cJSON_Delete(event); + } + + return KS_FALSE; +} + + +/* For Emacs: + * Local Variables: + * mode:c + * indent-tabs-mode:t + * tab-width:4 + * c-basic-offset:4 + * End: + * For VIM: + * vim:set softtabstop=4 shiftwidth=4 tabstop=4 noet: + */ diff --git a/libs/libblade/src/blade_module_wss.c b/libs/libblade/src/blade_module_wss.c index dc1118cb95..107a8420d3 100644 --- a/libs/libblade/src/blade_module_wss.c +++ b/libs/libblade/src/blade_module_wss.c @@ -122,7 +122,7 @@ ks_status_t blade_transport_wss_init_create(blade_transport_wss_init_t **bt_wssi ks_status_t blade_transport_wss_init_destroy(blade_transport_wss_init_t **bt_wssiP); -ks_bool_t blade_test_echo_request_handler(blade_request_t *breq); +ks_bool_t blade_test_echo_request_handler(blade_module_t *bm, blade_request_t *breq); ks_bool_t blade_test_echo_response_handler(blade_response_t *bres); @@ -427,7 +427,7 @@ KS_DECLARE(ks_status_t) blade_module_wss_on_startup(blade_module_t *bm, config_s blade_handle_transport_register(bm_wss->handle, bm, BLADE_MODULE_WSS_TRANSPORT_NAME, bm_wss->transport_callbacks); - blade_space_create(&space, bm_wss->handle, "blade.test"); + blade_space_create(&space, bm_wss->handle, bm, "blade.test"); ks_assert(space); blade_method_create(&method, space, "echo", blade_test_echo_request_handler); @@ -1248,9 +1248,10 @@ blade_connection_state_hook_t blade_transport_wss_on_state_ready_inbound(blade_c { ks_assert(bc); - ks_log(KS_LOG_DEBUG, "State Callback: %d\n", (int32_t)condition); + if (condition == BLADE_CONNECTION_STATE_CONDITION_PRE) { + ks_log(KS_LOG_DEBUG, "State Callback: %d\n", (int32_t)condition); + } - ks_sleep_ms(1000); return BLADE_CONNECTION_STATE_HOOK_SUCCESS; } @@ -1258,12 +1259,12 @@ blade_connection_state_hook_t blade_transport_wss_on_state_ready_outbound(blade_ { ks_assert(bc); - ks_log(KS_LOG_DEBUG, "State Callback: %d\n", (int32_t)condition); - if (condition == BLADE_CONNECTION_STATE_CONDITION_PRE) { blade_session_t *bs = NULL; cJSON *req = NULL; + ks_log(KS_LOG_DEBUG, "State Callback: %d\n", (int32_t)condition); + bs = blade_handle_sessions_get(blade_connection_handle_get(bc), blade_connection_session_get(bc)); ks_assert(bs); @@ -1273,17 +1274,17 @@ blade_connection_state_hook_t blade_transport_wss_on_state_ready_outbound(blade_ blade_session_read_unlock(bs); } - ks_sleep_ms(1000); return BLADE_CONNECTION_STATE_HOOK_SUCCESS; } -ks_bool_t blade_test_echo_request_handler(blade_request_t *breq) +ks_bool_t blade_test_echo_request_handler(blade_module_t *bm, blade_request_t *breq) { blade_session_t *bs = NULL; cJSON *res = NULL; + ks_assert(bm); ks_assert(breq); ks_log(KS_LOG_DEBUG, "Request Received!\n"); diff --git a/libs/libblade/src/blade_protocol.c b/libs/libblade/src/blade_protocol.c index 092c31f237..2752ce9fff 100644 --- a/libs/libblade/src/blade_protocol.c +++ b/libs/libblade/src/blade_protocol.c @@ -55,7 +55,7 @@ KS_DECLARE(ks_status_t) blade_request_create(blade_request_t **breqP, breq->pool = pool; breq->session_id = ks_pstrdup(pool, session_id); breq->message = cJSON_Duplicate(json, 1); - breq->message_id = cJSON_GetObjectCstr(json, "id"); + breq->message_id = cJSON_GetObjectCstr(breq->message, "id"); breq->callback = callback; *breqP = breq; @@ -81,7 +81,11 @@ KS_DECLARE(ks_status_t) blade_request_destroy(blade_request_t **breqP) } -KS_DECLARE(ks_status_t) blade_response_create(blade_response_t **bresP, blade_handle_t *bh, const char *session_id, blade_request_t *breq, cJSON *json) +KS_DECLARE(ks_status_t) blade_response_create(blade_response_t **bresP, + blade_handle_t *bh, + const char *session_id, + blade_request_t *breq, + cJSON *json) { blade_response_t *bres = NULL; ks_pool_t *pool = NULL; @@ -125,6 +129,50 @@ KS_DECLARE(ks_status_t) blade_response_destroy(blade_response_t **bresP) return KS_STATUS_SUCCESS; } +KS_DECLARE(ks_status_t) blade_event_create(blade_event_t **bevP, + blade_handle_t *bh, + const char *session_id, + cJSON *json) +{ + blade_event_t *bev = NULL; + ks_pool_t *pool = NULL; + + ks_assert(bevP); + ks_assert(bh); + ks_assert(session_id); + ks_assert(json); + + pool = blade_handle_pool_get(bh); + ks_assert(pool); + + bev = ks_pool_alloc(pool, sizeof(blade_event_t)); + bev->handle = bh; + bev->pool = pool; + bev->session_id = ks_pstrdup(pool, session_id); + bev->message = cJSON_Duplicate(json, 1); + + *bevP = bev; + + return KS_STATUS_SUCCESS; +} + +KS_DECLARE(ks_status_t) blade_event_destroy(blade_event_t **bevP) +{ + blade_event_t *bev = NULL; + + ks_assert(bevP); + ks_assert(*bevP); + + bev = *bevP; + + ks_pool_free(bev->pool, (void **)&bev->session_id); + cJSON_Delete(bev->message); + + ks_pool_free(bev->pool, bevP); + + return KS_STATUS_SUCCESS; +} + KS_DECLARE(ks_status_t) blade_rpc_request_create(ks_pool_t *pool, cJSON **json, cJSON **params, const char **id, const char *method) { cJSON *root = NULL; @@ -208,6 +256,35 @@ KS_DECLARE(ks_status_t) blade_rpc_error_create(ks_pool_t *pool, cJSON **json, cJ return KS_STATUS_SUCCESS; } +KS_DECLARE(ks_status_t) blade_rpc_event_create(ks_pool_t *pool, cJSON **json, cJSON **result, const char *event) +{ + cJSON *root = NULL; + cJSON *b = NULL; + cJSON *r = NULL; + + ks_assert(pool); + ks_assert(json); + ks_assert(event); + + root = cJSON_CreateObject(); + + cJSON_AddStringToObject(root, "jsonrpc", "2.0"); + + b = cJSON_CreateObject(); + cJSON_AddStringToObject(b, "event", event); + cJSON_AddItemToObject(root, "blade", b); + + if (result) { + r = cJSON_CreateObject(); + cJSON_AddItemToObject(root, "result", r); + *result = r; + } + + *json = root; + + return KS_STATUS_SUCCESS; +} + /* For Emacs: * Local Variables: * mode:c diff --git a/libs/libblade/src/blade_session.c b/libs/libblade/src/blade_session.c index 048baec183..f284d90045 100644 --- a/libs/libblade/src/blade_session.c +++ b/libs/libblade/src/blade_session.c @@ -48,6 +48,9 @@ struct blade_session_s { ks_q_t *sending; ks_q_t *receiving; + + cJSON *properties; + ks_rwl_t *properties_lock; }; void *blade_session_state_thread(ks_thread_t *thread, void *data); @@ -83,6 +86,11 @@ KS_DECLARE(ks_status_t) blade_session_create(blade_session_t **bsP, blade_handle ks_q_create(&bs->receiving, pool, 0); ks_assert(bs->receiving); + bs->properties = cJSON_CreateObject(); + ks_assert(bs->properties); + ks_rwl_create(&bs->properties_lock, pool); + ks_assert(bs->properties_lock); + *bsP = bs; ks_log(KS_LOG_DEBUG, "Created\n"); @@ -101,6 +109,10 @@ KS_DECLARE(ks_status_t) blade_session_destroy(blade_session_t **bsP) blade_session_shutdown(bs); + cJSON_Delete(bs->properties); + bs->properties = NULL; + ks_rwl_destroy(&bs->properties_lock); + list_destroy(&bs->connections); ks_q_destroy(&bs->receiving); ks_q_destroy(&bs->sending); @@ -174,6 +186,13 @@ KS_DECLARE(blade_handle_t *) blade_session_handle_get(blade_session_t *bs) return bs->handle; } +KS_DECLARE(ks_pool_t *) blade_session_pool_get(blade_session_t *bs) +{ + ks_assert(bs); + + return bs->pool; +} + KS_DECLARE(const char *) blade_session_id_get(blade_session_t *bs) { ks_assert(bs); @@ -190,6 +209,20 @@ KS_DECLARE(void) blade_session_id_set(blade_session_t *bs, const char *id) bs->id = ks_pstrdup(bs->pool, id); } +KS_DECLARE(blade_session_state_t) blade_session_state_get(blade_session_t *bs) +{ + ks_assert(bs); + + return bs->state; +} + +KS_DECLARE(cJSON *) blade_session_properties_get(blade_session_t *bs) +{ + ks_assert(bs); + + return bs->properties; +} + KS_DECLARE(ks_status_t) blade_session_read_lock(blade_session_t *bs, ks_bool_t block) { ks_status_t ret = KS_STATUS_SUCCESS; @@ -227,11 +260,50 @@ KS_DECLARE(ks_status_t) blade_session_write_unlock(blade_session_t *bs) } +KS_DECLARE(ks_status_t) blade_session_properties_read_lock(blade_session_t *bs, ks_bool_t block) +{ + ks_status_t ret = KS_STATUS_SUCCESS; + + ks_assert(bs); + + if (block) ret = ks_rwl_read_lock(bs->properties_lock); + else ret = ks_rwl_try_read_lock(bs->properties_lock); + return ret; +} + +KS_DECLARE(ks_status_t) blade_session_properties_read_unlock(blade_session_t *bs) +{ + ks_assert(bs); + + return ks_rwl_read_unlock(bs->properties_lock); +} + +KS_DECLARE(ks_status_t) blade_session_properties_write_lock(blade_session_t *bs, ks_bool_t block) +{ + ks_status_t ret = KS_STATUS_SUCCESS; + + ks_assert(bs); + + if (block) ret = ks_rwl_write_lock(bs->properties_lock); + else ret = ks_rwl_try_write_lock(bs->properties_lock); + return ret; +} + +KS_DECLARE(ks_status_t) blade_session_properties_write_unlock(blade_session_t *bs) +{ + ks_assert(bs); + + return ks_rwl_write_unlock(bs->properties_lock); +} + + KS_DECLARE(void) blade_session_state_set(blade_session_t *bs, blade_session_state_t state) { ks_assert(bs); bs->state = state; + + blade_handle_session_state_callbacks_execute(bs, BLADE_SESSION_STATE_CONDITION_PRE); } KS_DECLARE(void) blade_session_hangup(blade_session_t *bs) @@ -387,6 +459,8 @@ void *blade_session_state_thread(ks_thread_t *thread, void *data) } } + blade_handle_session_state_callbacks_execute(bs, BLADE_SESSION_STATE_CONDITION_POST); + switch (state) { case BLADE_SESSION_STATE_DESTROY: blade_session_state_on_destroy(bs); @@ -468,7 +542,7 @@ ks_status_t blade_session_state_on_ready(blade_session_t *bs) ks_assert(bs); - ks_log(KS_LOG_DEBUG, "Session (%s) state ready\n", bs->id); + //ks_log(KS_LOG_DEBUG, "Session (%s) state ready\n", bs->id); // @todo for now only process messages if there is a connection available if (list_size(&bs->connections) > 0) { @@ -479,7 +553,7 @@ ks_status_t blade_session_state_on_ready(blade_session_t *bs) } } - ks_sleep_ms(1000); + ks_sleep_ms(1); return KS_STATUS_SUCCESS; } @@ -495,7 +569,15 @@ KS_DECLARE(ks_status_t) blade_session_send(blade_session_t *bs, cJSON *json, bla method = cJSON_GetObjectCstr(json, "method"); id = cJSON_GetObjectCstr(json, "id"); - if (method) { + if (!id) { + cJSON *blade = NULL; + const char *event = NULL; + + blade = cJSON_GetObjectItem(json, "blade"); + event = cJSON_GetObjectCstr(blade, "event"); + + ks_log(KS_LOG_DEBUG, "Session (%s) sending event (%s)\n", bs->id, event); + } else if (method) { // @note This is scenario 1 // 1) Sending a request (client: method caller or consumer) ks_log(KS_LOG_DEBUG, "Session (%s) sending request (%s) for %s\n", bs->id, id, method); @@ -529,7 +611,10 @@ ks_status_t blade_session_process(blade_session_t *bs, cJSON *json) { blade_request_t *breq = NULL; blade_response_t *bres = NULL; + blade_event_t *bev = NULL; const char *jsonrpc = NULL; + cJSON *blade = NULL; + const char *blade_event = NULL; const char *id = NULL; const char *method = NULL; ks_bool_t disconnect = KS_FALSE; @@ -548,73 +633,99 @@ ks_status_t blade_session_process(blade_session_t *bs, cJSON *json) return KS_STATUS_FAIL; } - id = cJSON_GetObjectCstr(json, "id"); - if (!id) { - ks_log(KS_LOG_DEBUG, "Received message is missing 'id'\n"); - // @todo send error response, code = -32600 (invalid request) - // @todo hangup session entirely? - return KS_STATUS_FAIL; + blade = cJSON_GetObjectItem(json, "blade"); + if (blade) { + blade_event = cJSON_GetObjectCstr(blade, "event"); } - method = cJSON_GetObjectCstr(json, "method"); - if (method) { - // @note This is scenario 2 - // 2) Receiving a request (server: method callee or provider) - blade_space_t *tmp_space = NULL; - blade_method_t *tmp_method = NULL; - blade_request_callback_t callback = NULL; - char *space_name = ks_pstrdup(bs->pool, method); - char *method_name = strrchr(space_name, '.'); + if (blade_event) { + blade_event_callback_t callback = blade_handle_event_lookup(blade_session_handle_get(bs), blade_event); + if (!callback) { + ks_log(KS_LOG_DEBUG, "Received event message with no event callback '%s'\n", blade_event); + } else { + ks_log(KS_LOG_DEBUG, "Session (%s) processing event %s\n", bs->id, blade_event); - ks_log(KS_LOG_DEBUG, "Session (%s) receiving request (%s) for %s\n", bs->id, id, method); + blade_event_create(&bev, bs->handle, bs->id, json); + ks_assert(bev); - if (!method_name || method_name == space_name) { - ks_pool_free(bs->pool, (void **)&space_name); - // @todo send error response, code = -32601 (method not found) - // @todo hangup session entirely? - return KS_STATUS_FAIL; + disconnect = callback(bev); + + blade_event_destroy(&bev); } - *method_name = '\0'; - method_name++; // @todo check if can be postfixed safely on previous assignment, can't recall - - tmp_space = blade_handle_space_lookup(bs->handle, space_name); - if (tmp_space) tmp_method = blade_space_methods_get(tmp_space, method_name); - - ks_pool_free(bs->pool, (void **)&space_name); - - if (!tmp_method) { - // @todo send error response, code = -32601 (method not found) - // @todo hangup session entirely? - return KS_STATUS_FAIL; - } - callback = blade_method_callback_get(tmp_method); - ks_assert(callback); - - blade_request_create(&breq, bs->handle, bs->id, json, NULL); - ks_assert(breq); - - disconnect = callback(breq); - - blade_request_destroy(&breq); } else { - // @note This is scenario 4 - // 4) Receiving a response or error (client: method caller or consumer) - - ks_log(KS_LOG_DEBUG, "Session (%s) receiving response (%s)\n", bs->id, id); - - breq = blade_handle_requests_get(bs->handle, id); - if (!breq) { + id = cJSON_GetObjectCstr(json, "id"); + if (!id) { + ks_log(KS_LOG_DEBUG, "Received non-event message is missing 'id'\n"); + // @todo send error response, code = -32600 (invalid request) // @todo hangup session entirely? return KS_STATUS_FAIL; } - blade_handle_requests_remove(breq); - blade_response_create(&bres, bs->handle, bs->id, breq, json); - ks_assert(bres); + method = cJSON_GetObjectCstr(json, "method"); + if (method) { + // @note This is scenario 2 + // 2) Receiving a request (server: method callee or provider) + blade_space_t *tmp_space = NULL; + blade_method_t *tmp_method = NULL; + blade_request_callback_t callback = NULL; + char *space_name = ks_pstrdup(bs->pool, method); + char *method_name = strrchr(space_name, '.'); - disconnect = breq->callback(bres); + ks_log(KS_LOG_DEBUG, "Session (%s) receiving request (%s) for %s\n", bs->id, id, method); - blade_response_destroy(&bres); + if (!method_name || method_name == space_name) { + ks_log(KS_LOG_DEBUG, "Received unparsable method\n"); + ks_pool_free(bs->pool, (void **)&space_name); + // @todo send error response, code = -32601 (method not found) + return KS_STATUS_FAIL; + } + *method_name = '\0'; + method_name++; // @todo check if can be postfixed safely on previous assignment, can't recall + + ks_log(KS_LOG_DEBUG, "Looking for space %s\n", space_name); + + tmp_space = blade_handle_space_lookup(bs->handle, space_name); + if (tmp_space) { + ks_log(KS_LOG_DEBUG, "Looking for method %s\n", method_name); + tmp_method = blade_space_methods_get(tmp_space, method_name); + } + + ks_pool_free(bs->pool, (void **)&space_name); + + if (!tmp_method) { + ks_log(KS_LOG_DEBUG, "Received unknown method\n"); + // @todo send error response, code = -32601 (method not found) + return KS_STATUS_FAIL; + } + callback = blade_method_callback_get(tmp_method); + ks_assert(callback); + + blade_request_create(&breq, bs->handle, bs->id, json, NULL); + ks_assert(breq); + + disconnect = callback(blade_space_module_get(tmp_space), breq); + + blade_request_destroy(&breq); + } else { + // @note This is scenario 4 + // 4) Receiving a response or error (client: method caller or consumer) + + ks_log(KS_LOG_DEBUG, "Session (%s) receiving response (%s)\n", bs->id, id); + + breq = blade_handle_requests_get(bs->handle, id); + if (!breq) { + // @todo hangup session entirely? + return KS_STATUS_FAIL; + } + blade_handle_requests_remove(breq); + + blade_response_create(&bres, bs->handle, bs->id, breq, json); + ks_assert(bres); + + disconnect = breq->callback(bres); + + blade_response_destroy(&bres); + } } if (disconnect) { diff --git a/libs/libblade/src/blade_space.c b/libs/libblade/src/blade_space.c index 09a8574c4c..33931c2453 100644 --- a/libs/libblade/src/blade_space.c +++ b/libs/libblade/src/blade_space.c @@ -36,13 +36,14 @@ struct blade_space_s { blade_handle_t *handle; ks_pool_t *pool; + blade_module_t *module; const char *path; ks_hash_t *methods; }; -KS_DECLARE(ks_status_t) blade_space_create(blade_space_t **bsP, blade_handle_t *bh, const char *path) +KS_DECLARE(ks_status_t) blade_space_create(blade_space_t **bsP, blade_handle_t *bh, blade_module_t *bm, const char *path) { blade_space_t *bs = NULL; ks_pool_t *pool = NULL; @@ -56,6 +57,7 @@ KS_DECLARE(ks_status_t) blade_space_create(blade_space_t **bsP, blade_handle_t * bs = ks_pool_alloc(pool, sizeof(blade_space_t)); bs->handle = bh; bs->pool = pool; + bs->module = bm; bs->path = path; // @todo dup and keep copy? should mostly be literals ks_hash_create(&bs->methods, KS_HASH_MODE_CASE_INSENSITIVE, KS_HASH_FLAG_NOLOCK | KS_HASH_FLAG_DUP_CHECK, bs->pool); ks_assert(bs); @@ -101,6 +103,13 @@ KS_DECLARE(blade_handle_t *) blade_space_handle_get(blade_space_t *bs) return bs->handle; } +KS_DECLARE(blade_module_t *) blade_space_module_get(blade_space_t *bs) +{ + ks_assert(bs); + + return bs->module; +} + KS_DECLARE(const char *) blade_space_path_get(blade_space_t *bs) { ks_assert(bs); diff --git a/libs/libblade/src/blade_stack.c b/libs/libblade/src/blade_stack.c index 697a8d9feb..3ffb6ed65f 100644 --- a/libs/libblade/src/blade_stack.c +++ b/libs/libblade/src/blade_stack.c @@ -49,14 +49,21 @@ struct blade_handle_s { ks_hash_t *transports; // registered transports exposed by modules, NOT active connections ks_hash_t *spaces; // registered method spaces exposed by modules + // registered event callback registry + // @todo should probably use a blade_handle_event_registration_t and contain optional userdata to pass from registration back into the callback, like + // a blade_module_t to get at inner module data for events that service modules may need to subscribe to between each other + ks_hash_t *events; //blade_identity_t *identity; blade_datastore_t *datastore; // @todo insert on connection creations, remove on connection destructions, key based on a UUID for the connection ks_hash_t *connections; // active connections keyed by connection id + // @todo insert on session creations, remove on session destructions, key based on a UUID for the session ks_hash_t *sessions; // active sessions keyed by session id + ks_hash_t *session_state_callbacks; + // @todo another hash with sessions keyed by the remote identity without parameters for quick lookup by target identity on sending? ks_hash_t *requests; // outgoing requests waiting for a response keyed by the message id }; @@ -70,7 +77,6 @@ struct blade_handle_transport_registration_s { blade_transport_callbacks_t *callbacks; }; - KS_DECLARE(ks_status_t) blade_handle_transport_registration_create(blade_handle_transport_registration_t **bhtrP, ks_pool_t *pool, blade_module_t *module, @@ -110,6 +116,59 @@ KS_DECLARE(ks_status_t) blade_handle_transport_registration_destroy(blade_handle } +typedef struct blade_handle_session_state_callback_registration_s blade_handle_session_state_callback_registration_t; +struct blade_handle_session_state_callback_registration_s { + ks_pool_t *pool; + + const char *id; + void *data; + blade_session_state_callback_t callback; +}; + +ks_status_t blade_handle_session_state_callback_registration_create(blade_handle_session_state_callback_registration_t **bhsscrP, + ks_pool_t *pool, + void *data, + blade_session_state_callback_t callback) +{ + blade_handle_session_state_callback_registration_t *bhsscr = NULL; + uuid_t uuid; + + ks_assert(bhsscrP); + ks_assert(pool); + ks_assert(callback); + + ks_uuid(&uuid); + + bhsscr = ks_pool_alloc(pool, sizeof(blade_handle_session_state_callback_registration_t)); + bhsscr->pool = pool; + bhsscr->id = ks_uuid_str(pool, &uuid); + bhsscr->data = data; + bhsscr->callback = callback; + + *bhsscrP = bhsscr; + + return KS_STATUS_SUCCESS; +} + +ks_status_t blade_handle_session_state_callback_registration_destroy(blade_handle_session_state_callback_registration_t **bhsscrP) +{ + blade_handle_session_state_callback_registration_t *bhsscr = NULL; + + ks_assert(bhsscrP); + + bhsscr = *bhsscrP; + *bhsscrP = NULL; + + ks_assert(bhsscr); + + ks_pool_free(bhsscr->pool, &bhsscr->id); + + ks_pool_free(bhsscr->pool, &bhsscr); + + return KS_STATUS_SUCCESS; +} + + KS_DECLARE(ks_status_t) blade_handle_create(blade_handle_t **bhP, ks_pool_t *pool, ks_thread_pool_t *tpool) { @@ -137,12 +196,17 @@ KS_DECLARE(ks_status_t) blade_handle_create(blade_handle_t **bhP, ks_pool_t *poo ks_assert(bh->transports); ks_hash_create(&bh->spaces, KS_HASH_MODE_CASE_INSENSITIVE, KS_HASH_FLAG_NOLOCK | KS_HASH_FLAG_DUP_CHECK, bh->pool); ks_assert(bh->spaces); + ks_hash_create(&bh->events, KS_HASH_MODE_CASE_INSENSITIVE, KS_HASH_FLAG_NOLOCK | KS_HASH_FLAG_DUP_CHECK, bh->pool); + ks_assert(bh->events); ks_hash_create(&bh->connections, KS_HASH_MODE_CASE_INSENSITIVE, KS_HASH_FLAG_NOLOCK | KS_HASH_FLAG_DUP_CHECK, bh->pool); ks_assert(bh->connections); + ks_hash_create(&bh->sessions, KS_HASH_MODE_CASE_INSENSITIVE, KS_HASH_FLAG_NOLOCK | KS_HASH_FLAG_DUP_CHECK, bh->pool); ks_assert(bh->sessions); - // @todo decide if this is uint32_t or uuid string, prefer uuid string to avoid needing another lock and variable for next id + ks_hash_create(&bh->session_state_callbacks, KS_HASH_MODE_CASE_INSENSITIVE, KS_HASH_FLAG_NOLOCK | KS_HASH_FLAG_DUP_CHECK, bh->pool); + ks_assert(bh->session_state_callbacks); + ks_hash_create(&bh->requests, KS_HASH_MODE_CASE_INSENSITIVE, KS_HASH_FLAG_NOLOCK | KS_HASH_FLAG_DUP_CHECK, bh->pool); ks_assert(bh->requests); @@ -172,8 +236,10 @@ KS_DECLARE(ks_status_t) blade_handle_destroy(blade_handle_t **bhP) blade_handle_shutdown(bh); ks_hash_destroy(&bh->requests); + ks_hash_destroy(&bh->session_state_callbacks); ks_hash_destroy(&bh->sessions); ks_hash_destroy(&bh->connections); + ks_hash_destroy(&bh->events); ks_hash_destroy(&bh->spaces); ks_hash_destroy(&bh->transports); @@ -267,6 +333,14 @@ KS_DECLARE(ks_status_t) blade_handle_shutdown(blade_handle_t *bh) // @todo call onshutdown and onunload callbacks for modules from DSOs, which will unregister transports and spaces, and will disconnect remaining // unattached connections + for (it = ks_hash_first(bh->events, KS_UNLOCKED); it; it = ks_hash_next(&it)) { + void *key = NULL; + blade_event_callback_t *value = NULL; + + ks_hash_this(it, (const void **)&key, NULL, (void **)&value); + blade_handle_event_unregister(bh, (const char *)key); + } + for (it = ks_hash_first(bh->spaces, KS_UNLOCKED); it; it = ks_hash_next(&it)) { void *key = NULL; blade_space_t *value = NULL; @@ -409,6 +483,53 @@ KS_DECLARE(blade_space_t *) blade_handle_space_lookup(blade_handle_t *bh, const return bs; } +KS_DECLARE(ks_status_t) blade_handle_event_register(blade_handle_t *bh, const char *event, blade_event_callback_t callback) +{ + ks_assert(bh); + ks_assert(event); + ks_assert(callback); + + ks_hash_write_lock(bh->events); + ks_hash_insert(bh->events, (void *)event, (void *)(intptr_t)callback); + ks_hash_write_unlock(bh->events); + + ks_log(KS_LOG_DEBUG, "Event Registered: %s\n", event); + + return KS_STATUS_SUCCESS; +} + +KS_DECLARE(ks_status_t) blade_handle_event_unregister(blade_handle_t *bh, const char *event) +{ + ks_bool_t removed = KS_FALSE; + + ks_assert(bh); + ks_assert(event); + + ks_hash_write_lock(bh->events); + if (ks_hash_remove(bh->events, (void *)event)) removed = KS_TRUE; + ks_hash_write_unlock(bh->events); + + if (removed) { + ks_log(KS_LOG_DEBUG, "Event Unregistered: %s\n", event); + } + + return KS_STATUS_SUCCESS; +} + +KS_DECLARE(blade_event_callback_t) blade_handle_event_lookup(blade_handle_t *bh, const char *event) +{ + blade_event_callback_t callback = NULL; + + ks_assert(bh); + ks_assert(event); + + ks_hash_read_lock(bh->events); + callback = (blade_event_callback_t)(intptr_t)ks_hash_search(bh->events, (void *)event, KS_UNLOCKED); + ks_hash_read_unlock(bh->events); + + return callback; +} + KS_DECLARE(ks_status_t) blade_handle_connect(blade_handle_t *bh, blade_connection_t **bcP, blade_identity_t *target, const char *session_id) { ks_status_t ret = KS_STATUS_SUCCESS; @@ -512,10 +633,11 @@ KS_DECLARE(ks_status_t) blade_handle_connections_remove(blade_connection_t *bc) blade_connection_write_unlock(bc); + // @todo call bh->connection_callbacks + return ret; } - KS_DECLARE(blade_session_t *) blade_handle_sessions_get(blade_handle_t *bh, const char *sid) { blade_session_t *bs = NULL; @@ -569,6 +691,92 @@ KS_DECLARE(ks_status_t) blade_handle_sessions_remove(blade_session_t *bs) return ret; } +KS_DECLARE(void) blade_handle_sessions_send(blade_handle_t *bh, list_t *sessions, const char *exclude, cJSON *json) +{ + blade_session_t *bs = NULL; + + ks_assert(bh); + ks_assert(sessions); + ks_assert(json); + + list_iterator_start(sessions); + while (list_iterator_hasnext(sessions)) { + const char *sessionid = list_iterator_next(sessions); + if (exclude && !strcmp(exclude, sessionid)) continue; + bs = blade_handle_sessions_get(bh, sessionid); + if (!bs) { + ks_log(KS_LOG_DEBUG, "This should not happen\n"); + continue; + } + blade_session_send(bs, json, NULL); + blade_session_read_unlock(bs); + } + list_iterator_stop(sessions); +} + +KS_DECLARE(ks_status_t) blade_handle_session_state_callback_register(blade_handle_t *bh, void *data, blade_session_state_callback_t callback, const char **id) +{ + ks_status_t ret = KS_STATUS_SUCCESS; + blade_handle_session_state_callback_registration_t *bhsscr = NULL; + + ks_assert(bh); + ks_assert(callback); + ks_assert(id); + + blade_handle_session_state_callback_registration_create(&bhsscr, blade_handle_pool_get(bh), data, callback); + ks_assert(bhsscr); + + ks_hash_write_lock(bh->session_state_callbacks); + ret = ks_hash_insert(bh->session_state_callbacks, (void *)bhsscr->id, bhsscr); + ks_hash_write_unlock(bh->session_state_callbacks); + + *id = bhsscr->id; + + return ret; +} + +KS_DECLARE(ks_status_t) blade_handle_session_state_callback_unregister(blade_handle_t *bh, const char *id) +{ + ks_status_t ret = KS_STATUS_SUCCESS; + blade_handle_session_state_callback_registration_t *bhsscr = NULL; + + ks_assert(bh); + ks_assert(id); + + ks_hash_write_lock(bh->session_state_callbacks); + bhsscr = (blade_handle_session_state_callback_registration_t *)ks_hash_remove(bh->session_state_callbacks, (void *)id); + if (!bhsscr) ret = KS_STATUS_FAIL; + ks_hash_write_lock(bh->session_state_callbacks); + + if (bhsscr) blade_handle_session_state_callback_registration_destroy(&bhsscr); + + return ret; +} + +KS_DECLARE(void) blade_handle_session_state_callbacks_execute(blade_session_t *bs, blade_session_state_condition_t condition) +{ + blade_handle_t *bh = NULL; + ks_hash_iterator_t *it = NULL; + + ks_assert(bs); + + if (blade_session_state_get(bs) == BLADE_SESSION_STATE_NONE) return; + + bh = blade_session_handle_get(bs); + ks_assert(bh); + + ks_hash_read_lock(bh->session_state_callbacks); + for (it = ks_hash_first(bh->session_state_callbacks, KS_UNLOCKED); it; it = ks_hash_next(&it)) { + void *key = NULL; + blade_handle_session_state_callback_registration_t *value = NULL; + + ks_hash_this(it, (const void **)&key, NULL, (void **)&value); + + value->callback(bs, condition, value->data); + } + ks_hash_read_unlock(bh->session_state_callbacks); +} + KS_DECLARE(blade_request_t *) blade_handle_requests_get(blade_handle_t *bh, const char *mid) { diff --git a/libs/libblade/src/include/blade_module.h b/libs/libblade/src/include/blade_module.h index 706a8e892c..e5fc558f16 100644 --- a/libs/libblade/src/include/blade_module.h +++ b/libs/libblade/src/include/blade_module.h @@ -1,23 +1,23 @@ /* * Copyright (c) 2017, Shane Bryldt * All rights reserved. - * + * * Redistribution and use in source and binary forms, with or without * modification, are permitted provided that the following conditions * are met: - * + * * * Redistributions of source code must retain the above copyright * notice, this list of conditions and the following disclaimer. - * + * * * Redistributions in binary form must reproduce the above copyright * notice, this list of conditions and the following disclaimer in the * documentation and/or other materials provided with the distribution. - * + * * * Neither the name of the original author; nor the names of any contributors * may be used to endorse or promote products derived from this software * without specific prior written permission. - * - * + * + * * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS * "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT * LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR @@ -38,6 +38,7 @@ KS_BEGIN_EXTERN_C KS_DECLARE(ks_status_t) blade_module_create(blade_module_t **bmP, blade_handle_t *bh, void *module_data, blade_module_callbacks_t *module_callbacks); KS_DECLARE(ks_status_t) blade_module_destroy(blade_module_t **bmP); +KS_DECLARE(blade_handle_t *) blade_module_handle_get(blade_module_t *bm); KS_DECLARE(void *) blade_module_data_get(blade_module_t *bm); // @todo very temporary, this is just here to get the wss module loaded until DSO is in place @@ -45,6 +46,11 @@ KS_DECLARE(ks_status_t) blade_module_wss_on_load(blade_module_t **bmP, blade_han KS_DECLARE(ks_status_t) blade_module_wss_on_unload(blade_module_t *bm); KS_DECLARE(ks_status_t) blade_module_wss_on_startup(blade_module_t *bm, config_setting_t *config); KS_DECLARE(ks_status_t) blade_module_wss_on_shutdown(blade_module_t *bm); + +KS_DECLARE(ks_status_t) blade_module_chat_on_load(blade_module_t **bmP, blade_handle_t *bh); +KS_DECLARE(ks_status_t) blade_module_chat_on_unload(blade_module_t *bm); +KS_DECLARE(ks_status_t) blade_module_chat_on_startup(blade_module_t *bm, config_setting_t *config); +KS_DECLARE(ks_status_t) blade_module_chat_on_shutdown(blade_module_t *bm); KS_END_EXTERN_C #endif diff --git a/libs/libblade/src/include/blade_protocol.h b/libs/libblade/src/include/blade_protocol.h index 0d3b4c772f..cb4c6dff9b 100644 --- a/libs/libblade/src/include/blade_protocol.h +++ b/libs/libblade/src/include/blade_protocol.h @@ -44,9 +44,12 @@ KS_DECLARE(ks_status_t) blade_request_create(blade_request_t **breqP, KS_DECLARE(ks_status_t) blade_request_destroy(blade_request_t **breqP); KS_DECLARE(ks_status_t) blade_response_create(blade_response_t **bresP, blade_handle_t *bh, const char *session_id, blade_request_t *breq, cJSON *json); KS_DECLARE(ks_status_t) blade_response_destroy(blade_response_t **bresP); +KS_DECLARE(ks_status_t) blade_event_create(blade_event_t **bevP, blade_handle_t *bh, const char *session_id, cJSON *json); +KS_DECLARE(ks_status_t) blade_event_destroy(blade_event_t **bevP); KS_DECLARE(ks_status_t) blade_rpc_request_create(ks_pool_t *pool, cJSON **json, cJSON **params, const char **id, const char *method); KS_DECLARE(ks_status_t) blade_rpc_response_create(ks_pool_t *pool, cJSON **json, cJSON **result, const char *id); KS_DECLARE(ks_status_t) blade_rpc_error_create(ks_pool_t *pool, cJSON **json, cJSON **error, const char *id, int32_t code, const char *message); +KS_DECLARE(ks_status_t) blade_rpc_event_create(ks_pool_t *pool, cJSON **json, cJSON **result, const char *event); KS_END_EXTERN_C #endif diff --git a/libs/libblade/src/include/blade_session.h b/libs/libblade/src/include/blade_session.h index e79fbf34b0..25c26b01ac 100644 --- a/libs/libblade/src/include/blade_session.h +++ b/libs/libblade/src/include/blade_session.h @@ -41,12 +41,19 @@ KS_DECLARE(ks_status_t) blade_session_destroy(blade_session_t **bsP); KS_DECLARE(ks_status_t) blade_session_startup(blade_session_t *bs); KS_DECLARE(ks_status_t) blade_session_shutdown(blade_session_t *bs); KS_DECLARE(blade_handle_t *) blade_session_handle_get(blade_session_t *bs); +KS_DECLARE(ks_pool_t *) blade_session_pool_get(blade_session_t *bs); KS_DECLARE(const char *) blade_session_id_get(blade_session_t *bs); KS_DECLARE(void) blade_session_id_set(blade_session_t *bs, const char *id); +KS_DECLARE(blade_session_state_t) blade_session_state_get(blade_session_t *bs); +KS_DECLARE(cJSON *) blade_session_properties_get(blade_session_t *bs); KS_DECLARE(ks_status_t) blade_session_read_lock(blade_session_t *bs, ks_bool_t block); KS_DECLARE(ks_status_t) blade_session_read_unlock(blade_session_t *bs); KS_DECLARE(ks_status_t) blade_session_write_lock(blade_session_t *bs, ks_bool_t block); KS_DECLARE(ks_status_t) blade_session_write_unlock(blade_session_t *bs); +KS_DECLARE(ks_status_t) blade_session_properties_read_lock(blade_session_t *bs, ks_bool_t block); +KS_DECLARE(ks_status_t) blade_session_properties_read_unlock(blade_session_t *bs); +KS_DECLARE(ks_status_t) blade_session_properties_write_lock(blade_session_t *bs, ks_bool_t block); +KS_DECLARE(ks_status_t) blade_session_properties_write_unlock(blade_session_t *bs); KS_DECLARE(void) blade_session_state_set(blade_session_t *bs, blade_session_state_t state); KS_DECLARE(void) blade_session_hangup(blade_session_t *bs); KS_DECLARE(ks_bool_t) blade_session_terminating(blade_session_t *bs); diff --git a/libs/libblade/src/include/blade_space.h b/libs/libblade/src/include/blade_space.h index 65664fdba5..bf241e45c9 100644 --- a/libs/libblade/src/include/blade_space.h +++ b/libs/libblade/src/include/blade_space.h @@ -36,9 +36,10 @@ #include KS_BEGIN_EXTERN_C -KS_DECLARE(ks_status_t) blade_space_create(blade_space_t **bsP, blade_handle_t *bh, const char *path); +KS_DECLARE(ks_status_t) blade_space_create(blade_space_t **bsP, blade_handle_t *bh, blade_module_t *bm, const char *path); KS_DECLARE(ks_status_t) blade_space_destroy(blade_space_t **bsP); KS_DECLARE(blade_handle_t *) blade_space_handle_get(blade_space_t *bs); +KS_DECLARE(blade_module_t *) blade_space_module_get(blade_space_t *bs); KS_DECLARE(const char *) blade_space_path_get(blade_space_t *bs); KS_DECLARE(ks_status_t) blade_space_methods_add(blade_space_t *bs, blade_method_t *bm); KS_DECLARE(ks_status_t) blade_space_methods_remove(blade_space_t *bs, blade_method_t *bm); diff --git a/libs/libblade/src/include/blade_stack.h b/libs/libblade/src/include/blade_stack.h index 27b45bf93f..1e0bc134d9 100644 --- a/libs/libblade/src/include/blade_stack.h +++ b/libs/libblade/src/include/blade_stack.h @@ -55,6 +55,10 @@ KS_DECLARE(ks_status_t) blade_handle_space_register(blade_space_t *bs); KS_DECLARE(ks_status_t) blade_handle_space_unregister(blade_space_t *bs); KS_DECLARE(blade_space_t *) blade_handle_space_lookup(blade_handle_t *bh, const char *path); +KS_DECLARE(ks_status_t) blade_handle_event_register(blade_handle_t *bh, const char *event, blade_event_callback_t callback); +KS_DECLARE(ks_status_t) blade_handle_event_unregister(blade_handle_t *bh, const char *event); +KS_DECLARE(blade_event_callback_t) blade_handle_event_lookup(blade_handle_t *bh, const char *event); + KS_DECLARE(ks_status_t) blade_handle_connect(blade_handle_t *bh, blade_connection_t **bcP, blade_identity_t *target, const char *session_id); KS_DECLARE(blade_connection_t *) blade_handle_connections_get(blade_handle_t *bh, const char *cid); @@ -64,6 +68,10 @@ KS_DECLARE(ks_status_t) blade_handle_connections_remove(blade_connection_t *bc); KS_DECLARE(blade_session_t *) blade_handle_sessions_get(blade_handle_t *bh, const char *sid); KS_DECLARE(ks_status_t) blade_handle_sessions_add(blade_session_t *bs); KS_DECLARE(ks_status_t) blade_handle_sessions_remove(blade_session_t *bs); +KS_DECLARE(void) blade_handle_sessions_send(blade_handle_t *bh, list_t *sessions, const char *exclude, cJSON *json); +KS_DECLARE(ks_status_t) blade_handle_session_state_callback_register(blade_handle_t *bh, void *data, blade_session_state_callback_t callback, const char **id); +KS_DECLARE(ks_status_t) blade_handle_session_state_callback_unregister(blade_handle_t *bh, const char *id); +KS_DECLARE(void) blade_handle_session_state_callbacks_execute(blade_session_t *bs, blade_session_state_condition_t condition); KS_DECLARE(blade_request_t *) blade_handle_requests_get(blade_handle_t *bh, const char *mid); KS_DECLARE(ks_status_t) blade_handle_requests_add(blade_request_t *br); diff --git a/libs/libblade/src/include/blade_types.h b/libs/libblade/src/include/blade_types.h index 14c908f857..e78c6dad0f 100644 --- a/libs/libblade/src/include/blade_types.h +++ b/libs/libblade/src/include/blade_types.h @@ -43,21 +43,24 @@ typedef struct blade_identity_s blade_identity_t; typedef struct blade_module_s blade_module_t; typedef struct blade_module_callbacks_s blade_module_callbacks_t; typedef struct blade_transport_callbacks_s blade_transport_callbacks_t; +typedef struct blade_session_callbacks_s blade_session_callbacks_t; typedef struct blade_connection_s blade_connection_t; typedef struct blade_session_s blade_session_t; typedef struct blade_request_s blade_request_t; typedef struct blade_response_s blade_response_t; +typedef struct blade_event_s blade_event_t; typedef struct blade_space_s blade_space_t; typedef struct blade_method_s blade_method_t; -typedef ks_bool_t (*blade_request_callback_t)(blade_request_t *breq); -typedef ks_bool_t (*blade_response_callback_t)(blade_response_t *bres); - typedef struct blade_datastore_s blade_datastore_t; -typedef ks_bool_t (*blade_datastore_fetch_callback_t)(blade_datastore_t *bds, const void *data, uint32_t data_length, void *userdata); +typedef ks_bool_t (*blade_request_callback_t)(blade_module_t *bm, blade_request_t *breq); +typedef ks_bool_t (*blade_response_callback_t)(blade_response_t *bres); +typedef ks_bool_t (*blade_event_callback_t)(blade_event_t *bev); + +typedef ks_bool_t (*blade_datastore_fetch_callback_t)(blade_datastore_t *bds, const void *data, uint32_t data_length, void *userdata); typedef enum { @@ -94,6 +97,11 @@ typedef enum { } blade_connection_rank_t; +typedef enum { + BLADE_SESSION_STATE_CONDITION_PRE, + BLADE_SESSION_STATE_CONDITION_POST, +} blade_session_state_condition_t; + typedef enum { BLADE_SESSION_STATE_NONE, BLADE_SESSION_STATE_DESTROY, @@ -145,6 +153,8 @@ struct blade_transport_callbacks_s { blade_transport_state_callback_t onstate_ready_outbound; }; +typedef void (*blade_session_state_callback_t)(blade_session_t *bs, blade_session_state_condition_t condition, void *data); + struct blade_request_s { blade_handle_t *handle; @@ -167,6 +177,14 @@ struct blade_response_s { cJSON *message; }; +struct blade_event_s { + blade_handle_t *handle; + ks_pool_t *pool; + const char *session_id; + + cJSON *message; +}; + KS_END_EXTERN_C #endif diff --git a/libs/libblade/test/Makefile.am b/libs/libblade/test/Makefile.am index 328a71ef25..514af0198c 100644 --- a/libs/libblade/test/Makefile.am +++ b/libs/libblade/test/Makefile.am @@ -13,6 +13,11 @@ bladec_SOURCES = bladec.c tap.c bladec_CFLAGS = $(AM_CFLAGS) bladec_LDADD = $(TEST_LDADD) +check_PROGRAMS += blades +blades_SOURCES = blades.c tap.c +blades_CFLAGS = $(AM_CFLAGS) +blades_LDADD = $(TEST_LDADD) + TESTS=$(check_PROGRAMS) diff --git a/libs/libblade/test/bladec.c b/libs/libblade/test/bladec.c index 46bfda9969..ebc0384a21 100644 --- a/libs/libblade/test/bladec.c +++ b/libs/libblade/test/bladec.c @@ -26,22 +26,22 @@ struct command_def_s { command_callback callback; }; -void command_test(blade_handle_t *bh, char *args); void command_quit(blade_handle_t *bh, char *args); -void command_store(blade_handle_t *bh, char *args); -void command_fetch(blade_handle_t *bh, char *args); void command_connect(blade_handle_t *bh, char *args); +void command_chat(blade_handle_t *bh, char *args); static const struct command_def_s command_defs[] = { - { "test", command_test }, { "quit", command_quit }, - { "store", command_store }, - { "fetch", command_fetch }, { "connect", command_connect }, - + { "chat", command_chat }, + { NULL, NULL } }; +ks_bool_t on_blade_chat_join_response(blade_response_t *bres); +ks_bool_t on_blade_chat_message_event(blade_event_t *bev); +void on_blade_session_state_callback(blade_session_t *bs, blade_session_state_condition_t condition, void *data); + int main(int argc, char **argv) { blade_handle_t *bh = NULL; @@ -50,16 +50,16 @@ int main(int argc, char **argv) blade_module_t *mod_wss = NULL; //blade_identity_t *id = NULL; const char *cfgpath = "bladec.cfg"; - + const char *session_state_callback_id = NULL; ks_global_set_default_logger(KS_LOG_LEVEL_DEBUG); - + blade_init(); blade_handle_create(&bh, NULL, NULL); if (argc > 1) cfgpath = argv[1]; - + config_init(&config); if (!config_read_file(&config, cfgpath)) { ks_log(KS_LOG_ERROR, "%s:%d - %s\n", config_error_file(&config), config_error_line(&config), config_error_text(&config)); @@ -76,7 +76,7 @@ int main(int argc, char **argv) ks_log(KS_LOG_ERROR, "The 'blade' config setting is not a group\n"); return EXIT_FAILURE; } - + if (blade_handle_startup(bh, config_blade) != KS_STATUS_SUCCESS) { ks_log(KS_LOG_ERROR, "Blade startup failed\n"); return EXIT_FAILURE; @@ -91,8 +91,13 @@ int main(int argc, char **argv) return EXIT_FAILURE; } + blade_handle_event_register(bh, "blade.chat.message", on_blade_chat_message_event); + blade_handle_session_state_callback_register(bh, NULL, on_blade_session_state_callback, &session_state_callback_id); + loop(bh); + blade_handle_session_state_callback_unregister(bh, session_state_callback_id); + blade_module_wss_on_shutdown(mod_wss); blade_module_wss_on_unload(mod_wss); @@ -104,7 +109,37 @@ int main(int argc, char **argv) return 0; } +ks_bool_t on_blade_chat_message_event(blade_event_t *bev) +{ + cJSON *res = NULL; + const char *from = NULL; + const char *message = NULL; + ks_assert(bev); + + res = cJSON_GetObjectItem(bev->message, "result"); + from = cJSON_GetObjectCstr(res, "from"); + message = cJSON_GetObjectCstr(res, "message"); + + ks_log(KS_LOG_DEBUG, "Received Chat Message Event: (%s) %s\n", from, message); + + return KS_FALSE; +} + +void on_blade_session_state_callback(blade_session_t *bs, blade_session_state_condition_t condition, void *data) +{ + blade_session_state_t state = blade_session_state_get(bs); + + if (condition == BLADE_SESSION_STATE_CONDITION_PRE) { + ks_log(KS_LOG_DEBUG, "Blade Session State Changed: %s, %d\n", blade_session_id_get(bs), state); + if (state == BLADE_SESSION_STATE_READY) { + cJSON *req = NULL; + blade_rpc_request_create(blade_session_pool_get(bs), &req, NULL, NULL, "blade.chat.join"); + blade_session_send(bs, req, on_blade_chat_join_response); + cJSON_Delete(req); + } + } +} void buffer_console_input(void) { @@ -162,7 +197,7 @@ void loop(blade_handle_t *bh) void parse_argument(char **input, char **arg, char terminator) { char *tmp; - + ks_assert(input); ks_assert(*input); ks_assert(arg); @@ -183,11 +218,11 @@ void process_console_input(blade_handle_t *bh, char *line) char *args = line; char *cmd = NULL; ks_bool_t found = KS_FALSE; - + ks_log(KS_LOG_DEBUG, "Output: %s\n", line); parse_argument(&args, &cmd, ' '); - + ks_log(KS_LOG_DEBUG, "Command: %s, Args: %s\n", cmd, args); for (int32_t index = 0; command_defs[index].cmd; ++index) { @@ -199,61 +234,78 @@ void process_console_input(blade_handle_t *bh, char *line) if (!found) ks_log(KS_LOG_INFO, "Command '%s' unknown.\n", cmd); } -void command_test(blade_handle_t *bh, char *args) -{ - ks_log(KS_LOG_DEBUG, "Hello World!\n"); -} - void command_quit(blade_handle_t *bh, char *args) { ks_assert(bh); ks_assert(args); - + ks_log(KS_LOG_DEBUG, "Shutting down\n"); g_shutdown = KS_TRUE; } -void command_store(blade_handle_t *bh, char *args) -{ - char *key; - char *data; - - ks_assert(args); - - parse_argument(&args, &key, ' '); - parse_argument(&args, &data, ' '); - - blade_handle_datastore_store(bh, key, strlen(key), data, strlen(data) + 1); -} - -ks_bool_t blade_datastore_fetch_callback(blade_datastore_t *bds, const void *data, uint32_t data_length, void *userdata) -{ - ks_log(KS_LOG_INFO, "%s\n", data); - return KS_TRUE; -} - -void command_fetch(blade_handle_t *bh, char *args) -{ - char *key; - - ks_assert(args); - - parse_argument(&args, &key, ' '); - - blade_handle_datastore_fetch(bh, blade_datastore_fetch_callback, key, strlen(key), bh); -} - void command_connect(blade_handle_t *bh, char *args) { blade_connection_t *bc = NULL; blade_identity_t *target = NULL; - + ks_assert(bh); ks_assert(args); blade_identity_create(&target, blade_handle_pool_get(bh)); - + if (blade_identity_parse(target, args) == KS_STATUS_SUCCESS) blade_handle_connect(bh, &bc, target, NULL); blade_identity_destroy(&target); } + +ks_bool_t on_blade_chat_join_response(blade_response_t *bres) // @todo this should get userdata passed in from when the callback is registered +{ + ks_log(KS_LOG_DEBUG, "Received Chat Join Response!\n"); + return KS_FALSE; +} + +ks_bool_t on_blade_chat_send_response(blade_response_t *bres) // @todo this should get userdata passed in from when the callback is registered +{ + ks_log(KS_LOG_DEBUG, "Received Chat Send Response!\n"); + return KS_FALSE; +} + +void command_chat(blade_handle_t *bh, char *args) +{ + char *cmd = NULL; + + ks_assert(bh); + ks_assert(args); + + parse_argument(&args, &cmd, ' '); + ks_log(KS_LOG_DEBUG, "Chat Command: %s, Args: %s\n", cmd, args); + + if (!strcmp(cmd, "leave")) { + } else if (!strcmp(cmd, "send")) { + char *sid = NULL; + blade_session_t *bs = NULL; + cJSON *req = NULL; + cJSON *params = NULL; + + parse_argument(&args, &sid, ' '); + + bs = blade_handle_sessions_get(bh, sid); + if (!bs) { + ks_log(KS_LOG_DEBUG, "Unknown Session: %s\n", sid); + return; + } + blade_rpc_request_create(blade_handle_pool_get(bh), &req, ¶ms, NULL, "blade.chat.send"); + ks_assert(req); + ks_assert(params); + + cJSON_AddStringToObject(params, "message", args); + + blade_session_send(bs, req, on_blade_chat_send_response); + + blade_session_read_unlock(bs); + + cJSON_Delete(req); + } else { + ks_log(KS_LOG_DEBUG, "Unknown Chat Command: %s\n", cmd); + } +} diff --git a/libs/libblade/test/bladec.cfg b/libs/libblade/test/bladec.cfg index 2233b34608..14582ed3ab 100644 --- a/libs/libblade/test/bladec.cfg +++ b/libs/libblade/test/bladec.cfg @@ -1,9 +1,6 @@ blade: { - identity = "directory@domain"; - directory: - { - }; + identity = "peer@domain"; datastore: { database: @@ -11,18 +8,4 @@ blade: path = ":mem:"; }; }; - wss: - { - endpoints: - { - ipv4 = ( { address = "0.0.0.0", port = 2100 } ); - ipv6 = ( { address = "::", port = 2100 } ); - backlog = 128; - }; - # SSL group is optional, disabled when absent - ssl: - { - # todo: server SSL stuffs here - }; - }; }; diff --git a/libs/libblade/test/bladec2.cfg b/libs/libblade/test/bladec2.cfg deleted file mode 100644 index 14582ed3ab..0000000000 --- a/libs/libblade/test/bladec2.cfg +++ /dev/null @@ -1,11 +0,0 @@ -blade: -{ - identity = "peer@domain"; - datastore: - { - database: - { - path = ":mem:"; - }; - }; -}; diff --git a/libs/libblade/test/blades.c b/libs/libblade/test/blades.c new file mode 100644 index 0000000000..10f3f5d97c --- /dev/null +++ b/libs/libblade/test/blades.c @@ -0,0 +1,207 @@ +#include "blade.h" +#include "tap.h" + +#ifdef _WIN32 +#define STDIO_FD(_fs) _fileno(_fs) +#define READ(_fd, _buffer, _count) _read(_fd, _buffer, _count) +#else +#define STDIO_FD(_fs) fileno(_fs) +#define READ(_fd, _buffer, _count) read(_fd, _buffer, _count) +#endif + +#define CONSOLE_INPUT_MAX 512 + +ks_bool_t g_shutdown = KS_FALSE; +char g_console_input[CONSOLE_INPUT_MAX]; +size_t g_console_input_length = 0; +size_t g_console_input_eol = 0; + +void loop(blade_handle_t *bh); +void process_console_input(blade_handle_t *bh, char *line); + +typedef void (*command_callback)(blade_handle_t *bh, char *args); + +struct command_def_s { + const char *cmd; + command_callback callback; +}; + +void command_quit(blade_handle_t *bh, char *args); + +static const struct command_def_s command_defs[] = { + { "quit", command_quit }, + + { NULL, NULL } +}; + +int main(int argc, char **argv) +{ + blade_handle_t *bh = NULL; + config_t config; + config_setting_t *config_blade = NULL; + blade_module_t *mod_wss = NULL; + blade_module_t *mod_chat = NULL; + //blade_identity_t *id = NULL; + const char *cfgpath = "blades.cfg"; + + ks_global_set_default_logger(KS_LOG_LEVEL_DEBUG); + + blade_init(); + + blade_handle_create(&bh, NULL, NULL); + + if (argc > 1) cfgpath = argv[1]; + + config_init(&config); + if (!config_read_file(&config, cfgpath)) { + ks_log(KS_LOG_ERROR, "%s:%d - %s\n", config_error_file(&config), config_error_line(&config), config_error_text(&config)); + config_destroy(&config); + return EXIT_FAILURE; + } + config_blade = config_lookup(&config, "blade"); + if (!config_blade) { + ks_log(KS_LOG_ERROR, "Missing 'blade' config group\n"); + config_destroy(&config); + return EXIT_FAILURE; + } + if (config_setting_type(config_blade) != CONFIG_TYPE_GROUP) { + ks_log(KS_LOG_ERROR, "The 'blade' config setting is not a group\n"); + return EXIT_FAILURE; + } + + if (blade_handle_startup(bh, config_blade) != KS_STATUS_SUCCESS) { + ks_log(KS_LOG_ERROR, "Blade startup failed\n"); + return EXIT_FAILURE; + } + + if (blade_module_wss_on_load(&mod_wss, bh) != KS_STATUS_SUCCESS) { + ks_log(KS_LOG_ERROR, "Blade WSS module load failed\n"); + return EXIT_FAILURE; + } + if (blade_module_wss_on_startup(mod_wss, config_blade) != KS_STATUS_SUCCESS) { + ks_log(KS_LOG_ERROR, "Blade WSS module startup failed\n"); + return EXIT_FAILURE; + } + + blade_module_chat_on_load(&mod_chat, bh); + blade_module_chat_on_startup(mod_chat, config_blade); + + loop(bh); + + blade_module_chat_on_shutdown(mod_chat); + blade_module_chat_on_unload(mod_chat); + + blade_module_wss_on_shutdown(mod_wss); + + blade_module_wss_on_unload(mod_wss); + + blade_handle_destroy(&bh); + + blade_shutdown(); + + return 0; +} + + + +void buffer_console_input(void) +{ + ssize_t bytes = 0; + struct pollfd poll[1]; + poll[0].fd = STDIO_FD(stdin); + poll[0].events = POLLIN | POLLERR; + + if (ks_poll(poll, 1, 1) > 0) { + if (poll[0].revents & POLLIN) { + if ((bytes = READ(poll[0].fd, g_console_input + g_console_input_length, CONSOLE_INPUT_MAX - g_console_input_length)) <= 0) { + // @todo error + return; + } + g_console_input_length += bytes; + } + } +} + +void loop(blade_handle_t *bh) +{ + while (!g_shutdown) { + ks_bool_t eol = KS_FALSE; + buffer_console_input(); + + for (; g_console_input_eol < g_console_input_length; ++g_console_input_eol) { + char c = g_console_input[g_console_input_eol]; + if (c == '\r' || c == '\n') { + eol = KS_TRUE; + break; + } + } + if (eol) { + g_console_input[g_console_input_eol] = '\0'; + process_console_input(bh, g_console_input); + g_console_input_eol++; + for (; g_console_input_eol < g_console_input_length; ++g_console_input_eol) { + char c = g_console_input[g_console_input_eol]; + if (c != '\r' && c != '\n') break; + } + if (g_console_input_eol == g_console_input_length) g_console_input_eol = g_console_input_length = 0; + else { + memcpy(g_console_input, g_console_input + g_console_input_eol, g_console_input_length - g_console_input_eol); + g_console_input_length -= g_console_input_eol; + g_console_input_eol = 0; + } + } + if (g_console_input_length == CONSOLE_INPUT_MAX) { + // @todo lines must not exceed 512 bytes, treat as error and ignore buffer until next new line? + ks_assert(0); + } + } +} + +void parse_argument(char **input, char **arg, char terminator) +{ + char *tmp; + + ks_assert(input); + ks_assert(*input); + ks_assert(arg); + + tmp = *input; + *arg = tmp; + + while (*tmp && *tmp != terminator) ++tmp; + if (*tmp == terminator) { + *tmp = '\0'; + ++tmp; + } + *input = tmp; +} + +void process_console_input(blade_handle_t *bh, char *line) +{ + char *args = line; + char *cmd = NULL; + ks_bool_t found = KS_FALSE; + + ks_log(KS_LOG_DEBUG, "Output: %s\n", line); + + parse_argument(&args, &cmd, ' '); + + ks_log(KS_LOG_DEBUG, "Command: %s, Args: %s\n", cmd, args); + + for (int32_t index = 0; command_defs[index].cmd; ++index) { + if (!strcmp(command_defs[index].cmd, cmd)) { + found = KS_TRUE; + command_defs[index].callback(bh, args); + } + } + if (!found) ks_log(KS_LOG_INFO, "Command '%s' unknown.\n", cmd); +} + +void command_quit(blade_handle_t *bh, char *args) +{ + ks_assert(bh); + ks_assert(args); + + ks_log(KS_LOG_DEBUG, "Shutting down\n"); + g_shutdown = KS_TRUE; +} diff --git a/libs/libblade/test/blades.cfg b/libs/libblade/test/blades.cfg new file mode 100644 index 0000000000..3420d74ff2 --- /dev/null +++ b/libs/libblade/test/blades.cfg @@ -0,0 +1,28 @@ +blade: +{ + identity = "service@domain"; + directory: + { + }; + datastore: + { + database: + { + path = ":mem:"; + }; + }; + wss: + { + endpoints: + { + ipv4 = ( { address = "0.0.0.0", port = 2100 } ); + ipv6 = ( { address = "::", port = 2100 } ); + backlog = 128; + }; + # SSL group is optional, disabled when absent + ssl: + { + # todo: server SSL stuffs here + }; + }; +};