diff --git a/api/cloud/oc_cloud_rd.c b/api/cloud/oc_cloud_rd.c index 4da8e2bad3..6525e5da17 100644 --- a/api/cloud/oc_cloud_rd.c +++ b/api/cloud/oc_cloud_rd.c @@ -90,16 +90,16 @@ rd_link_find_by_href(oc_link_t *head, const char *href, size_t href_size) } static oc_link_t * -rd_link_remove(oc_link_t **head, oc_link_t *l) +rd_link_remove(oc_link_t **head, const oc_link_t *l) { - if (l) { - if (l == *head) { - return rd_link_pop(head); - } - oc_list_remove((oc_list_t)*head, l); - l->next = NULL; + if (*head == NULL || l == NULL) { + return NULL; + } + + if (l == *head) { + return rd_link_pop(head); } - return l; + return oc_list_remove2((oc_list_t)*head, l); } static oc_link_t * diff --git a/util/oc_list.c b/util/oc_list.c index 4b25955603..0c7c35583b 100644 --- a/util/oc_list.c +++ b/util/oc_list.c @@ -43,67 +43,24 @@ struct list struct list *next; }; -/*---------------------------------------------------------------------------*/ -/** - * Initialize a list. - * - * This function initalizes a list. The list will be empty after this - * function has been called. - * - * \param list The list to be initialized. - */ void oc_list_init(oc_list_t list) { *list = NULL; } -/*---------------------------------------------------------------------------*/ -/** - * Get a pointer to the first element of a list. - * - * This function returns a pointer to the first element of the - * list. The element will \b not be removed from the list. - * - * \param list The list. - * \return A pointer to the first element on the list. - * - * \sa oc_list_tail() - */ + void * oc_list_head(oc_list_t list) { return *list; } -/*---------------------------------------------------------------------------*/ -/** - * Duplicate a list. - * - * This function duplicates a list by copying the list reference, but - * not the elements. - * - * \note This function does \b not copy the elements of the list, but - * merely duplicates the pointer to the first element of the list. - * - * \param dest The destination list. - * \param src The source list. - */ + void oc_list_copy(oc_list_t dest, oc_list_t src) { *dest = *src; } -/*---------------------------------------------------------------------------*/ -/** - * Get the tail of a list. - * - * This function returns a pointer to the elements following the first - * element of a list. No elements are removed by this function. - * - * \param list The list - * \return A pointer to the element after the first element on the list. - * - * \sa oc_list_head() - */ + void * oc_list_tail(oc_list_t list) { @@ -117,18 +74,7 @@ oc_list_tail(oc_list_t list) return l; } -/*---------------------------------------------------------------------------*/ -/** - * Add an item at the end of a list. - * - * This function adds an item to the end of the list. - * - * \param list The list. - * \param item A pointer to the item to be added. - * - * \sa oc_list_push() - * - */ + void oc_list_add(oc_list_t list, void *item) { @@ -141,29 +87,26 @@ oc_list_add(oc_list_t list, void *item) l->next = (struct list *)item; } } -/*---------------------------------------------------------------------------*/ -/** - * Add an item to the start of the list. - */ + void oc_list_push(oc_list_t list, void *item) { - /* Make sure not to add the same element twice */ - oc_list_remove(list, item); - ((struct list *)item)->next = (struct list *)*list; *list = item; } -/*---------------------------------------------------------------------------*/ -/** - * Remove the last object on the list. - * - * This function removes the last object on the list and returns it. - * - * \param list The list - * \return The removed object - * - */ + +void +oc_list_insert(oc_list_t list, void *previtem, void *newitem) +{ + if (previtem == NULL) { + oc_list_push(list, newitem); + return; + } + + ((struct list *)newitem)->next = ((struct list *)previtem)->next; + ((struct list *)previtem)->next = (struct list *)newitem; +} + void * oc_list_chop(oc_list_t list) { @@ -184,63 +127,25 @@ oc_list_chop(oc_list_t list) l->next = NULL; return r; } -/*---------------------------------------------------------------------------*/ -/** - * Remove the first object on a list. - * - * This function removes the first object on the list and returns a - * pointer to it. - * - * \param list The list. - * \return Pointer to the removed element of list. - */ -/*---------------------------------------------------------------------------*/ + void * oc_list_pop(oc_list_t list) { struct list *l = (struct list *)*list; if (*list != NULL) { *list = ((struct list *)*list)->next; + l->next = NULL; } return l; } -/*---------------------------------------------------------------------------*/ -/** - * Remove a specific element from a list. - * - * This function removes a specified element from the list. - * - * \param list The list. - * \param item The item that is to be removed from the list. - * - */ -/*---------------------------------------------------------------------------*/ + void oc_list_remove(oc_list_t list, const void *item) { - struct list **l; - - for (l = (struct list **)list; *l != NULL; l = &(*l)->next) { - if (*l == item) { - *l = (*l)->next; - return; - } - } + oc_list_remove2(list, item); } -/*---------------------------------------------------------------------------*/ -/** - * Remove a specific element from a list and return a pointer to the removed - * item. - * - * This function removes a specified element from the list. - * - * \param list The list. - * \param item The item that is to be removed from the list. - * \return Pointer to the removed element of list. - * - */ -/*---------------------------------------------------------------------------*/ + void * oc_list_remove2(oc_list_t list, const void *item) { @@ -252,20 +157,9 @@ oc_list_remove2(oc_list_t list, const void *item) return l2; } } - return NULL; } -/*---------------------------------------------------------------------------*/ -/** - * Get the length of a list. - * - * This function counts the number of elements on a specified list. - * - * \param list The list. - * \return The length of the list. - */ -/*---------------------------------------------------------------------------*/ int oc_list_length(oc_list_t list) { @@ -276,47 +170,20 @@ oc_list_length(oc_list_t list) return n; } -/*---------------------------------------------------------------------------*/ -/** - * \brief Insert an item after a specified item on the list - * \param list The list - * \param previtem The item after which the new item should be inserted - * \param newitem The new item that is to be inserted - * \author Adam Dunkels - * - * This function inserts an item right after a specified - * item on the list. This function is useful when using - * the list module to ordered lists. - * - * If previtem is NULL, the new item is placed at the - * start of the list. - * - */ -void -oc_list_insert(oc_list_t list, void *previtem, void *newitem) -{ - if (previtem == NULL) { - oc_list_push(list, newitem); - } else { - ((struct list *)newitem)->next = ((struct list *)previtem)->next; - ((struct list *)previtem)->next = (struct list *)newitem; +bool +oc_list_has_item(oc_list_t list, const void *item) +{ + for (struct list *l = (struct list *)*list; l != NULL; l = l->next) { + if (l == item) { + return true; + } } + return false; } -/*---------------------------------------------------------------------------*/ -/** - * \brief Get the next item following this item - * \param item A list item - * \returns A next item on the list - * - * This function takes a list item and returns the next - * item on the list, or NULL if there are no more items on - * the list. This function is used when iterating through - * lists. - */ + void * oc_list_item_next(void *item) { return item == NULL ? NULL : ((struct list *)item)->next; } -/*---------------------------------------------------------------------------*/ diff --git a/util/oc_list.h b/util/oc_list.h index 355f1a3c73..5e8a8a20ca 100644 --- a/util/oc_list.h +++ b/util/oc_list.h @@ -57,6 +57,10 @@ #ifndef OC_LIST_H #define OC_LIST_H +#include "oc_export.h" +#include "util/oc_compiler.h" +#include + #ifdef __cplusplus extern "C" { #endif @@ -82,7 +86,9 @@ extern "C" { static void *OC_LIST_CONCAT(name, _list) = NULL; \ static oc_list_t name = &OC_LIST_CONCAT(name, _list) -/** Non-static version of OC_LIST */ +/** + * Declare a linked list with a local scope. + */ #define OC_LIST_LOCAL(name) \ void *OC_LIST_CONCAT(name, _list) = NULL; \ oc_list_t name = &OC_LIST_CONCAT(name, _list) @@ -128,28 +134,195 @@ extern "C" { /** * The linked list type. - * */ typedef void **oc_list_t; -void oc_list_init(oc_list_t list); -void *oc_list_head(oc_list_t list); -void *oc_list_tail(oc_list_t list); -void *oc_list_pop(oc_list_t list); -void oc_list_push(oc_list_t list, void *item); +/** + * Initialize a list. + * + * This function initalizes a list. The list will be empty after this function + * has been called. + * + * \param list The list to be initialized. + * + * \sa OC_LIST() + * \sa OC_LIST_LOCAL() + * \sa OC_LIST_STRUCT() + */ +OC_API +void oc_list_init(oc_list_t list) OC_NONNULL(); + +/** + * Get a pointer to the first element of a list. + * + * This function returns a pointer to the first element of the + * list. The element will \b not be removed from the list. + * + * \param list The list. + * \return A pointer to the first element on the list. + * + * \sa oc_list_tail() + */ +OC_API +void *oc_list_head(oc_list_t list) OC_NONNULL(); + +/** + * Get the tail of a list. + * + * This function returns a pointer to the elements following the first + * element of a list. No elements are removed by this function. + * + * \param list The list + * \return A pointer to the element after the first element on the list. + * + * \sa oc_list_head() + */ +OC_API +void *oc_list_tail(oc_list_t list) OC_NONNULL(); + +/** + * Remove the first object on a list. + * + * This function removes the first object on the list and returns a + * pointer to it. + * + * \param list The list. + * \return Pointer to the removed element of list. + */ +OC_API +void *oc_list_pop(oc_list_t list) OC_NONNULL(); + +/** + * Remove the last object on the list. + * + * This function removes the last object on the list and returns it. + * + * \param list The list + * \return The removed object + */ +OC_API +void *oc_list_chop(oc_list_t list) OC_NONNULL(); + +/** + * Add an item to the start of the list. + * + * \param list The list. + * \param item A pointer to the item to be added. + * + * \sa oc_list_add() + * \sa oc_list_insert() + */ +OC_API +void oc_list_push(oc_list_t list, void *item) OC_NONNULL(); + +/** + * Add an item at the end of a list. + * + * This function adds an item to the end of the list. + * + * \param list The list. + * \param item A pointer to the item to be added. + * + * \sa oc_list_push() + * \sa oc_list_insert() + */ +OC_API +void oc_list_add(oc_list_t list, void *item) OC_NONNULL(); + +/** + * \brief Insert an item after a specified item on the list + * \param list The list + * \param previtem The item after which the new item should be inserted + * \param newitem The new item that is to be inserted + * \author Adam Dunkels + * + * This function inserts an item right after a specified + * item on the list. This function is useful when using + * the list module to ordered lists. + * + * If previtem is NULL, the new item is placed at the + * start of the list. + * + * \sa oc_list_add() + * \sa oc_list_push() + */ +OC_API +void oc_list_insert(oc_list_t list, void *previtem, void *newitem) + OC_NONNULL(1, 3); -void *oc_list_chop(oc_list_t list); +/** + * Remove a specific element from a list. + * + * This function removes a specified element from the list. + * + * \param list The list. + * \param item The item that is to be removed from the list. + */ +OC_API +void oc_list_remove(oc_list_t list, const void *item) OC_NONNULL(1); -void oc_list_add(oc_list_t list, void *item); -void oc_list_remove(oc_list_t list, const void *item); -void *oc_list_remove2(oc_list_t list, const void *item); +/** + * Remove a specific element from a list and return a pointer to the removed + * item. + * + * This function removes a specified element from the list. + * + * \param list The list. + * \param item The item that is to be removed from the list. + * \return Pointer to the removed element of list. + */ +OC_API +void *oc_list_remove2(oc_list_t list, const void *item) OC_NONNULL(1); -int oc_list_length(oc_list_t list); +/** + * Get the length of a list. + * + * This function counts the number of elements on a specified list. + * + * \param list The list. + * \return The length of the list. + */ +OC_API +int oc_list_length(oc_list_t list) OC_NONNULL(); -void oc_list_copy(oc_list_t dest, oc_list_t src); +/** + * Check if a list contains a specific item. + * + * \param list The list. + * \param item The item to check for. + * + * \return True if the list contains the item + * \return False if the list does not contain the item + */ +OC_API +bool oc_list_has_item(oc_list_t list, const void *item) OC_NONNULL(); -void oc_list_insert(oc_list_t list, void *previtem, void *newitem); +/** + * Duplicate a list. + * + * This function duplicates a list by copying the list reference, but + * not the elements. + * + * \note This function does \b not copy the elements of the list, but + * merely duplicates the pointer to the first element of the list. + * + * \param dest The destination list. + * \param src The source list. + */ +OC_API +void oc_list_copy(oc_list_t dest, oc_list_t src) OC_NONNULL(); +/** + * \brief Get the next item following this item + * \param item A list item + * \returns A next item on the list + * + * This function takes a list item and returns the next + * item on the list, or NULL if there are no more items on + * the list. This function is used when iterating through + * lists. + */ +OC_API void *oc_list_item_next(void *item); #ifdef __cplusplus diff --git a/util/unittest/listtest.cpp b/util/unittest/listtest.cpp new file mode 100644 index 0000000000..9cab93cd1c --- /dev/null +++ b/util/unittest/listtest.cpp @@ -0,0 +1,320 @@ +/**************************************************************************** + * + * Copyright (c) 2023 plgd.dev s.r.o. + * + * 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. + * + ****************************************************************************/ + +#include "util/oc_list.h" + +#include +#include +#include + +struct TestListItem +{ + struct TestListItem *next; + std::string name; +}; + +class TestList : public testing::Test { +public: + static TestListItem *makeListItem(std::string_view name) + { + auto *item = new TestListItem(); + item->name = name; + return item; + } + + template + static void addToList(oc_list_t list, Ts... args) + { + (oc_list_add(list, makeListItem(args)), ...); + } + + template + static void pushToList(oc_list_t list, Ts... args) + { + (oc_list_push(list, makeListItem(args)), ...); + } + + static void clearList(oc_list_t list) + { + TestListItem *item = nullptr; + while ((item = static_cast(oc_list_pop(list)))) { + delete item; + } + } +}; + +TEST_F(TestList, Head) +{ + OC_LIST_LOCAL(list); + oc_list_init(list); + + EXPECT_EQ(nullptr, oc_list_head(list)); + + addToList(list, "a", "b", "c"); + const auto *item = static_cast(oc_list_head(list)); + EXPECT_STREQ("a", item->name.c_str()); + + clearList(list); +} + +TEST_F(TestList, Tail) +{ + OC_LIST_LOCAL(list); + oc_list_init(list); + + EXPECT_EQ(nullptr, oc_list_tail(list)); + + addToList(list, "a", "b", "c"); + const auto *item = static_cast(oc_list_tail(list)); + EXPECT_STREQ("c", item->name.c_str()); + + clearList(list); +} + +TEST_F(TestList, Length) +{ + OC_LIST_LOCAL(list); + oc_list_init(list); + + EXPECT_EQ(0, oc_list_length(list)); + + addToList(list, "a", "b", "c"); + EXPECT_EQ(3, oc_list_length(list)); + + clearList(list); +} + +TEST_F(TestList, Add) +{ + OC_LIST_LOCAL(list); + oc_list_init(list); + + addToList(list, "a", "b", "c"); + EXPECT_EQ(3, oc_list_length(list)); + auto *item1 = static_cast(oc_list_head(list)); + EXPECT_STREQ("a", item1->name.c_str()); + auto *item2 = static_cast(oc_list_item_next(item1)); + EXPECT_STREQ("b", item2->name.c_str()); + auto *item3 = static_cast(oc_list_item_next(item2)); + EXPECT_STREQ("c", item3->name.c_str()); + auto *item4 = oc_list_item_next(item3); + EXPECT_EQ(nullptr, item4); + + clearList(list); +} + +TEST_F(TestList, Push) +{ + OC_LIST_LOCAL(list); + oc_list_init(list); + + pushToList(list, "a", "b", "c"); + EXPECT_EQ(3, oc_list_length(list)); + auto *item1 = static_cast(oc_list_head(list)); + EXPECT_STREQ("c", item1->name.c_str()); + auto *item2 = static_cast(oc_list_item_next(item1)); + EXPECT_STREQ("b", item2->name.c_str()); + auto *item3 = static_cast(oc_list_item_next(item2)); + EXPECT_STREQ("a", item3->name.c_str()); + auto *item4 = oc_list_item_next(item3); + EXPECT_EQ(nullptr, item4); + + clearList(list); +} + +TEST_F(TestList, Insert) +{ + OC_LIST_LOCAL(list); + oc_list_init(list); + + auto *item1 = TestList::makeListItem("a"); + oc_list_insert(list, nullptr, item1); + EXPECT_EQ(1, oc_list_length(list)); + + auto *item2 = TestList::makeListItem("b"); + oc_list_insert(list, item1, item2); + EXPECT_EQ(2, oc_list_length(list)); + EXPECT_EQ(item1, oc_list_head(list)); + EXPECT_EQ(item2, oc_list_tail(list)); + + auto *item3 = TestList::makeListItem("c"); + oc_list_insert(list, item1, item3); + EXPECT_EQ(3, oc_list_length(list)); + EXPECT_EQ(item1, oc_list_head(list)); + EXPECT_EQ(item3, oc_list_item_next(item1)); + EXPECT_EQ(item2, oc_list_item_next(item3)); + + clearList(list); +} + +TEST_F(TestList, Chop) +{ + OC_LIST_LOCAL(list); + oc_list_init(list); + + addToList(list, "a", "b", "c"); + EXPECT_EQ(3, oc_list_length(list)); + auto *item1 = static_cast(oc_list_chop(list)); + EXPECT_STREQ("c", item1->name.c_str()); + delete item1; + EXPECT_EQ(2, oc_list_length(list)); + auto *item2 = static_cast(oc_list_chop(list)); + EXPECT_STREQ("b", item2->name.c_str()); + EXPECT_EQ(1, oc_list_length(list)); + delete item2; + auto *item3 = static_cast(oc_list_chop(list)); + EXPECT_STREQ("a", item3->name.c_str()); + delete item3; + EXPECT_EQ(0, oc_list_length(list)); + auto *item4 = static_cast(oc_list_chop(list)); + EXPECT_EQ(nullptr, item4); + + clearList(list); +} + +TEST_F(TestList, Pop) +{ + OC_LIST_LOCAL(list); + oc_list_init(list); + + addToList(list, "a", "b", "c"); + EXPECT_EQ(3, oc_list_length(list)); + auto *item1 = static_cast(oc_list_pop(list)); + EXPECT_STREQ("a", item1->name.c_str()); + delete item1; + EXPECT_EQ(2, oc_list_length(list)); + auto *item2 = static_cast(oc_list_pop(list)); + EXPECT_STREQ("b", item2->name.c_str()); + EXPECT_EQ(1, oc_list_length(list)); + delete item2; + auto *item3 = static_cast(oc_list_pop(list)); + EXPECT_STREQ("c", item3->name.c_str()); + delete item3; + EXPECT_EQ(0, oc_list_length(list)); + auto *item4 = static_cast(oc_list_pop(list)); + EXPECT_EQ(nullptr, item4); + + clearList(list); +} + +TEST_F(TestList, Remove) +{ + OC_LIST_LOCAL(list); + oc_list_init(list); + + addToList(list, "a", "b", "c"); + EXPECT_EQ(3, oc_list_length(list)); + auto *item1 = static_cast(oc_list_head(list)); + auto *item2 = static_cast(oc_list_item_next(item1)); + auto *item3 = static_cast(oc_list_item_next(item2)); + + TestListItem item4{}; + oc_list_remove(list, &item4); + EXPECT_EQ(3, oc_list_length(list)); + + oc_list_remove(list, item2); + delete item2; + EXPECT_EQ(2, oc_list_length(list)); + EXPECT_EQ(item1, oc_list_head(list)); + EXPECT_EQ(item3, oc_list_item_next(item1)); + EXPECT_EQ(item3, oc_list_tail(list)); + EXPECT_EQ(nullptr, oc_list_item_next(item3)); + + oc_list_remove(list, item1); + delete item1; + EXPECT_EQ(1, oc_list_length(list)); + EXPECT_EQ(item3, oc_list_head(list)); + EXPECT_EQ(item3, oc_list_tail(list)); + + clearList(list); +} + +TEST_F(TestList, Remove2) +{ + OC_LIST_LOCAL(list); + oc_list_init(list); + + addToList(list, "a", "b", "c"); + EXPECT_EQ(3, oc_list_length(list)); + auto *item1 = static_cast(oc_list_head(list)); + auto *item2 = static_cast(oc_list_item_next(item1)); + auto *item3 = static_cast(oc_list_item_next(item2)); + + delete static_cast(oc_list_remove2(list, item2)); + EXPECT_EQ(2, oc_list_length(list)); + EXPECT_EQ(item1, oc_list_head(list)); + EXPECT_EQ(item3, oc_list_item_next(item1)); + EXPECT_EQ(item3, oc_list_tail(list)); + EXPECT_EQ(nullptr, oc_list_item_next(item3)); + + delete static_cast(oc_list_remove2(list, item1)); + EXPECT_EQ(1, oc_list_length(list)); + EXPECT_EQ(item3, oc_list_head(list)); + EXPECT_EQ(item3, oc_list_tail(list)); + + clearList(list); +} + +TEST_F(TestList, HasItem) +{ + OC_LIST_LOCAL(list); + oc_list_init(list); + + auto *item1 = TestList::makeListItem("a"); + EXPECT_FALSE(oc_list_has_item(list, item1)); + + oc_list_add(list, item1); + EXPECT_TRUE(oc_list_has_item(list, item1)); + + clearList(list); +} + +TEST_F(TestList, Copy) +{ + OC_LIST_LOCAL(list); + oc_list_init(list); + + addToList(list, "a", "b", "c", "d"); + EXPECT_EQ(4, oc_list_length(list)); + + OC_LIST_LOCAL(list2); + oc_list_copy(list2, list); + EXPECT_EQ(4, oc_list_length(list2)); + + auto *item4 = static_cast(oc_list_chop(list)); + EXPECT_FALSE(oc_list_has_item(list, item4)); + EXPECT_FALSE(oc_list_has_item(list2, item4)); + delete item4; + + // since "copy" merely copies the head, poping the head should affect both + // lists and after the operation the second list should contain only the + // popped item + const auto *item1 = static_cast(oc_list_pop(list)); + EXPECT_FALSE(oc_list_has_item(list, item1)); + EXPECT_EQ(2, oc_list_length(list)); + EXPECT_TRUE(oc_list_has_item(list2, item1)); + EXPECT_EQ(1, oc_list_length(list2)); + + clearList(list2); + clearList(list); +} + +TEST_F(TestList, Next) +{ + EXPECT_EQ(nullptr, oc_list_item_next(nullptr)); +}