diff --git a/CHANGELOG.md b/CHANGELOG.md index 35186f13..46a20f76 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -1,5 +1,21 @@ # Changelog +## avs_commons 5.3.1 (June 12th, 2023) + +### Features + +* Added ``AVS_NET_SOCKET_OPT_PREFERRED_ADDR_FAMILY`` and + ``AVS_NET_SOCKET_OPT_FORCED_ADDR_FAMILY`` options that allow setting address + family configuration of an already created socket +* Automatically upgrading IPv4 sockets to IPv6 when connecting is now possible +* Added ``AVS_UNIT_MOCK_DECLARE()`` and ``AVS_UNIT_MOCK_DEFINE()`` to facilitate + declaring mocked functions with external linkage + +### Improvements + +* Slightly changed the semantics of ``avs_sched_run()``, to fix erroneous + behavior on platforms with low-resolution system clocks + ## avs_commons 5.3.0 (March 10th, 2023) ### Features diff --git a/CMakeLists.txt b/CMakeLists.txt index 4408c468..e58f50f9 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -17,7 +17,7 @@ cmake_minimum_required(VERSION 3.6.0) project(avs_commons C) -set(AVS_COMMONS_VERSION "5.3.0") +set(AVS_COMMONS_VERSION "5.3.1") ################# DISTRIBUTION ################################################# diff --git a/include_public/avsystem/commons/avs_sched.h b/include_public/avsystem/commons/avs_sched.h index d222094a..26553827 100644 --- a/include_public/avsystem/commons/avs_sched.h +++ b/include_public/avsystem/commons/avs_sched.h @@ -209,8 +209,22 @@ static inline int avs_sched_wait_for_next(avs_sched_t *sched, * Executes jobs scheduled for execution before or at the current point in time. * * Specifically, this function will execute any jobs scheduled for before or at - * the time of entry to this function. If any of the executed jobs schedule more - * jobs for "now", they will not be executed. + * the time returned by @ref avs_time_monotonic_now, queried once at the entry + * to this function. + * + * Note: In principle, jobs scheduled for "now" during the run of + * avs_sched_run() are not supposed to be executed within the + * same run. However, this depends on a sufficiently high resolution of the + * underlying monotonic clock - each @ref AVS_SCHED_NOW call performs a new call + * to @ref avs_time_monotonic_now, and such job will only be executed within the + * same run if the numeric value of the clock did not change since entry to + * avs_sched_run(). + * + * In other words, in the worst case scenario of an indirect "infinite loop" of + * a scheduler job scheduling itself for another execution "now", + * avs_sched_run() will block for one "tick" of the monotonic clock + * (i.e., a period between changes of its numeric value), plus execution time of + * the last such job. * * @param sched Scheduler object to access. */ diff --git a/include_public/avsystem/commons/avs_socket.h b/include_public/avsystem/commons/avs_socket.h index 0d25ef7f..4bb4b3ef 100644 --- a/include_public/avsystem/commons/avs_socket.h +++ b/include_public/avsystem/commons/avs_socket.h @@ -605,6 +605,35 @@ typedef enum { * the behaviour is undefined. */ AVS_NET_SOCKET_OPT_CONNECTION_ID_RESUMED, + + /** + * Used to set and retrieve the IP protocol version preferred for + * communication. This controls the same value as the + * preferred_family field of the @ref avs_net_socket_configuration_t + * structure. + * + * The value is passed in the addr_family field of the + * @ref avs_net_socket_opt_value_t union. The change will take effect at the + * next call to a method that performs address resolution, such as + * @ref avs_net_socket_connect . If the socket is already bound or + * connected, it will not be reconnected automatically. + */ + AVS_NET_SOCKET_OPT_PREFERRED_ADDR_FAMILY, + + /** + * Used to set and retrieve the only IP protocol version that is allowed to + * be used for communication. This controls the same value as the + * address_family field of the @ref avs_net_socket_configuration_t + * structure. It may be set to AVS_NET_UNSPEC to allow usage of both + * IPv4 and IPv6, if supported by the system. + * + * The value is passed in the addr_family field of the + * @ref avs_net_socket_opt_value_t union. The change will take effect at the + * next call to a method that performs address resolution, such as + * @ref avs_net_socket_connect . If the socket is already bound or + * connected, it will not be reconnected automatically. + */ + AVS_NET_SOCKET_OPT_FORCED_ADDR_FAMILY, } avs_net_socket_opt_key_t; typedef enum { diff --git a/include_public/avsystem/commons/avs_unit_mock_helpers.h b/include_public/avsystem/commons/avs_unit_mock_helpers.h index 5f74b48b..ae65086e 100644 --- a/include_public/avsystem/commons/avs_unit_mock_helpers.h +++ b/include_public/avsystem/commons/avs_unit_mock_helpers.h @@ -140,12 +140,26 @@ unsigned avs_unit_mock_invocations__(avs_unit_mock_func_ptr *invoked_func); avs_unit_mock_invocations__((avs_unit_mock_func_ptr *) &AVS_UNIT_MOCK(func)) /** - * Declares and defines a mocked function pointer. + * Produces a forward declaration of a mocked function pointer. + * + * The declaration may be preceded with linkage specifiers such as + * static. + * + * @param _function_to_mock Name of the mocked function. + */ +#define AVS_UNIT_MOCK_DECLARE(_function_to_mock) \ + __typeof__(_function_to_mock) *AVS_UNIT_MOCK(_function_to_mock) + +/** + * Defines a mocked function pointer. + * + * The definition shall exist in a single translation unit only, and may be + * preceded with linkage specifiers such as static. * * @param _function_to_mock Name of the mocked function. */ -#define AVS_UNIT_MOCK_CREATE(_function_to_mock) \ - static __typeof__(_function_to_mock) *AVS_UNIT_MOCK(_function_to_mock); \ +#define AVS_UNIT_MOCK_DEFINE(_function_to_mock) \ + __typeof__(_function_to_mock) *AVS_UNIT_MOCK(_function_to_mock) = NULL; \ static void _avs_unit_mock_constructor_##_function_to_mock(void) \ __attribute__((constructor)); \ static void _avs_unit_mock_constructor_##_function_to_mock(void) { \ @@ -153,6 +167,14 @@ unsigned avs_unit_mock_invocations__(avs_unit_mock_func_ptr *invoked_func); (avs_unit_mock_func_ptr *) &AVS_UNIT_MOCK(_function_to_mock)); \ } +/** + * Declares and defines a mocked function pointer with internal linkage. + * + * @param _function_to_mock Name of the mocked function. + */ +#define AVS_UNIT_MOCK_CREATE(_function_to_mock) \ + static AVS_UNIT_MOCK_DEFINE(_function_to_mock) + #ifdef __cplusplus } #endif diff --git a/src/net/compat/posix/avs_net_impl.c b/src/net/compat/posix/avs_net_impl.c index a4e84683..f3b2c171 100644 --- a/src/net/compat/posix/avs_net_impl.c +++ b/src/net/compat/posix/avs_net_impl.c @@ -1003,26 +1003,23 @@ resolve_addrinfo_for_socket(net_socket_impl_t *net_socket, avs_net_af_t socket_family = get_avs_af(get_socket_family(net_socket->socket)); if (socket_family != AVS_NET_AF_UNSPEC && socket_family != family) { -# if defined(AVS_COMMONS_NET_WITH_IPV4) && defined(AVS_COMMONS_NET_WITH_IPV6) +# ifdef WITH_AVS_V4MAPPED if (socket_family == AVS_NET_AF_INET6) { -# ifdef WITH_AVS_V4MAPPED // If we have an already created socket that is bound to IPv6, // but the requested family is something else, use v4-mapping resolve_flags |= AVS_NET_ADDRINFO_RESOLVE_F_V4MAPPED; -# else // WITH_AVS_V4MAPPED + } else +# endif // WITH_AVS_V4MAPPED + { + // The case when we have an already created socket, we cannot + // use IPv6-to-IPv4 mapping, and the requested family is + // different from the socket's bound one will be handled in + // try_connect() and ensure_socket_bound_to_family() if (!for_connect) { - // We shouldn't recreate the socket for sendto, just give up + // ...but we shouldn't recreate the socket for sendto, so + // just give up in that case return NULL; } -# endif // WITH_AVS_V4MAPPED - } else -# endif // defined(AVS_COMMONS_NET_WITH_IPV4) && - // defined(AVS_COMMONS_NET_WITH_IPV6) - { - // If we have an already created socket, we cannot use - // IPv6-to-IPv4 mapping, and the requested family is different - // than the socket's bound one - we're screwed, just give up - return NULL; } } } @@ -1135,9 +1132,7 @@ static avs_error_t create_listening_socket(net_socket_impl_t *net_socket, return err; } -# if defined(AVS_COMMONS_NET_WITH_IPV4) \ - && defined(AVS_COMMONS_NET_WITH_IPV6) \ - && !defined(WITH_AVS_V4MAPPED) +# if defined(AVS_COMMONS_NET_WITH_IPV4) && defined(AVS_COMMONS_NET_WITH_IPV6) static avs_error_t ensure_socket_bound_to_family(net_socket_impl_t *net_socket, const sockaddr_endpoint_union_t *target_address) { @@ -1182,15 +1177,13 @@ ensure_socket_bound_to_family(net_socket_impl_t *net_socket, return create_listening_socket(net_socket, &new_addr.addr, new_addr_len); } # endif // defined(AVS_COMMONS_NET_WITH_IPV4) && - // defined(AVS_COMMONS_NET_WITH_IPV6) && !defined(WITH_AVS_V4MAPPED) + // defined(AVS_COMMONS_NET_WITH_IPV6) static avs_error_t try_connect(net_socket_impl_t *net_socket, const sockaddr_endpoint_union_t *address) { char socket_was_already_open = (net_socket->socket != INVALID_SOCKET); avs_error_t err = AVS_OK; -# if defined(AVS_COMMONS_NET_WITH_IPV4) \ - && defined(AVS_COMMONS_NET_WITH_IPV6) \ - && !defined(WITH_AVS_V4MAPPED) +# if defined(AVS_COMMONS_NET_WITH_IPV4) && defined(AVS_COMMONS_NET_WITH_IPV6) if (socket_was_already_open && net_socket->type == AVS_NET_UDP_SOCKET) { err = ensure_socket_bound_to_family(net_socket, address); if (avs_is_err(err)) { @@ -1198,7 +1191,7 @@ static avs_error_t try_connect(net_socket_impl_t *net_socket, } } # endif // defined(AVS_COMMONS_NET_WITH_IPV4) && - // defined(AVS_COMMONS_NET_WITH_IPV6) && !defined(WITH_AVS_V4MAPPED) + // defined(AVS_COMMONS_NET_WITH_IPV6) if (!socket_was_already_open) { if ((net_socket->socket = socket(address->sockaddr_ep.addr.sa_family, @@ -2056,6 +2049,14 @@ static avs_error_t get_opt_net(avs_net_socket_t *net_socket_, case AVS_NET_SOCKET_HAS_BUFFERED_DATA: out_option_value->flag = false; return AVS_OK; + case AVS_NET_SOCKET_OPT_PREFERRED_ADDR_FAMILY: + out_option_value->addr_family = + net_socket->configuration.preferred_family; + return AVS_OK; + case AVS_NET_SOCKET_OPT_FORCED_ADDR_FAMILY: + out_option_value->addr_family = + net_socket->configuration.address_family; + return AVS_OK; default: LOG(DEBUG, _("get_opt_net: unknown or unsupported option key: ") @@ -2065,6 +2066,17 @@ static avs_error_t get_opt_net(avs_net_socket_t *net_socket_, } } +static bool is_valid_addr_family(avs_net_af_t family) { + return family == AVS_NET_AF_UNSPEC +# ifdef AVS_COMMONS_NET_WITH_IPV4 + || family == AVS_NET_AF_INET4 +# endif /* AVS_COMMONS_NET_WITH_IPV4 */ +# ifdef AVS_COMMONS_NET_WITH_IPV6 + || family == AVS_NET_AF_INET6 +# endif /* AVS_COMMONS_NET_WITH_IPV6 */ + ; +} + static avs_error_t set_opt_net(avs_net_socket_t *net_socket_, avs_net_socket_opt_key_t option_key, avs_net_socket_opt_value_t option_value) { @@ -2073,6 +2085,23 @@ static avs_error_t set_opt_net(avs_net_socket_t *net_socket_, case AVS_NET_SOCKET_OPT_RECV_TIMEOUT: net_socket->recv_timeout = option_value.recv_timeout; return AVS_OK; + case AVS_NET_SOCKET_OPT_PREFERRED_ADDR_FAMILY: + if (!is_valid_addr_family(option_value.addr_family)) { + LOG(DEBUG, _("set_opt_net: unsupported preferred address family")); + return avs_errno(AVS_EINVAL); + } else { + net_socket->configuration.preferred_family = + option_value.addr_family; + return AVS_OK; + } + case AVS_NET_SOCKET_OPT_FORCED_ADDR_FAMILY: + if (!is_valid_addr_family(option_value.addr_family)) { + LOG(DEBUG, _("set_opt_net: unsupported forced address family")); + return avs_errno(AVS_EINVAL); + } else { + net_socket->configuration.address_family = option_value.addr_family; + return AVS_OK; + } default: LOG(DEBUG, _("set_opt_net: unknown or unsupported option key: ") diff --git a/src/sched/avs_sched.c b/src/sched/avs_sched.c index 72a1f6ef..1ff9debb 100644 --- a/src/sched/avs_sched.c +++ b/src/sched/avs_sched.c @@ -312,7 +312,7 @@ static AVS_LIST(avs_sched_job_t) fetch_job(avs_sched_t *sched, AVS_LIST(avs_sched_job_t) result = NULL; nonfailing_mutex_lock(sched->mutex); if (sched->jobs - && avs_time_monotonic_before(sched->jobs->instant, deadline)) { + && !avs_time_monotonic_before(deadline, sched->jobs->instant)) { if (sched->jobs->handle_ptr) { nonfailing_mutex_lock(g_handle_access_mutex); assert(*sched->jobs->handle_ptr == sched->jobs); diff --git a/src/utils/avs_time.c b/src/utils/avs_time.c index 42c8cb4a..d249f770 100644 --- a/src/utils/avs_time.c +++ b/src/utils/avs_time.c @@ -437,13 +437,20 @@ const char *avs_time_duration_as_string_impl__( int result; if (avs_time_duration_valid(time)) { - if (time.seconds < 0 && time.nanoseconds > 0) { - ++time.seconds; - time.nanoseconds = 1000000000 - time.nanoseconds; + bool negative = time.seconds < 0; + if (negative) { + if (time.nanoseconds > 0) { + ++time.seconds; + time.nanoseconds = 1000000000 - time.nanoseconds; + } + time.seconds = -time.seconds; } - result = avs_simple_snprintf( - *buf, AVS_TIME_DURATION_AS_STRING_MAX_LENGTH, "%s.%09" PRId32, - AVS_INT64_AS_STRING(time.seconds), time.nanoseconds); + assert(time.seconds >= 0); + result = avs_simple_snprintf(*buf, + AVS_TIME_DURATION_AS_STRING_MAX_LENGTH, + "%s%s.%09" PRId32, negative ? "-" : "", + AVS_INT64_AS_STRING(time.seconds), + time.nanoseconds); } else { result = avs_simple_snprintf( *buf, AVS_TIME_DURATION_AS_STRING_MAX_LENGTH, "TIME_INVALID"); diff --git a/tests/net/socket_common_testcases.h b/tests/net/socket_common_testcases.h index d7241d62..253d9dd4 100644 --- a/tests/net/socket_common_testcases.h +++ b/tests/net/socket_common_testcases.h @@ -61,6 +61,8 @@ run_socket_set_opt_test_cases(avs_net_socket_t *socket, opt_val.state = AVS_NET_SOCKET_STATE_CONNECTED; break; case AVS_NET_SOCKET_OPT_ADDR_FAMILY: + case AVS_NET_SOCKET_OPT_PREFERRED_ADDR_FAMILY: + case AVS_NET_SOCKET_OPT_FORCED_ADDR_FAMILY: opt_val.addr_family = AVS_NET_AF_INET4; break; case AVS_NET_SOCKET_OPT_MTU: diff --git a/tests/net/socket_nosec.c b/tests/net/socket_nosec.c index e21bb469..039242b7 100644 --- a/tests/net/socket_nosec.c +++ b/tests/net/socket_nosec.c @@ -257,13 +257,13 @@ AVS_UNIT_TEST(socket, udp_connect_ipv4v6) { AVS_UNIT_ASSERT_SUCCESS(avs_net_socket_bind(socket, "0.0.0.0", "0")); AVS_UNIT_ASSERT_SUCCESS(avs_net_socket_get_local_port(socket, bound_port, sizeof(bound_port))); - // Upgrading from IPv4 to IPv6 will not work - AVS_UNIT_ASSERT_FAILED(avs_net_socket_connect(socket, "::1", listen_port)); + // Upgrading from IPv4 to IPv6 + AVS_UNIT_ASSERT_SUCCESS(avs_net_socket_connect(socket, "::1", listen_port)); AVS_UNIT_ASSERT_SUCCESS(avs_net_socket_cleanup(&socket)); AVS_UNIT_ASSERT_SUCCESS(avs_net_udp_socket_create(&socket, NULL)); AVS_UNIT_ASSERT_SUCCESS(avs_net_socket_bind(socket, "::", bound_port)); - // ...but downgrading from IPv6 to IPv4 should + // Downgrading from IPv6 to IPv4 AVS_UNIT_ASSERT_SUCCESS( avs_net_socket_connect(socket, "127.0.0.1", listen_port)); AVS_UNIT_ASSERT_SUCCESS(avs_net_socket_get_local_port( diff --git a/tests/sched/test_sched.c b/tests/sched/test_sched.c index ccac3689..3aa0b20f 100644 --- a/tests/sched/test_sched.c +++ b/tests/sched/test_sched.c @@ -53,8 +53,6 @@ int clock_gettime(clockid_t clock, struct timespec *t) { // all clocks are equivalent for our purposes, so ignore clock t->tv_sec = (time_t) MOCK_CLOCK.since_monotonic_epoch.seconds; t->tv_nsec = MOCK_CLOCK.since_monotonic_epoch.nanoseconds; - MOCK_CLOCK = avs_time_monotonic_add( - MOCK_CLOCK, avs_time_duration_from_scalar(1, AVS_TIME_NS)); return 0; } else { return orig_clock_gettime(clock, t); diff --git a/tests/utils/time.c b/tests/utils/time.c index 0b8db7cc..4cbaa62d 100644 --- a/tests/utils/time.c +++ b/tests/utils/time.c @@ -506,3 +506,9 @@ AVS_UNIT_TEST(time, negative_duration_as_string) { AVS_UNIT_ASSERT_EQUAL_STRING(AVS_TIME_DURATION_AS_STRING(value), "-122.999999544"); } + +AVS_UNIT_TEST(time, small_negative_duration_as_string) { + avs_time_duration_t value = { -1, 234 }; + AVS_UNIT_ASSERT_EQUAL_STRING(AVS_TIME_DURATION_AS_STRING(value), + "-0.999999766"); +}