diff --git a/src/wolfsentry_util.c b/src/wolfsentry_util.c index fc590a0..cbe66f3 100644 --- a/src/wolfsentry_util.c +++ b/src/wolfsentry_util.c @@ -1538,16 +1538,47 @@ static int freertos_sem_destroy( sem_t * sem ) } WOLFSENTRY_RETURN_VALUE(0); } + #ifndef EAGAIN + #define EAGAIN 11 /* Try again */ + #endif + #ifndef EINTR + #define EINTR 4 /* Interrupted system call */ + #endif + #define sem_timedwait threadx_sem_timedwait static int threadx_sem_timedwait( sem_t * sem, const struct timespec * abstime ) { - if (tx_semaphore_get(sem, (uint32_t)(TX_TICK_TIME_MS * - (abstime->tv_sec * 1000) + (abstime->tv_nsec / 1000000))) != TX_SUCCESS) { - errno = EINVAL; - WOLFSENTRY_RETURN_VALUE(-1); + ULONG wait_ticks = TX_WAIT_FOREVER; + UINT tx_ret; + + if (abstime != NULL) { + uint64_t deadline_ticks, now_ticks; + if ((abstime->tv_sec < 0) || (abstime->tv_nsec < 0) || (abstime->tv_nsec >= 1000000000L)) { + errno = EINVAL; + WOLFSENTRY_RETURN_VALUE(-1); + } + /* abstime is on the builtin get_time clock, so invert that tick mapping, rounding up so a sub-tick future deadline still waits a tick. */ + deadline_ticks = ((uint64_t)abstime->tv_sec * (uint64_t)TX_TICK_TIME_MS) + (((uint64_t)abstime->tv_nsec + (uint64_t)999999) / (uint64_t)1000000); + now_ticks = (uint64_t)tx_time_get(); + if (deadline_ticks <= now_ticks) + wait_ticks = TX_NO_WAIT; + else if (deadline_ticks - now_ticks >= (uint64_t)TX_WAIT_FOREVER) + wait_ticks = (ULONG)(TX_WAIT_FOREVER - 1); + else + wait_ticks = (ULONG)(deadline_ticks - now_ticks); } - WOLFSENTRY_RETURN_VALUE(0); + + tx_ret = tx_semaphore_get(sem, wait_ticks); + if (tx_ret == TX_SUCCESS) + WOLFSENTRY_RETURN_VALUE(0); + if (tx_ret == TX_NO_INSTANCE) + errno = ETIMEDOUT; + else if (tx_ret == TX_WAIT_ABORTED) + errno = EINTR; + else + errno = EINVAL; + WOLFSENTRY_RETURN_VALUE(-1); } #define sem_wait threadx_sem_wait static int threadx_sem_wait( sem_t * sem ) @@ -1561,11 +1592,16 @@ static int freertos_sem_destroy( sem_t * sem ) #define sem_trywait threadx_sem_trywait static int threadx_sem_trywait( sem_t * sem ) { - if (tx_semaphore_get(sem, TX_NO_WAIT) != TX_SUCCESS) { + UINT tx_ret = tx_semaphore_get(sem, TX_NO_WAIT); + if (tx_ret == TX_SUCCESS) + WOLFSENTRY_RETURN_VALUE(0); + if (tx_ret == TX_NO_INSTANCE) + errno = EAGAIN; + else if (tx_ret == TX_WAIT_ABORTED) + errno = EINTR; + else errno = EINVAL; - WOLFSENTRY_RETURN_VALUE(-1); - } - WOLFSENTRY_RETURN_VALUE(0); + WOLFSENTRY_RETURN_VALUE(-1); } static int threadx_sem_destroy( sem_t * sem ) { @@ -3018,9 +3054,13 @@ WOLFSENTRY_API wolfsentry_errcode_t wolfsentry_lock_unlock(struct wolfsentry_rwl WOLFSENTRY_THREAD_ASSERT_NULL_OR_INITED(thread); - /* unlocking a recursive mutex, like recursively locking one, can be done lock-free. */ + /* unlocking a recursive mutex, like recursively locking one, can be done + * lock-free, except when unwinding to the promotion depth, which must + * evaluate the downgrade logic below. + */ if ((WOLFSENTRY_ATOMIC_LOAD(lock->write_lock_holder) == WOLFSENTRY_THREAD_GET_ID) && - (lock->holder_count.write > 1)) + (lock->holder_count.write > 1) && + (lock->holder_count.write - 1 != lock->promoted_at_count)) { --lock->holder_count.write; if (thread) @@ -3127,16 +3167,25 @@ WOLFSENTRY_API wolfsentry_errcode_t wolfsentry_lock_unlock(struct wolfsentry_rwl if (lock->promoted_at_count == lock->holder_count.write) { lock->promoted_at_count = 0; if ((flags & WOLFSENTRY_LOCK_FLAG_AUTO_DOWNGRADE) && (! thread->tracked_shared_lock)) { - WOLFSENTRY_ATOMIC_STORE(lock->state, WOLFSENTRY_LOCK_SHARED); + /* same bookkeeping as wolfsentry_lock_mutex2shared(): writer count becomes reader count. */ if (flags & (WOLFSENTRY_LOCK_FLAG_TRY_RESERVATION_TOO | WOLFSENTRY_LOCK_FLAG_GET_RESERVATION_TOO)) { + if (lock->read2write_reservation_holder != WOLFSENTRY_THREAD_NO_ID) /* can't happen, but be sure. */ { + ret = WOLFSENTRY_ERROR_ENCODE(INTERNAL_CHECK_FATAL); + goto out; + } + ++thread->mutex_and_reservation_count; WOLFSENTRY_ATOMIC_STORE(lock->read2write_reservation_holder, WOLFSENTRY_THREAD_GET_ID); + lock->read2write_waiter_read_count = lock->holder_count.write; /* note, not incrementing write_waiter_count, to allow shared lockers to get locks until the redemption phase. */ - lock->read2write_waiter_read_count = lock->holder_count.read; + } + WOLFSENTRY_ATOMIC_STORE(lock->state, WOLFSENTRY_LOCK_SHARED); + WOLFSENTRY_ATOMIC_STORE(lock->write_lock_holder, WOLFSENTRY_THREAD_NO_ID); + thread->mutex_and_reservation_count -= lock->holder_count.write; + thread->shared_count += lock->holder_count.read; + thread->recursion_of_tracked_lock = lock->holder_count.read; + thread->tracked_shared_lock = lock; + if (flags & (WOLFSENTRY_LOCK_FLAG_TRY_RESERVATION_TOO | WOLFSENTRY_LOCK_FLAG_GET_RESERVATION_TOO)) { ++lock->holder_count.read; /* suppress posts to sem_read2write_waiters until wolfsentry_lock_shared2mutex_redeem() is entered. */ - thread->mutex_and_reservation_count -= lock->holder_count.write - 1; /* -1 for the reservation */ - thread->shared_count += lock->holder_count.read; - thread->tracked_shared_lock = lock; - thread->recursion_of_tracked_lock = lock->holder_count.read; ret = WOLFSENTRY_SUCCESS_ENCODE(LOCK_OK_AND_GOT_RESV); } else ret = WOLFSENTRY_ERROR_ENCODE(OK); @@ -3167,11 +3216,12 @@ WOLFSENTRY_API wolfsentry_errcode_t wolfsentry_lock_unlock(struct wolfsentry_rwl } } } else if (lock->read_waiter_count > 0) { - int i; - lock->holder_count.read += lock->read_waiter_count; + /* post once per queued reader, not per holder, as existing shared holders need no wakeup. */ + int read_waiter_count = lock->read_waiter_count; + lock->holder_count.read += read_waiter_count; lock->read_waiter_count = 0; WOLFSENTRY_ATOMIC_STORE(lock->state, WOLFSENTRY_LOCK_SHARED); - for (i = 0; i < lock->holder_count.read; ++i) { + for (; read_waiter_count > 0; --read_waiter_count) { if (sem_post(&lock->sem_read_waiters) < 0) { ret = WOLFSENTRY_ERROR_ENCODE(SYS_OP_FATAL); goto out; @@ -3571,9 +3621,22 @@ WOLFSENTRY_API wolfsentry_errcode_t wolfsentry_object_checkout(WOLFSENTRY_CONTEX } WOLFSENTRY_API wolfsentry_errcode_t wolfsentry_object_release(WOLFSENTRY_CONTEXT_ARGS_IN, void *object, wolfsentry_action_res_t *action_results) { - if (object == NULL) + struct wolfsentry_table_ent_header *ent = (struct wolfsentry_table_ent_header *)object; + if (ent == NULL) WOLFSENTRY_ERROR_RETURN(INVALID_ARG); - WOLFSENTRY_ERROR_RERETURN(((struct wolfsentry_table_ent_header *)object)->parent_table->free_fn(WOLFSENTRY_CONTEXT_ARGS_OUT, (struct wolfsentry_table_ent_header *)object, action_results)); + if (ent->parent_table == NULL) { + /* the fallthrough route is never linked into a table, but is documented as releasable here. */ + if ((wolfsentry->routes != NULL) && + (wolfsentry->routes->fallthrough_route != NULL) && + (object == (void *)wolfsentry->routes->fallthrough_route)) + { + WOLFSENTRY_ERROR_RERETURN(wolfsentry_route_drop_reference(WOLFSENTRY_CONTEXT_ARGS_OUT, wolfsentry->routes->fallthrough_route, action_results)); + } + WOLFSENTRY_ERROR_RETURN(INVALID_ARG); + } + if (ent->parent_table->free_fn == NULL) + WOLFSENTRY_ERROR_RETURN(INVALID_ARG); + WOLFSENTRY_ERROR_RERETURN(ent->parent_table->free_fn(WOLFSENTRY_CONTEXT_ARGS_OUT, ent, action_results)); } WOLFSENTRY_API wolfsentry_errcode_t wolfsentry_eventconfig_init( @@ -3743,8 +3806,13 @@ static wolfsentry_errcode_t wolfsentry_context_free_1( ) { #ifdef WOLFSENTRY_THREADSAFE - wolfsentry_errcode_t ret = wolfsentry_lock_have_mutex(&(*wolfsentry)->lock, thread, WOLFSENTRY_LOCK_FLAG_NONE); - WOLFSENTRY_RERETURN_IF_ERROR(ret); + wolfsentry_errcode_t ret; + /* a context whose lock never came up is still private to its allocator, so no mutex is possible or needed. */ + int lock_inited = (WOLFSENTRY_ATOMIC_LOAD((*wolfsentry)->lock.state) != WOLFSENTRY_LOCK_UNINITED); + if (lock_inited) { + ret = wolfsentry_lock_have_mutex(&(*wolfsentry)->lock, thread, WOLFSENTRY_LOCK_FLAG_NONE); + WOLFSENTRY_RERETURN_IF_ERROR(ret); + } #endif if ((*wolfsentry)->routes != NULL) @@ -3763,10 +3831,12 @@ static wolfsentry_errcode_t wolfsentry_context_free_1( #endif #ifdef WOLFSENTRY_THREADSAFE - ret = wolfsentry_lock_unlock(&(*wolfsentry)->lock, thread, WOLFSENTRY_LOCK_FLAG_NONE); - WOLFSENTRY_RERETURN_IF_ERROR(ret); - ret = wolfsentry_lock_destroy(&(*wolfsentry)->lock, thread, WOLFSENTRY_LOCK_FLAG_NONE); - WOLFSENTRY_RERETURN_IF_ERROR(ret); + if (lock_inited) { + ret = wolfsentry_lock_unlock(&(*wolfsentry)->lock, thread, WOLFSENTRY_LOCK_FLAG_NONE); + WOLFSENTRY_RERETURN_IF_ERROR(ret); + ret = wolfsentry_lock_destroy(&(*wolfsentry)->lock, thread, WOLFSENTRY_LOCK_FLAG_NONE); + WOLFSENTRY_RERETURN_IF_ERROR(ret); + } #endif WOLFSENTRY_FREE_1((*wolfsentry)->hpi.allocator, *wolfsentry); @@ -3774,6 +3844,16 @@ static wolfsentry_errcode_t wolfsentry_context_free_1( WOLFSENTRY_RETURN_OK; } +static void *wolfsentry_context_alloc_zeroed( + WOLFSENTRY_CONTEXT_ARGS_IN_EX(const struct wolfsentry_host_platform_interface *hpi), + size_t size) +{ + void *ret = WOLFSENTRY_MALLOC_1(hpi->allocator, size); + if (ret != NULL) + memset(ret, 0, size); + WOLFSENTRY_RETURN_VALUE(ret); +} + static wolfsentry_errcode_t wolfsentry_context_init_1( struct wolfsentry_context *wolfsentry #ifdef WOLFSENTRY_THREADSAFE @@ -3817,36 +3897,29 @@ static wolfsentry_errcode_t wolfsentry_context_alloc_1( ) { wolfsentry_errcode_t ret; - if ((*wolfsentry = (struct wolfsentry_context *)WOLFSENTRY_MALLOC_1(hpi->allocator, sizeof **wolfsentry)) == NULL) + if ((*wolfsentry = (struct wolfsentry_context *)wolfsentry_context_alloc_zeroed(WOLFSENTRY_CONTEXT_ARGS_OUT_EX(hpi), sizeof **wolfsentry)) == NULL) WOLFSENTRY_ERROR_RETURN(SYS_RESOURCE_FAILED); - memset(*wolfsentry, 0, sizeof **wolfsentry); - (*wolfsentry)->hpi = *hpi; - if ((((*wolfsentry)->events = (struct wolfsentry_event_table *)WOLFSENTRY_MALLOC_1(hpi->allocator, sizeof *(*wolfsentry)->events)) == NULL) || - (((*wolfsentry)->actions = (struct wolfsentry_action_table *)WOLFSENTRY_MALLOC_1(hpi->allocator, sizeof *(*wolfsentry)->actions)) == NULL) || - (((*wolfsentry)->routes = (struct wolfsentry_route_table *)WOLFSENTRY_MALLOC_1(hpi->allocator, sizeof *(*wolfsentry)->routes)) == NULL) || - (((*wolfsentry)->user_values = (struct wolfsentry_kv_table *)WOLFSENTRY_MALLOC_1(hpi->allocator, sizeof *(*wolfsentry)->user_values)) == NULL) || - (((*wolfsentry)->addr_families_bynumber = (struct wolfsentry_addr_family_bynumber_table *)WOLFSENTRY_MALLOC_1(hpi->allocator, sizeof *(*wolfsentry)->addr_families_bynumber)) == NULL) + /* each table is zeroed as allocated, so a partial failure leaves only well-formed tables for free_1 to tear down. */ + if ((((*wolfsentry)->events = (struct wolfsentry_event_table *)wolfsentry_context_alloc_zeroed(WOLFSENTRY_CONTEXT_ARGS_OUT_EX(hpi), sizeof *(*wolfsentry)->events)) == NULL) || + (((*wolfsentry)->actions = (struct wolfsentry_action_table *)wolfsentry_context_alloc_zeroed(WOLFSENTRY_CONTEXT_ARGS_OUT_EX(hpi), sizeof *(*wolfsentry)->actions)) == NULL) || + (((*wolfsentry)->routes = (struct wolfsentry_route_table *)wolfsentry_context_alloc_zeroed(WOLFSENTRY_CONTEXT_ARGS_OUT_EX(hpi), sizeof *(*wolfsentry)->routes)) == NULL) || + (((*wolfsentry)->user_values = (struct wolfsentry_kv_table *)wolfsentry_context_alloc_zeroed(WOLFSENTRY_CONTEXT_ARGS_OUT_EX(hpi), sizeof *(*wolfsentry)->user_values)) == NULL) || + (((*wolfsentry)->addr_families_bynumber = (struct wolfsentry_addr_family_bynumber_table *)wolfsentry_context_alloc_zeroed(WOLFSENTRY_CONTEXT_ARGS_OUT_EX(hpi), sizeof *(*wolfsentry)->addr_families_bynumber)) == NULL) #ifdef WOLFSENTRY_PROTOCOL_NAMES - || (((*wolfsentry)->addr_families_byname = (struct wolfsentry_addr_family_byname_table *)WOLFSENTRY_MALLOC_1(hpi->allocator, sizeof *(*wolfsentry)->addr_families_byname)) == NULL) + || (((*wolfsentry)->addr_families_byname = (struct wolfsentry_addr_family_byname_table *)wolfsentry_context_alloc_zeroed(WOLFSENTRY_CONTEXT_ARGS_OUT_EX(hpi), sizeof *(*wolfsentry)->addr_families_byname)) == NULL) #endif ) { - (void)wolfsentry_context_free_1(WOLFSENTRY_CONTEXT_ARGS_OUT); + WOLFSENTRY_WARN_ON_FAILURE(wolfsentry_context_free_1(WOLFSENTRY_CONTEXT_ARGS_OUT)); WOLFSENTRY_ERROR_RETURN(SYS_RESOURCE_FAILED); } - memset((*wolfsentry)->events, 0, sizeof *(*wolfsentry)->events); - memset((*wolfsentry)->actions, 0, sizeof *(*wolfsentry)->actions); - memset((*wolfsentry)->routes, 0, sizeof *(*wolfsentry)->routes); - memset((*wolfsentry)->user_values, 0, sizeof *(*wolfsentry)->user_values); - memset((*wolfsentry)->addr_families_bynumber, 0, sizeof *(*wolfsentry)->addr_families_bynumber); #ifdef WOLFSENTRY_PROTOCOL_NAMES - memset((*wolfsentry)->addr_families_byname, 0, sizeof *(*wolfsentry)->addr_families_byname); if ((ret = wolfsentry_addr_family_table_pair(WOLFSENTRY_CONTEXT_ARGS_OUT_EX(*wolfsentry), (*wolfsentry)->addr_families_bynumber, (*wolfsentry)->addr_families_byname)) < 0) { - (void)wolfsentry_context_free_1(WOLFSENTRY_CONTEXT_ARGS_OUT); + WOLFSENTRY_WARN_ON_FAILURE(wolfsentry_context_free_1(WOLFSENTRY_CONTEXT_ARGS_OUT)); WOLFSENTRY_ERROR_RERETURN(ret); } #endif @@ -3856,7 +3929,12 @@ static wolfsentry_errcode_t wolfsentry_context_alloc_1( , lock_flags #endif )) < 0) { - (void)wolfsentry_context_free_1(WOLFSENTRY_CONTEXT_ARGS_OUT); +#ifdef WOLFSENTRY_THREADSAFE + /* init_1 can fail after the lock is up, and free_1 then needs the mutex, which no one else can hold yet. */ + if (WOLFSENTRY_ATOMIC_LOAD((*wolfsentry)->lock.state) != WOLFSENTRY_LOCK_UNINITED) + WOLFSENTRY_WARN_ON_FAILURE(wolfsentry_lock_mutex_abstimed(&(*wolfsentry)->lock, NULL /* thread */, ×pec_deadline_now, WOLFSENTRY_LOCK_FLAG_NONE)); +#endif + WOLFSENTRY_WARN_ON_FAILURE(wolfsentry_context_free_1(WOLFSENTRY_CONTEXT_ARGS_OUT_EX4(wolfsentry, NULL /* thread */))); WOLFSENTRY_ERROR_RERETURN(ret); } @@ -3873,6 +3951,7 @@ WOLFSENTRY_API wolfsentry_errcode_t wolfsentry_init_ex( struct wolfsentry_host_platform_interface hpi; #ifdef WOLFSENTRY_THREADSAFE wolfsentry_lock_flags_t lock_flags = WOLFSENTRY_LOCK_FLAG_NONE; + wolfsentry_errcode_t cleanup_ret; #endif wolfsentry_errcode_t ret; @@ -3952,8 +4031,10 @@ WOLFSENTRY_API wolfsentry_errcode_t wolfsentry_init_ex( * because wolfsentry_interval_from_seconds() needs a valid * wolfsentry_context (circular dependency). fix it now that we can. */ - hpi.timecbs.interval_from_seconds((long int)(*wolfsentry)->config.config.penaltybox_duration, 0 /* howlong_nsecs */, &((*wolfsentry)->config.config.penaltybox_duration)); - hpi.timecbs.interval_from_seconds((long int)(*wolfsentry)->config.config.route_idle_time_for_purge, 0 /* howlong_nsecs */, &((*wolfsentry)->config.config.route_idle_time_for_purge)); + if ((ret = hpi.timecbs.interval_from_seconds((time_t)(*wolfsentry)->config.config.penaltybox_duration, 0 /* howlong_nsecs */, &((*wolfsentry)->config.config.penaltybox_duration))) < 0) + goto out; + if ((ret = hpi.timecbs.interval_from_seconds((time_t)(*wolfsentry)->config.config.route_idle_time_for_purge, 0 /* howlong_nsecs */, &((*wolfsentry)->config.config.route_idle_time_for_purge))) < 0) + goto out; (*wolfsentry)->config_at_creation = (*wolfsentry)->config; @@ -3968,7 +4049,15 @@ WOLFSENTRY_API wolfsentry_errcode_t wolfsentry_init_ex( out: if (ret < 0) { - (void)wolfsentry_context_free_1(WOLFSENTRY_CONTEXT_ARGS_OUT); +#ifdef WOLFSENTRY_THREADSAFE + /* the new context is still private, so tear it down with no thread context, which a read-only thread or an expired deadline cannot block. */ + cleanup_ret = wolfsentry_lock_mutex_abstimed(&(*wolfsentry)->lock, NULL /* thread */, ×pec_deadline_now, WOLFSENTRY_LOCK_FLAG_NONE); + if (cleanup_ret >= 0) + cleanup_ret = wolfsentry_context_free_1(WOLFSENTRY_CONTEXT_ARGS_OUT_EX4(wolfsentry, NULL /* thread */)); + WOLFSENTRY_WARN_ON_FAILURE(cleanup_ret); +#else + WOLFSENTRY_WARN_ON_FAILURE(wolfsentry_context_free_1(WOLFSENTRY_CONTEXT_ARGS_OUT)); +#endif } WOLFSENTRY_ERROR_RERETURN(ret); } @@ -4147,6 +4236,9 @@ WOLFSENTRY_API wolfsentry_errcode_t wolfsentry_context_clone( wolfsentry_clone_flags_t flags) { wolfsentry_errcode_t ret; +#ifdef WOLFSENTRY_THREADSAFE + wolfsentry_errcode_t cleanup_ret; +#endif #ifdef WOLFSENTRY_THREADSAFE WOLFSENTRY_SHARED_OR_RETURN(); @@ -4154,8 +4246,14 @@ WOLFSENTRY_API wolfsentry_errcode_t wolfsentry_context_clone( if ((ret = wolfsentry_context_alloc_1(&wolfsentry->hpi, thread, clone, wolfsentry->lock.flags)) < 0) WOLFSENTRY_ERROR_UNLOCK_AND_RERETURN(ret); - if ((ret = wolfsentry_context_lock_mutex_abstimed(*clone, thread, NULL)) < 0) - goto out; + if ((ret = wolfsentry_context_lock_mutex_abstimed(*clone, thread, NULL)) < 0) { + /* the clone is still private, so tear it down with no thread context, which a read-only thread or an expired deadline cannot block. */ + cleanup_ret = wolfsentry_lock_mutex_abstimed(&(*clone)->lock, NULL /* thread */, ×pec_deadline_now, WOLFSENTRY_LOCK_FLAG_NONE); + if (cleanup_ret >= 0) + cleanup_ret = wolfsentry_context_free_1(WOLFSENTRY_CONTEXT_ARGS_OUT_EX4(clone, NULL /* thread */)); + WOLFSENTRY_WARN_ON_FAILURE(cleanup_ret); + WOLFSENTRY_ERROR_UNLOCK_AND_RERETURN(ret); + } #else if ((ret = wolfsentry_context_alloc_1(&wolfsentry->hpi, clone)) < 0) WOLFSENTRY_ERROR_RERETURN(ret); @@ -4202,7 +4300,8 @@ WOLFSENTRY_API wolfsentry_errcode_t wolfsentry_context_clone( #endif if (WOLFSENTRY_CHECK_BITS(flags, WOLFSENTRY_CLONE_FLAG_AS_AT_CREATION)) { - ret = WOLFSENTRY_ERROR_ENCODE(OK); + /* the fresh route table needs its own fallthrough route, as wolfsentry_init_ex() provides. */ + ret = wolfsentry_route_table_fallthrough_route_alloc(WOLFSENTRY_CONTEXT_ARGS_OUT_EX(*clone), (*clone)->routes); goto out; } @@ -4289,6 +4388,10 @@ WOLFSENTRY_API wolfsentry_errcode_t wolfsentry_context_exchange(WOLFSENTRY_CONTE wolfsentry2->ents_by_id = scratch.ents_by_id; + /* keep the lock-free copy of the default policy in step with the swapped route tables. */ + WOLFSENTRY_ATOMIC_STORE(wolfsentry->default_policy, wolfsentry->routes->default_policy); + WOLFSENTRY_ATOMIC_STORE(wolfsentry2->default_policy, wolfsentry2->routes->default_policy); + /* note that wolfsentry->lock and wolfsentry->cleanup_hooks are not copied. * both of these follow the context pointer itself, which is necessarily * invariant once allocated by wolfsentry_init(), until final deallocation diff --git a/tests/unittests.c b/tests/unittests.c index 7a75686..f2ef6fc 100644 --- a/tests/unittests.c +++ b/tests/unittests.c @@ -1387,6 +1387,384 @@ TEST_SKIP(test_rw_locks) #endif /* WOLFSENTRY_THREADSAFE */ +#if defined(WOLFSENTRY_THREADSAFE) + +static int test_rw_locks_auto_downgrade(void) { + struct wolfsentry_context *wolfsentry; + struct wolfsentry_rwlock *lock; +#ifdef WOLFSENTRY_HAVE_DESIGNATED_INITIALIZERS + struct wolfsentry_eventconfig config = { .route_private_data_size = PRIVATE_DATA_SIZE, .route_private_data_alignment = PRIVATE_DATA_ALIGNMENT, .max_connection_count = 10 }; +#else + struct wolfsentry_eventconfig config = { 32, 0, 10, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0 }; +#endif + + WOLFSENTRY_THREAD_HEADER_CHECKED(WOLFSENTRY_THREAD_FLAG_NONE); + + WOLFSENTRY_EXIT_ON_FAILURE(wolfsentry_init_ex(wolfsentry_build_settings, + WOLFSENTRY_TEST_HPI, + thread, + &config, + &wolfsentry, + WOLFSENTRY_INIT_FLAG_NONE)); + WOLFSENTRY_EXIT_ON_FAILURE(wolfsentry_lock_alloc(wolfsentry_get_hpi(wolfsentry), thread, &lock, WOLFSENTRY_LOCK_FLAG_NONE)); + + /* promote through a redeemed reservation, which records the promotion depth. */ + WOLFSENTRY_EXIT_UNLESS_EXPECTED_SUCCESS(LOCK_OK_AND_GOT_RESV, wolfsentry_lock_shared(lock, thread, WOLFSENTRY_LOCK_FLAG_GET_RESERVATION_TOO)); + WOLFSENTRY_EXIT_ON_FAILURE(wolfsentry_lock_shared2mutex_redeem(lock, thread, WOLFSENTRY_LOCK_FLAG_NONE)); + WOLFSENTRY_EXIT_ON_FAILURE(wolfsentry_lock_mutex(lock, thread, WOLFSENTRY_LOCK_FLAG_NONE)); + WOLFSENTRY_EXIT_UNLESS_EXPECTED_SUCCESS(HAVE_MUTEX, wolfsentry_lock_have_mutex(lock, thread, WOLFSENTRY_LOCK_FLAG_NONE)); + + /* unwinding to the promotion depth with AUTO_DOWNGRADE must leave a shared lock. */ + WOLFSENTRY_EXIT_ON_FAILURE(wolfsentry_lock_unlock(lock, thread, WOLFSENTRY_LOCK_FLAG_AUTO_DOWNGRADE)); + WOLFSENTRY_EXIT_UNLESS_EXPECTED_SUCCESS(HAVE_READ_LOCK, wolfsentry_lock_have_shared(lock, thread, WOLFSENTRY_LOCK_FLAG_NONE)); + WOLFSENTRY_EXIT_UNLESS_EXPECTED_FAILURE(LACKING_MUTEX, wolfsentry_lock_have_mutex(lock, thread, WOLFSENTRY_LOCK_FLAG_NONE)); + WOLFSENTRY_EXIT_ON_FALSE(thread->shared_count == 1); + WOLFSENTRY_EXIT_ON_FALSE(thread->mutex_and_reservation_count == 0); + WOLFSENTRY_EXIT_ON_FALSE(thread->tracked_shared_lock == lock); + WOLFSENTRY_EXIT_ON_FAILURE(wolfsentry_lock_unlock(lock, thread, WOLFSENTRY_LOCK_FLAG_NONE)); + WOLFSENTRY_EXIT_ON_FAILURE(wolfsentry_lock_destroy(lock, thread, WOLFSENTRY_LOCK_FLAG_NONE)); + + /* same again, but downgrade with a new reservation and redeem it. */ + WOLFSENTRY_EXIT_ON_FAILURE(wolfsentry_lock_init(wolfsentry_get_hpi(wolfsentry), thread, lock, WOLFSENTRY_LOCK_FLAG_NONE)); + WOLFSENTRY_EXIT_UNLESS_EXPECTED_SUCCESS(LOCK_OK_AND_GOT_RESV, wolfsentry_lock_shared(lock, thread, WOLFSENTRY_LOCK_FLAG_GET_RESERVATION_TOO)); + WOLFSENTRY_EXIT_ON_FAILURE(wolfsentry_lock_shared2mutex_redeem(lock, thread, WOLFSENTRY_LOCK_FLAG_NONE)); + WOLFSENTRY_EXIT_ON_FAILURE(wolfsentry_lock_mutex(lock, thread, WOLFSENTRY_LOCK_FLAG_NONE)); + WOLFSENTRY_EXIT_UNLESS_EXPECTED_SUCCESS(LOCK_OK_AND_GOT_RESV, wolfsentry_lock_unlock(lock, thread, WOLFSENTRY_LOCK_FLAG_AUTO_DOWNGRADE | WOLFSENTRY_LOCK_FLAG_GET_RESERVATION_TOO)); + WOLFSENTRY_EXIT_UNLESS_EXPECTED_SUCCESS(HAVE_READ_LOCK, wolfsentry_lock_have_shared(lock, thread, WOLFSENTRY_LOCK_FLAG_NONE)); + WOLFSENTRY_EXIT_ON_FAILURE(wolfsentry_lock_have_shared2mutex_reservation(lock, thread, WOLFSENTRY_LOCK_FLAG_NONE)); + WOLFSENTRY_EXIT_ON_FAILURE(wolfsentry_lock_shared2mutex_redeem(lock, thread, WOLFSENTRY_LOCK_FLAG_NONE)); + WOLFSENTRY_EXIT_UNLESS_EXPECTED_SUCCESS(HAVE_MUTEX, wolfsentry_lock_have_mutex(lock, thread, WOLFSENTRY_LOCK_FLAG_NONE)); + WOLFSENTRY_EXIT_ON_FAILURE(wolfsentry_lock_unlock(lock, thread, WOLFSENTRY_LOCK_FLAG_NONE)); + + /* unwinding past the promotion depth without AUTO_DOWNGRADE stays exclusive. */ + WOLFSENTRY_EXIT_UNLESS_EXPECTED_SUCCESS(LOCK_OK_AND_GOT_RESV, wolfsentry_lock_shared(lock, thread, WOLFSENTRY_LOCK_FLAG_GET_RESERVATION_TOO)); + WOLFSENTRY_EXIT_ON_FAILURE(wolfsentry_lock_shared2mutex_redeem(lock, thread, WOLFSENTRY_LOCK_FLAG_NONE)); + WOLFSENTRY_EXIT_ON_FAILURE(wolfsentry_lock_mutex(lock, thread, WOLFSENTRY_LOCK_FLAG_NONE)); + WOLFSENTRY_EXIT_ON_FAILURE(wolfsentry_lock_unlock(lock, thread, WOLFSENTRY_LOCK_FLAG_NONE)); + WOLFSENTRY_EXIT_UNLESS_EXPECTED_SUCCESS(HAVE_MUTEX, wolfsentry_lock_have_mutex(lock, thread, WOLFSENTRY_LOCK_FLAG_NONE)); + WOLFSENTRY_EXIT_ON_FAILURE(wolfsentry_lock_unlock(lock, thread, WOLFSENTRY_LOCK_FLAG_NONE)); + + WOLFSENTRY_EXIT_ON_FAILURE(wolfsentry_lock_free(&lock, thread, WOLFSENTRY_LOCK_FLAG_NONE)); + WOLFSENTRY_EXIT_ON_FAILURE(wolfsentry_shutdown(&wolfsentry, thread)); + WOLFSENTRY_EXIT_ON_FAILURE(WOLFSENTRY_THREAD_TAILER(WOLFSENTRY_THREAD_FLAG_NONE)); + + WOLFSENTRY_RETURN_OK; +} + +static void *wr_timeout_routine(struct rwlock_args *args) { + WOLFSENTRY_THREAD_HEADER(WOLFSENTRY_THREAD_FLAG_NONE); + WOLFSENTRY_EXIT_ON_FAILURE(WOLFSENTRY_THREAD_GET_ERROR); + INCREMENT_PHASE(args); + WOLFSENTRY_EXIT_UNLESS_EXPECTED_FAILURE(TIMED_OUT, wolfsentry_lock_mutex_timed(args->lock, thread, args->max_wait, WOLFSENTRY_LOCK_FLAG_NONE)); + INCREMENT_PHASE(args); + WOLFSENTRY_EXIT_ON_FAILURE(WOLFSENTRY_THREAD_TAILER(WOLFSENTRY_THREAD_FLAG_NONE)); + return 0; +} + +static int test_rw_locks_reader_wakeup(void) { + struct wolfsentry_context *wolfsentry; + struct wolfsentry_rwlock *lock; +#ifdef WOLFSENTRY_HAVE_DESIGNATED_INITIALIZERS + struct wolfsentry_eventconfig config = { .route_private_data_size = PRIVATE_DATA_SIZE, .route_private_data_alignment = PRIVATE_DATA_ALIGNMENT, .max_connection_count = 10 }; +#else + struct wolfsentry_eventconfig config = { 32, 0, 10, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0 }; +#endif + volatile int measured_sequence[8], measured_sequence_i = 0; + pthread_t reader_thread, writer_thread; + struct rwlock_args reader_args, writer_args; + int reader_phase; + + WOLFSENTRY_THREAD_HEADER_CHECKED(WOLFSENTRY_THREAD_FLAG_NONE); + + (void)alarm(2); + + WOLFSENTRY_EXIT_ON_FAILURE(wolfsentry_init_ex(wolfsentry_build_settings, + WOLFSENTRY_TEST_HPI, + thread, + &config, + &wolfsentry, + WOLFSENTRY_INIT_FLAG_NONE)); + WOLFSENTRY_EXIT_ON_FAILURE(wolfsentry_lock_alloc(wolfsentry_get_hpi(wolfsentry), thread, &lock, WOLFSENTRY_LOCK_FLAG_NONE)); + + memset(&reader_args, 0, sizeof reader_args); + reader_args.wolfsentry = wolfsentry; + reader_args.measured_sequence = measured_sequence; + reader_args.measured_sequence_i = &measured_sequence_i; + reader_args.lock = lock; + writer_args = reader_args; + reader_args.thread_id = 1; + reader_args.max_wait = -1; + writer_args.thread_id = 2; + writer_args.max_wait = MAX_WAIT; + WOLFSENTRY_EXIT_ON_FAILURE_PTHREAD(pthread_mutex_init(&reader_args.thread_phase_lock, NULL)); + WOLFSENTRY_EXIT_ON_FAILURE_PTHREAD(pthread_mutex_init(&writer_args.thread_phase_lock, NULL)); + + /* hold the lock shared twice, so the first unlock leaves a shared holder in place. */ + WOLFSENTRY_EXIT_ON_FAILURE(wolfsentry_lock_shared(lock, thread, WOLFSENTRY_LOCK_FLAG_NONE)); + WOLFSENTRY_EXIT_ON_FAILURE(wolfsentry_lock_shared(lock, thread, WOLFSENTRY_LOCK_FLAG_NONE)); + + /* a timed writer queues, which forces the subsequent reader to queue behind it. */ + WOLFSENTRY_EXIT_ON_FAILURE_PTHREAD(pthread_create(&writer_thread, 0 /* attr */, (void *(*)(void *))wr_timeout_routine, (void *)&writer_args)); + WAIT_FOR_PHASE(writer_args, 1); + WOLFSENTRY_EXIT_ON_FAILURE_PTHREAD(pthread_kill(writer_thread, 0)); + usleep(10000); + + WOLFSENTRY_EXIT_ON_FAILURE_PTHREAD(pthread_create(&reader_thread, 0 /* attr */, (void *(*)(void *))rd_routine, (void *)&reader_args)); + WAIT_FOR_PHASE(reader_args, 1); + WOLFSENTRY_EXIT_ON_FAILURE_PTHREAD(pthread_kill(reader_thread, 0)); + usleep(10000); + + /* the writer times out and dequeues itself, leaving the reader queued. */ + WOLFSENTRY_EXIT_ON_FAILURE_PTHREAD(pthread_join(writer_thread, 0 /* retval */)); + + /* the reader must still be parked, not already holding the lock, else the scenario was not reached. */ + usleep(10000); + WOLFSENTRY_EXIT_ON_FAILURE_PTHREAD(pthread_mutex_lock(&reader_args.thread_phase_lock)); + reader_phase = reader_args.thread_phase; + WOLFSENTRY_EXIT_ON_FAILURE_PTHREAD(pthread_mutex_unlock(&reader_args.thread_phase_lock)); + WOLFSENTRY_EXIT_ON_FALSE(reader_phase == 1); + WOLFSENTRY_EXIT_ON_FALSE(WOLFSENTRY_ATOMIC_LOAD(measured_sequence_i) == 0); + + /* this unlock releases the queued reader, and must post exactly one wakeup. */ + WOLFSENTRY_EXIT_ON_FAILURE(wolfsentry_lock_unlock(lock, thread, WOLFSENTRY_LOCK_FLAG_NONE)); + WOLFSENTRY_EXIT_ON_FAILURE_PTHREAD(pthread_join(reader_thread, 0 /* retval */)); + WOLFSENTRY_EXIT_ON_FALSE(WOLFSENTRY_ATOMIC_LOAD(measured_sequence_i) == 2); + WOLFSENTRY_EXIT_ON_FAILURE(wolfsentry_lock_unlock(lock, thread, WOLFSENTRY_LOCK_FLAG_NONE)); + + /* a surplus wakeup token is detected as an inconsistency at destroy time. */ + WOLFSENTRY_EXIT_ON_FAILURE(wolfsentry_lock_free(&lock, thread, WOLFSENTRY_LOCK_FLAG_NONE)); + + WOLFSENTRY_EXIT_ON_FAILURE_PTHREAD(pthread_mutex_destroy(&reader_args.thread_phase_lock)); + WOLFSENTRY_EXIT_ON_FAILURE_PTHREAD(pthread_mutex_destroy(&writer_args.thread_phase_lock)); + + WOLFSENTRY_EXIT_ON_FAILURE(wolfsentry_shutdown(&wolfsentry, thread)); + + (void)alarm(0); + + WOLFSENTRY_EXIT_ON_FAILURE(WOLFSENTRY_THREAD_TAILER(WOLFSENTRY_THREAD_FLAG_NONE)); + + WOLFSENTRY_RETURN_OK; +} + +#else + +TEST_SKIP(test_rw_locks_auto_downgrade) +TEST_SKIP(test_rw_locks_reader_wakeup) + +#endif /* WOLFSENTRY_THREADSAFE */ + +static int test_object_release_tableless(void) { + struct wolfsentry_context *wolfsentry; + struct wolfsentry_route_table *main_routes; + const struct wolfsentry_route *fallthrough_route; + struct wolfsentry_route *route_ref; + wolfsentry_action_res_t action_results; + wolfsentry_route_flags_t flags = WOLFSENTRY_ROUTE_FLAG_TCPLIKE_PORT_NUMBERS | WOLFSENTRY_ROUTE_FLAG_DIRECTION_IN; + struct { + struct wolfsentry_sockaddr sa; + byte addr_buf[4]; + } remote, local; +#ifdef WOLFSENTRY_HAVE_DESIGNATED_INITIALIZERS + struct wolfsentry_eventconfig config = { .route_private_data_size = PRIVATE_DATA_SIZE, .route_private_data_alignment = PRIVATE_DATA_ALIGNMENT, .max_connection_count = 10 }; +#else + struct wolfsentry_eventconfig config = { 32, 0, 10, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0 }; +#endif + + WOLFSENTRY_THREAD_HEADER_CHECKED(WOLFSENTRY_THREAD_FLAG_NONE); + + WOLFSENTRY_EXIT_ON_FAILURE(wolfsentry_init_ex(wolfsentry_build_settings, + WOLFSENTRY_CONTEXT_ARGS_OUT_EX(WOLFSENTRY_TEST_HPI), + &config, + &wolfsentry, + WOLFSENTRY_INIT_FLAG_NONE)); + + WOLFSENTRY_EXIT_ON_FAILURE(wolfsentry_context_lock_mutex(WOLFSENTRY_CONTEXT_ARGS_OUT)); + + /* the fallthrough route is never linked into a table, but is documented as releasable. */ + WOLFSENTRY_EXIT_ON_FAILURE(wolfsentry_route_get_main_table(WOLFSENTRY_CONTEXT_ARGS_OUT, &main_routes)); + WOLFSENTRY_EXIT_ON_FAILURE(wolfsentry_route_table_fallthrough_route_get(WOLFSENTRY_CONTEXT_ARGS_OUT, main_routes, &fallthrough_route)); + WOLFSENTRY_EXIT_ON_FALSE(wolfsentry_get_object_type(fallthrough_route) == WOLFSENTRY_OBJECT_TYPE_UNINITED); + WOLFSENTRY_EXIT_ON_FAILURE(wolfsentry_object_release(WOLFSENTRY_CONTEXT_ARGS_OUT, (void *)fallthrough_route, NULL /* action_results */)); + + /* a checked-out route that has since been deleted is table-less too, and must be rejected, not dereferenced. */ + memset(&remote, 0, sizeof remote); + memset(&local, 0, sizeof local); + remote.sa.sa_family = local.sa.sa_family = AF_INET; + remote.sa.sa_proto = local.sa.sa_proto = IPPROTO_TCP; + remote.sa.sa_port = 12345; + local.sa.sa_port = 443; + remote.sa.addr_len = local.sa.addr_len = sizeof remote.addr_buf * BITS_PER_BYTE; + remote.sa.interface = local.sa.interface = 1; + memcpy(remote.sa.addr,"\0\1\2\3",sizeof remote.addr_buf); + memcpy(local.sa.addr,"\377\376\375\374",sizeof local.addr_buf); + + WOLFSENTRY_EXIT_ON_FAILURE(wolfsentry_route_insert_and_check_out(WOLFSENTRY_CONTEXT_ARGS_OUT, NULL /* caller_arg */, &remote.sa, &local.sa, flags, 0 /* event_label */, 0 /* event_label_len */, &route_ref, &action_results)); + WOLFSENTRY_EXIT_ON_FALSE(wolfsentry_get_object_type(route_ref) == WOLFSENTRY_OBJECT_TYPE_ROUTE); + WOLFSENTRY_EXIT_ON_FAILURE(wolfsentry_route_delete_by_id(WOLFSENTRY_CONTEXT_ARGS_OUT, NULL /* caller_arg */, wolfsentry_get_object_id(route_ref), 0 /* trigger_label */, 0 /* trigger_label_len */, &action_results)); + WOLFSENTRY_EXIT_ON_FALSE(wolfsentry_get_object_type(route_ref) == WOLFSENTRY_OBJECT_TYPE_UNINITED); + WOLFSENTRY_EXIT_UNLESS_EXPECTED_FAILURE(INVALID_ARG, wolfsentry_object_release(WOLFSENTRY_CONTEXT_ARGS_OUT, route_ref, NULL /* action_results */)); + WOLFSENTRY_EXIT_ON_FAILURE(wolfsentry_route_drop_reference(WOLFSENTRY_CONTEXT_ARGS_OUT, route_ref, &action_results)); + WOLFSENTRY_EXIT_ON_FALSE(WOLFSENTRY_CHECK_BITS(action_results, WOLFSENTRY_ACTION_RES_DEALLOCATED)); + + WOLFSENTRY_EXIT_ON_FAILURE(wolfsentry_context_unlock(WOLFSENTRY_CONTEXT_ARGS_OUT)); + WOLFSENTRY_EXIT_ON_FAILURE(wolfsentry_shutdown(WOLFSENTRY_CONTEXT_ARGS_OUT_EX(&wolfsentry))); + WOLFSENTRY_EXIT_ON_FAILURE(WOLFSENTRY_THREAD_TAILER(WOLFSENTRY_THREAD_FLAG_NONE)); + + WOLFSENTRY_RETURN_OK; +} + +#if defined(WOLFSENTRY_THREADSAFE) + +static int test_context_clone_readonly_thread(void) { + struct wolfsentry_context *wolfsentry, *clone = NULL; +#ifdef WOLFSENTRY_HAVE_DESIGNATED_INITIALIZERS + struct wolfsentry_eventconfig config = { .route_private_data_size = PRIVATE_DATA_SIZE, .route_private_data_alignment = PRIVATE_DATA_ALIGNMENT, .max_connection_count = 10 }; +#else + struct wolfsentry_eventconfig config = { 32, 0, 10, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0 }; +#endif + + WOLFSENTRY_THREAD_HEADER_CHECKED(WOLFSENTRY_THREAD_FLAG_NONE); + + WOLFSENTRY_EXIT_ON_FAILURE(wolfsentry_init_ex(wolfsentry_build_settings, + WOLFSENTRY_TEST_HPI, + thread, + &config, + &wolfsentry, + WOLFSENTRY_INIT_FLAG_NONE)); + + /* a read-only thread can clone, but cannot take the clone's mutex, so the clone must be torn down. */ + WOLFSENTRY_EXIT_ON_FAILURE(wolfsentry_set_thread_readonly(thread)); + WOLFSENTRY_EXIT_UNLESS_EXPECTED_FAILURE(NOT_PERMITTED, wolfsentry_context_clone(WOLFSENTRY_CONTEXT_ARGS_OUT, &clone, WOLFSENTRY_CLONE_FLAG_NONE)); + WOLFSENTRY_EXIT_ON_FALSE(clone == NULL); + WOLFSENTRY_EXIT_ON_FAILURE(wolfsentry_set_thread_readwrite(thread)); + + WOLFSENTRY_EXIT_ON_FAILURE(wolfsentry_shutdown(&wolfsentry, thread)); + WOLFSENTRY_EXIT_ON_FAILURE(WOLFSENTRY_THREAD_TAILER(WOLFSENTRY_THREAD_FLAG_NONE)); + + WOLFSENTRY_RETURN_OK; +} + +#else + +TEST_SKIP(test_context_clone_readonly_thread) + +#endif /* WOLFSENTRY_THREADSAFE */ + +static int test_context_clone_fallthrough(void) { + struct wolfsentry_context *wolfsentry, *clone = NULL; + struct wolfsentry_route_table *clone_routes; + const struct wolfsentry_route *fallthrough_route; +#ifdef WOLFSENTRY_HAVE_DESIGNATED_INITIALIZERS + struct wolfsentry_eventconfig config = { .route_private_data_size = PRIVATE_DATA_SIZE, .route_private_data_alignment = PRIVATE_DATA_ALIGNMENT, .max_connection_count = 10 }; +#else + struct wolfsentry_eventconfig config = { 32, 0, 10, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0 }; +#endif + + WOLFSENTRY_THREAD_HEADER_CHECKED(WOLFSENTRY_THREAD_FLAG_NONE); + + WOLFSENTRY_EXIT_ON_FAILURE(wolfsentry_init_ex(wolfsentry_build_settings, + WOLFSENTRY_CONTEXT_ARGS_OUT_EX(WOLFSENTRY_TEST_HPI), + &config, + &wolfsentry, + WOLFSENTRY_INIT_FLAG_NONE)); + + WOLFSENTRY_EXIT_ON_FAILURE(wolfsentry_context_lock_mutex(WOLFSENTRY_CONTEXT_ARGS_OUT)); + + /* a full-flush clone starts with an empty route table, which must still carry a fallthrough route. */ + WOLFSENTRY_EXIT_ON_FAILURE(wolfsentry_context_clone(WOLFSENTRY_CONTEXT_ARGS_OUT, &clone, WOLFSENTRY_CLONE_FLAG_AS_AT_CREATION)); + WOLFSENTRY_EXIT_ON_FAILURE(wolfsentry_route_get_main_table(WOLFSENTRY_CONTEXT_ARGS_OUT_EX(clone), &clone_routes)); + WOLFSENTRY_EXIT_ON_FAILURE(wolfsentry_route_table_fallthrough_route_get(WOLFSENTRY_CONTEXT_ARGS_OUT_EX(clone), clone_routes, &fallthrough_route)); + WOLFSENTRY_EXIT_ON_FAILURE(wolfsentry_object_release(WOLFSENTRY_CONTEXT_ARGS_OUT_EX(clone), (void *)fallthrough_route, NULL /* action_results */)); + WOLFSENTRY_EXIT_ON_FAILURE(wolfsentry_context_free(WOLFSENTRY_CONTEXT_ARGS_OUT_EX(&clone))); + + WOLFSENTRY_EXIT_ON_FAILURE(wolfsentry_context_unlock(WOLFSENTRY_CONTEXT_ARGS_OUT)); + WOLFSENTRY_EXIT_ON_FAILURE(wolfsentry_shutdown(WOLFSENTRY_CONTEXT_ARGS_OUT_EX(&wolfsentry))); + WOLFSENTRY_EXIT_ON_FAILURE(WOLFSENTRY_THREAD_TAILER(WOLFSENTRY_THREAD_FLAG_NONE)); + + WOLFSENTRY_RETURN_OK; +} + +static wolfsentry_errcode_t failing_interval_from_seconds(time_t howlong_secs, long howlong_nsecs, wolfsentry_time_t *howlong) { + (void)howlong_secs; + (void)howlong_nsecs; + (void)howlong; + WOLFSENTRY_ERROR_RETURN(NUMERIC_ARG_TOO_BIG); +} + +static int test_init_interval_conversion_failure(void) { + struct wolfsentry_context *wolfsentry, *wolfsentry2 = NULL; + struct wolfsentry_host_platform_interface hpi; +#ifdef WOLFSENTRY_HAVE_DESIGNATED_INITIALIZERS + struct wolfsentry_eventconfig config = { .route_private_data_size = PRIVATE_DATA_SIZE, .route_private_data_alignment = PRIVATE_DATA_ALIGNMENT, .max_connection_count = 10, .penaltybox_duration = 1 }; +#else + struct wolfsentry_eventconfig config = { 32, 0, 10, 0, 1, 0, 0, 0, 0, 0, 0, 0, 0 }; +#endif + + WOLFSENTRY_THREAD_HEADER_CHECKED(WOLFSENTRY_THREAD_FLAG_NONE); + + WOLFSENTRY_EXIT_ON_FAILURE(wolfsentry_init_ex(wolfsentry_build_settings, + WOLFSENTRY_CONTEXT_ARGS_OUT_EX(WOLFSENTRY_TEST_HPI), + &config, + &wolfsentry, + WOLFSENTRY_INIT_FLAG_NONE)); + + /* borrow the working platform interface, then make the seconds conversion fail. */ + hpi = *wolfsentry_get_hpi(wolfsentry); + hpi.timecbs.interval_from_seconds = failing_interval_from_seconds; + + /* a rejected duration must fail initialization and leave no context behind. */ + WOLFSENTRY_EXIT_UNLESS_EXPECTED_FAILURE(NUMERIC_ARG_TOO_BIG, wolfsentry_init_ex(wolfsentry_build_settings, + WOLFSENTRY_CONTEXT_ARGS_OUT_EX(&hpi), + &config, + &wolfsentry2, + WOLFSENTRY_INIT_FLAG_NONE)); + WOLFSENTRY_EXIT_ON_FALSE(wolfsentry2 == NULL); + + WOLFSENTRY_EXIT_ON_FAILURE(wolfsentry_shutdown(WOLFSENTRY_CONTEXT_ARGS_OUT_EX(&wolfsentry))); + WOLFSENTRY_EXIT_ON_FAILURE(WOLFSENTRY_THREAD_TAILER(WOLFSENTRY_THREAD_FLAG_NONE)); + + WOLFSENTRY_RETURN_OK; +} + +static int test_context_exchange_default_policy(void) { + struct wolfsentry_context *wolfsentry, *clone = NULL; + wolfsentry_action_res_t policy; +#ifdef WOLFSENTRY_HAVE_DESIGNATED_INITIALIZERS + struct wolfsentry_eventconfig config = { .route_private_data_size = PRIVATE_DATA_SIZE, .route_private_data_alignment = PRIVATE_DATA_ALIGNMENT, .max_connection_count = 10 }; +#else + struct wolfsentry_eventconfig config = { 32, 0, 10, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0 }; +#endif + + WOLFSENTRY_THREAD_HEADER_CHECKED(WOLFSENTRY_THREAD_FLAG_NONE); + + WOLFSENTRY_EXIT_ON_FAILURE(wolfsentry_init_ex(wolfsentry_build_settings, + WOLFSENTRY_CONTEXT_ARGS_OUT_EX(WOLFSENTRY_TEST_HPI), + &config, + &wolfsentry, + WOLFSENTRY_INIT_FLAG_NONE)); + + WOLFSENTRY_EXIT_ON_FAILURE(wolfsentry_context_lock_mutex(WOLFSENTRY_CONTEXT_ARGS_OUT)); + WOLFSENTRY_EXIT_ON_FAILURE(wolfsentry_route_default_policy_set(WOLFSENTRY_CONTEXT_ARGS_OUT, WOLFSENTRY_ACTION_RES_REJECT)); + + /* stage a new policy in a clone, then swap it in, as a config reload does. */ + WOLFSENTRY_EXIT_ON_FAILURE(wolfsentry_context_clone(WOLFSENTRY_CONTEXT_ARGS_OUT, &clone, WOLFSENTRY_CLONE_FLAG_NONE)); + WOLFSENTRY_EXIT_ON_FAILURE(wolfsentry_route_default_policy_set(WOLFSENTRY_CONTEXT_ARGS_OUT_EX(clone), WOLFSENTRY_ACTION_RES_ACCEPT)); + WOLFSENTRY_EXIT_ON_FAILURE(wolfsentry_context_exchange(WOLFSENTRY_CONTEXT_ARGS_OUT, clone)); + + WOLFSENTRY_EXIT_ON_FAILURE(wolfsentry_route_default_policy_get(WOLFSENTRY_CONTEXT_ARGS_OUT, &policy)); + WOLFSENTRY_EXIT_ON_FALSE(policy == WOLFSENTRY_ACTION_RES_ACCEPT); + /* the lock-free dispatch fallback reads the context-level copy, which must follow the swap. */ + WOLFSENTRY_EXIT_ON_FALSE(wolfsentry->default_policy == WOLFSENTRY_ACTION_RES_ACCEPT); + WOLFSENTRY_EXIT_ON_FALSE(clone->default_policy == WOLFSENTRY_ACTION_RES_REJECT); + + WOLFSENTRY_EXIT_ON_FAILURE(wolfsentry_context_free(WOLFSENTRY_CONTEXT_ARGS_OUT_EX(&clone))); + WOLFSENTRY_EXIT_ON_FAILURE(wolfsentry_context_unlock(WOLFSENTRY_CONTEXT_ARGS_OUT)); + WOLFSENTRY_EXIT_ON_FAILURE(wolfsentry_shutdown(WOLFSENTRY_CONTEXT_ARGS_OUT_EX(&wolfsentry))); + WOLFSENTRY_EXIT_ON_FAILURE(WOLFSENTRY_THREAD_TAILER(WOLFSENTRY_THREAD_FLAG_NONE)); + + WOLFSENTRY_RETURN_OK; +} + #endif /* TEST_RWLOCKS */ #ifdef TEST_STATIC_ROUTES @@ -5733,6 +6111,41 @@ int main(int argc, char* argv[]) { (void)fprintf(stderr, "test_rw_locks failed, " WOLFSENTRY_ERROR_FMT "\n", WOLFSENTRY_ERROR_FMT_ARGS(ret)); err = 1; } + ret = test_rw_locks_auto_downgrade(); + if (! WOLFSENTRY_ERROR_CODE_IS(ret, OK)) { + (void)fprintf(stderr, "test_rw_locks_auto_downgrade failed, " WOLFSENTRY_ERROR_FMT "\n", WOLFSENTRY_ERROR_FMT_ARGS(ret)); + err = 1; + } + ret = test_rw_locks_reader_wakeup(); + if (! WOLFSENTRY_ERROR_CODE_IS(ret, OK)) { + (void)fprintf(stderr, "test_rw_locks_reader_wakeup failed, " WOLFSENTRY_ERROR_FMT "\n", WOLFSENTRY_ERROR_FMT_ARGS(ret)); + err = 1; + } + ret = test_object_release_tableless(); + if (! WOLFSENTRY_ERROR_CODE_IS(ret, OK)) { + (void)fprintf(stderr, "test_object_release_tableless failed, " WOLFSENTRY_ERROR_FMT "\n", WOLFSENTRY_ERROR_FMT_ARGS(ret)); + err = 1; + } + ret = test_context_clone_readonly_thread(); + if (! WOLFSENTRY_ERROR_CODE_IS(ret, OK)) { + (void)fprintf(stderr, "test_context_clone_readonly_thread failed, " WOLFSENTRY_ERROR_FMT "\n", WOLFSENTRY_ERROR_FMT_ARGS(ret)); + err = 1; + } + ret = test_context_clone_fallthrough(); + if (! WOLFSENTRY_ERROR_CODE_IS(ret, OK)) { + (void)fprintf(stderr, "test_context_clone_fallthrough failed, " WOLFSENTRY_ERROR_FMT "\n", WOLFSENTRY_ERROR_FMT_ARGS(ret)); + err = 1; + } + ret = test_init_interval_conversion_failure(); + if (! WOLFSENTRY_ERROR_CODE_IS(ret, OK)) { + (void)fprintf(stderr, "test_init_interval_conversion_failure failed, " WOLFSENTRY_ERROR_FMT "\n", WOLFSENTRY_ERROR_FMT_ARGS(ret)); + err = 1; + } + ret = test_context_exchange_default_policy(); + if (! WOLFSENTRY_ERROR_CODE_IS(ret, OK)) { + (void)fprintf(stderr, "test_context_exchange_default_policy failed, " WOLFSENTRY_ERROR_FMT "\n", WOLFSENTRY_ERROR_FMT_ARGS(ret)); + err = 1; + } #endif #ifdef TEST_STATIC_ROUTES