--- /dev/null
+/*
+ * Copyright (C) 2021 Tobias Brunner, codelabs GmbH
+ *
+ * Permission is hereby granted, free of charge, to any person obtaining a copy
+ * of this software and associated documentation files (the "Software"), to deal
+ * in the Software without restriction, including without limitation the rights
+ * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
+ * copies of the Software, and to permit persons to whom the Software is
+ * furnished to do so, subject to the following conditions:
+ *
+ * The above copyright notice and this permission notice shall be included in
+ * all copies or substantial portions of the Software.
+ *
+ * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
+ * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
+ * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
+ * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
+ * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
+ * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
+ * THE SOFTWARE.
+ */
+
+#include "metadata_set.h"
+
+#include <collections/array.h>
+
+/**
+ * Set of metadata objects, indexed via string.
+ */
+struct metadata_set_t {
+ /** Stored metadata objects (entry_t) */
+ array_t *entries;
+};
+
+/**
+ * Stored data for each metadata object.
+ */
+typedef struct {
+ /** Key of the entry */
+ const char *key;
+ /** Stored metadata object */
+ metadata_t *data;
+} entry_t;
+
+/**
+ * Destroy the given entry.
+ */
+static void destroy_entry(entry_t *entry)
+{
+ entry->data->destroy(entry->data);
+ free((char*)entry->key);
+ free(entry);
+}
+
+/**
+ * Sort entries by key
+ */
+static int entry_sort(const void *a, const void *b, void *user)
+{
+ const entry_t *ea = a, *eb = b;
+ return strcmp(ea->key, eb->key);
+}
+
+/**
+ * Find an entry by key
+ */
+static int entry_find(const void *a, const void *b)
+{
+ return entry_sort(a, b, NULL);
+}
+
+/*
+ * Described in header
+ */
+metadata_set_t *metadata_set_create()
+{
+ metadata_set_t *set;
+
+ INIT(set);
+
+ return set;
+}
+
+/*
+ * Described in header
+ */
+void metadata_set_put(metadata_set_t *set, const char *key, metadata_t *data)
+{
+ entry_t *entry = NULL, lookup = {
+ .key = key,
+ };
+ int idx;
+
+ if (!set)
+ {
+ DESTROY_IF(data);
+ return;
+ }
+
+ idx = array_bsearch(set->entries, &lookup, entry_find, &entry);
+ if (idx != -1)
+ {
+ if (data)
+ {
+ entry->data->destroy(entry->data);
+ entry->data = data;
+ }
+ else
+ {
+ array_remove(set->entries, idx, NULL);
+ destroy_entry(entry);
+ }
+ }
+ else if (data)
+ {
+ INIT(entry,
+ .key = strdup(key),
+ .data = data,
+ );
+ array_insert_create(&set->entries, ARRAY_TAIL, entry);
+ array_sort(set->entries, entry_sort, NULL);
+ }
+}
+
+/*
+ * Described in header
+ */
+metadata_t *metadata_set_get(metadata_set_t *set, const char *key)
+{
+ entry_t *entry = NULL, lookup = {
+ .key = key,
+ };
+
+ if (set && array_bsearch(set->entries, &lookup, entry_find, &entry) != -1)
+ {
+ return entry->data;
+ }
+ return NULL;
+}
+
+/*
+ * Described in header
+ */
+metadata_set_t *metadata_set_clone(metadata_set_t *set)
+{
+ metadata_set_t *clone;
+ entry_t *entry, *entry_clone;
+ int i;
+
+ if (!set)
+ {
+ return NULL;
+ }
+
+ INIT(clone,
+ .entries = array_create(0, array_count(set->entries)),
+ );
+ for (i = 0; i < array_count(set->entries); i++)
+ {
+ array_get(set->entries, i, &entry);
+ INIT(entry_clone,
+ .key = strdup(entry->key),
+ .data = entry->data->clone(entry->data),
+ );
+ array_insert(clone->entries, i, entry_clone);
+ }
+ return clone;
+}
+
+/*
+ * Described in header
+ */
+void metadata_set_destroy(metadata_set_t *set)
+{
+ if (set)
+ {
+ array_destroy_function(set->entries, (void*)destroy_entry, NULL);
+ free(set);
+ }
+}
--- /dev/null
+/*
+ * Copyright (C) 2021 Tobias Brunner, codelabs GmbH
+ *
+ * Permission is hereby granted, free of charge, to any person obtaining a copy
+ * of this software and associated documentation files (the "Software"), to deal
+ * in the Software without restriction, including without limitation the rights
+ * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
+ * copies of the Software, and to permit persons to whom the Software is
+ * furnished to do so, subject to the following conditions:
+ *
+ * The above copyright notice and this permission notice shall be included in
+ * all copies or substantial portions of the Software.
+ *
+ * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
+ * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
+ * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
+ * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
+ * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
+ * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
+ * THE SOFTWARE.
+ */
+
+/**
+ * @defgroup metadata_set metadata_set
+ * @{ @ingroup metadata
+ */
+
+#ifndef METADATA_SET_H_
+#define METADATA_SET_H_
+
+#include "metadata.h"
+
+/**
+ * Set of metadata objects, indexed via string.
+ */
+typedef struct metadata_set_t metadata_set_t;
+
+/**
+ * Create a metadata_set_t instance.
+ */
+metadata_set_t *metadata_set_create();
+
+/**
+ * Add a metadata object with the given key to the set, replacing any previous
+ * object with the same key.
+ *
+ * @param set set to add metadata to
+ * @param key key under which to store metadata (cloned)
+ * @param data metadata object (adopted), NULL to remove/destroy
+ * existing object
+ */
+void metadata_set_put(metadata_set_t *set, const char *key, metadata_t *data);
+
+/**
+ * Retrieve the metadata object with the given key.
+ *
+ * @param set set to query
+ * @param key key of the metadata object
+ * @return metadata object, NULL if not found
+ */
+metadata_t *metadata_set_get(metadata_set_t *set, const char *key);
+
+/**
+ * Clone a complete metadata set.
+ *
+ * @param set set to clone
+ * @return cloned set
+ */
+metadata_set_t *metadata_set_clone(metadata_set_t *set);
+
+/**
+ * Destroy a metadata set, destroying all contained metadata objects.
+ *
+ * @param set set to destroy
+ */
+void metadata_set_destroy(metadata_set_t *set);
+
+#endif /** METADATA_SET_H_ @}*/
--- /dev/null
+/*
+ * Copyright (C) 2021 Tobias Brunner, codelabs GmbH
+ *
+ * Permission is hereby granted, free of charge, to any person obtaining a copy
+ * of this software and associated documentation files (the "Software"), to deal
+ * in the Software without restriction, including without limitation the rights
+ * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
+ * copies of the Software, and to permit persons to whom the Software is
+ * furnished to do so, subject to the following conditions:
+ *
+ * The above copyright notice and this permission notice shall be included in
+ * all copies or substantial portions of the Software.
+ *
+ * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
+ * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
+ * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
+ * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
+ * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
+ * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
+ * THE SOFTWARE.
+ */
+
+#include "test_suite.h"
+
+#include <metadata/metadata_set.h>
+
+START_TEST(test_destroy_null)
+{
+ metadata_set_t *set = NULL;
+ metadata_set_destroy(set);
+}
+END_TEST
+
+START_TEST(test_destroy_empty)
+{
+ metadata_set_t *set = metadata_set_create();
+ metadata_set_destroy(set);
+}
+END_TEST
+
+START_TEST(test_put_null)
+{
+ metadata_set_t *set = NULL;
+ metadata_t *metadata;
+
+ metadata = lib->metadata->create(lib->metadata, "int", 42);
+ metadata_set_put(set, "key", metadata);
+ metadata_set_put(set, "other", NULL);
+}
+END_TEST
+
+START_TEST(test_put)
+{
+ metadata_set_t *set = metadata_set_create();
+ metadata_t *metadata;
+
+ metadata = lib->metadata->create(lib->metadata, "int", 42);
+ metadata_set_put(set, "key", metadata);
+ metadata_set_destroy(set);
+}
+END_TEST
+
+START_TEST(test_get_null)
+{
+ metadata_set_t *set = NULL;
+ metadata_t *metadata;
+
+ metadata = metadata_set_get(set, "key");
+ ck_assert(!metadata);
+}
+END_TEST
+
+/**
+ * Assert that the given int metadata value is found with the given key.
+ */
+static void assert_int_value(metadata_set_t *set, const char *key, int expected)
+{
+ metadata_t *metadata;
+ int value;
+
+ metadata = metadata_set_get(set, key);
+ ck_assert(metadata);
+ metadata->get(metadata, &value);
+ ck_assert_int_eq(expected, value);
+}
+
+START_TEST(test_get)
+{
+ metadata_set_t *set = metadata_set_create();
+ metadata_t *metadata;
+
+ metadata = lib->metadata->create(lib->metadata, "int", 42);
+ metadata_set_put(set, "key", metadata);
+ assert_int_value(set, "key", 42);
+ metadata_set_destroy(set);
+}
+END_TEST
+
+START_TEST(test_get_missing)
+{
+ metadata_set_t *set = metadata_set_create();
+ metadata_t *metadata;
+
+ metadata = metadata_set_get(set, "key");
+ ck_assert(!metadata);
+
+ metadata = lib->metadata->create(lib->metadata, "int", 42);
+ metadata_set_put(set, "other", metadata);
+ metadata = metadata_set_get(set, "key");
+ ck_assert(!metadata);
+ metadata_set_destroy(set);
+}
+END_TEST
+
+START_TEST(test_get_multi)
+{
+ metadata_set_t *set = metadata_set_create();
+ metadata_t *metadata;
+
+ metadata = lib->metadata->create(lib->metadata, "int", 42);
+ metadata_set_put(set, "key", metadata);
+ metadata = lib->metadata->create(lib->metadata, "int", 0);
+ metadata_set_put(set, "other", metadata);
+ assert_int_value(set, "key", 42);
+ assert_int_value(set, "other", 0);
+ metadata_set_destroy(set);
+}
+END_TEST
+
+START_TEST(test_put_replace)
+{
+ metadata_set_t *set = metadata_set_create();
+ metadata_t *metadata;
+
+ metadata = lib->metadata->create(lib->metadata, "int", 42);
+ metadata_set_put(set, "whatever", metadata);
+ metadata = lib->metadata->create(lib->metadata, "int", 0);
+ metadata_set_put(set, "other", metadata);
+ metadata = lib->metadata->create(lib->metadata, "int", 666);
+ metadata_set_put(set, "other", metadata);
+ assert_int_value(set, "whatever", 42);
+ assert_int_value(set, "other", 666);
+ metadata_set_destroy(set);
+}
+END_TEST
+
+START_TEST(test_put_remove)
+{
+ metadata_set_t *set = metadata_set_create();
+ metadata_t *metadata;
+
+ metadata = lib->metadata->create(lib->metadata, "int", 42);
+ metadata_set_put(set, "key", metadata);
+ metadata = lib->metadata->create(lib->metadata, "int", 0);
+ metadata_set_put(set, "other", metadata);
+ metadata_set_put(set, "other", NULL);
+ assert_int_value(set, "key", 42);
+ metadata = metadata_set_get(set, "other");
+ ck_assert(!metadata);
+ metadata_set_put(set, "key", NULL);
+ metadata = metadata_set_get(set, "key");
+ ck_assert(!metadata);
+ metadata_set_destroy(set);
+}
+END_TEST
+
+START_TEST(test_put_remove_missing)
+{
+ metadata_set_t *set = metadata_set_create();
+ metadata_t *metadata;
+
+ metadata_set_put(set, "key", NULL);
+ metadata = lib->metadata->create(lib->metadata, "int", 42);
+ metadata_set_put(set, "key", metadata);
+ assert_int_value(set, "key", 42);
+ metadata_set_put(set, "key", NULL);
+ metadata = metadata_set_get(set, "key");
+ ck_assert(!metadata);
+ metadata_set_put(set, "key", NULL);
+ metadata_set_destroy(set);
+}
+END_TEST
+
+START_TEST(test_clone_null)
+{
+ metadata_set_t *set = NULL, *clone;
+
+ clone = metadata_set_clone(set);
+ ck_assert(!clone);
+}
+END_TEST
+
+START_TEST(test_clone_empty)
+{
+ metadata_set_t *set = metadata_set_create(), *clone;
+
+ clone = metadata_set_clone(set);
+ ck_assert(clone != set);
+
+ metadata_set_destroy(clone);
+ metadata_set_destroy(set);
+}
+END_TEST
+
+START_TEST(test_clone)
+{
+ metadata_set_t *set = metadata_set_create(), *clone;
+ metadata_t *metadata;
+ struct {
+ const char *key;
+ int value;
+ } expected[] = {
+ { "key", 42, },
+ { "other", 666, },
+ { "abc", 4500, },
+ };
+ int i;
+
+ for (i = 0; i < countof(expected); i++)
+ {
+ metadata = lib->metadata->create(lib->metadata, "int", expected[i].value);
+ metadata_set_put(set, expected[i].key, metadata);
+ }
+
+ clone = metadata_set_clone(set);
+ ck_assert(clone != set);
+
+ for (i = 0; i < countof(expected); i++)
+ {
+ assert_int_value(set, expected[i].key, expected[i].value);
+ assert_int_value(clone, expected[i].key, expected[i].value);
+ }
+
+ metadata_set_put(set, expected[0].key, NULL);
+ assert_int_value(clone, expected[0].key, expected[0].value);
+
+ metadata_set_destroy(clone);
+ metadata_set_destroy(set);
+}
+END_TEST
+
+Suite *metadata_set_suite_create()
+{
+ Suite *s;
+ TCase *tc;
+
+ s = suite_create("metadata_set");
+
+ tc = tcase_create("create/destroy");
+ tcase_add_test(tc, test_destroy_null);
+ tcase_add_test(tc, test_destroy_empty);
+ suite_add_tcase(s, tc);
+
+ tc = tcase_create("put/get");
+ tcase_add_test(tc, test_put_null);
+ tcase_add_test(tc, test_put);
+ tcase_add_test(tc, test_get_null);
+ tcase_add_test(tc, test_get);
+ tcase_add_test(tc, test_get_missing);
+ tcase_add_test(tc, test_get_multi);
+ tcase_add_test(tc, test_put_replace);
+ tcase_add_test(tc, test_put_remove);
+ tcase_add_test(tc, test_put_remove_missing);
+ suite_add_tcase(s, tc);
+
+ tc = tcase_create("clone");
+ tcase_add_test(tc, test_clone_null);
+ tcase_add_test(tc, test_clone_empty);
+ tcase_add_test(tc, test_clone);
+ suite_add_tcase(s, tc);
+
+ return s;
+}