From 8b2a5f830a1bc6e3cea40726b74e4ad9e5a3d506 Mon Sep 17 00:00:00 2001 From: Carl Niklas Rydberg Date: Sun, 21 Dec 2025 08:44:04 +0100 Subject: [PATCH] Implement TGK store API and in-memory adapter with tests --- CMakeLists.txt | 10 + include/amduat/tgk/store.h | 170 +++++ include/amduat/tgk/tgk_store_mem.h | 36 ++ src/adapters/tgk_store_mem/tgk_store_mem.c | 694 ++++++++++++++++++++- src/tgk_stack/store/store.c | 146 +++++ tests/tgk/test_tgk_store_mem.c | 433 +++++++++++++ 6 files changed, 1488 insertions(+), 1 deletion(-) create mode 100644 tests/tgk/test_tgk_store_mem.c diff --git a/CMakeLists.txt b/CMakeLists.txt index be4f9d5..559ca17 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -241,6 +241,16 @@ target_link_libraries(amduat_test_tgk1_edge ) add_test(NAME tgk1_edge COMMAND amduat_test_tgk1_edge) +add_executable(amduat_test_tgk_store_mem tests/tgk/test_tgk_store_mem.c) +target_include_directories(amduat_test_tgk_store_mem + PRIVATE ${AMDUAT_INTERNAL_DIR} + PRIVATE ${AMDUAT_INCLUDE_DIR} +) +target_link_libraries(amduat_test_tgk_store_mem + PRIVATE amduat_tgk_store_mem +) +add_test(NAME tgk_store_mem COMMAND amduat_test_tgk_store_mem) + add_executable(amduat_test_pel_program_dag_exec tests/pel/test_pel_program_dag_exec.c) target_include_directories(amduat_test_pel_program_dag_exec diff --git a/include/amduat/tgk/store.h b/include/amduat/tgk/store.h index e69de29..650c26c 100644 --- a/include/amduat/tgk/store.h +++ b/include/amduat/tgk/store.h @@ -0,0 +1,170 @@ +#ifndef AMDUAT_TGK_STORE_H +#define AMDUAT_TGK_STORE_H + +#include "amduat/asl/core.h" +#include "amduat/asl/store.h" +#include "amduat/tgk/core.h" + +#include +#include +#include + +#ifdef __cplusplus +extern "C" { +#endif + +typedef enum { + GS_ERR_NOT_EDGE = 1, + GS_ERR_ARTIFACT_ERROR = 2, + GS_ERR_UNSUPPORTED = 3, + GS_ERR_INTEGRITY = 4 +} amduat_tgk_graph_error_t; + +typedef struct { + amduat_tgk_edge_type_id_t *types; + size_t types_len; +} amduat_tgk_edge_type_filter_t; + +typedef enum { + AMDUAT_TGK_GRAPH_DIR_OUT = 1, + AMDUAT_TGK_GRAPH_DIR_IN = 2, + AMDUAT_TGK_GRAPH_DIR_BOTH = 3 +} amduat_tgk_graph_direction_t; + +typedef struct { + amduat_reference_t edge_ref; + amduat_tgk_edge_body_t body; +} amduat_tgk_graph_edge_view_t; + +typedef struct { + amduat_tgk_graph_edge_view_t *edges; + size_t len; +} amduat_tgk_graph_edge_view_list_t; + +typedef struct { + amduat_reference_t *nodes; + size_t len; +} amduat_tgk_node_list_t; + +typedef struct { + amduat_tgk_graph_edge_view_list_t edges; + amduat_octets_t next_page_token; + bool has_next_page; +} amduat_tgk_graph_scan_result_t; + +typedef struct { + amduat_asl_encoding_profile_id_t encoding_profile; + amduat_hash_id_t hash_id; +} amduat_tgk_identity_domain_t; + +typedef struct { + amduat_tgk_identity_domain_t *domains; + size_t domains_len; +} amduat_tgk_id_space_config_t; + +typedef struct { + amduat_octets_t description; +} amduat_tgk_artifact_scope_t; + +typedef struct { + uint32_t *edge_tags; + size_t edge_tags_len; + amduat_tgk_edge_type_id_t *edge_types; + size_t edge_types_len; + amduat_asl_encoding_profile_id_t *encodings; + size_t encodings_len; +} amduat_tgk_profile_set_t; + +typedef struct { + amduat_tgk_id_space_config_t id_space; + amduat_tgk_artifact_scope_t artifact_scope; + amduat_tgk_profile_set_t tgk_profiles; +} amduat_tgk_store_config_t; + +typedef struct { + bool (*get_config)(void *ctx, amduat_tgk_store_config_t *out_config); + amduat_tgk_graph_error_t (*resolve_edge)(void *ctx, + amduat_reference_t ref, + amduat_tgk_edge_body_t *out_body); + bool (*edges_from)(void *ctx, + amduat_reference_t node, + amduat_tgk_edge_type_filter_t type_filter, + amduat_tgk_graph_edge_view_list_t *out_edges); + bool (*edges_to)(void *ctx, + amduat_reference_t node, + amduat_tgk_edge_type_filter_t type_filter, + amduat_tgk_graph_edge_view_list_t *out_edges); + bool (*edges_incident)(void *ctx, + amduat_reference_t node, + amduat_tgk_edge_type_filter_t type_filter, + amduat_tgk_graph_edge_view_list_t *out_edges); + bool (*scan_edges)(void *ctx, + amduat_tgk_edge_type_filter_t type_filter, + amduat_octets_t page_token, + bool has_page_token, + amduat_tgk_graph_scan_result_t *out_scan); + bool (*neighbors)(void *ctx, + amduat_reference_t node, + amduat_tgk_edge_type_filter_t type_filter, + amduat_tgk_graph_direction_t direction, + amduat_tgk_node_list_t *out_nodes); +} amduat_tgk_store_ops_t; + +typedef struct { + amduat_tgk_store_config_t config; + amduat_tgk_store_ops_t ops; + void *ctx; +} amduat_tgk_store_t; + +void amduat_tgk_store_init(amduat_tgk_store_t *store, + amduat_tgk_store_config_t config, + amduat_tgk_store_ops_t ops, + void *ctx); + +bool amduat_tgk_store_get_config(amduat_tgk_store_t *store, + amduat_tgk_store_config_t *out_config); + +amduat_tgk_graph_error_t amduat_tgk_store_resolve_edge( + amduat_tgk_store_t *store, + amduat_reference_t ref, + amduat_tgk_edge_body_t *out_body); + +bool amduat_tgk_store_edges_from(amduat_tgk_store_t *store, + amduat_reference_t node, + amduat_tgk_edge_type_filter_t type_filter, + amduat_tgk_graph_edge_view_list_t *out_edges); + +bool amduat_tgk_store_edges_to(amduat_tgk_store_t *store, + amduat_reference_t node, + amduat_tgk_edge_type_filter_t type_filter, + amduat_tgk_graph_edge_view_list_t *out_edges); + +bool amduat_tgk_store_edges_incident( + amduat_tgk_store_t *store, + amduat_reference_t node, + amduat_tgk_edge_type_filter_t type_filter, + amduat_tgk_graph_edge_view_list_t *out_edges); + +bool amduat_tgk_store_scan_edges(amduat_tgk_store_t *store, + amduat_tgk_edge_type_filter_t type_filter, + amduat_octets_t page_token, + bool has_page_token, + amduat_tgk_graph_scan_result_t *out_scan); + +bool amduat_tgk_store_neighbors(amduat_tgk_store_t *store, + amduat_reference_t node, + amduat_tgk_edge_type_filter_t type_filter, + amduat_tgk_graph_direction_t direction, + amduat_tgk_node_list_t *out_nodes); + +/* Query results are owned by the caller; free with helpers below. */ +void amduat_tgk_graph_edge_view_list_free( + amduat_tgk_graph_edge_view_list_t *list); +void amduat_tgk_node_list_free(amduat_tgk_node_list_t *list); +void amduat_tgk_graph_scan_result_free(amduat_tgk_graph_scan_result_t *scan); + +#ifdef __cplusplus +} /* extern "C" */ +#endif + +#endif /* AMDUAT_TGK_STORE_H */ diff --git a/include/amduat/tgk/tgk_store_mem.h b/include/amduat/tgk/tgk_store_mem.h index fc8bd38..10c73a7 100644 --- a/include/amduat/tgk/tgk_store_mem.h +++ b/include/amduat/tgk/tgk_store_mem.h @@ -3,4 +3,40 @@ /* In-memory TGK store adapter public API. */ +#include "amduat/tgk/store.h" + +#include +#include + +#ifdef __cplusplus +extern "C" { +#endif + +typedef struct { + amduat_reference_t ref; + amduat_artifact_t artifact; +} amduat_tgk_store_mem_artifact_t; + +typedef struct { + amduat_tgk_store_config_t config; + const amduat_tgk_store_mem_artifact_t *artifacts; + size_t artifacts_len; + amduat_tgk_graph_edge_view_t *edges; + size_t edges_len; +} amduat_tgk_store_mem_t; + +/* Artifacts are borrowed; decoded edges are owned by the store. */ +bool amduat_tgk_store_mem_init(amduat_tgk_store_mem_t *mem, + amduat_tgk_store_config_t config, + const amduat_tgk_store_mem_artifact_t *artifacts, + size_t artifacts_len); + +void amduat_tgk_store_mem_free(amduat_tgk_store_mem_t *mem); + +amduat_tgk_store_ops_t amduat_tgk_store_mem_ops(void); + +#ifdef __cplusplus +} /* extern "C" */ +#endif + #endif /* AMDUAT_TGK_TGK_STORE_MEM_H */ diff --git a/src/adapters/tgk_store_mem/tgk_store_mem.c b/src/adapters/tgk_store_mem/tgk_store_mem.c index dffde7d..54dcb23 100644 --- a/src/adapters/tgk_store_mem/tgk_store_mem.c +++ b/src/adapters/tgk_store_mem/tgk_store_mem.c @@ -1,3 +1,695 @@ #include "amduat/tgk/tgk_store_mem.h" -/* TODO: implement in-memory TGK store adapter. */ +#include "amduat/enc/tgk1_edge.h" + +#include +#include + +static bool amduat_tgk_store_mem_hash_id_supported( + const amduat_tgk_store_mem_t *mem, + amduat_hash_id_t hash_id) { + size_t i; + + if (mem == NULL) { + return false; + } + for (i = 0; i < mem->config.id_space.domains_len; ++i) { + if (mem->config.id_space.domains[i].hash_id == hash_id) { + return true; + } + } + return false; +} + +static bool amduat_tgk_store_mem_edge_tag_supported( + const amduat_tgk_store_mem_t *mem, + uint32_t tag_id) { + size_t i; + + if (mem == NULL) { + return false; + } + for (i = 0; i < mem->config.tgk_profiles.edge_tags_len; ++i) { + if (mem->config.tgk_profiles.edge_tags[i] == tag_id) { + return true; + } + } + return false; +} + +static bool amduat_tgk_store_mem_edge_type_supported( + const amduat_tgk_store_mem_t *mem, + amduat_tgk_edge_type_id_t type_id) { + size_t i; + + if (mem == NULL) { + return false; + } + for (i = 0; i < mem->config.tgk_profiles.edge_types_len; ++i) { + if (mem->config.tgk_profiles.edge_types[i] == type_id) { + return true; + } + } + return false; +} + +static bool amduat_tgk_store_mem_encoding_supported( + const amduat_tgk_store_mem_t *mem, + amduat_asl_encoding_profile_id_t profile_id) { + size_t i; + + if (mem == NULL) { + return false; + } + for (i = 0; i < mem->config.tgk_profiles.encodings_len; ++i) { + if (mem->config.tgk_profiles.encodings[i] == profile_id) { + return true; + } + } + return false; +} + +static bool amduat_tgk_store_mem_type_filter_match( + amduat_tgk_edge_type_filter_t filter, + amduat_tgk_edge_type_id_t type_id) { + size_t i; + + if (filter.types_len == 0 || filter.types == NULL) { + return true; + } + for (i = 0; i < filter.types_len; ++i) { + if (filter.types[i] == type_id) { + return true; + } + } + return false; +} + +static bool amduat_tgk_store_mem_node_in_list( + amduat_reference_t node, + const amduat_reference_t *list, + size_t len) { + size_t i; + + for (i = 0; i < len; ++i) { + if (amduat_reference_eq(node, list[i])) { + return true; + } + } + return false; +} + +static int amduat_tgk_store_mem_octets_cmp(amduat_octets_t a, + amduat_octets_t b) { + size_t min_len; + int cmp; + + min_len = a.len < b.len ? a.len : b.len; + if (min_len != 0 && a.data != NULL && b.data != NULL) { + cmp = memcmp(a.data, b.data, min_len); + if (cmp != 0) { + return cmp; + } + } else if (min_len != 0) { + return (a.data == NULL) ? -1 : 1; + } + + if (a.len < b.len) { + return -1; + } + if (a.len > b.len) { + return 1; + } + return 0; +} + +static int amduat_tgk_store_mem_ref_cmp(amduat_reference_t a, + amduat_reference_t b) { + if (a.hash_id < b.hash_id) { + return -1; + } + if (a.hash_id > b.hash_id) { + return 1; + } + return amduat_tgk_store_mem_octets_cmp(a.digest, b.digest); +} + +static int amduat_tgk_store_mem_ref_ptr_cmp(const void *a, const void *b) { + const amduat_reference_t *ref_a = (const amduat_reference_t *)a; + const amduat_reference_t *ref_b = (const amduat_reference_t *)b; + + return amduat_tgk_store_mem_ref_cmp(*ref_a, *ref_b); +} + +static int amduat_tgk_store_mem_edge_cmp(const void *a, const void *b) { + const amduat_tgk_graph_edge_view_t *edge_a = + (const amduat_tgk_graph_edge_view_t *)a; + const amduat_tgk_graph_edge_view_t *edge_b = + (const amduat_tgk_graph_edge_view_t *)b; + + return amduat_tgk_store_mem_ref_cmp(edge_a->edge_ref, edge_b->edge_ref); +} + +static bool amduat_tgk_store_mem_reference_clone(amduat_reference_t src, + amduat_reference_t *dst) { + uint8_t *digest_copy; + + if (dst == NULL) { + return false; + } + *dst = amduat_reference(src.hash_id, amduat_octets(NULL, 0)); + if (src.digest.len == 0) { + return true; + } + if (src.digest.data == NULL) { + return false; + } + digest_copy = (uint8_t *)malloc(src.digest.len); + if (digest_copy == NULL) { + return false; + } + memcpy(digest_copy, src.digest.data, src.digest.len); + dst->digest = amduat_octets(digest_copy, src.digest.len); + return true; +} + +static void amduat_tgk_store_mem_reference_free(amduat_reference_t *ref) { + if (ref == NULL) { + return; + } + free((void *)ref->digest.data); + ref->digest.data = NULL; + ref->digest.len = 0; +} + +static bool amduat_tgk_store_mem_edge_body_clone( + const amduat_tgk_edge_body_t *src, + amduat_tgk_edge_body_t *dst) { + size_t i; + + if (src == NULL || dst == NULL) { + return false; + } + memset(dst, 0, sizeof(*dst)); + dst->type = src->type; + if (src->from_len != 0) { + dst->from = + (amduat_reference_t *)calloc(src->from_len, + sizeof(amduat_reference_t)); + if (dst->from == NULL) { + return false; + } + } + for (i = 0; i < src->from_len; ++i) { + if (!amduat_tgk_store_mem_reference_clone(src->from[i], &dst->from[i])) { + dst->from_len = i; + amduat_tgk_edge_body_free(dst); + return false; + } + } + dst->from_len = src->from_len; + + if (src->to_len != 0) { + dst->to = + (amduat_reference_t *)calloc(src->to_len, + sizeof(amduat_reference_t)); + if (dst->to == NULL) { + amduat_tgk_edge_body_free(dst); + return false; + } + } + for (i = 0; i < src->to_len; ++i) { + if (!amduat_tgk_store_mem_reference_clone(src->to[i], &dst->to[i])) { + dst->to_len = i; + amduat_tgk_edge_body_free(dst); + return false; + } + } + dst->to_len = src->to_len; + + if (!amduat_tgk_store_mem_reference_clone(src->payload, &dst->payload)) { + amduat_tgk_edge_body_free(dst); + return false; + } + + return true; +} + +static bool amduat_tgk_store_mem_edge_view_clone( + const amduat_tgk_graph_edge_view_t *src, + amduat_tgk_graph_edge_view_t *dst) { + if (src == NULL || dst == NULL) { + return false; + } + memset(dst, 0, sizeof(*dst)); + if (!amduat_tgk_store_mem_reference_clone(src->edge_ref, &dst->edge_ref)) { + return false; + } + if (!amduat_tgk_store_mem_edge_body_clone(&src->body, &dst->body)) { + amduat_tgk_store_mem_reference_free(&dst->edge_ref); + return false; + } + return true; +} + +static const amduat_tgk_store_mem_artifact_t * +amduat_tgk_store_mem_lookup_artifact(const amduat_tgk_store_mem_t *mem, + amduat_reference_t ref) { + size_t i; + + if (mem == NULL) { + return NULL; + } + for (i = 0; i < mem->artifacts_len; ++i) { + if (amduat_reference_eq(mem->artifacts[i].ref, ref)) { + return &mem->artifacts[i]; + } + } + return NULL; +} + +static amduat_tgk_graph_error_t amduat_tgk_store_mem_decode_edge( + const amduat_tgk_store_mem_t *mem, + const amduat_tgk_store_mem_artifact_t *entry, + amduat_tgk_edge_body_t *out_body) { + if (out_body != NULL) { + memset(out_body, 0, sizeof(*out_body)); + } + if (mem == NULL || entry == NULL || out_body == NULL) { + return GS_ERR_UNSUPPORTED; + } + if (!entry->artifact.has_type_tag || + !amduat_tgk_store_mem_edge_tag_supported( + mem, entry->artifact.type_tag.tag_id)) { + return GS_ERR_NOT_EDGE; + } + if (!amduat_tgk_store_mem_encoding_supported(mem, TGK1_EDGE_ENC_V1)) { + return GS_ERR_UNSUPPORTED; + } + if (entry->artifact.bytes.len != 0 && + entry->artifact.bytes.data == NULL) { + return GS_ERR_INTEGRITY; + } + if (!amduat_enc_tgk1_edge_decode_v1(entry->artifact.bytes, out_body)) { + return GS_ERR_INTEGRITY; + } + if (!amduat_tgk_edge_body_has_endpoints(out_body)) { + amduat_tgk_edge_body_free(out_body); + return GS_ERR_INTEGRITY; + } + if (!amduat_tgk_store_mem_edge_type_supported(mem, out_body->type)) { + amduat_tgk_edge_body_free(out_body); + return GS_ERR_NOT_EDGE; + } + return 0; +} + +static bool amduat_tgk_store_mem_build_edge_list( + const amduat_tgk_store_mem_t *mem, + amduat_tgk_edge_type_filter_t type_filter, + const amduat_reference_t *node, + bool check_from, + bool check_to, + amduat_tgk_graph_edge_view_list_t *out_edges) { + size_t count = 0; + size_t i; + size_t idx; + + if (mem == NULL || out_edges == NULL) { + return false; + } + out_edges->edges = NULL; + out_edges->len = 0; + + for (i = 0; i < mem->edges_len; ++i) { + const amduat_tgk_graph_edge_view_t *edge = &mem->edges[i]; + bool matched = false; + if (!amduat_tgk_store_mem_type_filter_match(type_filter, edge->body.type)) { + continue; + } + if (node == NULL) { + matched = true; + } else { + if (check_from && + amduat_tgk_store_mem_node_in_list(*node, edge->body.from, + edge->body.from_len)) { + matched = true; + } + if (check_to && + amduat_tgk_store_mem_node_in_list(*node, edge->body.to, + edge->body.to_len)) { + matched = true; + } + } + if (matched) { + count++; + } + } + + if (count == 0) { + return true; + } + out_edges->edges = + (amduat_tgk_graph_edge_view_t *)calloc(count, + sizeof(amduat_tgk_graph_edge_view_t)); + if (out_edges->edges == NULL) { + return false; + } + + idx = 0; + for (i = 0; i < mem->edges_len; ++i) { + const amduat_tgk_graph_edge_view_t *edge = &mem->edges[i]; + bool matched = false; + if (!amduat_tgk_store_mem_type_filter_match(type_filter, edge->body.type)) { + continue; + } + if (node == NULL) { + matched = true; + } else { + if (check_from && + amduat_tgk_store_mem_node_in_list(*node, edge->body.from, + edge->body.from_len)) { + matched = true; + } + if (check_to && + amduat_tgk_store_mem_node_in_list(*node, edge->body.to, + edge->body.to_len)) { + matched = true; + } + } + if (matched) { + if (!amduat_tgk_store_mem_edge_view_clone(edge, + &out_edges->edges[idx])) { + out_edges->len = idx; + amduat_tgk_graph_edge_view_list_free(out_edges); + return false; + } + idx++; + } + } + out_edges->len = idx; + return true; +} + +static bool amduat_tgk_store_mem_get_config(void *ctx, + amduat_tgk_store_config_t *out_config) { + amduat_tgk_store_mem_t *mem = (amduat_tgk_store_mem_t *)ctx; + + if (mem == NULL || out_config == NULL) { + return false; + } + *out_config = mem->config; + return true; +} + +static amduat_tgk_graph_error_t amduat_tgk_store_mem_resolve_edge( + void *ctx, + amduat_reference_t ref, + amduat_tgk_edge_body_t *out_body) { + amduat_tgk_store_mem_t *mem = (amduat_tgk_store_mem_t *)ctx; + const amduat_tgk_store_mem_artifact_t *entry; + + if (out_body != NULL) { + memset(out_body, 0, sizeof(*out_body)); + } + if (mem == NULL || out_body == NULL) { + return GS_ERR_UNSUPPORTED; + } + if (!amduat_tgk_store_mem_hash_id_supported(mem, ref.hash_id)) { + return GS_ERR_UNSUPPORTED; + } + entry = amduat_tgk_store_mem_lookup_artifact(mem, ref); + if (entry == NULL) { + return GS_ERR_ARTIFACT_ERROR; + } + return amduat_tgk_store_mem_decode_edge(mem, entry, out_body); +} + +static bool amduat_tgk_store_mem_edges_from( + void *ctx, + amduat_reference_t node, + amduat_tgk_edge_type_filter_t type_filter, + amduat_tgk_graph_edge_view_list_t *out_edges) { + amduat_tgk_store_mem_t *mem = (amduat_tgk_store_mem_t *)ctx; + + return amduat_tgk_store_mem_build_edge_list(mem, type_filter, &node, true, + false, out_edges); +} + +static bool amduat_tgk_store_mem_edges_to( + void *ctx, + amduat_reference_t node, + amduat_tgk_edge_type_filter_t type_filter, + amduat_tgk_graph_edge_view_list_t *out_edges) { + amduat_tgk_store_mem_t *mem = (amduat_tgk_store_mem_t *)ctx; + + return amduat_tgk_store_mem_build_edge_list(mem, type_filter, &node, false, + true, out_edges); +} + +static bool amduat_tgk_store_mem_edges_incident( + void *ctx, + amduat_reference_t node, + amduat_tgk_edge_type_filter_t type_filter, + amduat_tgk_graph_edge_view_list_t *out_edges) { + amduat_tgk_store_mem_t *mem = (amduat_tgk_store_mem_t *)ctx; + + return amduat_tgk_store_mem_build_edge_list(mem, type_filter, &node, true, + true, out_edges); +} + +static bool amduat_tgk_store_mem_scan_edges( + void *ctx, + amduat_tgk_edge_type_filter_t type_filter, + amduat_octets_t page_token, + bool has_page_token, + amduat_tgk_graph_scan_result_t *out_scan) { + amduat_tgk_store_mem_t *mem = (amduat_tgk_store_mem_t *)ctx; + + (void)page_token; + (void)has_page_token; + if (out_scan == NULL) { + return false; + } + out_scan->edges.edges = NULL; + out_scan->edges.len = 0; + out_scan->next_page_token.data = NULL; + out_scan->next_page_token.len = 0; + out_scan->has_next_page = false; + + return amduat_tgk_store_mem_build_edge_list(mem, type_filter, NULL, false, + false, &out_scan->edges); +} + +static bool amduat_tgk_store_mem_node_list_add( + amduat_reference_t node, + amduat_reference_t **nodes, + size_t *nodes_len, + size_t *nodes_cap) { + size_t i; + size_t new_cap; + amduat_reference_t *next; + + for (i = 0; i < *nodes_len; ++i) { + if (amduat_reference_eq((*nodes)[i], node)) { + return true; + } + } + + if (*nodes_len == *nodes_cap) { + new_cap = (*nodes_cap == 0) ? 4u : (*nodes_cap * 2u); + next = (amduat_reference_t *)realloc(*nodes, + new_cap * sizeof(amduat_reference_t)); + if (next == NULL) { + return false; + } + *nodes = next; + *nodes_cap = new_cap; + } + + if (!amduat_tgk_store_mem_reference_clone(node, &(*nodes)[*nodes_len])) { + return false; + } + (*nodes_len)++; + return true; +} + +static bool amduat_tgk_store_mem_neighbors( + void *ctx, + amduat_reference_t node, + amduat_tgk_edge_type_filter_t type_filter, + amduat_tgk_graph_direction_t direction, + amduat_tgk_node_list_t *out_nodes) { + amduat_tgk_store_mem_t *mem = (amduat_tgk_store_mem_t *)ctx; + amduat_reference_t *nodes = NULL; + size_t nodes_len = 0; + size_t nodes_cap = 0; + size_t i; + size_t j; + + if (mem == NULL || out_nodes == NULL) { + return false; + } + out_nodes->nodes = NULL; + out_nodes->len = 0; + + for (i = 0; i < mem->edges_len; ++i) { + const amduat_tgk_graph_edge_view_t *edge = &mem->edges[i]; + bool use_out = false; + bool use_in = false; + + if (!amduat_tgk_store_mem_type_filter_match(type_filter, edge->body.type)) { + continue; + } + + if (direction == AMDUAT_TGK_GRAPH_DIR_OUT || + direction == AMDUAT_TGK_GRAPH_DIR_BOTH) { + if (amduat_tgk_store_mem_node_in_list(node, edge->body.from, + edge->body.from_len)) { + use_out = true; + } + } + if (direction == AMDUAT_TGK_GRAPH_DIR_IN || + direction == AMDUAT_TGK_GRAPH_DIR_BOTH) { + if (amduat_tgk_store_mem_node_in_list(node, edge->body.to, + edge->body.to_len)) { + use_in = true; + } + } + + if (use_out) { + for (j = 0; j < edge->body.to_len; ++j) { + if (!amduat_tgk_store_mem_node_list_add(edge->body.to[j], &nodes, + &nodes_len, &nodes_cap)) { + goto cleanup; + } + } + } + if (use_in) { + for (j = 0; j < edge->body.from_len; ++j) { + if (!amduat_tgk_store_mem_node_list_add(edge->body.from[j], &nodes, + &nodes_len, &nodes_cap)) { + goto cleanup; + } + } + } + } + + if (nodes_len > 1) { + qsort(nodes, nodes_len, sizeof(amduat_reference_t), + amduat_tgk_store_mem_ref_ptr_cmp); + } + + out_nodes->nodes = nodes; + out_nodes->len = nodes_len; + return true; + +cleanup: + for (i = 0; i < nodes_len; ++i) { + amduat_tgk_store_mem_reference_free(&nodes[i]); + } + free(nodes); + return false; +} + +bool amduat_tgk_store_mem_init(amduat_tgk_store_mem_t *mem, + amduat_tgk_store_config_t config, + const amduat_tgk_store_mem_artifact_t *artifacts, + size_t artifacts_len) { + size_t i; + size_t count = 0; + amduat_tgk_graph_edge_view_t *edges; + + if (mem == NULL) { + return false; + } + mem->config = config; + mem->artifacts = artifacts; + mem->artifacts_len = artifacts_len; + mem->edges = NULL; + mem->edges_len = 0; + + if (artifacts_len == 0) { + return true; + } + if (artifacts == NULL) { + return false; + } + + edges = (amduat_tgk_graph_edge_view_t *)calloc( + artifacts_len, sizeof(amduat_tgk_graph_edge_view_t)); + if (edges == NULL) { + return false; + } + + for (i = 0; i < artifacts_len; ++i) { + amduat_tgk_edge_body_t body; + amduat_tgk_graph_error_t err; + const amduat_tgk_store_mem_artifact_t *entry = &artifacts[i]; + + if (!amduat_tgk_store_mem_hash_id_supported(mem, entry->ref.hash_id)) { + continue; + } + err = amduat_tgk_store_mem_decode_edge(mem, entry, &body); + if (err != 0) { + continue; + } + if (!amduat_tgk_store_mem_reference_clone(entry->ref, + &edges[count].edge_ref)) { + amduat_tgk_edge_body_free(&body); + goto cleanup; + } + edges[count].body = body; + count++; + } + + if (count > 1) { + qsort(edges, count, sizeof(amduat_tgk_graph_edge_view_t), + amduat_tgk_store_mem_edge_cmp); + } + + mem->edges = edges; + mem->edges_len = count; + return true; + +cleanup: + for (i = 0; i < count; ++i) { + amduat_tgk_store_mem_reference_free(&edges[i].edge_ref); + amduat_tgk_edge_body_free(&edges[i].body); + } + free(edges); + mem->edges = NULL; + mem->edges_len = 0; + return false; +} + +void amduat_tgk_store_mem_free(amduat_tgk_store_mem_t *mem) { + size_t i; + + if (mem == NULL) { + return; + } + for (i = 0; i < mem->edges_len; ++i) { + amduat_tgk_store_mem_reference_free(&mem->edges[i].edge_ref); + amduat_tgk_edge_body_free(&mem->edges[i].body); + } + free(mem->edges); + mem->edges = NULL; + mem->edges_len = 0; + mem->artifacts = NULL; + mem->artifacts_len = 0; +} + +amduat_tgk_store_ops_t amduat_tgk_store_mem_ops(void) { + amduat_tgk_store_ops_t ops; + + memset(&ops, 0, sizeof(ops)); + ops.get_config = amduat_tgk_store_mem_get_config; + ops.resolve_edge = amduat_tgk_store_mem_resolve_edge; + ops.edges_from = amduat_tgk_store_mem_edges_from; + ops.edges_to = amduat_tgk_store_mem_edges_to; + ops.edges_incident = amduat_tgk_store_mem_edges_incident; + ops.scan_edges = amduat_tgk_store_mem_scan_edges; + ops.neighbors = amduat_tgk_store_mem_neighbors; + return ops; +} diff --git a/src/tgk_stack/store/store.c b/src/tgk_stack/store/store.c index e69de29..8320a6c 100644 --- a/src/tgk_stack/store/store.c +++ b/src/tgk_stack/store/store.c @@ -0,0 +1,146 @@ +#include "amduat/tgk/store.h" + +#include + +static void amduat_tgk_store_reference_free(amduat_reference_t *ref) { + if (ref == NULL) { + return; + } + free((void *)ref->digest.data); + ref->digest.data = NULL; + ref->digest.len = 0; +} + +void amduat_tgk_store_init(amduat_tgk_store_t *store, + amduat_tgk_store_config_t config, + amduat_tgk_store_ops_t ops, + void *ctx) { + if (store == NULL) { + return; + } + store->config = config; + store->ops = ops; + store->ctx = ctx; +} + +bool amduat_tgk_store_get_config(amduat_tgk_store_t *store, + amduat_tgk_store_config_t *out_config) { + if (store == NULL || store->ops.get_config == NULL) { + return false; + } + return store->ops.get_config(store->ctx, out_config); +} + +amduat_tgk_graph_error_t amduat_tgk_store_resolve_edge( + amduat_tgk_store_t *store, + amduat_reference_t ref, + amduat_tgk_edge_body_t *out_body) { + if (store == NULL || store->ops.resolve_edge == NULL) { + return GS_ERR_UNSUPPORTED; + } + return store->ops.resolve_edge(store->ctx, ref, out_body); +} + +bool amduat_tgk_store_edges_from(amduat_tgk_store_t *store, + amduat_reference_t node, + amduat_tgk_edge_type_filter_t type_filter, + amduat_tgk_graph_edge_view_list_t *out_edges) { + if (store == NULL || store->ops.edges_from == NULL) { + return false; + } + return store->ops.edges_from(store->ctx, node, type_filter, out_edges); +} + +bool amduat_tgk_store_edges_to(amduat_tgk_store_t *store, + amduat_reference_t node, + amduat_tgk_edge_type_filter_t type_filter, + amduat_tgk_graph_edge_view_list_t *out_edges) { + if (store == NULL || store->ops.edges_to == NULL) { + return false; + } + return store->ops.edges_to(store->ctx, node, type_filter, out_edges); +} + +bool amduat_tgk_store_edges_incident( + amduat_tgk_store_t *store, + amduat_reference_t node, + amduat_tgk_edge_type_filter_t type_filter, + amduat_tgk_graph_edge_view_list_t *out_edges) { + if (store == NULL || store->ops.edges_incident == NULL) { + return false; + } + return store->ops.edges_incident(store->ctx, node, type_filter, out_edges); +} + +bool amduat_tgk_store_scan_edges(amduat_tgk_store_t *store, + amduat_tgk_edge_type_filter_t type_filter, + amduat_octets_t page_token, + bool has_page_token, + amduat_tgk_graph_scan_result_t *out_scan) { + if (store == NULL || store->ops.scan_edges == NULL) { + return false; + } + return store->ops.scan_edges(store->ctx, type_filter, page_token, + has_page_token, out_scan); +} + +bool amduat_tgk_store_neighbors(amduat_tgk_store_t *store, + amduat_reference_t node, + amduat_tgk_edge_type_filter_t type_filter, + amduat_tgk_graph_direction_t direction, + amduat_tgk_node_list_t *out_nodes) { + if (store == NULL || store->ops.neighbors == NULL) { + return false; + } + return store->ops.neighbors(store->ctx, node, type_filter, direction, + out_nodes); +} + +void amduat_tgk_graph_edge_view_list_free( + amduat_tgk_graph_edge_view_list_t *list) { + size_t i; + + if (list == NULL) { + return; + } + if (list->edges == NULL) { + list->len = 0; + return; + } + for (i = 0; i < list->len; ++i) { + amduat_tgk_store_reference_free(&list->edges[i].edge_ref); + amduat_tgk_edge_body_free(&list->edges[i].body); + } + free(list->edges); + list->edges = NULL; + list->len = 0; +} + +void amduat_tgk_node_list_free(amduat_tgk_node_list_t *list) { + size_t i; + + if (list == NULL) { + return; + } + if (list->nodes == NULL) { + list->len = 0; + return; + } + for (i = 0; i < list->len; ++i) { + amduat_tgk_store_reference_free(&list->nodes[i]); + } + free(list->nodes); + list->nodes = NULL; + list->len = 0; +} + +void amduat_tgk_graph_scan_result_free(amduat_tgk_graph_scan_result_t *scan) { + if (scan == NULL) { + return; + } + amduat_tgk_graph_edge_view_list_free(&scan->edges); + free((void *)scan->next_page_token.data); + scan->next_page_token.data = NULL; + scan->next_page_token.len = 0; + scan->has_next_page = false; +} diff --git a/tests/tgk/test_tgk_store_mem.c b/tests/tgk/test_tgk_store_mem.c new file mode 100644 index 0000000..6834142 --- /dev/null +++ b/tests/tgk/test_tgk_store_mem.c @@ -0,0 +1,433 @@ +#include "amduat/enc/asl1_core.h" +#include "amduat/enc/tgk1_edge.h" +#include "amduat/hash/asl1.h" +#include "amduat/tgk/tgk_store_mem.h" + +#include +#include +#include +#include +#include + +static const uint8_t k_non_edge_bytes[] = {0xde, 0xad, 0xbe, 0xef}; + +typedef struct { + amduat_tgk_store_t store; + amduat_tgk_store_mem_t mem; + amduat_tgk_store_mem_artifact_t artifacts[5]; + amduat_octets_t edge_bytes[4]; + amduat_reference_t ref_edge1; + amduat_reference_t ref_edge2; + amduat_reference_t ref_edge3; + amduat_reference_t ref_edge_bad; + amduat_reference_t ref_non_edge; + amduat_reference_t node_a; + amduat_reference_t node_b; + amduat_reference_t node_c; + amduat_reference_t payload1; + amduat_reference_t payload2; + amduat_reference_t payload3; + amduat_reference_t payload_bad; + uint8_t digest_edge1[32]; + uint8_t digest_edge2[32]; + uint8_t digest_edge3[32]; + uint8_t digest_edge_bad[32]; + uint8_t digest_non_edge[32]; + uint8_t digest_node_a[32]; + uint8_t digest_node_b[32]; + uint8_t digest_node_c[32]; + uint8_t digest_payload1[32]; + uint8_t digest_payload2[32]; + uint8_t digest_payload3[32]; + uint8_t digest_payload_bad[32]; + amduat_tgk_identity_domain_t domains[1]; + uint32_t edge_tags[1]; + amduat_tgk_edge_type_id_t edge_types[2]; + amduat_asl_encoding_profile_id_t encodings[1]; + amduat_tgk_store_config_t config; +} test_env_t; + +static void fill_digest(uint8_t *out, uint8_t value) { + memset(out, value, 32); +} + +static amduat_reference_t make_ref(uint8_t value, uint8_t *storage) { + fill_digest(storage, value); + return amduat_reference(AMDUAT_HASH_ASL1_ID_SHA256, + amduat_octets(storage, 32)); +} + +static void free_edge_bytes(test_env_t *env) { + size_t i; + + for (i = 0; i < 4; ++i) { + free((void *)env->edge_bytes[i].data); + env->edge_bytes[i].data = NULL; + env->edge_bytes[i].len = 0; + } +} + +static bool init_env(test_env_t *env) { + amduat_tgk_edge_body_t edge1; + amduat_tgk_edge_body_t edge2; + amduat_tgk_edge_body_t edge3; + amduat_tgk_edge_body_t edge_bad; + amduat_reference_t edge1_from[1]; + amduat_reference_t edge1_to[1]; + amduat_reference_t edge2_from[1]; + amduat_reference_t edge2_to[1]; + amduat_reference_t edge3_from[1]; + amduat_reference_t edge3_to[1]; + amduat_reference_t edge_bad_from[1]; + amduat_reference_t edge_bad_to[1]; + + memset(env, 0, sizeof(*env)); + + env->domains[0].encoding_profile = AMDUAT_ENC_ASL1_CORE_V1; + env->domains[0].hash_id = AMDUAT_HASH_ASL1_ID_SHA256; + env->edge_tags[0] = TYPE_TAG_TGK1_EDGE_V1; + env->edge_types[0] = 0x10; + env->edge_types[1] = 0x20; + env->encodings[0] = TGK1_EDGE_ENC_V1; + + env->config.id_space.domains = env->domains; + env->config.id_space.domains_len = 1; + env->config.artifact_scope.description = amduat_octets(NULL, 0); + env->config.tgk_profiles.edge_tags = env->edge_tags; + env->config.tgk_profiles.edge_tags_len = 1; + env->config.tgk_profiles.edge_types = env->edge_types; + env->config.tgk_profiles.edge_types_len = 2; + env->config.tgk_profiles.encodings = env->encodings; + env->config.tgk_profiles.encodings_len = 1; + + env->ref_edge1 = make_ref(0x20, env->digest_edge1); + env->ref_edge2 = make_ref(0x10, env->digest_edge2); + env->ref_edge3 = make_ref(0x30, env->digest_edge3); + env->ref_edge_bad = make_ref(0x40, env->digest_edge_bad); + env->ref_non_edge = make_ref(0x50, env->digest_non_edge); + + env->node_a = make_ref(0xa1, env->digest_node_a); + env->node_b = make_ref(0xb1, env->digest_node_b); + env->node_c = make_ref(0xc1, env->digest_node_c); + + env->payload1 = make_ref(0xe1, env->digest_payload1); + env->payload2 = make_ref(0xe2, env->digest_payload2); + env->payload3 = make_ref(0xe3, env->digest_payload3); + env->payload_bad = make_ref(0xe4, env->digest_payload_bad); + + memset(&edge1, 0, sizeof(edge1)); + edge1.type = 0x10; + edge1_from[0] = env->node_a; + edge1.from = edge1_from; + edge1.from_len = 1; + edge1_to[0] = env->node_b; + edge1.to = edge1_to; + edge1.to_len = 1; + edge1.payload = env->payload1; + + memset(&edge2, 0, sizeof(edge2)); + edge2.type = 0x10; + edge2_from[0] = env->node_a; + edge2.from = edge2_from; + edge2.from_len = 1; + edge2_to[0] = env->node_c; + edge2.to = edge2_to; + edge2.to_len = 1; + edge2.payload = env->payload2; + + memset(&edge3, 0, sizeof(edge3)); + edge3.type = 0x20; + edge3_from[0] = env->node_b; + edge3.from = edge3_from; + edge3.from_len = 1; + edge3_to[0] = env->node_a; + edge3.to = edge3_to; + edge3.to_len = 1; + edge3.payload = env->payload3; + + memset(&edge_bad, 0, sizeof(edge_bad)); + edge_bad.type = 0x99; + edge_bad_from[0] = env->node_a; + edge_bad.from = edge_bad_from; + edge_bad.from_len = 1; + edge_bad_to[0] = env->node_b; + edge_bad.to = edge_bad_to; + edge_bad.to_len = 1; + edge_bad.payload = env->payload_bad; + + if (!amduat_enc_tgk1_edge_encode_v1(&edge1, &env->edge_bytes[0]) || + !amduat_enc_tgk1_edge_encode_v1(&edge2, &env->edge_bytes[1]) || + !amduat_enc_tgk1_edge_encode_v1(&edge3, &env->edge_bytes[2]) || + !amduat_enc_tgk1_edge_encode_v1(&edge_bad, &env->edge_bytes[3])) { + free_edge_bytes(env); + return false; + } + + env->artifacts[0].ref = env->ref_edge1; + env->artifacts[0].artifact = + amduat_artifact_with_type(env->edge_bytes[0], + amduat_type_tag(TYPE_TAG_TGK1_EDGE_V1)); + env->artifacts[1].ref = env->ref_edge2; + env->artifacts[1].artifact = + amduat_artifact_with_type(env->edge_bytes[1], + amduat_type_tag(TYPE_TAG_TGK1_EDGE_V1)); + env->artifacts[2].ref = env->ref_edge3; + env->artifacts[2].artifact = + amduat_artifact_with_type(env->edge_bytes[2], + amduat_type_tag(TYPE_TAG_TGK1_EDGE_V1)); + env->artifacts[3].ref = env->ref_edge_bad; + env->artifacts[3].artifact = + amduat_artifact_with_type(env->edge_bytes[3], + amduat_type_tag(TYPE_TAG_TGK1_EDGE_V1)); + env->artifacts[4].ref = env->ref_non_edge; + env->artifacts[4].artifact = + amduat_artifact(amduat_octets(k_non_edge_bytes, sizeof(k_non_edge_bytes))); + + if (!amduat_tgk_store_mem_init(&env->mem, env->config, env->artifacts, 5)) { + free_edge_bytes(env); + return false; + } + + amduat_tgk_store_init(&env->store, env->config, amduat_tgk_store_mem_ops(), + &env->mem); + return true; +} + +static void free_env(test_env_t *env) { + amduat_tgk_store_mem_free(&env->mem); + free_edge_bytes(env); +} + +static int test_resolve_edge_ok(const test_env_t *env) { + amduat_tgk_edge_body_t body; + amduat_tgk_graph_error_t err; + + err = amduat_tgk_store_resolve_edge((amduat_tgk_store_t *)&env->store, + env->ref_edge1, &body); + if (err != 0) { + fprintf(stderr, "resolve_edge ok failed: %d\n", err); + return 1; + } + if (body.type != 0x10 || body.from_len != 1 || body.to_len != 1) { + fprintf(stderr, "resolve_edge body mismatch\n"); + amduat_tgk_edge_body_free(&body); + return 1; + } + if (!amduat_reference_eq(body.from[0], env->node_a) || + !amduat_reference_eq(body.to[0], env->node_b)) { + fprintf(stderr, "resolve_edge endpoints mismatch\n"); + amduat_tgk_edge_body_free(&body); + return 1; + } + amduat_tgk_edge_body_free(&body); + return 0; +} + +static int test_resolve_edge_not_edge(const test_env_t *env) { + amduat_tgk_edge_body_t body; + amduat_tgk_graph_error_t err; + + err = amduat_tgk_store_resolve_edge((amduat_tgk_store_t *)&env->store, + env->ref_edge_bad, &body); + if (err != GS_ERR_NOT_EDGE) { + fprintf(stderr, "resolve_edge not_edge mismatch: %d\n", err); + return 1; + } + return 0; +} + +static int test_type_filter(const test_env_t *env) { + amduat_tgk_edge_type_id_t types[1] = {0x10}; + amduat_tgk_edge_type_filter_t filter; + amduat_tgk_graph_edge_view_list_t edges; + size_t i; + + filter.types = types; + filter.types_len = 1; + + if (!amduat_tgk_store_edges_incident((amduat_tgk_store_t *)&env->store, + env->node_a, filter, &edges)) { + fprintf(stderr, "edges_incident type filter failed\n"); + return 1; + } + if (edges.len != 2) { + fprintf(stderr, "edges_incident type filter count mismatch\n"); + amduat_tgk_graph_edge_view_list_free(&edges); + return 1; + } + for (i = 0; i < edges.len; ++i) { + if (edges.edges[i].body.type != 0x10) { + fprintf(stderr, "edges_incident type filter type mismatch\n"); + amduat_tgk_graph_edge_view_list_free(&edges); + return 1; + } + } + amduat_tgk_graph_edge_view_list_free(&edges); + return 0; +} + +static int test_ordering(const test_env_t *env) { + amduat_tgk_edge_type_filter_t filter; + amduat_tgk_graph_edge_view_list_t edges; + + filter.types = NULL; + filter.types_len = 0; + + if (!amduat_tgk_store_edges_from((amduat_tgk_store_t *)&env->store, + env->node_a, filter, &edges)) { + fprintf(stderr, "edges_from ordering failed\n"); + return 1; + } + if (edges.len != 2) { + fprintf(stderr, "edges_from ordering count mismatch\n"); + amduat_tgk_graph_edge_view_list_free(&edges); + return 1; + } + if (!amduat_reference_eq(edges.edges[0].edge_ref, env->ref_edge2) || + !amduat_reference_eq(edges.edges[1].edge_ref, env->ref_edge1)) { + fprintf(stderr, "edges_from ordering mismatch\n"); + amduat_tgk_graph_edge_view_list_free(&edges); + return 1; + } + amduat_tgk_graph_edge_view_list_free(&edges); + return 0; +} + +static int test_adjacency(const test_env_t *env) { + amduat_tgk_edge_type_filter_t filter; + amduat_tgk_graph_edge_view_list_t edges; + + filter.types = NULL; + filter.types_len = 0; + + if (!amduat_tgk_store_edges_to((amduat_tgk_store_t *)&env->store, + env->node_a, filter, &edges)) { + fprintf(stderr, "edges_to failed\n"); + return 1; + } + if (edges.len != 1 || + !amduat_reference_eq(edges.edges[0].edge_ref, env->ref_edge3)) { + fprintf(stderr, "edges_to mismatch\n"); + amduat_tgk_graph_edge_view_list_free(&edges); + return 1; + } + amduat_tgk_graph_edge_view_list_free(&edges); + + if (!amduat_tgk_store_edges_incident((amduat_tgk_store_t *)&env->store, + env->node_a, filter, &edges)) { + fprintf(stderr, "edges_incident failed\n"); + return 1; + } + if (edges.len != 3 || + !amduat_reference_eq(edges.edges[0].edge_ref, env->ref_edge2) || + !amduat_reference_eq(edges.edges[1].edge_ref, env->ref_edge1) || + !amduat_reference_eq(edges.edges[2].edge_ref, env->ref_edge3)) { + fprintf(stderr, "edges_incident mismatch\n"); + amduat_tgk_graph_edge_view_list_free(&edges); + return 1; + } + amduat_tgk_graph_edge_view_list_free(&edges); + + return 0; +} + +static int test_neighbors(const test_env_t *env) { + amduat_tgk_edge_type_filter_t filter; + amduat_tgk_node_list_t nodes; + + filter.types = NULL; + filter.types_len = 0; + + if (!amduat_tgk_store_neighbors((amduat_tgk_store_t *)&env->store, + env->node_a, filter, + AMDUAT_TGK_GRAPH_DIR_OUT, &nodes)) { + fprintf(stderr, "neighbors out failed\n"); + return 1; + } + if (nodes.len != 2 || + !amduat_reference_eq(nodes.nodes[0], env->node_b) || + !amduat_reference_eq(nodes.nodes[1], env->node_c)) { + fprintf(stderr, "neighbors out mismatch\n"); + amduat_tgk_node_list_free(&nodes); + return 1; + } + amduat_tgk_node_list_free(&nodes); + + if (!amduat_tgk_store_neighbors((amduat_tgk_store_t *)&env->store, + env->node_a, filter, + AMDUAT_TGK_GRAPH_DIR_IN, &nodes)) { + fprintf(stderr, "neighbors in failed\n"); + return 1; + } + if (nodes.len != 1 || + !amduat_reference_eq(nodes.nodes[0], env->node_b)) { + fprintf(stderr, "neighbors in mismatch\n"); + amduat_tgk_node_list_free(&nodes); + return 1; + } + amduat_tgk_node_list_free(&nodes); + + if (!amduat_tgk_store_neighbors((amduat_tgk_store_t *)&env->store, + env->node_a, filter, + AMDUAT_TGK_GRAPH_DIR_BOTH, &nodes)) { + fprintf(stderr, "neighbors both failed\n"); + return 1; + } + if (nodes.len != 2 || + !amduat_reference_eq(nodes.nodes[0], env->node_b) || + !amduat_reference_eq(nodes.nodes[1], env->node_c)) { + fprintf(stderr, "neighbors both mismatch\n"); + amduat_tgk_node_list_free(&nodes); + return 1; + } + amduat_tgk_node_list_free(&nodes); + + return 0; +} + +static int test_scan_edges(const test_env_t *env) { + amduat_tgk_edge_type_filter_t filter; + amduat_tgk_graph_scan_result_t scan; + + filter.types = NULL; + filter.types_len = 0; + + if (!amduat_tgk_store_scan_edges((amduat_tgk_store_t *)&env->store, filter, + amduat_octets(NULL, 0), false, &scan)) { + fprintf(stderr, "scan_edges failed\n"); + return 1; + } + if (scan.edges.len != 3 || + !amduat_reference_eq(scan.edges.edges[0].edge_ref, env->ref_edge2) || + !amduat_reference_eq(scan.edges.edges[1].edge_ref, env->ref_edge1) || + !amduat_reference_eq(scan.edges.edges[2].edge_ref, env->ref_edge3)) { + fprintf(stderr, "scan_edges ordering mismatch\n"); + amduat_tgk_graph_scan_result_free(&scan); + return 1; + } + amduat_tgk_graph_scan_result_free(&scan); + return 0; +} + +int main(void) { + test_env_t env; + + if (!init_env(&env)) { + fprintf(stderr, "failed to init tgk store mem test env\n"); + return 1; + } + + if (test_resolve_edge_ok(&env) != 0 || + test_resolve_edge_not_edge(&env) != 0 || + test_type_filter(&env) != 0 || + test_ordering(&env) != 0 || + test_adjacency(&env) != 0 || + test_neighbors(&env) != 0 || + test_scan_edges(&env) != 0) { + free_env(&env); + return 1; + } + + free_env(&env); + return 0; +}