diff --git a/.clang-format b/.clang-format index 2b2089a4..a28b22d6 100644 --- a/.clang-format +++ b/.clang-format @@ -88,7 +88,7 @@ PenaltyExcessCharacter: 4 PenaltyReturnTypeOnItsOwnLine: 100 PointerAlignment: Left ReflowComments: true -SortIncludes: false +SortIncludes: Never SortUsingDeclarations: true SpaceAfterCStyleCast: false SpaceAfterTemplateKeyword: true @@ -101,7 +101,7 @@ SpacesInContainerLiterals: true SpacesInCStyleCastParentheses: false SpacesInParentheses: false SpacesInSquareBrackets: false -Standard: Cpp11 +Standard: c++11 TabWidth: 4 UseTab: Never ... diff --git a/.github/workflows/run-tests.yml b/.github/workflows/run-tests.yml index 7c929c6e..51dffaca 100644 --- a/.github/workflows/run-tests.yml +++ b/.github/workflows/run-tests.yml @@ -21,8 +21,8 @@ jobs: runner: - os: ubuntu-latest group: Default - - os: macos-13 - group: macos-gh +# - os: macos-13 +# group: macos-gh steps: - name: Checkout project uses: actions/checkout@v3 diff --git a/.pubnub.yml b/.pubnub.yml index d021ad1f..be2364c5 100644 --- a/.pubnub.yml +++ b/.pubnub.yml @@ -1,8 +1,23 @@ name: c-core schema: 1 -version: "4.13.1" +version: "4.14.0" scm: github.com/pubnub/c-core changelog: + - date: 2024-10-15 + version: v4.14.0 + changes: + - type: feature + text: "Add core Event Engine implementation with the required set of types and methods." + - type: feature + text: "Add Subscribe Event Engine built atop of the core Event Engine implementation." + - type: feature + text: "Add the following entities: channel, channel group, uuid and channel metadata objects." + - type: feature + text: "Add objects to manage subscriptions and provides interface for update listeners." + - type: feature + text: "Add new event listeners, which make it possible to add listeners to a specific entity or group of entities (though subscription and subscription set)." + - type: feature + text: "Added ability to configure automated retry policies for failed requests." - date: 2024-09-05 version: v4.13.1 changes: @@ -853,7 +868,7 @@ sdks: distribution-type: source code distribution-repository: GitHub release package-name: C-Core - location: https://github.com/pubnub/c-core/releases/tag/v4.13.1 + location: https://github.com/pubnub/c-core/releases/tag/v4.14.0 requires: - name: "miniz" @@ -919,7 +934,7 @@ sdks: distribution-type: source code distribution-repository: GitHub release package-name: C-Core - location: https://github.com/pubnub/c-core/releases/tag/v4.13.1 + location: https://github.com/pubnub/c-core/releases/tag/v4.14.0 requires: - name: "miniz" @@ -985,7 +1000,7 @@ sdks: distribution-type: source code distribution-repository: GitHub release package-name: C-Core - location: https://github.com/pubnub/c-core/releases/tag/v4.13.1 + location: https://github.com/pubnub/c-core/releases/tag/v4.14.0 requires: - name: "miniz" @@ -1047,7 +1062,7 @@ sdks: distribution-type: source code distribution-repository: GitHub release package-name: C-Core - location: https://github.com/pubnub/c-core/releases/tag/v4.13.1 + location: https://github.com/pubnub/c-core/releases/tag/v4.14.0 requires: - name: "miniz" @@ -1108,7 +1123,7 @@ sdks: distribution-type: source code distribution-repository: GitHub release package-name: C-Core - location: https://github.com/pubnub/c-core/releases/tag/v4.13.1 + location: https://github.com/pubnub/c-core/releases/tag/v4.14.0 requires: - name: "miniz" @@ -1164,7 +1179,7 @@ sdks: distribution-type: source code distribution-repository: GitHub release package-name: C-Core - location: https://github.com/pubnub/c-core/releases/tag/v4.13.1 + location: https://github.com/pubnub/c-core/releases/tag/v4.14.0 requires: - name: "miniz" @@ -1217,7 +1232,7 @@ sdks: distribution-type: source code distribution-repository: GitHub release package-name: C-Core - location: https://github.com/pubnub/c-core/releases/tag/v4.13.1 + location: https://github.com/pubnub/c-core/releases/tag/v4.14.0 requires: - name: "miniz" diff --git a/CHANGELOG.md b/CHANGELOG.md index 6096aec3..100e6944 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -1,3 +1,14 @@ +## v4.14.0 +October 15 2024 + +#### Added +- Add core Event Engine implementation with the required set of types and methods. +- Add Subscribe Event Engine built atop of the core Event Engine implementation. +- Add the following entities: channel, channel group, uuid and channel metadata objects. +- Add objects to manage subscriptions and provides interface for update listeners. +- Add new event listeners, which make it possible to add listeners to a specific entity or group of entities (though subscription and subscription set). +- Added ability to configure automated retry policies for failed requests. + ## v4.13.1 September 05 2024 diff --git a/CMakeLists.txt b/CMakeLists.txt index 8d1e204f..32c34b42 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -60,7 +60,9 @@ num_option(ONLY_PUBSUB_API "Only pubsub API" OFF) num_option(USE_PROXY "Use proxy" ON) num_option(USE_GZIP_COMPRESSION "Use gzip compression" ON) num_option(RECEIVE_GZIP_RESPONSE "Use gzip decompression" ON) +num_option(USE_RETRY_CONFIGURATION "Use requests retry" OFF) num_option(USE_SUBSCRIBE_V2 "Use subscribe v2" ON) +num_option(USE_SUBSCRIBE_EVENT_ENGINE "Use Subscribe Event Engine" OFF) num_option(USE_ADVANCED_HISTORY "Use advanced history" ON) num_option(USE_OBJECTS_API "Use objects API" ON) num_option(USE_AUTO_HEARTBEAT "Use auto heartbeat" ON) @@ -97,13 +99,16 @@ set(FLAGS "\ -D PUBNUB_PROXY_API=${USE_PROXY} \ -D PUBNUB_USE_GZIP_COMPRESSION=${USE_GZIP_COMPRESSION} \ -D PUBNUB_RECEIVE_GZIP_RESPONSE=${RECEIVE_GZIP_RESPONSE} \ + -D PUBNUB_USE_RETRY_CONFIGURATION=${USE_RETRY_CONFIGURATION} \ -D PUBNUB_USE_SUBSCRIBE_V2=${USE_SUBSCRIBE_V2} \ + -D PUBNUB_USE_SUBSCRIBE_EVENT_ENGINE=${USE_SUBSCRIBE_EVENT_ENGINE} \ -D PUBNUB_USE_OBJECTS_API=${USE_OBJECTS_API} \ -D PUBNUB_USE_ACTIONS_API=${USE_ACTIONS_API} \ -D PUBNUB_USE_AUTO_HEARTBEAT=${USE_AUTO_HEARTBEAT} \ -D PUBNUB_USE_GRANT_TOKEN_API=${USE_GRANT_TOKEN_API} \ -D PUBNUB_USE_REVOKE_TOKEN_API=${USE_REVOKE_TOKEN_API} \ -D PUBNUB_USE_FETCH_HISTORY=${USE_FETCH_HISTORY} \ + -D PUBNUB_CRYPTO_API=${USE_CRYPTO_API} \ -D PUBNUB_RAND_INIT_VECTOR=${USE_LEGACY_CRYPTO_RANDOM_IV} \ -D PUBNUB_MBEDTLS=${MBEDTLS}") @@ -324,13 +329,43 @@ if(${RECEIVE_GZIP_RESPONSE}) ${CMAKE_CURRENT_LIST_DIR}/core/pbgzip_decompress.c) endif() -if(${USE_SUBSCRIBE_V2}) +if (${USE_RETRY_CONFIGURATION}) set(FEATURE_SOURCEFILES + ${FEATURE_SOURCEFILES} + ${CMAKE_CURRENT_LIST_DIR}/core/pbcc_request_retry_timer.c + ${CMAKE_CURRENT_LIST_DIR}/core/pubnub_retry_configuration.c) +endif () + +if(${USE_SUBSCRIBE_V2}) +set(FEATURE_SOURCEFILES ${FEATURE_SOURCEFILES} ${CMAKE_CURRENT_LIST_DIR}/core/pbcc_subscribe_v2.c ${CMAKE_CURRENT_LIST_DIR}/core/pubnub_subscribe_v2.c) endif() +if(${USE_SUBSCRIBE_EVENT_ENGINE}) + message(STATUS "Using subscribe event engine API") + set(LIB_SOURCEFILES + ${LIB_SOURCEFILES} + ${CMAKE_CURRENT_LIST_DIR}/lib/pbarray.c + ${CMAKE_CURRENT_LIST_DIR}/lib/pbhash_set.c + ${CMAKE_CURRENT_LIST_DIR}/lib/pbref_counter.c + ${CMAKE_CURRENT_LIST_DIR}/lib/pbstrdup.c) + set(FEATURE_SOURCEFILES + ${FEATURE_SOURCEFILES} + ${CMAKE_CURRENT_LIST_DIR}/core/pbcc_memory_utils.c + ${CMAKE_CURRENT_LIST_DIR}/core/pbcc_event_engine.c + ${CMAKE_CURRENT_LIST_DIR}/core/pbcc_subscribe_event_engine.c + ${CMAKE_CURRENT_LIST_DIR}/core/pbcc_subscribe_event_engine_states.c + ${CMAKE_CURRENT_LIST_DIR}/core/pbcc_subscribe_event_engine_events.c + ${CMAKE_CURRENT_LIST_DIR}/core/pbcc_subscribe_event_engine_effects.c + ${CMAKE_CURRENT_LIST_DIR}/core/pbcc_subscribe_event_engine_transitions.c + ${CMAKE_CURRENT_LIST_DIR}/core/pbcc_subscribe_event_listener.c + ${CMAKE_CURRENT_LIST_DIR}/core/pubnub_entities.c + ${CMAKE_CURRENT_LIST_DIR}/core/pubnub_subscribe_event_engine.c + ${CMAKE_CURRENT_LIST_DIR}/core/pubnub_subscribe_event_listener.c) +endif() + if(${USE_ADVANCED_HISTORY}) set(FEATURE_SOURCEFILES ${FEATURE_SOURCEFILES} @@ -440,12 +475,7 @@ if(${OPENSSL}) set(FEATURE_SOURCEFILES ${CMAKE_CURRENT_LIST_DIR}/openssl/pbpal_openssl.c ${CMAKE_CURRENT_LIST_DIR}/openssl/pbpal_connect_openssl.c - ${CMAKE_CURRENT_LIST_DIR}/openssl/pbpal_openssl_blocking_io.c - ${CMAKE_CURRENT_LIST_DIR}/core/pbcc_crypto.c - ${CMAKE_CURRENT_LIST_DIR}/core/pbcc_crypto_aes_cbc.c - ${CMAKE_CURRENT_LIST_DIR}/core/pbcc_crypto_legacy.c - ${CMAKE_CURRENT_LIST_DIR}/core/pubnub_crypto.c - ${CMAKE_CURRENT_LIST_DIR}/openssl/pbaes256.c + ${CMAKE_CURRENT_LIST_DIR}/openssl/pbpal_openssl_blocking_io.c ${FEATURE_SOURCEFILES}) set(FEATURE_SOURCEFILES @@ -454,6 +484,16 @@ if(${OPENSSL}) set(LDLIBS ${LDLIBS}) + if(USE_CRYPTO_API) + set(FEATURE_SOURCEFILES + ${CMAKE_CURRENT_LIST_DIR}/core/pbcc_crypto.c + ${CMAKE_CURRENT_LIST_DIR}/core/pbcc_crypto_aes_cbc.c + ${CMAKE_CURRENT_LIST_DIR}/core/pbcc_crypto_legacy.c + ${CMAKE_CURRENT_LIST_DIR}/core/pubnub_crypto.c + ${CMAKE_CURRENT_LIST_DIR}/openssl/pbaes256.c + ${FEATURE_SOURCEFILES}) + endif() + if(UNIX) set(FEATURE_SOURCEFILES ${FEATURE_SOURCEFILES} ${CMAKE_CURRENT_LIST_DIR}/openssl/pbpal_add_system_certs_posix.c) elseif(WIN32 OR WIN64 OR MSVC) @@ -669,6 +709,11 @@ if(${EXAMPLES}) subscribe_publish_from_callback publish_callback_subloop_sample publish_queue_callback_subloop) + if(${USE_SUBSCRIBE_EVENT_ENGINE}) + set(EXAMPLE_LIST + subscribe_event_engine_sample + ${EXAMPLE_LIST}) + endif() if (WITH_CPP) set(CPP_EXAMPLE_LIST subscribe_publish_callback_sample # Only supports callback! diff --git a/core/Makefile b/core/Makefile index 73c10d12..79de3513 100644 --- a/core/Makefile +++ b/core/Makefile @@ -106,7 +106,6 @@ pubnub_subscribe_v2_unittest: $(PROJECT_SOURCEFILES) $(SUBSCRIBE_V2_SOURCEFILES) $(CGREEN_RUNNER) ./pubnub_subscribe_v2_unit_test.so #$(GCOVR) -r . --html --html-details -o coverage.html - clean: find . -type d -iname "*.dSYM" -exec rm -rf {} \+ find . -type f -name "*.so" -o -name "*.gcda" -o -name "*.gcno" -o -name "*.html" | xargs -r rm -rf diff --git a/core/pbauto_heartbeat.c b/core/pbauto_heartbeat.c index dbdee5aa..2f860991 100644 --- a/core/pbauto_heartbeat.c +++ b/core/pbauto_heartbeat.c @@ -59,6 +59,7 @@ static int copy_context_settings(pubnub_t* pb_clone, pubnub_t const* pb) PUBNUB_ASSERT_OPT(pb_valid_ctx_ptr(pb)); pubnub_mutex_lock(pb_clone->monitor); + pb_clone->core.auth_token = pb->core.auth_token; pb_clone->core.auth = pb->core.auth; strcpy(pb_clone->core.user_id, pb->core.user_id); if (PUBNUB_ORIGIN_SETTABLE) { diff --git a/core/pbcc_crypto.h b/core/pbcc_crypto.h index 90fae34e..1661c3c2 100644 --- a/core/pbcc_crypto.h +++ b/core/pbcc_crypto.h @@ -2,6 +2,7 @@ #ifndef PBCC_CRYPTO_H #define PBCC_CRYPTO_H +#if PUBNUB_CRYPTO_API /** @file pbcc_crypto.h @@ -294,7 +295,6 @@ void pbcc_set_crypto_module(struct pbcc_context *ctx, struct pubnub_crypto_provi */ pubnub_crypto_provider_t *pbcc_get_crypto_module(struct pbcc_context *ctx); -#if PUBNUB_CRYPTO_API /** Decrypt the message received from PubNub with the crypto module. diff --git a/core/pbcc_event_engine.c b/core/pbcc_event_engine.c new file mode 100644 index 00000000..e41535c1 --- /dev/null +++ b/core/pbcc_event_engine.c @@ -0,0 +1,710 @@ +/* -*- c-file-style:"stroustrup"; indent-tabs-mode: nil -*- */ +#include "pbcc_event_engine.h" + +#include + +#include "core/pbcc_memory_utils.h" +#include "core/pubnub_assert.h" +#include "core/pubnub_mutex.h" +#include "lib/pbref_counter.h" +#include "core/pubnub_log.h" + + +// ---------------------------------------------- +// Types +// ---------------------------------------------- + +/** Sharable data type definition. */ +struct pbcc_ee_data { + /** Object references counter. */ + pbref_counter_t* counter; + /** + * @brief Pointer to the data which should be associated with any of + * Event Engine objects: event, state, transition, or invocation. + */ + void* data; + /** Data destruct function. */ + pbcc_ee_data_free_function_t data_free; +}; + +/** Event Engine state definition. */ +struct pbcc_ee_state { + /** Object references counter. */ + pbref_counter_t* counter; + /** A specific Event Engine state type. */ + int type; + /** + * @brief Pointer to the additional information associated with a specific + * Event Engine state. + */ + pbcc_ee_data_t* data; + /** + * @brief Pointer to the invocations, which should be called when the + * Event Engine is about to enter a specific state. + * + * \b Important: `pbarray_t` should be created without elements destructor + * to let `pbcc_ee_transition_t` manage invocation objects' life-cycle. + */ + pbarray_t* on_enter_invocations; + /** + * @brief Pointer to the invocations, which should be called when the + * Event Engine is about to leave a specific state. + * + * \b Important: `pbarray_t` should be created without elements destructor + * to let `pbcc_ee_transition_t` manage invocation objects' life-cycle. + */ + pbarray_t* on_exit_invocations; + /** + * @brief State-specific transition function to compute target state and + * invocations to call. + */ + pbcc_ee_transition_function_t transition; +}; + +/** Event Engine effect invocation status definition */ +typedef enum { + /** Effect invocation just has been created and not processed. */ + PBCC_EE_INVOCATION_CREATED, + /** Invocation running is running effect implementation. */ + PBCC_EE_INVOCATION_RUNNING, + /** Invocation completed effect execution. */ + PBCC_EE_INVOCATION_COMPLETED +} pbcc_ee_invocation_status; + +/** Business logic invocation definition. */ +struct pbcc_ee_invocation { + /** A specific Event Engine invocation type. */ + int type; + /** + * @brief Whether invocation should be processed immediately without + * addition to the queue or not. + */ + bool immediate; + /** Current effect invocation status. */ + pbcc_ee_invocation_status status; + /** Object references counter. */ + pbref_counter_t* counter; + /** + * @brief Pointer to the additional information associated with a specific + * the Event Engine dispatcher into the effect execution function. + */ + pbcc_ee_data_t* data; + /** State-specific effect function. */ + pbcc_ee_effect_function_t effect; +}; + +/** State transition instruction definition. */ +struct pbcc_ee_transition { + /** + * @brief Pointer to the next Event Engine state, which is based on current + * state and event passed into `transition` function. + */ + pbcc_ee_state_t* target_state; + /** + * @brief Pointer to the list of effect invocations which should be called + * during the current Event Engine state change. + * + * \b Important: Array allocated with `pbcc_ee_invocation_free` elements + * destructor. + */ + pbarray_t* invocations; +}; + +/** Event Engine definition. */ +struct pbcc_event_engine { + /** Pointer to the current Event Engine state. */ + pbcc_ee_state_t* current_state; + /** + * @brief Pointer to the FIFO list of invocations. + * + * \b Important: Array allocated with `pbcc_ee_invocation_free` elements + * destructor. + * + * @see pbcc_ee_invocation_t + */ + pbarray_t* invocations; + /** Shared resources access lock. */ + pubnub_mutex_t mutw; +}; + + +// ---------------------------------------------- +// Function prototypes +// ---------------------------------------------- + +/** + * @brief Update current Event Engine state. + * + * @param ee Pointer to the event engine for which state should be changed. + * @param state Pointer to the state which should be set as current Event Engine + * state. + */ +static void pbcc_ee_current_state_set_( + pbcc_event_engine_t* ee, + pbcc_ee_state_t* state); + +/** + * @brief Create a copy of existing state. + * + * @note Copies share state and update reference counter to reflect active + * entities. + * + * @param state Pointer to the state object from which copy should be created. + * @return Pointer to the ready to use Event Engine state or `NULL` in case of + * insufficient memory error. The returned pointer must be passed to + * the `pbcc_ee_state_free` to avoid a memory leak. + * + * @see pbcc_ee_state_free + */ +static pbcc_ee_state_t* pbcc_ee_state_copy_(pbcc_ee_state_t* state); + +/** + * @brief Add invocation into invocations list. + * + * @param invocations Pointer to the array where new invocation should be added. + * @param invocation Pointer to the invocation which should be called when + * Event Engine will leave `state` as current state before + * entering new state. + *
\b Important: Event Engine will manage `invocation` + * memory after successful addition. + * @return Result of invocations list modification operation. + */ +static enum pubnub_res pbcc_ee_state_add_invocation_( + pbarray_t** invocations, + pbcc_ee_invocation_t* invocation); + +/** + * @brief Update Event Engine state transition invocations list. + * + * @param transition Pointer to the Event Engine state transition object for + * which invocations list should be updated. + * @param invocations Pointer to the list with invocation objects for + * transition. + * @return `false` if transition's invocations list modification failed. + */ +static bool pbcc_ee_transition_add_invocations_( + const pbcc_ee_transition_t* transition, + pbarray_t* invocations); + +/** + * @brief Execute effect invocation. + * + * @param invocation Pointer to the invocation which should execute effect. + */ +static void pbcc_ee_invocation_exec_(pbcc_ee_invocation_t* invocation); + +/** + * @brief Handle effect invocation completion. + * + * @param ee Pointer to the event engine which enqueued effect + * execution. + * @param invocation Pointer to the invocation which has been used to execute + * effect. + * @param paused Whether `invocation` execution has been paused or not. + * Paused invocation will reset its execution status to + * 'CREATED'. When paused, then the Event Engine should be + * told when the next invocation should be processed. + */ +static void pbcc_ee_effect_completion_( + pbcc_event_engine_t* ee, + pbcc_ee_invocation_t* invocation, + bool paused); + + +// ---------------------------------------------- +// Functions +// ---------------------------------------------- + +pbcc_event_engine_t* pbcc_ee_alloc(pbcc_ee_state_t* initial_state) +{ + PUBNUB_ASSERT_OPT(NULL != initial_state); + + PBCC_ALLOCATE_TYPE(ee, pbcc_event_engine_t, true, NULL); + ee->current_state = initial_state; + pubnub_mutex_init(ee->mutw); + + ee->invocations = pbarray_alloc(5, + PBARRAY_RESIZE_BALANCED, + PBARRAY_GENERIC_CONTENT_TYPE, + (pbarray_element_free) + pbcc_ee_invocation_free); + if (NULL == ee->invocations) { + PUBNUB_LOG_ERROR("pbcc_ee_alloc: failed to allocate memory\n"); + pbcc_ee_free(&ee); + return NULL; + } + + return ee; +} + +void pbcc_ee_free(pbcc_event_engine_t** ee) +{ + if (NULL == ee || NULL == *ee) { return; } + + pubnub_mutex_lock((*ee)->mutw); + if (NULL != (*ee)->invocations) { pbarray_free(&(*ee)->invocations); } + if (NULL != (*ee)->current_state) + pbcc_ee_state_free(&(*ee)->current_state); + pubnub_mutex_unlock((*ee)->mutw); + pubnub_mutex_destroy((*ee)->mutw); + free(*ee); + *ee = NULL; +} + +pbcc_ee_state_t* pbcc_ee_current_state(pbcc_event_engine_t* ee) +{ + pubnub_mutex_lock(ee->mutw); + pbcc_ee_state_t* state = pbcc_ee_state_copy_(ee->current_state); + pubnub_mutex_unlock(ee->mutw); + + return state; +} + +enum pubnub_res pbcc_ee_handle_event( + pbcc_event_engine_t* ee, + pbcc_ee_event_t* event) +{ + PUBNUB_ASSERT_OPT(NULL != ee->current_state); + + pubnub_mutex_lock(ee->mutw); + pbcc_ee_state_t* state = pbcc_ee_state_copy_(ee->current_state); + pbcc_ee_transition_t* transition = ee->current_state->transition( + ee, + state, + event); + pbcc_ee_state_free(&state); + pbcc_ee_event_free(&event); + + if (NULL == transition) { + PUBNUB_LOG_ERROR("pbcc_ee_handle_event: failed to allocate memory\n"); + pubnub_mutex_unlock(ee->mutw); + return PNR_OUT_OF_MEMORY; + } + + /** + * Check whether current state should be changed in response to `event` + * or not. This is special case of transition to mark that there were no + * error during `transition` function call. + */ + if (NULL == transition->target_state) { + pbcc_ee_transition_free(&transition); + pubnub_mutex_unlock(ee->mutw); + return PNR_OK; + } + + /** + * Check whether any invocations expected to be called during transition + * or not. + */ + if (NULL == transition->invocations) { + pbcc_ee_current_state_set_(ee, transition->target_state); + pbcc_ee_transition_free(&transition); + pubnub_mutex_unlock(ee->mutw); + + return PNR_OK; + } + + /** + * Merging transition invocations into Event Engine invocations queue. + */ + enum pubnub_res rslt = PNR_OK; + pbarray_t* invocations = transition->invocations; + const size_t count = pbarray_count(invocations); + const pbarray_res merge_rslt = pbarray_merge(ee->invocations, invocations); + if (PBAR_OK != merge_rslt) { + rslt = PBTT_ADD_MEMBERS == merge_rslt + ? PNR_OUT_OF_MEMORY + : PNR_INVALID_PARAMETERS; + } + + if (PNR_OK != rslt || 0 == count) { + if (0 != count) + pbarray_subtract(ee->invocations, invocations, true); + if (PNR_OK == rslt) + pbcc_ee_current_state_set_(ee, transition->target_state); + pubnub_mutex_unlock(ee->mutw); + + pbcc_ee_transition_free(&transition); + pbcc_ee_process_next_invocation(ee); + + return PNR_OK; + } + + pbcc_ee_current_state_set_(ee, transition->target_state); + pubnub_mutex_unlock(ee->mutw); + + for (size_t i = 0; i < count; ++i) { + pbcc_ee_invocation_t* inv = (pbcc_ee_invocation_t*) + pbarray_element_at(invocations, i); + /** + * Make sure that invocation has proper ref count after addition to the + * Event Engine invocations queue from transition. + */ + pbref_counter_increment(inv->counter); + } + + /** Check whether any immediate */ + pbcc_ee_invocation_t* inv = + (pbcc_ee_invocation_t*)pbarray_first(invocations); + if (inv->immediate) { pbcc_ee_invocation_exec_(inv); } + else { pbcc_ee_process_next_invocation(ee); } + + pbcc_ee_transition_free(&transition); + + return rslt; +} + +size_t pbcc_ee_process_next_invocation(pbcc_event_engine_t* ee) +{ + pubnub_mutex_lock(ee->mutw); + const size_t count = pbarray_count(ee->invocations); + + if (0 == count) { + pubnub_mutex_unlock(ee->mutw); + return count; + } + + pbcc_ee_invocation_t* invocation = (pbcc_ee_invocation_t*) + pbarray_first(ee->invocations); + pubnub_mutex_unlock(ee->mutw); + pbcc_ee_invocation_exec_(invocation); + + return count; +} + +void pbcc_ee_handle_effect_completion( + pbcc_event_engine_t* ee, + pbcc_ee_invocation_t* invocation) +{ + /** Check whether invocation is running or not. */ + if (PBCC_EE_INVOCATION_RUNNING != invocation->status) { return; } + + pubnub_mutex_lock(ee->mutw); + invocation->status = PBCC_EE_INVOCATION_COMPLETED; + pbarray_remove(ee->invocations, (void**)&invocation, true); + pubnub_mutex_unlock(ee->mutw); +} + +pbcc_ee_data_t* pbcc_ee_data_alloc( + void* data, + const pbcc_ee_data_free_function_t data_free) +{ + PBCC_ALLOCATE_TYPE(ee_data, pbcc_ee_data_t, true, NULL); + ee_data->counter = pbref_counter_alloc(); + ee_data->data = data; + ee_data->data_free = data_free; + + return ee_data; +} + +void pbcc_ee_data_free(pbcc_ee_data_t* data) +{ + if (NULL == data) { return; } + + if (0 == pbref_counter_free(data->counter)) { + if (NULL != data->data_free) { data->data_free(data->data); } + free(data); + } +} + +void* pbcc_ee_data_value(pbcc_ee_data_t* data) +{ + if (NULL == data) { return NULL; } + return data->data; +} + +pbcc_ee_data_t* pbcc_ee_data_copy(pbcc_ee_data_t* data) +{ + if (NULL == data) { return NULL; } + + pbref_counter_increment(data->counter); + + return data; +} + +pbcc_ee_state_t* pbcc_ee_state_alloc( + const int type, + pbcc_ee_data_t* data, + const pbcc_ee_transition_function_t transition) +{ + PBCC_ALLOCATE_TYPE(state, pbcc_ee_state_t, true, NULL); + state->counter = pbref_counter_alloc(); + state->type = type; + state->data = pbcc_ee_data_copy(data); + state->on_enter_invocations = NULL; + state->on_exit_invocations = NULL; + state->transition = transition; + + return state; +} + +void pbcc_ee_state_free(pbcc_ee_state_t** state) +{ + if (NULL == state || NULL == *state) { return; } + + if (0 == pbref_counter_free((*state)->counter)) { + if (NULL != (*state)->on_enter_invocations) + pbarray_free(&(*state)->on_enter_invocations); + if (NULL != (*state)->on_exit_invocations) + pbarray_free(&(*state)->on_exit_invocations); + if (NULL != (*state)->data) { pbcc_ee_data_free((*state)->data); } + + free(*state); + *state = NULL; + } +} + +int pbcc_ee_state_type(const pbcc_ee_state_t* state) +{ + return state->type; +} + +const pbcc_ee_data_t* pbcc_ee_state_data(const pbcc_ee_state_t* state) +{ + if (NULL == state->data) { return NULL; } + return pbcc_ee_data_copy(state->data); +} + +enum pubnub_res pbcc_ee_state_add_on_enter_invocation( + pbcc_ee_state_t* state, + pbcc_ee_invocation_t* invocation) +{ + return pbcc_ee_state_add_invocation_(&state->on_enter_invocations, + invocation); +} + +enum pubnub_res pbcc_ee_state_add_on_exit_invocation( + pbcc_ee_state_t* state, + pbcc_ee_invocation_t* invocation) +{ + return pbcc_ee_state_add_invocation_(&state->on_exit_invocations, + invocation); +} + +pbcc_ee_event_t* pbcc_ee_event_alloc(const int type, pbcc_ee_data_t* data) +{ + PBCC_ALLOCATE_TYPE(event, pbcc_ee_event_t, true, NULL); + event->type = type; + event->data = data; + + return event; +} + +void pbcc_ee_event_free(pbcc_ee_event_t** event) +{ + if (NULL == event || NULL == *event) { return; } + + if (NULL != (*event)->data) { pbcc_ee_data_free((*event)->data); } + free(*event); + *event = NULL; +} + +pbcc_ee_invocation_t* pbcc_ee_invocation_alloc( + int type, + const pbcc_ee_effect_function_t effect, + pbcc_ee_data_t* data, + const bool immediate) +{ + PBCC_ALLOCATE_TYPE(invocation, pbcc_ee_invocation_t, true, NULL); + invocation->immediate = immediate; + invocation->counter = pbref_counter_alloc(); + invocation->status = PBCC_EE_INVOCATION_CREATED; + invocation->effect = effect; + invocation->data = pbcc_ee_data_copy(data); + invocation->type = type; + + return invocation; +} + +void pbcc_ee_invocation_cancel_by_type(pbcc_event_engine_t* ee, int type) +{ + pubnub_mutex_lock(ee->mutw); + const size_t count = pbarray_count(ee->invocations); + if (0 == count) { + pubnub_mutex_unlock(ee->mutw); + return; + } + + for (size_t i = 0; i < count; i++) { + const pbcc_ee_invocation_t* invocation = + pbarray_element_at(ee->invocations, i); + + if (invocation->status == PBCC_EE_INVOCATION_RUNNING) { continue; } + if (type == invocation->type) { + pbarray_remove(ee->invocations, (void**)&invocation, false); + break; + } + } + pubnub_mutex_unlock(ee->mutw); +} + +void pbcc_ee_invocation_free(pbcc_ee_invocation_t* invocation) +{ + if (NULL == invocation) { return; } + + if (0 == pbref_counter_free(invocation->counter)) { + if (NULL != invocation->data) { pbcc_ee_data_free(invocation->data); } + free(invocation); + } +} + +pbcc_ee_transition_t* pbcc_ee_transition_alloc( + pbcc_event_engine_t* ee, + pbcc_ee_state_t* state, + pbarray_t* invocations) +{ + PUBNUB_ASSERT_OPT(NULL != ee->current_state); + + pubnub_mutex_lock(ee->mutw); + const pbcc_ee_state_t* current_state = ee->current_state; + PBCC_ALLOCATE_TYPE(transition, pbcc_ee_transition_t, true, NULL); + transition->target_state = pbcc_ee_state_copy_(state); + + /** Gather transition invocations list. */ + size_t invocations_count = 0; + if (NULL != state && NULL != current_state->on_exit_invocations) + invocations_count += pbarray_count(current_state->on_exit_invocations); + if (NULL != state && NULL != invocations) + invocations_count += pbarray_count(invocations); + if (NULL != state && NULL != state->on_enter_invocations) + invocations_count += pbarray_count(state->on_enter_invocations); + + if (invocations_count > 0) { + transition->invocations = pbarray_alloc(invocations_count, + PBARRAY_RESIZE_NONE, + PBARRAY_GENERIC_CONTENT_TYPE, + (pbarray_element_free) + pbcc_ee_invocation_free); + + pbarray_t* on_exit = current_state->on_exit_invocations; + pbarray_t* on_enter = + NULL != state ? state->on_enter_invocations : NULL; + + /** + * Attention: `invocations` should be called before `on_exit` because + * in most of the cases `on_exit` cancels previous request and it will + * reset all read buffers making it impossible to process data received + * for current state. + */ + if (NULL == transition->invocations || + !pbcc_ee_transition_add_invocations_(transition, invocations) || + !pbcc_ee_transition_add_invocations_(transition, on_exit) || + !pbcc_ee_transition_add_invocations_(transition, on_enter)) { + PUBNUB_LOG_ERROR( + "pbcc_ee_transition_alloc: failed to allocate memory " + "for transition invocations\n"); + if (NULL != invocations) { pbarray_free(&invocations); } + pbcc_ee_transition_free(&transition); + pubnub_mutex_unlock(ee->mutw); + + return NULL; + } + } + else { transition->invocations = NULL; } + if (NULL != invocations) { pbarray_free(&invocations); } + pubnub_mutex_unlock(ee->mutw); + + return transition; +} + +void pbcc_ee_transition_free(pbcc_ee_transition_t** transition) +{ + if (NULL == transition || NULL == *transition) { return; } + + pbcc_ee_state_free(&(*transition)->target_state); + if (NULL != (*transition)->invocations) + pbarray_free(&(*transition)->invocations); + free(*transition); + *transition = NULL; +} + +void pbcc_ee_current_state_set_(pbcc_event_engine_t* ee, pbcc_ee_state_t* state) +{ + pbcc_ee_state_free(&ee->current_state); + ee->current_state = pbcc_ee_state_copy_(state); +} + +pbcc_ee_state_t* pbcc_ee_state_copy_(pbcc_ee_state_t* state) +{ + if (NULL == state) { return NULL; } + + /** + * Copying is done by making sure that an instance won't be freed until last + * reference to it will be freed. + */ + pbref_counter_increment(state->counter); + + return state; +} + +enum pubnub_res pbcc_ee_state_add_invocation_( + pbarray_t** invocations, + pbcc_ee_invocation_t* invocation) +{ + if (NULL == *invocations) { + *invocations = pbarray_alloc(1, + PBARRAY_RESIZE_CONSERVATIVE, + PBARRAY_GENERIC_CONTENT_TYPE, + (pbarray_element_free) + pbcc_ee_invocation_free); + if (NULL == *invocations) { + PUBNUB_LOG_ERROR("pbcc_ee_state_add_invocation: failed to allocate " + "memory for invocations\n"); + return PNR_OUT_OF_MEMORY; + } + } + + const pbarray_res result = pbarray_add(*invocations, invocation); + if (PBAR_OUT_OF_MEMORY == result) { return PNR_OUT_OF_MEMORY; } + if (PBAR_INDEX_OUT_OF_RANGE == result) { return PNR_INVALID_PARAMETERS; } + + return PNR_OK; +} + +bool pbcc_ee_transition_add_invocations_( + const pbcc_ee_transition_t* transition, + pbarray_t* invocations) +{ + if (NULL == invocations) { return true; } + if (PBAR_OK != pbarray_merge(transition->invocations, invocations)) + return false; + + const size_t count = pbarray_count(invocations); + for (size_t i = 0; i < count; i++) { + const pbcc_ee_invocation_t* invocation = + pbarray_element_at(invocations, i); + pbref_counter_increment(invocation->counter); + } + + return true; +} + +void pbcc_ee_invocation_exec_(pbcc_ee_invocation_t* invocation) +{ + /** Check whether invocation still runnable or not. */ + if (PBCC_EE_INVOCATION_CREATED != invocation->status) { return; } + + invocation->status = PBCC_EE_INVOCATION_RUNNING; + invocation->effect( + invocation, + invocation->data, + (pbcc_ee_effect_completion_function_t)pbcc_ee_effect_completion_); +} + +void pbcc_ee_effect_completion_( + pbcc_event_engine_t* ee, + pbcc_ee_invocation_t* invocation, + const bool paused) +{ + /** Check whether invocation is running or not. */ + if (PBCC_EE_INVOCATION_RUNNING != invocation->status) { return; } + + /** Check whether called effect asked to pospone its execution or not. */ + if (paused) { + invocation->status = PBCC_EE_INVOCATION_CREATED; + return; + } + + pbcc_ee_handle_effect_completion(ee, invocation); + pbcc_ee_process_next_invocation(ee); +} \ No newline at end of file diff --git a/core/pbcc_event_engine.h b/core/pbcc_event_engine.h new file mode 100644 index 00000000..b51ad844 --- /dev/null +++ b/core/pbcc_event_engine.h @@ -0,0 +1,450 @@ +/* -*- c-file-style:"stroustrup"; indent-tabs-mode: nil -*- */ +#ifndef PBCC_EVENT_ENGINE_H +#define PBCC_EVENT_ENGINE_H + + +/** + * @file pbcc_event_engine.h + * @brief Event Engine core implementation with base type definitions and + * functions. + */ + +#include "core/pubnub_api_types.h" +#include "lib/pbarray.h" + + +// ---------------------------------------------- +// Types forwarding +// ---------------------------------------------- + +/** Event Engine definition. */ +typedef struct pbcc_event_engine pbcc_event_engine_t; + +/** + * @brief State transition instruction definition. + * + * Transition contains information about expected target state and set of + * invocations which should be performed during state change. + */ +typedef struct pbcc_ee_transition pbcc_ee_transition_t; + +/** + * @brief Business logic invocation definition. + * + * Invocation is intention to exec business logic and contains required data + * for dispatcher to call actual implementation. + */ +typedef struct pbcc_ee_invocation pbcc_ee_invocation_t; + +/** Event Engine state event forward declaration. */ +typedef struct pbcc_ee_event pbcc_ee_event_t; + +/** Event Engine state forward declaration. */ +typedef struct pbcc_ee_state pbcc_ee_state_t; + +/** User-provided data forward declaration. */ +typedef struct pbcc_ee_data pbcc_ee_data_t; + + +// ---------------------------------------------- +// Types +// ---------------------------------------------- + +/** + * @brief Transition computation function definition. + * + * @param ee Pointer to the Event Engine which will be requesting + * for transition details in response to event. + * @param current_state Pointer to the current Event Engine state. + * @param event Pointer to the Event Engine state changing event with + * additional information required to decide on target + * state. + * @return Pointer to the transition for Event Engine current state change + * operation or `NULL` in case of insufficient memory error. The + * returned pointer must be passed to the `pbcc_ee_transition_free` to + * avoid a memory leak. + */ +typedef pbcc_ee_transition_t* (*pbcc_ee_transition_function_t)( + pbcc_event_engine_t* ee, + const pbcc_ee_state_t* current_state, + const pbcc_ee_event_t* event); + +/** + * @brief User-provided data (`pbcc_ee_data_t`) destruction function + * definition. + * + * @param data Pointer to the user-provided data pointer, which should free up + * used resources. + */ +typedef void (*pbcc_ee_data_free_function_t)(void* data); + +/** + * @brief Effect invocation completion function definition. + * + * Function used by effect execution code to tell when required actions has been + * done. + * + * @param ee Pointer to the event engine which enqueued effect + * execution. + * @param invocation Pointer to the invocation which has been used to execute + * effect. + * @param paused Whether `invocation` execution has been paused or not. + * Paused invocation will reset its execution status to + * 'CREATED'. When paused, then the Event Engine should be + * told when the next invocation should be processed. + */ +typedef void (*pbcc_ee_effect_completion_function_t)( + pbcc_event_engine_t* ee, + pbcc_ee_invocation_t* invocation, + bool paused); + +/** + * @brief Invocation effect function definition. + * + * Function which is used by invocation dispatcher to perform business logic + * for specific invocation. + * + * @param invocation Pointer to the invocation which has been used to execute + * effect. + * @param data Pointer to the user-provided data, which will be passed + * into function for processing. + * @param cb Pointer to the effect execution completion callback + * function. + * @return Effect execution result. + */ +typedef void (*pbcc_ee_effect_function_t)( + const pbcc_ee_invocation_t* invocation, + pbcc_ee_data_t* data, + pbcc_ee_effect_completion_function_t cb); + +/** Event Engine state event definition. */ +struct pbcc_ee_event +{ + /** Specific Event Engine event type. */ + int type; + /** + * @brief Pointer to the additional information associated with a specific + * Event Engine event by event emitting code. + */ + pbcc_ee_data_t* data; +}; + + +// ---------------------------------------------- +// Functions +// ---------------------------------------------- + +/** + * @brief Create Event Engine with initial state. + * + * @param initial_state Pointer to the state from which all events handling and + * transition should start. + * @return Pointer to the ready to use Event Engine or `NULL` in case of + * insufficient memory error. The returned pointer must be passed to + * the `pbcc_ee_free` to avoid a memory leak. + */ +pbcc_event_engine_t* pbcc_ee_alloc(pbcc_ee_state_t* initial_state); + +/** + * @brief Clean up resources used by the Event Engine object. + * + * \b Important: Provided event engine pointer will be NULLified. + * + * @param ee Pointer to the Event Engine, which should free up used resources. + */ +void pbcc_ee_free(pbcc_event_engine_t** ee); + +/** + * @brief Retrieve copy of the current Event Engine state. + * + * @note Copies share state and update reference counter to reflect active + * entities. + * + * @param ee Pointer to the Event Engine for which current state should be + * returned. + * @return The current state in which the Event Engine is at this moment. The + * returned pointer must be passed to the `pbcc_ee_state_free` to avoid + * a memory leak. + * + * @see pbcc_ee_state_free + */ +pbcc_ee_state_t* pbcc_ee_current_state(pbcc_event_engine_t* ee); + +/** + * @brief Handle new event for transition computation. + * + * @note Memory allocated for `event` will be managed by the Event Engine. + * + * @param ee Pointer to the Event Engine which should process event. + * @param event Pointer to the Event Engine state changing event object pointer + * which should be processed. + * @return Result of event handling operation. + */ +enum pubnub_res pbcc_ee_handle_event( + pbcc_event_engine_t* ee, + pbcc_ee_event_t* event); + +/** + * @brief Process any scheduled invocations. + * + * @param ee Pointer to the Event Engine which should dequeue + * and process next invocation. + * @return Number of enqueued effect invocations (ongoing and just created). + */ +size_t pbcc_ee_process_next_invocation(pbcc_event_engine_t* ee); + +/** + * @brief Handle effect invocation completion. + * + * @param ee Pointer to the event engine which enqueued effect + * execution. + * @param invocation Pointer to the invocation which has been used to execute + * effect. + */ +void pbcc_ee_handle_effect_completion( + pbcc_event_engine_t* ee, + pbcc_ee_invocation_t* invocation); + +/** + * @brief Create sharable user-provided data object. + * + * @note Memory allocated for `data` will be managed by the Event Engine if + * `data` destructor provided (`data_free`). + * + * @param data Pointer to the data which will be shared between + * different objects. + * @param data_free User-provided data destruction function. + * @return Pointer to the ready to use sharable Event Engine data or `NULL` + * in case of insufficient memory error. The returned pointer must be + * passed to the `pbcc_ee_data_free` to avoid a memory leak. + */ +pbcc_ee_data_t* pbcc_ee_data_alloc( + void* data, + pbcc_ee_data_free_function_t data_free); + +/** + * @brief Clean up resources used by the Event Engine shared data object. + * + * @param data Pointer to the Event Engine data, which should free up used + * resources. + */ +void pbcc_ee_data_free(pbcc_ee_data_t* data); + +/** + * @brief User-provided value. + * + * @param data Pointer to the data object from which user-provided value + * should be retrieved. + * @return Pointer to the user-provided value. + */ +void* pbcc_ee_data_value(pbcc_ee_data_t* data); + +/** + * @brief Create a copy of existing data. + * + * @note Copies share data and update reference counter to reflect active + * entities. + * + * @param data Pointer to the data object from which copy should be created. + * @return Pointer to the ready to use Event Engine data or `NULL` in case of + * insufficient memory error. The returned pointer must be passed to + * the `pbcc_ee_data_free` to avoid a memory leak. + */ +pbcc_ee_data_t* pbcc_ee_data_copy(pbcc_ee_data_t* data); + +/** + * @brief Create Event Engine state object. + * + * @note Memory allocated for `data` will be managed by the Event Engine if + * `data` destructor provided (`data_free`). + * + * @param type Specific Event Engine event type. + * @param data Pointer to the additional information associated with a + * specific Event Engine event by event emitting code. + * @param transition State-specific transition function to compute target state + * and invocations to call. + * @return Pointer to the Event Engine state. + * + * @see pbcc_ee_state_free + */ +pbcc_ee_state_t* pbcc_ee_state_alloc( + int type, + pbcc_ee_data_t* data, + pbcc_ee_transition_function_t transition); + +/** + * @brief Clean up resources used by event engine state object. + * + * \b Important: Provided event engine state pointer will be NULLified. + * + * @param state Pointer to the state, which should free up resources. + */ +void pbcc_ee_state_free(pbcc_ee_state_t** state); + +/** + * @brief Get user-specified Event Engine state type. + * + * @note Safely, this function can be used only with state objects received from + * `pbcc_ee_current_state` function because the method returns shallow + * copy and there is no risk of segmentation fault error because of + * attempt to access freed memory. + * + * @param state Pointer to the state for which type should be retrieved. + * @return User-specified state type. + * + * @see pbcc_ee_current_state + */ +int pbcc_ee_state_type(const pbcc_ee_state_t* state); + +/** + * @brief Get data associated with Event Engine state. + * + * @note Safely, this function can be used only with state objects received from + * `pbcc_ee_current_state` function because the method returns shallow + * copy and there is no risk of segmentation fault error because of + * attempt to access freed memory. + * + * @param state Pointer to the state for which associated data should be + * retrieved. + * @return Data associated with state object or `NULL` in case if there is no + * data. The returned pointer must be passed to the `pbcc_ee_data_free` + * to avoid a memory leak. + * + * @see pbcc_ee_current_state + */ +const pbcc_ee_data_t* pbcc_ee_state_data(const pbcc_ee_state_t* state); + +/** + * @brief Add on state \b enter invocation. + * + * @note Memory allocated for `invocation` will be managed by the Event Engine. + * + * @param state Pointer to the state, for which `on enter` invocation + * will be added. + * @param invocation Pointer to the invocation which should be called when + * Event Engine will enter `state` as current state.
+ * \b Important: Event Engine will manage `invocation` + * memory after successful addition. + * @return Result of `on enter` invocations list modification operation. + */ +enum pubnub_res pbcc_ee_state_add_on_enter_invocation( + pbcc_ee_state_t* state, + pbcc_ee_invocation_t* invocation); + +/** + * @brief Add on state \b exit invocation. + * + * @note Memory allocated for `invocation` will be managed by the Event Engine. + * + * @param state Pointer to the state, for which `on exit` invocation + * will be added. + * @param invocation Pointer to the invocation which should be called when + * Event Engine will leave `state` as current state before + * entering new state.
+ * \b Important: Event Engine will manage `invocation` memory + * after successful addition. + * @return Result of `on exit` invocations list modification operation. + */ +enum pubnub_res pbcc_ee_state_add_on_exit_invocation( + pbcc_ee_state_t* state, + pbcc_ee_invocation_t* invocation); + +/** + * @brief Create Event Engine state changing event. + * + * @note Memory allocated for `data` will be managed by the Event Engine if + * `data` destructor provided (`data_free`). + * + * @param type Specific Event Engine event type. + * @param data Pointer to the additional information associated with a + * specific Event Engine event by the event emitting code. + * @return Pointer to ready to use event object or `NULL` in case of + * insufficient memory error. The returned pointer must be passed to + * the `pbcc_ee_event_free` to avoid a memory leak. + */ +pbcc_ee_event_t* pbcc_ee_event_alloc(int type, pbcc_ee_data_t* data); + +/** + * @brief Clean up resources used by the event object. + * + * \b Important: Provided event pointer will be NULLified. + * + * @param event Pointer to the event object which, should free up used + * resources. + */ +void pbcc_ee_event_free(pbcc_ee_event_t** event); + +/** + * @brief Create business logic invocation intent object. + * + * @note Memory allocated for `data` will be managed by the Event Engine if + * `data` destructor provided (`data_free`). + * + * @param type A specific Event Engine invocation type. + * @param effect Function which will be called by dispatcher during + * invocation handling. + * @param data Pointer to the data which has been associated by business + * logic with an event and will be passed to the `effect` + * function. + * @param immediate Whether invocation should be processed immediately without + * addition to the queue or not. + * @return Pointer to the ready to use invocation object or `NULL` in case of + * insufficient memory error. The returned pointer must be passed to + * the `pbcc_ee_invocation_free` to avoid a memory leak. + */ +pbcc_ee_invocation_t* pbcc_ee_invocation_alloc( + int type, + pbcc_ee_effect_function_t effect, + pbcc_ee_data_t* data, + bool immediate); + +/** + * @brief Cancel next non-running invocation of specified type. + * + * The first invocation which is currently not active and matches the specified + * type will be dequeued and freed. + * + * @param ee Pointer to the Event Engine which enqueue scheduled invocations. + * @param type A type of invocation which should be cancelled. + */ +void pbcc_ee_invocation_cancel_by_type(pbcc_event_engine_t* ee, int type); + +/** + * @brief Clean up resources used by the invocation object. + * + * @param invocation Pointer to the invocation, which should free up + * resources. + */ +void pbcc_ee_invocation_free(pbcc_ee_invocation_t* invocation); + +/** + * @brief Create state transition instruction object. + * + * @note Memory allocated for `state` and `invocations` will be managed by + * the Event Engine. + * + * @param ee Pointer to the Event Engine which will perform + * transition. + * @param state Pointer to the target Event Engine state. + * @param invocations Pointer to the list of invocations which should be done + * before entering target state. + * @return Pointer to the ready to use Event Engine state transition object or + * `NULL` in case of insufficient memory error. The returned pointer + * must be passed to the `pbcc_ee_transition_free` to avoid a memory + * leak. + */ +pbcc_ee_transition_t* pbcc_ee_transition_alloc( + pbcc_event_engine_t* ee, + pbcc_ee_state_t* state, + pbarray_t* invocations); + +/** + * @brief Clean up resources used by the transition object. + * + * @note Function will `NULL`ify provided transition pointer. + * + * @param transition Pointer to the transition, which should free up + * resources. + */ +void pbcc_ee_transition_free(pbcc_ee_transition_t** transition); +#endif // #ifndef PBCC_EVENT_ENGINE_H diff --git a/core/pbcc_fetch_history.h b/core/pbcc_fetch_history.h index 8288cbc4..c16739e4 100644 --- a/core/pbcc_fetch_history.h +++ b/core/pbcc_fetch_history.h @@ -1,8 +1,8 @@ /* -*- c-file-style:"stroustrup"; indent-tabs-mode: nil -*- */ -#if PUBNUB_USE_FETCH_HISTORY -#if !defined INC_PBCC_FETCH_HISTORY -#define INC_PBCC_FETCH_HISTORY +#ifndef INC_PBCC_FETCH_HISTORY +#define INC_PBCC_FETCH_HISTORY +#if PUBNUB_USE_FETCH_HISTORY struct pbcc_context; @@ -33,6 +33,6 @@ enum pubnub_res pbcc_parse_fetch_history_response(struct pbcc_context* pb); pubnub_chamebl_t pbcc_get_fetch_history(struct pbcc_context* pb); -#endif /* INC_PBCC_FETCH_HISTORY */ #endif /* PUBNUB_USE_FETCH_HISTORY */ +#endif /* #ifndef INC_PBCC_FETCH_HISTORY */ diff --git a/core/pbcc_memory_utils.c b/core/pbcc_memory_utils.c new file mode 100644 index 00000000..36f384aa --- /dev/null +++ b/core/pbcc_memory_utils.c @@ -0,0 +1,17 @@ +/* -*- c-file-style:"stroustrup"; indent-tabs-mode: nil -*- */ +#include "pbcc_memory_utils.h" + +#include + + +// ---------------------------------------------- +// Functions +// ---------------------------------------------- + +void pbcc_free_ptr(void** ptr) +{ + if (NULL == ptr || NULL == *ptr) return; + + free(*ptr); + *ptr = NULL; +} \ No newline at end of file diff --git a/core/pbcc_memory_utils.h b/core/pbcc_memory_utils.h new file mode 100644 index 00000000..2d3f4420 --- /dev/null +++ b/core/pbcc_memory_utils.h @@ -0,0 +1,61 @@ +#ifndef PBCC_MEMORY_UTILS_H +#define PBCC_MEMORY_UTILS_H + + +/** + * @file pbcc_memory_utils.h + * @brief Module with utility function and macros to work with memory. + */ + +#include +#include "core/pubnub_log.h" + + +// ---------------------------------------------- +// Macros +// ---------------------------------------------- + + +/** + * @brief Macro for type allocation and error handling. + * @code + * my_user_t* db_user_allocate(db_t* db, char* name) { + * PBCC_ALLOCATE_TYPE(user, my_user_t, false, NULL); + * user->f_name = strdup(name); + * db_connect_store(db, user); + * + * return user; + * } + * @endcode + * + * @param var The name of the variable which will be assigned to + * the allocated memory pointer. + * @param type Name of type for which memory will be allocated. + * @param print_error_message Whether a memory allocation error should be + * printed to the console and log file or not. + * @param return_value What should be returned from the function which + * use macro in case of a memory allocation error. + * The value can be 'NULL' or 'enum' field . + */ +#define PBCC_ALLOCATE_TYPE(var, type, print_error_message, return_value) \ + type *var = (type *)malloc(sizeof(type)); \ + if (NULL == var) { \ + if (true == print_error_message) { \ + PUBNUB_LOG_ERROR("[%s] Failed to allocate memory for '%s'\n",\ + __func__, #type); \ + } \ + return return_value; \ + } + + +// ---------------------------------------------- +// Functions +// ---------------------------------------------- + +/** + * @brief Clean up resources allocated for pointer and set it to `NULL`. + * + * @param ptr Pointer with dynamically allocated resources for clean up. + */ +void pbcc_free_ptr(void** ptr); +#endif //PBCC_MEMORY_UTILS_H diff --git a/core/pbcc_request_retry_timer.c b/core/pbcc_request_retry_timer.c new file mode 100644 index 00000000..c59e3c34 --- /dev/null +++ b/core/pbcc_request_retry_timer.c @@ -0,0 +1,198 @@ +/* -*- c-file-style:"stroustrup"; indent-tabs-mode: nil -*- */ +#include "pbcc_request_retry_timer.h" + +#include +#include +#include + +#include "lib/msstopwatch/msstopwatch.h" +#include "core/pbcc_memory_utils.h" +#include "core/pubnub_assert.h" +#include "core/pubnub_mutex.h" +#include "core/pb_sleep_ms.h" +#include "pubnub_internal.h" + + +// ---------------------------------------------- +// Constants +// ---------------------------------------------- + +/** + * @brief Polling timeout. + * + * Timeout used with sleep function inside of `while` loop to break constant + * polling and give other processes some CPU time. + */ +#define PBCC_POLLING_TIMEOUT 10 + + +// ---------------------------------------------- +// Types +// ---------------------------------------------- + +#if defined _WIN32 +typedef DWORD pubnub_thread_t; +typedef FILETIME pubnub_timespec_t; +typedef void pubnub_watcher_t; +#define thread_handle_field() HANDLE thread_handle; +#define UNIT_IN_MILLI 1000 +#else +typedef pthread_t pubnub_thread_t; +typedef struct timespec pubnub_timespec_t; +typedef void* pubnub_watcher_t; +#define thread_handle_field() +#endif + +/** Failed requests retry timer definition. */ +struct pbcc_request_retry_timer { + /** PubNub context for which timer will restart requests. */ + pubnub_t* pb; +#if defined(PUBNUB_CALLBACK_API) + /** Timer's run thread. */ + pubnub_thread_t timer_thread; +#endif // #if defined(PUBNUB_CALLBACK_API) + /** Whether timer is running at this moment or not. */ + volatile bool running; + /** Active timer delay value in milliseconds. */ + uint16_t delay; + /** Shared resources access lock. */ + pubnub_mutex_t mutw; +}; + + +// ---------------------------------------------- +// Function prototypes +// ---------------------------------------------- + + +/** + * @brief Run request retry timer. + * + * Launch timer which will await till timeout and try to restart request if + * possible. + * + * @param timer Pointer to the timer which should run. + */ +static void* pbcc_request_retry_timer_run_(pbcc_request_retry_timer_t* timer); + + +// ---------------------------------------------- +// Functions +// ---------------------------------------------- + +pbcc_request_retry_timer_t* pbcc_request_retry_timer_alloc(pubnub_t* pb) +{ + PBCC_ALLOCATE_TYPE(timer, pbcc_request_retry_timer_t, true, NULL); + pubnub_mutex_init(timer->mutw); +#if defined(PUBNUB_CALLBACK_API) + timer->timer_thread = NULL; +#endif // #if defined(PUBNUB_CALLBACK_API) + timer->running = false; + timer->pb = pb; + + return timer; +} + +void pbcc_request_retry_timer_free(pbcc_request_retry_timer_t** timer) +{ + if (NULL == timer || NULL == *timer) { return; } + + pbcc_request_retry_timer_stop(*timer); + pubnub_mutex_destroy((*timer)->mutw); + free(*timer); + *timer = NULL; +} + +void pbcc_request_retry_timer_start( + pbcc_request_retry_timer_t* timer, + const uint16_t delay) +{ + if (NULL == timer || timer->running) { return; } + PUBNUB_ASSERT(pb_valid_ctx_ptr(timer->pb)); + + /** There shouldn't be any active timers. */ + pbcc_request_retry_timer_stop(timer); + + pubnub_mutex_lock(timer->pb->monitor); + pubnub_mutex_lock(timer->mutw); + timer->running = true; + timer->delay = delay; + pubnub_mutex_unlock(timer->mutw); + timer->pb->state = PBS_WAIT_RETRY; + timer->pb->core.http_retry_count++; + pubnub_mutex_unlock(timer->pb->monitor); + +#if defined(PUBNUB_CALLBACK_API) + if (pthread_create(&timer->timer_thread, + NULL, + (void*(*)(void*))pbcc_request_retry_timer_run_, + timer)) { + PUBNUB_LOG_ERROR( + "pbcc_request_retry_timer_start: unable to create thread"); + + /** + * To not stall FSM we let it retry request right away if thread can't + * be created. + */ + pubnub_mutex_lock(timer->mutw); + timer->running = false; + pubnub_mutex_unlock(timer->mutw); + pubnub_mutex_lock(timer->pb->monitor); + if (PBS_WAIT_RETRY == timer->pb->state) { + timer->pb->state = PBS_RETRY; + pbnc_fsm(timer->pb); + } + pubnub_mutex_unlock(timer->pb->monitor); + } +#else + pbcc_request_retry_timer_run_(timer); +#endif // #if defined(PUBNUB_CALLBACK_API) +} + +void pbcc_request_retry_timer_stop(pbcc_request_retry_timer_t* timer) +{ + if (NULL == timer) { return; } + + pubnub_mutex_lock(timer->mutw); +#if defined(PUBNUB_CALLBACK_API) + if (NULL != timer->timer_thread) { + pthread_join(timer->timer_thread, NULL); + timer->timer_thread = NULL; + } +#endif // #if defined(PUBNUB_CALLBACK_API) + timer->running = false; + pubnub_mutex_unlock(timer->mutw); +} + +void* pbcc_request_retry_timer_run_(pbcc_request_retry_timer_t* timer) +{ + const pbmsref_t t0 = pbms_start(); + pbms_t delta = 0; + bool stopped = false; + + pubnub_mutex_lock(timer->mutw); + const uint16_t delay = timer->delay; + pubnub_mutex_unlock(timer->mutw); + while (delta < delay) { + pubnub_mutex_lock(timer->mutw); + if (!timer->running) { + pubnub_mutex_unlock(timer->mutw); + stopped = true; + break; + } + pubnub_mutex_unlock(timer->mutw); + + delta = pbms_elapsed(t0); + pb_sleep_ms(PBCC_POLLING_TIMEOUT); + } + + pubnub_mutex_lock(timer->pb->monitor); + if (!stopped && PBS_WAIT_RETRY == timer->pb->state) { + timer->pb->state = PBS_RETRY; + pbnc_fsm(timer->pb); + } + pubnub_mutex_unlock(timer->pb->monitor); + pbcc_request_retry_timer_stop(timer); + + return NULL; +} \ No newline at end of file diff --git a/core/pbcc_request_retry_timer.h b/core/pbcc_request_retry_timer.h new file mode 100644 index 00000000..159bf94d --- /dev/null +++ b/core/pbcc_request_retry_timer.h @@ -0,0 +1,75 @@ +/* -*- c-file-style:"stroustrup"; indent-tabs-mode: nil -*- */ +#ifndef PBCC_REQUEST_RETRY_TIMER_H +#define PBCC_REQUEST_RETRY_TIMER_H +#if PUBNUB_USE_RETRY_CONFIGURATION + + +/** + * @file pbcc_request_retry_timer.h + * @brief PubNub request retry timer implementation. + */ + +#include + +#include "core/pubnub_ntf_callback.h" +#include "core/pubnub_api_types.h" + + +// ---------------------------------------------- +// Types forwarding +// ---------------------------------------------- + +/** + * @brief Failed requests retry timer definition. + * + * Timer basically holds further transactions processing by putting FSM into new + * state for the time of delay. + */ +typedef struct pbcc_request_retry_timer pbcc_request_retry_timer_t; + + +// ---------------------------------------------- +// Functions +// ---------------------------------------------- + +/** + * @brief Create a request retry timer. + * + * @param pb Pointer to the PubNub context for which timer will restart request. + * @return Pointer to the ready to use request retry timer or `NULL` in case of + * insufficient memory error. The returned pointer must be passed to the + * `pbcc_request_retry_timer_free` to avoid a memory leak. + */ +pbcc_request_retry_timer_t* pbcc_request_retry_timer_alloc(pubnub_t* pb); + +/** + * @brief Release resources used by request retry timer object. + * + * @note Function will NULLify provided request retry timer pointer. + * + * @param timer Pointer to the timer object, which should free up resources. + */ +void pbcc_request_retry_timer_free(pbcc_request_retry_timer_t** timer); + +/** + * @brief Start request retry timer. + * + * This function will be blocking if built without PUBNUB_CALLBACK_API + * precompile macro. + * + * @param timer Pointer to the timer which will start and call completion + * handler on timeout. + * @param delay Timer timeout in milliseconds. + */ +void pbcc_request_retry_timer_start( + pbcc_request_retry_timer_t* timer, + uint16_t delay); + +/** + * @brief Stop request retry timer. + * + * @param timer Pointer to the timer which should stop earlier that timeout. + */ +void pbcc_request_retry_timer_stop(pbcc_request_retry_timer_t* timer); +#endif // #if PUBNUB_USE_RETRY_CONFIGURATION +#endif // #ifndef PBCC_REQUEST_RETRY_TIMER_H diff --git a/core/pbcc_subscribe_event_engine.c b/core/pbcc_subscribe_event_engine.c new file mode 100644 index 00000000..bab5eca0 --- /dev/null +++ b/core/pbcc_subscribe_event_engine.c @@ -0,0 +1,1120 @@ +/* -*- c-file-style:"stroustrup"; indent-tabs-mode: nil -*- */ +#include "pbcc_subscribe_event_engine.h" + +#include +#include +#include +#include + +#include "core/pubnub_helper.h" +#include "core/pubnub_subscribe_event_engine_internal.h" +#include "core/pbcc_subscribe_event_engine_states.h" +#include "core/pbcc_subscribe_event_engine_events.h" +#include "core/pbcc_subscribe_event_engine_types.h" +#include "core/pbcc_subscribe_event_listener.h" +#include "core/pubnub_server_limits.h" +#include "core/pbcc_event_engine.h" +#include "core/pbcc_memory_utils.h" +#include "core/pubnub_mutex.h" +#include "core/pubnub_log.h" +#include "pubnub_callback.h" +#include "pubnub_internal.h" +#include "lib/pbhash_set.h" +#include "lib/pbstrdup.h" + + +// ---------------------------------------------- +// Constants +// ---------------------------------------------- + +/** How many subscribable objects `pbcc_subscribe_ee_t` can hold by default. */ +#define SUBSCRIBABLE_LENGTH 10 + + +// ---------------------------------------------- +// Function prototypes +// ---------------------------------------------- + +/** + * @brief Transit to `handshaking` / `receiving` state. + * + * Depending on from the flow, Subscribe Event Engine will transit to the + * `receiving` state directly or through the `handshaking` state (if required). + * Updated list of subscribables will be used to trigger proper event for + * subscription loop. + * + * @param ee Pointer to the Subscribe Event Engine, which should transit + * to `receiving` state. + * @param cursor Pointer to the subscription cursor to be used with + * subscribe REST API. The SDK will try to catch up on missed + * messages if a cursor with older PubNub high-precision + * timetoken has been provided. Pass `NULL` to keep using + * cursor received from the previous subscribe REST API + * response. + * @param update Whether list of subscribable objects should be updated or + * not (for unsubscribe, it updated beforehand). + * @param sent_by_ee Whether event has been sent by Subscribe Event Engine or + * not. + * @return Result of subscribe enqueue transaction. + */ +static enum pubnub_res pbcc_subscribe_ee_subscribe_( + pbcc_subscribe_ee_t* ee, + const pubnub_subscribe_cursor_t* cursor, + bool update, + bool sent_by_ee); + +/** + * @brief Transit to `handshaking` / `receiving` or `unsubscribed` state. + * + * Depending on from whether there are any channels left for the next + * subscription loop can be created, `SUBSCRIBE_EE_EVENT_UNSUBSCRIBE_ALL` event + * instead of `EVENT_SUBSCRIPTION_CHANGE`. + * + * @param ee Pointer to the Subscribe Event Engine, which should + * transit to `handshaking` / `receiving` or `unsubscribed` + * state. + * @param subscribables Pointer to the hash with subscribables from which user + * requested to unsubscribed. + * @return Result of unsubscribe enqueue transaction. + */ +static enum pubnub_res pbcc_subscribe_ee_unsubscribe_( + pbcc_subscribe_ee_t* ee, + pbhash_set_t* subscribables); + +/** + * @brief Perform postponed `unsubscribe` / `leave` operation. + * + * Subscribe Event Engine may save channels and groups from which it should + * unsubscribe for later time because there is ongoing request. Saved + * information will be used in this function to complete `leave` operation. + * + * @param ee Pointer to the Subscribe Event Engine, which may have information for `leave` request. + * @return `true` in case if `leave` request sending has been scheduled. + */ +static bool pbcc_subscribe_ee_postponed_unsubscribe_(pbcc_subscribe_ee_t* ee); + +/** + * @brief Actualize list of subscribable objects. + * + * Update list of subscribable objects using `active` subscriptions and + * subscription sets. + * + * @note Because of subscription / subscription set unsubscribe process, it is + * impossible to implement subscribable update in + * `_subscribe` / `_unsubscribe` methods. Subscribable addition, won't be + * a problem, but the removal process won't be able to figure out whether + * subscribable has been added by standalone subscribe object alone or + * another subscription object from subscription set tried to do the same. + * As a result, the object may be removed from the subscription loop. + * + * @param ee Pointer to the Subscribe Event Engine for which list of + * subscribable objects should be updated. + * @return Result of subscribable list update. + * + * @see pubnub_subscription_t + * @see pubnub_subscription_set_t + */ +static enum pubnub_res pbcc_subscribe_ee_update_subscribables_( + const pbcc_subscribe_ee_t* ee); + +/** + * @brief Update Subscribe Event Engine list of subscribable objects. + * + * @param ee Pointer to the Subscribe Event Engine for which + * `subscribables` should be added. + * @param subscribables Pointer to the hash set with subscribable objects which + * is used for channels and channel groups list aggregation + * for subscription loop. + * @return Result of subscribables addition operation. + */ +static enum pubnub_res pbcc_subscribe_ee_add_subscribables_( + const pbcc_subscribe_ee_t* ee, + pbhash_set_t* subscribables); + +/** + * @brief Get list of channel and group identifiers from provided subscribables. + * + * @param subscribables Pointer to the hash set with unique subscribable + * objects for which list of comma-separated + * identifiers should be generated. + * @param [out] channels The parameter will hold a pointer to the + * byte string with comma-separated / empty channel + * identifiers for subscribe loop or `NULL` in case + * of insufficient memory error. + * @param [out] channel_groups The parameter will hold a pointer to the + * byte string with comma-separated / empty channel + * group identifiers for subscribe loop or `NULL` in + * case of insufficient memory error. + * @param include_presence Whether channels with `-pnpres` should be + * included into `channels` and `channel_groups`. + * @return Result of subscribables computation operation. + */ +static enum pubnub_res pbcc_subscribe_ee_subscribables_( + pbhash_set_t* subscribables, + char** channels, + char** channel_groups, + bool include_presence); + +/** + * @brief Handle real-time updates from subscription loop. + * + * @param pb Pointer to the PubNub context which received updates in + * subscription loop. + * @param trans Type of transaction for which callback has been called. + * @param result Result of `trans` processing + * @param user_data Pointer to the Subscribe Event Engine object. + */ +static void pbcc_subscribe_callback_( + pubnub_t* pb, + enum pubnub_trans trans, + enum pubnub_res result, + const void* user_data); + +/** + * @brief Create string from the `array` elements. + * + * @param array Pointer to the array with string elements which should be + * joined by the separator. + * @param separator Pointer to the string which should be used to join elements. + * @return Pointer to the string from the `array` elements or `NULL` in case of + * insufficient memory error. The returned pointer must be passed to the + * `free` to avoid a memory leak. + */ +static char* pbcc_subscribe_ee_joined_array_elements_( + pbarray_t* array, + const char* separator); + + +// ---------------------------------------------- +// Functions +// ---------------------------------------------- + +pbcc_subscribe_ee_t* pbcc_subscribe_ee_alloc(pubnub_t* pb) +{ + PUBNUB_ASSERT(pb_valid_ctx_ptr(pb)); + + PBCC_ALLOCATE_TYPE(ee, pbcc_subscribe_ee_t, true, NULL); + ee->subscription_sets = pbarray_alloc( + SUBSCRIBABLE_LENGTH, + PBARRAY_RESIZE_BALANCED, + PBARRAY_GENERIC_CONTENT_TYPE, + (pbarray_element_free)pubnub_subscription_set_free_); + ee->subscriptions = pbarray_alloc( + SUBSCRIBABLE_LENGTH, + PBARRAY_RESIZE_BALANCED, + PBARRAY_GENERIC_CONTENT_TYPE, + (pbarray_element_free)pubnub_subscription_free_); + ee->leave_channels = pbarray_alloc( + SUBSCRIBABLE_LENGTH, + PBARRAY_RESIZE_BALANCED, + PBARRAY_CHAR_CONTENT_TYPE, + (pbarray_element_free)free); + ee->leave_channel_groups = pbarray_alloc( + SUBSCRIBABLE_LENGTH, + PBARRAY_RESIZE_BALANCED, + PBARRAY_CHAR_CONTENT_TYPE, + (pbarray_element_free)free); + ee->current_transaction = PBTT_NONE; + ee->cancel_invocation = NULL; + pubnub_mutex_init(ee->mutw); + + if (NULL == ee->subscriptions) { + PUBNUB_LOG_ERROR("pbcc_subscribe_ee_alloc: failed to allocate memory " + "for subscriptions list\n"); + pbcc_subscribe_ee_free(&ee); + return NULL; + } + if (NULL == ee->subscription_sets) { + PUBNUB_LOG_ERROR("pbcc_subscribe_ee_alloc: failed to allocate memory " + "for subscription sets list\n"); + pbcc_subscribe_ee_free(&ee); + return NULL; + } + if (NULL == ee->leave_channels) { + PUBNUB_LOG_ERROR("pbcc_subscribe_ee_alloc: failed to allocate memory " + "for unsubscribed channels\n"); + pbcc_subscribe_ee_free(&ee); + return NULL; + } + if (NULL == ee->leave_channel_groups) { + PUBNUB_LOG_ERROR("pbcc_subscribe_ee_alloc: failed to allocate memory " + "for unsubscribed channel groups\n"); + pbcc_subscribe_ee_free(&ee); + return NULL; + } + + /** Prepare storage for computed list of subscribable objects. */ + ee->subscribables = pbhash_set_alloc( + SUBSCRIBABLE_LENGTH, + PBHASH_SET_CHAR_CONTENT_TYPE, + (pbhash_set_element_free)pubnub_subscribable_free_); + if (NULL == ee->subscribables) { + PUBNUB_LOG_ERROR("pbcc_subscribe_ee_alloc: failed to allocate memory " + "for subscribable objects\n"); + pbcc_subscribe_ee_free(&ee); + return NULL; + } + ee->filter_expr = NULL; + ee->heartbeat = PUBNUB_DEFAULT_PRESENCE_HEARTBEAT_VALUE; + ee->status = PNSS_SUBSCRIPTION_STATUS_DISCONNECTED; + ee->pb = pb; + + /** Setup event engine */ + ee->ee = pbcc_ee_alloc(pbcc_unsubscribed_state_alloc()); + if (NULL == ee->ee) { + PUBNUB_LOG_ERROR("pbcc_subscribe_ee_alloc: failed to allocate memory " + "for event engine with initial state\n"); + pbcc_subscribe_ee_free(&ee); + return NULL; + } + + /** Setup event listener */ + ee->event_listener = pbcc_event_listener_alloc(pb); + if (NULL == ee->ee) { + PUBNUB_LOG_ERROR("pbcc_subscribe_ee_alloc: failed to allocate memory " + "for event listener\n"); + pbcc_subscribe_ee_free(&ee); + return NULL; + } + + pubnub_register_callback(pb, + (pubnub_callback_t)pbcc_subscribe_callback_, + ee); + + return ee; +} + +void pbcc_subscribe_ee_free(pbcc_subscribe_ee_t** ee) +{ + if (NULL == ee || NULL == *ee) { return; } + + pubnub_mutex_lock((*ee)->mutw); + if (NULL != (*ee)->subscription_sets) + pbarray_free(&(*ee)->subscription_sets); + if (NULL != (*ee)->subscriptions) { pbarray_free(&(*ee)->subscriptions); } + if (NULL != (*ee)->subscribables) + pbhash_set_free(&(*ee)->subscribables); + if (NULL != (*ee)->leave_channel_groups) + pbarray_free(&(*ee)->leave_channel_groups); + if (NULL != (*ee)->leave_channels) { pbarray_free(&(*ee)->leave_channels); } + if (NULL != (*ee)->filter_expr) { free((*ee)->filter_expr); } + if (NULL != (*ee)->ee) { pbcc_ee_free(&(*ee)->ee); } + if (NULL != (*ee)->event_listener) + pbcc_event_listener_free(&(*ee)->event_listener); + pubnub_mutex_unlock((*ee)->mutw); + pubnub_mutex_destroy((*ee)->mutw); + free(*ee); + *ee = NULL; +} + +pbcc_event_listener_t* pbcc_subscribe_ee_event_listener( + const pbcc_subscribe_ee_t* ee) +{ + return ee->event_listener; +} + +pbcc_ee_data_t* pbcc_subscribe_ee_current_state_context( + const pbcc_subscribe_ee_t* ee) +{ + pbcc_ee_state_t* current_state = pbcc_ee_current_state(ee->ee); + const pbcc_ee_data_t* data = NULL; + + if (NULL != current_state) { data = pbcc_ee_state_data(current_state); } + pbcc_ee_state_free(¤t_state); + + return (pbcc_ee_data_t*)data; +} + +void pbcc_subscribe_ee_set_filter_expression( + pbcc_subscribe_ee_t* ee, + const char* filter_expr) +{ + PUBNUB_ASSERT_OPT(NULL != ee); + + pubnub_mutex_lock(ee->mutw); + if (NULL != ee->filter_expr) { free(ee->filter_expr); } + if (NULL == filter_expr) { ee->filter_expr = NULL; } + else { ee->filter_expr = pbstrdup(filter_expr); } + pubnub_mutex_unlock(ee->mutw); +} + +void pbcc_subscribe_ee_set_heartbeat( + pbcc_subscribe_ee_t* ee, + const unsigned heartbeat) +{ + PUBNUB_ASSERT_OPT(NULL != ee); + + pubnub_mutex_lock(ee->mutw); + ee->heartbeat = PUBNUB_MINIMUM_PRESENCE_HEARTBEAT_VALUE > heartbeat + ? PUBNUB_MINIMUM_PRESENCE_HEARTBEAT_VALUE + : heartbeat; + pubnub_mutex_unlock(ee->mutw); +} + +enum pubnub_res pbcc_subscribe_ee_subscribe_with_subscription( + pbcc_subscribe_ee_t* ee, + pubnub_subscription_t* sub, + const pubnub_subscribe_cursor_t* cursor) +{ + PUBNUB_ASSERT_OPT(NULL != ee); + + pubnub_mutex_lock(ee->mutw); + if (PBAR_OK != pbarray_add(ee->subscriptions, sub)) { + PUBNUB_LOG_ERROR("pbcc_subscribe_ee_subscribe_with_subscription: failed" + " to allocate memory to store subscription\n"); + pubnub_mutex_unlock(ee->mutw); + return PNR_OUT_OF_MEMORY; + } + + const enum pubnub_res rslt = pbcc_subscribe_ee_subscribe_( + ee, + cursor, + true, + false); + pubnub_mutex_unlock(ee->mutw); + + return rslt; +} + +enum pubnub_res pbcc_subscribe_ee_unsubscribe_with_subscription( + pbcc_subscribe_ee_t* ee, + const pubnub_subscription_t* sub) +{ + PUBNUB_ASSERT_OPT(NULL != ee); + + pubnub_mutex_lock(ee->mutw); + if (!pbarray_contains(ee->subscriptions, sub)) { + pubnub_mutex_unlock(ee->mutw); + return PNR_SUB_NOT_FOUND; + } + + pbhash_set_t* subs = pubnub_subscription_subscribables_(sub, NULL); + + if (NULL == subs) { + PUBNUB_LOG_ERROR("pbcc_subscribe_ee_unsubscribe_with_subscription: " + "failed to allocate memory for subscribables\n"); + pubnub_mutex_unlock(ee->mutw); + return PNR_OUT_OF_MEMORY; + } + + pbarray_remove(ee->subscriptions, (void**)&sub, true); + const enum pubnub_res rslt = pbcc_subscribe_ee_unsubscribe_(ee, subs); + + /** + * Subscribables list not needed anymore because channels / groups list + * already composed for presence leave REST API in + * `pbcc_subscribe_ee_unsubscribe_`. + */ + pbhash_set_free_with_destructor( + &subs, + (pbhash_set_element_free)pubnub_subscribable_free_); + pubnub_mutex_unlock(ee->mutw); + + return rslt; +} + +enum pubnub_res pbcc_subscribe_ee_subscribe_with_subscription_set( + pbcc_subscribe_ee_t* ee, + pubnub_subscription_set_t* set, + const pubnub_subscribe_cursor_t* cursor) +{ + PUBNUB_ASSERT_OPT(NULL != ee); + + pubnub_mutex_lock(ee->mutw); + if (PBAR_OK != pbarray_add(ee->subscription_sets, set)) { + PUBNUB_LOG_ERROR( + "pbcc_subscribe_ee_subscribe_with_subscription_set: failed to " + "allocate memory to store subscription set\n"); + pubnub_mutex_unlock(ee->mutw); + return PNR_OUT_OF_MEMORY; + } + + const enum pubnub_res rslt = pbcc_subscribe_ee_subscribe_( + ee, + cursor, + true, + false); + pubnub_mutex_unlock(ee->mutw); + + return rslt; +} + +enum pubnub_res pbcc_subscribe_ee_unsubscribe_with_subscription_set( + pbcc_subscribe_ee_t* ee, + pubnub_subscription_set_t* set) +{ + PUBNUB_ASSERT_OPT(NULL != ee); + + pubnub_mutex_lock(ee->mutw); + if (!pbarray_contains(ee->subscription_sets, set)) { + pubnub_mutex_unlock(ee->mutw); + return PNR_SUB_NOT_FOUND; + } + + pbhash_set_t* subs = pubnub_subscription_set_subscribables_(set); + + if (NULL == subs) { + PUBNUB_LOG_ERROR("pbcc_subscribe_ee_unsubscribe_with_subscription_set: " + "failed to allocate memory for subscribables\n"); + pubnub_mutex_unlock(ee->mutw); + return PNR_OUT_OF_MEMORY; + } + + pbarray_remove(ee->subscription_sets, (void**)&set, true); + const enum pubnub_res rslt = pbcc_subscribe_ee_unsubscribe_(ee, subs); + + /** + * Subscribables list not needed anymore because channels / groups list + * already composed for presence leave REST API in + * `pbcc_subscribe_ee_unsubscribe_`. + */ + pbhash_set_free_with_destructor( + &subs, + (pbhash_set_element_free)pubnub_subscribable_free_); + pubnub_mutex_unlock(ee->mutw); + + return rslt; +} + +enum pubnub_res pbcc_subscribe_ee_change_subscription_with_subscription_set( + pbcc_subscribe_ee_t* ee, + const pubnub_subscription_set_t* set, + const pubnub_subscription_t* sub, + const bool added) +{ + PUBNUB_ASSERT_OPT(NULL != ee); + + pubnub_mutex_lock(ee->mutw); + enum pubnub_res rslt; + + if (added) { rslt = pbcc_subscribe_ee_subscribe_(ee, NULL, true, false); } + else { + const pubnub_subscription_options_t options = + *(pubnub_subscription_options_t*)set; + pbhash_set_t* subs = pubnub_subscription_subscribables_(sub, &options); + + if (NULL == subs) { + PUBNUB_LOG_ERROR( + "pbcc_subscribe_ee_change_subscription_with_subscription_set: " + "failed to allocate memory for subscribables\n"); + pubnub_mutex_unlock(ee->mutw); + return PNR_OUT_OF_MEMORY; + } + + rslt = pbcc_subscribe_ee_unsubscribe_(ee, subs); + /** + * Subscribables list not needed anymore because channels / groups list + * already composed for presence leave REST API in + * `pbcc_subscribe_ee_unsubscribe_`. + */ + pbhash_set_free_with_destructor( + &subs, + (pbhash_set_element_free)pubnub_subscribable_free_); + } + pubnub_mutex_unlock(ee->mutw); + + return rslt; +} + +enum pubnub_res pbcc_subscribe_ee_disconnect(pbcc_subscribe_ee_t* ee) +{ + PUBNUB_ASSERT_OPT(NULL != ee); + + pubnub_mutex_lock(ee->mutw); + pbcc_ee_event_t* event = pbcc_disconnect_event_alloc(ee); + if (NULL == event) { + PUBNUB_LOG_ERROR("pbcc_subscribe_ee_disconnect: failed to allocate " + "memory for event\n"); + pubnub_mutex_unlock(ee->mutw); + return PNR_OUT_OF_MEMORY; + } + pubnub_mutex_unlock(ee->mutw); + + return pbcc_ee_handle_event(ee->ee, event); +} + +enum pubnub_res pbcc_subscribe_ee_reconnect( + pbcc_subscribe_ee_t* ee, + const pubnub_subscribe_cursor_t cursor) +{ + PUBNUB_ASSERT_OPT(NULL != ee); + + pubnub_mutex_lock(ee->mutw); + pbcc_ee_event_t* event = pbcc_reconnect_event_alloc(ee, cursor); + if (NULL == event) { + PUBNUB_LOG_ERROR("pbcc_subscribe_ee_reconnect: failed to allocate " + "memory for event\n"); + pubnub_mutex_unlock(ee->mutw); + return PNR_OUT_OF_MEMORY; + } + pubnub_mutex_unlock(ee->mutw); + + return pbcc_ee_handle_event(ee->ee, event); +} + +enum pubnub_res pbcc_subscribe_ee_unsubscribe_all(pbcc_subscribe_ee_t* ee) +{ + PUBNUB_ASSERT_OPT(NULL != ee); + + pubnub_mutex_lock(ee->mutw); + char* ch,* cg; + enum pubnub_res rslt = pbcc_subscribe_ee_subscribables_(ee->subscribables, + &ch, + &cg, + false); + + if (PNR_OK == rslt) { + pbcc_ee_event_t* event = + pbcc_unsubscribe_all_event_alloc(ee, &ch, &cg); + if (NULL == event) { + PUBNUB_LOG_ERROR("pbcc_subscribe_ee_reconnect: failed to allocate " + "memory for event\n"); + rslt = PNR_OUT_OF_MEMORY; + + if (NULL != ch) { free(ch); } + if (NULL != cg) { free(cg); } + } + else { + pbarray_remove_all(ee->subscription_sets); + pbarray_remove_all(ee->subscriptions); + + /** + * Update user presence information for channels which user actually + * left. + */ + if ((NULL != ch && 0 != strlen(ch)) || + (NULL != cg && 0 != strlen(cg))) { + pubnub_mutex_lock(ee->pb->monitor); + if (!pbnc_can_start_transaction(ee->pb)) { + pubnub_mutex_unlock(ee->pb->monitor); + /** Using array to handle consequencive call to unsubscribe. */ + pubnub_mutex_lock(ee->mutw); + if (NULL != ch && strlen(ch) > 0) + pbarray_add(ee->leave_channels, ch); + if (NULL != cg && strlen(cg) > 0) + pbarray_add(ee->leave_channel_groups, cg); + pubnub_mutex_unlock(ee->mutw); + } + else { + pubnub_mutex_unlock(ee->pb->monitor); + + pubnub_mutex_lock(ee->mutw); + ee->current_transaction = PBTT_LEAVE; + pubnub_mutex_unlock(ee->mutw); + + pubnub_leave(ee->pb, + 0 == strlen(ch) ? NULL : ch, + 0 == strlen(cg) ? NULL : cg); + } + } + + rslt = pbcc_ee_handle_event(ee->ee, event); + } + } + pubnub_mutex_unlock(ee->mutw); + + /** Looks like there is nothing to unsubscribe from. */ + if (PNR_INVALID_PARAMETERS == rslt) { rslt = PNR_OK; } + + return rslt; +} + +void pbcc_subscribe_ee_handle_subscribe_error( + pbcc_subscribe_ee_t* ee, + const enum pubnub_res error) +{ + pbcc_ee_event_t* event = NULL; + pbcc_ee_state_t* state_object = pbcc_ee_current_state(ee->ee); + + if (SUBSCRIBE_EE_STATE_HANDSHAKING == pbcc_ee_state_type(state_object)) + event = pbcc_handshake_failure_event_alloc(ee, error); + else if (SUBSCRIBE_EE_STATE_RECEIVING == pbcc_ee_state_type(state_object)) + event = pbcc_receive_failure_event_alloc(ee, error); + + if (NULL != event) { pbcc_ee_handle_event(ee->ee, event); } + if (NULL != state_object) { pbcc_ee_state_free(&state_object); } +} + +pubnub_subscription_t** pbcc_subscribe_ee_subscriptions( + pbcc_subscribe_ee_t* ee, + size_t* count) +{ + PUBNUB_ASSERT_OPT(NULL != ee); + + pubnub_mutex_lock(ee->mutw); + pubnub_subscription_t** subs = (pubnub_subscription_t**) + pbarray_elements(ee->subscriptions, count); + pubnub_mutex_unlock(ee->mutw); + + return subs; +} + +pubnub_subscription_set_t** pbcc_subscribe_ee_subscription_sets( + pbcc_subscribe_ee_t* ee, + size_t* count) +{ + PUBNUB_ASSERT_OPT(NULL != ee); + + pubnub_mutex_lock(ee->mutw); + pubnub_subscription_set_t** subs = (pubnub_subscription_set_t**) + pbarray_elements(ee->subscription_sets, count); + pubnub_mutex_unlock(ee->mutw); + + return subs; +} + +void pbcc_subscribe_callback_( + pubnub_t* pb, + const enum pubnub_trans trans, + const enum pubnub_res result, + const void* user_data + ) +{ + pbcc_subscribe_ee_t* ee = (pbcc_subscribe_ee_t*)user_data; + + /** + * Checking whether a leave request should be performed if a previous + * (potentially subscribe) request has been cancelled or was another leave + * request. + */ + if (PNR_CANCELLED == result || PBTT_HEARTBEAT == trans || + PBTT_LEAVE == trans) { + pubnub_mutex_lock(ee->mutw); + if (NULL != ee->cancel_invocation) { + pbcc_ee_handle_effect_completion(ee->ee, ee->cancel_invocation); + ee->cancel_invocation = NULL; + } + if (PBTT_HEARTBEAT == trans || PBTT_LEAVE == trans) { + ee->current_transaction = PBTT_NONE; + /** Flush read buffer. */ + pubnub_get(ee->pb); + } + pubnub_mutex_unlock(ee->mutw); + + if (pbcc_subscribe_ee_postponed_unsubscribe_(ee)) { return; } + + /** + * Leave or heartbeat request successfully processed. Trying schedule + * any pending effect invocations. + */ + if (0 != pbcc_ee_process_next_invocation(ee->ee)) { return; } + } + + const bool error = PNR_OK != result; + pbcc_ee_state_t* state_object = pbcc_ee_current_state(ee->ee); + pbcc_ee_event_t* event = NULL; + pubnub_subscribe_cursor_t cursor; + cursor.region = 0; + + if (!error) { + /** Retrieve parsed timetoken stored for next subscription loop. */ + pubnub_mutex_lock(pb->monitor); + size_t token_len = strlen(pb->core.timetoken); + memcpy(cursor.timetoken, pb->core.timetoken, token_len); + cursor.timetoken[token_len] = '\0'; + if (pb->core.region > 0) { cursor.region = pb->core.region; } + pbpal_mutex_unlock(pb->monitor); + } + + if (SUBSCRIBE_EE_STATE_HANDSHAKING == pbcc_ee_state_type(state_object)) { + if (!error) { event = pbcc_handshake_success_event_alloc(ee, cursor); } + else { event = pbcc_handshake_failure_event_alloc(ee, result); } + } + else if (SUBSCRIBE_EE_STATE_RECEIVING == pbcc_ee_state_type(state_object)) { + if (!error) { event = pbcc_receive_success_event_alloc(ee, cursor); } + else { event = pbcc_receive_failure_event_alloc(ee, result); } + } + + if (NULL != event) { pbcc_ee_handle_event(ee->ee, event); } + if (NULL != state_object) { pbcc_ee_state_free(&state_object); } +} + +enum pubnub_res pbcc_subscribe_ee_subscribe_( + pbcc_subscribe_ee_t* ee, + const pubnub_subscribe_cursor_t* cursor, + const bool update, + const bool sent_by_ee) +{ + pbcc_ee_event_t* event = NULL; + char* ch = NULL,* cg = NULL; + enum pubnub_res rslt = PNR_OK; + + if (update) { rslt = pbcc_subscribe_ee_update_subscribables_(ee); } + if (PNR_OK == rslt) { + rslt = pbcc_subscribe_ee_subscribables_(ee->subscribables, + &ch, + &cg, + true); + + /** + * Empty list allowed and will mean that event engine should transit to + * the `Unsubscribed` state. + */ + if (PNR_INVALID_PARAMETERS == rslt) { + if (NULL != ch) { free(ch); } + if (NULL != cg) { free(cg); } + ch = NULL; + cg = NULL; + rslt = PNR_OK; + } + } + + if (PNR_OK == rslt) { + const bool restore = NULL != cursor && '0' != cursor->timetoken[0]; + + if (NULL == cursor || !restore) + event = pbcc_subscription_changed_event_alloc( + ee, + &ch, + &cg, + sent_by_ee); + else + event = pbcc_subscription_restored_event_alloc( + ee, + &ch, + &cg, + *cursor, + sent_by_ee); + if (NULL == event) { + PUBNUB_LOG_ERROR( + "pbcc_subscribe_ee_subscribe: failed to allocate memory for " + "event\n"); + if (NULL != ch) { free(ch); } + if (NULL != cg) { free(cg); } + rslt = PNR_OUT_OF_MEMORY; + } + } + + // TODO: We probably will need presence event engine implementation. + /** + * `pubnub_heartbeat` not used separately because `pubnub_subscribe_v2` + * internally use automated heartbeat feature. + */ + + return PNR_OK == rslt ? pbcc_ee_handle_event(ee->ee, event) : rslt; +} + +enum pubnub_res pbcc_subscribe_ee_unsubscribe_( + pbcc_subscribe_ee_t* ee, + pbhash_set_t* subscribables) +{ + char* ch = NULL,* cg = NULL; + bool send_leave = false; + + size_t count = 0; + pubnub_subscribable_t** subs = (pubnub_subscribable_t**) + pbhash_set_elements(subscribables, &count); + if (NULL == subs) { return PNR_OUT_OF_MEMORY; } + + /** + * After subscription or subscription set removal we need to update + * subscribables list. + */ + enum pubnub_res rslt = pbcc_subscribe_ee_update_subscribables_(ee); + if (PNR_OK != rslt) { + free(subs); + return rslt; + } + + /** Removing subscribable which is not part of subscription loop. */ + for (size_t i = 0; i < count; ++i) { + pubnub_subscribable_t* sub = subs[i]; + + if (pbhash_set_contains(ee->subscribables, sub->id->ptr)) { + pbhash_set_remove(subscribables, + (void**)&sub->id->ptr, + (void**)&sub); + pubnub_subscribable_free_(sub); + } + } + free(subs); + + rslt = pbcc_subscribe_ee_subscribables_(subscribables, &ch, &cg, false); + if (PNR_OK == rslt || PNR_INVALID_PARAMETERS == rslt) { + send_leave = PNR_OK == rslt; + rslt = PNR_OK; + } + + /** + * Update user presence information for channels which user actually + * left. + */ + bool sending_leave = false; + if (PNR_OK == rslt && send_leave) { + pubnub_mutex_lock(ee->pb->monitor); + if (!pbnc_can_start_transaction(ee->pb)) { + pubnub_mutex_unlock(ee->pb->monitor); + /** Using array to handle consequencive call to unsubscribe. */ + pubnub_mutex_lock(ee->mutw); + if (NULL != ch && strlen(ch) > 0) + pbarray_add(ee->leave_channels, ch); + if (NULL != cg && strlen(cg) > 0) + pbarray_add(ee->leave_channel_groups, cg); + pubnub_mutex_unlock(ee->mutw); + } + else { + pubnub_mutex_unlock(ee->pb->monitor); + + pubnub_mutex_lock(ee->mutw); + ee->current_transaction = PBTT_LEAVE; + pubnub_mutex_unlock(ee->mutw); + sending_leave = true; + + pubnub_leave(ee->pb, + 0 == strlen(ch) ? NULL : ch, + 0 == strlen(cg) ? NULL : cg); + } + } + + if (PNR_OK != rslt || !send_leave || sending_leave) { + if (NULL != ch) { free(ch); } + if (NULL != cg) { free(cg); } + } + + /** Update subscription loop. */ + if (PNR_OK == rslt) + rslt = pbcc_subscribe_ee_subscribe_(ee, NULL, false, true); + + return rslt; +} + +bool pbcc_subscribe_ee_postponed_unsubscribe_(pbcc_subscribe_ee_t* ee) +{ + pubnub_mutex_lock(ee->mutw); + if (0 == pbarray_count(ee->leave_channels) && + 0 == pbarray_count(ee->leave_channel_groups)) { + pubnub_mutex_unlock(ee->mutw); + return false; + } + + /** + * Checking whether there is another ongoing request and we need to wait + * till its completion to start another one. + */ + if (PBTT_NONE != ee->current_transaction) { + pubnub_mutex_unlock(ee->mutw); + return true; + } + + char* ch = + pbcc_subscribe_ee_joined_array_elements_(ee->leave_channels, ","); + char* cg = + pbcc_subscribe_ee_joined_array_elements_(ee->leave_channel_groups, ","); + + /** Cleaning up postponed channels and groups. */ + pbarray_remove_all(ee->leave_channels); + pbarray_remove_all(ee->leave_channel_groups); + + if (NULL == ch && NULL == cg) { + pubnub_mutex_unlock(ee->mutw); + return false; + } + + ee->current_transaction = PBTT_LEAVE; + pubnub_mutex_unlock(ee->mutw); + + pubnub_leave(ee->pb, + NULL == ch || 0 == strlen(ch) ? NULL : ch, + NULL == cg || 0 == strlen(cg) ? NULL : cg); + if (NULL != ch) { free(ch); } + if (NULL != cg) { free(cg); } + + return true; +} + +enum pubnub_res pbcc_subscribe_ee_update_subscribables_( + const pbcc_subscribe_ee_t* ee) +{ + PUBNUB_ASSERT_OPT(NULL != ee); + pbarray_t* sets = ee->subscription_sets; + pbarray_t* subs = ee->subscriptions; + enum pubnub_res rslt = PNR_OK; + + /** Clean up previous set to start with fresh list. */ + pbhash_set_remove_all(ee->subscribables); + + for (int i = 0; i < pbarray_count(subs); ++i) { + const pubnub_subscription_t* sub = pbarray_element_at(subs, i); + pbhash_set_t* subc = pubnub_subscription_subscribables_(sub, NULL); + rslt = pbcc_subscribe_ee_add_subscribables_(ee, subc); + pbhash_set_free(&subc); + if (PNR_OK != rslt) { return rslt; } + } + + for (int i = 0; i < pbarray_count(sets); ++i) { + const pubnub_subscription_set_t* sub = pbarray_element_at(sets, i); + pbhash_set_t* subc = pubnub_subscription_set_subscribables_(sub); + rslt = pbcc_subscribe_ee_add_subscribables_(ee, subc); + pbhash_set_free(&subc); + if (PNR_OK != rslt) { return rslt; } + } + + return rslt; +} + +enum pubnub_res pbcc_subscribe_ee_add_subscribables_( + const pbcc_subscribe_ee_t* ee, + pbhash_set_t* subscribables) +{ + PUBNUB_ASSERT_OPT(NULL != ee); + PUBNUB_ASSERT_OPT(NULL != subscribables); + + enum pubnub_res rslt = PNR_OK; + pbhash_set_t* dups = NULL; + + if (NULL == subscribables || + PBHSR_OUT_OF_MEMORY == pbhash_set_union( + ee->subscribables, + subscribables, + &dups)) { + if (NULL == subscribables) { + PUBNUB_LOG_ERROR( + "pbcc_subscribe_ee_add_subscribables: failed to allocate " + "memory for subscription's subscribables\n"); + } + else { + PUBNUB_LOG_ERROR( + "pbcc_subscribe_ee_add_subscribables: failed to allocate " + "memory to store subscribables\n"); + } + + rslt = PNR_OUT_OF_MEMORY; + } + + if (NULL == dups) { return rslt; } + + /** Make sure that duplicates will be freed. */ + size_t dups_count = 0; + pubnub_subscribable_t** elms = (pubnub_subscribable_t**) + pbhash_set_elements(dups, &dups_count); + for (size_t j = 0; j < dups_count; ++j) { + pubnub_subscribable_t* elm = elms[j]; + + if (PBHSR_VALUE_EXISTS == pbhash_set_match_element( + ee->subscribables, + elm)) { + pubnub_subscribable_free_(elm); + } + } + if (NULL != elms) { free(elms); } + pbhash_set_free(&dups); + + return rslt; +} + +enum pubnub_res pbcc_subscribe_ee_subscribables_( + pbhash_set_t* subscribables, + char** channels, + char** channel_groups, + const bool include_presence) +{ + PUBNUB_ASSERT_OPT(NULL != subscribables); + + /** Expected length of comma-separated channel names. */ + size_t ch_list_length = 0, ch_count = 0, ch_offset = 0; + /** Expected length of comma-separated channel group names. */ + size_t cg_list_length = 0, cg_count = 0, cg_offset = 0; + + size_t count; + const pubnub_subscribable_t** subs = (const pubnub_subscribable_t**) + pbhash_set_elements(subscribables, &count); + + for (int i = 0; i < count; ++i) { + const pubnub_subscribable_t* sub = subs[i]; + /** Ignoring presence subscribables if they shouldn't be included. */ + if (!include_presence && pubnub_subscribable_is_presence_(sub)) + continue; + + if (!pubnub_subscribable_is_cg_(sub)) { + ch_list_length += pubnub_subscribable_length_(sub); + ch_count++; + } + else { + cg_list_length += pubnub_subscribable_length_(sub); + cg_count++; + } + } + + /** Extra byte for null-terminator. */ + const size_t ch_len = ch_list_length + ch_count + 1; + const size_t cg_len = cg_list_length + cg_count + 1; + *channels = calloc(ch_len, sizeof(char)); + *channel_groups = calloc(cg_len, sizeof(char)); + + if (NULL == *channels || NULL == *channel_groups) { + PUBNUB_LOG_ERROR( + "pbcc_subscribe_ee_subscribables: failed to allocate memory to " + "store subscription channels / channel groups\n"); + if (NULL != *channel_groups) { + free(*channel_groups); + *channel_groups = NULL; + } + if (NULL != *channels) { + free(*channels); + *channels = NULL; + } + if (NULL != subs) { free(subs); } + return PNR_OUT_OF_MEMORY; + } + + if (0 == cg_count) { *channel_groups[0] = '\0'; } + if (0 == ch_count) { *channels[0] = '\0'; } + + for (int i = 0; i < count; ++i) { + const pubnub_subscribable_t* sub = subs[i]; + /** Ignoring presence subscribables if they shouldn't be included. */ + if (!include_presence && pubnub_subscribable_is_presence_(sub)) + continue; + + if (!pubnub_subscribable_is_cg_(sub)) { + ch_offset += snprintf(*channels + ch_offset, + ch_len - ch_offset, + "%s%s", + ch_offset > 0 ? ",": "", + sub->id->ptr); + } + else { + cg_offset += snprintf(*channel_groups + cg_offset, + cg_len - cg_offset, + "%s%s", + cg_offset > 0 ? ",": "", + sub->id->ptr); + } + } + if (NULL != subs) { free(subs); } + + return ch_list_length == 0 && cg_list_length == 0 + ? PNR_INVALID_PARAMETERS + : PNR_OK; +} + +char* pbcc_subscribe_ee_joined_array_elements_( + pbarray_t* array, + const char* separator) +{ + size_t offset = 0; + size_t count; + char** elements = (char**)pbarray_elements(array, &count); + if (NULL == elements) { return NULL; } + + /** Extra byte for null-terminator. */ + size_t len = (count > 0 ? (count - 1) * strlen(separator) : 0) + 1; + for (size_t i = 0; i < count; ++i) { len += strlen(elements[i]); } + + char* joined_str = malloc(len * sizeof(char)); + if (NULL == joined_str || 0 == count) { + if (NULL != joined_str) { joined_str[0] = '\0'; } + free(elements); + return joined_str; + } + + for (int i = 0; i < count; ++i) { + offset += snprintf(joined_str + offset, + len - offset, + "%s%s", + offset > 0 ? separator : "", + elements[i]); + } + + return joined_str; +} \ No newline at end of file diff --git a/core/pbcc_subscribe_event_engine.h b/core/pbcc_subscribe_event_engine.h new file mode 100644 index 00000000..992ba915 --- /dev/null +++ b/core/pbcc_subscribe_event_engine.h @@ -0,0 +1,289 @@ +/* -*- c-file-style:"stroustrup"; indent-tabs-mode: nil -*- */ +#ifndef PBCC_SUBSCRIBE_EVENT_ENGINE_H +#define PBCC_SUBSCRIBE_EVENT_ENGINE_H +#if PUBNUB_USE_SUBSCRIBE_EVENT_ENGINE +#if !PUBNUB_USE_SUBSCRIBE_V2 +#error Subscribe event engine requires subscribe v2 API, so you must define PUBNUB_USE_SUBSCRIBE_V2=1 +#endif // #if !PUBNUB_USE_SUBSCRIBE_V2 +#ifndef PUBNUB_CALLBACK_API +#error Subscribe event engine requires callback based PubNub context, so you must define PUBNUB_CALLBACK_API +#endif // #ifndef PUBNUB_CALLBACK_API + + +/** + * @file pbcc_subscribe_event_engine.h + * @brief Event engine implementation for subscription loop. + */ + +#include "core/pbcc_subscribe_event_engine_types.h" +#include "core/pbcc_subscribe_event_listener.h" +#include "core/pubnub_subscribe_event_engine.h" + + +// ---------------------------------------------- +// Types forwarding +// ---------------------------------------------- + +/** Subscribe event engine structure. */ +typedef struct pbcc_subscribe_ee pbcc_subscribe_ee_t; + + +// ---------------------------------------------- +// Functions +// ---------------------------------------------- + +/** + * @brief Create Subscribe Event Engine. + * + * @param pb PubNub context, which should be used for effects execution by + * Subscribe Event Engine effects dispatcher. + * @return Pointer to the ready to use Subscribe Event Engine or `NULL` in case + * of out-of-memory error. The returned pointer must be passed to the + * `pbcc_subscribe_ee_free` to avoid a memory leak. + */ +pbcc_subscribe_ee_t* pbcc_subscribe_ee_alloc(pubnub_t* pb); + +/** + * @brief Dispose and free up resources used by Subscribe Event Engine. + * + * @note Function will NULLify provided subscribe event engine pointer. + * + * @param ee Pointer to the Subscribe Event Engine which should be disposed. + */ +void pbcc_subscribe_ee_free(pbcc_subscribe_ee_t** ee); + +/** + * @brief Get Subscribe Event Listener. + * + * @param ee Pointer to the Subscribe Event Engine, which should provide + * listener. + * @return Pointer to the ready to use Event Listener. + */ +pbcc_event_listener_t* pbcc_subscribe_ee_event_listener( + const pbcc_subscribe_ee_t* ee); + +/** + * @brief Get current state context. + * + * @param ee Pointer to the Subscribe Event Engine, which should provide current + * state object. + * @return Current Subscribe Event Engine context data. + */ +pbcc_ee_data_t* pbcc_subscribe_ee_current_state_context( + const pbcc_subscribe_ee_t* ee); + +/** + * @brief Update real-time updates filter expression. + * + * @param ee Pointer to the Subscribe Event Engine, which will use the + * provided expression with the next subscription loop call. + * @param filter_expr Pointer to the real-time update filter expression. + */ +void pbcc_subscribe_ee_set_filter_expression( + pbcc_subscribe_ee_t* ee, + const char* filter_expr); + +/** + * @brief Update client presence heartbeat / timeout. + * + * @param ee Pointer to the Subscribe Event Engine, which will use the + * provided heartbeat with the next subscription loop call. + * @param heartbeat How long (in seconds) the server will consider the client + * alive for presence. + */ +void pbcc_subscribe_ee_set_heartbeat( + pbcc_subscribe_ee_t* ee, + unsigned heartbeat); + +/** + * @brief Transit to `receiving` state. + * + * Depending on from the flow, Subscribe Event Engine will transit to the + * `receiving` state directly or through the `handshaking` state (if required). + * + * \b Warning: A single PubNub context can't have multiple subscription options + * and will override the active we last used. + * + * @param ee Pointer to the Subscribe Event Engine, which should transit to + * `receiving` state. + * @param sub Pointer to the subscription, which should be used to update + * context data. + * @param cursor Pointer to the subscription cursor to be used with subscribe + * REST API. The SDK will try to catch up on missed messages if a + * cursor with older PubNub high-precision timetoken has been + * provided. Pass `NULL` to keep using cursor received from the + * previous subscribe REST API response. + * @return Result of `subscribe` enqueue transaction. + */ +enum pubnub_res pbcc_subscribe_ee_subscribe_with_subscription( + pbcc_subscribe_ee_t* ee, + pubnub_subscription_t* sub, + const pubnub_subscribe_cursor_t* cursor); + +/** + * @brief Transit to `unsubscribed` / `receiving` state. + * + * Depending on from data stored in the Subscription Event Engine, transition + * may happen to the `receiving` or `unsubscribed`. + * + * @param ee Pointer to the Subscribe Event Engine, which should transit to the + * state suitable for the current context state. + * @param sub Pointer to the subscription, which should be used to update context + * data. + * @return Result of `unsubscribe` enqueue transaction. + */ +enum pubnub_res pbcc_subscribe_ee_unsubscribe_with_subscription( + pbcc_subscribe_ee_t* ee, + const pubnub_subscription_t* sub); + +/** + * @brief Transit to `receiving` state. + * + * Depending on from the flow, Subscribe Event Engine will transit to the + * `receiving` state directly or through the `handshaking` state (if required). + * + * \b Warning: A single PubNub context can't have multiple subscription options + * and will override the active we last used. + * + * @param ee Pointer to the Subscribe Event Engine, which should transit to + * `receiving` state. + * @param set Pointer to the subscription set, which should be used to update + * context data. + * @param cursor Pointer to the subscription cursor to be used with subscribe + * REST API. The SDK will try to catch up on missed messages if a + * cursor with older PubNub high-precision timetoken has been + * provided. Pass `NULL` to keep using cursor received from the + * previous subscribe REST API response. + * @return Result of `subscribe` enqueue transaction. + */ +enum pubnub_res pbcc_subscribe_ee_subscribe_with_subscription_set( + pbcc_subscribe_ee_t* ee, + pubnub_subscription_set_t* set, + const pubnub_subscribe_cursor_t* cursor); + +/** + * @brief Transit to `unsubscribed` / `receiving` state. + * + * Depending on from data stored in the Subscription Event Engine, transition + * may happen to the `receiving` or `unsubscribed`. + * + * @param ee Pointer to the Subscribe Event Engine, which should transit to the + * state suitable for the current context state. + * @param set Pointer to the subscription set, which should be used to update + * context data. + * @return Result of `unsubscribe` enqueue transaction. + */ +enum pubnub_res pbcc_subscribe_ee_unsubscribe_with_subscription_set( + pbcc_subscribe_ee_t* ee, + pubnub_subscription_set_t* set); + +/** + * @brief Subscription set modification handler. + * + * Handle changes to subscribed set subscriptions list. + * + * @param ee Pointer to the Subscribe Event Engine, which should transit to + * the state suitable for the current context state. + * @param set Pointer to the subscription set, which has been updated. + * @param sub Pointer to the subscription which has been added or removed. + * @param added Whether new subscription has been added to the `set` or not. + * @return Result of `subscription change` enqueue transaction. + */ +enum pubnub_res pbcc_subscribe_ee_change_subscription_with_subscription_set( + pbcc_subscribe_ee_t* ee, + const pubnub_subscription_set_t* set, + const pubnub_subscription_t* sub, + bool added); + +/** + * @brief Transit to `disconnected` state. + * + * Make transition from current event engine state to the `disconnected` state. + * + * \b Warning: Application execution will be terminated if Subscribe Event Engine + * pointer is an invalid. + * + * @param ee Pointer to the Subscribe Event Engine, which should transit to + * `disconnected` state. + * @return Result of `disconnect` enqueue transaction. + */ +enum pubnub_res pbcc_subscribe_ee_disconnect( + pbcc_subscribe_ee_t* ee); + +/** + * @brief Transit to `receiving` state. + * + * Make transition from the current event engine state to the `receiving` state. + * + * \b Warning: Application execution will be terminated if Subscribe Event Engine + * pointer is an invalid. + * + * @param ee Pointer to the Subscribe Event Engine, which should transit to + * `receiving` state. + * @param cursor Custom catch up time cursor to start receiving real-time + * updates from specific time. + * @return Result of `reconnect` enqueue transaction. + */ +enum pubnub_res pbcc_subscribe_ee_reconnect( + pbcc_subscribe_ee_t* ee, + pubnub_subscribe_cursor_t cursor); + +/** + * @brief Transit to `unsubscribed` state. + * + * @param ee Pointer to the Subscribe Event Engine, which should transit to + * `unsubscribed` state. + * @return Result of `unsubscribe all` enqueue transaction. + */ +enum pubnub_res pbcc_subscribe_ee_unsubscribe_all(pbcc_subscribe_ee_t* ee); + +/** + * @brief Handle Subscribe v2 request schedule error. + * + * @param ee Pointer to the Subscribe Event Engine, which should handle next + * subscription schedule error. + * @param error Subscribe operation call result (error). + */ +void pbcc_subscribe_ee_handle_subscribe_error( + pbcc_subscribe_ee_t* ee, + enum pubnub_res error); + +/** + * @brief Retrieve created subscriptions. + * + * \b Warning: Application execution will be terminated if Subscribe Event Engine + * pointer is an invalid. + * + * @param ee Pointer to the Subscribe Event Engine from which list of + * subscriptions should be retrieved. + * @param [out] count Parameter will hold the count of returned subscriptions. + * @return Pointer to the array with subscription object pointers, or `NULL` in + * case of error (insufficient memory or PubNub pointer is an invalid + * context pointer). The returned pointer must be passed to the `free` to + * avoid a memory leak. + */ +pubnub_subscription_t** pbcc_subscribe_ee_subscriptions( + pbcc_subscribe_ee_t* ee, + size_t* count); + +/** + * @brief Retrieve created subscription sets. + * + * \b Warning: Application execution will be terminated if Subscribe Event Engine + * pointer is an invalid. + * + * @param ee Pointer to the Subscribe Event Engine from which list of + * subscription sets should be retrieved. + * @param [out] count Parameter will hold the count of returned subscription + * sets. + * @return Pointer to the array with subscription set object pointers, or `NULL` + * will be returned in case of insufficient memory error. The returned pointer + * must be passed to the `free` to avoid a memory leak. + */ +pubnub_subscription_set_t** pbcc_subscribe_ee_subscription_sets( + pbcc_subscribe_ee_t* ee, + size_t* count); +#else // #if PUBNUB_USE_SUBSCRIBE_EVENT_ENGINE +#error To use subscribe event engine API you must define PUBNUB_USE_SUBSCRIBE_EVENT_ENGINE=1 +#endif // #if PUBNUB_USE_SUBSCRIBE_EVENT_ENGINE +#endif // #ifndef PBCC_SUBSCRIBE_EVENT_ENGINE_H diff --git a/core/pbcc_subscribe_event_engine_effects.c b/core/pbcc_subscribe_event_engine_effects.c new file mode 100644 index 00000000..732c5f63 --- /dev/null +++ b/core/pbcc_subscribe_event_engine_effects.c @@ -0,0 +1,225 @@ +/* -*- c-file-style:"stroustrup"; indent-tabs-mode: nil -*- */ +#include "pbcc_subscribe_event_engine_effects.h" + +#include + +#include "core/pbcc_subscribe_event_engine_types.h" +#include "core/pubnub_subscribe_v2.h" +#include "core/pubnub_pubsubapi.h" +#include "core/pubnub_coreapi.h" +#include "core/pubnub_assert.h" +#include "pubnub_internal.h" + + +// ---------------------------------------------- +// Constants +// ---------------------------------------------- + +/** Maximum channel name length */ +#define PBCC_SUBSCRIBE_EE_CHANNEL_MAXIMUM_LENGTH 2100 + + +// ---------------------------------------------- +// Function prototypes +// ---------------------------------------------- + +/** + * @brief Make actual call to the Subscribe v2. + * + * @param invocation Pointer to the invocation which has been used to execute + * effect. + * @param context Pointer to the Subscription context with all required + * information to perform Subscribe v2 call. + * @param cb Pointer to the effect execution completion callback + * function. + */ +static void make_subscribe_request_( + pbcc_ee_invocation_t* invocation, + pbcc_ee_data_t* context, + pbcc_ee_effect_completion_function_t cb); + + +// ---------------------------------------------- +// Functions +// ---------------------------------------------- + +void pbcc_subscribe_ee_handshake_effect( + pbcc_ee_invocation_t* invocation, + pbcc_ee_data_t* context, + const pbcc_ee_effect_completion_function_t cb) +{ + make_subscribe_request_(invocation, context, cb); +} + +void pbcc_subscribe_ee_receive_effect( + pbcc_ee_invocation_t* invocation, + pbcc_ee_data_t* context, + const pbcc_ee_effect_completion_function_t cb) +{ + make_subscribe_request_(invocation, context, cb); +} + +void pbcc_subscribe_ee_emit_status_effect( + pbcc_ee_invocation_t* invocation, + pbcc_ee_data_t* context, + const pbcc_ee_effect_completion_function_t cb) +{ + pbcc_ee_data_t* context_copy = pbcc_ee_data_copy(context); + const pbcc_subscribe_ee_context_t* ctx = pbcc_ee_data_value(context_copy); + pbcc_subscribe_ee_t* subscribe_ee = ctx->pb->core.subscribe_ee; + + pubnub_mutex_lock(subscribe_ee->mutw); + const pubnub_subscription_status status = subscribe_ee->status; + pubnub_mutex_unlock(subscribe_ee->mutw); + + pbcc_event_listener_emit_status(subscribe_ee->event_listener, + status, + ctx->reason, + pbcc_ee_data_value(ctx->channels), + pbcc_ee_data_value(ctx->channel_groups)); + cb(subscribe_ee->ee, invocation, false); + pbcc_ee_data_free(context_copy); +} + +void pbcc_subscribe_ee_emit_messages_effect( + pbcc_ee_invocation_t* invocation, + pbcc_ee_data_t* context, + const pbcc_ee_effect_completion_function_t cb) +{ + char subscribable_name[PBCC_SUBSCRIBE_EE_CHANNEL_MAXIMUM_LENGTH]; + pbcc_ee_data_t* context_copy = pbcc_ee_data_copy(context); + const pbcc_subscribe_ee_context_t* ctx = pbcc_ee_data_value(context_copy); + const pbcc_subscribe_ee_t* subscribe_ee = ctx->pb->core.subscribe_ee; + pubnub_t* pb = ctx->pb; + + for (struct pubnub_v2_message msg = pubnub_get_v2(pb); msg.payload.size > 0; + msg = pubnub_get_v2(pb)) { + struct pubnub_char_mem_block subscribable; + if (msg.match_or_group.size) { subscribable = msg.match_or_group; } + else {subscribable = msg.channel;} + memcpy(subscribable_name, subscribable.ptr, subscribable.size); + subscribable_name[subscribable.size] = '\0'; + pbcc_event_listener_emit_message(subscribe_ee->event_listener, subscribable_name, msg); + } + + cb(subscribe_ee->ee, invocation, false); + pbcc_ee_data_free(context_copy); +} + +void pbcc_subscribe_ee_cancel_effect( + pbcc_ee_invocation_t* invocation, + pbcc_ee_data_t* context, + const pbcc_ee_effect_completion_function_t cb) +{ + PUBNUB_ASSERT_OPT(NULL != context); + + pbcc_ee_data_t* context_copy = pbcc_ee_data_copy(context); + const pbcc_subscribe_ee_context_t* ctx = pbcc_ee_data_value(context_copy); + pbcc_subscribe_ee_t* subscribe_ee = ctx->pb->core.subscribe_ee; + PUBNUB_ASSERT(pb_valid_ctx_ptr(ctx->pb)); + + /** + * Check whether there is any request is in progress and don't let it to be + * cancelled. + */ + pubnub_mutex_lock(subscribe_ee->mutw); + const enum pubnub_trans transaction = subscribe_ee->current_transaction; + if (PBTT_SUBSCRIBE_V2 != transaction && PBTT_NONE != transaction) { + pubnub_mutex_unlock(subscribe_ee->mutw); + pbcc_ee_data_free(context_copy); + cb(subscribe_ee->ee, invocation, true); + + return; + } + pubnub_mutex_unlock(subscribe_ee->mutw); + + if (PN_CANCEL_FINISHED == pubnub_cancel(ctx->pb)) + cb(subscribe_ee->ee, invocation, false); + else + subscribe_ee->cancel_invocation = invocation; + pbcc_ee_data_free(context_copy); +} + +void make_subscribe_request_( + pbcc_ee_invocation_t* invocation, + pbcc_ee_data_t* context, + const pbcc_ee_effect_completion_function_t cb) +{ + PUBNUB_ASSERT_OPT(NULL != context); + + pbcc_ee_data_t* context_copy = pbcc_ee_data_copy(context); + pbcc_subscribe_ee_context_t* ctx = pbcc_ee_data_value(context_copy); + pbcc_subscribe_ee_t* subscribe_ee = ctx->pb->core.subscribe_ee; + pubnub_t* pb = ctx->pb; + + /** + * Check whether there any request is in progress and postpone subscribe + * effect execution untill it will be completed. + */ + pubnub_mutex_lock(subscribe_ee->mutw); + if (PBTT_NONE != subscribe_ee->current_transaction) { + pubnub_mutex_unlock(subscribe_ee->mutw); + cb(subscribe_ee->ee, invocation, true); + pbcc_ee_data_free(context_copy); + + return; + } + + if (ctx->send_heartbeat) { + pubnub_mutex_lock(pb->monitor); + if (!pbnc_can_start_transaction(pb)) { + pubnub_mutex_unlock(pb->monitor); + + /** Postpone invocation because there is ongoing request. */ + cb(subscribe_ee->ee, invocation, true); + pbcc_ee_data_free(context_copy); + + return; + } + + pubnub_mutex_unlock(pb->monitor); + ctx->send_heartbeat = false; + subscribe_ee->current_transaction = PBTT_HEARTBEAT; + pubnub_heartbeat(subscribe_ee->pb, + pbcc_ee_data_value(ctx->channels), + pbcc_ee_data_value(ctx->channel_groups)); + pubnub_mutex_unlock(subscribe_ee->mutw); + + /** Postpone invocation because there is ongoing heratbeat request. */ + cb(subscribe_ee->ee, invocation, true); + pbcc_ee_data_free(context_copy); + + return; + } + pubnub_mutex_unlock(subscribe_ee->mutw); + + /** Override timetoken stored for next subscription loop. */ + pubnub_mutex_lock(pb->monitor); + size_t token_len = strlen(ctx->cursor.timetoken); + memcpy(pb->core.timetoken, ctx->cursor.timetoken, token_len); + pb->core.timetoken[token_len] = '\0'; + pb->core.region = ctx->cursor.region; + pbpal_mutex_unlock(pb->monitor); + + struct pubnub_subscribe_v2_options opts = pubnub_subscribe_v2_defopts(); + opts.filter_expr = subscribe_ee->filter_expr; + opts.channel_group = pbcc_ee_data_value(ctx->channel_groups); + opts.heartbeat = subscribe_ee->heartbeat; + + const enum pubnub_res rslt = pubnub_subscribe_v2( + pb, + pbcc_ee_data_value(ctx->channels), + opts); + + /** + * Report effect invocation called or should be paused if not started. + * If other request in progress, after registered callback call it will + * trigger next invocation which will call paused once more. + */ + cb(subscribe_ee->ee, invocation, PNR_IN_PROGRESS == rslt); + + if (PNR_OK != rslt && PNR_STARTED != rslt && PNR_IN_PROGRESS != rslt) + pbcc_subscribe_ee_handle_subscribe_error(subscribe_ee, rslt); + + pbcc_ee_data_free(context_copy); +} \ No newline at end of file diff --git a/core/pbcc_subscribe_event_engine_effects.h b/core/pbcc_subscribe_event_engine_effects.h new file mode 100644 index 00000000..527cc0e9 --- /dev/null +++ b/core/pbcc_subscribe_event_engine_effects.h @@ -0,0 +1,107 @@ +/* -*- c-file-style:"stroustrup"; indent-tabs-mode: nil -*- */ +#ifndef PBCC_SUBSCRIBE_EVENT_ENGINE_EFFECTS_H +#define PBCC_SUBSCRIBE_EVENT_ENGINE_EFFECTS_H +#if PUBNUB_USE_SUBSCRIBE_EVENT_ENGINE +#if !PUBNUB_USE_SUBSCRIBE_V2 +#error Subscribe event engine requires subscribe v2 API, so you must define PUBNUB_USE_SUBSCRIBE_V2=1 +#endif // #if !PUBNUB_USE_SUBSCRIBE_V2 + + +/** + * @file pbcc_subscribe_event_engine_events.h + * @brief Subscribe Event Engine states transition events. +*/ + +#include "core/pbcc_event_engine.h" + + +// ---------------------------------------------- +// Functions +// ---------------------------------------------- + +/** + * @brief Execute (schedule) initial subscribe to the channels / groups. + * + * Effect scheduled because PubNub context uses a callback interface and status + * will be known only with callback call. + * + * @param invocation Pointer to the invocation which has been used to execute + * effect. + * @param context Pointer to the Subscription context object with information + * for initial subscribe (tt=0). + * @param cb Pointer to the effect execution completion callback + * function. + */ +void pbcc_subscribe_ee_handshake_effect( + pbcc_ee_invocation_t* invocation, + pbcc_ee_data_t* context, + pbcc_ee_effect_completion_function_t cb); + +/** + * @brief Execute (schedule) next subscription loop. + * + * Effect scheduled because PubNub context uses a callback interface and status + * will be known only with callback call. + * + * @param invocation Pointer to the invocation which has been used to execute + * effect. + * @param context Pointer to the Subscription context object with information + * for next subscription loop (tt!=0). + * @param cb Pointer to the effect execution completion callback + * function. + */ +void pbcc_subscribe_ee_receive_effect( + pbcc_ee_invocation_t* invocation, + pbcc_ee_data_t* context, + pbcc_ee_effect_completion_function_t cb); + +/** + * @brief Notify status change listeners. + * + * @param invocation Pointer to the invocation which has been used to execute + * effect. + * @param context Pointer to the Subscription context with information which + * is required by the Event Listener to notify all status + * change listeners. + * @param cb Pointer to the effect execution completion callback + * function. + */ +void pbcc_subscribe_ee_emit_status_effect( + pbcc_ee_invocation_t* invocation, + pbcc_ee_data_t* context, + pbcc_ee_effect_completion_function_t cb); + +/** + * @brief Notify real-time updates listeners. + * + * @param invocation Pointer to the invocation which has been used to execute + * effect. + * @param context Pointer to the Subscription context with information which + * is required by the Event Listener to notify all real-time + * update listeners. + * @param cb Pointer to the effect execution completion callback + * function. + */ +void pbcc_subscribe_ee_emit_messages_effect( + pbcc_ee_invocation_t* invocation, + pbcc_ee_data_t* context, + pbcc_ee_effect_completion_function_t cb); + +/** + * @brief Cancel previously started HTTP operation. + * + * @param invocation Pointer to the invocation which has been used to execute + * effect. + * @param context Pointer to the Subscription context with information which + * is required to cancel a previously started HTTP operation. + * @param cb Pointer to the effect execution completion callback + * function. + */ +void pbcc_subscribe_ee_cancel_effect( + pbcc_ee_invocation_t* invocation, + pbcc_ee_data_t* context, + pbcc_ee_effect_completion_function_t cb); +#else // #if PUBNUB_USE_SUBSCRIBE_EVENT_ENGINE +#error To use subscribe event engine API you must define PUBNUB_USE_SUBSCRIBE_EVENT_ENGINE=1 +#endif // #if PUBNUB_USE_SUBSCRIBE_EVENT_ENGINE +#endif //PBCC_SUBSCRIBE_EVENT_ENGINE_EFFECTS_H diff --git a/core/pbcc_subscribe_event_engine_events.c b/core/pbcc_subscribe_event_engine_events.c new file mode 100644 index 00000000..c4b38b03 --- /dev/null +++ b/core/pbcc_subscribe_event_engine_events.c @@ -0,0 +1,394 @@ +/* -*- c-file-style:"stroustrup"; indent-tabs-mode: nil -*- */ +#include "pbcc_subscribe_event_engine_events.h" + +#include + +#include "core/pubnub_log.h" + + +// ---------------------------------------------- +// Function prototypes +// ---------------------------------------------- + +/** + * @brief Create event for underlying Event Engine. + * + * @param ee Pointer to the Subscribe Event Engine for + * which will process the event. + * @param event Type of Subscribe Event Engine event. + * @param [in,out] channels Pointer to the byte sting of comma-separated + * channels from which PubNub client should + * receive real-time updates. + * @param [in,out] channel_groups Pointer to the byte string of comma-separated + * channel groups from which PubNub client should + * receive real-time updates. + * @param cursor Time token, which should be used with next + * subscription REST API loop call. + * @param reason Subscription processing result code which + * explain failure reason. + * @param sent_by_ee Whether event created per Event Engine request + * or not. + * @return Pointer to requested `event` type, which will be processed by the + * Subscribe Event Engine to get transition instructions from the + * current state. + */ +static pbcc_ee_event_t* pbcc_subscribe_ee_event_alloc_( + pbcc_subscribe_ee_t* ee, + pbcc_subscribe_ee_event event, + char** channels, + char** channel_groups, + const pubnub_subscribe_cursor_t* cursor, + const enum pubnub_res* reason, + bool sent_by_ee); + +/** + * @brief Create Subscribe Event Engine context object. + * + * @param pb PubNub context, which should be used for + * effects execution by Subscribe Event Engine + * effects dispatcher. + * @param [in,out] channels Pointer to the byte string with + * comma-separated of channels which should be + * passed between states in context object. + * @param [in,out] channel_groups Pointer to the byte string with + * comma-separated of channel groups which should + * be passed between states in context object. + * @return Pointer to the ready to use Subscribe Event Engine context object, or + * `NULL` in case of insufficient memory error. + * The returned pointer must be passed to the + * `pbcc_subscribe_ee_context_free_` to avoid a memory leak. + */ +pbcc_subscribe_ee_context_t* pbcc_subscribe_ee_context_alloc_( + pubnub_t* pb, + char** channels, + char** channel_groups); + +/** + * @brief Copy source context with `channels` and `channel_groups` override. +* + * @param pb PubNub context, which should be used for + * effects execution by Subscribe Event Engine + * effects dispatcher. + * @param ctx Pointer to the context, from which data will + * be copied to the next Subscribe Event Engine + * context. + * @param [in,out] channels Pointer to the byte string with + * comma-separated channels which should be used + * instead of `channels` from provided context. + * @param [in,out] channel_groups Pointer to the byte string with + * comma-separated channel groups which should be + * used instead of `channel_groups` from provided + * context. + * @param copy_subscribables Whether subscribables should be copied from + * the context or not. + * @return Pointer to the ready to use Subscribe Event Engine context created + * from the data of source context, or `NULL` in case of insufficient + * memory error. + * The returned pointer must be passed to the + * `pbcc_subscribe_ee_context_free_` to avoid a memory leak. + */ +pbcc_subscribe_ee_context_t* pbcc_subscribe_ee_context_copy_( + pubnub_t* pb, + const pbcc_subscribe_ee_context_t* ctx, + char** channels, + char** channel_groups, + bool copy_subscribables); + +/** + * @brief Clean up resources used by subscription context. + * + * @param ctx Pointer to the context, which should free up resources. + */ +void pbcc_subscribe_ee_context_free_(pbcc_subscribe_ee_context_t* ctx); + + +// ---------------------------------------------- +// Functions +// ---------------------------------------------- + +pbcc_ee_event_t* pbcc_subscription_changed_event_alloc( + pbcc_subscribe_ee_t* ee, + char** channels, + char** channel_groups, + const bool sent_by_ee) +{ + if (NULL != channels && NULL != *channels && 0 == strlen(*channels)) { + free(*channels); + *channels = NULL; + } + if (NULL != channel_groups && NULL != *channel_groups && + 0 == strlen(*channel_groups)) { + free(*channel_groups); + *channel_groups = NULL; + } + + const pbcc_subscribe_ee_event type = + SUBSCRIBE_EE_EVENT_SUBSCRIPTION_CHANGED; + return pbcc_subscribe_ee_event_alloc_(ee, + type, + channels, + channel_groups, + NULL, + NULL, + sent_by_ee); +} + +pbcc_ee_event_t* pbcc_subscription_restored_event_alloc( + pbcc_subscribe_ee_t* ee, + char** channels, + char** channel_groups, + const pubnub_subscribe_cursor_t cursor, + const bool sent_by_ee) +{ + if (NULL != channels && NULL != *channels && 0 == strlen(*channels)) { + free(*channels); + *channels = NULL; + } + if (NULL != channel_groups && NULL != *channel_groups && + 0 == strlen(*channel_groups)) { + free(*channel_groups); + *channel_groups = NULL; + } + + const pbcc_subscribe_ee_event type = + SUBSCRIBE_EE_EVENT_SUBSCRIPTION_RESTORED; + return pbcc_subscribe_ee_event_alloc_(ee, + type, + channels, + channel_groups, + &cursor, + NULL, + sent_by_ee); +} + +pbcc_ee_event_t* pbcc_handshake_success_event_alloc( + pbcc_subscribe_ee_t* ee, + const pubnub_subscribe_cursor_t cursor) +{ + const pbcc_subscribe_ee_event type = SUBSCRIBE_EE_EVENT_HANDSHAKE_SUCCESS; + return pbcc_subscribe_ee_event_alloc_(ee, + type, + NULL, + NULL, + &cursor, + NULL, + true); +} + +pbcc_ee_event_t* pbcc_handshake_failure_event_alloc( + pbcc_subscribe_ee_t* ee, + const enum pubnub_res reason) +{ + const pbcc_subscribe_ee_event type = SUBSCRIBE_EE_EVENT_HANDSHAKE_FAILURE; + return pbcc_subscribe_ee_event_alloc_(ee, + type, + NULL, + NULL, + NULL, + &reason, + true); +} + +pbcc_ee_event_t* pbcc_receive_success_event_alloc( + pbcc_subscribe_ee_t* ee, + const pubnub_subscribe_cursor_t cursor) +{ + const pbcc_subscribe_ee_event type = SUBSCRIBE_EE_EVENT_RECEIVE_SUCCESS; + return pbcc_subscribe_ee_event_alloc_(ee, + type, + NULL, + NULL, + &cursor, + NULL, + true); +} + +pbcc_ee_event_t* pbcc_receive_failure_event_alloc( + pbcc_subscribe_ee_t* ee, + const enum pubnub_res reason) +{ + const pbcc_subscribe_ee_event type = SUBSCRIBE_EE_EVENT_RECEIVE_FAILURE; + return pbcc_subscribe_ee_event_alloc_(ee, + type, + NULL, + NULL, + NULL, + &reason, + true); +} + +pbcc_ee_event_t* pbcc_disconnect_event_alloc(pbcc_subscribe_ee_t* ee) +{ + const pbcc_subscribe_ee_event type = SUBSCRIBE_EE_EVENT_DISCONNECT; + return pbcc_subscribe_ee_event_alloc_(ee, + type, + NULL, + NULL, + NULL, + NULL, + true); +} + +pbcc_ee_event_t* pbcc_reconnect_event_alloc( + pbcc_subscribe_ee_t* ee, + const pubnub_subscribe_cursor_t cursor) +{ + const pbcc_subscribe_ee_event type = SUBSCRIBE_EE_EVENT_RECONNECT; + return pbcc_subscribe_ee_event_alloc_(ee, + type, + NULL, + NULL, + &cursor, + NULL, + true); +} + +pbcc_ee_event_t* pbcc_unsubscribe_all_event_alloc( + pbcc_subscribe_ee_t* ee, + char** channels, + char** channel_groups) +{ + if (NULL != channels && NULL != *channels && 0 == strlen(*channels)) { + free(*channels); + *channels = NULL; + } + if (NULL != channel_groups && NULL != *channel_groups && + 0 == strlen(*channel_groups)) { + free(*channel_groups); + *channel_groups = NULL; + } + + const pbcc_subscribe_ee_event type = SUBSCRIBE_EE_EVENT_UNSUBSCRIBE_ALL; + return pbcc_subscribe_ee_event_alloc_(ee, + type, + channels, + channel_groups, + NULL, + NULL, + false); +} + +pbcc_ee_event_t* pbcc_subscribe_ee_event_alloc_( + pbcc_subscribe_ee_t* ee, + const pbcc_subscribe_ee_event event, + char** channels, + char** channel_groups, + const pubnub_subscribe_cursor_t* cursor, + const enum pubnub_res* reason, + const bool sent_by_ee) +{ + pubnub_mutex_lock(ee->mutw); + pbcc_ee_data_t* current_state_data = + pbcc_subscribe_ee_current_state_context(ee); + const pbcc_subscribe_ee_context_t* current_context = + pbcc_ee_data_value(current_state_data); + const bool copy_subscribables = + event != SUBSCRIBE_EE_EVENT_SUBSCRIPTION_CHANGED && + event != SUBSCRIBE_EE_EVENT_SUBSCRIPTION_RESTORED; + + pbcc_subscribe_ee_context_t* ctx = pbcc_subscribe_ee_context_copy_( + ee->pb, + current_context, + channels, + channel_groups, + copy_subscribables); + /** For subscription change / restore we need to send heartbeat. */ + ctx->send_heartbeat = !copy_subscribables && !sent_by_ee; + pbcc_ee_data_free(current_state_data); + + if (NULL == ctx) { + pubnub_mutex_unlock(ee->mutw); + return NULL; + } + if (NULL != cursor) { ctx->cursor = *cursor; } + if (NULL != reason) { ctx->reason = *reason; } + + pbcc_ee_data_t* data = pbcc_ee_data_alloc( + ctx, + (pbcc_ee_data_free_function_t)pbcc_subscribe_ee_context_free_); + if (NULL == data) { + pubnub_mutex_unlock(ee->mutw); + pbcc_subscribe_ee_context_free_(ctx); + return NULL; + } + pubnub_mutex_unlock(ee->mutw); + + pbcc_ee_event_t* event_object = pbcc_ee_event_alloc(event, data); + if (NULL == event_object) { + pbcc_ee_data_free(data); + return NULL; + } + + return event_object; +} + +pbcc_subscribe_ee_context_t* pbcc_subscribe_ee_context_alloc_( + pubnub_t* pb, + char** channels, + char** channel_groups) +{ + pbcc_subscribe_ee_context_t* context = + malloc(sizeof(pbcc_subscribe_ee_context_t)); + if (NULL == context) { + PUBNUB_LOG_ERROR( + "pbcc_subscribe_ee_context_alloc_: failed to allocate memory\n"); + if (NULL != channels && NULL != *channels) { + free(*channels); + *channels = NULL; + } + if (NULL != channel_groups && NULL != *channel_groups) { + free(*channel_groups); + *channel_groups = NULL; + } + return NULL; + } + + context->pb = pb; + context->reason = PNR_OK; + context->channels = NULL; + context->channel_groups = NULL; + context->send_heartbeat = false; + context->cursor = pubnub_subscribe_cursor(NULL); + if (NULL != channels && NULL != *channels) + context->channels = pbcc_ee_data_alloc(*channels, free); + if (NULL != channel_groups && NULL != *channel_groups) { + context->channel_groups = + pbcc_ee_data_alloc(*channel_groups, free); + } + + return context; +} + +pbcc_subscribe_ee_context_t* pbcc_subscribe_ee_context_copy_( + pubnub_t* pb, + const pbcc_subscribe_ee_context_t* ctx, + char** channels, + char** channel_groups, + const bool copy_subscribables) +{ + pbcc_subscribe_ee_context_t* context = pbcc_subscribe_ee_context_alloc_( + pb, + channels, + channel_groups); + if (NULL == context) { return NULL; } + if (NULL == ctx) { return context; } + + if (copy_subscribables) { + if (NULL == context->channels && NULL != ctx->channels) + context->channels = pbcc_ee_data_copy(ctx->channels); + if (NULL == context->channel_groups && NULL != ctx->channel_groups) + context->channel_groups = pbcc_ee_data_copy(ctx->channel_groups); + } + context->cursor = ctx->cursor; + + return context; +} + +void pbcc_subscribe_ee_context_free_(pbcc_subscribe_ee_context_t* ctx) +{ + if (NULL == ctx) { return; } + + if (NULL != ctx->channels) { pbcc_ee_data_free(ctx->channels); } + if (NULL != ctx->channel_groups) { pbcc_ee_data_free(ctx->channel_groups); } + free(ctx); +} \ No newline at end of file diff --git a/core/pbcc_subscribe_event_engine_events.h b/core/pbcc_subscribe_event_engine_events.h new file mode 100644 index 00000000..a73c7e4b --- /dev/null +++ b/core/pbcc_subscribe_event_engine_events.h @@ -0,0 +1,222 @@ +/* -*- c-file-style:"stroustrup"; indent-tabs-mode: nil -*- */ +#ifndef PBCC_SUBSCRIBE_EVENT_ENGINE_EVENTS_H +#define PBCC_SUBSCRIBE_EVENT_ENGINE_EVENTS_H +#if PUBNUB_USE_SUBSCRIBE_EVENT_ENGINE +#if !PUBNUB_USE_SUBSCRIBE_V2 +#error Subscribe event engine requires subscribe v2 API, so you must define PUBNUB_USE_SUBSCRIBE_V2=1 +#endif // #if !PUBNUB_USE_SUBSCRIBE_V2 + + +/** + * @file pbcc_subscribe_event_engine_events.h + * @brief Subscribe Event Engine states transition events. + */ + +#include "core/pbcc_subscribe_event_engine_types.h" +#include "core/pbcc_subscribe_event_engine.h" +#include "core/pbcc_event_engine.h" + + +// ---------------------------------------------- +// Functions +// ---------------------------------------------- + +/** + * @brief Subscription list of channels / groups change event. + * + * This event will be emitted each time when user would like to add new or + * remove already active channels / channel groups. + * + * @note State and subscription context objects will handle further memory + * management of the provided channels and channel groups byte string + * pointers. + * + * @param ee Pointer to the Subscribe Event Engine for + * which will process the event. + * @param [in,out] channels Pointer to the byte sting of comma-separated + * channels from which PubNub client should + * receive real-time updates. + * @param [in,out] channel_groups Pointer to the byte sting of comma-separated + * channel groups from which PubNub client should + * receive real-time updates. + * @param sent_by_ee Whether event has been sent by Subscribe Event + * Engine or not. + * @return Pointer to the `Subscription change event`, which will be processed + * by the Subscribe Event Engine to get transition instructions from the + * current state. + */ +pbcc_ee_event_t* pbcc_subscription_changed_event_alloc( + pbcc_subscribe_ee_t* ee, + char** channels, + char** channel_groups, + bool sent_by_ee); + +/** + * @brief Subscription restore / catchup event. + * + * This event will be emitted each time when user would like to add new or + * remove already active channels / channel groups with specific time token + * (cursor) to catch up messages from. + * + * @note State and subscription context objects will handle further memory + * management of the provided channels and channel groups byte string + * pointers. + * + * @param ee Pointer to the Subscribe Event Engine for + * which will process the event. + * @param [in,out] channels Pointer to the byte string of comma-separated + * channels from which PubNub client should + * receive real-time updates. + * @param [in,out] channel_groups Pointer to the byte string of comma-separated + * channel groups from which PubNub client should + * receive real-time updates. + * @param cursor Time token to which PubNub client should try + * to restore subscription (catch up on missing + * messages) loop. + * @param sent_by_ee Whether event has been sent by Subscribe Event + * Engine or not. + * @return Pointer to the `Subscription restore event`, which will be processed + * by the Subscribe Event Engine to get transition instructions from the + * current state. + */ +pbcc_ee_event_t* pbcc_subscription_restored_event_alloc( + pbcc_subscribe_ee_t* ee, + char** channels, + char** channel_groups, + pubnub_subscribe_cursor_t cursor, + bool sent_by_ee); + +/** + * @brief Initial subscription handshake success event. + * + * This event will be emitted each time when initial subscription to the set of + * channels / groups will complete with a service response which contains the + * time token (cursor) for the next subscription loop. + * + * @param ee Pointer to the Subscribe Event Engine for which will process + * the event. + * @param cursor Time token, which should be used with next subscription loop. + * @return Pointer to the `Subscription handshake success event`, which will be + * processed by the Subscribe Event Engine to get transition + * instructions from the current state. + */ +pbcc_ee_event_t* pbcc_handshake_success_event_alloc( + pbcc_subscribe_ee_t* ee, + pubnub_subscribe_cursor_t cursor); + +/** + * @brief Initial subscription handshake failure event. + * + * This event will be emitted each time when initial subscription to the set of + * channels / groups fails. Failure in most of the cases caused by permissions + * and network issues. + * + * @param ee Pointer to the Subscribe Event Engine for which will process + * the event. + * @param reason Subscription processing result code which explains failure + * reason. + * @return Pointer to the `Subscription handshake failure event`, which will be + * processed by the Subscribe Event Engine to get transition + * instructions from the current state. + */ +pbcc_ee_event_t* pbcc_handshake_failure_event_alloc( + pbcc_subscribe_ee_t* ee, + enum pubnub_res reason); + +/** + * @default Real-time updates receive success event. + * + * This event will be emitted each time when subscription receives a response + * from PubNub service. + * + * @param ee Pointer to the Subscribe Event Engine for which will process + * the event. + * @param cursor Time token, which should be used with next subscription loop. + * @return Pointer to the `Subscription receive success event`, which will be + * processed by the Subscribe Event Engine to get transition + * instructions from the current state. + */ +pbcc_ee_event_t* pbcc_receive_success_event_alloc( + pbcc_subscribe_ee_t* ee, + pubnub_subscribe_cursor_t cursor); + +/** +* @brief Real-time updates receive failure event. + * + * @param ee Pointer to the Subscribe Event Engine for which will process + * the event. + * @param reason Subscription processing result code which explain failure + * reason. + * @return Pointer to the `Subscription receive failure event`, which will be + * processed by the Subscribe Event Engine to get transition + * instructions from the current state. + */ +pbcc_ee_event_t* pbcc_receive_failure_event_alloc( + pbcc_subscribe_ee_t* ee, + enum pubnub_res reason); + +/** + * @brief Disconnect from real-time updates event. + * + * @note List of channels / groups and active time token (cursor) will be + * saved for subscription restore. + * + * @param ee Pointer to the Subscribe Event Engine for which will process the + * event. + * @return Pointer to the `Disconnect event`, which will be processed by the + * Subscribe Event Engine to get transition instructions from the + * current state. + */ +pbcc_ee_event_t* pbcc_disconnect_event_alloc(pbcc_subscribe_ee_t* ee); + +/** + * @brief Reconnect for real-time updates event. + * + * @note Subscription loop will use time token (cursor) which has been in + * use before disconnection / failure. + * + * @param ee Pointer to the Subscribe Event Engine for which will process + * the event. + * @param cursor Time token, which should be used with next subscription + * REST API loop call. + * @return Pointer to the `Reconnect event`, which will be processed by the + * Subscribe Event Engine to get transition instructions from the + * current state. + */ +pbcc_ee_event_t* pbcc_reconnect_event_alloc( + pbcc_subscribe_ee_t* ee, + pubnub_subscribe_cursor_t cursor); + +/** + * @brief Unsubscribe from all channel / groups event. + * + * Unsubscribe from all real-time updates and notify other channel / groups + * subscribers about client presence change (`leave`). + * + * @note The list of channels / groups and used time token (cursor) will be + * reset, and further real-time updates can't be received without a + * `subscribe` call. + * @note State and subscription context objects will handle further memory + * management of the provided channels and channel groups byte string + * pointers. + * + * @param ee Pointer to the Subscribe Event Engine for + * which will process the event. + * @param [in,out] channels Pointer to the byte sting of comma-separated + * channels from which PubNub client should + * receive real-time updates. + * @param [in,out] channel_groups Pointer to the byte string of comma-separated + * channel groups from which PubNub client should + * receive real-time updates. + * @return Pointer to the `Unsubscribe all event`, which will be processed by + * the Subscribe Event Engine to get transition instructions from the + * current state. + */ +pbcc_ee_event_t* pbcc_unsubscribe_all_event_alloc( + pbcc_subscribe_ee_t* ee, + char** channels, + char** channel_groups); +#else // #if PUBNUB_USE_SUBSCRIBE_EVENT_ENGINE +#error To use subscribe event engine API you must define PUBNUB_USE_SUBSCRIBE_EVENT_ENGINE=1 +#endif // #if PUBNUB_USE_SUBSCRIBE_EVENT_ENGINE +#endif //PBCC_SUBSCRIBE_EVENT_ENGINE_EVENTS_H diff --git a/core/pbcc_subscribe_event_engine_states.c b/core/pbcc_subscribe_event_engine_states.c new file mode 100644 index 00000000..b9b80944 --- /dev/null +++ b/core/pbcc_subscribe_event_engine_states.c @@ -0,0 +1,134 @@ +/* -*- c-file-style:"stroustrup"; indent-tabs-mode: nil -*- */ +#include "pbcc_subscribe_event_engine_states.h" + +#include "core/pbcc_subscribe_event_engine_transitions.h" +#include "core/pbcc_subscribe_event_engine_effects.h" +#include "core/pbcc_subscribe_event_engine_types.h" +#include "core/pubnub_assert.h" + + +// ---------------------------------------------- +// Functions +// ---------------------------------------------- + +pbcc_ee_state_t* pbcc_unsubscribed_state_alloc() +{ + return pbcc_ee_state_alloc( + SUBSCRIBE_EE_STATE_UNSUBSCRIBED, + NULL, + pbcc_unsubscribed_state_transition_alloc); +} + +pbcc_ee_state_t* pbcc_handshaking_state_alloc( + pbcc_event_engine_t* ee, + pbcc_ee_data_t* context) +{ + pbcc_ee_state_t* state = pbcc_ee_state_alloc( + SUBSCRIBE_EE_STATE_HANDSHAKING, + context, + pbcc_handshaking_state_transition_alloc); + if (NULL == state) { return NULL; } + + pbcc_ee_invocation_t* on_enter = pbcc_ee_invocation_alloc( + SUBSCRIBE_EE_INVOCATION_HANDSHAKE, + (pbcc_ee_effect_function_t)pbcc_subscribe_ee_handshake_effect, + context, + false); + + /** Make sure that there is no outdated subscribe call. */ + pbcc_ee_invocation_cancel_by_type(ee, SUBSCRIBE_EE_INVOCATION_HANDSHAKE); + + if (NULL == on_enter || + PNR_OK != pbcc_ee_state_add_on_enter_invocation(state, on_enter)) { + if (NULL != on_enter) { pbcc_ee_invocation_free(on_enter); } + pbcc_ee_state_free(&state); + return NULL; + } + + pbcc_ee_invocation_t* on_exit = pbcc_ee_invocation_alloc( + SUBSCRIBE_EE_INVOCATION_CANCEL, + (pbcc_ee_effect_function_t)pbcc_subscribe_ee_cancel_effect, + context, + true); + if (NULL == on_exit || + PNR_OK != pbcc_ee_state_add_on_exit_invocation(state, on_exit)) { + if (NULL != on_exit) { pbcc_ee_invocation_free(on_exit); } + pbcc_ee_state_free(&state); + return NULL; + } + + return state; +} + +pbcc_ee_state_t* pbcc_handshake_failed_state_alloc(pbcc_ee_data_t* context) +{ + return pbcc_ee_state_alloc( + SUBSCRIBE_EE_STATE_HANDSHAKE_FAILED, + context, + pbcc_handshake_failed_state_transition_alloc); +} + +pbcc_ee_state_t* pbcc_handshake_stopped_state_alloc(pbcc_ee_data_t* context) +{ + return pbcc_ee_state_alloc( + SUBSCRIBE_EE_STATE_HANDSHAKE_STOPPED, + context, + pbcc_handshake_stopped_state_transition_alloc); +} + +pbcc_ee_state_t* pbcc_receiving_state_alloc( + pbcc_event_engine_t* ee, + pbcc_ee_data_t* context) +{ + pbcc_ee_state_t* state = pbcc_ee_state_alloc( + SUBSCRIBE_EE_STATE_RECEIVING, + context, + pbcc_receiving_state_transition_alloc); + if (NULL == state) { return NULL; } + + pbcc_ee_invocation_t* on_enter = pbcc_ee_invocation_alloc( + SUBSCRIBE_EE_INVOCATION_RECEIVE, + (pbcc_ee_effect_function_t)pbcc_subscribe_ee_receive_effect, + context, + false); + + /** Make sure that there is no outdated subscribe call. */ + pbcc_ee_invocation_cancel_by_type(ee, SUBSCRIBE_EE_INVOCATION_RECEIVE); + + if (NULL == on_enter || + PNR_OK != pbcc_ee_state_add_on_enter_invocation(state, on_enter)) { + if (NULL != on_enter) { pbcc_ee_invocation_free(on_enter); } + pbcc_ee_state_free(&state); + return NULL; + } + + pbcc_ee_invocation_t* on_exit = pbcc_ee_invocation_alloc( + SUBSCRIBE_EE_INVOCATION_CANCEL, + (pbcc_ee_effect_function_t)pbcc_subscribe_ee_cancel_effect, + context, + true); + if (NULL == on_exit || + PNR_OK != pbcc_ee_state_add_on_exit_invocation(state, on_exit)) { + if (NULL != on_exit) { pbcc_ee_invocation_free(on_exit); } + pbcc_ee_state_free(&state); + return NULL; + } + + return state; +} + +pbcc_ee_state_t* pbcc_receive_failed_state_alloc(pbcc_ee_data_t* context) +{ + return pbcc_ee_state_alloc( + SUBSCRIBE_EE_STATE_RECEIVE_FAILED, + context, + pbcc_receiving_failed_state_transition_alloc); +} + +pbcc_ee_state_t* pbcc_receive_stopped_state_alloc(pbcc_ee_data_t* context) +{ + return pbcc_ee_state_alloc( + SUBSCRIBE_EE_STATE_RECEIVE_STOPPED, + context, + pbcc_receiving_stopped_state_transition_alloc); +} \ No newline at end of file diff --git a/core/pbcc_subscribe_event_engine_states.h b/core/pbcc_subscribe_event_engine_states.h new file mode 100644 index 00000000..58e79610 --- /dev/null +++ b/core/pbcc_subscribe_event_engine_states.h @@ -0,0 +1,130 @@ +/* -*- c-file-style:"stroustrup"; indent-tabs-mode: nil -*- */ +#ifndef PBCC_SUBSCRIBE_EVENT_ENGINE_STATES_H +#define PBCC_SUBSCRIBE_EVENT_ENGINE_STATES_H +#if PUBNUB_USE_SUBSCRIBE_EVENT_ENGINE +#if !PUBNUB_USE_SUBSCRIBE_V2 +#error Subscribe event engine requires subscribe v2 API, so you must define PUBNUB_USE_SUBSCRIBE_V2=1 +#endif // #if !PUBNUB_USE_SUBSCRIBE_V2 + + +/** + * @file pbcc_subscribe_event_engine_states.h + * @brief Subscribe Event Engine states. + */ + +#include "core/pbcc_event_engine.h" + + +// ---------------------------------------------- +// Functions +// ---------------------------------------------- + +/** + * @brief Inactive Subscribe Event Engine state. + * + * State with which Subscribe Event Engine will be initialized and end up + * when there will be no channels or groups to subscribe to. + * + * @param ee Pointer to the Event Engine which new state should be created. + * @return Pointer to the `Unsubscribed` state. + */ +pbcc_ee_state_t* pbcc_unsubscribed_state_alloc(void); + +/** + * @brief Initial subscription state. + * + * State, which is used to perform initial subscription to receive the next + * subscription loop time token (cursor) and notify channels / groups subscribes + * about change in subscriber's presence. + * + * @param ee Pointer to the Event Engine which new state should be created. + * @param context Pointer to the context with updated Subscribe Event Engine + * information which should be used for the next subscription + * loop. + * @return Pointer to the `Handshaking state`, which should be used as + * Subscribe Event Engine state transition target state. + */ +pbcc_ee_state_t* pbcc_handshaking_state_alloc( + pbcc_event_engine_t* ee, + pbcc_ee_data_t* context); + +/** + * @brief Initial subscription failed state. + * + * State, which is used by the Subscribe Event Engine when the last + * subscription loop can't be retried anymore. + * + * @param context Pointer to the context with list of channels / groups and time + * token (cursor) which has been used during last initial + * subscription before receiving operation failure result. + * @return Pointer to the `Handshake failed state`, which should be used as + * Subscribe Event Engine state transition target state. + */ +pbcc_ee_state_t* pbcc_handshake_failed_state_alloc(pbcc_ee_data_t* context); + +/** + * @brief Initial subscription stopped state. + * + * State, which is used by the Subscribe Event Engine to preserve its last + * context while disconnected, and awaiting its reconnection. + * + * @param context Pointer to the context with list of channels / groups and time + * token (cursor) which has been used during last initial + * subscription. + * @return Pointer to the `Handshake stopped state`, which should be used as + * Subscribe Event Engine state transition target state. + */ +pbcc_ee_state_t* pbcc_handshake_stopped_state_alloc(pbcc_ee_data_t* context); + +/** + * @brief Real-time updates receiving state. + * + * State, which is used by the Subscribe Event Engine to perform a long-poll + * subscription loop to receive real-time updates. + * + * @param ee Pointer to the Event Engine which new state should be created. + * @param context Pointer to the context with list of channels / groups and time + * token (cursor) received from previous subscription loop to + * receive next real-time updates. + * @return Pointer to the `Receiving real-time updates state`, which should be + * used as Subscribe Event Engine state transition target state. + */ +pbcc_ee_state_t* pbcc_receiving_state_alloc( + pbcc_event_engine_t* ee, + pbcc_ee_data_t* context); + +/** + * @brief Real-time updates receive failed state. + * + * State, which is used by the Subscribe Event Engine when the last long-poll + * subscription loop can't be retried anymore. + * + * @param context Pointer to the context with list of channels / groups and time + * token (cursor) received from previous subscription loop to + * receive next real-time updates before receiving operation + * failure result. + * @return Pointer to the `Receive real-time updates failed state`, which should + * be used as Subscribe Event Engine state transition target state. + */ +pbcc_ee_state_t* pbcc_receive_failed_state_alloc(pbcc_ee_data_t* context); + +/** + * @brief Real-time updates receive stopped state. + * + * State, which is used by the Subscribe Event Engine to preserve its last + * context used for long-poll subscription loop while disconnected, and awaiting + * its reconnection. + * + * @param context Pointer to the context with list of channels / groups and time + * token (cursor) received from previous subscription loop to + * receive next real-time updates. + * receive next real-time updates. + * @return Pointer to the `Receive real-time updates stopped state`, which + * should be used as Subscribe Event Engine state transition target + * state. + */ +pbcc_ee_state_t* pbcc_receive_stopped_state_alloc(pbcc_ee_data_t* context); +#else // #if PUBNUB_USE_SUBSCRIBE_EVENT_ENGINE +#error To use subscribe event engine API you must define PUBNUB_USE_SUBSCRIBE_EVENT_ENGINE=1 +#endif // #if PUBNUB_USE_SUBSCRIBE_EVENT_ENGINE +#endif //PBCC_SUBSCRIBE_EVENT_ENGINE_STATES_H diff --git a/core/pbcc_subscribe_event_engine_transitions.c b/core/pbcc_subscribe_event_engine_transitions.c new file mode 100644 index 00000000..4887154a --- /dev/null +++ b/core/pbcc_subscribe_event_engine_transitions.c @@ -0,0 +1,492 @@ +/* -*- c-file-style:"stroustrup"; indent-tabs-mode: nil -*- */ +#include "pbcc_subscribe_event_engine_transitions.h" + +#include "core/pbcc_subscribe_event_engine_effects.h" +#include "core/pbcc_subscribe_event_engine_states.h" +#include "core/pbcc_subscribe_event_engine_types.h" +#include "core/pubnub_assert.h" +#include "pubnub_internal.h" +#include "core/pubnub_log.h" + + +// ---------------------------------------------- +// Function prototypes +// ---------------------------------------------- + +/** + * @brief Create transition details for Event Engine. + * + * @param ee Pointer to the Event Engine which requested for + * transition details. + * @param target_state_type Type of target Subscribe Event Engine state. + * @param context Subscribe Event Engine data which should be passed + * to the next state and transition invocations. + * @param invocations Pointer to the list of invocations which should be + * called before Event Engine will enter new state. + * @return Pointer to the ready to use transition details or `NULL` in case of + * insufficient memory error. The returned pointer must be passed to the + * `pbcc_ee_transition_free` to avoid a memory leak. + */ +static pbcc_ee_transition_t* pbcc_transition_alloc_( + pbcc_event_engine_t* ee, + pbcc_subscribe_ee_state target_state_type, + pbcc_ee_data_t* context, + pbarray_t* invocations); + + +// ---------------------------------------------- +// Functions +// ---------------------------------------------- + +pbcc_ee_transition_t* pbcc_unsubscribed_state_transition_alloc( + pbcc_event_engine_t* ee, + const pbcc_ee_state_t* current_state, + const pbcc_ee_event_t* event) +{ + if (pbcc_ee_state_type(current_state) != SUBSCRIBE_EE_STATE_UNSUBSCRIBED || + (event->type != SUBSCRIBE_EE_EVENT_SUBSCRIPTION_CHANGED && + event->type != SUBSCRIBE_EE_EVENT_SUBSCRIPTION_RESTORED)) { + PUBNUB_LOG_INFO("pbcc_unsubscribed_state_transition_alloc: can't " + "handle transition for %d event type\n", + event->type); + return pbcc_transition_alloc_(ee, SUBSCRIBE_EE_STATE_NONE, NULL, NULL); + } + + return pbcc_transition_alloc_(ee, + SUBSCRIBE_EE_STATE_HANDSHAKING, + event->data, + NULL); +} + +pbcc_ee_transition_t* pbcc_handshaking_state_transition_alloc( + pbcc_event_engine_t* ee, + const pbcc_ee_state_t* current_state, + const pbcc_ee_event_t* event) +{ + if (pbcc_ee_state_type(current_state) != SUBSCRIBE_EE_STATE_HANDSHAKING) + return pbcc_transition_alloc_(ee, SUBSCRIBE_EE_STATE_NONE, NULL, NULL); + + pbcc_subscribe_ee_state target_state_type = SUBSCRIBE_EE_STATE_NONE; + pbcc_ee_data_t* data = event->data; + pbarray_t* invocations = NULL; + + switch (event->type) { + case SUBSCRIBE_EE_EVENT_SUBSCRIPTION_CHANGED: { + PUBNUB_ASSERT_OPT(NULL != data); + + const pbcc_subscribe_ee_context_t* context = pbcc_ee_data_value(data); + const char* channel_groups = + pbcc_ee_data_value(context->channel_groups); + const char* channels = pbcc_ee_data_value(context->channels); + + if (NULL != context && 0 == strlen(channels) && + 0 == strlen(channel_groups)) { + target_state_type = SUBSCRIBE_EE_STATE_UNSUBSCRIBED; + data = NULL; + } + else { target_state_type = SUBSCRIBE_EE_STATE_HANDSHAKING; } + } + break; + case SUBSCRIBE_EE_EVENT_SUBSCRIPTION_RESTORED: + target_state_type = SUBSCRIBE_EE_STATE_HANDSHAKING; + break; + case SUBSCRIBE_EE_EVENT_HANDSHAKE_FAILURE: + case SUBSCRIBE_EE_EVENT_HANDSHAKE_SUCCESS: { + const pbcc_subscribe_ee_context_t* context = pbcc_ee_data_value(data); + pbcc_subscribe_ee_t* subscribe_ee = context->pb->core.subscribe_ee; + target_state_type = event->type == + SUBSCRIBE_EE_EVENT_HANDSHAKE_SUCCESS + ? SUBSCRIBE_EE_STATE_RECEIVING + : SUBSCRIBE_EE_STATE_HANDSHAKE_FAILED; + invocations = pbarray_alloc( + 1, + PBARRAY_RESIZE_NONE, + PBARRAY_GENERIC_CONTENT_TYPE, + (pbarray_element_free)pbcc_ee_invocation_free); + pbcc_ee_invocation_t* invocation = pbcc_ee_invocation_alloc( + SUBSCRIBE_EE_INVOCATION_EMIT_STATUS, + (pbcc_ee_effect_function_t)pbcc_subscribe_ee_emit_status_effect, + data, + false); + + if (NULL == invocations || NULL == invocation) { + PUBNUB_LOG_ERROR("pbcc_handshaking_state_transition_alloc: failed " + "to allocate memory for invocations\n"); + if (NULL != invocations) { pbarray_free(&invocations); } + if (NULL != invocation) { pbcc_ee_invocation_free(invocation); } + return NULL; + } + + if (PBAR_OK != pbarray_add(invocations, invocation)) { + PUBNUB_LOG_ERROR("pbcc_handshaking_state_transition_alloc: failed " + "to allocate memory for invocation entry\n"); + pbcc_ee_invocation_free(invocation); + pbarray_free(&invocations); + return NULL; + } + + /** Update latest Subscribe Event Engine subscription status. */ + pubnub_mutex_lock(subscribe_ee->mutw); + subscribe_ee->status = SUBSCRIBE_EE_STATE_RECEIVING == target_state_type + ? PNSS_SUBSCRIPTION_STATUS_CONNECTED + : PNSS_SUBSCRIPTION_STATUS_CONNECTION_ERROR; + pubnub_mutex_unlock(subscribe_ee->mutw); + } + break; + case SUBSCRIBE_EE_EVENT_DISCONNECT: + target_state_type = SUBSCRIBE_EE_STATE_HANDSHAKE_STOPPED; + break; + case SUBSCRIBE_EE_EVENT_UNSUBSCRIBE_ALL: + target_state_type = SUBSCRIBE_EE_STATE_UNSUBSCRIBED; + break; + default: + invocations = NULL; + data = NULL; + break; + } + + if (SUBSCRIBE_EE_STATE_NONE == target_state_type) { + PUBNUB_LOG_INFO("pbcc_handshaking_state_transition_alloc: can't handle " + "transition for %d event type\n", + event->type); + } + + return pbcc_transition_alloc_(ee, target_state_type, data, invocations); +} + +pbcc_ee_transition_t* pbcc_handshake_failed_state_transition_alloc( + pbcc_event_engine_t* ee, + const pbcc_ee_state_t* current_state, + const pbcc_ee_event_t* event) +{ + if (pbcc_ee_state_type(current_state) != + SUBSCRIBE_EE_STATE_HANDSHAKE_FAILED) { + return pbcc_transition_alloc_(ee, SUBSCRIBE_EE_STATE_NONE, NULL, NULL); + } + + pbcc_subscribe_ee_state target_state_type = SUBSCRIBE_EE_STATE_NONE; + pbcc_ee_data_t* data = event->data; + + switch (event->type) { + case SUBSCRIBE_EE_EVENT_SUBSCRIPTION_CHANGED: + case SUBSCRIBE_EE_EVENT_SUBSCRIPTION_RESTORED: { + PUBNUB_ASSERT_OPT(NULL != data); + + const pbcc_subscribe_ee_context_t* context = pbcc_ee_data_value(data); + const char* channel_groups = + pbcc_ee_data_value(context->channel_groups); + const char* channels = pbcc_ee_data_value(context->channels); + + if (NULL != context && (NULL == channels || 0 == strlen(channels)) && + (NULL == channel_groups || 0 == strlen(channel_groups))) { + target_state_type = SUBSCRIBE_EE_STATE_UNSUBSCRIBED; + } + else { target_state_type = SUBSCRIBE_EE_STATE_HANDSHAKING; } + } + break; + case SUBSCRIBE_EE_EVENT_RECONNECT: + target_state_type = SUBSCRIBE_EE_STATE_HANDSHAKING; + break; + case SUBSCRIBE_EE_EVENT_UNSUBSCRIBE_ALL: + target_state_type = SUBSCRIBE_EE_STATE_UNSUBSCRIBED; + break; + default: + data = NULL; + break; + } + + if (SUBSCRIBE_EE_STATE_NONE == target_state_type) { + PUBNUB_LOG_INFO("pbcc_handshake_failed_state_transition_alloc: can't " + "handle transition for %d event type\n", + event->type); + } + + return pbcc_transition_alloc_(ee, target_state_type, data, NULL); +} + +pbcc_ee_transition_t* pbcc_handshake_stopped_state_transition_alloc( + pbcc_event_engine_t* ee, + const pbcc_ee_state_t* current_state, + const pbcc_ee_event_t* event) +{ + if (pbcc_ee_state_type(current_state) != + SUBSCRIBE_EE_STATE_HANDSHAKE_STOPPED) { + return pbcc_transition_alloc_(ee, SUBSCRIBE_EE_STATE_NONE, NULL, NULL); + } + + pbcc_subscribe_ee_state target_state_type = SUBSCRIBE_EE_STATE_NONE; + + switch (event->type) { + case SUBSCRIBE_EE_EVENT_SUBSCRIPTION_CHANGED: + case SUBSCRIBE_EE_EVENT_SUBSCRIPTION_RESTORED: + target_state_type = SUBSCRIBE_EE_STATE_HANDSHAKE_STOPPED; + break; + case SUBSCRIBE_EE_EVENT_RECONNECT: + target_state_type = SUBSCRIBE_EE_STATE_HANDSHAKING; + break; + case SUBSCRIBE_EE_EVENT_UNSUBSCRIBE_ALL: + target_state_type = SUBSCRIBE_EE_STATE_UNSUBSCRIBED; + break; + default: + break; + } + + if (SUBSCRIBE_EE_STATE_NONE == target_state_type) { + PUBNUB_LOG_INFO("pbcc_handshake_stopped_state_transition_alloc: can't " + "handle transition for %d event type\n", + event->type); + } + + return pbcc_transition_alloc_(ee, target_state_type, event->data, NULL); +} + +pbcc_ee_transition_t* pbcc_receiving_state_transition_alloc( + pbcc_event_engine_t* ee, + const pbcc_ee_state_t* current_state, + const pbcc_ee_event_t* event) +{ + if (pbcc_ee_state_type(current_state) != SUBSCRIBE_EE_STATE_RECEIVING) + return pbcc_transition_alloc_(ee, SUBSCRIBE_EE_STATE_NONE, NULL, NULL); + + pbcc_subscribe_ee_state target_state_type = SUBSCRIBE_EE_STATE_NONE; + pubnub_subscription_status status = -1; + pbarray_t* invocations = NULL; + pbcc_ee_invocation_t* invocation = NULL; + pbcc_ee_data_t* data = event->data; + + switch (event->type) { + case SUBSCRIBE_EE_EVENT_SUBSCRIPTION_CHANGED: + case SUBSCRIBE_EE_EVENT_SUBSCRIPTION_RESTORED: { + const pbcc_subscribe_ee_context_t* context = ( + pbcc_subscribe_ee_context_t*) + pbcc_ee_data_value(data); + const char* channel_groups = + pbcc_ee_data_value(context->channel_groups); + const char* channels = pbcc_ee_data_value(context->channels); + + if (NULL != context && (NULL == channels || 0 == strlen(channels)) && + (NULL == channel_groups || 0 == strlen(channel_groups))) { + target_state_type = SUBSCRIBE_EE_STATE_UNSUBSCRIBED; + status = PNSS_SUBSCRIPTION_STATUS_DISCONNECTED; + } + else { + target_state_type = SUBSCRIBE_EE_STATE_RECEIVING; + status = PNSS_SUBSCRIPTION_STATUS_SUBSCRIPTION_CHANGED; + } + } + break; + case SUBSCRIBE_EE_EVENT_RECEIVE_SUCCESS: + target_state_type = SUBSCRIBE_EE_STATE_RECEIVING; + break; + case SUBSCRIBE_EE_EVENT_RECEIVE_FAILURE: + target_state_type = SUBSCRIBE_EE_STATE_RECEIVE_FAILED; + status = PNSS_SUBSCRIPTION_STATUS_DISCONNECTED_UNEXPECTEDLY; + break; + case SUBSCRIBE_EE_EVENT_DISCONNECT: + target_state_type = SUBSCRIBE_EE_STATE_RECEIVE_STOPPED; + status = PNSS_SUBSCRIPTION_STATUS_DISCONNECTED; + break; + case SUBSCRIBE_EE_EVENT_UNSUBSCRIBE_ALL: + target_state_type = SUBSCRIBE_EE_STATE_UNSUBSCRIBED; + status = PNSS_SUBSCRIPTION_STATUS_DISCONNECTED; + break; + default: + data = NULL; + break; + } + + if (SUBSCRIBE_EE_EVENT_RECEIVE_SUCCESS == event->type || -1 != status) { + invocations = pbarray_alloc( + 1, + PBARRAY_RESIZE_NONE, + PBARRAY_GENERIC_CONTENT_TYPE, + (pbarray_element_free)pbcc_ee_invocation_free); + + if (NULL == invocations) { + PUBNUB_LOG_ERROR("pbcc_receiving_state_transition_alloc: failed " + "to allocate memory for invocations\n"); + return NULL; + } + } + + if (-1 != status) { + const pbcc_subscribe_ee_context_t* context = pbcc_ee_data_value(data); + pbcc_subscribe_ee_t* subscribe_ee = context->pb->core.subscribe_ee; + invocation = pbcc_ee_invocation_alloc( + SUBSCRIBE_EE_INVOCATION_EMIT_STATUS, + (pbcc_ee_effect_function_t)pbcc_subscribe_ee_emit_status_effect, + data, + false); + + /** Update latest Subscribe Event Engine subscription status. */ + pubnub_mutex_lock(subscribe_ee->mutw); + subscribe_ee->status = status; + pubnub_mutex_unlock(subscribe_ee->mutw); + } + + if (SUBSCRIBE_EE_EVENT_RECEIVE_SUCCESS == event->type) { + invocation = pbcc_ee_invocation_alloc( + SUBSCRIBE_EE_INVOCATION_EMIT_MESSAGE, + (pbcc_ee_effect_function_t)pbcc_subscribe_ee_emit_messages_effect, + data, + false); + } + + if (NULL != invocation) { + if (PBAR_OK != pbarray_add(invocations, invocation)) { + PUBNUB_LOG_ERROR("pbcc_receiving_state_transition_alloc: failed " + "to allocate memory for invocation entry\n"); + pbcc_ee_invocation_free(invocation); + pbarray_free(&invocations); + return NULL; + } + } + else if (SUBSCRIBE_EE_EVENT_RECEIVE_SUCCESS == event->type || + -1 != status) { + PUBNUB_LOG_ERROR("pbcc_receiving_state_transition_alloc: failed " + "to allocate memory for invocation\n"); + if (NULL != invocations) { pbarray_free(&invocations); } + return NULL; + } + + if (SUBSCRIBE_EE_STATE_NONE == target_state_type) { + PUBNUB_LOG_INFO("pbcc_receiving_state_transition_alloc: can't handle " + "transition for %d event type\n", + event->type); + } + + return pbcc_transition_alloc_(ee, + target_state_type, + data, + invocations); +} + +pbcc_ee_transition_t* pbcc_receiving_failed_state_transition_alloc( + pbcc_event_engine_t* ee, + const pbcc_ee_state_t* current_state, + const pbcc_ee_event_t* event) +{ + if (pbcc_ee_state_type(current_state) != + SUBSCRIBE_EE_STATE_RECEIVE_FAILED) { + return pbcc_transition_alloc_(ee, SUBSCRIBE_EE_STATE_NONE, NULL, NULL); + } + + pbcc_subscribe_ee_state target_state_type = SUBSCRIBE_EE_STATE_NONE; + + switch (event->type) { + case SUBSCRIBE_EE_EVENT_SUBSCRIPTION_CHANGED: + case SUBSCRIBE_EE_EVENT_SUBSCRIPTION_RESTORED: + case SUBSCRIBE_EE_EVENT_RECONNECT: + target_state_type = SUBSCRIBE_EE_STATE_HANDSHAKING; + break; + case SUBSCRIBE_EE_EVENT_UNSUBSCRIBE_ALL: + target_state_type = SUBSCRIBE_EE_STATE_UNSUBSCRIBED; + break; + default: + break; + } + + if (SUBSCRIBE_EE_STATE_NONE == target_state_type) { + PUBNUB_LOG_INFO("pbcc_receiving_failed_state_transition_alloc: can't " + "handle transition for %d event type\n", + event->type); + } + + return pbcc_transition_alloc_(ee, target_state_type, event->data, NULL); +} + +pbcc_ee_transition_t* pbcc_receiving_stopped_state_transition_alloc( + pbcc_event_engine_t* ee, + const pbcc_ee_state_t* current_state, + const pbcc_ee_event_t* event) +{ + if (pbcc_ee_state_type(current_state) != + SUBSCRIBE_EE_STATE_RECEIVE_STOPPED) { + return pbcc_transition_alloc_(ee, SUBSCRIBE_EE_STATE_NONE, NULL, NULL); + } + + pbcc_subscribe_ee_state target_state_type = SUBSCRIBE_EE_STATE_NONE; + + switch (event->type) { + case SUBSCRIBE_EE_EVENT_SUBSCRIPTION_CHANGED: + case SUBSCRIBE_EE_EVENT_SUBSCRIPTION_RESTORED: + target_state_type = SUBSCRIBE_EE_STATE_RECEIVE_STOPPED; + break; + case SUBSCRIBE_EE_EVENT_RECONNECT: + target_state_type = SUBSCRIBE_EE_STATE_HANDSHAKING; + break; + case SUBSCRIBE_EE_EVENT_UNSUBSCRIBE_ALL: + target_state_type = SUBSCRIBE_EE_STATE_UNSUBSCRIBED; + break; + default: + break; + } + + if (SUBSCRIBE_EE_STATE_NONE == target_state_type) { + PUBNUB_LOG_INFO("pbcc_receiving_stopped_state_transition_alloc: can't " + "handle transition for %d event type\n", + event->type); + } + + return pbcc_transition_alloc_(ee, target_state_type, event->data, NULL); +} + +pbcc_ee_transition_t* pbcc_transition_alloc_( + pbcc_event_engine_t* ee, + const pbcc_subscribe_ee_state target_state_type, + pbcc_ee_data_t* context, + pbarray_t* invocations) +{ + pbcc_ee_state_t* target_state = NULL; + pbcc_ee_data_t* state_context = NULL; + + if (NULL != context && SUBSCRIBE_EE_STATE_NONE != target_state_type && + SUBSCRIBE_EE_STATE_UNSUBSCRIBED != target_state_type) { + state_context = context; + } + + switch (target_state_type) { + case SUBSCRIBE_EE_STATE_UNSUBSCRIBED: + target_state = pbcc_unsubscribed_state_alloc(); + break; + case SUBSCRIBE_EE_STATE_HANDSHAKING: + target_state = pbcc_handshaking_state_alloc(ee, state_context); + break; + case SUBSCRIBE_EE_STATE_HANDSHAKE_FAILED: + target_state = pbcc_handshake_failed_state_alloc(state_context); + break; + case SUBSCRIBE_EE_STATE_HANDSHAKE_STOPPED: + target_state = pbcc_handshake_stopped_state_alloc(state_context); + break; + case SUBSCRIBE_EE_STATE_RECEIVING: + target_state = pbcc_receiving_state_alloc(ee, state_context); + break; + case SUBSCRIBE_EE_STATE_RECEIVE_FAILED: + target_state = pbcc_receive_failed_state_alloc(state_context); + break; + case SUBSCRIBE_EE_STATE_RECEIVE_STOPPED: + target_state = pbcc_receive_stopped_state_alloc(state_context); + break; + default: + PUBNUB_LOG_ERROR("pbcc_transition_alloc: unknown target state type\n"); + break; + } + + if (NULL == target_state && SUBSCRIBE_EE_STATE_NONE != target_state_type) { + PUBNUB_LOG_ERROR("pbcc_transition_alloc: failed to allocate memory for " + "state object\n"); + return NULL; + } + + pbcc_ee_transition_t* transition = pbcc_ee_transition_alloc( + ee, + target_state, + invocations); + if (NULL == transition) { + PUBNUB_LOG_ERROR("pbcc_transition_alloc: failed to allocate memory for " + "transition object\n"); + if (NULL != target_state) { pbcc_ee_state_free(&target_state); } + } + + return transition; +} \ No newline at end of file diff --git a/core/pbcc_subscribe_event_engine_transitions.h b/core/pbcc_subscribe_event_engine_transitions.h new file mode 100644 index 00000000..c11ef207 --- /dev/null +++ b/core/pbcc_subscribe_event_engine_transitions.h @@ -0,0 +1,148 @@ +/* -*- c-file-style:"stroustrup"; indent-tabs-mode: nil -*- */ +#ifndef PBCC_SUBSCRIBE_EVENT_ENGINE_TRANSITIONS_H +#define PBCC_SUBSCRIBE_EVENT_ENGINE_TRANSITIONS_H +#if PUBNUB_USE_SUBSCRIBE_EVENT_ENGINE +#if !PUBNUB_USE_SUBSCRIBE_V2 +#error Subscribe event engine requires subscribe v2 API, so you must define PUBNUB_USE_SUBSCRIBE_V2=1 +#endif // #if !PUBNUB_USE_SUBSCRIBE_V2 + + +/** + * @file pbcc_subscribe_event_engine_transitions.h + * @brief Subscribe Event Engine state transitions. + */ + +#include "core/pbcc_event_engine.h" + + +// ---------------------------------------------- +// Functions +// ---------------------------------------------- + +/** + * @brief Create transition from `Unsubscribed` state basing on received + * `event`. + * + * @param ee Pointer to the Event Engine which requested for + * transition details. + * @param current_state Pointer to the current Event Engine state from which + * transition should be decided. + * @param event Pointer to the Event Engine state changing event with + * information required for transition. + * @return Pointer to the state transition object or `NULL` in case of + * insufficient memory error. + */ +pbcc_ee_transition_t* pbcc_unsubscribed_state_transition_alloc( + pbcc_event_engine_t* ee, + const pbcc_ee_state_t* current_state, + const pbcc_ee_event_t* event); + +/** + * @brief Create transition from `Handshaking` state basing on received `event`. + * + * @param ee Pointer to the Event Engine which requested for + * transition details. + * @param current_state Pointer to the current Event Engine state from which + * transition should be decided. + * @param event Pointer to the Event Engine state changing event with + * information required for transition. + * @return Pointer to the state transition object or `NULL` in case of + * insufficient memory error. + */ +pbcc_ee_transition_t* pbcc_handshaking_state_transition_alloc( + pbcc_event_engine_t* ee, + const pbcc_ee_state_t* current_state, + const pbcc_ee_event_t* event); + +/** + * @brief Create transition from `Handshake Failed` state basing on received + * `event`. + * + * @param ee Pointer to the Event Engine which requested for + * transition details. + * @param current_state Pointer to the current Event Engine state from which + * transition should be decided. + * @param event Pointer to the Event Engine state changing event with + * information required for transition. + * @return Pointer to the state transition object or `NULL` in case of + * insufficient memory error. + */ +pbcc_ee_transition_t* pbcc_handshake_failed_state_transition_alloc( + pbcc_event_engine_t* ee, + const pbcc_ee_state_t* current_state, + const pbcc_ee_event_t* event); + +/** + * @brief Create transition from `Handshake Stopped` state basing on received + * `event`. + * + * @param ee Pointer to the Event Engine which requested for + * transition details. + * @param current_state Pointer to the current Event Engine state from which + * transition should be decided. + * @param event Pointer to the Event Engine state changing event with + * information required for transition. + * @return Pointer to the state transition object or `NULL` in case of + * insufficient memory error. + */ +pbcc_ee_transition_t* pbcc_handshake_stopped_state_transition_alloc( + pbcc_event_engine_t* ee, + const pbcc_ee_state_t* current_state, + const pbcc_ee_event_t* event); + +/** + * @brief Create transition from `Receiving` state basing on received `event`. + * + * @param ee Pointer to the Event Engine which requested for + * transition details. + * @param current_state Pointer to the current Event Engine state from which + * transition should be decided. + * @param event Pointer to the Event Engine state changing event with + * information required for transition. + * @return Pointer to the state transition object or `NULL` in case of + * insufficient memory error. + */ +pbcc_ee_transition_t* pbcc_receiving_state_transition_alloc( + pbcc_event_engine_t* ee, + const pbcc_ee_state_t* current_state, + const pbcc_ee_event_t* event); + +/** + * @brief Create transition from `Receiving Failed` state basing on received + * `event`. + * + * @param ee Pointer to the Event Engine which requested for + * transition details. + * @param current_state Pointer to the current Event Engine state from which + * transition should be decided. + * @param event Pointer to the Event Engine state changing event with + * information required for transition. + * @return Pointer to the state transition object or `NULL` in case of + * insufficient memory error. + */ +pbcc_ee_transition_t* pbcc_receiving_failed_state_transition_alloc( + pbcc_event_engine_t* ee, + const pbcc_ee_state_t* current_state, + const pbcc_ee_event_t* event); + +/** + * @brief Create transition from `Receiving Stopped` state basing on received + * `event`. + * + * @param ee Pointer to the Event Engine which requested for + * transition details. + * @param current_state Pointer to the current Event Engine state from which + * transition should be decided. + * @param event Pointer to the Event Engine state changing event with + * information required for transition. + * @return Pointer to the state transition object or `NULL` in case of + * insufficient memory error. + */ +pbcc_ee_transition_t* pbcc_receiving_stopped_state_transition_alloc( + pbcc_event_engine_t* ee, + const pbcc_ee_state_t* current_state, + const pbcc_ee_event_t* event); +#else // #if PUBNUB_USE_SUBSCRIBE_EVENT_ENGINE +#error To use subscribe event engine API you must define PUBNUB_USE_SUBSCRIBE_EVENT_ENGINE=1 +#endif // #if PUBNUB_USE_SUBSCRIBE_EVENT_ENGINE +#endif //PBCC_SUBSCRIBE_EVENT_ENGINE_TRANSITIONS_H diff --git a/core/pbcc_subscribe_event_engine_types.h b/core/pbcc_subscribe_event_engine_types.h new file mode 100644 index 00000000..e6f363c6 --- /dev/null +++ b/core/pbcc_subscribe_event_engine_types.h @@ -0,0 +1,192 @@ +/* -*- c-file-style:"stroustrup"; indent-tabs-mode: nil -*- */ +#ifndef PBCC_SUBSCRIBE_EVENT_ENGINE_TYPES_H +#define PBCC_SUBSCRIBE_EVENT_ENGINE_TYPES_H + + +/** + * @file pbcc_subscribe_event_engine_types.h + * @brief Subscribe Event Engine shared types. + */ + +#include "core/pbcc_subscribe_event_listener.h" +#include "core/pbcc_event_engine.h" +#include "core/pubnub_mutex.h" +#include "lib/pbhash_set.h" + + +// ---------------------------------------------- +// Types +// ---------------------------------------------- + +/** Subscribe Event Engine events. */ +typedef enum { + /** Subscription list of channels / groups change event. */ + SUBSCRIBE_EE_EVENT_SUBSCRIPTION_CHANGED, + /** Subscription restore / catchup event. */ + SUBSCRIBE_EE_EVENT_SUBSCRIPTION_RESTORED, + /** Initial subscription handshake success event. */ + SUBSCRIBE_EE_EVENT_HANDSHAKE_SUCCESS, + /** Initial subscription handshake failure event. */ + SUBSCRIBE_EE_EVENT_HANDSHAKE_FAILURE, + /** Real-time updates receive success event. */ + SUBSCRIBE_EE_EVENT_RECEIVE_SUCCESS, + /** Real-time updates receive failure event. */ + SUBSCRIBE_EE_EVENT_RECEIVE_FAILURE, + /** Disconnect from real-time updates event. */ + SUBSCRIBE_EE_EVENT_DISCONNECT, + /** Reconnect for real-time updates event. */ + SUBSCRIBE_EE_EVENT_RECONNECT, + /** Unsubscribe from all channel / groups. */ + SUBSCRIBE_EE_EVENT_UNSUBSCRIBE_ALL, +} pbcc_subscribe_ee_event; + +/** Subscribe Event Engine states. */ +typedef enum { + /** + * @brief Unknown state. + * + * Placeholder state type without actual implementation which won't be + * treated as insufficient memory error. + */ + SUBSCRIBE_EE_STATE_NONE, + /** Inactive Subscribe Event Engine state. */ + SUBSCRIBE_EE_STATE_UNSUBSCRIBED, + /** Initial subscription state. */ + SUBSCRIBE_EE_STATE_HANDSHAKING, + /** Initial subscription failed state. */ + SUBSCRIBE_EE_STATE_HANDSHAKE_FAILED, + /** Initial subscription stopped state. */ + SUBSCRIBE_EE_STATE_HANDSHAKE_STOPPED, + /** Real-time updates receiving state. */ + SUBSCRIBE_EE_STATE_RECEIVING, + /** Real-time updates receive failed state. */ + SUBSCRIBE_EE_STATE_RECEIVE_FAILED, + /** Real-time updates receive stopped state. */ + SUBSCRIBE_EE_STATE_RECEIVE_STOPPED, +} pbcc_subscribe_ee_state; + +/** Subscribe Event Engine events. */ +typedef enum { + /** Subscription list of channels / groups handshake invocation. */ + SUBSCRIBE_EE_INVOCATION_HANDSHAKE, + /** Receive real-time updates for list of channels / groups invocation. */ + SUBSCRIBE_EE_INVOCATION_RECEIVE, + /** Emit subscription change status invocation. */ + SUBSCRIBE_EE_INVOCATION_EMIT_STATUS, + /** Emit real-time update invocation. */ + SUBSCRIBE_EE_INVOCATION_EMIT_MESSAGE, + /** Cancel subscription invocation. */ + SUBSCRIBE_EE_INVOCATION_CANCEL, +} pbcc_subscribe_ee_invocation; + +/** Subscribe event engine structure. */ +struct pbcc_subscribe_ee { + /** + * @brief Pointer to the set of subscribable objects which should be used in + * subscription loop. + * + * \b Important: Array allocated with `pubnub_subscribable_free_` elements + * destructor. + */ + pbhash_set_t* subscribables; + /** + * @brief Pointer to the list of active subscription sets. + * + * List of subscription sets which should be used in subscription loop and + * listen for real-time updates. + * + * \b Important: Array allocated with `pubnub_subscription_set_free_` + * elements destructor. + */ + pbarray_t* subscription_sets; + /** + * @brief List of active subscriptions. + * + * List of subscriptions which should be used in subscription loop and + * listen for real-time updates. + * + * \b Important: Array allocated with `pubnub_subscription_free_` + * elements destructor. + */ + pbarray_t* subscriptions; + /** + * @brief How long (in seconds) the server will consider the client alive + * for presence. + */ + unsigned heartbeat; + /** Pointer to the real-time updates filtering expression. */ + char* filter_expr; + /** Recent subscription status. */ + pubnub_subscription_status status; + /** Pointer to the Subscribe Event Listener. */ + pbcc_event_listener_t* event_listener; + /** + * @brief Pointer to the active subscription cancel invocation. + * + * PubNub context may need some time to complete cancellation operation and + * report to the callback + */ + pbcc_ee_invocation_t* cancel_invocation; + /** + * @brief Pointer to the list of channels to leave. + * + * @note Because of PubNub specific with single request at a time we need to + * wait when cancel invocation will complete to be able to send + * `leave` request for channels. + */ + pbarray_t* leave_channels; + /** + * @brief Pointer to the list of channel groups to leave. + * + * @note Because of PubNub specific with single request at a time we need to + * wait when cancel invocation will complete to be able to send + * `leave` request for channel groups. + */ + pbarray_t* leave_channel_groups; + /** Type of transaction which is currently in progress. */ + enum pubnub_trans current_transaction; + /** + * @brief Pointer to the Event Engine which handles all states and + * transitions on events. + */ + pbcc_event_engine_t* ee; + /** + * @brief Pointer to the PubNub context, which should be used for effects + * execution by Subscribe Event Engine effects dispatcher. + */ + pubnub_t* pb; + /** Shared resources access lock. */ + pubnub_mutex_t mutw; +}; + +/** + * @brief Events and state context object. + * + * Context object contains information related required for proper + * Subscribe Event Engine operation. States and events contain event engine + * context 'snapshot' at the moment when they have been created. + */ +typedef struct { + /** + * @brief Pointer to the comma-separated channels which are or will be used + * in subscription loop. + */ + pbcc_ee_data_t* channels; + /** + * @brief Pointer to the comma-separated channel groups which are or will be + * used in subscription loop. + */ + pbcc_ee_data_t* channel_groups; + /** Subscription cursor which is or will be used in subscription loop. */ + pubnub_subscribe_cursor_t cursor; + /** Previous request failure reason. */ + enum pubnub_res reason; + /** Whether subscription requires heartbeat to be sent before subscribe. */ + bool send_heartbeat; + /** + * @brief Pointer to the PubNub context, which should be used for effects + * execution by Subscribe Event Engine effects dispatcher. + */ + pubnub_t* pb; +} pbcc_subscribe_ee_context_t; +#endif //PBCC_SUBSCRIBE_EVENT_ENGINE_TYPES_H diff --git a/core/pbcc_subscribe_event_listener.c b/core/pbcc_subscribe_event_listener.c new file mode 100644 index 00000000..30d23e34 --- /dev/null +++ b/core/pbcc_subscribe_event_listener.c @@ -0,0 +1,681 @@ +/* -*- c-file-style:"stroustrup"; indent-tabs-mode: nil -*- */ +#include "pbcc_subscribe_event_listener.h" + +#include + +#include "core/pbcc_memory_utils.h" +#include "core/pubnub_mutex.h" +#include "lib/pbref_counter.h" +#include "core/pubnub_log.h" +#include "pubnub_internal.h" +#include "lib/pbhash_set.h" +#include "lib/pbarray.h" + + +// ---------------------------------------------- +// Constants +// ---------------------------------------------- + +/** How many listener objects `pbcc_object_listener_t` can hold by default. */ +#define LISTENERS_LENGTH 10 + + +// ---------------------------------------------- +// Types +// ---------------------------------------------- + +/** + * @brief Channel / channel group listener definition. + * + * The object allows grouping listeners for the same subscribable entity and + * calling them when update real-time arrives for the entity. + */ +typedef struct { + /** + * @brief Pointer to the byte string with the name of the channel / group + * from which listener expected to process updates. + */ + char* name; + /** + * @brief Pointer to the array with real-time update listeners. + * + * @see pbcc_listener_t + */ + pbarray_t* listeners; +} pbcc_object_listener_t; + +/** + * @brief Listener definition. + * + * Object listeners entry object with information about the source of real-time + * updates and callback callback to use. + */ +typedef struct { + /** + * @brief Pointer to the subscription object which has been used to register + * listener. + * + * @note Value can be `NULL` if listener is stored for PubNub context. + * @note Value needed only when subscription object listener removed from + * the list to better identify exact listener. + * + * @see pubnub_subscription_t + * @see pubnub_subscription_set_t + */ + const void* subscription_object; + /** Type of real-time update for which listener will be called. */ + pubnub_subscribe_listener_type type; + /** Real-time update handling listener function. */ + pubnub_subscribe_message_callback_t callback; + /** Object references counter. */ + pbref_counter_t* counter; +} pbcc_listener_t; + +/** Event Listener definition. */ +struct pbcc_event_listener { + /** Pointer to the array with subscription change listeners. */ + pbarray_t* global_status; + /** Pointer to the array with PubNub context listeners (global). */ + pbarray_t* global_events; + /** + * @brief Pointer to the hash set with listeners mapped to the actual + * channel names. + * + * @see pbcc_object_listener_t + */ + pbhash_set_t* listeners; + /** + * @brief Pointer to the PubNub context, which should be used with + * registered event listener functions. + */ + const pubnub_t* pb; + /** Shared resources access lock. */ + pubnub_mutex_t mutw; +}; + + +// ---------------------------------------------- +// Function prototypes +// ---------------------------------------------- + +/** + * @brief Create object-scoped list of real-time update listeners. + * + * @param name Pointer to the byte string with name of object for which update + * listeners will be registered. + * @return Pointer to the ready to use updates listener or `NULL` in case of + * insufficient memory error. The returned pointer must be passed to the + * `_pbcc_object_listener_free` to avoid a memory leak. + */ +static pbcc_object_listener_t* pbcc_object_listener_alloc_(char* name); + +/** + * @brief Add / register object's listener. + * + * @param event_listener Pointer to the event listener which maintain objects' + * real-time update listeners. + * @param name Pointer to the subscription name with which `listener` + * should be associated. + * @param listener Pointer to the real-time update listener, which should + * be used for new updates. + * @return Result of the listener addition. + */ +static enum pubnub_res pbcc_add_object_listener_( + const pbcc_event_listener_t* event_listener, + char* name, + pbcc_listener_t* listener); + +/** +* @brief Remove / unregister real-time update listener. + * + * @param event_listener Pointer to the event listener which maintain objects' + * real-time update listeners. + * @param name Pointer to the subscription name with which `listener` + * was associated. + * @param listener Pointer to the real-time update listener, which + * shouldn't be used for updates anymore. + * @return Result of the listener removal. + */ +static enum pubnub_res pbcc_remove_object_listener_( + const pbcc_event_listener_t* event_listener, + char* name, + const pbcc_listener_t* listener); + +/** + * @brief Clean up resources used by updates listener object. + * + * @param listener Pointer to the updates listener, which should free up + * resources. + */ +static void pbcc_object_listener_free_(pbcc_object_listener_t* listener); + +/** + * @brief Create real-time events listener. + * + * @param subscription Pointer to the subscription object which has been used to + * register listener. + * @param type Type of real-time update for which listener will be + * called. + * @param callback Real-time update handling listener function. + * @return Pointer to the ready to use real-time events listener or `NULL` in + * case of insufficient memory error. The returned pointer must be + * passed to the `_pbcc_listener_free` to avoid a memory leak. + */ +static pbcc_listener_t* pbcc_listener_alloc_( + const void* subscription, + pubnub_subscribe_listener_type type, + pubnub_subscribe_message_callback_t callback); + +/** + * @brief Add / register real-time update listener. + * + * @param listeners Pointer to the list of real-time update listeners. + * @param listener Pointer to the real-time update listener, which should be + * used for new updates. + * @return Result of the listener addition. + */ +static enum pubnub_res pbcc_add_listener_( + pbarray_t* listeners, + pbcc_listener_t* listener); + +/** + * @brief Remove / unregister real-time update listener. + * + * @param listeners Pointer to the list of real-time update listeners. + * @param listener Pointer to the real-time update listener, which shouldn't be + * used for updates anymore. + * @return Result of the listener removal. + */ +static enum pubnub_res pbcc_remove_listener_( + pbarray_t* listeners, + const pbcc_listener_t* listener); + +/** + * @brief Clean up resources used by the real-time updates listener object. + * + * @param listener Pointer to the object real-time events listeners, which + * should free up resources. + */ +static void pbcc_listener_free_(pbcc_listener_t* listener); + +/** + * @brief Helper function to notify listeners from the list. + * + * Helper let iterate over the list of listeners and call them if the message + * type corresponds to expected one or this is a global listener. + * + * @param listener Pointer to the Event Listener, which contains provided list + * of event listeners. + * @param listeners Pointer to the array of event registered listeners which can + * be notified about new `message`. + * @param message Received message which should be delivered to the listeners. + */ +static void pbcc_event_listener_emit_message_( + const pbcc_event_listener_t* listener, + pbarray_t* listeners, + struct pubnub_v2_message message); + +/** + * @brief Identify type of events source from the message type. + * + * Event Listener uses this information to identify listeners which explicitly + * added to track specific event type. + * + * @param type Received message type specified by PubNub service. + * @return Subscription event source type from message type. + */ +static pubnub_subscribe_listener_type pbcc_message_type_to_listener_type_( + enum pubnub_message_type type); + +/** + * @brief Initialize and assign array. + * + * @param array Pointer to the address of the array which should be + * initialized. + * @param free_fn Array element destruction function. Can be set to `NULL` to + * keep elements after they removed from array or whole array is + * freed. + * @return Pointer to the initialized array or `NULL` in case of insufficient + * memory error. The returned pointer must be passed to the + * `pbarray_free` to avoid a memory leak. + */ +static pbarray_t* pbcc_initialize_array_( + pbarray_t** array, + pbarray_element_free free_fn); + + +// ---------------------------------------------- +// Functions +// ---------------------------------------------- + +pbcc_event_listener_t* pbcc_event_listener_alloc(const pubnub_t* pb) +{ + PBCC_ALLOCATE_TYPE(listener, pbcc_event_listener_t, true, NULL); + pubnub_mutex_init(listener->mutw); + listener->global_events = NULL; + listener->global_status = NULL; + listener->listeners = NULL; + listener->pb = pb; + + return listener; +} + +enum pubnub_res pbcc_event_listener_add_status_listener( + pbcc_event_listener_t* listener, + const pubnub_subscribe_status_callback_t cb) +{ + if (NULL == listener || NULL == cb) { return PNR_INVALID_PARAMETERS; } + + pubnub_mutex_lock(listener->mutw); + /** Check whether listeners array should be created on demand or not. */ + if (NULL == pbcc_initialize_array_(&listener->global_status, NULL)) { + pubnub_mutex_unlock(listener->mutw); + return PNR_OUT_OF_MEMORY; + } + + const pbarray_res result = pbarray_add(listener->global_status, cb); + pubnub_mutex_unlock(listener->mutw); + + return PBAR_OUT_OF_MEMORY != result ? PNR_OK : PNR_OUT_OF_MEMORY; +} + +enum pubnub_res pbcc_event_listener_remove_status_listener( + pbcc_event_listener_t* listener, + pubnub_subscribe_status_callback_t cb) +{ + if (NULL == listener || NULL == cb) { return PNR_INVALID_PARAMETERS; } + + pubnub_mutex_lock(listener->mutw); + if (NULL == listener->global_status) { + pubnub_mutex_unlock(listener->mutw); + return PNR_OK; + } + + pbarray_remove(listener->global_status, (void**)&cb, true); + pubnub_mutex_unlock(listener->mutw); + + return PNR_OK; +} + +enum pubnub_res pbcc_event_listener_add_message_listener( + pbcc_event_listener_t* listener, + const pubnub_subscribe_listener_type type, + const pubnub_subscribe_message_callback_t cb) +{ + if (NULL == listener || NULL == cb) { return PNR_INVALID_PARAMETERS; } + + pubnub_mutex_lock(listener->mutw); + /** Check whether listeners array should be created on demand or not. */ + if (NULL == pbcc_initialize_array_(&listener->global_events, + (pbarray_element_free) + pbcc_listener_free_)) { + pubnub_mutex_unlock(listener->mutw); + return PNR_OUT_OF_MEMORY; + } + + pbcc_listener_t* _listener = pbcc_listener_alloc_(NULL, type, cb); + if (NULL == _listener) { + pubnub_mutex_unlock(listener->mutw); + return PNR_OUT_OF_MEMORY; + } + + const enum pubnub_res result = pbcc_add_listener_( + listener->global_events, + _listener); + if (PNR_OK != result) { pbcc_listener_free_(_listener); } + pubnub_mutex_unlock(listener->mutw); + + return result; +} + +enum pubnub_res pbcc_event_listener_remove_message_listener( + pbcc_event_listener_t* listener, + const pubnub_subscribe_listener_type type, + const pubnub_subscribe_message_callback_t cb) +{ + if (NULL == listener || NULL == cb) + return PNR_INVALID_PARAMETERS; + + pubnub_mutex_lock(listener->mutw); + if (NULL == listener->global_events) { + pubnub_mutex_unlock(listener->mutw); + return PNR_OK; + } + + pbcc_listener_t* _listener = pbcc_listener_alloc_(NULL, type, cb); + if (NULL == _listener) { + pubnub_mutex_unlock(listener->mutw); + return PNR_OUT_OF_MEMORY; + } + + const enum pubnub_res rslt = pbcc_remove_listener_( + listener->global_events, + _listener); + + /** + * It is safe to release temporarily object which used only to match object. + */ + pbcc_listener_free_(_listener); + pubnub_mutex_unlock(listener->mutw); + + return rslt; +} + +enum pubnub_res pbcc_event_listener_add_subscription_object_listener( + pbcc_event_listener_t* listener, + const pubnub_subscribe_listener_type type, + pbarray_t* names, + const void* subscription, + const pubnub_subscribe_message_callback_t cb) +{ + if (NULL == listener || NULL == cb) { return PNR_INVALID_PARAMETERS; } + + pubnub_mutex_lock(listener->mutw); + /** Check whether object listeners hash set is set or not. */ + if (NULL == listener->listeners) { + listener->listeners = pbhash_set_alloc( + LISTENERS_LENGTH, + PBHASH_SET_CHAR_CONTENT_TYPE, + (pbhash_set_element_free)pbcc_object_listener_free_); + + if (NULL == listener->listeners) { + pubnub_mutex_unlock(listener->mutw); + return PNR_OUT_OF_MEMORY; + } + } + + pbcc_listener_t* _listener = pbcc_listener_alloc_(subscription, type, cb); + bool added = false; + if (NULL == _listener) { + pubnub_mutex_unlock(listener->mutw); + return PNR_OUT_OF_MEMORY; + } + + enum pubnub_res rslt = PNR_OK; + const size_t names_count = pbarray_count(names); + for (size_t i = 0; i < names_count; ++i) { + char* name = (char*)pbarray_element_at(names, i); + rslt = pbcc_add_object_listener_(listener, name, _listener); + if (PNR_OK == rslt) { added = true; } + else { break; } + } + + if (added && PNR_OK != rslt) { + /** Remove any added entries of the `listener` in case of failure. */ + for (size_t i = 0; i < names_count; ++i) { + char* name = (char*)pbarray_element_at(names, i); + pbcc_remove_object_listener_(listener, name, _listener); + } + } + /** Manual `free` required if arrays doesn't manage listener lifetime. */ + if (PNR_OK != rslt && !added) { pbcc_listener_free_(_listener); } + pubnub_mutex_unlock(listener->mutw); + + return rslt; +} + +enum pubnub_res pbcc_event_listener_remove_subscription_object_listener( + pbcc_event_listener_t* listener, + const pubnub_subscribe_listener_type type, + pbarray_t* names, + const void* subscription, + const pubnub_subscribe_message_callback_t cb) +{ + if (NULL == listener || NULL == cb) { return PNR_INVALID_PARAMETERS; } + + pubnub_mutex_lock(listener->mutw); + if (NULL == listener->global_events) { + pubnub_mutex_unlock(listener->mutw); + return PNR_OK; + } + + pbcc_listener_t* _listener = pbcc_listener_alloc_(subscription, type, cb); + if (NULL == _listener) { + pubnub_mutex_unlock(listener->mutw); + return PNR_OUT_OF_MEMORY; + } + + const size_t names_count = pbarray_count(names); + for (size_t i = 0; i < names_count; ++i) { + char* name = (char*)pbarray_element_at(names, i); + pbcc_remove_object_listener_(listener, name, _listener); + } + + /** + * It is safe to release temporarily object which used only to match object. + */ + pbcc_listener_free_(_listener); + pubnub_mutex_unlock(listener->mutw); + + return PNR_OK; +} + +void pbcc_event_listener_emit_status( + pbcc_event_listener_t* listener, + const pubnub_subscription_status status, + const enum pubnub_res reason, + const char* channels, + const char* channel_groups) +{ + if (NULL == listener) { return; } + + pubnub_mutex_lock(listener->mutw); + const pubnub_subscription_status_data_t data = + { reason, channels, channel_groups }; + const size_t status_count = pbarray_count(listener->global_status); + for (size_t i = 0; i < status_count; ++i) { + const pubnub_subscribe_status_callback_t cb = ( + pubnub_subscribe_status_callback_t) + pbarray_element_at(listener->global_status, i); + cb(listener->pb, status, data); + } + pubnub_mutex_unlock(listener->mutw); +} + +void pbcc_event_listener_emit_message( + pbcc_event_listener_t* listener, + const char* subscribable, + const struct pubnub_v2_message message) +{ + if (NULL == listener) { return; } + + pubnub_mutex_lock(listener->mutw); + // Notify global message listeners (if any has been registered). + if (NULL != listener->global_events) { + pbcc_event_listener_emit_message_(listener, + listener->global_events, + message); + } + + // Notify subscribable object message listeners (if any has been registered). + const pbcc_object_listener_t* object_listener = (pbcc_object_listener_t*) + pbhash_set_element(listener->listeners, subscribable); + if (NULL != object_listener) { + pbcc_event_listener_emit_message_(listener, + object_listener->listeners, + message); + } + pubnub_mutex_unlock(listener->mutw); +} + +void pbcc_event_listener_free(pbcc_event_listener_t** event_listener) +{ + if (NULL == event_listener || NULL == *event_listener) { return; } + + pubnub_mutex_lock((*event_listener)->mutw); + pbarray_free(&(*event_listener)->global_status); + pbarray_free(&(*event_listener)->global_events); + pbhash_set_free(&(*event_listener)->listeners); + pubnub_mutex_unlock((*event_listener)->mutw); + pubnub_mutex_destroy((*event_listener)->mutw); + free(*event_listener); + *event_listener = NULL; +} + +pbcc_object_listener_t* pbcc_object_listener_alloc_(char* name) +{ + PBCC_ALLOCATE_TYPE(updates, pbcc_object_listener_t, true, NULL); + updates->name = name; + updates->listeners = NULL; + pbcc_initialize_array_( + &updates->listeners, + (pbarray_element_free)pbcc_listener_free_); + + return updates; +} + +enum pubnub_res pbcc_add_object_listener_( + const pbcc_event_listener_t* event_listener, + char* name, + pbcc_listener_t* listener) +{ + // Try to retrieve previously created object-scoped listeners. + pbcc_object_listener_t* object_listener = (pbcc_object_listener_t*) + pbhash_set_element(event_listener->listeners, name); + if (NULL == object_listener) { + object_listener = pbcc_object_listener_alloc_(name); + if (NULL == object_listener) { return PNR_OUT_OF_MEMORY; } + + const pbhash_set_res rslt = pbhash_set_add( + event_listener->listeners, + name, + object_listener); + if (PBHSR_OUT_OF_MEMORY == rslt) { + pbcc_object_listener_free_(object_listener); + return PNR_OUT_OF_MEMORY; + } + } + + return pbcc_add_listener_(object_listener->listeners, listener); +} + +enum pubnub_res pbcc_remove_object_listener_( + const pbcc_event_listener_t* event_listener, + char* name, + const pbcc_listener_t* listener) +{ + // Try to retrieve previously created object-scoped listeners. + const pbcc_object_listener_t* object_listener = pbhash_set_element( + event_listener->listeners, + name); + if (NULL == object_listener) { return PNR_OK; } + + const enum pubnub_res rslt = pbcc_remove_listener_( + object_listener->listeners, + listener); + + if (PNR_OK == rslt && 0 == pbarray_count(object_listener->listeners)) + pbhash_set_remove(event_listener->listeners, (void**)&name, NULL); + + return rslt; +} + +void pbcc_object_listener_free_(pbcc_object_listener_t* listener) +{ + if (NULL == listener) { return; } + + if (NULL != listener->listeners) { pbarray_free(&listener->listeners); } + if (NULL != listener->name) { free(listener->name); } + free(listener); +} + +pbcc_listener_t* pbcc_listener_alloc_( + const void* subscription, + const pubnub_subscribe_listener_type type, + const pubnub_subscribe_message_callback_t callback) +{ + PBCC_ALLOCATE_TYPE(listener, pbcc_listener_t, true, NULL); + listener->counter = pbref_counter_alloc(); + listener->type = type; + listener->subscription_object = subscription; + listener->callback = callback; + + return listener; +} + +enum pubnub_res pbcc_add_listener_( + pbarray_t* listeners, + pbcc_listener_t* listener) +{ + const pbarray_res rslt = pbarray_add(listeners, listener); + if (PBAR_OK == rslt) { pbref_counter_increment(listener->counter); } + + return PBAR_OK == rslt ? PNR_OK : PNR_OUT_OF_MEMORY; +} + +enum pubnub_res pbcc_remove_listener_( + pbarray_t* listeners, + const pbcc_listener_t* listener) +{ + if (NULL == listeners) { return PNR_OK; } + if (NULL == listener) { return PNR_INVALID_PARAMETERS; } + + for (size_t i = 0; i < pbarray_count(listeners);) { + pbcc_listener_t* _listener = (pbcc_listener_t*) + pbarray_element_at(listeners, i); + + if (_listener->type == listener->type && + _listener->subscription_object == listener->subscription_object && + _listener->callback == listener->callback) { + pbref_counter_decrement(_listener->counter); + pbarray_remove(listeners, (void**)&_listener, true); + } + else { i++; } + } + + return PNR_OK; +} + +void pbcc_listener_free_(pbcc_listener_t* listener) +{ + if (NULL == listener) { return; } + if (0 == pbref_counter_free(listener->counter)) { free(listener); } +} + +void pbcc_event_listener_emit_message_( + const pbcc_event_listener_t* listener, + pbarray_t* listeners, + const struct pubnub_v2_message message) +{ + const pubnub_subscribe_listener_type type = + pbcc_message_type_to_listener_type_(message.message_type); + const size_t count = pbarray_count(listeners); + + for (size_t i = 0; i < count; ++i) { + pbcc_listener_t* _listener = (pbcc_listener_t*) + pbarray_element_at(listeners, i); + if (NULL == _listener->subscription_object || _listener->type == type) + _listener->callback(listener->pb, message); + } +} + +pubnub_subscribe_listener_type pbcc_message_type_to_listener_type_( + enum pubnub_message_type type) +{ + switch (type) { + case pbsbPublished: + return PBSL_LISTENER_ON_MESSAGE; + case pbsbSignal: + return PBSL_LISTENER_ON_SIGNAL; + case pbsbAction: + return PBSL_LISTENER_ON_MESSAGE_ACTION; + case pbsbObjects: + return PBSL_LISTENER_ON_OBJECTS; + case pbsbFiles: + return PBSL_LISTENER_ON_FILES; + } + + return PBSL_LISTENER_ON_MESSAGE; +} + +pbarray_t* pbcc_initialize_array_( + pbarray_t** array, + const pbarray_element_free free_fn) +{ + if (NULL != *array) { return *array; } + + return *array = pbarray_alloc(5, + PBARRAY_RESIZE_BALANCED, + PBARRAY_GENERIC_CONTENT_TYPE, + free_fn); +} \ No newline at end of file diff --git a/core/pbcc_subscribe_event_listener.h b/core/pbcc_subscribe_event_listener.h new file mode 100644 index 00000000..37971723 --- /dev/null +++ b/core/pbcc_subscribe_event_listener.h @@ -0,0 +1,201 @@ +/* -*- c-file-style:"stroustrup"; indent-tabs-mode: nil -*- */ +#ifndef PBCC_SUBSCRIBE_EVENT_LISTENER_H +#define PBCC_SUBSCRIBE_EVENT_LISTENER_H +#if !PUBNUB_USE_SUBSCRIBE_V2 +#error Subscribe event engine requires subscribe v2 API, so you must define PUBNUB_USE_SUBSCRIBE_V2=1 +#endif // #if !PUBNUB_USE_SUBSCRIBE_V2 +#ifndef PUBNUB_CALLBACK_API +#error Subscribe event engine requires callback based PubNub context, so you must define PUBNUB_CALLBACK_API +#endif // #ifndef PUBNUB_CALLBACK_API + + +/** + * @file pbcc_subscribe_event_listener.h + * @brief Subscribe Event Listener core implementation with base type + * definitions and functions. + */ + +#include "core/pubnub_subscribe_event_listener_types.h" +#include "core/pubnub_api_types.h" +#include "lib/pbarray.h" + + +// ---------------------------------------------- +// Types forwarding +// ---------------------------------------------- + +/** Event Listener definition. */ +typedef struct pbcc_event_listener pbcc_event_listener_t; + + +// ---------------------------------------------- +// Functions +// ---------------------------------------------- + +/** + * @brief Create Event Listener. + * + * @param pb Pointer to the PubNub context which will be passed into listener. + * @return Pointer to the ready to use Event Listener or `NULL` in case of + * insufficient memory error. The returned pointer must be passed to + * the `pbcc_event_listener_free` to avoid a memory leak. + */ +pbcc_event_listener_t* pbcc_event_listener_alloc(const pubnub_t* pb); + +/** + * @brief Add subscription status change listener. + * + * @param listener Pointer to the Event Listener which is used to track and call + * registered subscription status change listeners. + * @param cb Subscription status change handling listener function. + * @return Results of listener addition. + */ +enum pubnub_res pbcc_event_listener_add_status_listener( + pbcc_event_listener_t* listener, + pubnub_subscribe_status_callback_t cb); + +/** + * @brief Remove subscription status change listener. + * + * \b Warning: All occurrences of the same `listener` will be removed from the + * list. If multiple observers registered with the same listener function +* (same address) they all will stop receiving updates. + * + * @param listener Pointer to the Event Listener which should unregister + * subscription status change listener. + * @param cb Subscription status change handling listener function. + * @return Results of listener removal. + */ +enum pubnub_res pbcc_event_listener_remove_status_listener( + pbcc_event_listener_t* listener, + pubnub_subscribe_status_callback_t cb); + +/** + * @brief Add subscription real-time updates listener. + * + * PubNub context will receive real-time updates from all subscription and + * subscription sets. + * + * @param listener Pointer to the Event Listener which is used to track and call + * registered real-time updates listeners. + * @param type Type of real-time update for which listener will be called. + * @param cb Real-time update handling listener function. + * @return Results of listener addition. + */ +enum pubnub_res pbcc_event_listener_add_message_listener( + pbcc_event_listener_t* listener, + pubnub_subscribe_listener_type type, + pubnub_subscribe_message_callback_t cb); + +/** + * @brief Remove subscription real-time updates listener. + * + * \b Warning: All occurrences of the same `listener` will be removed from the + * list. If multiple observers registered with the same listener function +* (same address) they all will stop receiving updates. + * + * @param listener Pointer to the Event Listener which should unregister +* registered real-time updates listeners. + * @param type Type of real-time update for which listener won't be called + * anymore. + * @param cb Real-time update handling listener function. + * @return Results of listener removal. + */ +enum pubnub_res pbcc_event_listener_remove_message_listener( + pbcc_event_listener_t* listener, + pubnub_subscribe_listener_type type, + pubnub_subscribe_message_callback_t cb); + +/** + * @brief Add subscription real-time updates listener. + * + * @param listener Pointer to the Event Listener which is used to track and + * call registered real-time updates listeners. + * @param type Type of real-time update for which listener will be + * called. + * @param names Pointer to the list of subscription names with which + * `callback` should be associated. + * @param subscription Pointer to the subscription object for which 'callback' + * for specific real-time updates 'type' will be registered. + * @param cb Real-time update handling listener function. + * @return Result of listener addition. + */ +enum pubnub_res pbcc_event_listener_add_subscription_object_listener( + pbcc_event_listener_t* listener, + pubnub_subscribe_listener_type type, + pbarray_t* names, + const void* subscription, + pubnub_subscribe_message_callback_t cb); + +/** + * @brief Remove subscription real-time updates listener. + * + * \b Warning: All occurrences of the same `listener` for specific `type` will + * be removed from the list. If multiple observers registered with the same + * listener function (same address) and `type` they all will stop receiving + * updates. + * + * @param listener Pointer to the Event Listener which should unregister + * registered real-time updates listeners. + * @param type Type of real-time update for which listener won't be + * called anymore. + * @param names Pointer to the list of subscription names with which + * `callback` was associated. + * @param subscription Subscription object for which `callback` for specific + * real-time updates 'type' will be removed. + * @param cb Real-time update handling listener function. + * @return Results of listener removal. + */ +enum pubnub_res pbcc_event_listener_remove_subscription_object_listener( + pbcc_event_listener_t* listener, + pubnub_subscribe_listener_type type, + pbarray_t* names, + const void* subscription, + pubnub_subscribe_message_callback_t cb); + +/** + * @brief Notify subscription status listeners about status change. + * + * @param listener Pointer to the Event Listener which contains list of + * listeners for subscription status change event. + * @param status New subscription status which should be sent the the + * listeners. + * @param reason In case of `PNSS_SUBSCRIPTION_STATUS_CONNECTION_ERROR` + * and `PNSS_SUBSCRIPTION_STATUS_DISCONNECTED_UNEXPECTEDLY` + * may contain additional information about reasons of + * failure. + * @param channels Byte string with comma-separated / `NULL` channel + * identifiers which have been used with recent operation. + * @param channel_groups Byte string with comma-separated / `NULL` channel group + * identifiers which have been used with recent operation. + */ +void pbcc_event_listener_emit_status( + pbcc_event_listener_t* listener, + pubnub_subscription_status status, + enum pubnub_res reason, + const char* channels, + const char* channel_groups); + +/** + * @brief Notify listeners about new real-time update / message. + * + * @param listener Pointer to the Event Listener which contains list of + * listeners for subscription real-time update / message + * event. + * @param subscribable Pointer to the subscrbable entity for which `message` has + * been received. + * @param message Received message which should be delivered to the listeners. + */ +void pbcc_event_listener_emit_message( + pbcc_event_listener_t* listener, + const char* subscribable, + struct pubnub_v2_message message); + +/** + * @brief Clean up resources used by the Event Listener object. + * + * @param event_listener Pointer to the Event Listener, which should free up + * used resources. + */ +void pbcc_event_listener_free(pbcc_event_listener_t** event_listener); +#endif // #ifndef PBCC_SUBSCRIBE_EVENT_LISTENER_H diff --git a/core/pbcc_subscribe_v2.c b/core/pbcc_subscribe_v2.c index 90b2e36f..e1c50650 100644 --- a/core/pbcc_subscribe_v2.c +++ b/core/pbcc_subscribe_v2.c @@ -49,10 +49,10 @@ enum pubnub_res pbcc_subscribe_v2_prep(struct pbcc_context* p, p->timetoken[1] = '\0'; tr = NULL; } - else { + else if (p->region > 0) { snprintf(region_str, sizeof region_str, "%d", p->region); tr = region_str; - } + } else { tr = NULL; } p->http_content_len = 0; p->msg_ofs = p->msg_end = 0; @@ -302,7 +302,7 @@ struct pubnub_v2_message pbcc_get_msg_v2(struct pbcc_context* p) else { rslt.message_type = pbsbPublished; } - + jpresult = pbjson_get_object_value(&el, "p", &found); if (jonmpOK == jpresult) { struct pbjson_elem titel; @@ -328,8 +328,8 @@ struct pubnub_v2_message pbcc_get_msg_v2(struct pbcc_context* p) } if (jonmpOK == pbjson_get_object_value(&el, "b", &found)) { - rslt.match_or_group.ptr = (char*)found.start; - rslt.match_or_group.size = found.end - found.start; + rslt.match_or_group.ptr = (char*)found.start + 1; + rslt.match_or_group.size = found.end - found.start - 2; } if (jonmpOK == pbjson_get_object_value(&el, "u", &found)) { diff --git a/core/pbpal_ntf_callback_admin.c b/core/pbpal_ntf_callback_admin.c index cf1c75b4..46a67e42 100644 --- a/core/pbpal_ntf_callback_admin.c +++ b/core/pbpal_ntf_callback_admin.c @@ -1,17 +1,45 @@ /* -*- c-file-style:"stroustrup"; indent-tabs-mode: nil -*- */ #include "pubnub_internal.h" +#if PUBNUB_USE_RETRY_CONFIGURATION +#include "core/pubnub_retry_configuration_internal.h" +#include "core/pubnub_pubsubapi.h" +#endif // #if PUBNUB_USE_RETRY_CONFIGURATION + #include "pbntf_trans_outcome_common.h" #include "pubnub_log.h" #include "pubnub_assert.h" + void pbntf_trans_outcome(pubnub_t* pb, enum pubnub_state state) { PBNTF_TRANS_OUTCOME_COMMON(pb, state); PUBNUB_ASSERT(pbnc_can_start_transaction(pb)); + pb->flags.sent_queries = 0; +#if PUBNUB_USE_RETRY_CONFIGURATION + if (NULL != pb->core.retry_configuration && + pubnub_retry_configuration_retryable_result_(pb)) { + uint16_t delay = pubnub_retry_configuration_delay_(pb); + + if (delay > 0) { + if (NULL == pb->core.retry_timer) + pb->core.retry_timer = pbcc_request_retry_timer_alloc(pb); + if (NULL != pb->core.retry_timer) { + pbcc_request_retry_timer_start(pb->core.retry_timer, delay); + return; + } + } + } + + /** There were no need to start retry timer, we can free it if exists. */ + if (NULL != pb->core.retry_timer) { + pb->core.http_retry_count = 0; + pbcc_request_retry_timer_free(&pb->core.retry_timer); + } +#endif // #if PUBNUB_USE_RETRY_CONFIGURATION if (pb->cb != NULL) { PUBNUB_LOG_TRACE("pbntf_trans_outcome(pb=%p) calling callback:\n" "pb->trans = %d, pb->core.last_result=%d, pb->user_data=%p\n", diff --git a/core/pubnub_api_types.h b/core/pubnub_api_types.h index 92f6e5c0..606ee660 100644 --- a/core/pubnub_api_types.h +++ b/core/pubnub_api_types.h @@ -65,6 +65,12 @@ enum pubnub_res { PNR_SUB_NO_TT_ERROR, /** No Region in the subscribe response */ PNR_SUB_NO_REG_ERROR, +#if PUBNUB_USE_SUBSCRIBE_EVENT_ENGINE + /** Subscription object already exists in subscription set */ + PNR_SUB_ALREADY_ADDED, + /** Subscription object not found in subscription set */ + PNR_SUB_NOT_FOUND, +#endif /** Request cancelled by user. */ PNR_CANCELLED, /** Transaction started. Await the outcome. */ diff --git a/core/pubnub_ccore_pubsub.c b/core/pubnub_ccore_pubsub.c index 4ff54b03..6822467e 100644 --- a/core/pubnub_ccore_pubsub.c +++ b/core/pubnub_ccore_pubsub.c @@ -15,6 +15,10 @@ #include "pubnub_crypto.h" #endif +#if PUBNUB_USE_SUBSCRIBE_EVENT_ENGINE +#include "core/pbcc_subscribe_event_engine.h" +#endif // PUBNUB_USE_SUBSCRIBE_EVENT_ENGINE + #include #include @@ -42,7 +46,11 @@ void pbcc_init(struct pbcc_context* p, const char* publish_key, const char* subs #endif /* PUBNUB_DYNAMIC_REPLY_BUFFER */ p->message_to_send = NULL; p->state = NULL; - +#if PUBNUB_USE_RETRY_CONFIGURATION + p->retry_configuration = NULL; + p->http_retry_count = 0; + p->retry_timer = NULL; +#endif // #if PUBNUB_USE_RETRY_CONFIGURATION #if PUBNUB_USE_GZIP_COMPRESSION p->gzip_msg_len = 0; #endif @@ -69,12 +77,28 @@ void pbcc_deinit(struct pbcc_context* p) } #endif /* PUBNUB_RECEIVE_GZIP_RESPONSE */ #endif /* PUBNUB_DYNAMIC_REPLY_BUFFER */ +#if PUBNUB_USE_RETRY_CONFIGURATION + if (NULL != p->retry_configuration) + pubnub_retry_configuration_free(&p->retry_configuration); + p->http_retry_count = 0; + if (NULL != p->retry_timer) { + pbcc_request_retry_timer_stop(p->retry_timer); + pbcc_request_retry_timer_free(&p->retry_timer); + } +#endif // #if PUBNUB_USE_RETRY_CONFIGURATION #if PUBNUB_CRYPTO_API if (NULL != p->crypto_module) { free(p->crypto_module); p->crypto_module = NULL; } #endif /* PUBNUB_CRYPTO_API */ +#if PUBNUB_USE_SUBSCRIBE_EVENT_ENGINE + pbcc_subscribe_ee_free(&p->subscribe_ee); +#endif // PUBNUB_USE_SUBSCRIBE_EVENT_ENGINE +#if PUBNUB_USE_RETRY_CONFIGURATION + if (NULL != p->retry_configuration) + pubnub_retry_configuration_free(&p->retry_configuration); +#endif // #if PUBNUB_USE_RETRY_CONFIGURATION } diff --git a/core/pubnub_ccore_pubsub.h b/core/pubnub_ccore_pubsub.h index ba703650..1e03e6a8 100644 --- a/core/pubnub_ccore_pubsub.h +++ b/core/pubnub_ccore_pubsub.h @@ -2,6 +2,15 @@ #if !defined INC_PUBNUB_CCORE_PUBSUB #define INC_PUBNUB_CCORE_PUBSUB +#if PUBNUB_USE_SUBSCRIBE_EVENT_ENGINE +#include "pbcc_subscribe_event_engine.h" +#endif + +#if PUBNUB_USE_RETRY_CONFIGURATION +#include "core/pubnub_retry_configuration.h" +#include "core/pbcc_request_retry_timer.h" +#endif // #if PUBNUB_USE_RETRY_CONFIGURATION + #include "pubnub_config.h" #include "pubnub_api_types.h" #include "pubnub_generate_uuid.h" @@ -57,6 +66,11 @@ struct pbcc_context { int region; #endif +#if PUBNUB_USE_SUBSCRIBE_EVENT_ENGINE + // Event Engine which supports new subscription loop. + pbcc_subscribe_ee_t *subscribe_ee; +#endif + /** The result of the last Pubnub transaction */ enum pubnub_res last_result; @@ -119,6 +133,15 @@ struct pbcc_context { */ unsigned chan_ofs, chan_end; +#if PUBNUB_USE_RETRY_CONFIGURATION + /** Pointer to the configuration with failed request handling details. */ + pubnub_retry_configuration_t* retry_configuration; + /** Failed request retry timer. */ + pbcc_request_retry_timer_t* retry_timer; + /** Current number of retry attempts. */ + int http_retry_count; +#endif // #if PUBNUB_USE_RETRY_CONFIGURATION + #if PUBNUB_CRYPTO_API /** Secret key to use for encryption/decryption */ char const* secret_key; diff --git a/core/pubnub_coreapi.c b/core/pubnub_coreapi.c index 326c4d75..5eec3c42 100644 --- a/core/pubnub_coreapi.c +++ b/core/pubnub_coreapi.c @@ -81,7 +81,7 @@ static void check_if_default_channel_and_groups(pubnub_t* p, enum pubnub_res pubnub_leave(pubnub_t* p, const char* channel, const char* channel_group) { - enum pubnub_res rslt; + enum pubnub_res rslt = PNR_IN_PROGRESS; char const* prep_channel; char const* prep_channel_group; @@ -90,7 +90,7 @@ enum pubnub_res pubnub_leave(pubnub_t* p, const char* channel, const char* chann pubnub_mutex_lock(p->monitor); if (!pbnc_can_start_transaction(p)) { pubnub_mutex_unlock(p->monitor); - return PNR_IN_PROGRESS; + return rslt; } check_if_default_channel_and_groups(p, channel, diff --git a/core/pubnub_crypto.h b/core/pubnub_crypto.h index 7e702c73..a28fe7f7 100644 --- a/core/pubnub_crypto.h +++ b/core/pubnub_crypto.h @@ -1,8 +1,8 @@ /* -*- c-file-style:"stroustrup"; indent-tabs-mode: nil -*- */ -#include "lib/pb_deprecated.h" #if !defined INC_PUBNUB_CRYPTO #define INC_PUBNUB_CRYPTO - +#if PUBNUB_CRYPTO_API +#include "lib/pb_deprecated.h" #include "core/pubnub_api_types.h" #include "core/pubnub_memory_block.h" #include "core/pbcc_crypto.h" @@ -235,5 +235,5 @@ PUBNUB_EXTERN void pubnub_set_crypto_module(pubnub_t *pubnub, struct pubnub_cryp @return Pointer to the crypto provider used by the pubnub context. */ PUBNUB_EXTERN pubnub_crypto_provider_t *pubnub_get_crypto_module(pubnub_t *pubnub); - -#endif /* defined INC_PUBNUB_CRYPTO */ +#endif // #if PUBNUB_CRYPTO_API +#endif /* defined INC_PUBNUB_CRYPTO */ \ No newline at end of file diff --git a/core/pubnub_entities.c b/core/pubnub_entities.c new file mode 100644 index 00000000..dc1b283c --- /dev/null +++ b/core/pubnub_entities.c @@ -0,0 +1,153 @@ +/* -*- c-file-style:"stroustrup"; indent-tabs-mode: nil -*- */ +#include "pubnub_entities.h" + +#include + +#include "core/pubnub_entities_internal.h" +#include "core/pbcc_memory_utils.h" +#include "core/pubnub_assert.h" +#include "core/pubnub_log.h" +#include "pubnub_internal.h" +#include "lib/pbstrdup.h" + + +// ---------------------------------- +// Function prototypes +// ---------------------------------- + +/** + * @brief Create a specific PubNub entity. + * + * @param pb Pointer to the PubNub context, which will be used by the created + * entity to interact with PubNub REST API. + * @param id Pointer to the identifier which will be used with entity-specific + * REST API. + * @param type Specific PubNub entity type. + * @return Ready to use PubNub entity. The returned pointer must be passed to + * the `pubnub_entity_free` to avoid a memory leak. + */ +static pubnub_entity_t* create_entity_( + pubnub_t* pb, + const char* id, + pubnub_entity_type type); + +/** + * @brief Check whether the passed value is one of known PubNub entity types. + * + * @param entity Pointer to the value which should be checked to be PubNub + * entity. + * @return `true` if passed `entity` is one of PubNub entities. + */ +static bool is_pubnub_entity_(void* entity); + + +// ---------------------------------- +// Functions +// ---------------------------------- + +pubnub_channel_t* pubnub_channel_alloc( + pubnub_t* pb, + const char* name) +{ + return (pubnub_channel_t*)create_entity_(pb, name, PUBNUB_ENTITY_CHANNEL); +} + +pubnub_channel_group_t* pubnub_channel_group_alloc( + pubnub_t* pb, + const char* name) +{ + return (pubnub_channel_group_t*)create_entity_( + pb, + name, + PUBNUB_ENTITY_CHANNEL_GROUP); +} + +pubnub_channel_metadata_t* pubnub_channel_metadata_alloc( + pubnub_t* pb, + const char* id) +{ + return (pubnub_channel_metadata_t*)create_entity_( + pb, + id, + PUBNUB_ENTITY_CHANNEL_METADATA); +} + +pubnub_user_metadata_t* pubnub_user_metadata_alloc( + pubnub_t* pb, + const char* id) +{ + return (pubnub_user_metadata_t*)create_entity_( + pb, + id, + PUBNUB_ENTITY_USER_METADATA); +} + +bool pubnub_entity_free(void** entity) +{ + if (NULL == entity || NULL == *entity) { return false; } + + PUBNUB_ASSERT_OPT(true == is_pubnub_entity_(*entity)); + + pubnub_entity_t* _entity = *entity; + pubnub_mutex_lock(_entity->mutw); + if (0 == pbref_counter_free(_entity->counter)) { + free(_entity->id.ptr); + pubnub_mutex_unlock(_entity->mutw); + pubnub_mutex_destroy(_entity->mutw); + free(*entity); + *entity = NULL; + return true; + } + pubnub_mutex_unlock(_entity->mutw); + + return false; +} + +pubnub_entity_t* create_entity_( + pubnub_t* pb, + const char* id, + const pubnub_entity_type type) +{ + PUBNUB_ASSERT(pb_valid_ctx_ptr(pb)); + PUBNUB_ASSERT_OPT(NULL != id); + + PBCC_ALLOCATE_TYPE(entity, pubnub_entity_t, true, NULL); + entity->id.size = strlen(id); + entity->id.ptr = pbstrndup(id, entity->id.size); + if (NULL == entity->id.ptr) { + PUBNUB_LOG_ERROR( + "create_entity: failed to allocate memory for entity id\n"); + free(entity); + return NULL; + } + + pubnub_mutex_init(entity->mutw); + entity->counter = pbref_counter_alloc(); + entity->type = type; + entity->pb = pb; + + return entity; +} + +bool is_pubnub_entity_(void* entity) +{ + if (NULL == entity) { return false; } + + const pubnub_entity_type type = *(pubnub_entity_type*)entity; + + return type == PUBNUB_ENTITY_CHANNEL || type == PUBNUB_ENTITY_CHANNEL_GROUP + || type == PUBNUB_ENTITY_CHANNEL_METADATA + || type == PUBNUB_ENTITY_USER_METADATA; +} + +void entity_reference_count_update_( + pubnub_entity_t* entity, + const bool increase) +{ + PUBNUB_ASSERT_OPT(NULL != entity); + + pubnub_mutex_lock(entity->mutw); + if (increase) { pbref_counter_increment(entity->counter); } + else { pbref_counter_decrement(entity->counter); } + pubnub_mutex_unlock(entity->mutw); +} \ No newline at end of file diff --git a/core/pubnub_entities.h b/core/pubnub_entities.h new file mode 100644 index 00000000..98e5c792 --- /dev/null +++ b/core/pubnub_entities.h @@ -0,0 +1,148 @@ +/* -*- c-file-style:"stroustrup"; indent-tabs-mode: nil -*- */ +#ifndef PUBNUB_ENTITIES_H +#define PUBNUB_ENTITIES_H +// Currently, entities is part of the subscribe event engine feature. +#if PUBNUB_USE_SUBSCRIBE_EVENT_ENGINE + + +/** + * @file pubnub_entities.h + * @brief Module to manage first-class types. + * + * Entities used to access contextual endpoints related to a specific entity. + */ + +#include + +#include "core/pubnub_api_types.h" +#include "lib/pb_extern.h" + + +// ---------------------------------- +// Types +// ---------------------------------- + +/** PubNub Channel entity definition. */ +typedef struct pubnub_channel pubnub_channel_t; + +/** PubNub Channel Group entity definition. */ +typedef struct pubnub_channel_group pubnub_channel_group_t; + +/** PubNub Channel Metadata entity (App Context) definition. */ +typedef struct pubnub_channel_metadata pubnub_channel_metadata_t; + +/** PubNub User Metadata entity (App Context) definition. */ +typedef struct pubnub_user_metadata pubnub_user_metadata_t; + +/** PubNub Entity definition. */ +typedef struct pubnub_entity pubnub_entity_t; + + +// ---------------------------------- +// Functions +// ---------------------------------- + +/** + * @brief Create channel entity. + * + * @code + * pubnub_channel_t* channel = pubnub_channel_alloc(pb, "my_channel"); + * @endcode + * + * @param pb Pointer to the PubNub context, which will be used by the created + * entity to interact with PubNub REST API. + * @param name Pointer to the name which will be used with channel-specific + * REST API. + * @return Pointer to the ready to use PubNub Channel entity. The returned + * pointer must be passed to the `pubnub_entity_free` to avoid a memory + * leak. + */ +PUBNUB_EXTERN pubnub_channel_t* pubnub_channel_alloc( + pubnub_t* pb, + const char* name); + +/** + * @brief Create channel group entity. + * + * @code + * pubnub_channel_group_t* group = pubnub_channel_group_alloc(pb, "my_group"); + * @endcode + * + * @note `name` will be copied. + * + * @param pb PubNub context, which will be used by the created entity to + * interact with PubNub REST API. + * @param name Identifier which will be used with channel group-specific + * REST API. + * @return Pointer to the ready to use PubNub Channel Group entity. The returned + * pointer must be passed to the `pubnub_entity_free` to avoid a memory + * leak. + */ +PUBNUB_EXTERN pubnub_channel_group_t* pubnub_channel_group_alloc( + pubnub_t* pb, + const char* name); + +/** + * @brief Create channel metadata entity. + * + * @code + * pubnub_channel_metadata_t* metadata = pubnub_channel_metadata_alloc(pb, "channel_meta"); + * @endcode + * + * @note `id` will be copied. + * + * @param pb PubNub context, which will be used by the created entity to + * interact with PubNub REST API. + * @param id Identifier which will be used with channel metadata specific + * REST API. + * @return Ready to use PubNub Channel Metadata entity. The returned + * pointer must be passed to the `pubnub_entity_free` to avoid a memory + * leak. + */ +PUBNUB_EXTERN pubnub_channel_metadata_t* pubnub_channel_metadata_alloc( + pubnub_t* pb, + const char* id); + +/** + * @brief Create user metadata entity. + * + * @code + * pubnub_user_metadata_t* metadata = pubnub_user_metadata_alloc(pb, "user_meta"); + * @endcode + * + * @note `id` will be copied. + * + * @param pb PubNub context, which will be used by the created entity to + * interact with PubNub REST API. + * @param id Identifier which will be used with user metadata specific REST API. + * @return Ready to use PubNub User Metadata entity. The returned + * pointer must be passed to the `pubnub_entity_free` to avoid a memory + * leak. + */ +PUBNUB_EXTERN pubnub_user_metadata_t* pubnub_user_metadata_alloc( + pubnub_t* pb, + const char* id); + +/** + * @brief Release resources used by PubNub entity. + * + * @code + * pubnub_channel_group_t* group = pubnub_channel_group_alloc(pb, "my_group"); + * // ... + * pubnub_entity_free(&group); + * @endcode + * + * \b Warning: Make sure that calls to the `pubnub_entity_free` is done only + * once to not disrupt other types which still use this entity. + * + * @note Function will `NULL`ify provided entity pointer (if there is no more + * references to the entity). + * + * @param entity PubNub entity, which should free up resources. + * @return `false` if there are more references to the entity. + */ +PUBNUB_EXTERN bool pubnub_entity_free(void** entity); +#else // #if PUBNUB_USE_SUBSCRIBE_EVENT_ENGINE +#error To use entities API you must define PUBNUB_USE_SUBSCRIBE_EVENT_ENGINE=1 +#endif // #if PUBNUB_USE_SUBSCRIBE_EVENT_ENGINE +#endif // #ifndef PUBNUB_ENTITIES_H \ No newline at end of file diff --git a/core/pubnub_entities_internal.h b/core/pubnub_entities_internal.h new file mode 100644 index 00000000..36fa0ed5 --- /dev/null +++ b/core/pubnub_entities_internal.h @@ -0,0 +1,99 @@ +/* -*- c-file-style:"stroustrup"; indent-tabs-mode: nil -*- */ +#ifndef PUBNUB_ENTITIES_INTERNAL_H +#define PUBNUB_ENTITIES_INTERNAL_H +// Currently, entities is part of the subscribe event engine feature. +#if PUBNUB_USE_SUBSCRIBE_EVENT_ENGINE + + +/** + * @file pubnub_entities_internal.h + * @brief Module to manage first-class types internal / lib interface. + */ + +#include + +#include "core/pubnub_memory_block.h" +#include "core/pubnub_api_types.h" +#include "core/pubnub_mutex.h" +#include "lib/pbref_counter.h" + + + +// ---------------------------------- +// Types +// ---------------------------------- + +/** Known PubNub entities. */ +typedef enum { + PUBNUB_ENTITY_CHANNEL, + PUBNUB_ENTITY_CHANNEL_GROUP, + PUBNUB_ENTITY_CHANNEL_METADATA, + PUBNUB_ENTITY_USER_METADATA, +} pubnub_entity_type; + +/** PubNub Channel entity definition. */ +struct pubnub_channel { + /** Channel entity type (`PUBNUB_ENTITY_CHANNEL`). */ + pubnub_entity_type type; + /** Identifier which will be used with channel-specific REST API. */ + struct pubnub_char_mem_block name; +}; + +/** PubNub Channel Group entity definition. */ +struct pubnub_channel_group { + /** Channel group entity type (`PUBNUB_ENTITY_CHANNEL_GROUP`). */ + pubnub_entity_type type; + /** Identifier which will be used with channel group-specific REST API. */ + struct pubnub_char_mem_block name; +}; + +/** PubNub Channel Metadata entity (App Context) definition. */ +struct pubnub_channel_metadata { + /** Channel metadata entity type (`PUBNUB_ENTITY_CHANNEL_METADATA`). */ + pubnub_entity_type type; + /** Identifier which will be used with channel metadata specific REST API. */ + struct pubnub_char_mem_block id; +}; + +/** PubNub User Metadata entity (App Context) definition. */ +struct pubnub_user_metadata { + /** User metadata entity type (`PUBNUB_ENTITY_USER_METADATA`). */ + pubnub_entity_type type; + /** Identifier which will be used with user metadata specific REST API. */ + struct pubnub_char_mem_block id; +}; + +/** PubNub Entity definition. */ +struct pubnub_entity { + /** PubNub entity type. */ + pubnub_entity_type type; + /** Entity identifier. */ + struct pubnub_char_mem_block id; + /** + * @brief PubNub context, which will be used by the created entity to + * interact with PubNub REST API + */ + pubnub_t* pb; + /** Object references counter. */ + pbref_counter_t* counter; + /** Shared resources access lock. */ + pubnub_mutex_t mutw; +}; + + +// ---------------------------------- +// Functions +// ---------------------------------- + +/** + * @brief Update number of subscriptions which refer to PubNub `entity`. + * + * @param entity PubNub entity for which number of subscription references + * should be updated. + * @param increase Whether reference count should be increased or decreased. + */ +void entity_reference_count_update_(pubnub_entity_t* entity, bool increase); +#else // #if PUBNUB_USE_SUBSCRIBE_EVENT_ENGINE +#error To use entities API you must define PUBNUB_USE_SUBSCRIBE_EVENT_ENGINE=1 +#endif // #if PUBNUB_USE_SUBSCRIBE_EVENT_ENGINE +#endif // #ifndef PUBNUB_ENTITIES_INTERNAL_H \ No newline at end of file diff --git a/core/pubnub_fetch_history.c b/core/pubnub_fetch_history.c index 1f9ec59d..c4626190 100644 --- a/core/pubnub_fetch_history.c +++ b/core/pubnub_fetch_history.c @@ -6,6 +6,7 @@ #include "pubnub_fetch_history.h" #include "pubnub_json_parse.h" +#include "pbcc_fetch_history.h" #include "pubnub_assert.h" #include "pubnub_log.h" diff --git a/core/pubnub_fetch_history.h b/core/pubnub_fetch_history.h index c7f6df49..45e92c42 100644 --- a/core/pubnub_fetch_history.h +++ b/core/pubnub_fetch_history.h @@ -1,10 +1,9 @@ /* -*- c-file-style:"stroustrup"; indent-tabs-mode: nil -*- */ -#if PUBNUB_USE_FETCH_HISTORY -#if !defined INC_PUBNUB_FETCH_HISTORY +#ifndef INC_PUBNUB_FETCH_HISTORY #define INC_PUBNUB_FETCH_HISTORY +#if PUBNUB_USE_FETCH_HISTORY -#include "pbcc_fetch_history.h" #include "lib/pb_extern.h" /** Options for fetch history. */ @@ -86,7 +85,7 @@ PUBNUB_EXTERN enum pubnub_res pubnub_fetch_history(pubnub_t* PUBNUB_EXTERN pubnub_chamebl_t pubnub_get_fetch_history(pubnub_t* pb); -#endif /* !defined INC_PUBNUB_FETCH_HISTORY */ - #endif /* PUBNUB_USE_FETCH_HISTORY */ +#endif /* #ifndef INC_PUBNUB_FETCH_HISTORY */ + diff --git a/core/pubnub_internal_common.h b/core/pubnub_internal_common.h index 20ecbb68..ba6a733e 100644 --- a/core/pubnub_internal_common.h +++ b/core/pubnub_internal_common.h @@ -1,8 +1,8 @@ /* -*- c-file-style:"stroustrup"; indent-tabs-mode: nil -*- */ -#include "pbcc_crypto.h" #if !defined INC_PUBNUB_INTERNAL_COMMON #define INC_PUBNUB_INTERNAL_COMMON +#include #include "pubnub_config.h" #include "core/pubnub_ccore_pubsub.h" #include "core/pubnub_netcore.h" @@ -359,6 +359,11 @@ struct pubnub_ { /** Last received HTTP (result) code */ uint16_t http_code; +#if PUBNUB_USE_RETRY_CONFIGURATION + /** Last received HTTP (result) `Retry-After` header value. */ + uint16_t http_header_retry_after; +#endif // #if PUBNUB_USE_RETRY_CONFIGURATION + #if defined PUBNUB_ORIGIN_SETTABLE char const* origin; diff --git a/core/pubnub_netcore.c b/core/pubnub_netcore.c index 29369faa..62bad26b 100644 --- a/core/pubnub_netcore.c +++ b/core/pubnub_netcore.c @@ -410,6 +410,10 @@ static char const* pbnc_state2str(enum pubnub_state e) case PBS_RETRY: return "PBS_RETRY"; #endif +#if PUBNUB_USE_RETRY_CONFIGURATION + case PBS_WAIT_RETRY: + return "PBS_WAIT_RETRY"; +#endif // #if PUBNUB_USE_RETRY_CONFIGURATION case PBS_IDLE: return "PBS_IDLE"; case PBS_READY: @@ -1060,6 +1064,10 @@ int pbnc_fsm(struct pubnub_* pb) pb->http_code = atoi(pb->core.http_buf + 9); WATCH_USHORT(pb->http_code); pb->core.http_content_len = 0; +#if PUBNUB_USE_RETRY_CONFIGURATION + /** Reset failed request `Retry-After` HTTP header value. */ + pb->http_header_retry_after = 0; +#endif // #if PUBNUB_USE_RETRY_CONFIGURATION pb->http_chunked = false; pb->state = PBS_RX_HEADERS; goto next_state; @@ -1090,6 +1098,15 @@ int pbnc_fsm(struct pubnub_* pb) switch (pbrslt) { case PNR_IN_PROGRESS: break; + case PNR_TX_BUFF_TOO_SMALL: + /** We could copy the "line so far" to the reply buffer + and then process the line from the reply buffer when + it's finished. This would be much more complex, yet + most lines will not be that long and, if the reply + buffer is static, it, too, might not be large enough. + */ + pb->state = PBS_RX_HEADERS; + goto next_state; case PNR_OK: { /* We know that Pubnub will always use keep-alive unless we ask for `close`, so, we don't check for the @@ -1098,6 +1115,9 @@ int pbnc_fsm(struct pubnub_* pb) char h_chunked[] = "Transfer-Encoding: chunked"; char h_length[] = "Content-Length: "; char h_close[] = "Connection: close"; +#if PUBNUB_USE_RETRY_CONFIGURATION + char h_retry_after[] = "Retry-After: "; +#endif // #if PUBNUB_USE_RETRY_CONFIGURATION #if PUBNUB_RECEIVE_GZIP_RESPONSE char h_encoding[] = "Content-Encoding: gzip"; #endif @@ -1143,6 +1163,11 @@ int pbnc_fsm(struct pubnub_* pb) } pb->core.http_content_len = len; } +#if PUBNUB_USE_RETRY_CONFIGURATION + else if (pb_strncasecmp(pb->core.http_buf, h_retry_after, sizeof h_retry_after - 1) == 0) { + pb->http_header_retry_after = atoi(pb->core.http_buf + sizeof h_retry_after - 1); + } +#endif // #if PUBNUB_USE_RETRY_CONFIGURATION else if (pb_strncasecmp(pb->core.http_buf, h_close, sizeof h_close - 1) == 0) { pb->flags.should_close = true; } @@ -1159,15 +1184,6 @@ int pbnc_fsm(struct pubnub_* pb) pb->state = PBS_RX_HEADERS; goto next_state; } - case PNR_TX_BUFF_TOO_SMALL: - /** We could copy the "line so far" to the reply buffer - and then process the line from the reply buffer when - it's finished. This would be much more complex, yet - most lines will not be that long and, if the reply - buffer is static, it, too, might not be large enough. - */ - pb->state = PBS_RX_HEADERS; - goto next_state; default: outcome_detected(pb, pbrslt); break; diff --git a/core/pubnub_netcore.h b/core/pubnub_netcore.h index 0014f8f6..4bb8cb62 100644 --- a/core/pubnub_netcore.h +++ b/core/pubnub_netcore.h @@ -21,6 +21,10 @@ enum pubnub_state { 407, sending (some more) authentication data. */ PBS_RETRY, +#if PUBNUB_USE_RETRY_CONFIGURATION + /** Request retry timer is active and FSM await for it to timeout. */ + PBS_WAIT_RETRY, +#endif // #if PUBNUB_USE_RETRY_CONFIGURATION /** Ready to start a transaction */ PBS_READY, /** Waiting for sending a DNS request */ diff --git a/core/pubnub_ntf_sync.c b/core/pubnub_ntf_sync.c index cfee8dcd..20caac8e 100644 --- a/core/pubnub_ntf_sync.c +++ b/core/pubnub_ntf_sync.c @@ -1,6 +1,11 @@ /* -*- c-file-style:"stroustrup"; indent-tabs-mode: nil -*- */ #include "pubnub_ntf_sync.h" +#if PUBNUB_USE_RETRY_CONFIGURATION +#include "core/pubnub_retry_configuration_internal.h" +#include "core/pubnub_pubsubapi.h" +#endif // #if PUBNUB_USE_RETRY_CONFIGURATION + #include "pubnub_internal.h" #include "pbpal.h" #include "pubnub_assert.h" @@ -54,6 +59,27 @@ void pbntf_trans_outcome(pubnub_t* pb, enum pubnub_state state) { PBNTF_TRANS_OUTCOME_COMMON(pb, state); PUBNUB_ASSERT(pbnc_can_start_transaction(pb)); +#if PUBNUB_USE_RETRY_CONFIGURATION + if (NULL != pb->core.retry_configuration && + pubnub_retry_configuration_retryable_result_(pb)) { + uint16_t delay = pubnub_retry_configuration_delay_(pb); + + if (delay > 0) { + if (NULL == pb->core.retry_timer) + pb->core.retry_timer = pbcc_request_retry_timer_alloc(pb); + if (NULL != pb->core.retry_timer) { + pbcc_request_retry_timer_start(pb->core.retry_timer, delay); + return; + } + } + } + + /** There were no need to start retry timer, we can free it if exists. */ + if (NULL != pb->core.retry_timer) { + pb->core.http_retry_count = 0; + pbcc_request_retry_timer_free(&pb->core.retry_timer); + } +#endif // #if PUBNUB_USE_RETRY_CONFIGURATION } diff --git a/core/pubnub_pubsubapi.c b/core/pubnub_pubsubapi.c index cf68eaa2..ac9b027d 100644 --- a/core/pubnub_pubsubapi.c +++ b/core/pubnub_pubsubapi.c @@ -88,7 +88,9 @@ pubnub_t* pubnub_init(pubnub_t* p, const char* publish_key, const char* subscrib #if PUBNUB_CRYPTO_API p->core.crypto_module = NULL; #endif - +#if PUBNUB_USE_SUBSCRIBE_EVENT_ENGINE + p->core.subscribe_ee = pbcc_subscribe_ee_alloc(p); +#endif pubnub_mutex_unlock(p->monitor); return p; @@ -302,6 +304,16 @@ int pubnub_last_http_code(pubnub_t* pb) return result; } +#if PUBNUB_USE_RETRY_CONFIGURATION +uint16_t pubnub_last_http_retry_header(pubnub_t* pb) +{ + PUBNUB_ASSERT(pb_valid_ctx_ptr(pb)); + pubnub_mutex_lock(pb->monitor); + uint16_t retry_after = pb->http_header_retry_after; + pubnub_mutex_unlock(pb->monitor); + return retry_after; +} +#endif // #if PUBNUB_USE_RETRY_CONFIGURATION char const* pubnub_last_time_token(pubnub_t* pb) { diff --git a/core/pubnub_pubsubapi.h b/core/pubnub_pubsubapi.h index c4bea77c..b7af7a87 100644 --- a/core/pubnub_pubsubapi.h +++ b/core/pubnub_pubsubapi.h @@ -296,6 +296,12 @@ PUBNUB_EXTERN enum pubnub_res pubnub_last_result(pubnub_t* p); * context. */ PUBNUB_EXTERN int pubnub_last_http_code(pubnub_t* p); +#if PUBNUB_USE_RETRY_CONFIGURATION +/** Returns the HTTP reply `Retry-After` header value of the last transaction in + * the @p p context. */ +PUBNUB_EXTERN uint16_t pubnub_last_http_retry_header(pubnub_t* pb); +#endif // #if PUBNUB_USE_RETRY_CONFIGURATION + /** Returns the string of the result of the last `publish` transaction, as returned from Pubnub. If the last transaction is not a publish, or there is some other error, it returns NULL. If the Publish diff --git a/core/pubnub_retry_configuration.c b/core/pubnub_retry_configuration.c new file mode 100644 index 00000000..1b06fb9d --- /dev/null +++ b/core/pubnub_retry_configuration.c @@ -0,0 +1,470 @@ +/* -*- c-file-style:"stroustrup"; indent-tabs-mode: nil -*- */ +#include "pubnub_retry_configuration_internal.h" + +#include +#include +#include +#include +#include + +#include "core/pubnub_api_types.h" +#include "core/pubnub_pubsubapi.h" +#include "core/pubnub_assert.h" +#include "core/pubnub_mutex.h" +#include "core/pubnub_log.h" +#include "pubnub_internal.h" + + +// ---------------------------------------------- +// Constants +// ---------------------------------------------- + +/** Minimum and default delay between retry attempt. */ +#define DEFAULT_MINIMUM_DELAY 2 +/** Maximum delay between retry attempt. */ +#define DEFAULT_MAXIMUM_DELAY 150 +/** Default failed request retry attempts with linear retry policy. */ +#define DEFAULT_LINEAR_RETRY_ATTEMPTS 10 +/** Default failed request retry attempts with exponential retry policy. */ +#define DEFAULT_EXPONENTIAL_RETRY_ATTEMPTS 6 + + +// ---------------------------------------------- +// Types +// ---------------------------------------------- + +/** Retry policy type. */ +typedef enum { + /** Use equal delays between retry attempts. */ + PUBNUB_LINEAR_RETRY_POLICY, + /** + * @brief Use a `minimum_delay` delay that will exponentially increase with + * each failed request retry attempt. + */ + PUBNUB_EXPONENTIAL_RETRY_POLICY, +} pubnub_retry_policy_t; + +/** Failed requests retry configuration definition. */ +struct pubnub_retry_configuration { + /** Delay computation retry policy. */ + pubnub_retry_policy_t policy; + /** + * @brief Maximum allowed number of failed requests that should be retried + * automatically before reporting an error. + * + * \b Important: The maximum allowed number of retries is \b 10. + */ + int max_retries; + /** + * @brief Minimum delay between retry attempts. + * + * The delay is used for the `PUBNUB_LINEAR_RETRY_POLICY` policy, which is + * used between retry attempts. For the `PUBNUB_EXPONENTIAL_RETRY_POLICY` + * policy, which is used as the `minimum_delay`, which will be used to + * calculate the next delay based on the number of retry attempts. + * + * \b Important: The minimum allowed delay is \b 2. + */ + int minimum_delay; + /** + * @brief Maximum allowed computed delay that should be used between retry + * attempts. + */ + int maximum_delay; + /** Excluded endpoints map. */ + bool excluded_endpoints[PNRC_ENDPOINTS_COUNT]; +}; + + +// ---------------------------------------------- +// Function prototypes +// ---------------------------------------------- + +/** + * @brief Create a request retry configuration with a linear retry policy and + * list of API groups which won't be retried. + * + * Configurations with a linear retry policy will use equal delays between retry + * attempts. The default implementation uses a \b 2 seconds delay and \b 10 + * maximum retry attempts. + * + * @note The PubNub client will schedule request retries after a calculated + * delay, but the actual retry may not happen at the same moment (there is + * a small leeway). + * + * @param policy Retry policy type. + * @param minimum_delay Delay is used for the 'PUBNUB_LINEAR_RETRY_POLICY' + * policy, which is used between retry attempts. For the + * 'PUBNUB_EXPONENTIAL_RETRY_POLICY' policy, which is used + * as the `minimum_delay`, which will be used to calculate + * the next delay based on the number of retry attempts. + * @param maximum_delay Maximum allowed computed delay that should be used + * between retry attempts. + * @param maximum_retry The number of failed requests that should be retried + * automatically before reporting an error. + * @param excluded Endpoint for which retry shouldn't be used. + * @param endpoints Variadic list of endpoints for which retry shouldn't be + * used. \b Important: list should be terminated by \b -1. + * @return Pointer to the ready to use PubNub request retry configuration or + * `NULL` in case of error insufficient memory error. The returned + * pointer must be passed to the `pubnub_retry_configuration_free` to + * avoid a memory leak. + */ +static pubnub_retry_configuration_t* +pubnub_retry_configuration_alloc_with_options_( + pubnub_retry_policy_t policy, + int minimum_delay, + int maximum_delay, + int maximum_retry, + pubnub_endpoint_t excluded, + va_list endpoints); + +/** + * @brief Handle user-provided excluded endpoints for retry configuration. + * + * @param configuration Pointer to the configuration for which list of + * exclusions should be set. + * @param excluded_endpoint First entry in the list of excluded endpoints. + * @param endpoints Variadic list of other excluded endpoints. + */ +static void pubnub_retry_configuration_set_excluded_( + pubnub_retry_configuration_t* configuration, + pubnub_endpoint_t excluded_endpoint, + va_list endpoints); + +/** + * @brief Check whether `transaction` can be retried after failure. + * + * @param configuration Pointer to the configuration with failed request + * handling details. + * @param transaction Failed transaction type. + * @param current_retry How many consequential retry attempts has been done. + * @param http_status Failed transaction status code. + * @return `true` if transaction can be handled once more after retry. + */ +static bool pubnub_retry_configuration_retryable_( + const pubnub_retry_configuration_t* configuration, + enum pubnub_trans transaction, + int current_retry, + int http_status); + +/** + * @brief Identify API endpoint group from transaction type. + * + * @param trans Transaction which should be translated. + * @return One of known PubNub API endpoint group type. + */ +static pubnub_endpoint_t pubnub_endpoint_from_transaction_( + enum pubnub_trans trans); + + +// ---------------------------------------------- +// Functions +// ---------------------------------------------- + +void pubnub_set_retry_configuration( + pubnub_t* pb, + pubnub_retry_configuration_t* configuration) +{ + PUBNUB_ASSERT(pb_valid_ctx_ptr(pb)); + pubnub_mutex_lock(pb->monitor); + if (NULL != pb->core.retry_configuration) + pubnub_retry_configuration_free(&pb->core.retry_configuration); + pb->core.retry_configuration = configuration; + pubnub_mutex_unlock(pb->monitor); +} + +pubnub_retry_configuration_t* pubnub_retry_configuration_linear_alloc(void) +{ + return pubnub_retry_configuration_linear_alloc_with_excluded(-1); +} + +pubnub_retry_configuration_t* +pubnub_retry_configuration_linear_alloc_with_excluded( + const int excluded, + ...) +{ + va_list args; + va_start(args, excluded); + pubnub_retry_configuration_t* configuration = + pubnub_retry_configuration_alloc_with_options_( + PUBNUB_LINEAR_RETRY_POLICY, + DEFAULT_MINIMUM_DELAY, + 0, + DEFAULT_LINEAR_RETRY_ATTEMPTS, + excluded, + args); + va_end(args); + + return configuration; +} + +pubnub_retry_configuration_t* +pubnub_retry_configuration_linear_alloc_with_options( + const int delay, + const int maximum_retry, + const int excluded, + ...) +{ + va_list args; + va_start(args, excluded); + pubnub_retry_configuration_t* configuration = + pubnub_retry_configuration_alloc_with_options_( + PUBNUB_LINEAR_RETRY_POLICY, + delay, + 0, + maximum_retry, + excluded, + args); + va_end(args); + + return configuration; +} + +pubnub_retry_configuration_t* pubnub_retry_configuration_exponential_alloc(void) +{ + return pubnub_retry_configuration_exponential_alloc_with_excluded(-1); +} + +pubnub_retry_configuration_t* +pubnub_retry_configuration_exponential_alloc_with_excluded( + const int excluded, + ...) +{ + va_list args; + va_start(args, excluded); + pubnub_retry_configuration_t* configuration = + pubnub_retry_configuration_alloc_with_options_( + PUBNUB_EXPONENTIAL_RETRY_POLICY, + DEFAULT_MINIMUM_DELAY, + DEFAULT_MAXIMUM_DELAY, + DEFAULT_EXPONENTIAL_RETRY_ATTEMPTS, + excluded, + args); + va_end(args); + + return configuration; +} + +pubnub_retry_configuration_t* +pubnub_retry_configuration_exponential_alloc_with_options( + const int minimum_delay, + const int maximum_delay, + const int maximum_retry, + const int excluded, + ...) +{ + va_list args; + va_start(args, excluded); + pubnub_retry_configuration_t* configuration = + pubnub_retry_configuration_alloc_with_options_( + PUBNUB_EXPONENTIAL_RETRY_POLICY, + minimum_delay, + maximum_delay, + maximum_retry, + excluded, + args); + va_end(args); + + return configuration; +} + +void pubnub_retry_configuration_free( + pubnub_retry_configuration_t** configuration) +{ + if (NULL == configuration || NULL == *configuration) { return; } + + free(*configuration); + *configuration = NULL; +} + +pubnub_retry_configuration_t* pubnub_retry_configuration_alloc_with_options_( + const pubnub_retry_policy_t policy, + const int minimum_delay, + const int maximum_delay, + const int maximum_retry, + const pubnub_endpoint_t excluded, + const va_list endpoints) +{ + pubnub_retry_configuration_t* configuration = malloc( + sizeof(pubnub_retry_configuration_t)); + if (NULL == configuration) { + PUBNUB_LOG_ERROR("pubnub_retry_configuration_linear_alloc: failed to " + "allocate memory\n"); + return configuration; + } + + memset(configuration->excluded_endpoints, + false, + sizeof(configuration->excluded_endpoints)); + configuration->policy = policy; + configuration->minimum_delay = minimum_delay; + configuration->maximum_delay = maximum_delay; + configuration->max_retries = maximum_retry; + + pubnub_retry_configuration_set_excluded_(configuration, + excluded, + endpoints); + + return configuration; +} + +bool pubnub_retry_configuration_retryable_result_(pubnub_t* pb) +{ + switch (pb->core.last_result) { + case PNR_ADDR_RESOLUTION_FAILED: + case PNR_WAIT_CONNECT_TIMEOUT: + case PNR_CONNECT_FAILED: + case PNR_CONNECTION_TIMEOUT: + case PNR_TIMEOUT: + case PNR_ABORTED: + case PNR_HTTP_ERROR: + return true; + default: + return false; + } +} + +bool pubnub_retry_configuration_retryable_( + const pubnub_retry_configuration_t* configuration, + const enum pubnub_trans transaction, + const int current_retry, + const int http_status) +{ + const pubnub_endpoint_t endpoint = + pubnub_endpoint_from_transaction_(transaction); + + if (configuration->excluded_endpoints[endpoint] || + current_retry >= configuration->max_retries) + return false; + + return http_status == 429 || http_status >= 500 || http_status == 0; +} + +size_t pubnub_retry_configuration_delay_(pubnub_t* pb) +{ + const pubnub_retry_configuration_t* configuration = + pb->core.retry_configuration; + const int current_retry = pb->core.http_retry_count; + const int http_status = pubnub_last_http_code(pb); + const int retry_after = pubnub_last_http_retry_header(pb); + + size_t delay = -1; + if (!pubnub_retry_configuration_retryable_(configuration, + pb->trans, + current_retry, + http_status)) + return delay; + + if (retry_after > 0 && 429 == http_status) { delay = retry_after; } + else if (PUBNUB_LINEAR_RETRY_POLICY == configuration->policy) + delay = configuration->minimum_delay; + else { + delay = (int)fmin( + (double)(configuration->minimum_delay * pow(2, current_retry - 1)), + (double)configuration->maximum_delay); + } + + return delay * 1000 + random() % 1000; +} + +void pubnub_retry_configuration_set_excluded_( + pubnub_retry_configuration_t* configuration, + const pubnub_endpoint_t excluded_endpoint, + va_list endpoints) +{ + int endpoint = excluded_endpoint; + + while (endpoint != -1) { + if (endpoint >= 0 && endpoint < PNRC_ENDPOINTS_COUNT) + configuration->excluded_endpoints[endpoint] = true; + endpoint = va_arg(endpoints, int); + } +} + +pubnub_endpoint_t pubnub_endpoint_from_transaction_( + const enum pubnub_trans trans) +{ + pubnub_endpoint_t endpoint; + + switch (trans) { + case PBTT_SUBSCRIBE: +#if PUBNUB_USE_SUBSCRIBE_V2 + case PBTT_SUBSCRIBE_V2: +#endif // #if PUBNUB_USE_SUBSCRIBE_V2 + endpoint = PNRC_SUBSCRIBE_ENDPOINT; + break; + case PBTT_PUBLISH: + case PBTT_SIGNAL: + endpoint = PNRC_MESSAGE_SEND_ENDPOINT; + break; + case PBTT_WHERENOW: + case PBTT_GLOBAL_HERENOW: + case PBTT_HERENOW: + case PBTT_LEAVE: + case PBTT_HEARTBEAT: + case PBTT_SET_STATE: + case PBTT_STATE_GET: + endpoint = PNRC_PRESENCE_ENDPOINT; + break; + case PBTT_HISTORY: +#if PUBNUB_USE_FETCH_HISTORY + case PBTT_FETCH_HISTORY: +#endif // #if PUBNUB_USE_FETCH_HISTORY +#if PUBNUB_USE_ADVANCED_HISTORY + case PBTT_MESSAGE_COUNTS: +#endif // #if PUBNUB_USE_ADVANCED_HISTORY +#if PUBNUB_USE_ACTIONS_API + case PBTT_HISTORY_WITH_ACTIONS: +#endif // #if PUBNUB_USE_ACTIONS_API + endpoint = PNRC_MESSAGE_STORAGE_ENDPOINT; + break; + case PBTT_REMOVE_CHANNEL_GROUP: + case PBTT_REMOVE_CHANNEL_FROM_GROUP: + case PBTT_ADD_CHANNEL_TO_GROUP: + case PBTT_LIST_CHANNEL_GROUP: + endpoint = PNRC_CHANNEL_GROUPS_ENDPOINT; + break; +#if PUBNUB_USE_OBJECTS_API + case PBTT_GETALL_UUIDMETADATA: + case PBTT_SET_UUIDMETADATA: + case PBTT_GET_UUIDMETADATA: + case PBTT_DELETE_UUIDMETADATA: + case PBTT_GETALL_CHANNELMETADATA: + case PBTT_SET_CHANNELMETADATA: + case PBTT_GET_CHANNELMETADATA: + case PBTT_REMOVE_CHANNELMETADATA: + case PBTT_GET_MEMBERSHIPS: + case PBTT_SET_MEMBERSHIPS: + case PBTT_REMOVE_MEMBERSHIPS: + case PBTT_GET_MEMBERS: + case PBTT_ADD_MEMBERS: + case PBTT_SET_MEMBERS: + case PBTT_REMOVE_MEMBERS: + endpoint = PNRC_APP_CONTEXT_ENDPOINT; + break; +#endif // #if PUBNUB_USE_OBJECTS_API +#if PUBNUB_USE_ACTIONS_API + case PBTT_ADD_ACTION: + case PBTT_REMOVE_ACTION: + case PBTT_GET_ACTIONS: + endpoint = PNRC_MESSAGE_REACTIONS_ENDPOINT; + break; +#endif // #if PUBNUB_USE_ACTIONS_API +#if PUBNUB_USE_GRANT_TOKEN_API || PUBNUB_USE_REVOKE_TOKEN_API +#if PUBNUB_USE_GRANT_TOKEN_API + case PBTT_GRANT_TOKEN: +#endif // #if PUBNUB_USE_GRANT_TOKEN_API +#if PUBNUB_USE_REVOKE_TOKEN_API + case PBTT_REVOKE_TOKEN: +#endif // #if PUBNUB_USE_REVOKE_TOKEN_API + endpoint = PNRC_PAM_ENDPOINT; + break; +#endif // #if PUBNUB_USE_GRANT_TOKEN_API || PUBNUB_USE_REVOKE_TOKEN_API + default: + endpoint = PNRC_UNKNOWN_ENDPOINT; + break; + } + + return endpoint; +} \ No newline at end of file diff --git a/core/pubnub_retry_configuration.h b/core/pubnub_retry_configuration.h new file mode 100644 index 00000000..2fd59a23 --- /dev/null +++ b/core/pubnub_retry_configuration.h @@ -0,0 +1,335 @@ +/* -*- c-file-style:"stroustrup"; indent-tabs-mode: nil -*- */ +#ifndef PUBNUB_RETRY_CONFIGURATION_H +#define PUBNUB_RETRY_CONFIGURATION_H +#if PUBNUB_USE_RETRY_CONFIGURATION + + +/** + * @file pubnub_retry_configuration.h + * @brief PubNub request retry configuration. + */ + +#include "core/pubnub_api_types.h" +#include "lib/pb_extern.h" + + +// ---------------------------------------------- +// Types forwarding +// ---------------------------------------------- + +/** + * @brief Failed requests retry configuration definition. + * + * PubNub client can automatically retry failed requests basing on retry + * configuration criteria. + */ +typedef struct pubnub_retry_configuration pubnub_retry_configuration_t; + + +// ---------------------------------------------- +// Types +// ---------------------------------------------- + +/** Excluded endpoint groups definition. */ +typedef enum +{ + /** Unknown API endpoint group. */ + PNRC_UNKNOWN_ENDPOINT, + /** The endpoints to send messages. */ + PNRC_MESSAGE_SEND_ENDPOINT, + /** The endpoint for real-time update retrieval. */ + PNRC_SUBSCRIBE_ENDPOINT, + /** + * @brief The endpoint to access and manage `user_id` presence and fetch + * channel presence information. + */ + PNRC_PRESENCE_ENDPOINT, + /** + * @brief The endpoint to access and manage messages for a specific + * channel(s) in the persistent storage. + */ + PNRC_MESSAGE_STORAGE_ENDPOINT, + /** The endpoint to access and manage channel groups. */ + PNRC_CHANNEL_GROUPS_ENDPOINT, +#if PUBNUB_USE_OBJECTS_API + /** The endpoint to access and manage App Context objects. */ + PNRC_APP_CONTEXT_ENDPOINT, +#endif // #if PUBNUB_USE_OBJECTS_API +#if PUBNUB_USE_ACTIONS_API + /** The endpoint to access and manage reactions for a specific message. */ + PNRC_MESSAGE_REACTIONS_ENDPOINT, +#endif // #if PUBNUB_USE_ACTIONS_API +#if PUBNUB_USE_GRANT_TOKEN_API + /** The endpoint to access and manage permissions (auth keys / tokens). */ + PNRC_PAM_ENDPOINT, +#endif // #if PUBNUB_USE_GRANT_TOKEN_API + /** Number of known endpoint groups. */ + PNRC_ENDPOINTS_COUNT +} pubnub_endpoint_t; + + +// ---------------------------------------------- +// Functions +// ---------------------------------------------- + +/** + * @brief Set failed request retry configuration. + * @code + * // Create default configuration with linear retry policy. + * pubnub_retry_configuration_t* configuration = + * pubnub_retry_configuration_linear_alloc(void); + * pubnub_set_retry_configuration(pb, configuration); + * @endcode + * + * @note `PubNub` context will free configuration instance on `deinit`. + * + * @param pb Pointer to the PubNub context which failed to complete + * transaction and will retry them depending from + * configuration. + * @param configuration Pointer to the request retry configuration, which will + * be used by PubNub to compute delays between retry + * attempts. + * + * @see pubnub_retry_configuration_linear_alloc + */ +PUBNUB_EXTERN void pubnub_set_retry_configuration( + pubnub_t* pb, + pubnub_retry_configuration_t* configuration); + +/** + * @brief Create a request retry configuration with a linear retry policy. + * @code + * // Create default configuration with linear retry policy. + * pubnub_retry_configuration_t* configuration = + * pubnub_retry_configuration_linear_alloc(void); + * @endcode + * + * Configurations with a linear retry policy will use equal delays between retry + * attempts. The default implementation uses a \b 2 seconds delay and \b 10 + * maximum retry attempts. + * + * @note The PubNub client will schedule request retries after a calculated + * delay, but the actual retry may not happen at the same moment (there is + * a small leeway). + * + * @return Pointer to the ready to use PubNub request retry configuration or + * `NULL` in case of insufficient memory error. The returned pointer + * must be passed to the `pubnub_retry_configuration_free` to avoid a + * memory leak. + * + * @see pubnub_set_retry_configuration + * @see pubnub_retry_configuration_free + */ +PUBNUB_EXTERN pubnub_retry_configuration_t* + pubnub_retry_configuration_linear_alloc(void); + +/** + * @brief Create a request retry configuration with a linear retry policy and + * list of API groups which won't be retried. + * @code + * // Create default configuration with linear retry policy and excluded + * // endpoints: publish and history. + * pubnub_retry_configuration_t* configuration = + * pubnub_retry_configuration_linear_alloc_with_excluded( + * PNRC_MESSAGE_SEND_ENDPOINT, + * PNRC_MESSAGE_STORAGE_ENDPOINT, + * -1); + * @endcode + * + * Configurations with a linear retry policy will use equal delays between retry + * attempts. The default implementation uses a \b 2 seconds delay and \b 10 + * maximum retry attempts. + * + * @note The PubNub client will schedule request retries after a calculated + * delay, but the actual retry may not happen at the same moment (there is + * a small leeway). + * + * @param excluded Endpoint for which retry shouldn't be used. + * @param ... Variadic list of endpoints for which retry shouldn't be used. + *
\b Important: list should be terminated by \b -1. + * @return Pointer to the ready to use PubNub request retry configuration or + * `NULL` in case of insufficient memory error. The returned pointer + * must be passed to the `pubnub_retry_configuration_free` to avoid a + * memory leak. + * + * @see pubnub_set_retry_configuration + * @see pubnub_retry_configuration_free + */ +PUBNUB_EXTERN pubnub_retry_configuration_t* +pubnub_retry_configuration_linear_alloc_with_excluded(int excluded, ...); + +/** + * @brief Create a request retry configuration with a linear retry policy and + * list of API groups which won't be retried. + * @code + * // Create custom configuration with linear retry policy and excluded + * // endpoints: publish and history. + * pubnub_retry_configuration_t* configuration = + * pubnub_retry_configuration_linear_alloc_with_options( + * 5, + * 3, + * PNRC_MESSAGE_SEND_ENDPOINT, + * PNRC_MESSAGE_STORAGE_ENDPOINT, + * -1); + * @endcode + * + * Configurations with a linear retry policy will use equal delays between retry + * attempts. The default implementation uses a \b 2 seconds delay and \b 10 + * maximum retry attempts. + * + * @note The PubNub client will schedule request retries after a calculated + * delay, but the actual retry may not happen at the same moment (there is + * a small leeway). + * + * @param delay Delay between failed requests automatically retries + * attempts. \b Important: The minimum allowed delay + * is \b 2.0. + * @param maximum_retry The number of failed requests that should be retried + * automatically before reporting an error. + * \b Important: The maximum allowed number of retries + * is \b 10. + * @param excluded Endpoint for which retry shouldn't be used. + * @param ... Variadic list of endpoints for which retry shouldn't be + * used. \b Important: list should be terminated by \b -1. + * @return Pointer to the ready to use PubNub request retry configuration or + * `NULL` in case of insufficient memory error. The returned pointer + * must be passed to the `pubnub_retry_configuration_free` to avoid a + * memory leak. + * + * @see pubnub_set_retry_configuration + * @see pubnub_retry_configuration_free + */ +PUBNUB_EXTERN pubnub_retry_configuration_t* +pubnub_retry_configuration_linear_alloc_with_options( + int delay, + int maximum_retry, + int excluded, + ...); + +/** + * @brief Create a request retry configuration with a exponential retry policy. + * @code + * // Create default configuration with exponential retry policy. + * pubnub_retry_configuration_t* configuration = + * pubnub_retry_configuration_exponential_alloc(void); + * @endcode + * + * Configurations with an exponential retry policy will use `minimum_delay` that + * will exponentially increase with each failed request retry attempt.
+ * The default implementation uses a \b 2 seconds minimum, \b 150 maximum + * delays, and \b 6 maximum retry attempts. + * + * @note The PubNub client will schedule request retries after a calculated + * delay, but the actual retry may not happen at the same moment (there is + * a small leeway). + * + * @return Pointer to the ready to use PubNub request retry configuration or + * `NULL` in case of insufficient memory error. The returned pointer + * must be passed to the `pubnub_retry_configuration_free` to avoid a + * memory leak. + * + * @see pubnub_set_retry_configuration + * @see pubnub_retry_configuration_free + */ +PUBNUB_EXTERN pubnub_retry_configuration_t* + pubnub_retry_configuration_exponential_alloc(void); + +/** + * @brief Create a request retry configuration with a exponential retry policy + * and list of API groups which won't be retried. + * @code + * // Create default configuration with exponential retry policy and excluded + * // endpoints: publish and history. + * pubnub_retry_configuration_t* configuration = + * pubnub_retry_configuration_exponential_alloc_with_excluded( + * PNRC_MESSAGE_SEND_ENDPOINT, + * PNRC_MESSAGE_STORAGE_ENDPOINT, + * -1); + * @endcode + * + * Configurations with an exponential retry policy will use `minimum_delay` that + * will exponentially increase with each failed request retry attempt.
+ * The default implementation uses a \b 2 seconds minimum, \b 150 maximum + * delays, and \b 6 maximum retry attempts. + * + * @note The PubNub client will schedule request retries after a calculated + * delay, but the actual retry may not happen at the same moment (there is + * a small leeway). + * + * @param excluded Endpoint for which retry shouldn't be used. + * @param ... Variadic list of endpoints for which retry shouldn't be used. + * \b Important: list should be terminated by \b -1. + * @return Pointer to the ready to use PubNub request retry configuration or + * `NULL` in case of insufficient memory error. The returned pointer + * must be passed to the `pubnub_retry_configuration_free` to avoid a + * memory leak. + * + * @see pubnub_set_retry_configuration + * @see pubnub_retry_configuration_free + */ +PUBNUB_EXTERN pubnub_retry_configuration_t* +pubnub_retry_configuration_exponential_alloc_with_excluded(int excluded, ...); + +/** + * @brief Create a request retry configuration with a exponential retry policy + * and list of API groups which won't be retried. + * @code + * // Create custom configuration with linear retry policy and excluded + * // endpoints: publish and history. + * pubnub_retry_configuration_t* configuration = + * pubnub_retry_configuration_linear_alloc_with_options( + * 5, + * 20, + * 6, + * PNRC_MESSAGE_SEND_ENDPOINT, + * PNRC_MESSAGE_STORAGE_ENDPOINT, + * -1); + * @endcode + * + * Configurations with an exponential retry policy will use `minimum_delay` that + * will exponentially increase with each failed request retry attempt.
+ * The default implementation uses a \b 2 seconds minimum, \b 150 maximum + * delays, and \b 6 maximum retry attempts. + * + * @note The PubNub client will schedule request retries after a calculated + * delay, but the actual retry may not happen at the same moment (there is + * a small leeway). + * + * @param minimum_delay Base delay, which will be used to calculate the next + * delay depending on the number of retry attempts. + * \b Important: The minimum allowed delay is \b 2. + * @param maximum_delay Maximum allowed computed delay that should be used + * between retry attempts. + * @param maximum_retry The number of failed requests that should be retried + * automatically before reporting an error. + * \b Important: The maximum allowed number of retries + * is \b 10. + * @param excluded Endpoint for which retry shouldn't be used. + * @param ... Variadic list of endpoints for which retry shouldn't be + * used. \b Important: list should be terminated by \b -1. + * @return Pointer to the ready to use PubNub request retry configuration or + * `NULL` in case of insufficient memory error. The returned pointer + * must be passed to the `pubnub_retry_configuration_free` to avoid a + * memory leak. + * + * @see pubnub_set_retry_configuration + * @see pubnub_retry_configuration_free + */ +PUBNUB_EXTERN pubnub_retry_configuration_t* +pubnub_retry_configuration_exponential_alloc_with_options( + int minimum_delay, + int maximum_delay, + int maximum_retry, + int excluded, + ...); + +/** + * @brief Release resources used by request retry configuration object. + * + * @param configuration Pointer to the configuration object, which should free + * up resources. + */ +PUBNUB_EXTERN void pubnub_retry_configuration_free( + pubnub_retry_configuration_t** configuration); +#endif // #if PUBNUB_USE_RETRY_CONFIGURATION +#endif // #ifndef PUBNUB_RETRY_CONFIGURATION_H diff --git a/core/pubnub_retry_configuration_internal.h b/core/pubnub_retry_configuration_internal.h new file mode 100644 index 00000000..23d904f6 --- /dev/null +++ b/core/pubnub_retry_configuration_internal.h @@ -0,0 +1,41 @@ +/* -*- c-file-style:"stroustrup"; indent-tabs-mode: nil -*- */ +#ifndef PUBNUB_RETRY_CONFIGURATION_INTERNAL_H +#define PUBNUB_RETRY_CONFIGURATION_INTERNAL_H + + +/** + * @file pubnub_retry_configuration_internal.h + * @brief PubNub request retry configuration. + */ + +#include "pubnub_retry_configuration.h" + +#include +#include + + + +// ---------------------------------------------- +// Functions +// ---------------------------------------------- + +/** + * @brief Check whether transaction and it outcome can be retried at all. + * + * Not all transactions can be handled and not all results makes sense to retry. + * + * @param pb Pointer to the PubNub context for which results of last transaction + * should be checked. + * @return `true` in case if it really failed and it wasn't because of user. + */ +bool pubnub_retry_configuration_retryable_result_(pubnub_t* pb); + +/** + * @brief Check whether `transaction` can be retried after failure. + * + * @param pb Pointer to the PubNub context for which delay should be computed. + * @return Next retry attempt delay value or \b -1 in case if retry is + * impossible. + */ +size_t pubnub_retry_configuration_delay_(pubnub_t* pb); +#endif //PUBNUB_RETRY_CONFIGURATION_INTERNAL_H diff --git a/core/pubnub_server_limits.h b/core/pubnub_server_limits.h index 7d92ec89..1259f464 100644 --- a/core/pubnub_server_limits.h +++ b/core/pubnub_server_limits.h @@ -5,7 +5,10 @@ /** Minimal presence heartbeat interval supported by Pubnub, in seconds. */ -#define PUBNUB_MINIMAL_HEARTBEAT_INTERVAL 270 +#define PUBNUB_MINIMAL_HEARTBEAT_INTERVAL 300 +#define PUBNUB_MINIMUM_PRESENCE_HEARTBEAT_VALUE 20 +/** Default presence heartbeat value to use if user didn't set any. */ +#define PUBNUB_DEFAULT_PRESENCE_HEARTBEAT_VALUE 300 /** The maximum channel name length */ #define PUBNUB_MAX_CHANNEL_NAME_LENGTH 92 diff --git a/core/pubnub_subscribe_event_engine.c b/core/pubnub_subscribe_event_engine.c new file mode 100644 index 00000000..70b7f9a4 --- /dev/null +++ b/core/pubnub_subscribe_event_engine.c @@ -0,0 +1,938 @@ +/* -*- c-file-style:"stroustrup"; indent-tabs-mode: nil -*- */ + +#include "pubnub_subscribe_event_engine_internal.h" + +#include + +#include "core/pubnub_entities_internal.h" +#include "core/pbcc_memory_utils.h" +#include "core/pubnub_assert.h" +#include "core/pubnub_mutex.h" +#include "core/pubnub_log.h" +#include "pubnub_internal.h" +#include "lib/pbhash_set.h" +#include "core/pubnub_helper.h" + + +// ---------------------------------------------- +// Constants +// ---------------------------------------------- + +// Pre-allocated subscription object pointers array length. +#define SUBSCRIPTIONS_LENGTH 10 + + +// ---------------------------------------------- +// Function prototypes +// ---------------------------------------------- + +/** + * @brief Add subscription to subscription set. + * + * @param set Pointer to the subscription set, which should be + * modified. + * @param sub Pointer to the subscription, which should be added to + * the set. + * @param notify_change Whether Subscribe Event Engine should be notified about + * change or not. + * + * @return Result of subscription add. + */ +static enum pubnub_res pubnub_subscription_set_add_( + const pubnub_subscription_set_t* set, + pubnub_subscription_t* sub, + bool notify_change); + +/** + * @brief Remove subscription from subscription set. + * + * @param set Pointer to the subscription set, which should be modified. + * @param sub Pointer to the subscription, which should be removed from the set. + * @param notify_change Whether Subscribe Event Engine should be notified about + * change or not. + * @return Result of subscription removal from the subscription set operation. + */ +static enum pubnub_res pubnub_subscription_set_remove_( + const pubnub_subscription_set_t* set, + pubnub_subscription_t** sub, + bool notify_change); + +/** + * @brief Create entity subscription. + * + * Subscription is high-level access to the entity which is used in the + * subscription loop. Subscriptions make it possible to add real-time update + * listeners for specific entity (channel, group, or App Context object). It is + * possible to `subscribe` and `unsubscribe` using a specific subscription. + * + * @note Pointer to the subscriptions later can be received with + * `pubnub_subscriptions` function. + * + * @param entity Pointer to the PubNub entity which should be used in + * subscription. + * @param options Pointer to the subscription configuration options. Set to + * `NULL` if options not required. + * @return Pointer to the ready to use PubNub subscription, or `NULL` will be + * returned in case of error (in most of the cases caused by + * insufficient memory for structure allocation). The returned pointer + * must be passed to the `pubnub_subscription_free` to avoid a memory + * leak. + * + * @see pubnub_subscriptions + */ +static pubnub_subscription_t* pubnub_subscription_alloc_( + pubnub_entity_t* entity, + const pubnub_subscription_options_t* options); + +/** + * @brief Update number references to the `subscription`. + * + * \b Warning: Application execution will be terminated if `subscription` is + * `NULL`. + * + * @param subscription Subscription object for which number references should be + * updated. + * @param increase Whether reference count should be increased or decreased. + */ +static void subscription_reference_count_update_( + pubnub_subscription_t* subscription, + bool increase); + +/** + * @brief Create a subscription set. + * + * @param options Subscription configuration options. Set to `NULL` if options + * not required. + * @param length Desired length of a pre-allocated set for subscription + * pointers. + * @return Ready to use a subscription set object, or `NULL` will be returned in + * case of error (in most of the cases caused by insufficient memory for + * structure allocation). The returned pointer must be passed to the + * `pubnub_subscription_set_free` to avoid a memory leak. + */ +static pubnub_subscription_set_t* subscription_set_alloc_( + const pubnub_subscription_options_t* options, + int length); + +/** +* @brief Update the number of subscription set which refer to `subscription`. + * + * \b Warning: Application execution will be terminated if `set` is `NULL`. + * + * @param set Subscription set object for which number references should be + * updated. + * @param increase Whether reference count should be increased or decreased. + */ +static void subscription_set_reference_count_update_( + pubnub_subscription_set_t* set, + bool increase); + +/** + * @brief Release resources used by subscription from subscription set. + * + * @param sub Pointer to the subscription, which should free up resources. + * @return `false` if there are more references to the subscription. + */ +static bool pubnub_subscription_set_subscription_free_( + pubnub_subscription_t* sub); + +/** + * @brief Create subscribable object. + * + * @param location Subscribable identifier location in subscribe REST API URI. + * @param id Subscribable identifier. + * @param presence Whether subscribable represent presence events stream or not. + * @return Ready to use a subscribable object. The returned pointer + * must be passed to the `_pubnub_subscribable_free` to avoid a memory + * leak. + */ +static pubnub_subscribable_t* pubnub_subscribable_alloc_( + pubnub_subscribable_location location, + struct pubnub_char_mem_block* id, + bool presence); + + +// ---------------------------------------------- +// Functions +// ---------------------------------------------- + +pubnub_subscription_options_t pubnub_subscription_options_defopts(void) +{ + return (pubnub_subscription_options_t){ false }; +} + +pubnub_subscription_t* pubnub_subscription_alloc( + pubnub_entity_t* entity, + const pubnub_subscription_options_t* options) +{ + return pubnub_subscription_alloc_(entity, options); +} + +bool pubnub_subscription_free(pubnub_subscription_t** sub) +{ + if (NULL == sub || NULL == *sub) { return false; } + + pubnub_mutex_lock((*sub)->mutw); + if (pubnub_subscription_free_(*sub)) { + pubnub_mutex_unlock((*sub)->mutw); + pubnub_mutex_destroy((*sub)->mutw); + *sub = NULL; + return true; + } + pubnub_mutex_unlock((*sub)->mutw); + + return false; +} + +bool pubnub_subscription_free_(pubnub_subscription_t* sub) +{ + if (NULL == sub) { return false; } + + if (0 == pbref_counter_free(sub->counter)) { + pubnub_entity_free((void**)&sub->entity); + free(sub); + return true; + } + + return false; +} + +pubnub_subscription_t** pubnub_subscriptions(const pubnub_t* pb, size_t* count) +{ + PUBNUB_ASSERT(pb_valid_ctx_ptr(pb)); + + size_t cnt = 0; + pubnub_subscription_t** subscriptions = pbcc_subscribe_ee_subscriptions( + pb->core.subscribe_ee, + &cnt); + + if (NULL != count) { *count = cnt; } + if (NULL == subscriptions) { + PUBNUB_LOG_ERROR("pubnub_subscriptions: failed to allocate memory for " + "subscriptions list\n"); + } + else if (0 == cnt) { + PUBNUB_LOG_INFO("pubnub_subscriptions: subscriptions list is empty"); + } + + return subscriptions; +} + +pubnub_subscription_set_t* +pubnub_subscription_set_alloc_with_entities( + pubnub_entity_t** entities, + const int entities_count, + const pubnub_subscription_options_t* options) +{ + PUBNUB_ASSERT_OPT(NULL != entities); + + const int length = entities_count - entities_count % SUBSCRIPTIONS_LENGTH + + SUBSCRIPTIONS_LENGTH; + const pubnub_t* pb = NULL; + pubnub_subscription_set_t* set = subscription_set_alloc_(options, length); + if (NULL == set) { return NULL; } + + for (int i = 0; i < entities_count; ++i) { + pubnub_entity_t* entity = entities[i]; + pubnub_subscription_t* sub = + pubnub_subscription_alloc_(entity, NULL); + + if (NULL == pb) { pb = entity->pb; } + if (NULL == sub || + PNR_OUT_OF_MEMORY == pubnub_subscription_set_add(set, sub)) { + pubnub_subscription_set_free(&set); + return NULL; + } + /** Release extra `subscription` reference because `set` retained it. */ + if (NULL != sub) { pubnub_subscription_free(&sub); } + } + + if (pb) { set->ee = pb->core.subscribe_ee; } + else { + PUBNUB_LOG_ERROR( + "pubnub_subscription_set_alloc_with_entities: invalid PubNub " + "context\n"); + pubnub_subscription_set_free(&set); + return NULL; + } + + return set; +} + +pubnub_subscription_set_t* +pubnub_subscription_set_alloc_with_subscriptions( + pubnub_subscription_t* sub1, + pubnub_subscription_t* sub2, + const pubnub_subscription_options_t* options) +{ + PUBNUB_ASSERT_OPT(NULL != sub1); + PUBNUB_ASSERT_OPT(NULL != sub2); + + pubnub_subscription_set_t* set = subscription_set_alloc_( + options, + SUBSCRIPTIONS_LENGTH); + if (NULL == set) { return NULL; } + + if (PNR_OUT_OF_MEMORY == pubnub_subscription_set_add(set, sub1) || + PNR_OUT_OF_MEMORY == pubnub_subscription_set_add(set, sub2)) { + pubnub_subscription_set_free(&set); + return NULL; + } + + set->ee = sub1->ee; + + return set; +} + +enum pubnub_res pubnub_subscription_set_add( + pubnub_subscription_set_t* set, + pubnub_subscription_t* sub) +{ + PUBNUB_ASSERT_OPT(NULL != set); + PUBNUB_ASSERT_OPT(NULL != sub); + + pubnub_mutex_lock(set->mutw); + const enum pubnub_res rslt = pubnub_subscription_set_add_(set, sub, true); + pubnub_mutex_unlock(set->mutw); + + return rslt; +} + +enum pubnub_res pubnub_subscription_set_remove( + pubnub_subscription_set_t* set, + pubnub_subscription_t** sub) +{ + PUBNUB_ASSERT_OPT(NULL != set); + PUBNUB_ASSERT_OPT(NULL != sub); + PUBNUB_ASSERT_OPT(NULL != *sub); + + pubnub_mutex_lock(set->mutw); + const enum pubnub_res rslt = + pubnub_subscription_set_remove_(set, sub, true); + pubnub_mutex_unlock(set->mutw); + + return rslt; +} + +enum pubnub_res pubnub_subscription_set_union( + pubnub_subscription_set_t* set, + pubnub_subscription_set_t* other_set) +{ + PUBNUB_ASSERT_OPT(NULL != set); + PUBNUB_ASSERT_OPT(NULL != other_set); + + pubnub_mutex_lock(other_set->mutw); + size_t count; + pubnub_subscription_t** subs = (pubnub_subscription_t**) + pbhash_set_elements(set->subscriptions, &count); + + for (int i = 0; i < count; ++i) { + if (PNR_OUT_OF_MEMORY == pubnub_subscription_set_add_( + set, + subs[i], + i + 1 == count)) { + pubnub_mutex_unlock(other_set->mutw); + if (NULL != subs) { free(subs); } + return PNR_OUT_OF_MEMORY; + } + } + pubnub_mutex_unlock(other_set->mutw); + if (NULL != subs) { free(subs); } + + return PNR_OK; +} + +void pubnub_subscription_set_subtract( + const pubnub_subscription_set_t* set, + pubnub_subscription_set_t* other_set) +{ + PUBNUB_ASSERT_OPT(NULL != set); + PUBNUB_ASSERT_OPT(NULL != other_set); + + pubnub_mutex_lock(other_set->mutw); + size_t count; + pubnub_subscription_t** subs = (pubnub_subscription_t**) + pbhash_set_elements(set->subscriptions, &count); + + for (int i = 0; i < count; ++i) { + pubnub_subscription_set_remove_(set, &subs[i], i + 1 == count); + } + pubnub_mutex_unlock(other_set->mutw); + if (NULL != subs) { free(subs); } +} + +pubnub_subscription_set_t** pubnub_subscription_sets( + const pubnub_t* pb, + size_t* count) +{ + PUBNUB_ASSERT(pb_valid_ctx_ptr(pb)); + size_t cnt = 0; + pubnub_subscription_set_t** sets = pbcc_subscribe_ee_subscription_sets( + pb->core.subscribe_ee, + &cnt); + + if (NULL != count) { *count = cnt; } + if (0 == cnt) { + PUBNUB_LOG_INFO( + "pubnub_subscription_sets: subscriptions list is empty"); + } + else if (NULL == sets) { + PUBNUB_LOG_ERROR("pubnub_subscription_sets: failed to allocate memory"); + } + + return sets; +} + +pubnub_subscription_t** pubnub_subscription_set_subscriptions( + pubnub_subscription_set_t* set, + size_t* count) +{ + PUBNUB_ASSERT_OPT(NULL != set); + + pubnub_mutex_lock(set->mutw); + size_t cnt; + pubnub_subscription_t** subs = (pubnub_subscription_t**) + pbhash_set_elements(set->subscriptions, &cnt); + + if (NULL != count) { *count = cnt; } + if (NULL == subs) { + PUBNUB_LOG_ERROR("pubnub_subscription_set_subscriptions: failed to " + "allocate memory for subscriptions list\n"); + } + else if (0 == cnt) { + PUBNUB_LOG_INFO( + "pubnub_subscription_set_subscriptions: subscriptions list is " + "empty"); + } + pubnub_mutex_unlock(set->mutw); + + return subs; +} + +bool pubnub_subscription_set_free(pubnub_subscription_set_t** set) +{ + if (NULL == set || NULL == *set) { return false; } + + pubnub_mutex_lock((*set)->mutw); + if (pubnub_subscription_set_free_(*set)) { + pubnub_mutex_unlock((*set)->mutw); + pubnub_mutex_destroy((*set)->mutw); + *set = NULL; + return true; + } + pubnub_mutex_unlock((*set)->mutw); + + return false; +} + +bool pubnub_subscription_set_free_(pubnub_subscription_set_t* set) +{ + if (NULL == set) { return false; } + + if (0 == pbref_counter_free(set->counter)) { + if (NULL != set->subscriptions) + pbhash_set_free(&set->subscriptions); + free(set); + return true; + } + + return false; +} + +void pubnub_subscribe_set_filter_expression( + const pubnub_t* pb, + const char* filter_expr) +{ + if (NULL == pb) { return; } + pbcc_subscribe_ee_set_filter_expression(pb->core.subscribe_ee, filter_expr); +} + +void pubnub_subscribe_set_heartbeat( + const pubnub_t* pb, + const unsigned heartbeat) +{ + if (NULL == pb) { return; } + pbcc_subscribe_ee_set_heartbeat(pb->core.subscribe_ee, heartbeat); +} + +pubnub_subscribe_cursor_t pubnub_subscribe_cursor(const char* timetoken) +{ + pubnub_subscribe_cursor_t cursor; + + if (NULL != timetoken) { + size_t token_len = strlen(timetoken); + memcpy(cursor.timetoken, timetoken, token_len); + cursor.timetoken[token_len] = '\0'; + } + else { + cursor.timetoken[0] = '0'; + cursor.timetoken[1] = '\0'; + } + cursor.region = 0; + + return cursor; +} + +enum pubnub_res pubnub_subscribe_with_subscription( + pubnub_subscription_t* sub, + const pubnub_subscribe_cursor_t* cursor) +{ + if (NULL == sub) { return PNR_INVALID_PARAMETERS; } + + const enum pubnub_res rslt = pbcc_subscribe_ee_subscribe_with_subscription( + sub->ee, + sub, + cursor); + + if (PNR_OUT_OF_MEMORY != rslt) + subscription_reference_count_update_(sub, true); + + return rslt; +} + +enum pubnub_res pubnub_unsubscribe_with_subscription( + pubnub_subscription_t** sub) +{ + if (NULL == sub || NULL == *sub) { return PNR_INVALID_PARAMETERS; } + + const enum pubnub_res rslt = + pbcc_subscribe_ee_unsubscribe_with_subscription((*sub)->ee, *sub); + + if (PNR_OK == rslt || PNR_SUB_NOT_FOUND == rslt) { + /** + * Decrease subscription reference counter only if it has been used for + * previous subscriptions. + */ + if (PNR_OK == rslt) + subscription_reference_count_update_(*sub, false); + + pubnub_subscription_free(sub); + } + + return rslt; +} + +enum pubnub_res pubnub_subscribe_with_subscription_set( + pubnub_subscription_set_t* set, + const pubnub_subscribe_cursor_t* cursor) +{ + if (NULL == set) { return PNR_INVALID_PARAMETERS; } + + const enum pubnub_res rslt = + pbcc_subscribe_ee_subscribe_with_subscription_set( + set->ee, + set, + cursor); + + if (PNR_OUT_OF_MEMORY != rslt) { + subscription_set_reference_count_update_(set, true); + pubnub_mutex_lock(set->mutw); + set->subscribed = true; + pubnub_mutex_unlock(set->mutw); + } + + return rslt; +} + +enum pubnub_res pubnub_unsubscribe_with_subscription_set( + pubnub_subscription_set_t** set) +{ + if (NULL == set || NULL == *set) { return PNR_INVALID_PARAMETERS; } + + const enum pubnub_res rslt = + pbcc_subscribe_ee_unsubscribe_with_subscription_set((*set)->ee, *set); + + if (PNR_OK == rslt || PNR_SUB_NOT_FOUND == rslt) { + /** + * Decrease subscription set reference counter only if it has been used + * for previous subscriptions. + */ + if (PNR_OK == rslt) + subscription_set_reference_count_update_(*set, false); + + pubnub_mutex_lock((*set)->mutw); + (*set)->subscribed = false; + pubnub_mutex_unlock((*set)->mutw); + pubnub_subscription_set_free(set); + } + + return rslt; +} + +enum pubnub_res pubnub_disconnect(const pubnub_t* pb) +{ + PUBNUB_ASSERT(pb_valid_ctx_ptr(pb)); + + return pbcc_subscribe_ee_disconnect(pb->core.subscribe_ee); +} + +enum pubnub_res pubnub_reconnect( + const pubnub_t* pb, + const pubnub_subscribe_cursor_t* cursor) +{ + PUBNUB_ASSERT(pb_valid_ctx_ptr(pb)); + + const pubnub_subscribe_cursor_t subscribe_cursor = + NULL != cursor ? *cursor : pubnub_subscribe_cursor(NULL); + return pbcc_subscribe_ee_reconnect(pb->core.subscribe_ee, subscribe_cursor); +} + +enum pubnub_res pubnub_unsubscribe_all(const pubnub_t* pb) +{ + PUBNUB_ASSERT(pb_valid_ctx_ptr(pb)); + + return pbcc_subscribe_ee_unsubscribe_all(pb->core.subscribe_ee); +} + +enum pubnub_res pubnub_subscription_set_add_( + const pubnub_subscription_set_t* set, + pubnub_subscription_t* sub, + const bool notify_change) +{ + const bool subscribed = set->subscribed; + enum pubnub_res rslt = PNR_OK; + + if (pbhash_set_contains(set->subscriptions, sub->entity->id.ptr)) { + PUBNUB_LOG_INFO( + "pubnub_subscription_set_add: subscription (sub=%p) for entity " + "('%s') already added into set", + sub, + sub->entity->id.ptr); + return PNR_SUB_ALREADY_ADDED; + } + + if (PBHSR_OUT_OF_MEMORY == pbhash_set_add(set->subscriptions, + sub->entity->id.ptr, + sub)) { + PUBNUB_LOG_ERROR( + "pubnub_subscription_set_add: failed to allocate memory " + "for subscription\n"); + return PNR_OUT_OF_MEMORY; + } + + subscription_reference_count_update_(sub, true); + + if (subscribed && notify_change) { + /** Notify changes in active subscription set. */ + rslt = pbcc_subscribe_ee_change_subscription_with_subscription_set( + set->ee, + NULL, + NULL, + true); + } + + return rslt; +} + +enum pubnub_res pubnub_subscription_set_remove_( + const pubnub_subscription_set_t* set, + pubnub_subscription_t** sub, + const bool notify_change) +{ + const bool subscribed = set->subscribed; + + if (!pbhash_set_contains(set->subscriptions, (*sub)->entity->id.ptr)) { + PUBNUB_LOG_INFO( + "pubnub_subscription_set_remove: subscription (sub=%p) for entity " + "('%s') not found", + *sub, + (*sub)->entity->id.ptr); + return PNR_SUB_NOT_FOUND; + } + + /** Preventing `pbhash_set` (set->subscriptions) from freeing `sub`. */ + pubnub_subscription_t* stored_subscription = (pubnub_subscription_t*) + pbhash_set_element(set->subscriptions, (*sub)->entity->id.ptr); + const bool same_object = stored_subscription == *sub; + subscription_reference_count_update_(stored_subscription, true); + pbhash_set_remove(set->subscriptions, + (void**)&stored_subscription->entity->id.ptr, + (void**)&stored_subscription); + + enum pubnub_res rslt = PNR_OK; + if (subscribed && notify_change) { + /** Notify changes in active subscription set. */ + rslt = pbcc_subscribe_ee_change_subscription_with_subscription_set( + set->ee, + set, + *sub, + false); + } + + /** Trying to free up memory used for `sub`. */ + pubnub_subscription_free(&stored_subscription); + if (NULL == stored_subscription && same_object) { *sub = NULL; } + + return rslt; +} + +pubnub_subscription_t* pubnub_subscription_alloc_( + pubnub_entity_t* entity, + const pubnub_subscription_options_t* options) +{ + PUBNUB_ASSERT_OPT(NULL != entity); + PUBNUB_ASSERT(pb_valid_ctx_ptr(entity->pb)); + + PBCC_ALLOCATE_TYPE(subscription, pubnub_subscription_t, true, NULL); + pubnub_subscription_options_t opts = pubnub_subscription_options_defopts(); + if (NULL != options) { + opts.receive_presence_events = options->receive_presence_events; + } + + pubnub_mutex_init(subscription->mutw); + subscription->options = opts; + subscription->counter = pbref_counter_alloc(); + subscription->entity = entity; + subscription->ee = entity->pb->core.subscribe_ee; + entity_reference_count_update_(entity, true); + + return subscription; +} + +void subscription_reference_count_update_( + pubnub_subscription_t* subscription, + const bool increase) +{ + PUBNUB_ASSERT_OPT(NULL != subscription); + + pubnub_mutex_lock(subscription->mutw); + if (increase) { pbref_counter_increment(subscription->counter); } + else { pbref_counter_decrement(subscription->counter); } + pubnub_mutex_unlock(subscription->mutw); +} + +pbhash_set_t* pubnub_subscription_subscribables_( + const pubnub_subscription_t* sub, + const pubnub_subscription_options_t* options) +{ + pubnub_subscription_options_t opts = sub->options; + if (NULL != options) { + opts.receive_presence_events = sub->options.receive_presence_events; + } + + const pubnub_subscribable_location location = + sub->entity->type == PUBNUB_ENTITY_CHANNEL_GROUP + ? SUBSCRIBABLE_LOCATION_QUERY + : SUBSCRIBABLE_LOCATION_PATH; + pbhash_set_t* hash = pbhash_set_alloc( + opts.receive_presence_events ? 2 : 1, + PBHASH_SET_CHAR_CONTENT_TYPE, + NULL); + if (NULL == hash) { + PUBNUB_LOG_ERROR("pubnub_subscription_subscribables: failed to allocate" + " memory for subscription's subscribable list\n"); + return hash; + } + + pubnub_subscribable_t* regular = pubnub_subscribable_alloc_( + location, + &sub->entity->id, + false); + if (NULL == regular) { + PUBNUB_LOG_ERROR("pubnub_subscription_subscribables: failed to allocate" + " memory for regular subscribable identifier\n"); + pbhash_set_free(&hash); + return hash; + } + + if (PBHSR_OUT_OF_MEMORY == + pbhash_set_add(hash, regular->id->ptr, regular)) { + PUBNUB_LOG_ERROR( + "pubnub_subscription_subscribables: failed to allocate memory " + "for node to store regular subscribable identifier in hash set\n"); + pubnub_subscribable_free_(regular); + pbhash_set_free(&hash); + return hash; + } + + if (!opts.receive_presence_events) { return hash; } + + pubnub_subscribable_t* presence = pubnub_subscribable_alloc_( + location, + &sub->entity->id, + true); + if (NULL == presence) { + PUBNUB_LOG_ERROR("pubnub_subscription_subscribables: failed to allocate" + " memory for presence subscribable identifier\n"); + pbhash_set_free_with_destructor( + &hash, + (pbhash_set_element_free)pubnub_subscribable_free_); + return hash; + } + + if (PBHSR_OUT_OF_MEMORY == pbhash_set_add( + hash, + presence->id->ptr, + presence)) { + PUBNUB_LOG_ERROR( + "pubnub_subscription_subscribables: failed to allocate memory for " + "node to store presence subscribable identifier in hash set\n"); + pbhash_set_free_with_destructor( + &hash, + (pbhash_set_element_free)pubnub_subscribable_free_); + pubnub_subscribable_free_(presence); + } + + return hash; +} + +pubnub_subscription_set_t* subscription_set_alloc_( + const pubnub_subscription_options_t* options, + const int length) +{ + PBCC_ALLOCATE_TYPE(subscription_set, pubnub_subscription_set_t, true, NULL); + pubnub_subscription_options_t opts = pubnub_subscription_options_defopts(); + if (NULL != options) { + opts.receive_presence_events = options->receive_presence_events; + } + + pubnub_mutex_init(subscription_set->mutw); + subscription_set->options = opts; + subscription_set->subscriptions = pbhash_set_alloc( + length, + PBHASH_SET_CHAR_CONTENT_TYPE, + (pbhash_set_element_free)pubnub_subscription_set_subscription_free_); + + if (NULL == subscription_set->subscriptions) { + PUBNUB_LOG_ERROR("subscription_set_alloc: failed to allocate memory for" + " subscriptions\n"); + pubnub_subscription_set_free(&subscription_set); + return NULL; + } + + subscription_set->subscribed = false; + subscription_set->counter = pbref_counter_alloc(); + + return subscription_set; +} + +void subscription_set_reference_count_update_( + pubnub_subscription_set_t* set, + const bool increase) +{ + pubnub_mutex_lock(set->mutw); + if (increase) { pbref_counter_increment(set->counter); } + else { pbref_counter_decrement(set->counter); } + pubnub_mutex_unlock(set->mutw); +} + +bool pubnub_subscription_set_subscription_free_(pubnub_subscription_t* sub) +{ + return pubnub_subscription_free(&sub); +} + +pbhash_set_t* pubnub_subscription_set_subscribables_( + const pubnub_subscription_set_t* set) +{ + size_t count; + const pubnub_subscription_t** subs = (const pubnub_subscription_t**) + pbhash_set_elements(set->subscriptions, &count); + + if (NULL == subs) { + PUBNUB_LOG_ERROR("pubnub_subscription_set_subscribables: failed to " + "allocate memory for subscriptions list\n"); + } + else if (0 == count) { + PUBNUB_LOG_INFO("pubnub_subscription_set_subscribables: subscriptions " + "list is empty"); + } + + pbhash_set_t* hash = pbhash_set_alloc( + (0 == count ? 1 : count) * ( + set->options.receive_presence_events ? 2 : 1), + PBHASH_SET_CHAR_CONTENT_TYPE, + NULL); + if (NULL == hash) { + PUBNUB_LOG_ERROR("pubnub_subscription_set_get_subscribable: failed to " + "allocate memory for subscribables list\n"); + if (NULL != subs) { free(subs); } + return hash; + } + + if (NULL == subs) { return hash; } + + for (int i = 0; i < count; ++i) { + pbhash_set_t* subsc = pubnub_subscription_subscribables_( + subs[i], + &set->options); + + if (NULL == subsc) { + pbhash_set_free_with_destructor( + &hash, + (pbhash_set_element_free)pubnub_subscribable_free_); + free(subs); + return hash; + } + + if (PBHSR_OUT_OF_MEMORY == pbhash_set_union(hash, subsc, NULL)) { + PUBNUB_LOG_ERROR( + "pubnub_subscription_set_get_subscribable: failed to allocate " + "memory for nodes to store subscribable identifiers from other " + "hash set\n"); + pbhash_set_free_with_destructor( + &hash, + (pbhash_set_element_free)pubnub_subscribable_free_); + pbhash_set_free_with_destructor( + &subsc, + (pbhash_set_element_free)pubnub_subscribable_free_); + free(subs); + return hash; + } + + pbhash_set_free(&subsc); + } + if (NULL != subs) { free(subs); } + + return hash; +} + +pubnub_subscribable_t* pubnub_subscribable_alloc_( + const pubnub_subscribable_location location, + struct pubnub_char_mem_block* id, + const bool presence) +{ + PBCC_ALLOCATE_TYPE(sub, pubnub_subscribable_t, true, NULL); + sub->location = location; + sub->managed_memory_block = !presence; + if (!presence) { sub->id = id; } + else { + const char* suffix = "-pnpres"; + sub->id = malloc(sizeof(struct pubnub_char_mem_block)); + sub->id->size = id->size + strlen(suffix); + sub->id->ptr = malloc(sub->id->size + 1); + sprintf(sub->id->ptr, "%s%s", id->ptr, suffix); + } + + return sub; +} + +size_t pubnub_subscribable_length_(const pubnub_subscribable_t* subscribable) +{ + return subscribable->id->size; +} + +bool pubnub_subscribable_is_presence_(const pubnub_subscribable_t* subscribable) +{ + /** + * Checking on variable which is set to `false` for presence because it is + * allocated by the client itself. + */ + return !subscribable->managed_memory_block; +} + +bool pubnub_subscribable_is_cg_(const pubnub_subscribable_t* subscribable) +{ + return subscribable->location == SUBSCRIBABLE_LOCATION_QUERY; +} + +void pubnub_subscribable_free_(pubnub_subscribable_t* subscribable) +{ + if (NULL == subscribable) { return; } + + if (!subscribable->managed_memory_block) { + if (subscribable->id->ptr) { free(subscribable->id->ptr); } + free(subscribable->id); + } + + free(subscribable); +} \ No newline at end of file diff --git a/core/pubnub_subscribe_event_engine.h b/core/pubnub_subscribe_event_engine.h new file mode 100644 index 00000000..c65ed4c8 --- /dev/null +++ b/core/pubnub_subscribe_event_engine.h @@ -0,0 +1,808 @@ +/* -*- c-file-style:"stroustrup"; indent-tabs-mode: nil -*- */ +#ifndef PUBNUB_SUBSCRIBE_EVENT_ENGINE_H +#define PUBNUB_SUBSCRIBE_EVENT_ENGINE_H +#if PUBNUB_USE_SUBSCRIBE_EVENT_ENGINE +#if !PUBNUB_USE_SUBSCRIBE_V2 +#error Subscribe event engine requires subscribe v2 API, so you must define PUBNUB_USE_SUBSCRIBE_V2=1 +#endif // #if !PUBNUB_USE_SUBSCRIBE_V2 +#ifndef PUBNUB_CALLBACK_API +#error Subscribe event engine requires callback based PubNub context, so you must define PUBNUB_CALLBACK_API +#endif // #ifndef PUBNUB_CALLBACK_API + + +/** + * @file pubnub_subscribe_event_engine.h + * @brief PubNub `subscribe` module interface implemented atop of event engine. + */ + +#include +#include + +#include "core/pubnub_subscribe_event_engine_types.h" +#include "core/pubnub_entities.h" +#include "lib/pb_extern.h" + + +// ---------------------------------------------- +// Functions +// ---------------------------------------------- + +/** + * @brief Default subscription configuration options. + * @code + * pubnub_subscription_options_t options = pubnub_subscription_options_defopts(); + * options.receive_presence_events = true; + * @endcode + * + * @return Default subscription and subscription set configuration options. + */ +PUBNUB_EXTERN pubnub_subscription_options_t +pubnub_subscription_options_defopts(void); + +/** + * @brief Create entity subscription. + * + * Subscription is high-level access to the entity which is used in the + * subscription loop. Subscriptions make it possible to add real-time updates + * listeners for specific entity (channel, group, or App Context object). It is + * possible to `subscribe` and `unsubscribe` using a specific subscription. + * + * Create subscription without configuration options: + * @code + * pubnub_subscription_t* subscription = pubnub_subscription_alloc(entity, NULL); + * if (NULL == subscription) { + * // Handler parameters error (missing entity or invalid PubNub context + * // pointer) or insufficient memory. + * } + * @endcode + *
+ * Create subscription which will use presence channel: + * @code + * pubnub_subscription_options_t options = pubnub_subscription_options_defopts(); + * options.receive_presence_events = true; + * pubnub_subscription_t* subscription = pubnub_subscription_alloc( + * entity, + * &options); + * if (NULL == subscription) { + * // Handler parameters error (missing entity or invalid PubNub context + * // pointer) or insufficient memory. + * } + * @endcode + * + * \b Warning: Application execution will be terminated if `entity` is `NULL` or + * entity's PubNub pointer is an invalid context pointer. + * + * @param entity Pointer to the PubNub entity which should be used in + * subscription. + * @param options Pointer to the subscription configuration options. Set to + * `NULL` if no options required. + * @return Pointer to the ready to use PubNub subscription, or `NULL` will be + * returned in case of error (in most of the cases caused by + * insufficient memory for structure allocation). The returned pointer + * must be passed to the `pubnub_subscription_free` to avoid a memory + * leak. + * + * @see pubnub_subscription_options_defopts + * @see pubnub_channel_alloc + * @see pubnub_channel_group_alloc + * @see pubnub_channel_metadata_alloc + * @see pubnub_user_metadata_alloc + */ +PUBNUB_EXTERN pubnub_subscription_t* pubnub_subscription_alloc( + pubnub_entity_t* entity, + const pubnub_subscription_options_t* options); + +/** + * @brief Release resources used by subscription. + * @code + * enum pubnub_res rslt = pubnub_subscribe_with_subscription(subscription, NULL); + * if (PNR_OK != rslt) { + * // handle subscription error (mostly because of parameter error). + * } + * // + * // ... later ... + * // + * if(!pubnub_subscription_free(&subscription)) { + * // This call will decrease references count, but won't actually release + * // resources because the event engine holds the additional reference to + * // the subscription object. + * // In this situation, when reference to the user-allocated subscription + * // is freed, resources will be freed only after 'pubnub_unsubscribe_all' + * // function call. + * } + * @endcode + * + * \b Important: Subscription resources won't be freed if: + * - subscription used in subscription loop, + * - subscription is part of subscription set. + * + * \b Warning: Make sure that calls to the `pubnub_subscription_free` are done + * only once to not disrupt other types which still use this entity. + * + * @note Function may NULLify provided subscription pointer if there is no other + * references to it. + * + * @param sub Pointer to the subscription, which should free up resources. + * @return `false` if there are more references to the subscription. + * + * @see pubnub_subscription_alloc + * @see pubnub_subscribe_with_subscription + */ +PUBNUB_EXTERN bool pubnub_subscription_free(pubnub_subscription_t** sub); + +/** + * @brief Retrieve subscriptions currently used in subscribe. + * + * The function will return subscriptions which have been used with + * `pubnub_subscription_subscribe` function call and currently used in the + * subscription loop. + * @code + * enum pubnub_res rslt = pubnub_subscribe_with_subscription(subscription1, NULL); + * if (PNR_OK != rslt) { + * // handle subscription error (mostly because of parameters error). + * } + * rslt = pubnub_subscribe_with_subscription(subscription2, NULL); + * if (PNR_OK != rslt) { + * // handle subscription error (mostly because of parameters error). + * } + * rslt = pubnub_subscribe_with_subscription(subscription3, NULL); + * if (PNR_OK != rslt) { + * // handle subscription error (mostly because of parameters error). + * } + * // + * // ... later ... + * // + * size_t subs_count; + * pubnub_subscription_t** subs = pubnub_subscriptions(pb, &subs_count); + * if (NULL == subs) { + * // Handle parameters error (invalid PubNub context pointer) or there is + * // no active subscriptions. + * } + * // subs: subscription1, subscription2, subscription3 + * // subs_count: 3 + * free(subs); + * @endcode + * + * \b Warning: Application execution will be terminated if PubNub pointer is an + * invalid context pointer. + * + * @param pb Pointer to the PubNub context from which list of + * subscriptions should be retrieved. + * @param [out] count Parameter will hold the count of returned subscriptions. + * @return Pointer to the array with subscription object pointers, or `NULL` + * will be returned in case of insufficient memory error. The returned + * pointer must be passed to the `free` to avoid a memory leak. + * + * @see pubnub_subscription_alloc + * @see pubnub_subscribe_with_subscription + */ +PUBNUB_EXTERN pubnub_subscription_t** pubnub_subscriptions( + const pubnub_t* pb, + size_t* count); + +/** + * @brief Create a subscription set from the list of entities. + * + * Subscriptions set is high-level access to the grouped entities which is used + * in the subscription loop. Subscriptions set make it possible to add real-time + * updates listeners to the group of entities (channel, group, or App Context + * object). It is possible to `subscribe` and `unsubscribe` using multiple + * subscriptions at once. + * Create subscription set without configuration options: + * @code + * pubnub_subscription_set_t* subscription_set = pubnub_subscription_set_alloc_with_entities( + * entities, + * 4, + * NULL); + * if (NULL == subscription_set) { + * // Handler parameters error (missing entities or invalid entity's PubNub + * // context pointer) or insufficient memory. + * } + * @endcode + *
+ * Create subscription set which will use presence channel: + * @code + * pubnub_subscription_options_t options = pubnub_subscription_options_defopts(); + * options.receive_presence_events = true; + * pubnub_subscription_set_t* subscription_set = pubnub_subscription_set_alloc_with_entities( + * entities, + * 4, + * &options); + * if (NULL == subscription_set) { + * // Handler parameters error (missing entities or invalid entity's PubNub + * // context pointer) or insufficient memory. + * } + * @endcode + * + * \b Important: Additional `pubnub_subscription_t` won't be created for + * duplicate entity entry. + * + * @param entities Pointer to the array with PubNub entities objet + * pointers which should be used in subscriptions set. + * @param entities_count Number of PubNub entities in array. + * @param options Pointer to the subscription configuration options. Set + * to `NULL` if options not required. + * @return Pointer to the ready to use PubNub subscriptions set, or `NULL` will + * be returned in case of error (in most of the cases caused by + * insufficient memory for structure allocation). The returned pointer + * must be passed to the `pubnub_subscription_set_free` to avoid a + * memory leak. + * + * @see pubnub_subscription_options_defopts + * @see pubnub_channel_alloc + * @see pubnub_channel_group_alloc + * @see pubnub_channel_metadata_alloc + * @see pubnub_user_metadata_alloc + */ +PUBNUB_EXTERN pubnub_subscription_set_t* +pubnub_subscription_set_alloc_with_entities( + pubnub_entity_t** entities, + int entities_count, + const pubnub_subscription_options_t* options); + +/** + * @brief Create subscription set from subscriptions. + * + * Subscription set is high-level access to the grouped entities which is used + * in the subscription loop. Subscription set make it possible to add real-time + * updates listeners to the group of entities (channel, group, or App Context + * object). It is possible to `subscribe` and `unsubscribe` using multiple + * subscriptions at once. + * @code + * pubnub_subscription_set_t* subscription_set = pubnub_subscription_set_alloc_with_subscriptions( + * subscription1, + * subscription2, + * NULL); + * if (NULL == subscription_set) { + * // Handler parameters error (missing subscriptions or invalid entity's + * // PubNub context pointer) or insufficient memory. + * } + * @endcode + * + * @param sub1 Pointer to the left-hand subscription to be used for + * subscription set. + * @param sub2 Pointer to the right-hand subscription to be used for + * subscription set. + * @param options Pointer to the subscription configuration options override. + * Set to `NULL` if override not required. + * @return Pointer to the ready to use PubNub subscription set, or `NULL` will + * be returned in case of error (in most of the cases caused by + * insufficient memory for structures allocation). The returned pointer + * must be passed to the `pubnub_subscription_set_free` to avoid a + * memory leak. + * + * @see pubnub_subscription_alloc + */ +PUBNUB_EXTERN pubnub_subscription_set_t* +pubnub_subscription_set_alloc_with_subscriptions( + pubnub_subscription_t* sub1, + pubnub_subscription_t* sub2, + const pubnub_subscription_options_t* options); + +/** + * @brief Release resources used by subscription set. + * @code + * enum pubnub_res rslt = pubnub_subscribe_with_subscription_set( + * subscription_set, + * NULL); + * if (PNR_OK != rslt) { + * // handle subscription error (mostly because of parameter error). + * } + * // + * // ... later ... + * // + * if (!pubnub_subscription_set_free(&subscription_set)) { + * // This call will decrease references count, but won't release resources + * // because the event engine holds the additional reference to the + * // subscription set object. + * // In this situation, when reference to the user-allocated subscription + * // set is freed, resources will be freed only after + * // 'pubnub_unsubscribe_all' function call. + * } + * @endcode + * + * \b Important: Only those subscription objects which are not used in other + * subscription sets will be freed.
+ * \b Warning: Make sure that calls to the `pubnub_subscription_set_free` are + * done only once to not disrupt other types which still use this entity. + * + * @note Function will NULLify provided subscription set pointer if it is no + * other references to it. + * + * @param set Pointer to the subscription set, which should free up resources. + * @return `false` if there are more references to the subscription set. + * + * @see pubnub_subscription_set_alloc_with_entities + * @see pubnub_subscription_set_alloc_with_subscriptions + * @see pubnub_subscribe_with_subscription_set + */ +PUBNUB_EXTERN bool pubnub_subscription_set_free( + pubnub_subscription_set_t** set); + +/** + * @brief Retrieve subscription sets currently used in subscribe. + * + * The function will return subscription sets which have been used with + * `pubnub_subscribe_with_subscription_set` function call and currently used in + * the subscription loop. + * @code + * enum pubnub_res rslt = pubnub_subscribe_with_subscription_set( + * subscription_set1, + * NULL); + * if (PNR_OK != rslt) { + * // handle subscription error (mostly because of parameters error). + * } + * + * rslt = pubnub_subscribe_with_subscription_set(subscription_set2, NULL); + * if (PNR_OK != rslt) { + * // handle subscription error (mostly because of parameters error). + * } + * // + * // ... later ... + * // + * size_t sets_count; + * pubnub_subscription_set_t** sets = pubnub_subscription_sets(pb, &sets_count); + * if (NULL == sets) { + * // Handle parameters error (invalid PubNub context pointer) or there is + * // no active subscriptions. + * } + * free(sets); + * // sets: subscription_set1, subscription_set2 + * // sets_count: 2 + * @endcode + * + * \b Warning: Application execution will be terminated if PubNub pointer is an + * invalid context pointer. + * + * @param pb Pointer to the PubNub context from which list of + * subscriptions should be retrieved. + * @param [out] count Parameter will hold the count of returned subscription + * sets. + * @return Pointer to the array with subscription set object pointers, or `NULL` + * will be returned in case of insufficient memory error. The returned + * pointer must be passed to the `free` to avoid a memory leak. + * + * @see pubnub_subscription_set_alloc_with_entities + * @see pubnub_subscription_set_alloc_with_subscriptions + * @see pubnub_subscribe_with_subscription_set + */ +PUBNUB_EXTERN pubnub_subscription_set_t** pubnub_subscription_sets( + const pubnub_t* pb, + size_t* count); + +/** + * @brief Retrieve subscriptions from subscription set. + * @code + * // Create `pubnub_subscription_set_t` with `subscription1` and `subscription2`. + * if (PNR_OUT_OF_MEMORY == pubnub_subscription_set_add(subscription_set, + * subscription3)) { + * // handle error because of insufficient memory. + * } + * if (PNR_OUT_OF_MEMORY == pubnub_subscription_set_add(subscription_set, + * subscription4)) { + * // handle error because of insufficient memory. + * } + * + * size_t subs_count; + * pubnub_subscription_t** subs = pubnub_subscription_set_subscriptions( + * subscription_set, + * &subs_count); + * if (NULL == subs) { + * // Handle parameters error (invalid PubNub context pointer) or there is + * // no active subscriptions. + * } + * free(subs); + * // subs: subscription1, subscription2, subscription3, subscription4 + * // subs_count: 4 + * @endcode + * + * \b Warning: Application execution will be terminated if subscription `set` is + * `NULL`. + * + * @param set Pointer to the subscription set for which array with + * underlying subscription object pointers should be + * returned. + * @param [out] count Parameter will hold the count of returned subscriptions. + * @return Pointer to the array of pointers to subscriptions, or `NULL` will be + * returned in case of insufficient memory error. The returned pointer + * must be passed to the `free` to avoid a memory leak. + * + * @see pubnub_subscription_set_alloc_with_entities + * @see pubnub_subscription_set_alloc_with_subscriptions + * @see pubnub_subscription_set_add + */ +PUBNUB_EXTERN pubnub_subscription_t** pubnub_subscription_set_subscriptions( + pubnub_subscription_set_t* set, + size_t* count); + +/** + * @brief Add subscription to subscription set. + * + * Update list of set's subscription objects used in sub loop. + * @code + * if (PNR_OUT_OF_MEMORY == pubnub_subscription_set_add(subscription_set, + * subscription3)) { + * // handle error because of insufficient memory. + * } + * + * // Following addition won't change anything because it is already in the set. + * if (PNR_SUB_ALREADY_ADDED == pubnub_subscription_set_add(subscription_set, + * subscription3)) { + * // .. because we already have it in the set. + * } + * @endcode + * + * \b Important: `sub` subscription `options` will be ignored.
+ * \b Important: `pubnub_subscribe_with_subscription` will be called for `sub` + * if `set` previously has been used for + * `pubnub_subscribe_with_subscription_set`.
+ * \b Warning: Application execution will be terminated if subscription `set` or + * subscription is `NULL`. + * + * @param set Pointer to the subscription set, which should be modified. + * @param sub Pointer to the subscription, which should be added to the set. + * @return Result of subscription add. + * + * @see pubnub_subscription_set_alloc_with_entities + * @see pubnub_subscription_set_alloc_with_subscriptions + * @see pubnub_subscribe_with_subscription_set + * @see pubnub_subscribe_with_subscription + */ +PUBNUB_EXTERN enum pubnub_res pubnub_subscription_set_add( + pubnub_subscription_set_t* set, + pubnub_subscription_t* sub); + +/** + * @brief Remove subscription from subscription set. + * + * Update list of set's subscription objects used in subscription loop. + * @code + * if (PNR_SUB_NOT_FOUND == pubnub_subscription_set_remove(subscription_set, + * &subscription2)) { + * // subscription is not part of set and can't be removed from it. + * } + * @endcode + * + * \b Important: `pubnub_unsubscribe_with_subscription` will be called for `sub` + * if `set` previously has been used for + * `pubnub_subscribe_with_subscription_set`.
+ * \b Warning: Application execution will be terminated if subscription `set` or + * subscription is `NULL`. + * + * @note Function may `NULL`ify provided subscription pointer if there is no + * other references to it. + * + * @param set Pointer to the subscription set, which should be modified. + * @param sub Pointer to the subscription, which should be removed from the set. + * @return Result of subscription removal from the subscription set operation. + * + * @see pubnub_subscription_set_alloc_with_entities + * @see pubnub_subscription_set_alloc_with_subscriptions + * @see pubnub_subscribe_with_subscription_set + * @see pubnub_unsubscribe_with_subscription + */ +PUBNUB_EXTERN enum pubnub_res pubnub_subscription_set_remove( + pubnub_subscription_set_t* set, + pubnub_subscription_t** sub); + +/** + * @brief Merge a subscription set with other subscription set. + * + * Update list of set's subscription objects used in subscription loop. + * @code + * // Add subscription objects of `subscription_set2` to the `subscription_set1`. + * If (!pubnub_subscription_set_union(subscription_set1, subscription_set2)) { + * // handle sets union error (verbose logs review may help find reason) + * } + * @endcode + * + * \b Important: `other_set` subscription option will be ignored.
+ * \b Important: `pubnub_subscribe_with_subscription` will be called for other + * set `subscriptions` if `set` previously has been used for + * `pubnub_subscribe_with_subscription_set`.
+ * \b Warning: Application execution will be terminated if either of provided + * subscription sets is `NULL`. + * + * @param set Pointer to the subscription set, which should be modified. + * @param other_set Pointer to the subscription set, which should be merged into + * a set. + * @return Result of subscription sets union operation. + * + * @see pubnub_subscription_set_alloc_with_entities + * @see pubnub_subscription_set_alloc_with_subscriptions + * @see pubnub_subscribe_with_subscription_set + * @see pubnub_subscribe_with_subscription + */ +PUBNUB_EXTERN enum pubnub_res pubnub_subscription_set_union( + pubnub_subscription_set_t* set, + pubnub_subscription_set_t* other_set); + +/** + * @brief Unmerge subscription set from another subscription set. + * + * Update list of set's subscription objects used in subscription loop. + * @code + * // Remove subscription objects of `subscription_set2` from the + * // `subscription_set1`. + * pubnub_subscription_set_subtract(subscription_set1, subscription_set2); + * @endcode + * + * \b Important: `pubnub_unsubscribe_with_subscription` will be called for other + * set `subscriptions` if `set` previously has been used for + * `pubnub_subscribe_with_subscription_set`.
+ * \b Warning: Application execution will be terminated if either of provided + * subscription sets is `NULL`. + * + * @param set Pointer to the subscription set, which should be modified. + * @param other_set Pointer to the subscription set, which should be unmerged + * from a set. + * + * @see pubnub_subscription_set_alloc_with_entities + * @see pubnub_subscription_set_alloc_with_subscriptions + * @see pubnub_subscribe_with_subscription_set + * @see pubnub_unsubscribe_with_subscription + */ +PUBNUB_EXTERN void pubnub_subscription_set_subtract( + const pubnub_subscription_set_t* set, + pubnub_subscription_set_t* other_set); + +/** + * @brief Update real-time updates filter expression. + * + * @code + * // Instruct PubNub service to send only messages which have been published by + * // PubNub instance which has been configured with `bob` as `user_id`. + * pubnub_subscribe_set_filter_expression(pb, "uuid=='bob'"); + * @endcode + * + * Provided filtering expression evaluated on the server side before + * returning a response on subscribe REST API call.
+ * More information about filtering expression syntax can be found on + * this page. + * + * \b Important: The filter expression will only be applied to messages + * which have been published with `metadata`, and the rest will be returned + * as-is.
+ * + * @param pb Pointer to the PubNub context, which will use the provided + * expression with the next subscription loop call. + * @param filter_expr Pointer to the real-time update filter expression. + */ +PUBNUB_EXTERN void pubnub_subscribe_set_filter_expression( + const pubnub_t* pb, + const char* filter_expr); + +/** + * @brief Update client presence heartbeat / timeout. + * + * @code + * // Instruct PubNub service to trigger a `timeout` presence event if the + * // client didn't send another `subscribe` or `heartbeat` request within 200 + * // seconds. + * pubnub_subscribe_set_heartbeat(pb, 200); + * @endcode + * + * \b Important: If the value is lower than the pre-defined minimum, the value + * will be reset to the minimum. + * + * @note The value is an \a optional and will be set to \b 300 seconds by + * default. + * + * @param pb Pointer to the PubNub context, which will use the provided + * heartbeat with the next subscription loop call. + * @param heartbeat How long (in seconds) the server will consider the client + * alive for presence. + */ +PUBNUB_EXTERN void pubnub_subscribe_set_heartbeat( + const pubnub_t* pb, + unsigned heartbeat); + +/** + * @brief Subscription cursor. + * @code + * pubnub_subscribe_cursor_t cursor = pubnub_subscribe_cursor("17231917458018858"); + * @endcode + * + * @param timetoken Pointer to the PubNub high-precision timetoken. + * @return Ready to use cursor with a default region. + */ +PUBNUB_EXTERN pubnub_subscribe_cursor_t pubnub_subscribe_cursor( + const char* timetoken); + +/** + * @brief Receive real-time updates for subscription entity. + * @code + * enum pubnub_res rslt = pubnub_subscribe_with_subscription(subscription, NULL); + * if (PNR_OK != rslt) { + * // handle subscription error (mostly because of parameters error). + * } + * @endcode + * + * \b Warning: A single PubNub context can't have multiple subscription cursors + * and will override the active we last used. + * +*Cursor used by subscription loop to identify the point in time after which + * updates will be delivered.
+ * Old cursor may help return missed messages (catch up) if they are still + * present in the cache. + * + * + * + * @param sub Pointer to the subscription, which should be used with the +* next subscription loop. + * @param cursor Pointer to the subscription cursor to be used with subscribe + * REST API. The SDK will try to catch up on missed messages if a + * cursor with older PubNub high-precision timetoken has been + * provided. Pass `NULL` to keep using cursor received from the + * previous subscribe REST API response. + * @return Result of subscription subscribe transaction. + * + * @see pubnub_subscription_alloc + */ +PUBNUB_EXTERN enum pubnub_res pubnub_subscribe_with_subscription( + pubnub_subscription_t* sub, + const pubnub_subscribe_cursor_t* cursor); + +/** + * @brief Stop receiving real-time updates for subscription entity. + * @code + * enum pubnub_res rslt = pubnub_unsubscribe_with_subscription(&subscription); + * if (PNR_OK != rslt) { + * // handle unsubscription error (mostly because of parameters error). + * } + * @endcode + * + * @note Function may NULLify provided subscription pointer if there is no other + * references to it. + * + * @param sub Pointer to the subscription, which should be removed from the next + * subscription loop. + * @return Result of subscription unsubscribe transaction. + * + * @see pubnub_subscription_alloc + */ +PUBNUB_EXTERN enum pubnub_res pubnub_unsubscribe_with_subscription( + pubnub_subscription_t** sub); + +/** + * @brief Receive real-time updates for subscriptions' entities from the + * subscription set. + * @code + * enum pubnub_res rslt = pubnub_subscribe_with_subscription_set( + * subscription_set, + * NULL); + * if (PNR_OK != rslt) { + * // handle subscription error (mostly because of parameters error). + * } + * @endcode + * + * \b Warning: A single PubNub context can't have multiple subscription cursors + * and will override the active we last used. + * + * @param set Pointer to the set of subscriptions, which should be used with + * the next subscription loop. + * @param cursor Pointer to the subscription cursor to be used with subscribe + * REST API. The SDK will try to catch up on missed messages if a + * cursor with older PubNub high-precision timetoken has been + * provided. Pass `NULL` to keep using cursor received from the + * previous subscribe REST API response. + * @return Result of subscription set subscribe transaction. + * + * @see pubnub_subscription_set_alloc_with_entities + * @see pubnub_subscription_set_alloc_with_subscriptions + */ +PUBNUB_EXTERN enum pubnub_res pubnub_subscribe_with_subscription_set( + pubnub_subscription_set_t* set, + const pubnub_subscribe_cursor_t* cursor); + +/** + * @brief Stop receiving real-time updates for subscriptions' entities from + * subscription set. + * @code + * enum pubnub_res rslt = pubnub_unsubscribe_with_subscription_set(subscription_set); + * if (PNR_OK != rslt) { + * // handle unsubscription error (mostly because of parameters error). + * } + * @endcode + * + * @note Function will NULLify provided subscription set pointer if it is no + * other references to it. + * + * @param set Pointer to the set of subscription, which should be removed from + * the next subscription loop. + * @return Result of subscription set unsubscribe transaction. + * + * @see pubnub_subscription_set_alloc_with_entities + * @see pubnub_subscription_set_alloc_with_subscriptions + */ +PUBNUB_EXTERN enum pubnub_res pubnub_unsubscribe_with_subscription_set( + pubnub_subscription_set_t** set); + +/** + * @brief Disconnect from real-time updates. + * @code + * enum pubnub_res rslt = pubnub_disconnect(pb); + * if (PNR_OK != rslt) { + * // handle disconnection error (mostly because of parameters error). + * } + * @endcode + * + * \b Warning: Application execution will be terminated if PubNub pointer is an + * invalid context pointer. + * + * @note List of channels / groups and active time token (cursor) will be + * saved for subscription restore. + * + * @param pb Pointer to the PubNub context which should be disconnected from + * real-time updates. + * @return Result of disconnection transaction. + */ +PUBNUB_EXTERN enum pubnub_res pubnub_disconnect(const pubnub_t* pb); + +/** + * @brief Reconnect for real-time updates. + * + * Reconnect using the last list of subscribable and timetoken to try to catch + * up with missing messages: + * @code + * enum pubnub_res rslt = pubnub_reconnect(pb, NULL); + * if (PNR_OK != rslt) { + * // handle reconnect error (mostly because of parameter error). + * } + * @endcode + * + * Reconnect using the last list of subscribable and custom timetoken to try to + * catch up with missing messages from specific time: + * @code + * pubnub_subscribe_cursor_t cursor = pubnub_subscribe_cursor("17231917458018858"); + * enum pubnub_res rslt = pubnub_reconnect(pb, &cursor); + * if (PNR_OK != rslt) { + * // handle reconnect error (mostly because of parameters error). + * } + * @endcode + * + * \b Warning: Application execution will be terminated if PubNub pointer is an + * invalid context pointer. + * + * @param pb Pointer to the PubNub context which should be reconnected to + * real-time updates. + * @param cursor Pointer to the time token, which should be used with the next + * subscription loop. Pass `NULL` to re-use cursor from the + * previous event engine operation time. + * @return Result of reconnection transaction. + * + * @note Subscription loop will use time token (cursor) which has been in + * use before disconnection / failure. + */ +PUBNUB_EXTERN enum pubnub_res pubnub_reconnect( + const pubnub_t* pb, + const pubnub_subscribe_cursor_t* cursor); + +/** + * @brief Unsubscribe from all channel / groups events. + * + * Unsubscribe from all real-time updates and notify other channel / groups + * subscribers about client presence change (`leave`). + * @code + * enum pubnub_res rslt = pubnub_unsubscribe_all(pb); + * if (PNR_OK != rslt) { + * // handle unsubscribe all error + * } + * @endcode + * + * \b Warning: Application execution will be terminated if PubNub pointer is an + * invalid context pointer. + * + * @note The list of channels / groups and used time token (cursor) will be + * reset, and further real-time updates can't be received without a + * `subscribe` call. + * + * @param pb Pointer to the PubNub context which should completely unsubscribe + * from real-time updates. + * @return Result of unsubscription transaction. + */ +PUBNUB_EXTERN enum pubnub_res pubnub_unsubscribe_all(const pubnub_t* pb); +#else // #if PUBNUB_USE_SUBSCRIBE_EVENT_ENGINE +#error To use subscribe event engine API you must define PUBNUB_USE_SUBSCRIBE_EVENT_ENGINE=1 +#endif // #if PUBNUB_USE_SUBSCRIBE_EVENT_ENGINE +#endif // #ifndef PUBNUB_SUBSCRIBE_EVENT_ENGINE_H diff --git a/core/pubnub_subscribe_event_engine_internal.h b/core/pubnub_subscribe_event_engine_internal.h new file mode 100644 index 00000000..254bee8f --- /dev/null +++ b/core/pubnub_subscribe_event_engine_internal.h @@ -0,0 +1,216 @@ +/* -*- c-file-style:"stroustrup"; indent-tabs-mode: nil -*- */ +#ifndef PUBNUB_SUBSCRIBE_EVENT_ENGINE_INTERNAL_H +#define PUBNUB_SUBSCRIBE_EVENT_ENGINE_INTERNAL_H +#if PUBNUB_USE_SUBSCRIBE_EVENT_ENGINE + + +/** + * @file pubnub_subscribe_event_engine_internal.h + * @brief Module to manage first-class types internal / lib interface. + */ + +#include "core/pubnub_subscribe_event_engine.h" +#include "core/pbcc_subscribe_event_engine.h" +#include "core/pubnub_memory_block.h" +#include "lib/pbref_counter.h" +#include "core/pubnub_mutex.h" +#include "lib/pbhash_set.h" + + +// ---------------------------------------------- +// Types +// ---------------------------------------------- + +/** Subscribable object location in subscribe REST API URI. */ +typedef enum { + /** Object should be used as part of the path. */ + SUBSCRIBABLE_LOCATION_PATH, + /** Object should be used as a query parameter. */ + SUBSCRIBABLE_LOCATION_QUERY, +} pubnub_subscribable_location; + +/** Subscribable object definition. */ +typedef struct { + /** Subscribable identifier location in subscribe REST API URI. */ + pubnub_subscribable_location location; + /** + * @brief Whether lifecycle of the memory block for subscribable identifier + * already managed or not. + * + * Calling code should free up resources when done working with subscribable + * if memory block lifecycle not managed. + */ + bool managed_memory_block; + /** + * @brief Pointer to the subscribable identifier. + * + * @note Declared as a pointer to make it easier to pass value between + * entity and subscribable object (to preserve memory). + */ + struct pubnub_char_mem_block* id; +} pubnub_subscribable_t; + +/** Subscription definition. */ +struct pubnub_subscription { + /** + * @brief Subscription configuration options. + * + * \b Important: Don't change field position because it is used in code to + * get access to the subscription / set object options. + */ + pubnub_subscription_options_t options; + /** + * @brief Pointer to the PubNub entity for which real-time updates should be + * picked when subscription used in subscription loop. + */ + pubnub_entity_t* entity; + /** + * @brief Pointer to the Subscribe Event Engine, which will operate with a + * subscription object. + */ + pbcc_subscribe_ee_t* ee; + /** Object references counter. */ + pbref_counter_t* counter; + /** Shared resources access lock. */ + pubnub_mutex_t mutw; +}; + +/** Subscription set definition. */ +struct pubnub_subscription_set { + /** + * @brief Subscription configuration options. + * + * \b Important: Don't change field position because it is used in code to + * get access to the subscription / set object options. + */ + pubnub_subscription_options_t options; + /** Pointer to the hash set with pointers to subscription objects. */ + pbhash_set_t* subscriptions; + /** Whether a subscription set is used in subscription loop or not. */ + bool subscribed; + /** + * @brief Pointer to the Subscribe Event Engine, which will operate with a + * subscription set object. + */ + pbcc_subscribe_ee_t* ee; + /** Object references counter. */ + pbref_counter_t* counter; + /** Shared resources access lock. */ + pubnub_mutex_t mutw; +}; + + +// ---------------------------------------------- +// Functions +// ---------------------------------------------- + +/** + * @brief Release resources used by subscription. + * + * @note Function may NULLify provided subscription pointer if there is no other + * references to it. + * + * @param sub Pointer to the subscription, which should free up resources. + * @return `false` if there are more references to the subscription. + */ +bool pubnub_subscription_free_(pubnub_subscription_t* sub); + +/** + * @brief Release resources used by subscription set. + * + * @note Function will NULLify provided subscription set pointer if it is no + * other references to it. + * + * @param set Pointer to the subscription set, which should free up resources. + * @return `false` if there are more references to the subscription set. + */ +bool pubnub_subscription_set_free_(pubnub_subscription_set_t* set); + +/** + * @brief Get a list of subscription's subscribable objects. + * + * Depending on from subscription `options`, the list may contain additional + * entries (presence for example). + * + * \b Important: This function should be used in `pbcc_subscribe_event_engine.c` + * file only to update list of active subscribables - because of this reason + * `pbhash_set_t` configured without element destruction function since their + * lifecycle will be managed by calling code. + * + * @param sub Pointer to the subscription, which should provide subscribable + * objects. + * @param options Pointer to the subscription configuration options override. + * `NULL` can be used and options from `sub` will be used instead. + * @return Pointer to the hash set of `pubnub_subscribable_t` objects for + * subscription loop or `NULL` in case of insufficient memory error. The + * returned pointer must be passed to the `pbhash_set_free` to avoid a + * memory leak. + * + * @see pubnub_subscribable_t + */ +pbhash_set_t* pubnub_subscription_subscribables_( + const pubnub_subscription_t* sub, + const pubnub_subscription_options_t* options); + +/** + * @brief Get a list of subscription set subscribable objects. + * + * Depending on from subscription set `options`, the list may contain additional + * entries (presence for example). + * + * \b Important: This function should be used in `pbcc_subscribe_event_engine.c` + * file only to update list of active subscribables - because of this reason + * `pbhash_set_t` configured without element destruction function since their + * lifecycle will be managed by calling code. + * + * @param set Pointer to the subscription set, which should provide subscribable + * objects. + * @return Pointer to the hash set of `pubnub_subscribable_t` objects for + * subscription loop or `NULL` in case of insufficient memory error. The + * returned pointer must be passed to the `pbhash_set_free` to avoid a + * memory leak. + * + * @see pubnub_subscribable_t + */ +pbhash_set_t* pubnub_subscription_set_subscribables_( + const pubnub_subscription_set_t* set); + +/** + * @brief Get length of the subscribable objects string. + * + * @param subscribable Pointer to the subscribable, for which length should be + * retrieved. + * @return Length of the subscribable string. + */ +size_t pubnub_subscribable_length_(const pubnub_subscribable_t* subscribable); + +/** + * @brief Check whether `subscribable` represent name for presence. + * + * @param subscribable Pointer to the subscribable, for which information should + * be retrieved. + * @return `false` if `subscribable` represents presence name. + */ +bool pubnub_subscribable_is_presence_( + const pubnub_subscribable_t* subscribable); + +/** + * @brief Check whether `subscribable` represent channel group. + * + * @param subscribable Pointer to the subscribable, for which information should + * be retrieved. + * @return `false` if `subscribable` represents regular channel name. + */ +bool pubnub_subscribable_is_cg_(const pubnub_subscribable_t* subscribable); + +/** + * @brief Clean up resources used by subscribable. + * + * @param subscribable Pointer to the subscribable, which should free up + * resources. + */ +void pubnub_subscribable_free_(pubnub_subscribable_t* subscribable); +#else // #if PUBNUB_USE_SUBSCRIBE_EVENT_ENGINE +#error To use subscribe event engine API you must define PUBNUB_USE_SUBSCRIBE_EVENT_ENGINE=1 +#endif // #if PUBNUB_USE_SUBSCRIBE_EVENT_ENGINE +#endif // #ifndef PUBNUB_SUBSCRIBE_EVENT_ENGINE_INTERNAL_H \ No newline at end of file diff --git a/core/pubnub_subscribe_event_engine_types.h b/core/pubnub_subscribe_event_engine_types.h new file mode 100644 index 00000000..b436cf41 --- /dev/null +++ b/core/pubnub_subscribe_event_engine_types.h @@ -0,0 +1,109 @@ +/* -*- c-file-style:"stroustrup"; indent-tabs-mode: nil -*- */ +#ifndef PUBNUB_SUBSCRIBE_EVENT_ENGINE_TYPES_H +#define PUBNUB_SUBSCRIBE_EVENT_ENGINE_TYPES_H +#if PUBNUB_USE_SUBSCRIBE_EVENT_ENGINE + + +/** + * @file pubnub_subscribe_event_engine_types.h + * @brief Public Subscribe Event Engine types. + */ + +#include + + +// ---------------------------------------------- +// Types forwarding +// ---------------------------------------------- + +/** + * @brief Subscription set definition. + * + * Subscription set aggregates individual subscription objects and allows + * performing similar operations on the set as on individual subscription + * objects. Added listeners will receive real-time updates for all entities + * which have been used in aggregated subscription objects. + * + * @see pubnub_subscription_set_alloc_with_entities + * @see pubnub_subscription_set_alloc_with_subscriptions + */ +typedef struct pubnub_subscription_set pubnub_subscription_set_t; + +/** + * @brief Subscription definition. + * + * Subscription object represent PubNub entities in a subscription loop which is + * used to receive real-time updates for them. + * + * The Subscription Event Engine module provides an interface to: + * - manage, + * - subscribe, + * - unsubscribe, + * - add and remove real-time update listeners. + * + * @see pubnub_subscription_alloc + */ +typedef struct pubnub_subscription pubnub_subscription_t; + + +// ---------------------------------------------- +// Types +// ---------------------------------------------- + +/** + * @brief Subscription and subscription set objects configuration options. + * @code + * pubnub_subscription_options_t options = pubnub_subscription_options_defopts(); + * @endcode + * + * @see pubnub_subscription_options_defopts + */ +typedef struct pubnub_subscription_options { + /** Whether presence events should be received or not. */ + bool receive_presence_events; +} pubnub_subscription_options_t; + +/** + * @brief Time cursor. + * + * Cursor used by subscription loop to identify the point in time after which + * updates will be delivered.
+ * Old cursor may help return missed messages (catch up) if they are still + * present in the cache. + * + * @see pubnub_subscribe_cursor + */ +typedef struct { + /** PubNub high-precision timestamp. */ + char timetoken[20]; + /** + * @brief Data center region for which `timetoken` has been generated. + * + * @note This is an \a optional value and can be set to `0` if not needed. + */ + int region; +} pubnub_subscribe_cursor_t; + +/** PubNub subscription statuses. */ +typedef enum { + /** PubNub client subscribe and ready to receive real-time updates. */ + PNSS_SUBSCRIPTION_STATUS_CONNECTED, + /** PubNub client were unable to subscribe to receive real-time updates. */ + PNSS_SUBSCRIPTION_STATUS_CONNECTION_ERROR, + /** PubNub client has been disconnected because of some error. */ + PNSS_SUBSCRIPTION_STATUS_DISCONNECTED_UNEXPECTEDLY, + /** + * @brief PubNub client has been intentionally temporarily disconnected from + * the real-time updates. + */ + PNSS_SUBSCRIPTION_STATUS_DISCONNECTED, + /** + * @brief PubNub client has been unsubscribed from all real-time update + * sources. + */ + PNSS_SUBSCRIPTION_STATUS_SUBSCRIPTION_CHANGED +} pubnub_subscription_status; +#else // #if PUBNUB_USE_SUBSCRIBE_EVENT_ENGINE +#error To use subscribe event engine API you must define PUBNUB_USE_SUBSCRIBE_EVENT_ENGINE=1 +#endif // #if PUBNUB_USE_SUBSCRIBE_EVENT_ENGINE +#endif // #ifndef PUBNUB_SUBSCRIBE_EVENT_ENGINE_TYPES_H \ No newline at end of file diff --git a/core/pubnub_subscribe_event_listener.c b/core/pubnub_subscribe_event_listener.c new file mode 100644 index 00000000..700f7537 --- /dev/null +++ b/core/pubnub_subscribe_event_listener.c @@ -0,0 +1,281 @@ +/* -*- c-file-style:"stroustrup"; indent-tabs-mode: nil -*- */ +#include "pubnub_subscribe_event_listener.h" + + +#include "pbcc_memory_utils.h" +#include "core/pubnub_subscribe_event_engine_internal.h" +#include "core/pubnub_log.h" +#include "pubnub_internal.h" +#include "lib/pbstrdup.h" + + +// ---------------------------------------------- +// Function prototypes +// ---------------------------------------------- + +/** + * @brief Update registered list of listeners. + * + * @param event_listener Pointer to the Event Listener for which registered + * listeners should be updated. + * @param add Whether listeners registered or unregistered. + * @param type Type of real-time update for which listener will be + * called. + * @param subscription Pointer to the subscription object for which 'callback' + * for specific real-time updates 'type' will be + * registered. + * @param subscribables Pointer to the subscription object subscribables. + * @param callback Real-time update handling listener function. + * @return Result of listeners list update operation. + */ +static enum pubnub_res pubnub_subscribe_manage_listener_( + pbcc_event_listener_t* event_listener, + bool add, + pubnub_subscribe_listener_type type, + const void* subscription, + pbhash_set_t* subscribables, + pubnub_subscribe_message_callback_t callback); + +/** + * @brief Get list of channel and groups which can be used to map listeners. + * + * @param subscribables Pointer to the set of subscribables for which names of + * channels and groups should be retrieved. + * @return Pointer to the list of subscribable names or `NULL` in case of + * insufficient memory error. The returned pointer must be passed to the + * `pbarray_free` to avoid a memory leak. + */ +static pbarray_t* pubnub_subscribe_subscribable_names_( + pbhash_set_t* subscribables); + + +// ---------------------------------------------- +// Functions +// ---------------------------------------------- + +enum pubnub_res pubnub_subscribe_add_status_listener( + const pubnub_t* pb, + const pubnub_subscribe_status_callback_t callback) +{ + pbcc_event_listener_t* event_listener = + pbcc_subscribe_ee_event_listener(pb->core.subscribe_ee); + return pbcc_event_listener_add_status_listener(event_listener, callback); +} + +enum pubnub_res pubnub_subscribe_remove_status_listener( + const pubnub_t* pb, + const pubnub_subscribe_status_callback_t callback) +{ + pbcc_event_listener_t* event_listener = + pbcc_subscribe_ee_event_listener(pb->core.subscribe_ee); + return pbcc_event_listener_remove_status_listener(event_listener, callback); +} + +enum pubnub_res pubnub_subscribe_add_message_listener( + const pubnub_t* pb, + const pubnub_subscribe_listener_type type, + const pubnub_subscribe_message_callback_t callback) +{ + pbcc_event_listener_t* event_listener = + pbcc_subscribe_ee_event_listener(pb->core.subscribe_ee); + return pbcc_event_listener_add_message_listener( + event_listener, + type, + callback); +} + +enum pubnub_res pubnub_subscribe_remove_message_listener( + const pubnub_t* pb, + const pubnub_subscribe_listener_type type, + const pubnub_subscribe_message_callback_t callback) +{ + pbcc_event_listener_t* event_listener = + pbcc_subscribe_ee_event_listener(pb->core.subscribe_ee); + return pbcc_event_listener_remove_message_listener( + event_listener, + type, + callback); +} + +enum pubnub_res pubnub_subscribe_add_subscription_listener( + const pubnub_subscription_t* subscription, + const pubnub_subscribe_listener_type type, + const pubnub_subscribe_message_callback_t callback) +{ + pbhash_set_t* subs = pubnub_subscription_subscribables_(subscription, NULL); + if (NULL == subs) { return PNR_OUT_OF_MEMORY; } + + pbcc_event_listener_t* event_listener = + pbcc_subscribe_ee_event_listener(subscription->ee); + const enum pubnub_res rslt = pubnub_subscribe_manage_listener_( + event_listener, + true, + type, + subscription, + subs, + callback); + pbhash_set_free_with_destructor( + &subs, + (pbhash_set_element_free)pubnub_subscribable_free_); + + return rslt; +} + +enum pubnub_res pubnub_subscribe_remove_subscription_listener( + const pubnub_subscription_t* subscription, + const pubnub_subscribe_listener_type type, + const pubnub_subscribe_message_callback_t callback) +{ + pbhash_set_t* subs = pubnub_subscription_subscribables_(subscription, NULL); + if (NULL == subs) { return PNR_OUT_OF_MEMORY; } + + pbcc_event_listener_t* event_listener = + pbcc_subscribe_ee_event_listener(subscription->ee); + const enum pubnub_res rslt = pubnub_subscribe_manage_listener_( + event_listener, + false, + type, + subscription, + subs, + callback); + pbhash_set_free_with_destructor( + &subs, + (pbhash_set_element_free)pubnub_subscribable_free_); + + return rslt; +} + +enum pubnub_res pubnub_subscribe_add_subscription_set_listener( + const pubnub_subscription_set_t* subscription_set, + const pubnub_subscribe_listener_type type, + const pubnub_subscribe_message_callback_t callback) +{ + pbhash_set_t* subs = + pubnub_subscription_set_subscribables_(subscription_set); + if (NULL == subs) { return PNR_OUT_OF_MEMORY; } + + pbcc_event_listener_t* event_listener = + pbcc_subscribe_ee_event_listener(subscription_set->ee); + const enum pubnub_res rslt = pubnub_subscribe_manage_listener_( + event_listener, + true, + type, + subscription_set, + subs, + callback); + pbhash_set_free_with_destructor( + &subs, + (pbhash_set_element_free)pubnub_subscribable_free_); + + return rslt; +} + +enum pubnub_res pubnub_subscribe_remove_subscription_set_listener( + const pubnub_subscription_set_t* subscription_set, + const pubnub_subscribe_listener_type type, + const pubnub_subscribe_message_callback_t callback) +{ + pbhash_set_t* subs = + pubnub_subscription_set_subscribables_(subscription_set); + if (NULL == subs) { return PNR_OUT_OF_MEMORY; } + + pbcc_event_listener_t* event_listener = + pbcc_subscribe_ee_event_listener(subscription_set->ee); + const enum pubnub_res rslt = pubnub_subscribe_manage_listener_( + event_listener, + false, + type, + subscription_set, + subs, + callback); + pbhash_set_free_with_destructor( + &subs, + (pbhash_set_element_free)pubnub_subscribable_free_); + + return rslt; +} + +enum pubnub_res pubnub_subscribe_manage_listener_( + pbcc_event_listener_t* event_listener, + const bool add, + const pubnub_subscribe_listener_type type, + const void* subscription, + pbhash_set_t* subscribables, + const pubnub_subscribe_message_callback_t callback) +{ + if (NULL == subscribables) { return PNR_OUT_OF_MEMORY; } + + /** Get list of names for which listeners should be updated. */ + pbarray_t* names = pubnub_subscribe_subscribable_names_(subscribables); + if (NULL == names) { return PNR_OUT_OF_MEMORY; } + + enum pubnub_res rslt = PNR_OK; + if (add) { + rslt = pbcc_event_listener_add_subscription_object_listener( + event_listener, + type, + names, + subscription, + callback); + } + else { + rslt = pbcc_event_listener_remove_subscription_object_listener( + event_listener, + type, + names, + subscribables, + callback); + } + + /** + * If listeners added successfully then names memory managed by event + * listener. + */ + if (PNR_OK != rslt || !add) { pbarray_free_with_destructor(&names, free); } + else { pbarray_free(&names); } + + return rslt; +} + +pbarray_t* pubnub_subscribe_subscribable_names_(pbhash_set_t* subscribables) +{ + size_t count; + pubnub_subscribable_t** subs = (pubnub_subscribable_t**) + pbhash_set_elements(subscribables, &count); + if (NULL == subs) { + PUBNUB_LOG_ERROR("pubnub_subscribe_subscribable_names: failed to " + "allocate memory for subscribables list\n"); + return NULL; + } + + pbarray_t* names = pbarray_alloc(count, + PBARRAY_RESIZE_NONE, + PBARRAY_GENERIC_CONTENT_TYPE, + NULL); + if (NULL == names) { + PUBNUB_LOG_ERROR("pubnub_subscribe_subscribable_names: failed to " + "allocate memory for subscribable names\n"); + free(subs); + return NULL; + } + + for (size_t i = 0; i < count; ++i) { + const pubnub_subscribable_t* subscribable = subs[i]; + pbarray_res result = PBAR_OK; + char* name = pbstrdup(subscribable->id->ptr); + + if (NULL != name) { result = pbarray_add(names, name); } + if (NULL == name || PBAR_OK != result) { + PUBNUB_LOG_ERROR("pubnub_subscribe_subscribable_names: failed to " + "allocate memory for subscribable names array " + "element\n"); + if (NULL != name) { free(name); } + pbarray_free_with_destructor(&names, free); + free(subs); + return NULL; + } + } + free(subs); + + return names; +} \ No newline at end of file diff --git a/core/pubnub_subscribe_event_listener.h b/core/pubnub_subscribe_event_listener.h new file mode 100644 index 00000000..acdd562e --- /dev/null +++ b/core/pubnub_subscribe_event_listener.h @@ -0,0 +1,168 @@ +/* -*- c-file-style:"stroustrup"; indent-tabs-mode: nil -*- */ +#ifndef PUBNUB_SUBSCRIBE_EVENT_LISTENER_H +#define PUBNUB_SUBSCRIBE_EVENT_LISTENER_H +#if PUBNUB_USE_SUBSCRIBE_EVENT_ENGINE +#if !PUBNUB_USE_SUBSCRIBE_V2 +#error Subscribe event engine requires subscribe v2 API, so you must define PUBNUB_USE_SUBSCRIBE_V2=1 +#endif // #if !PUBNUB_USE_SUBSCRIBE_V2 +#ifndef PUBNUB_CALLBACK_API +#error Subscribe event engine requires callback based PubNub context, so you must define PUBNUB_CALLBACK_API +#endif // #ifndef PUBNUB_CALLBACK_API + + +/** + * @file pubnub_subscribe_event_listener.h + * @brief PubNub `event listener` module interface for real-time updates + * retrieval. + */ + +#include "core/pubnub_subscribe_event_listener_types.h" +#include "core/pubnub_subscribe_event_engine_types.h" +#include "lib/pb_extern.h" + + +// ---------------------------------------------- +// Functions +// ---------------------------------------------- + +/** + * @brief Add subscription status change listener. + * + * @note It is possible to add multiple listeners. + * + * @param pb Pointer to the PubNub context from which subscription status + * changes should be reported by provided listener. + * @param callback Subscription status change handling listener function. + * @return Results of listener addition. + */ +PUBNUB_EXTERN enum pubnub_res pubnub_subscribe_add_status_listener( + const pubnub_t* pb, + pubnub_subscribe_status_callback_t callback); + +/** + * @brief Remove subscription status change listener. + * + * \b Warning: All occurrences of the same `listener` will be removed from the + * list. If multiple observers registered with the same listener function + * (same address) they all will stop receiving updates. + * + * @param pb Pointer to the PubNub context from which subscription status + * changes shouldn't be reported to the provided listener. + * @param callback Subscription status change handling listener function. + * @return Results of listener removal. + */ +PUBNUB_EXTERN enum pubnub_res pubnub_subscribe_remove_status_listener( + const pubnub_t* pb, + pubnub_subscribe_status_callback_t callback); + +/** + * @brief Add subscription real-time updates listener. + * + * PubNub context will receive real-time updates from all subscription and + * subscription sets. + * + * @note It is possible to add multiple listeners. + * + * @param pb Pointer to the PubNub context from which subscription status + * changes should be reported by provided listener. + * @param type Type of real-time update for which listener will be called. + * @param callback Subscription status change handling listener function. + * @return Results of listener addition. + */ +PUBNUB_EXTERN enum pubnub_res pubnub_subscribe_add_message_listener( + const pubnub_t* pb, + pubnub_subscribe_listener_type type, + pubnub_subscribe_message_callback_t callback); + +/** + * @brief Remove subscription real-time updates listener.. + * + * \b Warning: All occurrences of the same `listener` will be removed from the + * list. If multiple observers registered with the same listener function + * (same address) they all will stop receiving updates. + * + * @param pb Pointer to the PubNub context from which subscription status + * changes shouldn't be reported to the provided listener. + * @param type Type of real-time update for which listener won't be called + * anymore. + * @param callback Subscription status change handling listener function. + * @return Results of listener removal. + */ +PUBNUB_EXTERN enum pubnub_res pubnub_subscribe_remove_message_listener( + const pubnub_t* pb, + pubnub_subscribe_listener_type type, + pubnub_subscribe_message_callback_t callback); + +/** + * @brief Add subscription real-time updates listener. + * + * @param subscription Subscription object for which 'listener' for specific + * real-time updates 'type' will be registered. + * @param type Type of real-time update for which listener will be + * called. + * @param callback Real-time update handling listener function. + * @return Result of listener addition. + */ +PUBNUB_EXTERN enum pubnub_res pubnub_subscribe_add_subscription_listener( + const pubnub_subscription_t* subscription, + pubnub_subscribe_listener_type type, + pubnub_subscribe_message_callback_t callback); + +/** + * @brief Remove subscription real-time updates listener. + * + * \b Warning: All occurrences of the same `listener` for specific `type` will + * be removed from the list. If multiple observers registered with the same + * listener function (same address) and `type` they all will stop receiving + * updates. + * + * @param subscription Subscription object for which 'listener' for specific + * real-time updates 'type' will be removed. + * @param type Type of real-time update for which listener won't be + * called anymore. + * @param callback Real-time update handling listener function. + * @return Results of listener removal. + */ +PUBNUB_EXTERN enum pubnub_res pubnub_subscribe_remove_subscription_listener( + const pubnub_subscription_t* subscription, + pubnub_subscribe_listener_type type, + pubnub_subscribe_message_callback_t callback); + +/** + * @brief Add subscription set real-time updates listener. + * + * @param subscription_set Subscription set object for which 'listener' for + * specific real-time updates 'type' will be registered. + * @param type Type of real-time update for which listener will be + * called. + * @param callback Real-time update handling listener function. + * @return Result of listener addition. + */ +PUBNUB_EXTERN enum pubnub_res pubnub_subscribe_add_subscription_set_listener( + const pubnub_subscription_set_t* subscription_set, + pubnub_subscribe_listener_type type, + pubnub_subscribe_message_callback_t callback); + +/** + * @brief Remove subscription set real-time updates listener. + * + * \b Warning: All occurrences of the same `listener` for specific `type` will + * be removed from the list. If multiple observers registered with the same + * listener function (same address) and `type` they all will stop receiving + * updates. + * + * @param subscription_set Subscription set object for which 'listener' for + * specific real-time updates 'type' will be removed. + * @param type Type of real-time update for which listener won't be + * called anymore. + * @param callback Real-time update handling listener function. + * @return Results of listener removal. + */ +PUBNUB_EXTERN enum pubnub_res pubnub_subscribe_remove_subscription_set_listener( + const pubnub_subscription_set_t* subscription_set, + pubnub_subscribe_listener_type type, + pubnub_subscribe_message_callback_t callback); +#else // #if PUBNUB_USE_SUBSCRIBE_EVENT_ENGINE +#error To use subscribe event engine API you must define PUBNUB_USE_SUBSCRIBE_EVENT_ENGINE=1 +#endif // #if PUBNUB_USE_SUBSCRIBE_EVENT_ENGINE +#endif // #ifndef PUBNUB_SUBSCRIBE_EVENT_LISTENER_H \ No newline at end of file diff --git a/core/pubnub_subscribe_event_listener_types.h b/core/pubnub_subscribe_event_listener_types.h new file mode 100644 index 00000000..c2edb9f4 --- /dev/null +++ b/core/pubnub_subscribe_event_listener_types.h @@ -0,0 +1,101 @@ +/* -*- c-file-style:"stroustrup"; indent-tabs-mode: nil -*- */ +#ifndef PUBNUB_SUBSCRIBE_EVENT_LISTENER_TYPES_H +#define PUBNUB_SUBSCRIBE_EVENT_LISTENER_TYPES_H + + +/** + * @file pubnub_subscribe_event_listener_types.h + * @brief Public Subscribe Event Listener types. + */ + +#include "core/pubnub_subscribe_event_engine_types.h" +#include "core/pubnub_subscribe_v2_message.h" +#include "core/pubnub_api_types.h" + + +// ---------------------------------------------- +// Types +// ---------------------------------------------- + +/** PubNub subscribe event listener events sources. */ +typedef enum +{ + /** Listener to handle real-time messages. */ + PBSL_LISTENER_ON_MESSAGE, + /** Listener to handle real-time signals. */ + PBSL_LISTENER_ON_SIGNAL, + /** Listener to handle message action real-time updates. */ + PBSL_LISTENER_ON_MESSAGE_ACTION, + /** Listener to handle App Context real-time updates. */ + PBSL_LISTENER_ON_OBJECTS, + /** Listener to handle real-time files sharing events. */ + PBSL_LISTENER_ON_FILES +} pubnub_subscribe_listener_type; + +/** PubNub subscription status change data object. */ +typedef struct +{ + /** + * @brief Error details in case of error. + * + * In case of `PNSS_SUBSCRIPTION_STATUS_CONNECTION_ERROR` and + * `PNSS_SUBSCRIPTION_STATUS_DISCONNECTED_UNEXPECTEDLY` may contain + * additional information about reasons of failure. + */ + const enum pubnub_res reason; + /** + * @brief Affected channels. + * + * Byte string with comma-separated / `NULL` channel identifiers which have + * been used with recent operation. + * + * \b Warning: Memory managed by PubNub context and can be freed after + * callback function return. + */ + const char* channels; + /** + * @brief Affected channel groups. + * + * Byte string with comma-separated / `NULL` channel group identifiers which + * have been used with recent operation. + * + * \b Warning: Memory managed by PubNub context and can be freed after + * callback function return. + */ + const char* channel_groups; +} pubnub_subscription_status_data_t; + +/** + * @brief PubNub subscribe status change function definition. +* + * @param pb Pointer to the PubNub context for which subscribe status + * did change. + * @param status PubNub client new subscribe status. + * @param status_data Additional information with details to the recent + * subscription status change. + * + * @see pubnub_subscription_status + * @see pubnub_subscription_status_data_t + */ +typedef void (*pubnub_subscribe_status_callback_t)( + const pubnub_t* pb, + pubnub_subscription_status status, + pubnub_subscription_status_data_t status_data); + +/** + * @brief PubNub subscribe real-time updates (messages) function definition. + * + * @note Listener will be called for each received message designated to + * specific `pubnub_subscription_t` or `pubnub_subscription_set_t`. + * + * @param pb Pointer to the PubNub context which receive real-time update. + * @param message Object with a payload which corresponds to the type of source + * with which the listener has been registered. + * + * @see pubnub_subscription_t + * @see pubnub_subscription_set_t + */ +typedef void (*pubnub_subscribe_message_callback_t)( + const pubnub_t* pb, + struct pubnub_v2_message message); +#endif // #ifndef PUBNUB_SUBSCRIBE_EVENT_LISTENER_TYPES_H diff --git a/core/pubnub_subscribe_v2.c b/core/pubnub_subscribe_v2.c index e4b2b2d4..21323d7e 100644 --- a/core/pubnub_subscribe_v2.c +++ b/core/pubnub_subscribe_v2.c @@ -11,10 +11,6 @@ #include "pubnub_netcore.h" #include "pubnub_assert.h" #include "pubnub_log.h" -#include "pubnub_version.h" -#include "pubnub_json_parse.h" - -#include "pbpal.h" struct pubnub_subscribe_v2_options pubnub_subscribe_v2_defopts(void) diff --git a/core/pubnub_subscribe_v2_message.h b/core/pubnub_subscribe_v2_message.h index 7f338d59..bd02cbfb 100644 --- a/core/pubnub_subscribe_v2_message.h +++ b/core/pubnub_subscribe_v2_message.h @@ -9,7 +9,6 @@ #error To use the subscribe V2 API you must define PUBNUB_USE_SUBSCRIBE_V2=1 #endif -#include #include "pubnub_memory_block.h" /* subscribe_v2 message types */ diff --git a/core/pubnub_version_internal.h b/core/pubnub_version_internal.h index 8982e2e6..2719721e 100644 --- a/core/pubnub_version_internal.h +++ b/core/pubnub_version_internal.h @@ -3,7 +3,7 @@ #define INC_PUBNUB_VERSION_INTERNAL -#define PUBNUB_SDK_VERSION "4.13.1" +#define PUBNUB_SDK_VERSION "4.14.0" #endif /* !defined INC_PUBNUB_VERSION_INTERNAL */ diff --git a/core/samples/subscribe_event_engine_sample.c b/core/samples/subscribe_event_engine_sample.c new file mode 100644 index 00000000..01e18735 --- /dev/null +++ b/core/samples/subscribe_event_engine_sample.c @@ -0,0 +1,324 @@ +/* -*- c-file-style:"stroustrup"; indent-tabs-mode: nil -*- */ + +#include +#include +#include +#include + +#include "core/pubnub_subscribe_event_listener.h" +#include "core/pubnub_subscribe_event_engine.h" +#include "core/pubnub_helper.h" +#include "pubnub_callback.h" + + +// ---------------------------------------------- +// Function prototypes +// ---------------------------------------------- + +/** + * @brief Common message listener callback function. + * + * @param pb Pointer to the PubNub context which received real-time update. + * @param global Whether listener called from PubNub context or specific + * entity. + * @param message Received real-time update information. + */ +void subscribe_message_listener_( + const pubnub_t* pb, + bool global, + struct pubnub_v2_message message); + + +// ---------------------------------------------- +// Functions +// ---------------------------------------------- + +/** + * @brief Create string from the memory block. + * + * @param block Memory block with information required to create string. + * @return Pointer to the string or `NULL` in case if empty memory block has + * been provided. + */ +static char* string_from_mem_block(struct pubnub_char_mem_block block) +{ + if (0 == block.size) { return NULL; } + + char* string = malloc(block.size + 1); + memcpy(string, block.ptr, block.size); + string[block.size] = '\0'; + + return string; +} + +/** + * @brief Translate PubNub-defined real-time update type (enum field) to + * human-readable format. + * + * @param type PubNub-defined real-time update type. + * @return Human-readable real-time update type. + */ +static char* message_type_2_string(const enum pubnub_message_type type) +{ + switch (type) { + case pbsbSignal: + return "signal"; + case pbsbPublished: + return "message / presence update"; + case pbsbAction: + return "action"; + case pbsbObjects: + return "app context"; + case pbsbFiles: + return "file"; + } + + return "unknown update"; +} + +/** + * @brief Temporarily pause app execution. + * + * @param time_in_seconds How long further execution should be postponed. + */ +static void wait_seconds(const double time_in_seconds) +{ + const time_t start = time(NULL); + double time_passed_in_seconds; + do { + time_passed_in_seconds = difftime(time(NULL), start); + usleep(10); + } while (time_passed_in_seconds < time_in_seconds); +} + +/** + * @brief Subscription status change listener callback. + * + * @param pb Pointer to the PubNub context for which subscription + * status has been changed. + * @param status Current subscription status. + * @param status_data Information from subscriber. + */ +void subscribe_status_change_listener( + const pubnub_t* pb, + const pubnub_subscription_status status, + const pubnub_subscription_status_data_t status_data) +{ + switch (status) { + case PNSS_SUBSCRIPTION_STATUS_CONNECTED: + printf("PubNub client connected to:\n"); + break; + case PNSS_SUBSCRIPTION_STATUS_CONNECTION_ERROR: + printf("PubNub client connection error: %s\n", + pubnub_res_2_string(status_data.reason)); + break; + case PNSS_SUBSCRIPTION_STATUS_DISCONNECTED_UNEXPECTEDLY: + printf("PubNub client disconnected unexpectedly with error: %s\n", + pubnub_res_2_string(status_data.reason)); + break; + case PNSS_SUBSCRIPTION_STATUS_DISCONNECTED: + printf("PubNub client disconnected:\n"); + break; + case PNSS_SUBSCRIPTION_STATUS_SUBSCRIPTION_CHANGED: + printf("PubNub client changed subscription:\n"); + break; + } + + if (NULL != status_data.channels) + printf("\t- channels: %s\n", status_data.channels); + if (NULL != status_data.channel_groups) + printf("\t- channel groups: %s\n", status_data.channel_groups); +} + +/** + * @brief Global message listener callback function. + * + * @param pb Pointer to the PubNub context which received real-time update. + * @param message Received real-time update information. + */ +void global_message_listener( + const pubnub_t* pb, + const struct pubnub_v2_message message) +{ + subscribe_message_listener_(pb, true, message); +} + +/** + * @brief Subscription / subscription set message listener callback function. + * + * @param pb Pointer to the PubNub context which received real-time update. + * @param message Received real-time update information. + */ +void subscribe_message_listener( + const pubnub_t* pb, + const struct pubnub_v2_message message) +{ + subscribe_message_listener_(pb, false, message); +} + +void subscribe_message_listener_( + const pubnub_t* pb, + const bool global, + const struct pubnub_v2_message message) +{ + char* uuid = string_from_mem_block(message.publisher); + char* ch = string_from_mem_block(message.channel); + char* msg = string_from_mem_block(message.payload); + char* tt = string_from_mem_block(message.tt); + char* type = message_type_2_string(message.message_type); + char client[100]; + + if (global) { + snprintf(client, sizeof(client), "PubNub (%p) received", pb); + } + else { snprintf(client, sizeof(client), "Received"); } + + printf("%s %s on '%s' at %s:\n", client, type, ch, tt); + if (NULL != uuid) { + printf("\t- publisher: %s\n", uuid); + free(uuid); + } + if (NULL != msg) { + printf("\t- message: %s\n", msg); + free(msg); + } + + free(ch); + free(tt); +} + +int main() +{ + /** Setup PubNub context. */ + const char* publish_key = getenv("PUBNUB_PUBLISH_KEY"); + if (NULL == publish_key) { publish_key = "demo"; } + const char* subscribe_key = getenv("PUBNUB_SUBSCRIBE_KEY"); + if (NULL == subscribe_key) { subscribe_key = "demo"; } + + pubnub_t* pubnub = pubnub_alloc(); + pubnub_init(pubnub, publish_key, subscribe_key); + pubnub_set_user_id(pubnub, "demo-user"); + + /** Add subscription status change listener. */ + pubnub_subscribe_add_status_listener(pubnub, + subscribe_status_change_listener); + pubnub_subscribe_add_message_listener(pubnub, + PBSL_LISTENER_ON_MESSAGE, + global_message_listener); + + + /** + * Single subscription setup. + */ + pubnub_channel_t* channel = pubnub_channel_alloc( + pubnub, + "channel-test-history1"); + pubnub_subscription_t* subscription = + pubnub_subscription_alloc((pubnub_entity_t*)channel, NULL); + /** Subscription retained entity and it is safe to free */ + pubnub_entity_free((void**)&channel); + /** Add messages listeners for subscription. */ + pubnub_subscribe_add_subscription_listener(subscription, + PBSL_LISTENER_ON_MESSAGE, + subscribe_message_listener); + printf("Subscribing with subscription...\n"); + /** Subscribe using subscription. */ + enum pubnub_res rslt = pubnub_subscribe_with_subscription( + subscription, + NULL); + printf("Subscribe with subscription result: %s\n", + pubnub_res_2_string(rslt)); + + + /** + * Subscription set setup. + */ + pubnub_channel_t* channel_mx1 = pubnub_channel_alloc( + pubnub, + "channel-test-history1"); + pubnub_channel_t* channel_mx2 = pubnub_channel_alloc( + pubnub, + "channel-test-history2"); + pubnub_channel_group_t* group_mx3 = pubnub_channel_group_alloc( + pubnub, + "my-channel-group"); + pubnub_entity_t** entities = malloc(3 * sizeof(pubnub_entity_t*)); + entities[0] = (pubnub_entity_t*)channel_mx1; + entities[1] = (pubnub_entity_t*)channel_mx2; + entities[2] = (pubnub_entity_t*)group_mx3; + pubnub_subscription_set_t* set = + pubnub_subscription_set_alloc_with_entities(entities, 3, NULL); + /** Unredlying subscriptions in set retained entities and it is safe to free */ + pubnub_entity_free((void**)&channel_mx1); + pubnub_entity_free((void**)&channel_mx2); + pubnub_entity_free((void**)&group_mx3); + free(entities); + + /** Add messages listeners for subscription set. */ + pubnub_subscribe_add_subscription_set_listener( + set, + PBSL_LISTENER_ON_MESSAGE, + subscribe_message_listener); + printf("Subscribing with subscription set...\n"); + rslt = pubnub_subscribe_with_subscription_set( + set, + NULL); + printf("Subscribe with subscription set result: %s\n", + pubnub_res_2_string(rslt)); + + /** Wait for messages published to one of the channels (manual). */ + wait_seconds(60); + + + /** + * Remove subscription from subscription set (which is equal to unsubscribe + * on actively subscribed set). + */ + size_t count = 0; + pubnub_subscription_t** subs = pubnub_subscription_set_subscriptions( + set, + &count); + /** + * Removing first subscription in the list which will be `entities[0]`. + * + * Important: Actual unsubscribe won't happen because there exist separate + * subscription for the same entity for which subscription will be removed + * from set. + */ + printf("Removing subscription from subscription set...\n"); + rslt = pubnub_subscription_set_remove(set, &subs[0]); + printf("Subscription remove result: %s\n", + pubnub_res_2_string(rslt)); + free(subs); + + + /** + * Unsubscribe using subscription. + */ + printf("Unsubscribing from subscription...\n"); + rslt = pubnub_unsubscribe_with_subscription(&subscription); + printf("Unsubscribe with subscription result: %s\n", + pubnub_res_2_string(rslt)); + pubnub_subscription_free(&subscription); + + /** Sleep a bit before compltion of all stuff. */ + wait_seconds(3); + + + /** + * Unsubscribe from everything. + */ + printf("Unsubscribing from all...\n"); + rslt = pubnub_unsubscribe_all(pubnub); + printf("Unsubscribe from all result: %s\n", + pubnub_res_2_string(rslt)); + + /** Giving some time to complete unsubscribe. */ + wait_seconds(3); + + if (NULL != set) { pubnub_subscription_set_free(&set); } + + puts("Pubnub subscribe event engine demo is over."); + + return 0; +} \ No newline at end of file diff --git a/cpp/posix_openssl.mk b/cpp/posix_openssl.mk index 945018aa..12bb230e 100644 --- a/cpp/posix_openssl.mk +++ b/cpp/posix_openssl.mk @@ -1,5 +1,5 @@ -SOURCEFILES = ../core/pbcc_set_state.c ../core/pubnub_pubsubapi.c ../core/pubnub_coreapi.c ../core/pubnub_ccore_pubsub.c ../core/pubnub_ccore.c ../core/pubnub_netcore.c ../lib/sockets/pbpal_resolv_and_connect_sockets.c ../lib/sockets/pbpal_handle_socket_error.c ../openssl/pbpal_openssl.c ../openssl/pbpal_connect_openssl.c ../openssl/pbpal_add_system_certs_posix.c ../core/pubnub_alloc_std.c ../core/pubnub_assert_std.c ../core/pubnub_generate_uuid.c ../core/pubnub_blocking_io.c ../posix/posix_socket_blocking_io.c ../core/pubnub_free_with_timeout_std.c ../core/pubnub_timers.c ../core/pubnub_json_parse.c ../lib/md5/md5.c ../lib/base64/pbbase64.c ../lib/pb_strnlen_s.c ../lib/pb_strncasecmp.c ../core/pubnub_helper.c pubnub_version_posix.cpp ../posix/pubnub_generate_uuid_posix.c ../openssl/pbpal_openssl_blocking_io.c ../core/pubnub_crypto.c ../core/pubnub_coreapi_ex.c ../openssl/pbaes256.c ../posix/msstopwatch_monotonic_clock.c ../posix/pbtimespec_elapsed_ms.c ../core/pubnub_url_encode.c ../core/pubnub_memory_block.c ../posix/pb_sleep_ms.c ../core/pbcc_crypto.c ../core/pbcc_crypto_aes_cbc.c ../core/pbcc_crypto_legacy.c -OBJFILES = pbcc_set_state.o pubnub_pubsubapi.o pubnub_coreapi.o pubnub_coreapi_ex.o pubnub_ccore_pubsub.o pubnub_ccore.o pubnub_netcore.o pbpal_resolv_and_connect_sockets.o pbpal_handle_socket_error.o pubnub_alloc_std.o pubnub_assert_std.o pubnub_generate_uuid.o pubnub_blocking_io.o posix_socket_blocking_io.o pubnub_timers.o pubnub_json_parse.o md5.o pbbase64.o pb_strnlen_s.o pb_strncasecmp.o pubnub_helper.o pubnub_version_posix.o pubnub_generate_uuid_posix.o pubnub_free_with_timeout_std.o msstopwatch_monotonic_clock.o pbtimespec_elapsed_ms.o pubnub_url_encode.o pubnub_memory_block.o pb_sleep_ms.o pubnub_futres_sync.o pubnub_crypto.o pbaes256.o pbpal_connect_openssl.o pbpal_openssl.o pbpal_add_system_certs_posix.o pbpal_openssl_blocking_io.o pb_sleep_ms.o pbcc_crypto.o pbcc_crypto_aes_cbc.o pbcc_crypto_legacy.o +SOURCEFILES = ../core/pbcc_set_state.c ../core/pubnub_pubsubapi.c ../core/pubnub_coreapi.c ../core/pubnub_ccore_pubsub.c ../core/pubnub_ccore.c ../core/pubnub_netcore.c ../lib/sockets/pbpal_resolv_and_connect_sockets.c ../lib/sockets/pbpal_handle_socket_error.c ../openssl/pbpal_openssl.c ../openssl/pbpal_connect_openssl.c ../openssl/pbpal_add_system_certs_posix.c ../core/pubnub_alloc_std.c ../core/pubnub_assert_std.c ../core/pubnub_generate_uuid.c ../core/pubnub_blocking_io.c ../posix/posix_socket_blocking_io.c ../core/pubnub_free_with_timeout_std.c ../core/pubnub_timers.c ../core/pubnub_json_parse.c ../lib/md5/md5.c ../lib/base64/pbbase64.c ../lib/pb_strnlen_s.c ../lib/pb_strncasecmp.c ../core/pubnub_helper.c pubnub_version_posix.cpp ../posix/pubnub_generate_uuid_posix.c ../openssl/pbpal_openssl_blocking_io.c ../core/pubnub_coreapi_ex.c ../posix/msstopwatch_monotonic_clock.c ../posix/pbtimespec_elapsed_ms.c ../core/pubnub_url_encode.c ../core/pubnub_memory_block.c ../posix/pb_sleep_ms.c +OBJFILES = pbcc_set_state.o pubnub_pubsubapi.o pubnub_coreapi.o pubnub_coreapi_ex.o pubnub_ccore_pubsub.o pubnub_ccore.o pubnub_netcore.o pbpal_resolv_and_connect_sockets.o pbpal_handle_socket_error.o pubnub_alloc_std.o pubnub_assert_std.o pubnub_generate_uuid.o pubnub_blocking_io.o posix_socket_blocking_io.o pubnub_timers.o pubnub_json_parse.o md5.o pbbase64.o pb_strnlen_s.o pb_strncasecmp.o pubnub_helper.o pubnub_version_posix.o pubnub_generate_uuid_posix.o pubnub_free_with_timeout_std.o msstopwatch_monotonic_clock.o pbtimespec_elapsed_ms.o pubnub_url_encode.o pubnub_memory_block.o pb_sleep_ms.o pubnub_futres_sync.o pbpal_connect_openssl.o pbpal_openssl.o pbpal_add_system_certs_posix.o pbpal_openssl_blocking_io.o pb_sleep_ms.o ifndef ONLY_PUBSUB_API ONLY_PUBSUB_API = 0 @@ -49,6 +49,10 @@ ifndef USE_FETCH_HISTORY USE_FETCH_HISTORY = 1 endif +ifndef USE_CRYPTO_API +USE_CRYPTO_API = 1 +endif + ifeq ($(USE_PROXY), 1) SOURCEFILES += ../core/pubnub_proxy.c ../core/pubnub_proxy_core.c ../core/pbhttp_digest.c ../core/pbntlm_core.c ../core/pbntlm_packer_std.c OBJFILES += pubnub_proxy.o pubnub_proxy_core.o pbhttp_digest.o pbntlm_core.o pbntlm_packer_std.o @@ -104,7 +108,12 @@ SOURCEFILES += ../core/pubnub_revoke_token_api.c ../core/pbcc_revoke_token_api.c OBJFILES += pubnub_revoke_token_api.o pbcc_revoke_token_api.o endif -CFLAGS =-g -I .. -I . -I ../openssl -I ../lib/base64 -Wall -D PUBNUB_THREADSAFE -D PUBNUB_LOG_LEVEL=PUBNUB_LOG_LEVEL_WARNING -D PUBNUB_ONLY_PUBSUB_API=$(ONLY_PUBSUB_API) -D PUBNUB_PROXY_API=$(USE_PROXY) -D PUBNUB_USE_GZIP_COMPRESSION=$(USE_GZIP_COMPRESSION) -D PUBNUB_RECEIVE_GZIP_RESPONSE=$(RECEIVE_GZIP_RESPONSE) -D PUBNUB_USE_SUBSCRIBE_V2=$(USE_SUBSCRIBE_V2) -D PUBNUB_USE_OBJECTS_API=$(USE_OBJECTS_API) -D PUBNUB_USE_ACTIONS_API=$(USE_ACTIONS_API) -D PUBNUB_USE_AUTO_HEARTBEAT=$(USE_AUTO_HEARTBEAT) -D PUBNUB_USE_GRANT_TOKEN_API=$(USE_GRANT_TOKEN) -D PUBNUB_USE_REVOKE_TOKEN_API=$(USE_REVOKE_TOKEN) -D PUBNUB_USE_FETCH_HISTORY=$(USE_FETCH_HISTORY) +ifeq ($(USE_CRYPTO_API), 1) +SOURCEFILES += ../core/pbcc_crypto.c ../core/pbcc_crypto_aes_cbc.c ../core/pbcc_crypto_legacy.c ../core/pubnub_crypto.c ../openssl/pbaes256.c +OBJFILES += pbcc_crypto.o pbcc_crypto_aes_cbc.o pbcc_crypto_legacy.o pubnub_crypto.o pbaes256.o +endif + +CFLAGS =-g -I .. -I . -I ../openssl -I ../lib/base64 -Wall -D PUBNUB_THREADSAFE -D PUBNUB_LOG_LEVEL=PUBNUB_LOG_LEVEL_WARNING -D PUBNUB_ONLY_PUBSUB_API=$(ONLY_PUBSUB_API) -D PUBNUB_PROXY_API=$(USE_PROXY) -D PUBNUB_USE_GZIP_COMPRESSION=$(USE_GZIP_COMPRESSION) -D PUBNUB_RECEIVE_GZIP_RESPONSE=$(RECEIVE_GZIP_RESPONSE) -D PUBNUB_USE_SUBSCRIBE_V2=$(USE_SUBSCRIBE_V2) -D PUBNUB_USE_OBJECTS_API=$(USE_OBJECTS_API) -D PUBNUB_USE_ACTIONS_API=$(USE_ACTIONS_API) -D PUBNUB_USE_AUTO_HEARTBEAT=$(USE_AUTO_HEARTBEAT) -D PUBNUB_USE_GRANT_TOKEN_API=$(USE_GRANT_TOKEN) -D PUBNUB_USE_REVOKE_TOKEN_API=$(USE_REVOKE_TOKEN) -D PUBNUB_USE_FETCH_HISTORY=$(USE_FETCH_HISTORY) -D PUBNUB_CRYPTO_API=$(USE_CRYPTO_API) # -g enables debugging, remove to get a smaller executable OS := $(shell uname) diff --git a/cpp/pubnub_common.hpp b/cpp/pubnub_common.hpp index 2dc785f3..078773c2 100644 --- a/cpp/pubnub_common.hpp +++ b/cpp/pubnub_common.hpp @@ -540,6 +540,7 @@ class list_options { }; #endif /* PUBNUB_USE_OBJECTS_API */ +#if PUBNUB_CRYPTO_API /** Interface for a cryptor. It is an algorithm class that * provides encryption and decryption of arrays of bytes. * @@ -705,6 +706,7 @@ class crypto_module: public into_crypto_provider_ptr { private: pubnub_crypto_provider_t* d_module; }; +#endif // #if PUBNUB_CRYPTO_API /** The C++ Pubnub context. It is a wrapper of the Pubnub C context, * not a "native" C++ implementation. diff --git a/freertos/pubnub_ntf_callback_freertos.c b/freertos/pubnub_ntf_callback_freertos.c index 04c77b8c..5d7d9e7b 100644 --- a/freertos/pubnub_ntf_callback_freertos.c +++ b/freertos/pubnub_ntf_callback_freertos.c @@ -1,6 +1,11 @@ /* -*- c-file-style:"stroustrup"; indent-tabs-mode: nil -*- */ #include "pubnub_ntf_callback.h" +#if PUBNUB_USE_RETRY_CONFIGURATION +#include "core/pubnub_retry_configuration_internal.h" +#include "core/pubnub_pubsubapi.h" +#endif // #if PUBNUB_USE_RETRY_CONFIGURATION + #include "pubnub_internal.h" #include "pubnub_assert.h" #include "pubnub_log.h" @@ -291,6 +296,27 @@ void pbntf_update_socket(pubnub_t *pb, pb_socket_t socket) void pbntf_trans_outcome(pubnub_t *pb) { PBNTF_TRANS_OUTCOME_COMMON(pb); +#if PUBNUB_USE_RETRY_CONFIGURATION + if (NULL != pb->core.retry_configuration && + pubnub_retry_configuration_retryable_result_(pb)) { + uint16_t delay = pubnub_retry_configuration_delay_(pb); + + if (delay > 0) { + if (NULL == pb->core.retry_timer) + pb->core.retry_timer = pbcc_request_retry_timer_alloc(pb); + if (NULL != pb->core.retry_timer) { + pbcc_request_retry_timer_start(pb->core.retry_timer, delay); + return; + } + } + } + + /** There were no need to start retry timer, we can free it if exists. */ + if (NULL != pb->core.retry_timer) { + pb->core.http_retry_count = 0; + pbcc_request_retry_timer_free(&pb->core.retry_timer); + } +#endif // #if PUBNUB_USE_RETRY_CONFIGURATION if (pb->cb != NULL) { pb->cb(pb, pb->trans, pb->core.last_result, pb->user_data); } diff --git a/lib/Makefile b/lib/Makefile index bc725511..878edfcb 100644 --- a/lib/Makefile +++ b/lib/Makefile @@ -39,6 +39,16 @@ pubnub_dns_codec_unit_test: pubnub_dns_codec.c pubnub_dns_codec_unit_test.c $(CGREEN_RUNNER) ./pubnub_dns_codec_unit_test.so $(GCOVR) -r . --html --html-details -o coverage.html +pbarray_unit_test: pbref_counter.c pbarray.c pbarray_unit_test.c + gcc -o pbarray_unit_test.so -shared $(CFLAGS) $(LDFLAGS) -I../core/c99 -Wall $(COVERAGE_FLAGS) pbref_counter.c pbarray.c pbarray_unit_test.c -lcgreen -lm + $(CGREEN_RUNNER) ./pbarray_unit_test.so + $(GCOVR) -r . --html --html-details -o coverage.html + +pbhash_set_unit_test: pbref_counter.c pbhash_set.c pbarray.c pbhash_set_unit_test.c + gcc -o pbhash_set_unit_test.so -shared $(CFLAGS) $(LDFLAGS) -I../core/c99 -Wall $(COVERAGE_FLAGS) pbref_counter.c pbhash_set.c pbarray.c pbhash_set_unit_test.c -lcgreen -lm + $(CGREEN_RUNNER) ./pbhash_set_unit_test.so + $(GCOVR) -r . --html --html-details -o coverage.html + clean: find . -type d -iname "*.dSYM" -exec rm -rf {} \+ find . -type f -name "*.so" -o -name "*.gcda" -o -name "*.gcno" -o -name "*.html" | xargs -r rm -rf diff --git a/lib/pbarray.c b/lib/pbarray.c new file mode 100644 index 00000000..cb34e974 --- /dev/null +++ b/lib/pbarray.c @@ -0,0 +1,681 @@ +/* -*- c-file-style:"stroustrup"; indent-tabs-mode: nil -*- */ +#include "pbarray.h" + +#include +#include + +#include "core/pubnub_mutex.h" +#include "lib/pbref_counter.h" + + +// ---------------------------------- +// Types +// ---------------------------------- + +/** + * @brief Single array entry definition. + * + * To avoid potential segmentation failure on a secondary attempt to free + * resources used by the entry, non-raw pointers have been chosen. + */ +typedef struct pbarray_entry { + /** Pointer to the current entry value. */ + void* value; + /** Object references counter. */ + pbref_counter_t* counter; +} pbarray_entry_t; + +/** Auto-resizable array definition. */ +struct pbarray { + /** Array resize strategy. */ + pbarray_resize_strategy resize_strategy; + /** Type of data which will be stored in array. */ + pbarray_content_type content_type; + /** Stored elements `free` function. */ + pbarray_element_free free; + /** + * @brief Initial length of array. + * + * Reference length which will be used by new size computation function. + */ + size_t initial_length; + /** + * @brief Pointer to the pointers to the entries. + * + * @see pbarray_entry_t + */ + void** elements; + /** Current array capacity. */ + size_t length; + /** Number of elements stored in an array. */ + size_t count; + /** Shared resources access lock. */ + pubnub_mutex_t mutw; +}; + + +// ---------------------------------- +// Function prototypes +// ---------------------------------- + +/** + * @brief Remove element from array. + * + * \b Important: If provided during array allocation, the element destruct + * function will be used for the matched element only if there are no other + * references to it (not shared with other arrays after `pbarray_merge`).
+ * \b Important: The `element` pointer will be NULLified if the memory address + * and value of the `element` match the elements that have been freed + * (no references). + * + * @note Remove operation is expensive because it requires other elements to + * shift their position in array to close the “gap”. + * + * @param array Pointer to the array from which element should be + * removed. + * @param element Pointer to the element which should be removed. + * @param all_occurrences Whether all `element` occurrences should be + * removed or not. + * @param [in,out] freed_value Whether entry value actually has been freed + * (no references) or not. + * @return `PBAR_OK` in case if new element has been removed. + */ +static pbarray_res pbarray_remove_( + pbarray_t* array, + void** element, + bool all_occurrences, + bool* freed_value); + +/** + * @brief Compute size, which depends on whether the array increases or shrinks. + * + * @param array Pointer to the array for which new size should be computed. + * @param increase Whether array should be increased in size or not. + * @return New array size. + */ +static size_t pbarray_target_length_(const pbarray_t* array, bool increase); + +/** + * @brief Resize array to fit new elements or shrink to preserve memory. + * + * @param array Pointer to the array which should be resized if it will be + * required. + * @param increase Whether array should be increased in size or not. + * @return `PBAR_OK` in case if array resized or there were no need in resize. + */ +static pbarray_res pbarray_resize_(pbarray_t* array, bool increase); + +/** + * @brief Check whether elements are equal. + * + * @param array Pointer to the array inside which `element1` is stored. + * @param element1 Pointer to the element from the `array` which should be + * compared to another. + * @param element2 Pointer to the element which should be compared to the other + * from array. + * @return `true` in case if two elements are equal. + */ +static bool pbarray_element_is_equal_( + const pbarray_t* array, + const void* element1, + const void* element2); + +/** + * @brief Create an array entry object. + * + * @param value Pointer to the value which should be safely stored in an array. + * @return Pointer to the ready to use array entry object or `NULL` in case of + * insufficient memory error. The returned pointer must be passed to the + * `_pbarray_entry_free` to avoid a memory leak. + */ +static pbarray_entry_t* pbarray_entry_alloc_(void* value); + +/** + * @brief Add entry with user-provided value at specific index in array. + * + * @param array Pointer to the array which will store the entry. + * @param index Index into which `entry` should be placed. + * @param entry Pointer to the entry which holds user-provided value. + * @param clone Whether `entry` clone should be added into array instead. + * @return Entry addition operation result. + */ +static pbarray_res pbarray_add_entry_at_( + pbarray_t* array, + size_t index, + pbarray_entry_t* entry, + bool clone); + +/** + * @brief Remove entry by index from array. + * + * \b Important: The entry value destruct function (if provided during array + * allocation) will be used for the removed `element` (only if it existed in + * array). + * + * @note Remove operation is expensive because it requires other elements to + * shift their position in array to close the “gap”. + * + * @param array Pointer to the array from which entry should be + * removed. + * @param index Index of the `entry` which should be removed. + * @param free_value Whether entry value destructor should be used or + * not. + * @param [in,out] freed_value Whether entry value actually has been freed + * (no references) or not. + * @return Entry removal operation result. + */ +static pbarray_res pbarray_remove_entry_at_( + pbarray_t* array, + size_t index, + bool free_value, + bool* freed_value); + +/** + * @brief Clean up resources used by entry object. + * + * @param array Pointer to the array which stored 'entry'. + * @param entry Pointer to the entry which should be to free up resources. + * @param free_value Whether entry value destructor should be used or not. + * @return `true` if entry value actually has been freed. + */ +static bool pbarray_entry_free_( + const pbarray_t* array, + pbarray_entry_t* entry, + bool free_value); + + +// ---------------------------------- +// Functions +// ---------------------------------- + +pbarray_t* pbarray_alloc( + const size_t length, + const pbarray_resize_strategy resize_strategy, + const pbarray_content_type content_type, + const pbarray_element_free free_fn) +{ + pbarray_t* array = malloc(sizeof(pbarray_t)); + if (NULL == array) { return NULL; } + + array->elements = malloc(length * sizeof(pbarray_entry_t*)); + if (NULL == array->elements) { + free(array); + return NULL; + } + + pubnub_mutex_init(array->mutw); + array->resize_strategy = resize_strategy; + array->content_type = content_type; + array->initial_length = length; + array->length = length; + array->free = free_fn; + array->count = 0; + + return array; +} + +pbarray_t* pbarray_copy(pbarray_t* array) +{ + if (NULL == array->free) { return NULL; } + + pubnub_mutex_lock(array->mutw); + pbarray_t* copy = pbarray_alloc(array->length, + array->resize_strategy, + array->content_type, + array->free); + if (NULL == copy) { + pubnub_mutex_unlock(array->mutw); + return NULL; + } + + for (size_t i = 0; i < array->count; ++i) { + pbarray_entry_t* entry = array->elements[i]; + + if (PBAR_OK != pbarray_add_entry_at_(copy, i, entry, true)) { + pbarray_free(©); + break; + } + } + pubnub_mutex_unlock(array->mutw); + + return copy; +} + +size_t pbarray_count(pbarray_t* array) +{ + if (NULL == array) { return 0; } + + pubnub_mutex_lock(array->mutw); + const size_t count = array->count; + pubnub_mutex_unlock(array->mutw); + + return count; +} + +bool pbarray_contains(pbarray_t* array, const void* element) +{ + pubnub_mutex_lock(array->mutw); + for (int i = 0; i < array->count; ++i) { + const pbarray_entry_t* entry = array->elements[i]; + + if (pbarray_element_is_equal_(array, entry->value, element)) { + pubnub_mutex_unlock(array->mutw); + return true; + } + } + pubnub_mutex_unlock(array->mutw); + + return false; +} + +const void** pbarray_elements(pbarray_t* array, size_t* count) +{ + pubnub_mutex_lock(array->mutw); + const size_t cnt = array->count; + const void** elements = malloc((0 == cnt ? 1 : cnt) * sizeof(void*)); + + if (NULL != count) { *count = cnt; } + if (0 == cnt || NULL == elements) { + pubnub_mutex_unlock(array->mutw); + return elements; + } + + for (size_t i = 0; i < cnt; ++i) { + elements[i] = ((pbarray_entry_t*)array->elements[i])->value; + } + pubnub_mutex_unlock(array->mutw); + + return elements; +} + +pbarray_res pbarray_add(pbarray_t* array, void* element) +{ + pubnub_mutex_lock(array->mutw); + pbarray_entry_t* entry = pbarray_entry_alloc_(element); + if (NULL == entry) { + pubnub_mutex_unlock(array->mutw); + return PBAR_OUT_OF_MEMORY; + } + + const pbarray_res result = + pbarray_add_entry_at_(array, array->count, entry, false); + if (PBAR_OK != result) { pbarray_entry_free_(array, entry, false); } + pubnub_mutex_unlock(array->mutw); + + return result; +} + +pbarray_res pbarray_insert_at( + pbarray_t* array, + void* element, + const size_t idx) +{ + pubnub_mutex_lock(array->mutw); + pbarray_entry_t* entry = pbarray_entry_alloc_(element); + if (NULL == entry) { + pubnub_mutex_unlock(array->mutw); + return PBAR_OUT_OF_MEMORY; + } + + const pbarray_res result = pbarray_add_entry_at_(array, idx, entry, false); + if (PBAR_OK != result) { pbarray_entry_free_(array, entry, false); } + pubnub_mutex_unlock(array->mutw); + + return result; +} + +pbarray_res pbarray_merge(pbarray_t* array, pbarray_t* other_array) +{ + if (NULL == other_array) { return PBAR_OK; } + + pubnub_mutex_lock(array->mutw); + pbarray_res result = PBAR_OK; + + pubnub_mutex_lock(other_array->mutw); + for (size_t i = 0; i < other_array->count; ++i) { + pbarray_entry_t* entry = other_array->elements[i]; + result = pbarray_add_entry_at_(array, array->count, entry, true); + + if (PBAR_OK != result) { break; } + } + pubnub_mutex_unlock(other_array->mutw); + pubnub_mutex_unlock(array->mutw); + + return result; +} + +pbarray_res pbarray_remove( + pbarray_t* array, + void** element, + const bool all_occurrences) +{ + pubnub_mutex_lock(array->mutw); + bool freed = false; + const pbarray_res result = + pbarray_remove_(array, element, all_occurrences, &freed); + + /** Nullify element pointer if memory actually has been freed. */ + if (freed && NULL != element && NULL != *element) { *element = NULL; } + pubnub_mutex_unlock(array->mutw); + + return result; +} + +pbarray_res pbarray_remove_element_at(pbarray_t* array, const size_t idx) +{ + pubnub_mutex_lock(array->mutw); + if (0 == array->count || idx >= array->count) { + pubnub_mutex_unlock(array->mutw); + return idx >= array->count ? PBAR_INDEX_OUT_OF_RANGE : PBAR_OK; + } + + pbarray_remove_entry_at_(array, idx, true, NULL); + pubnub_mutex_unlock(array->mutw); + + return PBAR_OK; +} + +pbarray_res pbarray_remove_all(pbarray_t* array) +{ + pubnub_mutex_lock(array->mutw); + for (int i = 0; i < array->count; ++i) { + pbarray_entry_free_(array, array->elements[i], true); + } + array->count = 0; + pbarray_resize_(array, false); + pubnub_mutex_unlock(array->mutw); + + return PBAR_OK; +} + +void pbarray_subtract( + pbarray_t* array, + pbarray_t* other_array, + const bool all_occurrences) +{ + if (NULL == other_array) { return; } + + pubnub_mutex_lock(array->mutw); + pubnub_mutex_lock(other_array->mutw); + for (size_t i = 0; i < other_array->count; ++i) { + const pbarray_entry_t* other_entry = other_array->elements[i]; + + for (int j = 0; j < array->count;) { + const pbarray_entry_t* entry = array->elements[j]; + + if (pbarray_element_is_equal_(array, + entry->value, + other_entry->value)) { + pbarray_remove_entry_at_(array, j, true, NULL); + if (!all_occurrences) { break; } + } + else { j++; } + } + } + pbarray_resize_(array, false); + pubnub_mutex_unlock(other_array->mutw); + pubnub_mutex_unlock(array->mutw); +} + +const void* pbarray_element_at(pbarray_t* array, const size_t idx) +{ + pubnub_mutex_lock(array->mutw); + if (0 == array->count || idx >= array->count) { + pubnub_mutex_unlock(array->mutw); + return NULL; + } + + const pbarray_entry_t* entry = array->elements[idx]; + pubnub_mutex_unlock(array->mutw); + + return entry->value; +} + +const void* pbarray_first(pbarray_t* array) +{ + pubnub_mutex_lock(array->mutw); + const size_t count = array->count; + const pbarray_entry_t* entry = array->elements[0]; + pubnub_mutex_unlock(array->mutw); + + return 0 == count ? NULL : entry->value; +} + +const void* pbarray_last(pbarray_t* array) +{ + pubnub_mutex_lock(array->mutw); + const size_t count = array->count; + const pbarray_entry_t* entry = array->elements[count - 1]; + pubnub_mutex_unlock(array->mutw); + return 0 == count ? NULL : entry->value; +} + +const void* pbarray_pop_first(pbarray_t* array) +{ + pubnub_mutex_lock(array->mutw); + if (0 == array->count) { + pubnub_mutex_unlock(array->mutw); + return NULL; + } + + const pbarray_entry_t* entry = array->elements[0]; + const void* value = entry->value; + pbarray_remove_entry_at_(array, 0, false, NULL); + pubnub_mutex_unlock(array->mutw); + + return value; +} + +const void* pbarray_pop_last(pbarray_t* array) +{ + pubnub_mutex_lock(array->mutw); + if (0 == array->count) { + pubnub_mutex_unlock(array->mutw); + return NULL; + } + + const pbarray_entry_t* entry = array->elements[array->count - 1]; + const void* value = entry->value; + pbarray_remove_entry_at_(array, array->count - 1, false, NULL); + pubnub_mutex_unlock(array->mutw); + + return value; +} + +void pbarray_free(pbarray_t** array) +{ + pbarray_free_with_destructor(array, (*array)->free); +} + +void pbarray_free_with_destructor( + pbarray_t** array, + const pbarray_element_free free_fn) +{ + pubnub_mutex_lock((*array)->mutw); + if (NULL != free_fn) { (*array)->free = free_fn; } + for (int i = 0; i < (*array)->count; ++i) { + pbarray_entry_free_(*array, (*array)->elements[i], true); + } + pubnub_mutex_unlock((*array)->mutw); + pubnub_mutex_destroy((*array)->mutw); + free(*array); + *array = NULL; +} + +pbarray_res pbarray_remove_( + pbarray_t* array, + void** element, + const bool all_occurrences, + bool* freed_value) +{ + if (NULL == element || NULL == *element || 0 == array->count) + return PBAR_NOT_FOUND; + + bool freed_match = false; + bool found = false; + /** Whether single entry value is complete match of the element or not. */ + bool complete_match = false; + /** Whether single entry value freed or not. */ + bool freed = false; + + for (int i = 0; i < array->count;) { + const pbarray_entry_t* entry = array->elements[i]; + + if (pbarray_element_is_equal_(array, entry->value, *element)) { + complete_match = entry->value == *element; + found = true; + pbarray_remove_entry_at_(array, i, true, &freed); + + if (complete_match && !freed_match) { freed_match = freed; } + if (!all_occurrences) { break; } + } + else { i++; } + } + pbarray_resize_(array, false); + + /** Nullify element pointer if memory actually has been freed. */ + if (freed_match) { *element = NULL; } + if (NULL != freed_value) { *freed_value = freed_match; } + + return found ? PBAR_OK : PBAR_NOT_FOUND; +} + +size_t pbarray_target_length_(const pbarray_t* array, const bool increase) +{ + size_t resize_len = 0; + if (array->resize_strategy == PBARRAY_RESIZE_CONSERVATIVE) { + resize_len = 1; + } + else if (array->resize_strategy == PBARRAY_RESIZE_OPTIMISTIC) { + resize_len = increase ? array->length : array->initial_length; + } + else if (array->resize_strategy == PBARRAY_RESIZE_BALANCED) { + resize_len = array->initial_length / 2; + } + + if (!increase) { + if (array->length - array->count < resize_len && ( + array->resize_strategy == PBARRAY_RESIZE_OPTIMISTIC || + array->resize_strategy == PBARRAY_RESIZE_BALANCED)) { + resize_len = 0; + } + } + else { + if (array->count < array->length) { resize_len = 0; } + else + if (array->resize_strategy == PBARRAY_RESIZE_NONE) { return 0; } + } + + return array->length + resize_len * (increase ? 1 : -1); +} + +pbarray_res pbarray_resize_(pbarray_t* array, const bool increase) +{ + const size_t length = pbarray_target_length_(array, increase); + + if (0 == length) { return PBAR_FIXED_SIZE; } + if (length != array->length) { + void** elements = realloc(array->elements, + length * sizeof(pbarray_entry_t*)); + if (NULL == elements) { return PBAR_OUT_OF_MEMORY; } + + array->elements = elements; + array->length = length; + } + + return PBAR_OK; +} + +bool pbarray_element_is_equal_( + const pbarray_t* array, + const void* element1, + const void* element2) +{ + if (array->content_type == PBARRAY_CHAR_CONTENT_TYPE) { + if (strcmp(element1, element2) == 0) { return true; } + } + else + if (element1 == element2) { return true; } + + return false; +} + +pbarray_entry_t* pbarray_entry_alloc_(void* value) +{ + pbarray_entry_t* entry = malloc(sizeof(pbarray_entry_t)); + if (NULL == entry) { return NULL; } + + entry->counter = pbref_counter_alloc(); + entry->value = value; + + return entry; +} + +pbarray_res pbarray_add_entry_at_( + pbarray_t* array, + const size_t index, + pbarray_entry_t* entry, + const bool clone) +{ + if (index > array->length) { return PBAR_INDEX_OUT_OF_RANGE; } + + const pbarray_res resize_result = pbarray_resize_(array, true); + if (PBAR_OK != resize_result) { return resize_result; } + + /** + * Check whether addition is done not to the end of the array to shift + * other entries position. + */ + if (array->count != index) { + for (size_t i = array->count; i > index; --i) { + array->elements[i] = array->elements[i - 1]; + } + } + /** For safe sharing we need to increase counter. */ + if (clone) { pbref_counter_increment(entry->counter); } + + array->elements[index] = (void*)entry; + array->count++; + + return PBAR_OK; +} + +pbarray_res pbarray_remove_entry_at_( + pbarray_t* array, + const size_t index, + const bool free_value, + bool* freed_value) +{ + pbarray_entry_t* entry = array->elements[index]; + if (NULL == entry) { return PBAR_NOT_FOUND; } + + const bool freed = pbarray_entry_free_(array, entry, free_value); + if (NULL != freed_value) { *freed_value = freed; } + + for (size_t j = index; j < array->count - 1; ++j) { + array->elements[j] = array->elements[j + 1]; + } + array->count--; + + return PBAR_OK; +} + +bool pbarray_entry_free_( + const pbarray_t* array, + pbarray_entry_t* entry, + const bool free_value) +{ + if (NULL == entry) { return true; } + + bool freed = false; + + if (0 == pbref_counter_free(entry->counter)) { + if (free_value && NULL != array->free) { + array->free(entry->value); + freed = true; + } + free(entry); + } + + return freed; +} \ No newline at end of file diff --git a/lib/pbarray.h b/lib/pbarray.h new file mode 100644 index 00000000..ef4191b4 --- /dev/null +++ b/lib/pbarray.h @@ -0,0 +1,435 @@ +/* -*- c-file-style:"stroustrup"; indent-tabs-mode: nil -*- */ +#ifndef PBARRAY_H +#define PBARRAY_H + +/** + * @file pbarray.h + * + * Resizable array type implementation. + */ + +#include +#include + + +// ---------------------------------- +// Types +// ---------------------------------- + +typedef enum +{ + /** + * @brief Array with static length. + * + * If element doesn't fit array won't resize. + */ + PBARRAY_RESIZE_NONE, + /** + * @brief Minimalistic array resize strategy. + * + * Resize array by one (if element added or removed). + */ + PBARRAY_RESIZE_CONSERVATIVE, + /** + * @brief Aggressive array resize. + * + * With each resize, the size of the array will be doubled when a new + * element won't fit to the size of the array. + * + * @note The initial size of the array will be used to identify when the + * array should shrink to preserve memory. + */ + PBARRAY_RESIZE_OPTIMISTIC, + /** + * @brief Moderate array resize strategy. + * + * With each resize, the size of the array will be increased to half of the + * initial size when a new element won't fit to the size of the array. + * + * @note Half of the initial size of the array will be used to identify when + * the array should shrink to preserve memory. + */ + PBARRAY_RESIZE_BALANCED, +} pbarray_resize_strategy; + +/** + * @brief Type of data stored in an array. + * + * Information about array content lets some functions work more efficiently. + */ +typedef enum +{ + /** + * @brief Array used to store generic data as pointers. + * + * @note Content matching will be done using memory addresses. + */ + PBARRAY_GENERIC_CONTENT_TYPE, + /** + * @brief Array used to store string pointers. + * + * @note Content matching will be done using `strcmp`. + */ + PBARRAY_CHAR_CONTENT_TYPE +} pbarray_content_type; + +/** Array result codes. */ +typedef enum +{ + /** + * @brief Success. + * + * Operation finished successfully. + */ + PBAR_OK, + /** + * @brief Element not found. + * + * Referenced element not found in the array. + */ + PBAR_NOT_FOUND, + /** + * @brief Element can't be added or inserted. + * + * A new element cannot be inserted into the array as it has been configured + * with the `PBARRAY_RESIZE_NONE` resize policy to ensure its unalterable + * capacity. + */ + PBAR_FIXED_SIZE, + /**j + * @brief Ran out of dynamic memory. + * + * Operation failed because there were not enough memory to allocate or + * reallocate structures. + */ + PBAR_OUT_OF_MEMORY, + /** + * @brief Index is out of range. + * + * Provided element index is out of array values range (empty or larger than + * current array capacity). + */ + PBAR_INDEX_OUT_OF_RANGE, +} pbarray_res; + +/** + * @brief Array element `free` function prototype. + * + * Function which will be used when hash set is freed using 'pbarray_free'. + * + * @note Element destructor function will `NULL`ify provided pointer. + */ +typedef void (*pbarray_element_free)(void*); + +/** Auto-resizable array type definition. */ +typedef struct pbarray pbarray_t; + + +// ---------------------------------- +// Functions +// ---------------------------------- + +/** + * @brief Create auto-resizable array. + * @code + * // Example: Create a static sized array. + * pbarray_t* array = pbarray_alloc(1, + * PBARRAY_RESIZE_NONE, + * PBARRAY_CHAR_CONTENT_TYPE, + * free); + * pbarray_add(array, "hello"); + * // This call will fail because PBARRAY_RESIZE_NONE strategy has been used. + * pbarray_add(array, " there!"); + * @endcode + * @code + * // Example: Create array with dynamic length. + * pbarray_t* array = pbarray_alloc(1, + * PBARRAY_RESIZE_OPTIMISTIC, + * PBARRAY_CHAR_CONTENT_TYPE, + * free); + * pbarray_add(array, "hello"); + * pbarray_add(array, " there!"); + * @endcode + * + * @param length Initial length of the array. + * @param resize_strategy Strategy which should be used when a new element + * doesn't fit to the size of the array. + * @param content_type Type of data which will be stored in array. + * @param free_fn Elements `free` function. The value could be set to + * `NULL` and leave memory management to the array user. + * @return Pointer to the ready to use resizable array or `NULL` in case of + * insufficient memory error. The returned pointer must be passed to the + * `pbarray_free` to avoid a memory leak. + * + * @see pbarray_add + * @see pbarray_free + */ +pbarray_t* pbarray_alloc( + size_t length, + pbarray_resize_strategy resize_strategy, + pbarray_content_type content_type, + pbarray_element_free free_fn); + +/** + * @brief Create shallow array copy. + * @code + * pbarray_add(array_orig, "hello"); + * pbarray_add(array_orig, " there!"); + * pbarray_t* array_copy = pbarray_copy(array_orig); + * + * // Prints: Contains 'hello' in 'array_copy'? true + * printf("Contains 'hello' in 'array_copy'? %s", + * pbarray_contains(array_copy, "hello") ? "true" : "false"); + * + * // Prints: Contains ' there!' in 'array_copy'? true + * printf("Contains ' there!' in 'array_copy'? %s", + * pbarray_contains(array_copy, " there!") ? "true" : "false"); + * @endcode + * + * \b Important: Copies store shared object, which never shouldn't be freed + * directly to avoid runtime segmentation fault error. This implementation uses + * reference counting and actual data will be freed only if all arrays freed or + * entry has been removed from all arrays.
+ * \b Important: Make sure to have element destructor specified for the source + * array or in case of insufficient memory error already copied entries won't be + * able to free up used resource. + * + * @param array Pointer to the array from which shallow copy should be created. + * @return Pointer to the `array` copy or `NULL` if case of insufficient + * memory error. The returned pointer must be passed to the + * `pbarray_free` to avoid a memory leak. + * + * @see pbarray_remove + * @see pbarray_remove_element_at + * @see pbarray_remove_all + * @see pbarray_pop_first + * @see pbarray_pop_last + * @see pbarray_free + * @see pbarray_free_with_destructor + */ +pbarray_t* pbarray_copy(pbarray_t* array); + +/** + * @brief Number of elements in array. + * + * @param array Pointer to the array, for which number of elements should be + * retrieved. + * @return Number of elements added to the array. + */ +size_t pbarray_count(pbarray_t* array); + +/** + * @brief Check whether element has been added to the array or not. + * + * @param array Pointer to the array, inside which `element` presence should + * be checked. + * @param element Pointer to the element which should be checked. + * @return `true` in case if `element` already exists in the `array`. + */ +bool pbarray_contains(pbarray_t* array, const void* element); + +/** + * @brief Get array elements. + * @code + * // Example: Shallow copy of the elements in array. + * size_t count; + * void** elements = pbarray_elements(array, &count); + * if (NULL == elements) { + * // handle insufficient memory error. + * } else if (0 == count) { + * // array is empty. + * } else { + * // work with data. + * } + * + * // Free up when done. + * if (NULL != elements) { free(elements); } + * @endcode + * + * \b Warning: Returned pointer to the array's element pointers share entries + * with `array` and they shouldn't be manually freed.
+ * \b Warning: Returned pointer to the array's element pointers, and could be + * invalid if `array` or all (some) elements are freed before values will be + * used. + * + * @param array Pointer to the array, from which list of element pointers + * should be retrieved. + * @param [out] count Parameter will hold the count of returned elements. + * @return Pointer to the array's value pointers, or `NULL` in case of + * insufficient memory error. The returned pointer must be passed to the + * `free` to avoid a memory leak. + */ +const void** pbarray_elements(pbarray_t* array, size_t* count); + +/** + * @brief Add a new element to the end of the array. + * + * @param array Pointer to the array to which new element should be added. + * @param element Pointer to the element which should be added. + * @return `PBAR_OK` in case if new element has been added. + */ +pbarray_res pbarray_add(pbarray_t* array, void* element); + +/** + * @brief Insert a new element at a specific location in the array. + * + * @note Insert operation is expensive because it requires other elements to + * shift their position in the array to create a “gap” for the new element. + * + * @param array Pointer to the array to which new element should be inserted. + * @param element Pointer to the element which should be inserted. + * @param idx Index at which `element` should be inserted. Element at + * specified index, and after it will be pushed to the right. + * @return `PBAR_OK` in case if new element has been inserted. + */ +pbarray_res pbarray_insert_at(pbarray_t* array, void* element, size_t idx); + +/** + * @brief Add elements from another array to the end of the array. + * + * @param array Pointer to the array to which new elements should be added. + * @param other_array Pointer to the array with elements which should be added. + * @return `PBAR_OK` in case if new elements has been added. + */ +pbarray_res pbarray_merge(pbarray_t* array, pbarray_t* other_array); + +/** + * @brief Remove element from array. + * + * \b Important: If provided during array allocation, the element destruct + * function will be used for the matched element only if there are no other + * references to it (not shared with other arrays after `pbarray_merge`).
+ * \b Important: The `element` pointer will be NULLified if the memory address + * and value of the `element` match the elements that have been freed + * (no references). + * + * @note Remove operation is expensive because it requires other elements to + * shift their position in array to close the “gap”. + * + * @param array Pointer to the array from which element should be + * removed. + * @param element Pointer to the element which should be removed or not. + * @param all_occurrences Whether all `element` occurrences should be removed or + * not. + * @return `PBAR_OK` in case if element has been removed. + */ +pbarray_res pbarray_remove( + pbarray_t* array, + void** element, + bool all_occurrences); + +/** + * @brief Remove element at specific index from array. + * + * \b Important: The element destruct function (if provided during array + * allocation) will be used for the removed `element` (only if it existed in + * array). + * + * @note Remove operation is expensive because it requires other elements to + * shift their position in array to close the “gap”. + * + * @param array Pointer to the array from which element should be removed. + * @param idx Index at which element for removal is stored in array. + * @return `PBAR_OK` in case if element has been removed. + */ +pbarray_res pbarray_remove_element_at(pbarray_t* array, size_t idx); + +/** + * @brief Remove all element from array. + * + * \b Important: The element destruct function (if provided during array + * allocation) will be used for the removed `element` (only if it existed in + * array). + * + * @param array Pointer to the array from which elements should be removed. + * @return `PBAR_OK` in case if all elements has been removed. + */ +pbarray_res pbarray_remove_all(pbarray_t* array); + +/** + * @brief Remove elements from array which is present in both arrays. + * + * @param array Pointer to the array from which element should be + * removed. + * @param other_array Pointer to the array with elements which should be + * removed. + * @param all_occurrences Whether all `element` occurrences from `other_array` + * should be removed from `array` or not. + */ +void pbarray_subtract( + pbarray_t* array, + pbarray_t* other_array, + bool all_occurrences); + +/** + * @brief Get element from `array` at specified index. + * + * @param array Pointer to the array from which element should be retrieved. + * @param idx Index at which requested element stored in array. + * @return Pointer to the element from array at specified index or `NULL` if it + * is empty or smaller than provided `idx`. + */ +const void* pbarray_element_at(pbarray_t* array, size_t idx); + +/** + * @brief First element in `array`. + * + * @param array Pointer to the array from which element should be retrieved. + * @return Pointer to the first element in array or `NULL` if it is empty. + */ +const void* pbarray_first(pbarray_t* array); + +/** + * @brief Last element in `array`. + * + * @param array Pointer to the array from which element should be retrieved. + * @return Pointer to the last element in array or `NULL` if it is empty. + */ +const void* pbarray_last(pbarray_t* array); + +/** + * @brief Remove first element from `array`. + * + * @param array Pointer to the array from which element should be removed. + * @return Pointer to th first removed element in array or `NULL` if it is + * empty. The returned pointer must be freed to avoid a memory leak. + */ +const void* pbarray_pop_first(pbarray_t* array); + +/** + * @brief Remove last element from `array`. + * + * @param array Pointer to the array from which element should be removed. + * @return Pointer to the last removed element in array or `NULL` if it is + * empty. The returned pointer must be freed to avoid a memory leak. + */ +const void* pbarray_pop_last(pbarray_t* array); + +/** + * @brief Clean up resources used by `array`. + * + * @note User is responsible for reclaiming resources used by the elements if + * element destructing function is not provided. + * @note Function will `NULL`ify provided array pointer. + * + * @param array Pointer to the array, which should free up resources used for it + * and stored elements. + */ +void pbarray_free(pbarray_t** array); + +/** + * @brief Clean up resources used by `array`. + * + * @note User is responsible for reclaiming resources used by the elements if + * element destructing function is not provided. + * @note Function will NULLify provided array pointer. + * + * @param array Pointer to the array, which should free up resources used for + * it and stored elements. + * @param free_fn Elements `free` function. The value could be set to `NULL` and + * leave memory management to the array user. + */ +void pbarray_free_with_destructor( + pbarray_t** array, + pbarray_element_free free_fn); +#endif // #ifndef PBARRAY_H diff --git a/lib/pbarray_unit_test.c b/lib/pbarray_unit_test.c new file mode 100644 index 00000000..deef6cc6 --- /dev/null +++ b/lib/pbarray_unit_test.c @@ -0,0 +1,668 @@ +/* -*- c-file-style:"stroustrup"; indent-tabs-mode: nil -*- */ + +#include +#include +#include + +#include "cgreen/include/cgreen/constraint_syntax_helpers.h" +#include "cgreen/include/cgreen/constraint.h" +#include "cgreen/include/cgreen/assertions.h" +#include "cgreen/include/cgreen/filename.h" +#include "cgreen/include/cgreen/cgreen.h" + +#include "lib/pbarray.h" + + +// ---------------------------------- +// Statics +// ---------------------------------- + +static int elementFreeCounter = 0; +static pbarray_t* array = NULL; + + +// ---------------------------------- +// Helpers +// ---------------------------------- + +void freeElement(void* __unused element) +{ + elementFreeCounter++; +} + + +// ---------------------------------- +// Tests setup +// ---------------------------------- + +Describe(pbarray); + +BeforeEach(pbarray) +{ + array = NULL; + elementFreeCounter = 0; +} + +AfterEach(pbarray) +{ + if (NULL != array) { pbarray_free(&array); } +} + + +// ---------------------------------- +// Tests +// ---------------------------------- + +Ensure(pbarray, should_allocate) +{ + array = pbarray_alloc(2, + PBARRAY_RESIZE_NONE, + PBARRAY_GENERIC_CONTENT_TYPE, + NULL); + + assert_that(pbarray_count(array), is_equal_to(0)); + assert_that(array, is_non_null); +} + +Ensure(pbarray, should_contain_string_element) +{ + array = pbarray_alloc(2, + PBARRAY_RESIZE_BALANCED, + PBARRAY_CHAR_CONTENT_TYPE, + NULL); + pbarray_add(array, "hello once"); + pbarray_add(array, "hello twice"); + pbarray_add(array, "hello again"); + + assert_that(pbarray_contains(array, "hello twice"), is_true); + assert_that(pbarray_contains(array, "hello impostor"), is_false); +} + +Ensure(pbarray, should_make_copy) +{ + char* str1 = strdup("hello once"); + char* str2 = strdup("hello twice"); + char* str3 = strdup("hello again"); + array = pbarray_alloc(2, + PBARRAY_RESIZE_BALANCED, + PBARRAY_CHAR_CONTENT_TYPE, + // it is required to have destructore to make a copy. + free); + pbarray_add(array, str1); + pbarray_add(array, str2); + pbarray_add(array, str3); + + pbarray_t*array_copy = pbarray_copy(array); + + assert_that(pbarray_contains(array_copy, "hello twice"), is_true); + assert_that(pbarray_contains(array_copy, "hello impostor"), is_false); + pbarray_free(&array_copy); +} + +Ensure(pbarray, should_keep_elements_in_copy_on_free) +{ + char* str1 = strdup("hello once"); + char* str2 = strdup("hello twice"); + char* str3 = strdup("hello again"); + array = pbarray_alloc(2, + PBARRAY_RESIZE_BALANCED, + PBARRAY_CHAR_CONTENT_TYPE, + // it is required to have destructore to make a copy. + free); + pbarray_add(array, str1); + pbarray_add(array, str2); + pbarray_add(array, str3); + + pbarray_t*array_copy = pbarray_copy(array); + pbarray_free(&array); + + assert_that(pbarray_contains(array_copy, "hello twice"), is_true); + assert_that(pbarray_contains(array_copy, "hello impostor"), is_false); + pbarray_free(&array_copy); +} + +Ensure(pbarray, should_contain_data) +{ + array = pbarray_alloc(2, + PBARRAY_RESIZE_BALANCED, + PBARRAY_GENERIC_CONTENT_TYPE, + NULL); + char* once = malloc((strlen("hello once") + 1) * sizeof(char)); + strcpy(once, "hello once"); + char* twice = malloc((strlen("hello twice") + 1) * sizeof(char)); + strcpy(twice, "hello twice"); + pbarray_add(array, once); + pbarray_add(array, twice); + pbarray_add(array, "hello again"); + + assert_that(pbarray_contains(array, "hello once"), is_false); + assert_that(pbarray_contains(array, twice), is_true); + + free(once); + free(twice); +} + +Ensure(pbarray, should_return_elements) +{ + array = pbarray_alloc(2, + PBARRAY_RESIZE_BALANCED, + PBARRAY_GENERIC_CONTENT_TYPE, + NULL); + pbarray_add(array, "hello once"); + pbarray_add(array, "hello twice"); + + + size_t count; + const void** elements = pbarray_elements(array, &count); + assert_that(count, is_equal_to(2)); + assert_that(elements[0], is_equal_to_string("hello once")); + assert_that(elements[1], is_equal_to_string("hello twice")); +} + +Ensure(pbarray, should_not_return_elements_when_empty) +{ + array = pbarray_alloc(2, + PBARRAY_RESIZE_BALANCED, + PBARRAY_GENERIC_CONTENT_TYPE, + NULL); + + + size_t count; + const void** elements = pbarray_elements(array, &count); + assert_that(count, is_equal_to(0)); + assert_that(elements, is_not_null); + free(elements); +} + +Ensure(pbarray, should_add_element) +{ + array = pbarray_alloc(1, + PBARRAY_RESIZE_NONE, + PBARRAY_GENERIC_CONTENT_TYPE, + NULL); + + assert_that(pbarray_add(array, "hello"), is_equal_to(PBAR_OK)); + assert_that(pbarray_count(array), is_equal_to(1)); +} + +Ensure(pbarray, should_add_duplicate_element) +{ + array = pbarray_alloc(2, + PBARRAY_RESIZE_NONE, + PBARRAY_GENERIC_CONTENT_TYPE, + NULL); + pbarray_add(array, "hello"); + pbarray_add(array, "hello"); + + assert_that(pbarray_count(array), is_equal_to(2)); + assert_that(pbarray_first(array), is_equal_to(pbarray_last(array))); +} + +Ensure(pbarray, should_resize_when_add_two_elements) +{ + array = pbarray_alloc(1, + PBARRAY_RESIZE_BALANCED, + PBARRAY_GENERIC_CONTENT_TYPE, + NULL); + + assert_that(pbarray_add(array, "hello once"), is_equal_to(PBAR_OK)); + assert_that(pbarray_add(array, "hello twice"), is_equal_to(PBAR_OK)); + assert_that(pbarray_count(array), is_equal_to(2)); +} + +Ensure(pbarray, should_not_resize_when_add_two_elements_and_resize_policy_NONE) +{ + array = pbarray_alloc(1, + PBARRAY_RESIZE_NONE, + PBARRAY_GENERIC_CONTENT_TYPE, + NULL); + pbarray_add(array, "hello once"); + + assert_that(pbarray_add(array, "hello twice"), + is_equal_to(PBAR_FIXED_SIZE)); + assert_that(pbarray_count(array), is_equal_to(1)); +} + +Ensure(pbarray, should_insert_element_at_index_when_index_in_range) +{ + array = pbarray_alloc(4, + PBARRAY_RESIZE_NONE, + PBARRAY_GENERIC_CONTENT_TYPE, + NULL); + pbarray_add(array, "hello once"); + pbarray_add(array, "hello twice"); + pbarray_add(array, "hello again"); + + assert_that(pbarray_insert_at(array, "hello impostor", 1), + is_equal_to(PBAR_OK)); + assert_that(pbarray_count(array), is_equal_to(4)); + assert_that(pbarray_element_at(array, 0), is_equal_to_string("hello once")); + assert_that(pbarray_element_at(array, 1), + is_equal_to_string("hello impostor")); + assert_that(pbarray_element_at(array, 2), + is_equal_to_string("hello twice")); +} + +Ensure(pbarray, should_insert_element_at_index_when_index_equal_length) +{ + array = pbarray_alloc(2, + PBARRAY_RESIZE_BALANCED, + PBARRAY_GENERIC_CONTENT_TYPE, + NULL); + pbarray_add(array, "hello once"); + pbarray_add(array, "hello twice"); + + assert_that(pbarray_insert_at(array, "hello again", pbarray_count(array)), + is_equal_to(PBAR_OK)); + assert_that(pbarray_count(array), is_equal_to(3)); + assert_that(pbarray_element_at(array, 2), + is_equal_to_string("hello again")); +} + +Ensure(pbarray, should_merge_arrays) +{ + array = pbarray_alloc(2, + PBARRAY_RESIZE_BALANCED, + PBARRAY_GENERIC_CONTENT_TYPE, + NULL); + pbarray_add(array, "hello once"); + pbarray_add(array, "hello twice"); + pbarray_t* array2 = pbarray_alloc(2, + PBARRAY_RESIZE_BALANCED, + PBARRAY_GENERIC_CONTENT_TYPE, + NULL); + pbarray_add(array2, "hello again"); + + pbarray_merge(array, array2); + + assert_that(pbarray_contains(array, "hello again"), is_true); + pbarray_free(&array2); +} + +Ensure(pbarray, should_keep_elements_in_merge_on_free) +{ + array = pbarray_alloc(2, + PBARRAY_RESIZE_BALANCED, + PBARRAY_GENERIC_CONTENT_TYPE, + NULL); + pbarray_add(array, "hello once"); + pbarray_add(array, "hello twice"); + pbarray_t* array2 = pbarray_alloc(2, + PBARRAY_RESIZE_BALANCED, + PBARRAY_GENERIC_CONTENT_TYPE, + NULL); + pbarray_add(array2, "hello again"); + + pbarray_merge(array, array2); + pbarray_free(&array2); + + assert_that(pbarray_contains(array, "hello again"), is_true); +} + +Ensure(pbarray, should_not_insert_element_at_index_when_index_out_of_range) +{ + array = pbarray_alloc(2, + PBARRAY_RESIZE_BALANCED, + PBARRAY_GENERIC_CONTENT_TYPE, + NULL); + pbarray_add(array, "hello once"); + pbarray_add(array, "hello twice"); + + assert_that(pbarray_insert_at(array, "hello again", 4), + is_equal_to(PBAR_INDEX_OUT_OF_RANGE)); + assert_that(pbarray_count(array), is_equal_to(2)); +} + +Ensure(pbarray, should_remove_first_string_occurrence) +{ + const char* tested_str = "hello once"; + array = pbarray_alloc(2, + PBARRAY_RESIZE_BALANCED, + PBARRAY_CHAR_CONTENT_TYPE, + NULL); + pbarray_add(array, "hello once"); + pbarray_add(array, "hello twice"); + pbarray_add(array, "hello once"); + + assert_that(pbarray_remove(array, (void**)&tested_str, false), + is_equal_to(PBAR_OK)); + assert_that(pbarray_count(array), is_equal_to(2)); + assert_that(pbarray_element_at(array, 0), + is_equal_to_string("hello twice")); + assert_that(pbarray_element_at(array, 1), is_equal_to_string("hello once")); +} + +Ensure(pbarray, should_remove_all_string_occurrences) +{ + const char* tested_str = "hello once"; + array = pbarray_alloc(2, + PBARRAY_RESIZE_BALANCED, + PBARRAY_CHAR_CONTENT_TYPE, + NULL); + pbarray_add(array, "hello once"); + pbarray_add(array, "hello twice"); + pbarray_add(array, "hello once"); + + assert_that(pbarray_remove(array, (void**)&tested_str, true), + is_equal_to(PBAR_OK)); + assert_that(pbarray_count(array), is_equal_to(1)); + assert_that(pbarray_element_at(array, 0), + is_equal_to_string("hello twice")); +} + +Ensure(pbarray, should_not_remove_when_string_not_found) +{ + const char* tested_str = "hello impostor"; + array = pbarray_alloc(2, + PBARRAY_RESIZE_BALANCED, + PBARRAY_CHAR_CONTENT_TYPE, + freeElement); + pbarray_add(array, "hello once"); + pbarray_add(array, "hello twice"); + + assert_that(pbarray_remove(array, (void**)&tested_str, true), + is_equal_to(PBAR_NOT_FOUND)); + assert_that(pbarray_count(array), is_equal_to(2)); + assert_that(elementFreeCounter, is_equal_to(0)); +} + +Ensure(pbarray, should_not_remove_when_data_NULL) +{ + array = pbarray_alloc(2, + PBARRAY_RESIZE_BALANCED, + PBARRAY_GENERIC_CONTENT_TYPE, + freeElement); + pbarray_add(array, "hello once"); + pbarray_add(array, "hello twice"); + + assert_that(pbarray_remove(array, NULL, true), is_equal_to(PBAR_NOT_FOUND)); + assert_that(pbarray_count(array), is_equal_to(2)); + assert_that(elementFreeCounter, is_equal_to(0)); +} + +Ensure(pbarray, should_remove_first_data_occurrence) +{ + const char* removed_str = "hello once"; + array = pbarray_alloc(2, + PBARRAY_RESIZE_BALANCED, + PBARRAY_GENERIC_CONTENT_TYPE, + NULL); + pbarray_add(array, "hello once"); + pbarray_add(array, "hello twice"); + pbarray_add(array, "hello once"); + + assert_that(pbarray_remove(array, (void**)&removed_str, false), + is_equal_to(PBAR_OK)); + assert_that(pbarray_count(array), is_equal_to(2)); + assert_that(pbarray_element_at(array, 0), + is_equal_to_string("hello twice")); + assert_that(pbarray_element_at(array, 1), is_equal_to_string("hello once")); +} + +Ensure(pbarray, should_remove_all_data_occurrences) +{ + const char* removed_str = "hello once"; + array = pbarray_alloc(2, + PBARRAY_RESIZE_BALANCED, + PBARRAY_GENERIC_CONTENT_TYPE, + NULL); + char* once = malloc((strlen("hello once") + 1) * sizeof(char)); + strcpy(once, "hello once"); + pbarray_add(array, "hello once"); + pbarray_add(array, "hello twice"); + pbarray_add(array, "hello once"); + pbarray_add(array, once); + + assert_that(pbarray_remove(array, (void**)&removed_str, true), + is_equal_to(PBAR_OK)); + assert_that(pbarray_count(array), is_equal_to(2)); + assert_that(pbarray_element_at(array, 0), + is_equal_to_string("hello twice")); + assert_that(pbarray_element_at(array, 1), is_equal_to_string("hello once")); + + free(once); +} + +Ensure(pbarray, should_not_remove_when_data_not_found) +{ + array = pbarray_alloc(2, + PBARRAY_RESIZE_BALANCED, + PBARRAY_GENERIC_CONTENT_TYPE, + freeElement); + char* once = malloc((strlen("hello once") + 1) * sizeof(char)); + strcpy(once, "hello once"); + pbarray_add(array, "hello once"); + pbarray_add(array, "hello twice"); + + assert_that(pbarray_remove(array, (void**)&once, true), is_equal_to(PBAR_NOT_FOUND)); + assert_that(pbarray_count(array), is_equal_to(2)); + assert_that(elementFreeCounter, is_equal_to(0)); + + free(once); +} + +Ensure(pbarray, should_remove_element_at_index) +{ + array = pbarray_alloc(3, + PBARRAY_RESIZE_BALANCED, + PBARRAY_GENERIC_CONTENT_TYPE, + freeElement); + pbarray_add(array, "hello once"); + pbarray_add(array, "hello twice"); + pbarray_add(array, "hello again"); + + assert_that(pbarray_remove_element_at(array, 1), is_equal_to(PBAR_OK)); + assert_that(pbarray_count(array), is_equal_to(2)); + assert_that(elementFreeCounter, is_equal_to(1)); + assert_that(pbarray_contains(array, "hello twice"), is_false); +} + +Ensure(pbarray, should_not_remove_element_at_index_when_index_out_of_range) +{ + array = pbarray_alloc(3, + PBARRAY_RESIZE_BALANCED, + PBARRAY_GENERIC_CONTENT_TYPE, + freeElement); + pbarray_add(array, "hello once"); + pbarray_add(array, "hello twice"); + pbarray_add(array, "hello again"); + + assert_that(pbarray_remove_element_at(array, 4), is_equal_to(PBAR_INDEX_OUT_OF_RANGE)); + assert_that(pbarray_count(array), is_equal_to(3)); + assert_that(elementFreeCounter, is_equal_to(0)); +} + +Ensure(pbarray, should_remove_all_elements) +{ + array = pbarray_alloc(3, + PBARRAY_RESIZE_BALANCED, + PBARRAY_GENERIC_CONTENT_TYPE, + freeElement); + pbarray_add(array, "hello once"); + pbarray_add(array, "hello twice"); + pbarray_add(array, "hello again"); + + assert_that(pbarray_remove_all(array), is_equal_to(PBAR_OK)); + assert_that(pbarray_count(array), is_equal_to(0)); + assert_that(elementFreeCounter, is_equal_to(3)); +} + +Ensure(pbarray, should_subtract_elements) +{ + array = pbarray_alloc(3, + PBARRAY_RESIZE_BALANCED, + PBARRAY_CHAR_CONTENT_TYPE, + freeElement); + pbarray_add(array, "hello there"); + pbarray_add(array, "hello once"); + pbarray_add(array, "hello again"); + pbarray_add(array, "from test"); + pbarray_t* array2 = pbarray_alloc(3, + PBARRAY_RESIZE_BALANCED, + PBARRAY_CHAR_CONTENT_TYPE, + freeElement); + pbarray_add(array2, "hello once"); + pbarray_add(array2, "hello again"); + + pbarray_subtract(array, array2, true); + + assert_that(pbarray_contains(array, "hello once"), is_false); + assert_that(pbarray_contains(array, "hello again"), is_false); + assert_that(pbarray_count(array), is_equal_to(2)); + assert_that(elementFreeCounter, is_equal_to(2)); +} + +Ensure(pbarray, should_return_element_at_index) +{ + array = pbarray_alloc(3, + PBARRAY_RESIZE_BALANCED, + PBARRAY_GENERIC_CONTENT_TYPE, + NULL); + pbarray_add(array, "hello once"); + pbarray_add(array, "hello twice"); + pbarray_add(array, "hello again"); + + assert_that(pbarray_element_at(array, 1), + is_equal_to_string("hello twice")); + assert_that(pbarray_element_at(array, 2), + is_not_equal_to_string("hello once")); +} + +Ensure(pbarray, should_not_return_element_at_index_when_empty) +{ + array = pbarray_alloc(3, + PBARRAY_RESIZE_BALANCED, + PBARRAY_GENERIC_CONTENT_TYPE, + NULL); + + assert_that(pbarray_element_at(array, 0), is_null); +} + +Ensure(pbarray, should_not_return_element_at_index_when_out_of_range) +{ + array = pbarray_alloc(3, + PBARRAY_RESIZE_BALANCED, + PBARRAY_GENERIC_CONTENT_TYPE, + NULL); + pbarray_add(array, "hello once"); + + assert_that(pbarray_element_at(array, 1), is_null); +} + +Ensure(pbarray, should_return_first_element) +{ + array = pbarray_alloc(3, + PBARRAY_RESIZE_BALANCED, + PBARRAY_GENERIC_CONTENT_TYPE, + NULL); + pbarray_add(array, "hello once"); + pbarray_add(array, "hello twice"); + pbarray_add(array, "hello again"); + + assert_that(pbarray_first(array), is_equal_to_string("hello once")); +} + +Ensure(pbarray, should_not_return_first_element_when_empty) +{ + array = pbarray_alloc(3, + PBARRAY_RESIZE_BALANCED, + PBARRAY_GENERIC_CONTENT_TYPE, + NULL); + + assert_that(pbarray_first(array), is_null); +} + +Ensure(pbarray, should_return_last_element) +{ + array = pbarray_alloc(3, + PBARRAY_RESIZE_BALANCED, + PBARRAY_GENERIC_CONTENT_TYPE, + NULL); + pbarray_add(array, "hello once"); + pbarray_add(array, "hello twice"); + pbarray_add(array, "hello again"); + + assert_that(pbarray_last(array), is_equal_to_string("hello again")); +} + +Ensure(pbarray, should_not_return_last_element_when_empty) +{ + array = pbarray_alloc(3, + PBARRAY_RESIZE_BALANCED, + PBARRAY_GENERIC_CONTENT_TYPE, + NULL); + + assert_that(pbarray_last(array), is_null); +} + +Ensure(pbarray, should_remove_and_return_first_element) +{ + array = pbarray_alloc(3, + PBARRAY_RESIZE_BALANCED, + PBARRAY_GENERIC_CONTENT_TYPE, + freeElement); + pbarray_add(array, "hello once"); + pbarray_add(array, "hello twice"); + pbarray_add(array, "hello again"); + + assert_that(pbarray_pop_first(array), is_equal_to_string("hello once")); + assert_that(pbarray_count(array), is_equal_to(2)); + // We return element to the called and it responsible for resources free. + assert_that(elementFreeCounter, is_equal_to(0)); +} + +Ensure(pbarray, should_not_remove_and_return_first_element_when_empty) +{ + array = pbarray_alloc(3, + PBARRAY_RESIZE_BALANCED, + PBARRAY_GENERIC_CONTENT_TYPE, + freeElement); + + assert_that(pbarray_pop_first(array), is_null); + assert_that(elementFreeCounter, is_equal_to(0)); +} + +Ensure(pbarray, should_remove_and_return_last_element) +{ + array = pbarray_alloc(3, + PBARRAY_RESIZE_BALANCED, + PBARRAY_GENERIC_CONTENT_TYPE, + freeElement); + pbarray_add(array, "hello once"); + pbarray_add(array, "hello twice"); + pbarray_add(array, "hello again"); + + assert_that(pbarray_pop_last(array), is_equal_to_string("hello again")); + assert_that(pbarray_count(array), is_equal_to(2)); + // We return element to the called and it responsible for resources free. + assert_that(elementFreeCounter, is_equal_to(0)); +} + +Ensure(pbarray, should_not_remove_and_return_last_element_when_empty) +{ + array = pbarray_alloc(3, + PBARRAY_RESIZE_BALANCED, + PBARRAY_GENERIC_CONTENT_TYPE, + freeElement); + + assert_that(pbarray_pop_last(array), is_null); + assert_that(elementFreeCounter, is_equal_to(0)); +} + +Ensure(pbarray, should_free) +{ + array = pbarray_alloc(3, + PBARRAY_RESIZE_BALANCED, + PBARRAY_GENERIC_CONTENT_TYPE, + freeElement); + pbarray_add(array, "hello once"); + pbarray_add(array, "hello twice"); + pbarray_add(array, "hello again"); + + const int count = pbarray_count(array); + pbarray_free(&array); + + assert_that(elementFreeCounter, is_equal_to(count)); + + array = NULL; +} \ No newline at end of file diff --git a/lib/pbhash_set.c b/lib/pbhash_set.c new file mode 100644 index 00000000..df3772b5 --- /dev/null +++ b/lib/pbhash_set.c @@ -0,0 +1,561 @@ +/* -*- c-file-style:"stroustrup"; indent-tabs-mode: nil -*- */ +#include "pbhash_set.h" + +#include +#include + +#include "core/pubnub_mutex.h" +#include "lib/pbref_counter.h" + + +// ---------------------------------- +// Types +// ---------------------------------- + +typedef struct pbhash_set_node { + /** + * @brieg Optionally, could be a pointer to the structure which may contain + * `value`. + */ + void* containing; + /** Pointer to the current node value. */ + void* value; + /** Object references counter. */ + pbref_counter_t* counter; + /** Pointer to the next node with similar hash value. */ + struct pbhash_set_node* next; + /** Shared resources access lock. */ + pubnub_mutex_t mutw; +} pbhash_set_node_t; + +/** Hash set definition. */ +struct pbhash_set { + /** Type of data which will be stored in a hash set. */ + pbhash_set_content_type content_type; + /** Pointer to the list of node pointers which hold values. */ + pbhash_set_node_t** buckets; + /** A number of buckets can be maintained by hash set. */ + size_t bucket_length; + /** How many currently stored in a hash set. */ + size_t elements_count; + /** Element entries destructor. */ + pbhash_set_element_free free; + /** Shared resources access lock. */ + pubnub_mutex_t mutw; +}; + + +// ---------------------------------- +// Function prototypes +// ---------------------------------- + +/** + * @brief Free up resources used by hash set node. + * + * @param set Pointer to the hash set, from which node should be removed. + * @param node Pointer to the node which should be freed. + * @return `true` if node's value has been freed. + */ +static bool pbhash_set_free_node_( + const pbhash_set_t* set, + pbhash_set_node_t* node); + +/** + * @brief Add hash set node into the bucket. + * + * @note New node with state of origin `node` will be created. + * + * @param set Pointer to the hash set, into which new node should be + * added. + * @param node Pointer to the hash set node with value to add into the + * bucket. + * @param element_hash Computed hash of bucket to store `element` in it. + * @param clone Whether `node` clone should be inserted into `set` or + * not. + * @return Result of `node` addition to the hash set. + */ +static pbhash_set_res pbhash_set_add_node_( + pbhash_set_t* set, + pbhash_set_node_t* node, + size_t element_hash, + bool clone); + +/** + * @brief Quick hash compute for the `element`. + * + * @param set Pointer to the hash set which maybe used to store provided + * `element`. + * @param element Pointer to the element for which hash should be computed. + * @return Content type-dependant `element` hash value. + */ +static size_t pbhash_set_element_hash_( + const pbhash_set_t* set, + const void* element); + +/** + * @brief Check whether `element` already added to the `set`. + * + * \b Important: The type of the `element` should match `content_type` used + * during provided hash 'set' instantiation. + * + * @param set Pointer to the hash set, which should be checked for + * `element` presence. + * @param element Pointer to the element which should be checked. + * @param element_hash Computed hash of bucket to store `element` in it. + * @return `PBHSR_NOT_FOUND` in case if `element` not found in the hash set. + */ +static pbhash_set_res pbhash_set_contains_( + const pbhash_set_t* set, + const void* element, + size_t element_hash); + +/** + * @brief Identify type of the `element` match in the `set`. + * + * @param set Pointer to the hash set, which should be checked for `element` + * presence. + * @param element Pointer to the element which should be checked. + * @return `PBHSR_NOT_FOUND` in case if `element` not found in the hash set. + * + * @see pbhash_set_add + */ +static pbhash_set_res pbhash_set_match_element_( + const pbhash_set_t* set, + const void* element); + +/** + * @brief Check whether an element from hash `set` matches the other element. + * + * \b Important: The types of elements should match `content_type` used during + * provided hash `set` instantiation. + * + * @param set Pointer to the hash set, for which two elements should be + * compared. + * @param element1 Pointer to the left-hand element which should be used in + * compare. + * @param element2 Pointer to the right-hand element which should be used in + * compare. + * @return `true` in case if two elements match each other. + */ +static bool pbhash_set_is_equal_elements_( + const pbhash_set_t* set, + const void* element1, + const void* element2); + + +// ---------------------------------- +// Functions +// ---------------------------------- + +pbhash_set_t* pbhash_set_alloc( + const size_t length, + const pbhash_set_content_type content_type, + const pbhash_set_element_free free_fn) +{ + pbhash_set_t* set = malloc(sizeof(pbhash_set_t)); + if (NULL == set) { return NULL; } + + set->buckets = calloc(length, sizeof(pbhash_set_node_t*)); + if (NULL == set->buckets) { + free(set); + return NULL; + } + + pubnub_mutex_init(set->mutw); + set->content_type = content_type; + set->bucket_length = length; + set->elements_count = 0; + set->free = free_fn; + + return set; +} + +pbhash_set_res pbhash_set_add( + pbhash_set_t* set, + void* element, + void* containing) +{ + pubnub_mutex_lock(set->mutw); + const size_t hash = pbhash_set_element_hash_(set, element); + pbhash_set_res rslt = pbhash_set_contains_(set, element, hash); + + if (PBHSR_NOT_FOUND != rslt) { + pubnub_mutex_unlock(set->mutw); + return rslt; + } + + pbhash_set_node_t* node = malloc(sizeof(pbhash_set_node_t)); + if (NULL == node) { + pubnub_mutex_unlock(set->mutw); + return PBHSR_OUT_OF_MEMORY; + } + + pubnub_mutex_init(node->mutw); + node->counter = pbref_counter_alloc(); + node->containing = containing; + node->value = element; + node->next = NULL; + rslt = pbhash_set_add_node_(set, node, hash, false); + pubnub_mutex_unlock(set->mutw); + + return rslt; +} + +pbhash_set_res pbhash_set_remove( + pbhash_set_t* set, + void** element, + void** containing) +{ + pubnub_mutex_lock(set->mutw); + if (NULL == element || NULL == *element || 0 == set->elements_count) { + pubnub_mutex_unlock(set->mutw); + return PBHSR_NOT_FOUND; + } + + const size_t hash = pbhash_set_element_hash_(set, *element); + pbhash_set_node_t* node = set->buckets[hash]; + pbhash_set_node_t* prev = NULL; + + while (NULL != node) { + if (pbhash_set_is_equal_elements_(set, node->value, *element)) { + if (NULL == prev) { set->buckets[hash] = node->next; } + else { prev->next = node->next; } + if (pbhash_set_free_node_(set, node)) { + if (NULL != containing) { *containing = NULL; } + else { *element = NULL; } + } + set->elements_count--; + pubnub_mutex_unlock(set->mutw); + return PBHSR_OK; + } + + prev = node; + node = node->next; + } + pubnub_mutex_unlock(set->mutw); + + return PBHSR_NOT_FOUND; +} + +pbhash_set_res pbhash_set_union( + pbhash_set_t* set, + pbhash_set_t* other_set, + pbhash_set_t** duplicates_set) +{ + pbhash_set_res rslt = PBHSR_OK; + + if (NULL != duplicates_set) { + *duplicates_set = pbhash_set_alloc(set->bucket_length, + set->content_type, + NULL); + } + + pubnub_mutex_lock(set->mutw); + pubnub_mutex_lock(other_set->mutw); + for (int i = 0; i < other_set->bucket_length; ++i) { + pbhash_set_node_t* node = other_set->buckets[i]; + while (NULL != node && PBHSR_OUT_OF_MEMORY != rslt) { + const size_t hash = pbhash_set_element_hash_(set, node->value); + if (PBHSR_NOT_FOUND == + pbhash_set_contains_(set, node->value, hash)) { + rslt = pbhash_set_add_node_(set, node, hash, true); + } + else if (NULL != duplicates_set) { + pbhash_set_add(*duplicates_set, node->value, node->containing); + } + + node = node->next; + } + } + pubnub_mutex_unlock(other_set->mutw); + pubnub_mutex_unlock(set->mutw); + + return rslt; +} + +pbhash_set_res pbhash_set_subtract( + pbhash_set_t* set, + pbhash_set_t* other_set) +{ + pbhash_set_res rslt = PBHSR_OK; + + pubnub_mutex_lock(other_set->mutw); + for (int i = 0; i < other_set->bucket_length; ++i) { + const pbhash_set_node_t* node = other_set->buckets[i]; + while (NULL != node) { + const pbhash_set_node_t* next = node->next; + rslt = pbhash_set_remove(set, + (void**)&node->value, + (void**)&node->containing); + node = next; + } + } + pubnub_mutex_unlock(other_set->mutw); + + return rslt; +} + +const void* pbhash_set_element(pbhash_set_t* set, const void* element) +{ + const void* matched_element = element; + pubnub_mutex_lock(set->mutw); + const size_t hash = pbhash_set_element_hash_(set, element); + const pbhash_set_node_t* node = set->buckets[hash]; + bool found = false; + + while (NULL != node) { + if (pbhash_set_is_equal_elements_(set, node->value, element)) { + if (NULL != node->containing) { + matched_element = node->containing; + } + found = true; + break; + } + + node = node->next; + } + pubnub_mutex_unlock(set->mutw); + if (!found) { matched_element = NULL; } + + return matched_element; +} + +bool pbhash_set_contains(pbhash_set_t* set, const void* element) +{ + pubnub_mutex_lock(set->mutw); + const size_t hash = pbhash_set_element_hash_(set, element); + const bool contains = PBHSR_NOT_FOUND != pbhash_set_contains_( + set, + element, + hash); + pubnub_mutex_unlock(set->mutw); + + return contains; +} + +bool pbhash_set_is_equal(pbhash_set_t* set, pbhash_set_t* other_set) +{ + pubnub_mutex_lock(set->mutw); + pubnub_mutex_lock(other_set->mutw); + bool equal = set->content_type == other_set->content_type; + equal = equal && set->elements_count == other_set->elements_count; + if (equal) { + size_t count; + const void** elements = pbhash_set_elements(set, &count); + for (size_t i = 0; i < count; ++i) { + equal = pbhash_set_contains(other_set, elements[i]); + if (!equal) { break; } + } + free(elements); + } + pubnub_mutex_unlock(other_set->mutw); + pubnub_mutex_unlock(set->mutw); + + return equal; +} + +pbhash_set_res pbhash_set_match_element( + pbhash_set_t* set, + const void* element) +{ + pubnub_mutex_lock(set->mutw); + const pbhash_set_res rslt = pbhash_set_match_element_(set, element); + pubnub_mutex_unlock(set->mutw); + + return rslt; +} + +pbhash_set_content_type pbhash_set_type( + const pbhash_set_t* set) +{ + return set->content_type; +} + +size_t pbhash_set_count(pbhash_set_t* set) +{ + pubnub_mutex_lock(set->mutw); + const size_t count = set->elements_count; + pubnub_mutex_unlock(set->mutw); + + return count; +} + +const void** pbhash_set_elements(pbhash_set_t* set, size_t* count) +{ + pubnub_mutex_lock(set->mutw); + if (NULL != count) { *count = set->elements_count; } + + const void** elements = calloc( + 0 == set->elements_count ? 1 : set->elements_count, + sizeof(void*)); + if (NULL == elements || 0 == set->elements_count) { + pubnub_mutex_unlock(set->mutw); + return elements; + } + + int element_index = 0; + + for (int i = 0; i < set->bucket_length; ++i) { + const pbhash_set_node_t* node = set->buckets[i]; + while (NULL != node) { + elements[element_index++] = NULL != node->containing + ? node->containing + : node->value; + node = node->next; + } + } + pubnub_mutex_unlock(set->mutw); + + return elements; +} + +void pbhash_set_remove_all(pbhash_set_t* set) +{ + pubnub_mutex_lock(set->mutw); + for (size_t i = 0; i < set->bucket_length; ++i) { + pbhash_set_node_t* node = set->buckets[i]; + while (NULL != node) { + pbhash_set_node_t* next = node->next; + pbhash_set_free_node_(set, node); + node = next; + } + } + memset(set->buckets, 0, set->bucket_length * sizeof(pbhash_set_node_t *)); + set->elements_count = 0; + pubnub_mutex_unlock(set->mutw); +} + +void pbhash_set_free(pbhash_set_t** set) +{ + if (NULL == set || NULL == *set) { return; } + + pbhash_set_free_with_destructor(set, (*set)->free); +} + +void pbhash_set_free_with_destructor( + pbhash_set_t** set, + const pbhash_set_element_free free_fn) +{ + if (NULL == set || NULL == *set) { return; } + + pubnub_mutex_lock((*set)->mutw); + if (NULL != free_fn) { (*set)->free = free_fn; } + for (size_t i = 0; i < (*set)->bucket_length; ++i) { + pbhash_set_node_t* node = (*set)->buckets[i]; + while (NULL != node) { + pbhash_set_node_t* next = node->next; + pbhash_set_free_node_(*set, node); + node = next; + } + } + + free((*set)->buckets); + pubnub_mutex_unlock((*set)->mutw); + pubnub_mutex_destroy((*set)->mutw); + free(*set); + *set = NULL; +} + +pbhash_set_res pbhash_set_match_element_( + const pbhash_set_t* set, + const void* element) +{ + const size_t hash = pbhash_set_element_hash_(set, element); + return pbhash_set_contains_(set, element, hash); +} + +bool pbhash_set_free_node_(const pbhash_set_t* set, pbhash_set_node_t* node) +{ + if (NULL == node) { return true; } + + bool removed = false; + + /** Ensure that node doesn't have any other references before `free`. */ + if (0 == pbref_counter_free(node->counter)) { + if (NULL != set->free) { + if (node->containing) { set->free(node->containing); } + else { set->free(node->value); } + removed = true; + } + } + + free(node); + return removed; +} + +pbhash_set_res pbhash_set_add_node_( + pbhash_set_t* set, + pbhash_set_node_t* node, + const size_t element_hash, + const bool clone) +{ + pbhash_set_node_t* node_to_add = node; + + if (clone) { + node_to_add = malloc(sizeof(pbhash_set_node_t)); + if (NULL == node_to_add) { return PBHSR_OUT_OF_MEMORY; } + + /** Sharing source data with a copy. */ + pubnub_mutex_init(node_to_add->mutw); + node_to_add->counter = node->counter; + node_to_add->containing = node->containing; + node_to_add->value = node->value; + node_to_add->next = NULL; + pbref_counter_increment(node_to_add->counter); + } + + node_to_add->next = set->buckets[element_hash]; + set->buckets[element_hash] = node_to_add; + set->elements_count++; + + return PBHSR_OK; +} + +size_t pbhash_set_element_hash_(const pbhash_set_t* set, const void* element) +{ + size_t hash = 0; + + if (set->content_type == PBHASH_SET_GENERIC_CONTENT_TYPE) { + hash = (size_t)element % set->bucket_length; + } + else if (set->content_type == PBHASH_SET_CHAR_CONTENT_TYPE) { + const size_t bucket_count = set->bucket_length; + const char* str = element; + while (*str) { + hash = (hash * 31 + *str++) % bucket_count; + } + } + + return hash; +} + +pbhash_set_res pbhash_set_contains_( + const pbhash_set_t* set, + const void* element, + const size_t element_hash) +{ + const pbhash_set_node_t* node = set->buckets[element_hash]; + + while (NULL != node) { + if (pbhash_set_is_equal_elements_(set, node->value, element)) { + if (node->value == element) { return PBHSR_EXACT_MATCH_EXISTS; } + return PBHSR_VALUE_EXISTS; + } + + node = node->next; + } + + return PBHSR_NOT_FOUND; +} + +bool pbhash_set_is_equal_elements_( + const pbhash_set_t* set, + const void* element1, + const void* element2) +{ + if (set->content_type == PBHASH_SET_CHAR_CONTENT_TYPE) { + return strcmp(element1, element2) == 0; + } + + return element1 == element2; +} \ No newline at end of file diff --git a/lib/pbhash_set.h b/lib/pbhash_set.h new file mode 100644 index 00000000..5d7d0e02 --- /dev/null +++ b/lib/pbhash_set.h @@ -0,0 +1,508 @@ +/* -*- c-file-style:"stroustrup"; indent-tabs-mode: nil -*- */ +#ifndef PBHASH_SET_H +#define PBHASH_SET_H + +/** + * @file pbhash_set.h + * + * Hash set implementation for unique + */ + +#include +#include + + +// ---------------------------------- +// Types +// ---------------------------------- + +/** + * @brief Type of data stored in hash. + * + * The type of data affects hash computation algorithm. + */ +typedef enum +{ + /** + * @brief Hash set used for generic data storage. + * + * Hash for generic data based on pointer value module from buckets length. + */ + PBHASH_SET_GENERIC_CONTENT_TYPE, + /** + * @brief Hash set used for string data storage. + * + * Hash for string data computed from the value of each character in the + * string. + */ + PBHASH_SET_CHAR_CONTENT_TYPE +} pbhash_set_content_type; + +/** Hash set result codes. */ +typedef enum +{ + /** + * @brief Success. + * + * The operation finished successfully. + */ + PBHSR_OK, + /** + * @brief Element already added (by value). + * + * There was no need to add an element because it already exists in the hash + * set. + * + * \b Important: Passed `element` should be released manually (if required). + * @note For `PBHASH_SET_CHAR_CONTENT_TYPE` content type it means that the + * same string (by value) already added to the hash set. + */ + PBHSR_VALUE_EXISTS, + /** + * @brief Element already added (by pointer). + * + * \b Warning: Don't release a passed `element` because it may have an + * unexpected outcome at runtime. + * @note This status can be returned even when + * `PBHASH_SET_CHAR_CONTENT_TYPE` set as hash set's content type. + */ + PBHSR_EXACT_MATCH_EXISTS, + /** + * @brief Element not found. + * + * Referenced element not found in the hash set. + */ + PBHSR_NOT_FOUND, + /** + * @brief Ran out of dynamic memory. + * + * The operation failed because there was not enough memory to allocate or + * reallocate structures. + */ + PBHSR_OUT_OF_MEMORY, +} pbhash_set_res; + +/** + * @brief Element `free` function prototype. + * + * Function which will be used when a hash set is freed using `pbhash_set_free`. + * + * @note Element destructor function will `NULL`ify provided pointer. + */ +typedef void (*pbhash_set_element_free)(void*); + +/** Hash set definition. */ +typedef struct pbhash_set pbhash_set_t; + + +// ---------------------------------- +// Functions +// ---------------------------------- + +/** + * @brief Create an unordered hash set. + * + * \b Example: + * @code + * // Allocate hash for unique strings which will call `free_ptr()` on entry + * // remove. + * pbhash_set_t* set = pbhash_set_alloc(10, PBHASH_SET_CHAR_CONTENT_TYPE, free); + * @endcode + * + * \b Important: Added elements should be the same type as specified in + * `content_type` (if not set to the `PBHASH_SET_GENERIC_CONTENT_TYPE`). + * + * @param length Maximum number of buckets managed by the hash set. + * @param content_type Type of data which will be stored in a hash set. + * @param free_fn Elements `free` function. This value could be set to + * `NULL` and leave memory management to the hash set user. + * @return Pointer to the ready to use an unordered hash set or 'NULL' in case + * of insufficient memory error. The returned pointer must be passed to + * the `pbhash_set_free` or `pbhash_set_free_with_destructor` to avoid a + * memory leak. + */ +pbhash_set_t* pbhash_set_alloc( + size_t length, + pbhash_set_content_type content_type, + pbhash_set_element_free free_fn); + +/** + * @brief Insert a new element to the had set if it doesn't exist in the set. + * @code + * // Storing "simple" types. + * if (PBHSR_OK == pbhash_set_add(set, "String", NULL)) { + * // `String` value stored in hash. + * } + * @endcode + * @code + * // Storing structured types. + * typedef struct { + * char* name; + * int age; + * } user_t; + * + * user_t* user = malloc(sizeof(user_t)); + * user->name = name; + * user->age = age; + * + * if (PBHSR_OK == pbhash_set_add(set, user->name, user)) { + * // `user` struct stored along with `name` which is used for hash + * // computation and elements match. + * // `PBHASH_SET_CHAR_CONTENT_TYPE` content type should be used when + * // creating a hash set. + * } + * @endcode + * @code + * char* my_value = malloc(6 * sizeof(char)); + * strcpy(my_value, "my-value"); + * + * if (PBHSR_OK == pbhash_set_add(set, my_value, NULL)) { + * // `my_value` has been stored in a hash set. + * } + * + * if (PBHSR_EXACT_MATCH_EXISTS == pbhash_set_add(set, my_value, NULL)) { + * // 'my-value' has been found in the hash set by value and pointer match. + * // There is no action required if `set` has been allocated with elements + * // destructor. + * } + * + * // Allocate string with the same value as `my_value`. + * char* my_new_value = malloc(6 * sizeof(char)); + * strcpy(my_new_value, my_value); + * + * if (PBHSR_VALUE_EXISTS == pbhash_set_add(set, my_new_value, NULL)) { + * // 'my-value' has been found in the hash set by value match. + * // free 'my_new_value' because it has been created (not received). + * free(my_new_value); + * } + * @endcode + * + * \b Warning: A hash set doesn't check `element` data type, and the mechanism + * used to identify uniqueness may return unexpected results or cause a runtime + * exception. + * + * @param set Pointer to the hash set to which a new element should be + * inserted (if not present already). + * @param element Pointer to the element, which should be inserted. + * @param containing Pointer to the object which contains an `element`. `NULL` + * can be used if non-structured data is stored. + * @return Result of `element` addition to the hash set. + */ +pbhash_set_res pbhash_set_add( + pbhash_set_t* set, + void* element, + void* containing); + +/** + * @brief Remove an element from a hash set. + * + * Remove value previously added to the hash set: + * @code + * pbhash_set_res result = pbhash_set_remove(set, my_value, NULL); + * if (PBHSR_OK == result) { + * // `my_value` has been removed from a hash set. + * } else if (PBHSR_NOT_FOUND == result) { + * // `my_value` not found in the has `set` by value (for + * // `PBHASH_SET_CHAR_CONTENT_TYPE` content type) or by address. + * } + * @endcode + * Remove structured data previously added with unique key: + * @code + * // 'set' has been allocated with 'PBHASH_SET_CHAR_CONTENT_TYPE' content type. + * typedef struct { + * char* name; + * int age; + * } user_t; + * + * user_t* user = malloc(sizeof(user_t)); + * user->name = name; + * user->age = age; + * pbhash_set_add(set, user->name, user); + * + * // Remove structured data by `name`: + * if (PBHSR_OK == pbhash_set_remove(set, &user->name, &user)) { + * // `user` has been removed from hash `set` using same field value which + * // has been used to add it. + * } + * @endcode + * + * \b Important: If provided during set allocation, the element destruct + * function will be used for the matched element only if there are no other + * references to it (not shared with other sets after `pbhash_set_union`).
+ * \b Important: The `element` pointer will be NULLified if the memory address + * and value of the `element` match the elements that have been freed + * (no references). + * + * @param set Pointer to the hash set from which new element should be + * removed. + * @param element Pointer to the element which should be removed. + * @param containing Pointer to the object which contains an `element`. `NULL` + * can be used if non-structured data is stored. + * @return Result of `element` removal from the hash set. + * + * @see pbhash_set_add + * @see pbhash_set_union + */ +pbhash_set_res pbhash_set_remove( + pbhash_set_t* set, + void** element, + void** containing); + +/** +* @brief Union `other hash` set entries with source set. + * @code + * pbhash_set_t* set1 = pbhash_set_alloc(10, PBHASH_SET_CHAR_CONTENT_TYPE, NULL); + * pbhash_set_add(set1, value_1, NULL); + * pbhash_set_t* set2 = pbhash_set_alloc(10, PBHASH_SET_CHAR_CONTENT_TYPE, NULL); + * pbhash_set_add(set2, value_2, NULL); + * + * // Merge `set2` into `set1`: + * if (PBHSR_OUT_OF_MEMORY != pbhash_set_union(set1, set2, NULL)) { + * // `value_2` has been merged from `set2` into `set1`. + * } + * @endcode + * Handling duplicated entries: + * @code + * pbhash_set_t* set1 = pbhash_set_alloc(10, PBHASH_SET_CHAR_CONTENT_TYPE, NULL); + * pbhash_set_add(set1, value_1, NULL); + * pbhash_set_t* set2 = pbhash_set_alloc(10, PBHASH_SET_CHAR_CONTENT_TYPE, NULL); + * pbhash_set_add(set2, value_1, NULL); + * pbhash_set_add(set2, value_2, NULL); + * + * // Merge `set2` into `set1`: + * pbhash_set_t* duplicates; + * if (PBHSR_OUT_OF_MEMORY != pbhash_set_union(set1, set2, &duplicates)) { + * // `value_2` has been merged from `set2` into `set1`. + * // `duplicates` set will be initialized with single value which already + * // existed in `set1`: `value_1`. + * } + * @endcode + * + * @note Hash set won't let free up (call element destructor) on elements which + * are shared with another set. Destructor will be applied only element + * not shared between sets anymore.
\b Important: a hash set won't be + * able to protect data from direct release with `free`. + * + * @param set Pointer to the source hash set into which should + * be merged entries from `other` set. + * @param other_set Pointer to the hash set with values which will be + * added to the `set`. + * @param [out] duplicates_set Parameter will hold a set of `other_set` elements + * which already present in `set`. + * @return Result of `other_hash` union with a source hash set. + * + * @see pbhash_set_add + * @see pbhash_set_subtract + */ +pbhash_set_res pbhash_set_union( + pbhash_set_t* set, + pbhash_set_t* other_set, + pbhash_set_t** duplicates_set); + +/** + * @brief Subtract `other hash` set entries from source set. + * @code + * // `pbhash_set_union(set1, set2, NULL)` or same elements from `set2` has been + * // added to `set1` with `pbhash_set_add`. + * // + * // Removing `set2` entries from `set1`: + * if (PBHSR_OK == pbhash_set_subtract(set1, set2)) { + * // All values of `set2` has been removed from `set1`. + * } + * @endcode + * + * @param set Pointer to the source hash set from which entries from + * `other` set should be removed. + * @param other_set Pointer to the hash set with values which will be removed + * from the `hash`. + * @return Result of `other_hash` minus from a source hash set. + * + * @see pbhash_set_add + * @see pbhash_set_union + */ +pbhash_set_res pbhash_set_subtract(pbhash_set_t* set, pbhash_set_t* other_set); + +/** + * @brief Retrieve `element` or containing it object. + * + * \b Important: The type of the `element` should match `content_type` used + * during provided hash 'set' instantiation. + * + * @param set Pointer to the hash set from which element should be returned. + * @param element Pointer to the element which should be used for match. + * @return Pointer to the structure which contains provided `element` as field + * value, element itself if stored as non-structured data or `NULL` in + * case if `element` can't be found. + */ +const void* pbhash_set_element(pbhash_set_t* set, const void* element); + +/** + * @brief Check whether `element` already added to the `set`. + * @code + * const char* value = "hello once"; + * pbhash_set_t* set = pbhash_set_alloc(10, PBHASH_SET_CHAR_CONTENT_TYPE, NULL); + * char* once = malloc((strlen(value) + 1) * sizeof(char)); + * strcpy(once, value); + * pbhash_set_add(set, value, NULL); + * + * // Check with stack `value`: + * if (pbhash_set_contains(set, value)) { + * // `value` has been found in set by value (not address). + * } + * + * // Check with value allocated on heap: + * if (pbhash_set_contains(set, once)) { + * // `once` has been found in set by value (not address). + * } + * @endcode + * Check with `PBHASH_SET_GENERIC_CONTENT_TYPE` has set content type: + * @code + * const char* value = "hello once"; + * pbhash_set_t* set = pbhash_set_alloc(10, PBHASH_SET_GENERIC_CONTENT_TYPE, NULL); + * char* once = malloc((strlen(value) + 1) * sizeof(char)); + * strcpy(once, value); + * pbhash_set_add(set, value, NULL); + * + * // Check with stack `value`: + * if (pbhash_set_contains(set, value)) { + * // `value` has been found by address. + * } + * + * // Check with value allocated on heap: + * if (!pbhash_set_contains(set, once)) { + * // `once` has the same value as `once` but has different address and value + * // with this address not added into the hash `set`. + * } + * @endcode + * + * \b Important: The type of the `element` should match `content_type` used + * during provided hash 'set' instantiation. + * + * @param set Pointer to the hash set, which should be checked for `element` + * presence. + * @param element Pointer to the element which should be checked. + * @return `true` in case if `element` has been found in the hash set. + * + * @see pbhash_set_add + */ +bool pbhash_set_contains(pbhash_set_t* set, const void* element); + +/** + * @brief Check whether `set` content is equal to the other set content. + * + * @note Check will be done only by value (not `containing` object). + * + * @param set Pointer to the source hash set from which content will be + * compared to the other set. + * @param other_set Pointer to the hash set with values which will be used + * in comparison with the `hash`. + * @return `true` if both sets has similar content type, length and set of + * objects. + */ +bool pbhash_set_is_equal(pbhash_set_t* set, pbhash_set_t* other_set); + +/** + * @brief Identify type of the `element` match in the `set`. + * @code + * const char* value = "hello once"; + * pbhash_set_t* set = pbhash_set_alloc(10, PBHASH_SET_CHAR_CONTENT_TYPE, NULL); + * char* once = malloc((strlen(value) + 1) * sizeof(char)); + * strcpy(once, value); + * pbhash_set_add(set, value, NULL); + * + * // Check with stack `value`: + * if (PBHSR_VALUE_EXISTS == pbhash_set_match_element(set, value)) { + * // `value` is not exact data (by address). + * } + * + * // Check with value allocated on heap: + * if (PBHSR_EXACT_MATCH_EXISTS == pbhash_set_contains_exact(set, once)) { + * // `once` is exact data (by address). + * } + * @endcode + * + * \b Important: The type of the `element` should match `content_type` used + * during provided hash 'set' instantiation. + * + * @param set Pointer to the hash set, which should be checked for `element` + * presence. + * @param element Pointer to the element which should be checked. + * @return `PBHSR_NOT_FOUND` in case if `element` not found in the hash set. + * + * @see pbhash_set_add + */ +pbhash_set_res pbhash_set_match_element( + pbhash_set_t* set, + const void* element); + +/** + * @brief Retrieve a type of data stored in hash `set`. + * + * @param set Pointer to the hash set for which content type will be returned. + * @return One of `pbhash_set_content_type` enum fields. + * + * @see pbhash_set_content_type + */ +pbhash_set_content_type pbhash_set_type(const pbhash_set_t* set); + +/** + * @brief Get the number of elements in hash set. + * @code + * pbhash_set_t* set = pbhash_set_alloc(10, PBHASH_SET_CHAR_CONTENT_TYPE, NULL); + * pbhash_set_remove(set, "hello once", NULL); + * pbhash_set_remove(set, "hello twice", NULL); + * // Output: Number of elements: 2 + * printf("Number of elements: %d\n", pbhash_set_count(set)); + * @endcode + * + * @param set Pointer to the hash set for which number of elements should be + * retrieved. + * @return Number of elements stored in the provided hash set. + */ +size_t pbhash_set_count(pbhash_set_t* set); + +/** + * @brief Retrieve elements stored in the set. + * + * @param set Pointer to the hash set from which elements should be retrieved. + * @param [out] count Parameter will hold the count of returned elements. + * @return Pointer to the array of pointers to the elements, or `NULL` in case + * of insufficient memory error. The returned pointer must be passed to + * the `free` to avoid a memory leak. + */ +const void** pbhash_set_elements(pbhash_set_t* set, size_t* count); + +/** + * @brief Remove all hash set entries. + * @param set Pointer to the hash set from which all entries should be removed. + */ +void pbhash_set_remove_all(pbhash_set_t* set); + +/** + * @brief Clean up resources used by `set`. + * + * @note User is responsible for reclaiming resources used by the elements if + * element destructing function is not provided. Destructing function will + * be used on `containing` object if it has been provided along with added + * `element`. + * @note Function will NULLify provided array pointer. + * + * @param set Pointer to the hash set, which should free up resources used for + * it and stored elements. + */ +void pbhash_set_free(pbhash_set_t** set); + +/** + * @brief Clean up resources used by `set` with custom element destructor. + * + * @note Function will NULLify provided array pointer. + * + * @param set Pointer to the hash set, which should free up resources used + * for it and stored elements. + * @param free_fn Elements `free` function. This value could be set to `NULL` + * and leave memory management to the hash set user. + */ +void pbhash_set_free_with_destructor( + pbhash_set_t** set, + pbhash_set_element_free free_fn); +#endif // #ifndef PBHASH_SET_H diff --git a/lib/pbhash_set_unit_test.c b/lib/pbhash_set_unit_test.c new file mode 100644 index 00000000..d90d88b9 --- /dev/null +++ b/lib/pbhash_set_unit_test.c @@ -0,0 +1,341 @@ +/* -*- c-file-style:"stroustrup"; indent-tabs-mode: nil -*- */ + +#include +#include +#include + +#include "cgreen/include/cgreen/constraint_syntax_helpers.h" +#include "cgreen/include/cgreen/constraint.h" +#include "cgreen/include/cgreen/assertions.h" +#include "cgreen/include/cgreen/filename.h" +#include "cgreen/include/cgreen/cgreen.h" + +#include "lib/pbhash_set.h" + + +// ---------------------------------- +// Statics +// ---------------------------------- + +static int elementFreeCounter = 0; +static pbhash_set_t* set = NULL; + +typedef struct { + char* name; + int age; +} user_t; + +// ---------------------------------- +// Helpers +// ---------------------------------- + +user_t* createUser(char* name, const int age) +{ + user_t* user = malloc(sizeof(user_t)); + user->name = name; + user->age = age; + + return user; +} + +void freeElement(void* __unused element) +{ + elementFreeCounter++; +} + + +// ---------------------------------- +// Tests setup +// ---------------------------------- + +Describe(pbhash_set); + +BeforeEach(pbhash_set) +{ + set = NULL; + elementFreeCounter = 0; +} + +AfterEach(pbhash_set) +{ + if (NULL != set) { pbhash_set_free(&set); } +} + + +// ---------------------------------- +// Tests +// ---------------------------------- + +Ensure(pbhash_set, should_allocate) +{ + set = pbhash_set_alloc(10, PBHASH_SET_GENERIC_CONTENT_TYPE, NULL); + + assert_that(set, is_non_null); +} + +Ensure(pbhash_set, should_add_unique_strings) +{ + set = pbhash_set_alloc(10, PBHASH_SET_CHAR_CONTENT_TYPE, NULL); + + assert_that(pbhash_set_add(set, "hello once", NULL), is_equal_to(PBHSR_OK)); + assert_that(pbhash_set_add(set, "hello twice", NULL), + is_equal_to(PBHSR_OK)); +} + +Ensure(pbhash_set, should_not_add_duplicate_strings) +{ + char* value = "hello once"; + set = pbhash_set_alloc(10, PBHASH_SET_CHAR_CONTENT_TYPE, NULL); + char* once = malloc((strlen(value) + 1) * sizeof(char)); + strcpy(once, value); + + assert_that(pbhash_set_add(set, value, NULL), is_equal_to(PBHSR_OK)); + // String with same value already exists. + assert_that(pbhash_set_add(set, once, NULL), + is_equal_to(PBHSR_VALUE_EXISTS)); + + free(once); +} + +Ensure(pbhash_set, should_not_add_exact_duplicate_strings) +{ + char* value = "hello once"; + set = pbhash_set_alloc(10, PBHASH_SET_CHAR_CONTENT_TYPE, NULL); + + assert_that(pbhash_set_add(set, value, NULL), is_equal_to(PBHSR_OK)); + // String is the same by value and address. Value may require manual memory + // management. + assert_that(pbhash_set_add(set, value, NULL), + is_equal_to(PBHSR_EXACT_MATCH_EXISTS)); +} + +Ensure(pbhash_set, should_add_unique_data) +{ + set = pbhash_set_alloc(10, PBHASH_SET_GENERIC_CONTENT_TYPE, NULL); + + assert_that(pbhash_set_add(set, "hello once", NULL), is_equal_to(PBHSR_OK)); + assert_that(pbhash_set_add(set, "hello twice", NULL), + is_equal_to(PBHSR_OK)); +} + +Ensure(pbhash_set, should_add_duplicate_data_with_different_address) +{ + const char* value = "hello once"; + set = pbhash_set_alloc(10, PBHASH_SET_GENERIC_CONTENT_TYPE, NULL); + char* once1 = malloc((strlen(value) + 1) * sizeof(char)); + strcpy(once1, value); + char* once2 = malloc((strlen(value) + 1) * sizeof(char)); + strcpy(once2, value); + + assert_that(pbhash_set_add(set, once1, NULL), is_equal_to(PBHSR_OK)); + assert_that(pbhash_set_add(set, once2, NULL), is_equal_to(PBHSR_OK)); + + free(once1); + free(once2); +} + +Ensure(pbhash_set, should_not_add_duplicate_data) +{ + const char* value = "hello once"; + set = pbhash_set_alloc(10, PBHASH_SET_GENERIC_CONTENT_TYPE, NULL); + char* once = malloc((strlen(value) + 1) * sizeof(char)); + strcpy(once, value); + pbhash_set_add(set, once, NULL); + + // String with same value already exists. + assert_that(pbhash_set_add(set, once, NULL), + is_equal_to(PBHSR_EXACT_MATCH_EXISTS)); + + free(once); +} + +Ensure(pbhash_set, should_add_structured_data_with_key) +{ + set = pbhash_set_alloc(10, PBHASH_SET_GENERIC_CONTENT_TYPE, NULL); + user_t* user = createUser("Bob", 28); + + assert_that(pbhash_set_add(set, user->name, user), is_equal_to(PBHSR_OK)); + size_t count = 0; + const char** elements = (const char**)pbhash_set_elements(set, &count); + assert_that(elements, is_not_null); + assert_that(elements[0], is_equal_to(user)); + + free(user); +} + +Ensure(pbhash_set, should_not_add_duplicate_structured_data_with_key) +{ + set = pbhash_set_alloc(10, PBHASH_SET_GENERIC_CONTENT_TYPE, NULL); + user_t* user = createUser("Bob", 28); + + assert_that(pbhash_set_add(set, user->name, user), is_equal_to(PBHSR_OK)); + assert_that(pbhash_set_add(set, user->name, user), + is_equal_to(PBHSR_EXACT_MATCH_EXISTS)); + + free(user); +} + +Ensure(pbhash_set, should_remove_string) +{ + char* removed_str = "hello twice"; + set = pbhash_set_alloc(10, PBHASH_SET_CHAR_CONTENT_TYPE, freeElement); + pbhash_set_add(set, "hello once", NULL); + pbhash_set_add(set, "hello twice", NULL); + + assert_that(pbhash_set_remove(set, (void**)&removed_str, NULL), is_equal_to(PBHSR_OK)); + assert_that(elementFreeCounter, is_equal_to(1)); +} + +Ensure(pbhash_set, should_not_remove_string_when_not_found) +{ + char* removed_str = "hello twice"; + set = pbhash_set_alloc(10, PBHASH_SET_CHAR_CONTENT_TYPE, freeElement); + pbhash_set_add(set, "hello once", NULL); + + assert_that(pbhash_set_remove(set, (void**)&removed_str, NULL), + is_equal_to(PBHSR_NOT_FOUND)); + assert_that(elementFreeCounter, is_equal_to(0)); +} + +Ensure(pbhash_set, should_union_only_unique_elements) +{ + pbhash_set_t* set1 = pbhash_set_alloc(10, + PBHASH_SET_CHAR_CONTENT_TYPE, + NULL); + pbhash_set_add(set1, "hello once", NULL); + pbhash_set_t* set2 = pbhash_set_alloc(10, + PBHASH_SET_CHAR_CONTENT_TYPE, + NULL); + pbhash_set_add(set2, "hello once", NULL); + pbhash_set_add(set2, "hello twice", NULL); + + assert_that(pbhash_set_union(set1, set2, NULL), is_equal_to(PBHSR_OK)); + assert_that(pbhash_set_contains(set1, "hello twice"), is_true); + + pbhash_set_free(&set1); + pbhash_set_free(&set2); +} + +Ensure(pbhash_set, should_not_free_shared_data_after_union_when_one_set_free) +{ + pbhash_set_t* set1 = pbhash_set_alloc(10, + PBHASH_SET_CHAR_CONTENT_TYPE, + freeElement); + pbhash_set_add(set1, "hello once", NULL); + pbhash_set_t* set2 = pbhash_set_alloc(10, + PBHASH_SET_CHAR_CONTENT_TYPE, + freeElement); + pbhash_set_add(set2, "hello twice", NULL); + pbhash_set_union(set1, set2, NULL); + + pbhash_set_free(&set2); + assert_that(elementFreeCounter, is_equal_to(0)); + + pbhash_set_free(&set1); +} + +Ensure(pbhash_set, should_free_shared_data_shared_with_union_when_all_set_free) +{ + pbhash_set_t* set1 = pbhash_set_alloc(10, + PBHASH_SET_CHAR_CONTENT_TYPE, + freeElement); + pbhash_set_add(set1, "hello once", NULL); + pbhash_set_t* set2 = pbhash_set_alloc(10, + PBHASH_SET_CHAR_CONTENT_TYPE, + freeElement); + pbhash_set_add(set2, "hello twice", NULL); + pbhash_set_union(set1, set2, NULL); + + pbhash_set_free(&set2); + assert_that(elementFreeCounter, is_equal_to(0)); + pbhash_set_free(&set1); + assert_that(elementFreeCounter, is_equal_to(2)); +} + +Ensure(pbhash_set, should_subtract_elements) +{ + pbhash_set_t* set1 = pbhash_set_alloc(10, + PBHASH_SET_CHAR_CONTENT_TYPE, + freeElement); + pbhash_set_add(set1, "hello once", NULL); + pbhash_set_t* set2 = pbhash_set_alloc(10, + PBHASH_SET_CHAR_CONTENT_TYPE, + freeElement); + pbhash_set_add(set2, "hello twice", NULL); + pbhash_set_union(set1, set2, NULL); + + assert_that(pbhash_set_subtract(set1, set2), is_equal_to(PBHSR_OK)); + assert_that(pbhash_set_contains(set1, "hello twice"), is_false); + pbhash_set_free(&set2); + assert_that(elementFreeCounter, is_equal_to(1)); + + pbhash_set_free(&set1); +} + +Ensure(pbhash_set, should_contain_data_by_value) +{ + char* value = "hello once"; + set = pbhash_set_alloc(10, PBHASH_SET_CHAR_CONTENT_TYPE, freeElement); + char* once = malloc((strlen(value) + 1) * sizeof(char)); + strcpy(once, value); + pbhash_set_add(set, value, NULL); + + assert_that(pbhash_set_contains(set, once), is_true); + + free(once); +} + +Ensure(pbhash_set, should_not_contain_data_by_address) +{ + char* value = "hello once"; + set = pbhash_set_alloc(10, PBHASH_SET_GENERIC_CONTENT_TYPE, freeElement); + char* once = malloc((strlen(value) + 1) * sizeof(char)); + strcpy(once, value); + pbhash_set_add(set, value, NULL); + + assert_that(pbhash_set_contains(set, once), is_false); + + free(once); +} + +Ensure(pbhash_set, should_return_added_elements) +{ + char* value = "hello once"; + set = pbhash_set_alloc(10, PBHASH_SET_GENERIC_CONTENT_TYPE, NULL); + char* once = malloc((strlen(value) + 1) * sizeof(char)); + strcpy(once, value); + pbhash_set_add(set, value, NULL); + pbhash_set_add(set, "hello twice", NULL); + + size_t count = 0; + const char** elements = (const char**)pbhash_set_elements(set, &count); + assert_that(elements, is_not_null); + assert_that(count, is_equal_to(2)); + int found_one = 0; + int found_two = 0; + int found_three = 0; + for (size_t i = 0; i < count; i++) { + const char*element = elements[i]; + if (0 == strcmp(element, "hello once")) { found_one = 1; } + if (0 == strcmp(element, "hello twice")) { found_two = 1; } + if (0 == strcmp(element, once)) { found_three = 1; } + } + assert_that(found_one, is_equal_to(1)); + assert_that(found_two, is_equal_to(1)); + assert_that(found_three, is_equal_to(1)); + free(once); + +} + +Ensure(pbhash_set, should_free) +{ + set = pbhash_set_alloc(10, PBHASH_SET_CHAR_CONTENT_TYPE, freeElement); + pbhash_set_add(set, "hello once", NULL); + pbhash_set_add(set, "hello twice", NULL); + + pbhash_set_free(&set); + + assert_that(elementFreeCounter, is_equal_to(2)); + + set = NULL; +} \ No newline at end of file diff --git a/lib/pbref_counter.c b/lib/pbref_counter.c new file mode 100644 index 00000000..173fd7fc --- /dev/null +++ b/lib/pbref_counter.c @@ -0,0 +1,69 @@ +/* -*- c-file-style:"stroustrup"; indent-tabs-mode: nil -*- */ +#include "pbref_counter.h" + +#include +#include +#include + +#include "core/pubnub_mutex.h" + + +// ---------------------------------- +// Types +// ---------------------------------- + +/** Reference counter type definition. */ +struct pbref_counter { + /** Number of references to the tracked shared resource. */ + int count; + /** Counter value access lock. */ + pubnub_mutex_t mutw; +}; + + +// ---------------------------------- +// Functions +// ---------------------------------- + +pbref_counter_t* pbref_counter_alloc(void) +{ + pbref_counter_t* refc = malloc(sizeof(pbref_counter_t)); + if (NULL == refc) { return NULL; } + + pubnub_mutex_init(refc->mutw); + refc->count = 1; + + return refc; +} + +size_t pbref_counter_increment(pbref_counter_t* counter) +{ + pubnub_mutex_lock(counter->mutw); + const size_t count = ++counter->count; + pubnub_mutex_unlock(counter->mutw); + + return count; +} + +size_t pbref_counter_decrement(pbref_counter_t* counter) +{ + size_t count = 0; + pubnub_mutex_lock(counter->mutw); + if (0 != counter->count) { count = --counter->count; } + pubnub_mutex_unlock(counter->mutw); + + return count; +} + +size_t pbref_counter_free(pbref_counter_t* counter) +{ + pubnub_mutex_lock(counter->mutw); + size_t count = counter->count; + if (count > 0 && 0 == (count = --counter->count)) { + pubnub_mutex_unlock(counter->mutw); + pubnub_mutex_destroy(counter->mutw); + free(counter); + } + else { pubnub_mutex_unlock(counter->mutw); } + return count; +} \ No newline at end of file diff --git a/lib/pbref_counter.h b/lib/pbref_counter.h new file mode 100644 index 00000000..2e815efd --- /dev/null +++ b/lib/pbref_counter.h @@ -0,0 +1,69 @@ +/* -*- c-file-style:"stroustrup"; indent-tabs-mode: nil -*- */ +#ifndef PBREF_COUNTER_H +#define PBREF_COUNTER_H + +/** + * @file pbref_counter.h + * + * Thread-safe reference counter. + * The counter facilitates the process of transferring an object between various + * structures and ensures that it will be released only after all references to + * it have disappeared. + */ + +#include + + +// ---------------------------------- +// Types +// ---------------------------------- + +// Reference counter type definition. +typedef struct pbref_counter pbref_counter_t; + + +// ---------------------------------- +// Functions +// ---------------------------------- + +/** + * @brief Create a shared resource reference counter. + * + * @note Usually, the reference counter allocated along with the object for + * which references should be counted and, for simplicity, will be set + * to \b 1. + * + * @return Pointer to the resource reference counter object. + */ +pbref_counter_t* pbref_counter_alloc(void); + +/** + * @brief Increase number of references. + * + * @param counter Pointer to the counter, which should increase the number of + * references to the shared resource. + * @return Current number of references. + */ +size_t pbref_counter_increment(pbref_counter_t* counter); + +/** + * @brief Decrease number of references. + * + * @param counter Pointer to the counter, which should decrease the number of + * references to the shared resource. + * @return Current number of references. + */ +size_t pbref_counter_decrement(pbref_counter_t* counter); + +/** + * @brief Clean up resources used by reference counter object. + * + * \b Important: This function will try release reference counter resources but + * if number of references didn't reached \b 0 it will only references count. + * + * @param counter Pointer to the reference counter object, which should free up + * resources. + * @return Current number of references. + */ +size_t pbref_counter_free(pbref_counter_t* counter); +#endif // #ifndef PBREF_COUNTER_H \ No newline at end of file diff --git a/lib/pbstr_remove_from_list.c b/lib/pbstr_remove_from_list.c index 728083c1..7dd295f4 100644 --- a/lib/pbstr_remove_from_list.c +++ b/lib/pbstr_remove_from_list.c @@ -28,7 +28,7 @@ static void remove_member(char* list, const char* member, size_t member_len) } if (((size_t)(l_ch_end - l_start) == member_len) && (memcmp(l_start, member, member_len) == 0)) { - size_t rest = l_end - l_ch_end + 1; + const size_t rest = l_end != l_ch_end ? l_end - (l_ch_end + 1) : 0; if (rest > 1) { /* Moves everything behind next comma including string end */ memmove(l_start, l_ch_end + 1, rest); diff --git a/lib/pbstrdup.c b/lib/pbstrdup.c new file mode 100644 index 00000000..3406a846 --- /dev/null +++ b/lib/pbstrdup.c @@ -0,0 +1,55 @@ +/* -*- c-file-style:"stroustrup"; indent-tabs-mode: nil -*- */ +#include "pbstrdup.h" + +#include +#include + + +// ---------------------------------- +// Function prototypes +// ---------------------------------- + +/** + * @brief Create a copy of specific length from provided byte-string. + * + * @param src Pointer to the null-terminated byte string to copy. + * @param len How many (at most) bytes from `src` should be written into + * copy. + * @param check_len Whether `len` should be checked to not be larger that `src` + * byte string length. + * @return Pointer to the null-terminated byte string which is copied from + * `src`. `NULL` value will be returned in case of insufficient memory + * error. + */ +static char* _pbstrndup(const char* src, size_t len, bool check_len); + + +// ---------------------------------- +// Functions +// ---------------------------------- + +char* pbstrdup(const char* src) +{ + return _pbstrndup(src, strlen(src), false); +} + +char* pbstrndup(const char* src, const size_t len) +{ + return _pbstrndup(src, len, true); +} + +char* _pbstrndup(const char* src, size_t len, const bool check_len) +{ + if (check_len) { + const size_t actual_len = strlen(src); + if (actual_len < len) { len = actual_len; } + } + + char* copy = malloc(len + 1); + if (NULL != copy) { + memcpy(copy, src, len); + copy[len] = '\0'; + } + + return copy; +} \ No newline at end of file diff --git a/lib/pbstrdup.h b/lib/pbstrdup.h new file mode 100644 index 00000000..9382fe36 --- /dev/null +++ b/lib/pbstrdup.h @@ -0,0 +1,34 @@ +/* -*- c-file-style:"stroustrup"; indent-tabs-mode: nil -*- */ +#ifndef PBSTRDUP_H +#define PBSTRDUP_H + +#include + +/** + * @file pbstrdup.h + * @brief Portable `strdup` implementation. + */ + +/** + * @brief Create a copy from provided byte-string. + * + * @param src Pointer to the null-terminated byte string to copy. + * @return Pointer to the null-terminated byte string which is copied from + * `src`. `NULL` value will be returned in case of insufficient memory + * error. + */ +char* pbstrdup(const char* src); + +/** + * @brief Create a copy of specific length from provided byte-string. + * + * @note Of `len` is larger than `src` actual length then it will be ignored. + * + * @param src Pointer to the null-terminated byte string to copy. + * @param len How many (at most) bytes from `src` should be written into copy. + * @return Pointer to the null-terminated byte string which is copied from + * `src`. `NULL` value will be returned in case of insufficient memory + * error. + */ +char* pbstrndup(const char* src, size_t len); +#endif //PBSTRDUP_H \ No newline at end of file diff --git a/lib/pubnub_parse_ipv6_addr.c b/lib/pubnub_parse_ipv6_addr.c index 3d8671b4..3af1663a 100644 --- a/lib/pubnub_parse_ipv6_addr.c +++ b/lib/pubnub_parse_ipv6_addr.c @@ -8,8 +8,9 @@ #include "core/pubnub_assert.h" #include "core/pubnub_log.h" -#include +#include #include +#include #include #define FORWARD +1 diff --git a/lib/sockets/pbpal_ntf_callback_poller_poll.c b/lib/sockets/pbpal_ntf_callback_poller_poll.c index f36b9d86..cf0f744d 100644 --- a/lib/sockets/pbpal_ntf_callback_poller_poll.c +++ b/lib/sockets/pbpal_ntf_callback_poller_poll.c @@ -106,7 +106,7 @@ void pbpal_ntf_callback_remove_socket(struct pbpal_poll_data* data, pubnub_t* pb } } PUBNUB_LOG_DEBUG( - "pbpal_ntf_callback_remove_socket(pb=%p) sockt=%d: Not Found!", pb, sockt); + "pbpal_ntf_callback_remove_socket(pb=%p) sockt=%d: Not Found!\n", pb, sockt); } @@ -127,7 +127,7 @@ void pbpal_ntf_callback_update_socket(struct pbpal_poll_data* data, pubnub_t* pb } } PUBNUB_LOG_WARNING( - "pbpal_ntf_callback_update_socket(pb=%p) sockt=%d: Not Found!", pb, sockt); + "pbpal_ntf_callback_update_socket(pb=%p) sockt=%d: Not Found!\n", pb, sockt); } @@ -140,7 +140,7 @@ int pbpal_ntf_watch_out_events(struct pbpal_poll_data* data, pubnub_t* pbp) return 0; } } - PUBNUB_LOG_WARNING("pbpal_ntf_watch_out_events(pbp=%p): Not Found!", pbp); + PUBNUB_LOG_WARNING("pbpal_ntf_watch_out_events(pbp=%p): Not Found!\n", pbp); return -1; } @@ -154,7 +154,7 @@ int pbpal_ntf_watch_in_events(struct pbpal_poll_data* data, pubnub_t* pbp) return 0; } } - PUBNUB_LOG_WARNING("pbpal_ntf_watch_in_events(pbp=%p): Not Found!", pbp); + PUBNUB_LOG_WARNING("pbpal_ntf_watch_in_events(pbp=%p): Not Found!\n", pbp); return -1; } diff --git a/microchip_harmony/pubnub_ntf_harmony.c b/microchip_harmony/pubnub_ntf_harmony.c index 8ec947ab..4623dd06 100644 --- a/microchip_harmony/pubnub_ntf_harmony.c +++ b/microchip_harmony/pubnub_ntf_harmony.c @@ -1,6 +1,11 @@ /* -*- c-file-style:"stroustrup"; indent-tabs-mode: nil -*- */ #include "pubnub_ntf_callback.h" +#if PUBNUB_USE_RETRY_CONFIGURATION +#include "core/pubnub_retry_configuration_internal.h" +#include "core/pubnub_pubsubapi.h" +#endif // #if PUBNUB_USE_RETRY_CONFIGURATION + #include "pubnub_internal.h" #include "pubnub_assert.h" #include "pbntf_trans_outcome_common.h" @@ -148,6 +153,27 @@ void pbntf_lost_socket(pubnub_t *pb, pb_socket_t socket) void pbntf_trans_outcome(pubnub_t *pb) { PBNTF_TRANS_OUTCOME_COMMON(pb); +#if PUBNUB_USE_RETRY_CONFIGURATION + if (NULL != pb->core.retry_configuration && + pubnub_retry_configuration_retryable_result_(pb)) { + uint16_t delay = pubnub_retry_configuration_delay_(pb); + + if (delay > 0) { + if (NULL == pb->core.retry_timer) + pb->core.retry_timer = pbcc_request_retry_timer_alloc(pb); + if (NULL != pb->core.retry_timer) { + pbcc_request_retry_timer_start(pb->core.retry_timer, delay); + return; + } + } + } + + /** There were no need to start retry timer, we can free it if exists. */ + if (NULL != pb->core.retry_timer) { + pb->core.http_retry_count = 0; + pbcc_request_retry_timer_free(&pb->core.retry_timer); + } +#endif // #if PUBNUB_USE_RETRY_CONFIGURATION if (pb->cb != NULL) { pb->cb(pb, pb->trans, pb->core.last_result, pb->user_data); } diff --git a/openssl/posix.mk b/openssl/posix.mk index 471f3616..445d083b 100644 --- a/openssl/posix.mk +++ b/openssl/posix.mk @@ -1,6 +1,6 @@ -SOURCEFILES = ../core/pbcc_set_state.c ../core/pubnub_ssl.c ../core/pubnub_pubsubapi.c ../core/pubnub_coreapi.c ../core/pubnub_ccore_pubsub.c ../core/pubnub_ccore.c ../core/pubnub_netcore.c ../lib/sockets/pbpal_resolv_and_connect_sockets.c ../lib/sockets/pbpal_handle_socket_error.c pbpal_openssl.c pbpal_connect_openssl.c pbpal_add_system_certs_posix.c ../core/pubnub_alloc_std.c ../core/pubnub_assert_std.c ../core/pubnub_generate_uuid.c ../core/pubnub_blocking_io.c ../posix/posix_socket_blocking_io.c ../core/pubnub_timers.c ../core/pubnub_json_parse.c ../core/pubnub_helper.c ../posix/pubnub_version_posix.c ../posix/pubnub_generate_uuid_posix.c pbpal_openssl_blocking_io.c ../lib/base64/pbbase64.c ../lib/pb_strnlen_s.c ../lib/pb_strncasecmp.c ../core/pubnub_crypto.c ../core/pubnub_coreapi_ex.c ../core/pubnub_free_with_timeout_std.c pbaes256.c ../posix/msstopwatch_monotonic_clock.c ../posix/pbtimespec_elapsed_ms.c ../core/pubnub_url_encode.c ../core/pubnub_memory_block.c ../posix/pb_sleep_ms.c ../core/pbcc_crypto.c ../core/pbcc_crypto_aes_cbc.c ../core/pbcc_crypto_legacy.c +SOURCEFILES = ../core/pbcc_set_state.c ../core/pubnub_ssl.c ../core/pubnub_pubsubapi.c ../core/pubnub_coreapi.c ../core/pubnub_ccore_pubsub.c ../core/pubnub_ccore.c ../core/pubnub_netcore.c ../lib/sockets/pbpal_resolv_and_connect_sockets.c ../lib/sockets/pbpal_handle_socket_error.c pbpal_openssl.c pbpal_connect_openssl.c pbpal_add_system_certs_posix.c ../core/pubnub_alloc_std.c ../core/pubnub_assert_std.c ../core/pubnub_generate_uuid.c ../core/pubnub_blocking_io.c ../posix/posix_socket_blocking_io.c ../core/pubnub_timers.c ../core/pubnub_json_parse.c ../core/pubnub_helper.c ../posix/pubnub_version_posix.c ../posix/pubnub_generate_uuid_posix.c pbpal_openssl_blocking_io.c ../lib/base64/pbbase64.c ../lib/pb_strnlen_s.c ../lib/pb_strncasecmp.c ../core/pubnub_coreapi_ex.c ../core/pubnub_free_with_timeout_std.c ../posix/msstopwatch_monotonic_clock.c ../posix/pbtimespec_elapsed_ms.c ../core/pubnub_url_encode.c ../core/pubnub_memory_block.c ../posix/pb_sleep_ms.c -OBJFILES = pbcc_set_state.o pubnub_ssl.o pubnub_pubsubapi.o pubnub_coreapi.o pubnub_ccore_pubsub.o pubnub_ccore.o pubnub_netcore.o pbpal_resolv_and_connect_sockets.o pbpal_handle_socket_error.o pbpal_openssl.o pbpal_connect_openssl.o pbpal_add_system_certs_posix.o pubnub_alloc_std.o pubnub_assert_std.o pubnub_generate_uuid.o pubnub_blocking_io.o posix_socket_blocking_io.o pubnub_timers.o pubnub_json_parse.o pubnub_helper.o pubnub_version_posix.o pubnub_generate_uuid_posix.o pbpal_openssl_blocking_io.o pbbase64.o pb_strnlen_s.o pb_strncasecmp.o pubnub_crypto.o pubnub_coreapi_ex.o pubnub_free_with_timeout_std.o pbaes256.o msstopwatch_monotonic_clock.o pbtimespec_elapsed_ms.o pubnub_url_encode.o pubnub_memory_block.o pb_sleep_ms.o pbcc_crypto.o pbcc_crypto_aes_cbc.o pbcc_crypto_legacy.o +OBJFILES = pbcc_set_state.o pubnub_ssl.o pubnub_pubsubapi.o pubnub_coreapi.o pubnub_ccore_pubsub.o pubnub_ccore.o pubnub_netcore.o pbpal_resolv_and_connect_sockets.o pbpal_handle_socket_error.o pbpal_openssl.o pbpal_connect_openssl.o pbpal_add_system_certs_posix.o pubnub_alloc_std.o pubnub_assert_std.o pubnub_generate_uuid.o pubnub_blocking_io.o posix_socket_blocking_io.o pubnub_timers.o pubnub_json_parse.o pubnub_helper.o pubnub_version_posix.o pubnub_generate_uuid_posix.o pbpal_openssl_blocking_io.o pbbase64.o pb_strnlen_s.o pb_strncasecmp.o pubnub_coreapi_ex.o pubnub_free_with_timeout_std.o msstopwatch_monotonic_clock.o pbtimespec_elapsed_ms.o pubnub_url_encode.o pubnub_memory_block.o pb_sleep_ms.o ifndef ONLY_PUBSUB_API ONLY_PUBSUB_API = 0 @@ -10,6 +10,10 @@ ifndef USE_PROXY USE_PROXY = 1 endif +ifndef USE_RETRY_CONFIGURATION +USE_RETRY_CONFIGURATION = 0 +endif + ifndef USE_GZIP_COMPRESSION USE_GZIP_COMPRESSION = 1 endif @@ -51,7 +55,7 @@ USE_FETCH_HISTORY = 1 endif ifndef USE_CRYPTO_API -USE_CRYPTO_API = 0 +USE_CRYPTO_API = 1 endif ifeq ($(USE_PROXY), 1) @@ -109,7 +113,12 @@ REVOKE_TOKEN_SOURCEFILES = ../core/pubnub_revoke_token_api.c ../core/pbcc_revoke REVOKE_TOKEN_OBJFILES = pubnub_revoke_token_api.o pbcc_revoke_token_api.o endif -CFLAGS = -g -D PUBNUB_THREADSAFE -D PUBNUB_LOG_LEVEL=PUBNUB_LOG_LEVEL_WARNING -Wall -D PUBNUB_ONLY_PUBSUB_API=$(ONLY_PUBSUB_API) -D PUBNUB_PROXY_API=$(USE_PROXY) -D PUBNUB_USE_GZIP_COMPRESSION=$(USE_GZIP_COMPRESSION) -D PUBNUB_RECEIVE_GZIP_RESPONSE=$(RECEIVE_GZIP_RESPONSE) -D PUBNUB_USE_SUBSCRIBE_V2=$(USE_SUBSCRIBE_V2) -D PUBNUB_USE_OBJECTS_API=$(USE_OBJECTS_API) -D PUBNUB_USE_ACTIONS_API=$(USE_ACTIONS_API) -D PUBNUB_USE_AUTO_HEARTBEAT=$(USE_AUTO_HEARTBEAT) -D PUBNUB_USE_GRANT_TOKEN_API=$(USE_GRANT_TOKEN) -D PUBNUB_USE_REVOKE_TOKEN_API=$(USE_REVOKE_TOKEN) -D PUBNUB_USE_FETCH_HISTORY=$(USE_FETCH_HISTORY) +ifeq ($(USE_CRYPTO_API), 1) +SOURCEFILES += ../core/pbcc_crypto.c ../core/pbcc_crypto_aes_cbc.c ../core/pbcc_crypto_legacy.c ../core/pubnub_crypto.c ../openssl/pbaes256.c +OBJFILES += pbcc_crypto.o pbcc_crypto_aes_cbc.o pbcc_crypto_legacy.o pubnub_crypto.o pbaes256.o +endif + +CFLAGS = -g -D PUBNUB_THREADSAFE -D PUBNUB_LOG_LEVEL=PUBNUB_LOG_LEVEL_WARNING -Wall -D PUBNUB_ONLY_PUBSUB_API=$(ONLY_PUBSUB_API) -D PUBNUB_PROXY_API=$(USE_PROXY) -D PUBNUB_USE_RETRY_CONFIGURATION=$(USE_RETRY_CONFIGURATION) -D PUBNUB_USE_GZIP_COMPRESSION=$(USE_GZIP_COMPRESSION) -D PUBNUB_RECEIVE_GZIP_RESPONSE=$(RECEIVE_GZIP_RESPONSE) -D PUBNUB_USE_SUBSCRIBE_V2=$(USE_SUBSCRIBE_V2) -D PUBNUB_USE_OBJECTS_API=$(USE_OBJECTS_API) -D PUBNUB_USE_ACTIONS_API=$(USE_ACTIONS_API) -D PUBNUB_USE_AUTO_HEARTBEAT=$(USE_AUTO_HEARTBEAT) -D PUBNUB_USE_GRANT_TOKEN_API=$(USE_GRANT_TOKEN) -D PUBNUB_USE_REVOKE_TOKEN_API=$(USE_REVOKE_TOKEN) -D PUBNUB_USE_FETCH_HISTORY=$(USE_FETCH_HISTORY) -D PUBNUB_CRYPTO_API=$(USE_CRYPTO_API) # -g enables debugging, remove to get a smaller executable # -fsanitize=address Use AddressSanitizer # -fsanitize=thread Use ThreadSanitizer diff --git a/openssl/uwp.mk b/openssl/uwp.mk index e17e81eb..d535c682 100644 --- a/openssl/uwp.mk +++ b/openssl/uwp.mk @@ -23,11 +23,20 @@ ONLY_PUBSUB_API = 0 USE_PROXY = 0 !endif +!ifndef USE_RETRY_CONFIGURATION +USE_RETRY_CONFIGURATION = 0 +!endif + !if $(USE_PROXY) PROXY_INTF_SOURCEFILES = ..\core\pubnub_proxy.c ..\core\pubnub_proxy_core.c ..\core\pbhttp_digest.c ..\core\pbntlm_core.c ..\core\pbntlm_packer_sspi.c ..\windows\pubnub_set_proxy_from_system_windows.c PROXY_INTF_OBJFILES = pubnub_proxy.obj pubnub_proxy_core.obj pbhttp_digest.obj pbntlm_core.obj pbntlm_packer_sspi.obj pubnub_set_proxy_from_system_windows.obj !endif +!if $(USE_RETRY_CONFIGURATION) +PROXY_INTF_SOURCEFILES += ..\core\pbcc_request_retry_timer.c ..\core\pubnub_retry_configuration.c +PROXY_INTF_OBJFILES += pbcc_request_retry_timer.obj pubnub_retry_configuration.obj +!endif + !ifndef USE_REVOKE_TOKEN USE_REVOKE_TOKEN = 1 !endif @@ -41,7 +50,7 @@ USE_FETCH_HISTORY = 1 !endif !ifndef USE_CRYPTO_API -USE_CRYPTO_API = 1 +USE_CRYPTO_API = 1 !endif !if $(USE_REVOKE_TOKEN) @@ -59,7 +68,12 @@ FETCH_HIST_SOURCEFILES = ..\core\pubnub_fetch_history.c ..\core\pbcc_fetch_histo FETCH_HIST_OBJFILES = pubnub_fetch_history.obj pbcc_fetch_history.obj !endif -DEFINES=-D PUBNUB_THREADSAFE -D PUBNUB_LOG_LEVEL=PUBNUB_LOG_LEVEL_WARNING -D HAVE_STRERROR_S -D PUBNUB_ONLY_PUBSUB_API=$(ONLY_PUBSUB_API) -D PUBNUB_PROXY_API=$(USE_PROXY) -D PUBNUB_USE_WIN_SSPI=0 -D PUBNUB_CRYPTO_API=1 -D PUBNUB_USE_GRANT_TOKEN_API=$(USE_GRANT_TOKEN) -D PUBNUB_USE_REVOKE_TOKEN_API=$(USE_REVOKE_TOKEN) -D PUBNUB_USE_FETCH_HISTORY=$(USE_FETCH_HISTORY) +!if $(USE_CRYPTO_API) +FETCH_HIST_SOURCEFILES += ..\core\pbcc_crypto.c ..\core\pbcc_crypto_aes_cbc.c ..\core\pbcc_crypto_legacy.c ..\core\pubnub_crypto.c ..\openssl\pbaes256.c +FETCH_HIST_OBJFILES += pbcc_crypto.obj pbcc_crypto_aes_cbc.obj pbcc_crypto_legacy.obj pubnub_crypto.obj pbaes256.obj +!endif + +DEFINES=-D PUBNUB_THREADSAFE -D PUBNUB_LOG_LEVEL=PUBNUB_LOG_LEVEL_WARNING -D HAVE_STRERROR_S -D PUBNUB_ONLY_PUBSUB_API=$(ONLY_PUBSUB_API) -D PUBNUB_PROXY_API=$(USE_PROXY) -D PUBNUB_USE_RETRY_CONFIGURATION=$(USE_RETRY_CONFIGURATION) -D PUBNUB_USE_WIN_SSPI=0 -D PUBNUB_CRYPTO_API=1 -D PUBNUB_USE_GRANT_TOKEN_API=$(USE_GRANT_TOKEN) -D PUBNUB_USE_REVOKE_TOKEN_API=$(USE_REVOKE_TOKEN) -D PUBNUB_USE_FETCH_HISTORY=$(USE_FETCH_HISTORY) -D PUBNUB_CRYPTO_API=$(USE_CRYPTO_API) UDEFINES=-D "_UNICODE" -D "UNICODE" -D "WINAPI_FAMILY=WINAPI_FAMILY_APP" -D "__WRL_NO_DEFAULT_LIB__" -D "_CRT_SECURE_NO_WARNINGS" -D "_WINSOCK_DEPRECATED_NO_WARNINGS" -D "__UWP__" -D "HAVE_STRUCT_TIMESPEC" CFLAGS = -Yu"pch.h" -Zi -Y- -MP -W3 -Gy -Zc:wchar_t $(UDEFINES) $(DEFINES) -Gm- -O2 -sdl -errorReport:prompt -WX- -Zc:forScope /Gd /Oy- /Oi /MD /FC -FU"C:\Program Files (x86)\Microsoft Visual Studio\2019\Community\VC\Tools\MSVC\14.28.29333\lib\x86\store\references\platform.winmd" # /Zi enables debugging, remove to get a smaller .exe and no .pdb diff --git a/openssl/windows.mk b/openssl/windows.mk index beb600c5..a6f8bc9b 100644 --- a/openssl/windows.mk +++ b/openssl/windows.mk @@ -1,6 +1,6 @@ -SOURCEFILES = ..\core\pbcc_set_state.c ..\core\pubnub_pubsubapi.c ..\core\pubnub_coreapi.c ..\core\pubnub_ccore_pubsub.c ..\core\pubnub_ccore.c ..\core\pubnub_netcore.c ..\lib\sockets\pbpal_resolv_and_connect_sockets.c ..\lib\sockets\pbpal_handle_socket_error.c pbpal_openssl.c pbpal_connect_openssl.c pbpal_add_system_certs_windows.c ..\core\pubnub_alloc_std.c ..\core\pubnub_assert_std.c ..\core\pubnub_generate_uuid.c ..\core\pubnub_blocking_io.c ..\windows\windows_socket_blocking_io.c ..\core\pubnub_free_with_timeout_std.c ..\windows\pbtimespec_elapsed_ms.c ..\core\pubnub_timers.c ..\core\pubnub_json_parse.c ..\lib\md5\md5.c ..\lib\pb_strnlen_s.c ..\lib\pb_strncasecmp.c ..\core\pubnub_ssl.c ..\core\pubnub_helper.c ..\windows\pubnub_version_windows.c ..\windows\pubnub_generate_uuid_windows.c pbpal_openssl_blocking_io.c ..\lib\base64\pbbase64.c ..\core\pubnub_crypto.c ..\core\pubnub_coreapi_ex.c pbaes256.c ..\core\c99\snprintf.c ..\lib\miniz\miniz_tinfl.c ..\lib\miniz\miniz_tdef.c ..\lib\miniz\miniz.c ..\lib\pbcrc32.c ..\core\pbgzip_compress.c ..\core\pbgzip_decompress.c ..\core\pbcc_subscribe_v2.c ..\core\pubnub_subscribe_v2.c ..\windows\msstopwatch_windows.c ..\core\pubnub_url_encode.c ..\core\pbcc_advanced_history.c ..\core\pubnub_advanced_history.c ..\core\pbcc_objects_api.c ..\core\pubnub_objects_api.c ..\core\pbcc_actions_api.c ..\core\pubnub_actions_api.c ..\core\pubnub_memory_block.c ..\lib\pbstr_remove_from_list.c ..\windows\pb_sleep_ms.c ..\core\pbauto_heartbeat.c ..\windows\pbauto_heartbeat_init_windows.c ../core/pbcc_crypto.c ../core/pbcc_crypto_aes_cbc.c ../core/pbcc_crypto_legacy.c +SOURCEFILES = ..\core\pbcc_set_state.c ..\core\pubnub_pubsubapi.c ..\core\pubnub_coreapi.c ..\core\pubnub_ccore_pubsub.c ..\core\pubnub_ccore.c ..\core\pubnub_netcore.c ..\lib\sockets\pbpal_resolv_and_connect_sockets.c ..\lib\sockets\pbpal_handle_socket_error.c pbpal_openssl.c pbpal_connect_openssl.c pbpal_add_system_certs_windows.c ..\core\pubnub_alloc_std.c ..\core\pubnub_assert_std.c ..\core\pubnub_generate_uuid.c ..\core\pubnub_blocking_io.c ..\windows\windows_socket_blocking_io.c ..\core\pubnub_free_with_timeout_std.c ..\windows\pbtimespec_elapsed_ms.c ..\core\pubnub_timers.c ..\core\pubnub_json_parse.c ..\lib\md5\md5.c ..\lib\pb_strnlen_s.c ..\lib\pb_strncasecmp.c ..\core\pubnub_ssl.c ..\core\pubnub_helper.c ..\windows\pubnub_version_windows.c ..\windows\pubnub_generate_uuid_windows.c pbpal_openssl_blocking_io.c ..\core\pubnub_coreapi_ex.c pbaes256.c ..\core\c99\snprintf.c ..\lib\miniz\miniz_tinfl.c ..\lib\miniz\miniz_tdef.c ..\lib\miniz\miniz.c ..\lib\pbcrc32.c ..\core\pbgzip_compress.c ..\core\pbgzip_decompress.c ..\core\pbcc_subscribe_v2.c ..\core\pubnub_subscribe_v2.c ..\windows\msstopwatch_windows.c ..\core\pubnub_url_encode.c ..\core\pbcc_advanced_history.c ..\core\pubnub_advanced_history.c ..\core\pbcc_objects_api.c ..\core\pubnub_objects_api.c ..\core\pbcc_actions_api.c ..\core\pubnub_actions_api.c ..\core\pubnub_memory_block.c ..\lib\pbstr_remove_from_list.c ..\windows\pb_sleep_ms.c ..\core\pbauto_heartbeat.c ..\windows\pbauto_heartbeat_init_windows.c -OBJFILES = pbcc_set_state.obj pubnub_pubsubapi.obj pubnub_coreapi.obj pubnub_ccore_pubsub.obj pubnub_ccore.obj pubnub_netcore.obj pbpal_resolv_and_connect_sockets.obj pbpal_handle_socket_error.obj pbpal_openssl.obj pbpal_connect_openssl.obj pbpal_add_system_certs_windows.obj pubnub_alloc_std.obj pubnub_assert_std.obj pubnub_generate_uuid.obj pubnub_blocking_io.obj pubnub_free_with_timeout_std.obj pbtimespec_elapsed_ms.obj pubnub_timers.obj pubnub_json_parse.obj md5.obj pb_strnlen_s.obj pb_strncasecmp.obj pubnub_ssl.obj pubnub_helper.obj pubnub_version_windows.obj pubnub_generate_uuid_windows.obj pbpal_openssl_blocking_io.obj windows_socket_blocking_io.obj pbbase64.obj pubnub_crypto.obj pubnub_coreapi_ex.obj pbaes256.obj snprintf.obj miniz_tinfl.obj miniz_tdef.obj miniz.obj pbcrc32.obj pbgzip_compress.obj pbgzip_decompress.obj pbcc_subscribe_v2.obj pubnub_subscribe_v2.obj msstopwatch_windows.obj pubnub_url_encode.obj pbcc_advanced_history.obj pubnub_advanced_history.obj pbcc_objects_api.obj pubnub_objects_api.obj pbcc_actions_api.obj pubnub_actions_api.obj pubnub_memory_block.obj pbstr_remove_from_list.obj pb_sleep_ms.obj pbauto_heartbeat.obj pbauto_heartbeat_init_windows.obj pbcc_crypto.obj pbcc_crypto_aes_cbc.obj pbcc_crypto_legacy.obj +OBJFILES = pbcc_set_state.obj pubnub_pubsubapi.obj pubnub_coreapi.obj pubnub_ccore_pubsub.obj pubnub_ccore.obj pubnub_netcore.obj pbpal_resolv_and_connect_sockets.obj pbpal_handle_socket_error.obj pbpal_openssl.obj pbpal_connect_openssl.obj pbpal_add_system_certs_windows.obj pubnub_alloc_std.obj pubnub_assert_std.obj pubnub_generate_uuid.obj pubnub_blocking_io.obj pubnub_free_with_timeout_std.obj pbtimespec_elapsed_ms.obj pubnub_timers.obj pubnub_json_parse.obj md5.obj pb_strnlen_s.obj pb_strncasecmp.obj pubnub_ssl.obj pubnub_helper.obj pubnub_version_windows.obj pubnub_generate_uuid_windows.obj pbpal_openssl_blocking_io.obj windows_socket_blocking_io.obj pbbase64.obj pubnub_coreapi_ex.obj pbaes256.obj snprintf.obj miniz_tinfl.obj miniz_tdef.obj miniz.obj pbcrc32.obj pbgzip_compress.obj pbgzip_decompress.obj pbcc_subscribe_v2.obj pubnub_subscribe_v2.obj msstopwatch_windows.obj pubnub_url_encode.obj pbcc_advanced_history.obj pubnub_advanced_history.obj pbcc_objects_api.obj pubnub_objects_api.obj pbcc_actions_api.obj pubnub_actions_api.obj pubnub_memory_block.obj pbstr_remove_from_list.obj pb_sleep_ms.obj pbauto_heartbeat.obj pbauto_heartbeat_init_windows.obj !ifndef OPENSSLPATH OPENSSLPATH=C:\OpenSSL-Win32 @@ -44,11 +44,20 @@ USE_FETCH_HISTORY = 1 USE_CRYPTO_API = 0 !endif +!ifndef USE_RETRY_CONFIGURATION +USE_RETRY_CONFIGURATION = 0 +!endif + !if $(USE_PROXY) PROXY_INTF_SOURCEFILES = ..\core\pubnub_proxy.c ..\core\pubnub_proxy_core.c ..\core\pbhttp_digest.c ..\core\pbntlm_core.c ..\core\pbntlm_packer_sspi.c ..\windows\pubnub_set_proxy_from_system_windows.c PROXY_INTF_OBJFILES = pubnub_proxy.obj pubnub_proxy_core.obj pbhttp_digest.obj pbntlm_core.obj pbntlm_packer_sspi.obj pubnub_set_proxy_from_system_windows.obj !endif +!if $(USE_RETRY_CONFIGURATION) +PROXY_INTF_SOURCEFILES += ..\core\pbcc_request_retry_timer.c ..\core\pubnub_retry_configuration.c +PROXY_INTF_OBJFILES += pbcc_request_retry_timer.obj pubnub_retry_configuration.obj +!endif + !if $(USE_REVOKE_TOKEN) REVOKE_TOKEN_SOURCEFILES = ..\core\pubnub_revoke_token_api.c ..\core\pbcc_revoke_token_api.c REVOKE_TOKEN_OBJFILES = pubnub_revoke_token_api.obj pbcc_revoke_token_api.obj @@ -64,7 +73,12 @@ FETCH_HIST_SOURCEFILES = ..\core\pubnub_fetch_history.c ..\core\pbcc_fetch_histo FETCH_HIST_OBJFILES = pubnub_fetch_history.obj pbcc_fetch_history.obj !endif -CFLAGS = /Zi /MP -D PUBNUB_THREADSAFE /D PUBNUB_LOG_LEVEL=PUBNUB_LOG_LEVEL_WARNING /W3 /D PUBNUB_USE_WIN_SSPI=1 /D PUBNUB_ONLY_PUBSUB_API=$(ONLY_PUBSUB_API) /D PUBNUB_PROXY_API=$(USE_PROXY) /D PUBNUB_USE_SUBSCRIBE_V2=$(USE_SUBSCRIBE_V2) /D _CRT_SECURE_NO_WARNINGS /D PUBNUB_CRYPTO_API=1 /D PUBNUB_USE_GRANT_TOKEN_API=$(USE_GRANT_TOKEN) /D PUBNUB_USE_REVOKE_TOKEN_API=$(USE_REVOKE_TOKEN) /D PUBNUB_USE_FETCH_HISTORY=$(USE_FETCH_HISTORY) +!if $(USE_CRYPTO_API) +FETCH_HIST_SOURCEFILES += ..\core\pbcc_crypto.c ..\core\pbcc_crypto_aes_cbc.c ..\core\pbcc_crypto_legacy.c ..\core\pubnub_crypto.c ..\openssl\pbaes256.c +FETCH_HIST_OBJFILES += pbcc_crypto.obj pbcc_crypto_aes_cbc.obj pbcc_crypto_legacy.obj pubnub_crypto.obj pbaes256.obj +!endif + +CFLAGS = /Zi /MP -D PUBNUB_THREADSAFE /D PUBNUB_LOG_LEVEL=PUBNUB_LOG_LEVEL_WARNING /W3 /D PUBNUB_USE_WIN_SSPI=1 /D PUBNUB_ONLY_PUBSUB_API=$(ONLY_PUBSUB_API) /D PUBNUB_PROXY_API=$(USE_PROXY) /D PUBNUB_USE_RETRY_CONFIGURATION=$(USE_RETRY_CONFIGURATION) /D PUBNUB_USE_SUBSCRIBE_V2=$(USE_SUBSCRIBE_V2) /D _CRT_SECURE_NO_WARNINGS /D PUBNUB_CRYPTO_API=$(USE_CRYPTO_API) /D PUBNUB_USE_GRANT_TOKEN_API=$(USE_GRANT_TOKEN) /D PUBNUB_USE_REVOKE_TOKEN_API=$(USE_REVOKE_TOKEN) /D PUBNUB_USE_FETCH_HISTORY=$(USE_FETCH_HISTORY) # /Zi enables debugging, remove to get a smaller .exe and no .pdb # /MP uses one compiler (`cl`) process for each input file, enabling faster build # /analyze To run the static analyzer (not compatible w/clang-cl) diff --git a/posix/posix.mk b/posix/posix.mk index abc9cadb..80cc008a 100644 --- a/posix/posix.mk +++ b/posix/posix.mk @@ -10,6 +10,10 @@ ifndef USE_PROXY USE_PROXY = 1 endif +ifndef USE_RETRY_CONFIGURATION +USE_RETRY_CONFIGURATION = 0 +endif + ifndef USE_GZIP_COMPRESSION USE_GZIP_COMPRESSION = 1 endif @@ -22,6 +26,10 @@ ifndef USE_SUBSCRIBE_V2 USE_SUBSCRIBE_V2 = 1 endif +ifndef USE_SUBSCRIBE_EVENT_ENGINE +USE_SUBSCRIBE_EVENT_ENGINE = 0 +endif + ifndef USE_ADVANCED_HISTORY USE_ADVANCED_HISTORY = 1 endif @@ -55,6 +63,11 @@ SOURCEFILES += ../core/pubnub_proxy.c ../core/pubnub_proxy_core.c ../core/pbhttp OBJFILES += pubnub_proxy.o pubnub_proxy_core.o pbhttp_digest.o pbntlm_core.o pbntlm_packer_std.o endif +ifeq ($(USE_RETRY_CONFIGURATION), 1) +SOURCEFILES += ../core/pbcc_request_retry_timer.c ../core/pubnub_retry_configuration.c +OBJFILES += pbcc_request_retry_timer.o pubnub_retry_configuration.o +endif + ifeq ($(USE_GZIP_COMPRESSION), 1) SOURCEFILES += ../lib/miniz/miniz_tdef.c ../lib/miniz/miniz.c ../lib/pbcrc32.c ../core/pbgzip_compress.c OBJFILES += miniz_tdef.o miniz.o pbcrc32.o pbgzip_compress.o @@ -70,6 +83,11 @@ SOURCEFILES += ../core/pbcc_subscribe_v2.c ../core/pubnub_subscribe_v2.c OBJFILES += pbcc_subscribe_v2.o pubnub_subscribe_v2.o endif +ifeq ($(USE_SUBSCRIBE_EVENT_ENGINE), 1) +SOURCEFILES += ../core/pbcc_memory_utils.c ../core/pbcc_event_engine.c ../core/pbcc_subscribe_event_engine.c ../core/pbcc_subscribe_event_engine_effects.c ../core/pbcc_subscribe_event_engine_events.c ../core/pbcc_subscribe_event_engine_states.c ../core/pbcc_subscribe_event_engine_transitions.c ../core/pbcc_subscribe_event_listener.c ../core/pubnub_entities.c ../core/pubnub_subscribe_event_engine.c ../core/pubnub_subscribe_event_listener.c ../lib/pbarray.c ../lib/pbhash_set.c ../lib/pbref_counter.c ../lib/pbstrdup.c +OBJFILES += pbcc_memory_utils.o pbcc_event_engine.o pbcc_subscribe_event_engine.o pbcc_subscribe_event_engine_effects.o pbcc_subscribe_event_engine_events.o pbcc_subscribe_event_engine_states.o pbcc_subscribe_event_engine_transitions.o pbcc_subscribe_event_listener.o pubnub_entities.o pubnub_subscribe_event_engine.o pubnub_subscribe_event_listener.o pbarray.o pbhash_set.o pbref_counter.o pbstrdup.o +endif + ifeq ($(USE_ADVANCED_HISTORY), 1) SOURCEFILES += ../core/pbcc_advanced_history.c ../core/pubnub_advanced_history.c OBJFILES += pbcc_advanced_history.o pubnub_advanced_history.o @@ -106,9 +124,10 @@ OBJFILES += monotonic_clock_get_time_posix.o LDLIBS=-lrt -lpthread endif -CFLAGS =-g -Wall -D PUBNUB_THREADSAFE -D PUBNUB_LOG_LEVEL=PUBNUB_LOG_LEVEL_WARNING -D PUBNUB_ONLY_PUBSUB_API=$(ONLY_PUBSUB_API) -D PUBNUB_PROXY_API=$(USE_PROXY) -D PUBNUB_USE_GZIP_COMPRESSION=$(USE_GZIP_COMPRESSION) -D PUBNUB_RECEIVE_GZIP_RESPONSE=$(RECEIVE_GZIP_RESPONSE) -D PUBNUB_USE_SUBSCRIBE_V2=$(USE_SUBSCRIBE_V2) -D PUBNUB_USE_OBJECTS_API=$(USE_OBJECTS_API) -D PUBNUB_USE_ACTIONS_API=$(USE_ACTIONS_API) -D PUBNUB_USE_AUTO_HEARTBEAT=$(USE_AUTO_HEARTBEAT) -D PUBNUB_USE_GRANT_TOKEN_API=$(USE_GRANT_TOKEN) -D PUBNUB_USE_REVOKE_TOKEN_API=$(USE_REVOKE_TOKEN) -D PUBNUB_USE_FETCH_HISTORY=$(USE_FETCH_HISTORY) -# -g enables debugging, remove to get a smaller executable -# -fsanitize-address Use AddressSanitizer +CFLAGS =-g -Wall -D PUBNUB_THREADSAFE -D PUBNUB_LOG_LEVEL=PUBNUB_LOG_LEVEL_WARNING -D PUBNUB_ONLY_PUBSUB_API=$(ONLY_PUBSUB_API) -D PUBNUB_PROXY_API=$(USE_PROXY) -D PUBNUB_USE_RETRY_CONFIGURATION=$(USE_RETRY_CONFIGURATION) -D PUBNUB_USE_GZIP_COMPRESSION=$(USE_GZIP_COMPRESSION) -D PUBNUB_RECEIVE_GZIP_RESPONSE=$(RECEIVE_GZIP_RESPONSE) -D PUBNUB_USE_SUBSCRIBE_V2=$(USE_SUBSCRIBE_V2) -D PUBNUB_USE_SUBSCRIBE_EVENT_ENGINE=$(USE_SUBSCRIBE_EVENT_ENGINE) -D PUBNUB_USE_OBJECTS_API=$(USE_OBJECTS_API) -D PUBNUB_USE_ACTIONS_API=$(USE_ACTIONS_API) -D PUBNUB_USE_AUTO_HEARTBEAT=$(USE_AUTO_HEARTBEAT) -D PUBNUB_USE_GRANT_TOKEN_API=$(USE_GRANT_TOKEN) -D PUBNUB_USE_REVOKE_TOKEN_API=$(USE_REVOKE_TOKEN) -D PUBNUB_USE_FETCH_HISTORY=$(USE_FETCH_HISTORY) +# -g # enables debugging, remove to get a smaller executable +# -fsanitize=address # Use AddressSanitizer +# -fsanitize=thread # Use ThreadSanitizer: INCLUDES=-I .. -I . @@ -187,6 +206,10 @@ subscribe_publish_callback_sample: ../core/samples/subscribe_publish_callback_sa subscribe_publish_from_callback: ../core/samples/subscribe_publish_from_callback.c pubnub_callback.a $(CC) -o $@ -D PUBNUB_CALLBACK_API $(CFLAGS) $(CFLAGS_CALLBACK) $(INCLUDES) ../core/samples/subscribe_publish_from_callback.c pubnub_callback.a $(LDLIBS) +# Subscribe Event Engine rely on callback +subscribe_event_engine_sample: ../core/samples/subscribe_event_engine_sample.c pubnub_callback.a + $(CC) -o $@ -D PUBNUB_CALLBACK_API $(CFLAGS) $(CFLAGS_CALLBACK) $(INCLUDES) ../core/samples/subscribe_event_engine_sample.c pubnub_callback.a $(LDLIBS) + publish_callback_subloop_sample: ../core/samples/publish_callback_subloop_sample.c pubnub_callback.a $(CC) -o $@ -D PUBNUB_CALLBACK_API $(CFLAGS) $(CFLAGS_CALLBACK) $(INCLUDES) ../core/samples/publish_callback_subloop_sample.c pubnub_callback.a $(LDLIBS) diff --git a/windows/uwp.mk b/windows/uwp.mk index da6068f9..dcbbde9a 100644 --- a/windows/uwp.mk +++ b/windows/uwp.mk @@ -24,17 +24,30 @@ USE_FETCH_HISTORY = 1 USE_PROXY = 1 !endif +!ifndef USE_RETRY_CONFIGURATION +USE_RETRY_CONFIGURATION = 0 +!endif + +!ifndef USE_CRYPTO_API +USE_CRYPTO_API = 0 +!endif + !if $(USE_PROXY) PROXY_INTF_SOURCEFILES = ..\core\pubnub_proxy.c ..\core\pubnub_proxy_core.c ..\core\pbhttp_digest.c ..\core\pbntlm_core.c ..\core\pbntlm_packer_std.c PROXY_INTF_OBJFILES = pubnub_proxy.obj pubnub_proxy_core.obj pbhttp_digest.obj pbntlm_core.obj pbntlm_packer_std.obj !endif +!if $(USE_RETRY_CONFIGURATION) +PROXY_INTF_SOURCEFILES += ..\core\pbcc_request_retry_timer.c ..\core\pubnub_retry_configuration.c +PROXY_INTF_OBJFILES += pbcc_request_retry_timer.obj pubnub_retry_configuration.obj +!endif + !if $(USE_FETCH_HISTORY) FETCH_HIST_SOURCEFILES = ..\core\pubnub_fetch_history.c ..\core\pbcc_fetch_history.c FETCH_HIST_OBJFILES = pubnub_fetch_history.obj pbcc_fetch_history.obj !endif -DEFINES=-D PUBNUB_THREADSAFE -D PUBNUB_LOG_LEVEL=PUBNUB_LOG_LEVEL_WARNING -D HAVE_STRERROR_S -D PUBNUB_ONLY_PUBSUB_API=$(ONLY_PUBSUB_API) -D PUBNUB_PROXY_API=$(USE_PROXY) -D PUBNUB_USE_WIN_SSPI=0 -D PUBNUB_USE_GRANT_TOKEN_API=$(USE_GRANT_TOKEN) -D PUBNUB_USE_REVOKE_TOKEN_API=$(USE_REVOKE_TOKEN) -D PUBNUB_USE_FETCH_HISTORY=$(USE_FETCH_HISTORY) +DEFINES=-D PUBNUB_THREADSAFE -D PUBNUB_LOG_LEVEL=PUBNUB_LOG_LEVEL_WARNING -D HAVE_STRERROR_S -D PUBNUB_ONLY_PUBSUB_API=$(ONLY_PUBSUB_API) -D PUBNUB_PROXY_API=$(USE_PROXY) -D PUBNUB_USE_RETRY_CONFIGURATION=$(USE_RETRY_CONFIGURATION) -D PUBNUB_USE_WIN_SSPI=0 -D PUBNUB_USE_GRANT_TOKEN_API=$(USE_GRANT_TOKEN) -D PUBNUB_USE_REVOKE_TOKEN_API=$(USE_REVOKE_TOKEN) -D PUBNUB_USE_FETCH_HISTORY=$(USE_FETCH_HISTORY) UDEFINES=-D "_UNICODE" -D "UNICODE" -D "WINAPI_FAMILY=WINAPI_FAMILY_APP" -D "__WRL_NO_DEFAULT_LIB__" -D "_CRT_SECURE_NO_WARNINGS" -D "_WINSOCK_DEPRECATED_NO_WARNINGS" -D "__UWP__" -D "HAVE_STRUCT_TIMESPEC" CFLAGS = -Yu"pch.h" -Zi -Y- -MP -W3 -Gy -Zc:wchar_t $(UDEFINES) $(DEFINES) -Gm- -O2 -sdl -errorReport:prompt -WX- -Zc:forScope /Gd /Oy- /Oi /MD /FC -FU"C:\Program Files (x86)\Microsoft Visual Studio\2019\Community\VC\Tools\MSVC\14.28.29333\lib\x86\store\references\platform.winmd" # -Zi enables debugging, remove to get a smaller .exe and no .pdb diff --git a/windows/windows.mk b/windows/windows.mk index fcc7b899..426fc1f6 100644 --- a/windows/windows.mk +++ b/windows/windows.mk @@ -24,17 +24,26 @@ USE_FETCH_HISTORY = 1 USE_PROXY = 1 !endif +!ifndef USE_RETRY_CONFIGURATION +USE_RETRY_CONFIGURATION = 0 +!endif + !if $(USE_PROXY) PROXY_INTF_SOURCEFILES = ..\core\pubnub_proxy.c ..\core\pubnub_proxy_core.c ..\core\pbhttp_digest.c ..\core\pbntlm_core.c ..\core\pbntlm_packer_sspi.c pubnub_set_proxy_from_system_windows.c PROXY_INTF_OBJFILES = pubnub_proxy.obj pubnub_proxy_core.obj pbhttp_digest.obj pbntlm_core.obj pbntlm_packer_sspi.obj pubnub_set_proxy_from_system_windows.obj !endif +!if $(USE_RETRY_CONFIGURATION) +PROXY_INTF_SOURCEFILES += ..\core\pbcc_request_retry_timer.c ..\core\pubnub_retry_configuration.c +PROXY_INTF_OBJFILES += pbcc_request_retry_timer.obj pubnub_retry_configuration.obj +!endif + !if $(USE_FETCH_HISTORY) FETCH_HIST_SOURCEFILES = ..\core\pubnub_fetch_history.c ..\core\pbcc_fetch_history.c FETCH_HIST_OBJFILES = pubnub_fetch_history.obj pbcc_fetch_history.obj !endif -DEFINES=-D PUBNUB_THREADSAFE -D PUBNUB_LOG_LEVEL=PUBNUB_LOG_LEVEL_WARNING -D HAVE_STRERROR_S -D PUBNUB_ONLY_PUBSUB_API=$(ONLY_PUBSUB_API) -D PUBNUB_PROXY_API=$(USE_PROXY) -D PUBNUB_USE_GRANT_TOKEN_API=$(USE_GRANT_TOKEN) -D PUBNUB_USE_REVOKE_TOKEN_API=$(USE_REVOKE_TOKEN) -D PUBNUB_USE_FETCH_HISTORY=$(USE_FETCH_HISTORY) +DEFINES=-D PUBNUB_THREADSAFE -D PUBNUB_LOG_LEVEL=PUBNUB_LOG_LEVEL_WARNING -D HAVE_STRERROR_S -D PUBNUB_ONLY_PUBSUB_API=$(ONLY_PUBSUB_API) -D PUBNUB_PROXY_API=$(USE_PROXY) -D PUBNUB_USE_RETRY_CONFIGURATION=$(USE_RETRY_CONFIGURATION) -D PUBNUB_USE_GRANT_TOKEN_API=$(USE_GRANT_TOKEN) -D PUBNUB_USE_REVOKE_TOKEN_API=$(USE_REVOKE_TOKEN) -D PUBNUB_USE_FETCH_HISTORY=$(USE_FETCH_HISTORY) CFLAGS = -Zi -MP -W3 $(DEFINES) # -Zi enables debugging, remove to get a smaller .exe and no .pdb # -MP use one compiler process for each input, faster on multi-core (ignored by clang-cl)