diff --git a/common/Makefile.am b/common/Makefile.am index 9f368260..4e98f529 100644 --- a/common/Makefile.am +++ b/common/Makefile.am @@ -75,6 +75,8 @@ libcommon_la_SOURCES = \ string_calls.h \ thread_calls.c \ thread_calls.h \ + timers.c \ + timers.h \ trans.c \ trans.h \ unicode_defines.h \ diff --git a/common/timers.c b/common/timers.c new file mode 100644 index 00000000..0016e7b7 --- /dev/null +++ b/common/timers.c @@ -0,0 +1,94 @@ +/** + * xrdp: A Remote Desktop Protocol server. + * + * Copyright (C) Jay Sorg 2004-2025 + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + * + */ + +/** + * @file common/timers.h + * @brief Timers and related functions (declarations) + * @author Matt Burt + */ + +#if defined(HAVE_CONFIG_H) +#include "config_ac.h" +#endif + +#include "os_calls.h" +#include "timers.h" + +struct timers_oneshot +{ + unsigned int add_time; // Time event was added from_g_get_elapsed_ms() + int trigger_time; +}; + +/******************************************************************************/ +struct timers_oneshot * +timers_oneshot_init(int ms) +{ + struct timers_oneshot *t = (struct timers_oneshot *)malloc(sizeof(*t)); + if (t != NULL) + { + t->add_time = g_get_elapsed_ms(); + t->trigger_time = (ms <= 0) ? 0 : ms; + } + return t; +} + +/******************************************************************************/ +int +timers_oneshot_get_remaining(struct timers_oneshot *timer, + unsigned int now) +{ + int rv = -1; + if (timer != NULL) + { + if (timer->trigger_time == 0) + { + rv = 0; + } + else + { + rv = timer->trigger_time - (int)(now - timer->add_time); + if (rv <= 0) + { + rv = 0; + // (pathological) Make sure the timer doesn't stop + // triggering if it isn't attended to for the rollover + // period (~20 days for a 32-bit timer). + timer->trigger_time = 0; + } + } + } + + return rv; +} + +/******************************************************************************/ +void +timers_oneshot_update_poll(struct timers_oneshot *timer, unsigned int now, + int *timeout) +{ + if (timer != NULL && timeout != NULL) + { + int remaining = timers_oneshot_get_remaining(timer, now); + if (*timeout < 0 || *timeout > remaining) + { + *timeout = remaining; + } + } +} diff --git a/common/timers.h b/common/timers.h new file mode 100644 index 00000000..e7b34256 --- /dev/null +++ b/common/timers.h @@ -0,0 +1,74 @@ +/** + * xrdp: A Remote Desktop Protocol server. + * + * Copyright (C) Jay Sorg 2004-2025 + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + * + */ + +/** + * @file common/timers.h + * @brief Timers and related functions (declarations) + * @author Matt Burt + */ + +#ifndef TIMERS_H +#define TIMERS_H + +#include "arch.h" + +struct timers_oneshot; + +/** + * Initialise a one-shot timer + * @param ms Milliseconds until timer fires (>= 0) + * @return pointer to timer + * + * Returns NULL if no memory. + * + * When the timer is no longer required, it can simply be passed to free() + */ +struct timers_oneshot * +timers_oneshot_init(int ms); + +/** + * Return ms remaining on a one-shot timer + * @param timer pointer to timer (or NULL) + * @param now Value of g_get_elapsed_ms() + * @return remaining ms + * + * Once this routine has returned 0 for a particular timer, it will never + * return anything else. Don't pass anything to 'now' apart from a recent + * value from g_get_elapsed_ms() + * + * If the timer is NULL, -1 is returned. + */ +int +timers_oneshot_get_remaining(struct timers_oneshot *timer, + unsigned int now); + +/** + * Variant of timers_oneshot_get_remaining() for g_obj_wait() + * @param timer pointer to timer (or NULL) + * @param now Value of g_get_elapsed_ms() + * @param[in,out] poll timeout + * + * Use this to update a timeout passed to g_obj_wait() (or poll()). The + * timeout is updated if the timer will fire before the current timeout. + */ +void +timers_oneshot_update_poll(struct timers_oneshot *timer, unsigned int now, + int *timeout); + +#endif // TIMERS_H diff --git a/tests/common/Makefile.am b/tests/common/Makefile.am index 2cd7c5fa..a4bb1325 100644 --- a/tests/common/Makefile.am +++ b/tests/common/Makefile.am @@ -26,7 +26,8 @@ test_common_SOURCES = \ test_ssl_calls.c \ test_base64.c \ test_guid.c \ - test_scancode.c + test_scancode.c \ + test_timers.c test_common_CFLAGS = \ @CHECK_CFLAGS@ \ diff --git a/tests/common/test_common.h b/tests/common/test_common.h index 131bfe03..9d9e1376 100644 --- a/tests/common/test_common.h +++ b/tests/common/test_common.h @@ -19,6 +19,7 @@ Suite *make_suite_test_ssl_calls(void); Suite *make_suite_test_base64(void); Suite *make_suite_test_guid(void); Suite *make_suite_test_scancode(void); +Suite *make_suite_test_timers(void); TCase *make_tcase_test_os_calls_signals(void); diff --git a/tests/common/test_common_main.c b/tests/common/test_common_main.c index de54c30e..bcf88c65 100644 --- a/tests/common/test_common_main.c +++ b/tests/common/test_common_main.c @@ -58,6 +58,7 @@ int main (void) srunner_add_suite(sr, make_suite_test_base64()); srunner_add_suite(sr, make_suite_test_guid()); srunner_add_suite(sr, make_suite_test_scancode()); + srunner_add_suite(sr, make_suite_test_timers()); srunner_set_tap(sr, "-"); /* diff --git a/tests/common/test_timers.c b/tests/common/test_timers.c new file mode 100644 index 00000000..6189ff36 --- /dev/null +++ b/tests/common/test_timers.c @@ -0,0 +1,88 @@ + +#if defined(HAVE_CONFIG_H) +#include "config_ac.h" +#endif + +#include "limits.h" +#include "os_calls.h" +#include "timers.h" + +#include "test_common.h" + +/******************************************************************************/ +START_TEST(test_timers__null_timer) +{ + unsigned int now = g_get_elapsed_ms(); + struct timers_oneshot *timer = NULL; + int v = timers_oneshot_get_remaining(timer, now); + ck_assert_int_eq(v, -1); + + // Check any value of 'v' is not changed with a NULL timer + timers_oneshot_update_poll(timer, now, &v); + ck_assert_int_eq(v, -1); + + v = 0; + timers_oneshot_update_poll(timer, now, &v); + ck_assert_int_eq(v, 0); + + v = INT_MAX; + timers_oneshot_update_poll(timer, now, &v); + ck_assert_int_eq(v, INT_MAX); +} +END_TEST + +/******************************************************************************/ +START_TEST(test_timers__two_secs) +{ +#define TOLERANCE 25 // Percent +#define HALF_WAIT 1000 // A second + struct timers_oneshot *timer = timers_oneshot_init(2 * HALF_WAIT); + ck_assert_ptr_ne(timer, NULL); + + // Wait for half the total period and check the elapsed timer is + // within limits + g_sleep(HALF_WAIT); + int remaining = timers_oneshot_get_remaining(timer, g_get_elapsed_ms()); + + ck_assert_int_ge(remaining, HALF_WAIT - (HALF_WAIT * TOLERANCE / 100)); + ck_assert_int_le(remaining, HALF_WAIT + (HALF_WAIT * TOLERANCE / 100)); + + // Wait for the rest of the period and check the timer is zero (or near it) + g_sleep(remaining); + unsigned int now = g_get_elapsed_ms(); + int v = timers_oneshot_get_remaining(timer, now); + ck_assert_int_ge(v, 0); + ck_assert_int_le(v, 1); + + // Check the timer is zero in the future + v = timers_oneshot_get_remaining(timer, now + 1000); // Second + ck_assert_int_eq(v, 0); + v = timers_oneshot_get_remaining(timer, now + 3600 * 1000); // Hour + ck_assert_int_eq(v, 0); + v = timers_oneshot_get_remaining(timer, now + 86400 * 1000); // Day + ck_assert_int_eq(v, 0); + v = timers_oneshot_get_remaining(timer, now + 7 * 86400 * 1000); // Week + ck_assert_int_eq(v, 0); + + free(timer); +#undef TOLERANCE +#undef HALF_WAIT +} +END_TEST + +/******************************************************************************/ +Suite * +make_suite_test_timers(void) +{ + Suite *s; + TCase *tc_timers; + + s = suite_create("timers"); + + tc_timers = tcase_create("timers"); + suite_add_tcase(s, tc_timers); + tcase_add_test(tc_timers, test_timers__null_timer); + tcase_add_test(tc_timers, test_timers__two_secs); + + return s; +}