From 2ac6dfde9d202b9c6573d734f818ed94abf86bfa Mon Sep 17 00:00:00 2001 From: Cory McWilliams Date: Sun, 28 Sep 2025 17:35:54 -0400 Subject: [PATCH] test: Make the testing replicating a blob check the correct client, and add another test that does it while already connected. --- src/ssb.tests.c | 176 +++++++++++++++++++++++++++++++++++++++++++++++- src/ssb.tests.h | 10 ++- src/tests.c | 1 + 3 files changed, 184 insertions(+), 3 deletions(-) diff --git a/src/ssb.tests.c b/src/ssb.tests.c index 6a81c4fa..5c42156b 100644 --- a/src/ssb.tests.c +++ b/src/ssb.tests.c @@ -1220,7 +1220,181 @@ void tf_ssb_test_replicate(const tf_test_options_t* options) tf_printf("done\n"); tf_printf("Waiting for blob.\n"); - while (!tf_ssb_db_blob_get(ssb0, blob_id, NULL, NULL)) + while (!tf_ssb_db_blob_get(ssb1, blob_id, NULL, NULL)) + { + tf_ssb_set_main_thread(ssb1, true); + uv_run(&loop, UV_RUN_ONCE); + tf_ssb_set_main_thread(ssb1, false); + } + tf_printf("done\n"); + + tf_ssb_send_close(ssb1); + + uv_close((uv_handle_t*)&idle0, NULL); + uv_close((uv_handle_t*)&idle1, NULL); + + tf_printf("final run\n"); + tf_ssb_set_main_thread(ssb0, true); + tf_ssb_set_main_thread(ssb1, true); + uv_run(&loop, UV_RUN_DEFAULT); + tf_ssb_set_main_thread(ssb0, false); + tf_ssb_set_main_thread(ssb1, false); + tf_printf("done\n"); + + tf_printf("destroy 0\n"); + tf_ssb_destroy(ssb0); + tf_printf("destroy 1\n"); + tf_ssb_destroy(ssb1); + + tf_printf("close\n"); + uv_loop_close(&loop); +} + +void tf_ssb_test_replicate_blob(const tf_test_options_t* options) +{ + tf_printf("Testing blob replication.\n"); + + uv_loop_t loop = { 0 }; + uv_loop_init(&loop); + + unlink("out/test_db0.sqlite"); + tf_ssb_t* ssb0 = tf_ssb_create(&loop, NULL, "file:out/test_db0.sqlite", NULL); + tf_ssb_register(tf_ssb_get_context(ssb0), ssb0); + unlink("out/test_db1.sqlite"); + tf_ssb_t* ssb1 = tf_ssb_create(&loop, NULL, "file:out/test_db1.sqlite", NULL); + tf_ssb_register(tf_ssb_get_context(ssb1), ssb1); + + uv_idle_t idle0 = { .data = ssb0 }; + uv_idle_init(&loop, &idle0); + uv_idle_start(&idle0, _ssb_test_idle); + + uv_idle_t idle1 = { .data = ssb1 }; + uv_idle_init(&loop, &idle1); + uv_idle_start(&idle1, _ssb_test_idle); + + test_t test = { + .ssb0 = ssb0, + .ssb1 = ssb1, + }; + + tf_ssb_add_connections_changed_callback(ssb0, _ssb_test_connections_changed, NULL, &test); + tf_ssb_add_connections_changed_callback(ssb1, _ssb_test_connections_changed, NULL, &test); + + tf_ssb_generate_keys(ssb0); + tf_ssb_generate_keys(ssb1); + + uint8_t priv0[crypto_sign_SECRETKEYBYTES] = { 0 }; + uint8_t priv1[crypto_sign_SECRETKEYBYTES] = { 0 }; + tf_ssb_get_private_key(ssb0, priv0, sizeof(priv0)); + tf_ssb_get_private_key(ssb1, priv1, sizeof(priv1)); + + char id0[k_id_base64_len] = { 0 }; + char id1[k_id_base64_len] = { 0 }; + bool b = tf_ssb_whoami(ssb0, id0, sizeof(id0)); + (void)b; + assert(b); + b = tf_ssb_whoami(ssb1, id1, sizeof(id1)); + assert(b); + tf_printf("ID %s and %s\n", id0, id1); + + char priv0_str[512] = { 0 }; + char priv1_str[512] = { 0 }; + tf_base64_encode(priv0, sizeof(priv0), priv0_str, sizeof(priv0_str)); + tf_base64_encode(priv1, sizeof(priv0), priv1_str, sizeof(priv1_str)); + tf_ssb_db_identity_add(ssb0, "test", id0 + 1, priv0_str); + tf_ssb_db_identity_add(ssb1, "test", id1 + 1, priv1_str); + + static const int k_key_count = 5; + char public[k_key_count][k_id_base64_len - 1]; + char private[k_key_count][512]; + for (int i = 0; i < k_key_count; i++) + { + tf_ssb_generate_keys_buffer(public[i], sizeof(public[i]), private[i], sizeof(private[i])); + bool added = tf_ssb_db_identity_add(ssb0, "test", public[i], private[i]); + tf_printf("%s user %d = %s private=%s\n", added ? "added" : "failed", i, public[i], private[i]); + } + + tf_printf("ssb0\n"); + tf_ssb_db_identity_visit_all(ssb0, _test_print_identity, ssb0); + tf_printf("ssb1\n"); + tf_ssb_db_identity_visit_all(ssb1, _test_print_identity, ssb1); + + tf_ssb_server_open(ssb0, 12347); + + uint8_t id0bin[k_id_bin_len]; + tf_ssb_id_str_to_bin(id0bin, id0); + tf_ssb_connect(ssb1, "127.0.0.1", 12347, id0bin, 0, NULL, NULL); + + tf_printf("Waiting for connection.\n"); + while (test.connection_count0 != 1 || test.connection_count1 != 1) + { + tf_ssb_set_main_thread(ssb0, true); + tf_ssb_set_main_thread(ssb1, true); + uv_run(&loop, UV_RUN_ONCE); + tf_ssb_set_main_thread(ssb0, false); + tf_ssb_set_main_thread(ssb1, false); + } + tf_ssb_server_close(ssb0); + + char blob_id[k_id_base64_len] = { 0 }; + const char* k_blob = "Hello, new blob!"; + b = tf_ssb_db_blob_store(ssb0, (const uint8_t*)k_blob, strlen(k_blob), blob_id, sizeof(blob_id), NULL); + assert(b); + + JSContext* context0 = tf_ssb_get_context(ssb0); + for (int i = 0; i < k_key_count - 1; i++) + { + JSValue obj = JS_NewObject(context0); + JS_SetPropertyStr(context0, obj, "type", JS_NewString(context0, "contact")); + char self[k_id_base64_len]; + snprintf(self, sizeof(self), "@%s", public[i]); + char contact[k_id_base64_len]; + snprintf(contact, sizeof(contact), "@%s", public[i + 1]); + JS_SetPropertyStr(context0, obj, "contact", JS_NewString(context0, contact)); + JS_SetPropertyStr(context0, obj, "following", JS_TRUE); + bool stored = false; + uint8_t private_bin[512] = { 0 }; + tf_base64_decode(private[i], strlen(private[i]) - strlen(".ed25519"), private_bin, sizeof(private_bin)); + tf_printf("ssb0 %s following %s\n", self, contact); + JSValue signed_message = tf_ssb_sign_message(ssb0, self, private_bin, obj, NULL, 0); + tf_ssb_verify_strip_and_store_message(ssb0, signed_message, _message_stored, &stored); + JS_FreeValue(context0, signed_message); + _wait_stored(ssb0, &stored); + JS_FreeValue(context0, obj); + + obj = JS_NewObject(context0); + JS_SetPropertyStr(context0, obj, "type", JS_NewString(context0, "post")); + JS_SetPropertyStr(context0, obj, "text", JS_NewString(context0, "Hello, world!")); + JS_SetPropertyStr(context0, obj, "arbitrary_reference", JS_NewString(context0, blob_id)); + stored = false; + signed_message = tf_ssb_sign_message(ssb0, self, private_bin, obj, NULL, 0); + tf_ssb_verify_strip_and_store_message(ssb0, signed_message, _message_stored, &stored); + JS_FreeValue(context0, signed_message); + _wait_stored(ssb0, &stored); + JS_FreeValue(context0, obj); + } + + JSContext* context1 = tf_ssb_get_context(ssb1); + { + JSValue obj = JS_NewObject(context1); + JS_SetPropertyStr(context1, obj, "type", JS_NewString(context1, "contact")); + char self[k_id_base64_len]; + tf_string_set(self, sizeof(self), id1); + char contact[k_id_base64_len]; + snprintf(contact, sizeof(contact), "@%s", public[0]); + JS_SetPropertyStr(context1, obj, "contact", JS_NewString(context1, contact)); + JS_SetPropertyStr(context1, obj, "following", JS_TRUE); + bool stored = false; + tf_printf("ssb1 %s following %s\n", self, contact); + JSValue signed_message = tf_ssb_sign_message(ssb1, self, priv1, obj, NULL, 0); + tf_ssb_verify_strip_and_store_message(ssb1, signed_message, _message_stored, &stored); + JS_FreeValue(context1, signed_message); + _wait_stored(ssb1, &stored); + JS_FreeValue(context1, obj); + } + + tf_printf("Waiting for blob.\n"); + while (!tf_ssb_db_blob_get(ssb1, blob_id, NULL, NULL)) { tf_ssb_set_main_thread(ssb1, true); uv_run(&loop, UV_RUN_ONCE); diff --git a/src/ssb.tests.h b/src/ssb.tests.h index 76d83f6d..c9ed3733 100644 --- a/src/ssb.tests.h +++ b/src/ssb.tests.h @@ -66,13 +66,19 @@ void tf_ssb_test_peer_exchange(const tf_test_options_t* options); void tf_ssb_test_publish(const tf_test_options_t* options); /** -** Test connecting by string. +** Test message and replication. ** @param options The test options. */ void tf_ssb_test_replicate(const tf_test_options_t* options); /** -** Test invites. +** Test blob replication for a message received while already connected. +** @param options The test options. +*/ +void tf_ssb_test_replicate_blob(const tf_test_options_t* options); + +/** +** Test connecting by string. ** @param options The test options. */ void tf_ssb_test_connect_str(const tf_test_options_t* options); diff --git a/src/tests.c b/src/tests.c index 18d14b54..21c27c6d 100644 --- a/src/tests.c +++ b/src/tests.c @@ -988,6 +988,7 @@ void tf_tests(const tf_test_options_t* options) _tf_test_run(options, "peer_exchange", tf_ssb_test_peer_exchange, false); _tf_test_run(options, "publish", tf_ssb_test_publish, false); _tf_test_run(options, "replicate", tf_ssb_test_replicate, false); + _tf_test_run(options, "replicate_blob", tf_ssb_test_replicate_blob, false); _tf_test_run(options, "connect_str", tf_ssb_test_connect_str, false); _tf_test_run(options, "invite", tf_ssb_test_invite, false); _tf_test_run(options, "triggers", tf_ssb_test_triggers, false);