You can not select more than 25 topics Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.
 
 
 
 
 
 

170 lines
11 KiB

/* Реальный адаптер, SQLite и транзакции; без копии алгоритма и без сетевых таймингов. */
#include "../src/chat/member_sync.c"
#include <assert.h>
static void setup(struct UTUN_INSTANCE* inst, struct UASYNC* ua) {
memset(inst, 0, sizeof(*inst)); inst->ua = ua; inst->node_id = 1;
assert(sqlite3_open(":memory:", &inst->topo_sqlite_db) == SQLITE_OK);
assert(topo_node_sqlite_init(inst->topo_sqlite_db) == 0);
uint8_t key[32] = {1}, signature[64] = {0};
assert(topo_node_sqlite_channel_put(inst->topo_sqlite_db, "1001", "test", 1, key, NULL, key, NULL, signature) == 0);
assert(member_sync_init(inst) == 0);
}
static void put(struct UTUN_INSTANCE* inst, uint64_t id) {
uint8_t key[32] = {1};
assert(member_sync_put(inst, "1001", id, key, key, NULL, 0, NULL, 0, "{}", NULL, NULL, 0, 0, NULL) >= 0);
}
static void root(struct UTUN_INSTANCE* inst, uint8_t hash[32]) {
assert(merkle_sync_read_hash(inst, "1001", 0, 0, hash) == 0);
}
static void public_key(const uint8_t priv[32], uint8_t pub[32]) {
EVP_PKEY* pk = EVP_PKEY_new_raw_private_key(EVP_PKEY_ED25519, NULL, priv, 32); assert(pk);
size_t len = 32; assert(EVP_PKEY_get_raw_public_key(pk, pub, &len) == 1); EVP_PKEY_free(pk);
}
static void test_owner_sizes(struct UASYNC* ua) {
struct UTUN_INSTANCE source, target; setup(&source, ua); setup(&target, ua);
uint8_t x[32] = {9}, priv[32] = {7}, pub[32], owner_priv[32] = {8}, owner_pub[32], zero[64] = {0};
public_key(priv, pub); public_key(owner_priv, owner_pub);
assert(topo_node_sqlite_channel_put(source.topo_sqlite_db, "1001", "test", 1, x, NULL, owner_pub, owner_priv, zero) == 0);
assert(topo_node_sqlite_channel_put(target.topo_sqlite_db, "1001", "test", 1, x, NULL, owner_pub, NULL, zero) == 0);
uint64_t nid = sc_derive_node_id_from_pubkey(x);
uint8_t join[64], tree[64], owner[64], msg[MS_ADM_TAGS_MAX + 8];
int n = member_sync_build_join_msg(x, owner_pub, nid, x, 1, msg, sizeof(msg)); assert(n > 0);
assert(sc_ed25519_sign(priv, msg, n, join) == SC_OK);
assert(member_sync_sign_pubkey(owner_priv, x, tree) == 0);
char tags[MS_ADM_TAGS_MAX + 2];
struct ms_member_rec rec = { .node_id = nid, .x25519 = x, .ed25519 = pub,
.join_sig = join, .join_ts = 1, .userinfo = "", .adm_tags = tags, .adm_tags_sig = owner, .signature = tree };
const size_t sizes[] = {191, 192, 255, 256, 4096, MS_ADM_TAGS_MAX};
uint8_t page[MT_PAGE_SIZE], expected[32], actual[32];
for (size_t i = 0; i < sizeof(sizes) / sizeof(sizes[0]); i++) {
size_t len = sizes[i];
int prefix = snprintf(tags, sizeof(tags), "{\"ver\":\"%zu\",\"pad\":\"", i + 1);
const char* suffix = "\",\"storage\":\"yes\"}";
size_t tail = len - strlen(suffix);
memset(tags + prefix, 'a', tail - (size_t)prefix); memcpy(tags + tail, suffix, strlen(suffix) + 1);
n = member_sync_build_owner_msg(nid, tags, msg, sizeof(msg)); assert(n == (int)len + 8);
assert(sc_ed25519_sign(owner_priv, msg, n, owner) == SC_OK);
assert(member_sync_apply_record(&source, "1001", source.node_id, &rec) & MS_APPLY_CHANGED);
assert(member_sync_verify_local_record(source.topo_sqlite_db, "1001", &rec) == 1);
size_t bytes = sizeof(page); uint64_t next; int more;
assert(_member_get_page(&source, "1001", merkle_sync_level_prefix(nid, 5), 0, 0, page, &bytes, &next, &more) == 0);
assert(next == nid && !more && bytes < MT_PAGE_SIZE);
struct member_wire w; size_t used;
assert(member_decode(page + 2, bytes - 2, &w, &used) == 0 && used == bytes - 2 && !strcmp(w.rec.adm_tags, tags));
assert(_member_apply_items(&target, "1001", 2, page, bytes) == 0);
root(&source, expected); root(&target, actual); assert(!memcmp(expected, actual, 32));
sqlite3_stmt* st;
assert(sqlite3_prepare_v2(target.topo_sqlite_db, "SELECT adm_tags,storage FROM peers_1001", -1, &st, NULL) == SQLITE_OK);
assert(sqlite3_step(st) == SQLITE_ROW && sqlite3_column_bytes(st, 0) == (int)len);
assert(!strcmp((const char*)sqlite3_column_text(st, 0), tags));
char storage[8];
assert(json_flat_get((const char*)sqlite3_column_text(st, 0), "storage", storage, sizeof(storage)) == 0);
assert(!strcmp(storage, "yes"));
sqlite3_finalize(st);
size_t flip = tail - 1; tags[flip] = 'b';
assert(member_sync_apply_record(&target, "1001", 2, &rec) < 0);
assert(member_sync_apply_record(&source, "1001", source.node_id, &rec) < 0);
assert(member_sync_verify_local_record(target.topo_sqlite_db, "1001", &rec) == 0);
root(&target, actual); assert(!memcmp(expected, actual, 32));
assert(sqlite3_exec(target.topo_sqlite_db, "SAVEPOINT hash_tail", NULL, NULL, NULL) == SQLITE_OK);
assert(topo_node_sqlite_member_owner_put(target.topo_sqlite_db, "1001", nid, tags, owner, 0) == 0);
assert(merkle_sync_recompute_path(&target, "1001", nid) == 1);
root(&target, actual); assert(memcmp(expected, actual, 32));
assert(sqlite3_exec(target.topo_sqlite_db, "ROLLBACK TO hash_tail; RELEASE hash_tail", NULL, NULL, NULL) == SQLITE_OK);
tags[flip] = 'a';
assert(_member_apply_items(&target, "1001", 2, page, bytes - 1) < 0);
size_t length_offset = (size_t)(w.rec.adm_tags_sig - page) - len - 2;
page[length_offset] = 0xff; page[length_offset + 1] = 0xff;
assert(_member_apply_items(&target, "1001", 2, page, bytes) < 0);
}
puts("PASS: full owner signatures and wire roundtrip at 191/192/255/256/4096/8192 bytes; suffix tampering rejected");
struct chat_core_ctx cc = { .inst = &source, .db = source.topo_sqlite_db, .initialized = 1 };
source.chat_core = &cc;
char listing[MT_PAGE_SIZE]; size_t listed;
assert(chat_core_get_members_json(&source, "1001", listing, sizeof(listing), &listed) == 0 && listed > MS_ADM_TAGS_MAX);
struct chat_member_tags* edit = chat_member_tags_load(&source, "1001", nid); assert(edit);
const char* pad = chat_member_tags_get(edit, "pad"); assert(pad && strlen(pad) > 4096);
assert(chat_member_tags_set(edit, "storage", "no") == 0);
assert(chat_member_tags_commit(&source, edit) == 0);
chat_member_tags_free(edit);
edit = chat_member_tags_load(&source, "1001", nid); assert(edit);
pad = chat_member_tags_get(edit, "pad"); assert(pad && strlen(pad) > 4096);
assert(!strcmp(chat_member_tags_get(edit, "storage"), "no"));
root(&source, expected);
assert(sqlite3_exec(source.topo_sqlite_db, "CREATE TRIGGER reject_owner BEFORE UPDATE OF adm_tags ON peers_1001 "
"BEGIN SELECT RAISE(ABORT,'injected owner failure'); END", NULL, NULL, NULL) == SQLITE_OK);
assert(chat_member_tags_set(edit, "storage", "yes") == 0);
assert(chat_member_tags_commit(&source, edit) < 0);
root(&source, actual); assert(!memcmp(expected, actual, 32));
assert(sqlite3_exec(source.topo_sqlite_db, "DROP TRIGGER reject_owner", NULL, NULL, NULL) == SQLITE_OK);
memset(tags, 'x', MS_ADM_TAGS_MAX + 1); tags[MS_ADM_TAGS_MAX + 1] = 0;
assert(member_sync_build_owner_msg(nid, tags, msg, sizeof(msg)) < 0);
assert(chat_member_tags_set(edit, "pad", tags) < 0);
tags[MS_ADM_TAGS_MAX] = 0;
assert(chat_member_tags_set(edit, "pad", tags) == 0 && chat_member_tags_commit(&source, edit) < 0);
root(&source, actual); assert(!memcmp(expected, actual, 32));
assert(chat_member_tags_set(edit, "pad", "quoted\"\\value\n\r\t\b\f") == 0 && chat_member_tags_commit(&source, edit) == 0);
chat_member_tags_free(edit);
edit = chat_member_tags_load(&source, "1001", nid); assert(edit);
assert(!strcmp(chat_member_tags_get(edit, "pad"), "quoted\"\\value\n\r\t\b\f")); chat_member_tags_free(edit);
char small[8]; size_t output;
assert(chat_core_get_members_json(&source, "1001", small, sizeof(small), &output) < 0 && output == 0);
source.chat_core = NULL;
puts("PASS: long JSON edit/reload; escaping; size and SQLite errors preserve data and tree");
member_sync_destroy(&source); member_sync_destroy(&target);
assert(sqlite3_close(source.topo_sqlite_db) == SQLITE_OK && sqlite3_close(target.topo_sqlite_db) == SQLITE_OK);
}
int main(void) {
debug_config_init(); debug_set_level(DEBUG_LEVEL_ERROR);
size_t baseline = u_get_allocated_count();
struct UASYNC* ua = uasync_create(); assert(ua);
test_owner_sizes(ua);
struct UTUN_INSTANCE source, target;
setup(&source, ua); setup(&target, ua);
put(&source, 1); put(&source, 2);
uint8_t raw[2049], *page = raw + 1; /* проверяем невыровненный буфер */
size_t len = 300; uint64_t next; int more;
assert(_member_get_page(&source, "1001", 0, 0, 0, page, &len, &next, &more) == 0);
assert(page[0] == 0 && page[1] == 1 && next == 1 && more == 1 && len <= 300);
struct member_wire w; size_t used;
assert(member_decode(page + 2, len - 2, &w, &used) == 0 && w.rec.node_id == 1 && used == len - 2);
len = 300;
assert(_member_get_page(&source, "1001", 0, 1, next, page, &len, &next, &more) == 0 && next == 2 && !more);
puts("PASS: bounded member pages, canonical integers and unaligned buffers");
len = 2048;
assert(_member_get_page(&source, "1001", 0, 0, 0, page, &len, &next, &more) == 0 && page[1] == 2 && !more);
assert(_member_apply_items(&target, "1001", 1, page, len - 1) < 0 && member_sync_count(&target, "1001") == 0);
sqlite3* db = target.topo_sqlite_db;
assert(sqlite3_exec(db, "CREATE TRIGGER fail_second BEFORE INSERT ON peers_1001 WHEN NEW.node_id=2 "
"BEGIN SELECT RAISE(ABORT,'injected second record failure'); END", NULL, NULL, NULL) == SQLITE_OK);
assert(_member_apply_items(&target, "1001", 1, page, len) < 0);
uint8_t hash[32], empty[32] = {0}; root(&target, hash);
assert(member_sync_count(&target, "1001") == 0 && !memcmp(hash, empty, 32) && sqlite3_get_autocommit(db));
assert(sqlite3_exec(db, "DROP TRIGGER fail_second", NULL, NULL, NULL) == SQLITE_OK);
assert(_member_apply_items(&target, "1001", 1, page, len) == 0 && member_sync_count(&target, "1001") == 2);
uint8_t expected[32]; root(&source, expected); root(&target, hash); assert(!memcmp(hash, expected, 32));
puts("PASS: malformed page rejected; second-record error rolls back data and tree; complete page converges");
assert(sqlite3_exec(db, "UPDATE peers_1001 SET x25519_pubkey=X'01' WHERE node_id=1", NULL, NULL, NULL) == SQLITE_OK);
assert(merkle_sync_recompute_path(&target, "1001", 1) < 0);
root(&target, hash); assert(!memcmp(hash, expected, 32));
len = 2048;
assert(_member_get_page(&target, "1001", 0, 0, 0, page, &len, &next, &more) < 0);
puts("PASS: corrupt BLOB cannot be hashed or serialized");
member_sync_destroy(&target); member_sync_destroy(&source);
assert(sqlite3_close(target.topo_sqlite_db) == SQLITE_OK && sqlite3_close(source.topo_sqlite_db) == SQLITE_OK);
uasync_poll(ua, 0); uasync_destroy(ua, 0);
assert(u_get_allocated_count() == baseline);
puts("ALL PASS: member adapter");
return 0;
}