From b1714cf554b33d686a4756e8f5de201a63a09d07 Mon Sep 17 00:00:00 2001 From: Cory McWilliams Date: Thu, 7 Dec 2023 02:28:49 +0000 Subject: [PATCH] I think I fixed following calculations, again. Revived the test, though it's still very not thorough. git-svn-id: https://www.unprompted.com/svn/projects/tildefriends/trunk@4662 ed5197a5-7fde-0310-b194-c3ffbd925b24 --- src/ssb.db.c | 115 ++++++++++++++++++++++++++++++++++------------ src/ssb.tests.c | 119 +++++++++++++++++++++++------------------------- 2 files changed, 141 insertions(+), 93 deletions(-) diff --git a/src/ssb.db.c b/src/ssb.db.c index ff7f50a1..876e08a0 100644 --- a/src/ssb.db.c +++ b/src/ssb.db.c @@ -1301,6 +1301,7 @@ typedef struct _following_t int depth; int ref_count; int block_ref_count; + bool populated; } following_t; static int _following_compare(const void* a, const void* b) @@ -1310,6 +1311,11 @@ static int _following_compare(const void* a, const void* b) return strcmp(ida, (*followingb)->id); } +static bool _has_following_entry(const char* id, following_t** list, int count) +{ + return bsearch(id, list, count, sizeof(following_t*), _following_compare) != 0; +} + static bool _add_following_entry(following_t*** list, int* count, following_t* add) { int index = tf_util_insert_index(add->id, *list, *count, sizeof(following_t*), _following_compare); @@ -1343,19 +1349,37 @@ static bool _remove_following_entry(following_t*** list, int* count, following_t return false; } -static following_t* _get_following(tf_ssb_t* ssb, const char* id, following_t*** following, int* following_count, int depth, int max_depth) +typedef struct _block_node_t block_node_t; + +typedef struct _block_node_t { + following_t* entry; + block_node_t* parent; +} block_node_t; + +static bool _is_blocked_by_active_blocks(const char* id, const block_node_t* blocks) +{ + for (const block_node_t* b = blocks; b; b = b->parent) + { + if (_has_following_entry(id, b->entry->blocking, b->entry->blocking_count)) + { + return true; + } + } + return false; +} + +static following_t* _make_following_node(const char* id, following_t*** following, int* following_count, block_node_t* blocks) +{ + if (_is_blocked_by_active_blocks(id, blocks)) + { + return NULL; + } int index = tf_util_insert_index(id, *following, *following_count, sizeof(following_t*), _following_compare); following_t* entry = NULL; - bool already_populated = false; if (index < *following_count && strcmp(id, (*following)[index]->id) == 0) { entry = (*following)[index]; - already_populated = entry->depth < max_depth; - if (depth < entry->depth) - { - entry->depth = depth; - } } else { @@ -1368,29 +1392,34 @@ static following_t* _get_following(tf_ssb_t* ssb, const char* id, following_t*** (*following)[index] = entry; (*following_count)++; memset(entry, 0, sizeof(*entry)); + entry->depth = INT_MAX; snprintf(entry->id, sizeof(entry->id), "%s", id); - entry->depth = depth; } + return entry; +} - if (depth < max_depth && !already_populated) +static void _populate_follows_and_blocks(tf_ssb_t* ssb, following_t* entry, following_t*** following, int* following_count, block_node_t* active_blocks) +{ + sqlite3* db = tf_ssb_acquire_db_reader(ssb); + sqlite3_stmt* statement = NULL; + if (sqlite3_prepare(db, + "SELECT json_extract(content, '$.contact') AS contact, json_extract(content, '$.following'), json_extract(content, '$.blocking') " + "FROM messages " + "WHERE contact IS NOT NULL AND author = ? AND json_extract(content, '$.type') = 'contact' " + "ORDER BY sequence", + -1, &statement, NULL) == SQLITE_OK) { - sqlite3* db = tf_ssb_acquire_db_reader(ssb); - sqlite3_stmt* statement = NULL; - if (sqlite3_prepare(db, "SELECT json_extract(content, '$.contact'), json_extract(content, '$.following'), json_extract(content, '$.blocking') FROM messages WHERE author = ? AND json_extract(content, '$.type') = 'contact' ORDER BY sequence", -1, &statement, NULL) == SQLITE_OK) + if (sqlite3_bind_text(statement, 1, entry->id, -1, NULL) == SQLITE_OK) { - if (sqlite3_bind_text(statement, 1, id, -1, NULL) == SQLITE_OK) + while (sqlite3_step(statement) == SQLITE_ROW) { - while (sqlite3_step(statement) == SQLITE_ROW) + const char* contact = (const char*)sqlite3_column_text(statement, 0); + if (sqlite3_column_type(statement, 1) != SQLITE_NULL) { - const char* contact = (const char*)sqlite3_column_text(statement, 0); - if (!contact) + bool is_following = sqlite3_column_int(statement, 1) != 0; + following_t* next = _make_following_node(contact, following, following_count, active_blocks); + if (next) { - continue; - } - if (sqlite3_column_type(statement, 1) != SQLITE_NULL) - { - bool is_following = sqlite3_column_int(statement, 1) != 0; - following_t* next = _get_following(ssb, contact, following, following_count, depth + 1, max_depth); if (is_following) { if (_add_following_entry(&entry->following, &entry->following_count, next)) @@ -1406,10 +1435,13 @@ static following_t* _get_following(tf_ssb_t* ssb, const char* id, following_t*** } } } - if (sqlite3_column_type(statement, 2) != SQLITE_NULL) + } + if (sqlite3_column_type(statement, 2) != SQLITE_NULL) + { + bool is_blocking = sqlite3_column_int(statement, 2) != 0; + following_t* next = _make_following_node(contact, following, following_count, active_blocks); + if (next) { - bool is_blocking = sqlite3_column_int(statement, 2) != 0; - following_t* next = _get_following(ssb, contact, following, following_count, depth + 1, 0 /* don't dig deeper into blocked users */); if (is_blocking) { if (_add_following_entry(&entry->blocking, &entry->blocking_count, next)) @@ -1427,11 +1459,32 @@ static following_t* _get_following(tf_ssb_t* ssb, const char* id, following_t*** } } } - sqlite3_finalize(statement); } - tf_ssb_release_db_reader(ssb, db); + sqlite3_finalize(statement); + } + tf_ssb_release_db_reader(ssb, db); +} + +static void _get_following(tf_ssb_t* ssb, following_t* entry, following_t*** following, int* following_count, int depth, int max_depth, block_node_t* active_blocks) +{ + entry->depth = tf_min(depth, entry->depth); + if (depth < max_depth && !entry->populated && !_is_blocked_by_active_blocks(entry->id, active_blocks)) + { + entry->populated = true; + _populate_follows_and_blocks(ssb, entry, following, following_count, active_blocks); + + if (depth < max_depth) + { + block_node_t blocks = { .entry = entry, .parent = active_blocks }; + for (int i = 0; i < entry->following_count; i++) + { + if (!_has_following_entry(entry->following[i]->id, entry->blocking, entry->blocking_count)) + { + _get_following(ssb, entry->following[i], following, following_count, depth + 1, max_depth, &blocks); + } + } + } } - return entry; } tf_ssb_following_t* tf_ssb_db_following_deep(tf_ssb_t* ssb, const char** ids, int count, int depth) @@ -1440,7 +1493,8 @@ tf_ssb_following_t* tf_ssb_db_following_deep(tf_ssb_t* ssb, const char** ids, in int following_count = 0; for (int i = 0; i < count; i++) { - following_t* entry = _get_following(ssb, ids[i], &following, &following_count, 0, depth); + following_t* entry = _make_following_node(ids[i], &following, &following_count, NULL); + _get_following(ssb, entry, &following, &following_count, 0, depth, NULL); entry->ref_count++; } @@ -1487,7 +1541,8 @@ const char** tf_ssb_db_following_deep_ids(tf_ssb_t* ssb, const char** ids, int c int following_count = 0; for (int i = 0; i < count; i++) { - following_t* entry = _get_following(ssb, ids[i], &following, &following_count, 0, depth); + following_t* entry = _make_following_node(ids[i], &following, &following_count, NULL); + _get_following(ssb, entry, &following, &following_count, 0, depth, NULL); entry->ref_count++; } diff --git a/src/ssb.tests.c b/src/ssb.tests.c index c1e70a5b..86f9e6bb 100644 --- a/src/ssb.tests.c +++ b/src/ssb.tests.c @@ -496,6 +496,22 @@ void tf_ssb_test_rooms(const tf_test_options_t* options) uv_loop_close(&loop); } +static void _assert_visible(tf_ssb_t* ssb, const char* id, const char* contact, bool visible) +{ + const char** ids = tf_ssb_db_following_deep_ids(ssb, &id, 1, 2); + bool found = false; + for (int i = 0; ids[i]; i++) + { + if (strcmp(ids[i], contact) == 0) + { + found = true; + break; + } + } + tf_free(ids); + assert(found == visible); +} + void tf_ssb_test_following(const tf_test_options_t* options) { tf_printf("Testing following.\n"); @@ -503,91 +519,68 @@ void tf_ssb_test_following(const tf_test_options_t* options) uv_loop_t loop = { 0 }; uv_loop_init(&loop); - tf_ssb_t* ssb0 = tf_ssb_create(&loop, NULL, ":memory:"); + unlink("out/test_db0.sqlite"); + tf_ssb_t* ssb0 = tf_ssb_create(&loop, NULL, "file:out/test_db0.sqlite"); tf_ssb_generate_keys(ssb0); - tf_ssb_t* ssb1 = tf_ssb_create(&loop, NULL, ":memory:"); - tf_ssb_generate_keys(ssb1); - - tf_ssb_t* ssb2 = tf_ssb_create(&loop, NULL, ":memory:"); - tf_ssb_generate_keys(ssb2); - - char id0[k_id_base64_len] = { 0 }; - char id1[k_id_base64_len] = { 0 }; - char id2[k_id_base64_len] = { 0 }; - tf_ssb_whoami(ssb0, id0, sizeof(id0)); - tf_ssb_whoami(ssb1, id1, sizeof(id1)); - tf_ssb_whoami(ssb2, id2, sizeof(id2)); - + char id0[k_id_base64_len] = { "@" }; + char id1[k_id_base64_len] = { "@" }; + char id2[k_id_base64_len] = { "@" }; + char id3[k_id_base64_len] = { "@" }; + char priv0b[512] = { 0 }; + char priv1b[512] = { 0 }; + char priv2b[512] = { 0 }; + char priv3b[512] = { 0 }; uint8_t priv0[512] = { 0 }; uint8_t priv1[512] = { 0 }; uint8_t priv2[512] = { 0 }; - tf_ssb_get_private_key(ssb0, priv0, sizeof(priv0)); - tf_ssb_get_private_key(ssb1, priv1, sizeof(priv1)); - tf_ssb_get_private_key(ssb2, priv2, sizeof(priv2)); + uint8_t priv3[512] = { 0 }; - JSContext* context = NULL; + tf_ssb_generate_keys_buffer(id0 + 1, sizeof(id0) - 1, priv0b, sizeof(priv0b)); + tf_ssb_generate_keys_buffer(id1 + 1, sizeof(id1) - 1, priv1b, sizeof(priv1b)); + tf_ssb_generate_keys_buffer(id2 + 1, sizeof(id2) - 1, priv2b, sizeof(priv2b)); + tf_ssb_generate_keys_buffer(id3 + 1, sizeof(id3) - 1, priv3b, sizeof(priv3b)); + tf_base64_decode(priv0b, strlen(priv0b), priv0, sizeof(priv0)); + tf_base64_decode(priv1b, strlen(priv1b), priv1, sizeof(priv1)); + tf_base64_decode(priv2b, strlen(priv2b), priv2, sizeof(priv2)); + tf_base64_decode(priv3b, strlen(priv3b), priv3, sizeof(priv3)); + + JSContext* context = tf_ssb_get_context(ssb0); JSValue message; JSValue signed_message; bool stored; - #define FOLLOW(ssb, id, priv, follow) \ - context = tf_ssb_get_context(ssb); \ + #define FOLLOW_BLOCK(id, priv, contact, follow, block) \ message = JS_NewObject(context); \ JS_SetPropertyStr(context, message, "type", JS_NewString(context, "contact")); \ - JS_SetPropertyStr(context, message, "contact", JS_NewString(context, id)); \ + JS_SetPropertyStr(context, message, "contact", JS_NewString(context, contact)); \ JS_SetPropertyStr(context, message, "following", follow ? JS_TRUE : JS_FALSE); \ - signed_message = tf_ssb_sign_message(ssb, id, priv, message); \ + JS_SetPropertyStr(context, message, "blocking", block ? JS_TRUE : JS_FALSE); \ + signed_message = tf_ssb_sign_message(ssb0, id, priv, message); \ stored = false; \ - tf_ssb_verify_strip_and_store_message(ssb, signed_message, _message_stored, &stored); \ - _wait_stored(ssb, &stored); \ + tf_ssb_verify_strip_and_store_message(ssb0, signed_message, _message_stored, &stored); \ + _wait_stored(ssb0, &stored); \ JS_FreeValue(context, signed_message); \ JS_FreeValue(context, message); \ - context = NULL -#if 1 - /* TODO: This test doesn't actually really test anything anymore. */ - #define DUMP(id, depth) -#else - #define DUMP(id, depth) \ - do \ - { \ - tf_printf("following %d:\n", depth); \ - const char** tf_ssb_get_following_deep(tf_ssb_t* ssb_param, const char** ids, int depth_param); \ - const char** f = tf_ssb_get_following_deep(ssb0, (const char*[]) { id, NULL }, depth); \ - for (const char** p = f; p && *p; p++) \ - { \ - tf_printf("* %s\n", *p); \ - } \ - tf_printf("\n"); \ - tf_free(f); \ - } \ - while (0) -#endif + FOLLOW_BLOCK(id0, priv0, id1, true, false); + FOLLOW_BLOCK(id1, priv1, id2, true, false); + FOLLOW_BLOCK(id1, priv1, id3, true, false); + _assert_visible(ssb0, id0, id0, true); + _assert_visible(ssb0, id0, id1, true); + _assert_visible(ssb0, id0, id2, true); + _assert_visible(ssb0, id0, id3, true); + FOLLOW_BLOCK(id0, priv0, id3, false, true); + _assert_visible(ssb0, id0, id0, true); + _assert_visible(ssb0, id0, id1, true); + _assert_visible(ssb0, id0, id2, true); + _assert_visible(ssb0, id0, id3, false); - FOLLOW(ssb0, id1, priv1, true); - FOLLOW(ssb1, id2, priv2, true); - FOLLOW(ssb2, id0, priv0, true); - DUMP(id0, 2); - DUMP(id1, 2); - DUMP(id2, 2); - FOLLOW(ssb0, id1, priv1, false); - //FOLLOW(ssb0, id1, priv1, true); - //FOLLOW(ssb0, id1, priv1, true); - DUMP(id0, 1); - DUMP(id1, 2); - //FOLLOW(ssb0, id1, priv1, false); - //DUMP(1); - //DUMP(1); - - #undef FOLLOW - #undef DUMP + #undef FOLLOW_BLOCK uv_run(&loop, UV_RUN_DEFAULT); tf_ssb_destroy(ssb0); - tf_ssb_destroy(ssb1); - tf_ssb_destroy(ssb2); uv_loop_close(&loop); }