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");
+}