test(chat): cover profile trust, owner rollback, avatar policy, and startup failure
This commit is contained in:
1 parent
ecb589c670
commit
beac8afba8
1 file changed
+104
-1
+104
-1
@@ -6,6 +6,7 @@
|
||||
#include "../src/chat/chat_event.h"
|
||||
#include "../src/chat/chat_core_priv.h"
|
||||
#include "../src/routing_layer/topo_node_sqlite.h"
|
||||
#include "../src/routing_layer/topo_group.h"
|
||||
#include "../lib/debug_config.h"
|
||||
#include "../lib/mem.h"
|
||||
#include "../lib/json_flat.h"
|
||||
@@ -237,6 +238,107 @@ static void callback_deletion(struct UTUN_INSTANCE* inst) {
|
||||
puts("PASS profile:channel deleted from committed property callback without using old group/namespace");
|
||||
}
|
||||
|
||||
static int owner_update(struct UTUN_INSTANCE* inst) {
|
||||
char tags[96]; snprintf(tags, sizeof(tags), "{\"ver\":\"%u\",\"storage\":\"yes\"}", ++sequence);
|
||||
uint8_t key[32], body[MS_ADM_TAGS_MAX + 8], signature[64];
|
||||
assert(!topo_node_sqlite_channel_get_priv(inst->topo_sqlite_db, channel, key));
|
||||
int bytes = member_sync_build_owner_msg(inst->node_id, tags, body, sizeof(body)); assert(bytes > 0);
|
||||
assert(sc_ed25519_sign(key, body, (size_t)bytes, signature) == SC_OK); OPENSSL_cleanse(key, sizeof(key));
|
||||
return member_sync_put(inst, channel, inst->node_id, inst->my_keys.public_key, inst->my_ed25519_pubkey,
|
||||
NULL, 0, NULL, 0, NULL, tags, signature, 0, 0, NULL);
|
||||
}
|
||||
|
||||
static void owner_faults(struct UTUN_INSTANCE* inst) {
|
||||
char query[128]; snprintf(query, sizeof(query), "UPDATE \"peers_%s\" SET adm_tags=", channel); target = query; unsigned cases = 0;
|
||||
for (enum fault point = F_PREPARE; point <= F_FINALIZE; point++) {
|
||||
for (int index = 1; index <= (point == F_BIND ? 5 : 1); index++) {
|
||||
uint8_t before[32], after[32]; snapshot(reader, before); unsigned count = events;
|
||||
fault = point; index_number = index; assert(owner_update(inst) < 0 && fault == F_NONE && sqlite3_get_autocommit(inst->topo_sqlite_db));
|
||||
snapshot(reader, after); assert(!memcmp(before, after, 32) && events == count);
|
||||
assert((owner_update(inst) & MS_APPLY_CHANGED) && events == count + 1); cases++;
|
||||
}
|
||||
}
|
||||
uint8_t before[32], after[32]; snapshot(reader, before); unsigned count = events;
|
||||
fault = F_FINALIZE; fail_member_rollback = 1; assert(owner_update(inst) < 0 && fault == F_NONE && !fail_member_rollback);
|
||||
assert(member_rollback_failed && !releases_after_failure && sqlite3_get_autocommit(inst->topo_sqlite_db));
|
||||
member_rollback_failed = 0; snapshot(reader, after); assert(!memcmp(before, after, 32) && events == count);
|
||||
assert(owner_update(inst) & MS_APPLY_CHANGED);
|
||||
for (int bind = 1; bind <= 2; bind++) {
|
||||
assert(sqlite3_exec(inst->topo_sqlite_db, "BEGIN", NULL, NULL, NULL) == SQLITE_OK);
|
||||
fault = F_BIND; index_number = bind;
|
||||
assert(topo_node_sqlite_member_owner_put(inst->topo_sqlite_db, channel, inst->node_id, NULL, NULL, 0) < 0 && fault == F_NONE);
|
||||
assert(sqlite3_exec(inst->topo_sqlite_db, "ROLLBACK", NULL, NULL, NULL) == SQLITE_OK);
|
||||
}
|
||||
printf("PASS owner block:%u SQL faults/retries,NULL binds,and failed standalone ROLLBACK TO never commits partial owner data\n", cases);
|
||||
}
|
||||
|
||||
static void avatar(struct UTUN_INSTANCE* inst) {
|
||||
char file[70]; memset(file, 'a', 64); memcpy(file + 64, ".json", 6);
|
||||
const char* query = "SELECT adm_tags,source,adm_tags_sig,deleted";
|
||||
for (enum fault point = F_PREPARE; point <= F_FINALIZE; point++) {
|
||||
if (point == F_CHANGES) continue;
|
||||
uint8_t before[32], after[32]; snapshot(reader, before); unsigned count = events;
|
||||
target = query; fault = point; index_number = 1;
|
||||
assert(chat_core_set_my_avatar(inst, channel, inst->node_id, file) < 0 && fault == F_NONE);
|
||||
snapshot(reader, after); assert(!memcmp(before, after, 32) && events == count);
|
||||
}
|
||||
assert(!chat_core_set_my_avatar(inst, channel, inst->node_id, file)); unsigned count = events;
|
||||
assert(!chat_core_set_my_avatar(inst, channel, inst->node_id, file) && events == count);
|
||||
char json[MS_USERINFO_MAX + 1], value[70]; uint8_t signature[64]; uint64_t version;
|
||||
assert(!chat_core_get_my_profile(inst, channel, json, sizeof(json), signature, &version));
|
||||
assert(!json_flat_get(json, "avatar_file", value, sizeof(value)) && !strcmp(value, file));
|
||||
assert(!chat_core_set_my_avatar(inst, channel, 0, "")); count = events;
|
||||
assert(!chat_core_set_my_avatar(inst, channel, 0, "") && events == count);
|
||||
puts("PASS avatar profile:4 ACL SQL failures,publish/identical retry/delete keep signed options and committed events");
|
||||
}
|
||||
|
||||
static void no_apply(const char* ns, uint64_t node, const uint8_t* x, uint64_t parent, const uint8_t* sig, void* arg) {
|
||||
(void)ns; (void)node; (void)x; (void)parent; (void)sig; (void)arg;
|
||||
}
|
||||
|
||||
static void registration_failure(struct UTUN_INSTANCE* inst, struct UASYNC* ua) {
|
||||
size_t count = u_get_allocated_count(); fail_member_malloc = 1;
|
||||
assert(member_sync_add_apply_cbk(inst, no_apply, inst) < 0 && !fail_member_malloc && u_get_allocated_count() == count);
|
||||
struct UTUN_INSTANCE* other = utun_instance_create_from_str(ua,
|
||||
"[ntp]\nenabled=no\n[chatserver]\ngroup_autoconnect=0\nstorage_autoload=0\n");
|
||||
assert(other && !utun_core_start(other)); fail_apply_registration = 1;
|
||||
assert(chat_service_start(other) < 0 && !fail_apply_registration && !fail_member_malloc);
|
||||
assert(other->core_started && !other->chat_started && !other->chat_sync && !other->chat_core);
|
||||
assert(!chat_service_start(other)); utun_instance_destroy(other);
|
||||
puts("PASS profile/JOIN callback allocation:refused registration rolls service startup back;core survives,retry succeeds");
|
||||
}
|
||||
|
||||
static void set_profile_row(struct UTUN_INSTANCE* inst, const char* json, const uint8_t signature[64], uint64_t version, const uint8_t join[64]) {
|
||||
char sql[192]; snprintf(sql, sizeof(sql), "UPDATE peers_%s SET userinfo=?,update_sig=?,update_ts=?,join_sig=? WHERE node_id=?", channel);
|
||||
sqlite3_stmt* st = NULL; assert(sqlite3_prepare_v2(inst->topo_sqlite_db, sql, -1, &st, NULL) == SQLITE_OK);
|
||||
assert(sqlite3_bind_text(st, 1, json, -1, SQLITE_STATIC) == SQLITE_OK);
|
||||
assert((signature ? sqlite3_bind_blob(st, 2, signature, 64, SQLITE_STATIC) : sqlite3_bind_null(st, 2)) == SQLITE_OK);
|
||||
assert(sqlite3_bind_int64(st, 3, (sqlite3_int64)version) == SQLITE_OK && sqlite3_bind_blob(st, 4, join, 64, SQLITE_STATIC) == SQLITE_OK);
|
||||
assert(sqlite3_bind_int64(st, 5, (sqlite3_int64)inst->node_id) == SQLITE_OK && sqlite3_step(st) == SQLITE_DONE);
|
||||
assert(sqlite3_finalize(st) == SQLITE_OK);
|
||||
}
|
||||
|
||||
static void damaged_profile(struct UTUN_INSTANCE* inst) {
|
||||
char json[MS_USERINFO_MAX + 1]; uint8_t signature[64], changed[64], body[MS_USERINFO_MAX + 73]; uint64_t version;
|
||||
assert(!chat_core_get_my_profile(inst, channel, json, sizeof(json), signature, &version));
|
||||
set_profile_row(inst, "{\"untrusted\":\"do not sign\"}", signature, version, original_join); read_refused(inst); refused(inst);
|
||||
set_profile_row(inst, json, signature, version, original_join);
|
||||
memcpy(changed, original_join, 64); changed[0] ^= 1;
|
||||
int bytes = member_sync_build_update_msg(changed, version, json, body, sizeof(body)); assert(bytes > 0); uint8_t forged[64];
|
||||
assert(sc_ed25519_sign(inst->my_ed25519_privkey, body, (size_t)bytes, forged) == SC_OK);
|
||||
set_profile_row(inst, json, forged, version, changed); read_refused(inst); refused(inst);
|
||||
set_profile_row(inst, json, signature, version, original_join);
|
||||
const char* duplicate = "{\"name\":\"profile test\",\"x\":\"one\",\"x\":\"two\"}";
|
||||
bytes = member_sync_build_update_msg(original_join, version, duplicate, body, sizeof(body)); assert(bytes > 0);
|
||||
assert(sc_ed25519_sign(inst->my_ed25519_privkey, body, (size_t)bytes, forged) == SC_OK);
|
||||
set_profile_row(inst, duplicate, forged, version, original_join); read_refused(inst); refused(inst);
|
||||
set_profile_row(inst, "{\"untrusted\":\"discard unsigned attributes\"}", NULL, 0, original_join);
|
||||
assert(!update(inst));
|
||||
assert(!chat_core_get_my_profile(inst, channel, json, sizeof(json), signature, &version));
|
||||
char value[64]; assert(json_flat_get(json, "untrusted", value, sizeof(value)) < 0);
|
||||
puts("PASS profile trust:bad old signature,JOIN,and signed duplicate JSON rejected;unsigned foreign attributes never re-signed");
|
||||
}
|
||||
|
||||
static void unicode(void) {
|
||||
const char* json = "{\"name\":\"\\u0410\\uD83D\\uDE00\",\"protect\":\"yes\",\"control\":\"\\u0001\"}";
|
||||
char value[64]; assert(!json_flat_get(json, "name", value, sizeof(value)) && !strcmp(value, "А😀"));
|
||||
@@ -277,7 +379,8 @@ int main(void) {
|
||||
if (rc >= 0) DEBUG_ERROR(DEBUG_CATEGORY_CHAT, "profile test: failed finalize rc=%d pending=%d", rc, fail_read);
|
||||
assert(rc < 0 && !fail_read && timestamp == 123 && !memcmp(profile, previous, sizeof(profile)) && !memcmp(signature, old_signature, 64));
|
||||
assert(!chat_core_get_my_profile(inst, channel, profile, sizeof(profile), signature, ×tamp) && timestamp > 0);
|
||||
chat_event_set_handler(inst, event); reader_faults(inst); writer_faults(inst); semantic_profile(inst); callback_deletion(inst);
|
||||
chat_event_set_handler(inst, event); reader_faults(inst); writer_faults(inst); damaged_profile(inst); semantic_profile(inst);
|
||||
owner_faults(inst); avatar(inst); registration_failure(inst, ua); callback_deletion(inst);
|
||||
uint8_t before[32], after[32]; snapshot(reader, before); unsigned count = events;
|
||||
target = "COMMIT /* profile */"; fault = F_EXEC; fail_rollback = 1; inst->running = 1;
|
||||
assert(update(inst) < 0 && fault == F_NONE && !fail_rollback && !inst->running && !sqlite3_get_autocommit(inst->topo_sqlite_db));
|
||||
|
||||
Reference in new issue
Block a user