diff --git a/common/Makefile.am b/common/Makefile.am index b59a69d6..9f368260 100644 --- a/common/Makefile.am +++ b/common/Makefile.am @@ -67,6 +67,8 @@ libcommon_la_SOURCES = \ rail.h \ scancode.c \ scancode.h \ + set_int.c \ + set_int.h \ ssl_calls.c \ ssl_calls.h \ string_calls.c \ diff --git a/common/set_int.c b/common/set_int.c new file mode 100644 index 00000000..94125012 --- /dev/null +++ b/common/set_int.c @@ -0,0 +1,207 @@ +/** + * xrdp: A Remote Desktop Protocol server. + * + * Copyright (C) Jay Sorg 2004-2021 + * + * 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/set_int.c + * @brief Handle a set of integer values (definitions) + */ + +#if defined(HAVE_CONFIG_H) +#include +#endif + +#include + +#include "arch.h" +#include "set_int.h" +#include "log.h" + +// Type to use for the bitmap in the set. This should be an +// optimum type for the platform, to improve search performance for +// sparse sets. The type must be unsigned for defined behaviour on +// right-shift. +typedef unsigned int word_type; +#define BITS_PER_WORD (sizeof(word_type) * 8) + +struct set_int +{ + int min; + int max; + size_t word_count; +#ifdef __cplusplus + word_type bits[1]; +#else + word_type bits[]; +#endif +}; + +/*****************************************************************************/ +struct set_int * +set_int_init(int min, int max) +{ + struct set_int *result = NULL; + if (max < min) + { + LOG(LOG_LEVEL_ERROR, "Tried to create a set with max(%d) < min(%d)", + max, min); + } + else + { + // Calculate the number of words needed for the bits; + size_t word_count = (max - min) / BITS_PER_WORD + 1; + + result = (struct set_int *)malloc( + offsetof(struct set_int, bits) + + word_count * sizeof(word_type)); + if (result == NULL) + { + LOG(LOG_LEVEL_ERROR, "Out of memory constructing a set(%d, %d)", + min, max); + } + else + { + result->min = min; + result->max = max; + result->word_count = word_count; + set_int_remove_all(result); + } + } + + return result; +} + +/*****************************************************************************/ +void +set_int_delete(struct set_int *set) +{ + free(set); +} + +/*****************************************************************************/ +void +set_int_add(struct set_int *set, int val) +{ + if (set != NULL && val >= set->min && val <= set->max) + { + size_t index = (val - set->min) / BITS_PER_WORD; + unsigned int bit = (val - set->min) % BITS_PER_WORD; + set->bits[index] |= 1 << bit; + } +} + +/*****************************************************************************/ +void +set_int_remove(struct set_int *set, int val) +{ + if (set != NULL && val >= set->min && val <= set->max) + { + size_t index = (val - set->min) / BITS_PER_WORD; + unsigned int bit = (val - set->min) % BITS_PER_WORD; + set->bits[index] &= ~(1 << bit); + } +} + +/*****************************************************************************/ +int +set_int_contains(const struct set_int *set, int val) +{ + int result = 0; + if (set != NULL && val >= set->min && val <= set->max) + { + size_t index = (val - set->min) / BITS_PER_WORD; + unsigned int bit = (val - set->min) % BITS_PER_WORD; + result = (set->bits[index] >> bit) & 1; + } + + return result; +} + +/*****************************************************************************/ +void +set_int_add_all(struct set_int *set) +{ + if (set != NULL) + { + memset(set->bits, 0xff, set->word_count * sizeof(set->bits[0])); + } +} + +/*****************************************************************************/ +void +set_int_remove_all(struct set_int *set) +{ + if (set != NULL) + { + memset(set->bits, 0, set->word_count * sizeof(set->bits[0])); + } +} + +/*****************************************************************************/ +int +set_int_get_next(const struct set_int *set, int *val) +{ + // Sanity checks + if (set == NULL || *val >= set->max) + { + return 0; + } + + // Work out the next likely value + int next = (*val < set->min) ? set->min : (*val) + 1; + + // Convert that to an index and bit + size_t index = (next - set->min) / BITS_PER_WORD; + unsigned int bit = (next - set->min) % BITS_PER_WORD; + + // Any bits left in the current word? + word_type w = set->bits[index] >> bit; + if (w == 0) + { + // Look for the next word with set bits + do + { + ++index; + if (index >= set->word_count) + { + return 0; + } + } + while (set->bits[index] == 0); + + w = set->bits[index]; + bit = 0; + } + + // If we get here, w is guaranteed to have at least one set bit + while ((w & 1) == 0) + { + w = w >> 1; + ++bit; + } + + // Now the index and bit are pointing to the + // next set bit in the bitmap + next = index * BITS_PER_WORD + bit + set->min; // Turn back into a value + int result = (next <= set->max); // Must be in range. + if (result) + { + *val = next; + } + return result;; +} diff --git a/common/set_int.h b/common/set_int.h new file mode 100644 index 00000000..580ff86b --- /dev/null +++ b/common/set_int.h @@ -0,0 +1,121 @@ +/** + * xrdp: A Remote Desktop Protocol server. + * + * Copyright (C) Jay Sorg 2004-2021 + * + * 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/set_int.h + * @brief Handle a set of integer values (declarations) + */ + +#ifndef SET_INT_H +#define SET_INT_H + +#include "arch.h" + +struct set_int; + +/** + * Construct a set capable of holding integer values + * + * At most one copy of each vlue will be held in the set + * + * @param min Lowest value which will be added to the set + * @param max highest value which will be added to the set + * + * @return new set + * + * NULL is returned for no memory, or if min > max + * + * The set is initially empty + */ +struct set_int * +set_int_init(int min, int max); + +/** + * Destroy a set + * + * @param set set to destroy + */ +void +set_int_delete(struct set_int *set); + +/** + * Adds a value to a set + * + * @param set set + * @param val value to add + * + * values outside of the initial max..max range will be silently ignored + */ +void +set_int_add(struct set_int *set, int val); + + +/** + * Removes a value from a set + * + * @param set set + * @param val value to remove + * + * It is not an error to remove a value which isn't in the set + */ +void +set_int_remove(struct set_int *set, int val); + + +/** + * Tests whether a set contains a particular value + * + * @param set set + * @param val value to test + * + * @return != 0 if the value is in the set + */ +int +set_int_contains(const struct set_int *set, int val); + +/** + * Adds all values in the range min..max to a set + * + * @param set set + */ +void +set_int_add_all(struct set_int *set); + +/** + * Removes all values in the range min..max from a set + * + * @param set set + */ +void +set_int_remove_all(struct set_int *set); + +/** + * Gets the next value from a set + * + * @param set set + * @param[in,out] val Value to search from + * @return != 0 if another value was found + * + * On success, the val parameter is replaced with the next highest + * value from the set. + */ +int +set_int_get_next(const struct set_int *set, int *val); + +#endif // SET_INT_H diff --git a/tests/common/Makefile.am b/tests/common/Makefile.am index 52de5565..2cd7c5fa 100644 --- a/tests/common/Makefile.am +++ b/tests/common/Makefile.am @@ -18,6 +18,7 @@ test_common_SOURCES = \ test_list_calls.c \ test_list16_calls.c \ test_parse.c \ + test_set_int.c \ test_string_calls.c \ test_string_calls_unicode.c \ test_os_calls.c \ diff --git a/tests/common/test_common.h b/tests/common/test_common.h index 12995411..131bfe03 100644 --- a/tests/common/test_common.h +++ b/tests/common/test_common.h @@ -11,6 +11,7 @@ Suite *make_suite_test_fifo(void); Suite *make_suite_test_list(void); Suite *make_suite_test_list16(void); Suite *make_suite_test_parse(void); +Suite *make_suite_test_set_int(void); Suite *make_suite_test_string(void); Suite *make_suite_test_string_unicode(void); Suite *make_suite_test_os_calls(void); diff --git a/tests/common/test_common_main.c b/tests/common/test_common_main.c index 6107e2b5..de54c30e 100644 --- a/tests/common/test_common_main.c +++ b/tests/common/test_common_main.c @@ -50,6 +50,7 @@ int main (void) srunner_add_suite(sr, make_suite_test_list()); srunner_add_suite(sr, make_suite_test_list16()); srunner_add_suite(sr, make_suite_test_parse()); + srunner_add_suite(sr, make_suite_test_set_int()); srunner_add_suite(sr, make_suite_test_string()); srunner_add_suite(sr, make_suite_test_string_unicode()); srunner_add_suite(sr, make_suite_test_os_calls()); diff --git a/tests/common/test_set_int.c b/tests/common/test_set_int.c new file mode 100644 index 00000000..254f1f24 --- /dev/null +++ b/tests/common/test_set_int.c @@ -0,0 +1,189 @@ + +#if defined(HAVE_CONFIG_H) +#include "config_ac.h" +#endif + +#include + +#include "set_int.h" + +#include "test_common.h" + +START_TEST(test_basic__bad_args) +{ + struct set_int *set = set_int_init(10, 1); + ck_assert_ptr_eq(set, NULL); + + // Check for no segfaults with a NULL set + set_int_add(set, 10); + set_int_remove(set, 10); + ck_assert_int_eq(set_int_contains(set, 10), 0); + set_int_add_all(set); + set_int_remove_all(set); + + int val = 0; + ck_assert_int_eq(set_int_get_next(set, &val), 0); + + set_int_delete(set); +} +END_TEST + +START_TEST(test_basic__one_to_ten) +{ + int i; + + struct set_int *set = set_int_init(1, 10); + ck_assert_ptr_ne(set, NULL); + + set_int_add(set, 0); // out-of-range + for (i = 1 ; i <= 5; ++i) + { + set_int_add(set, i); + } + set_int_add(set, 11); // out-of-range + + ck_assert_int_eq(set_int_contains(set, 0), 0); + ck_assert_int_eq(set_int_contains(set, 1), 1); + ck_assert_int_eq(set_int_contains(set, 2), 1); + ck_assert_int_eq(set_int_contains(set, 3), 1); + ck_assert_int_eq(set_int_contains(set, 4), 1); + ck_assert_int_eq(set_int_contains(set, 5), 1); + ck_assert_int_eq(set_int_contains(set, 6), 0); + ck_assert_int_eq(set_int_contains(set, 7), 0); + ck_assert_int_eq(set_int_contains(set, 8), 0); + ck_assert_int_eq(set_int_contains(set, 9), 0); + ck_assert_int_eq(set_int_contains(set, 10), 0); + ck_assert_int_eq(set_int_contains(set, 11), 0); + + set_int_delete(set); +} +END_TEST + +START_TEST(test_basic__add_remove_all) +{ + struct set_int *set = set_int_init(1, 10); + ck_assert_ptr_ne(set, NULL); + + set_int_add(set, 0); // out-of-range + set_int_add_all(set); + set_int_add(set, 11); // out-of-range + + ck_assert_int_eq(set_int_contains(set, 0), 0); + ck_assert_int_eq(set_int_contains(set, 1), 1); + ck_assert_int_eq(set_int_contains(set, 2), 1); + ck_assert_int_eq(set_int_contains(set, 3), 1); + ck_assert_int_eq(set_int_contains(set, 4), 1); + ck_assert_int_eq(set_int_contains(set, 5), 1); + ck_assert_int_eq(set_int_contains(set, 6), 1); + ck_assert_int_eq(set_int_contains(set, 7), 1); + ck_assert_int_eq(set_int_contains(set, 8), 1); + ck_assert_int_eq(set_int_contains(set, 9), 1); + ck_assert_int_eq(set_int_contains(set, 10), 1); + ck_assert_int_eq(set_int_contains(set, 11), 0); + + set_int_remove_all(set); + ck_assert_int_eq(set_int_contains(set, 1), 0); + ck_assert_int_eq(set_int_contains(set, 2), 0); + ck_assert_int_eq(set_int_contains(set, 3), 0); + ck_assert_int_eq(set_int_contains(set, 4), 0); + ck_assert_int_eq(set_int_contains(set, 5), 0); + ck_assert_int_eq(set_int_contains(set, 6), 0); + ck_assert_int_eq(set_int_contains(set, 7), 0); + ck_assert_int_eq(set_int_contains(set, 8), 0); + ck_assert_int_eq(set_int_contains(set, 9), 0); + ck_assert_int_eq(set_int_contains(set, 10), 0); + + set_int_delete(set); +} +END_TEST + +START_TEST(test_basic__single_element) +{ +#define VAL -1000 + + struct set_int *set = set_int_init(VAL, VAL); + ck_assert_ptr_ne(set, NULL); + + set_int_add(set, VAL - 1); // out-of-range + set_int_add(set, VAL); + set_int_add(set, VAL + 1); // out-of-range + + ck_assert_int_eq(set_int_contains(set, VAL - 1), 0); + ck_assert_int_eq(set_int_contains(set, VAL), 1); + ck_assert_int_eq(set_int_contains(set, VAL + 1), 0); + + set_int_add_all(set); + ck_assert_int_eq(set_int_contains(set, VAL - 1), 0); + ck_assert_int_eq(set_int_contains(set, VAL), 1); + ck_assert_int_eq(set_int_contains(set, VAL + 1), 0); + + set_int_remove(set, VAL); + ck_assert_int_eq(set_int_contains(set, VAL), 0); + + set_int_delete(set); +#undef VAL +} +END_TEST + +START_TEST(test_basic__get_next) +{ + int i; + struct set_int *set = set_int_init(0, 1000); + ck_assert_ptr_ne(set, NULL); + + for (i = 1 ; i <= 10; ++i) + { + set_int_add(set, i); + } + + set_int_add(set, 500); + + int val = INT_MIN; + ck_assert_int_eq(set_int_get_next(set, &val), 1); + ck_assert_int_eq(val, 1); + ck_assert_int_eq(set_int_get_next(set, &val), 1); + ck_assert_int_eq(val, 2); + ck_assert_int_eq(set_int_get_next(set, &val), 1); + ck_assert_int_eq(val, 3); + ck_assert_int_eq(set_int_get_next(set, &val), 1); + ck_assert_int_eq(val, 4); + ck_assert_int_eq(set_int_get_next(set, &val), 1); + ck_assert_int_eq(val, 5); + ck_assert_int_eq(set_int_get_next(set, &val), 1); + ck_assert_int_eq(val, 6); + ck_assert_int_eq(set_int_get_next(set, &val), 1); + ck_assert_int_eq(val, 7); + ck_assert_int_eq(set_int_get_next(set, &val), 1); + ck_assert_int_eq(val, 8); + ck_assert_int_eq(set_int_get_next(set, &val), 1); + ck_assert_int_eq(val, 9); + ck_assert_int_eq(set_int_get_next(set, &val), 1); + ck_assert_int_eq(val, 10); + ck_assert_int_eq(set_int_get_next(set, &val), 1); + ck_assert_int_eq(val, 500); + ck_assert_int_eq(set_int_get_next(set, &val), 0); + + set_int_delete(set); +} +END_TEST + +/******************************************************************************/ + +Suite * +make_suite_test_set_int(void) +{ + Suite *s; + TCase *tc_basic; + + s = suite_create("SetInt"); + + tc_basic = tcase_create("basic"); + suite_add_tcase(s, tc_basic); + tcase_add_test(tc_basic, test_basic__bad_args); + tcase_add_test(tc_basic, test_basic__one_to_ten); + tcase_add_test(tc_basic, test_basic__add_remove_all); + tcase_add_test(tc_basic, test_basic__single_element); + tcase_add_test(tc_basic, test_basic__get_next); + + return s; +}