355 lines
20 KiB
C
355 lines
20 KiB
C
/* Штатные member/SQLite/crypto: отказы не удаляют корректные записи и не публикуют rollback. */
|
|
#include "chat/member_sync.h"
|
|
#include "chat/chat_core_priv.h"
|
|
#include "utun_instance.h"
|
|
#include "routing_layer/topo_node_sqlite.h"
|
|
#include "transport_layer/etcp.h"
|
|
#include "../lib/mem.h"
|
|
#include "../lib/debug_config.h"
|
|
#include <assert.h>
|
|
#include <openssl/sha.h>
|
|
#include <string.h>
|
|
|
|
enum { FAULT_NONE, FAULT_KEY, FAULT_CTX, FAULT_INIT, FAULT_VERIFY, FAULT_ID };
|
|
static int crypto_fault, crypto_at, crypto_calls, realloc_at, realloc_calls;
|
|
static const char *prepare_fault, *bind_fault, *step_fault, *finalize_fault, *exec_fault;
|
|
static int step_at, step_calls;
|
|
|
|
static int crypto_fails(int kind) {
|
|
return crypto_fault == kind && ++crypto_calls == crypto_at;
|
|
}
|
|
|
|
extern EVP_PKEY* __real_EVP_PKEY_new_raw_public_key(int, ENGINE*, const unsigned char*, size_t);
|
|
EVP_PKEY* __wrap_EVP_PKEY_new_raw_public_key(int type, ENGINE* engine, const unsigned char* key, size_t len) {
|
|
return crypto_fails(FAULT_KEY) ? NULL : __real_EVP_PKEY_new_raw_public_key(type, engine, key, len);
|
|
}
|
|
extern EVP_MD_CTX* __real_EVP_MD_CTX_new(void);
|
|
EVP_MD_CTX* __wrap_EVP_MD_CTX_new(void) { return crypto_fails(FAULT_CTX) ? NULL : __real_EVP_MD_CTX_new(); }
|
|
extern int __real_EVP_DigestVerifyInit(EVP_MD_CTX*, EVP_PKEY_CTX**, const EVP_MD*, ENGINE*, EVP_PKEY*);
|
|
int __wrap_EVP_DigestVerifyInit(EVP_MD_CTX* ctx, EVP_PKEY_CTX** key_ctx, const EVP_MD* md, ENGINE* engine, EVP_PKEY* key) {
|
|
return crypto_fails(FAULT_INIT) ? 0 : __real_EVP_DigestVerifyInit(ctx, key_ctx, md, engine, key);
|
|
}
|
|
extern int __real_EVP_DigestVerify(EVP_MD_CTX*, const unsigned char*, size_t, const unsigned char*, size_t);
|
|
int __wrap_EVP_DigestVerify(EVP_MD_CTX* ctx, const unsigned char* sig, size_t size, const unsigned char* msg, size_t len) {
|
|
return crypto_fails(FAULT_VERIFY) ? -1 : __real_EVP_DigestVerify(ctx, sig, size, msg, len);
|
|
}
|
|
extern unsigned char* __real_SHA256(const unsigned char*, size_t, unsigned char*);
|
|
unsigned char* __wrap_SHA256(const unsigned char* data, size_t len, unsigned char* hash) {
|
|
return crypto_fails(FAULT_ID) ? NULL : __real_SHA256(data, len, hash);
|
|
}
|
|
extern void* __real_u_realloc_impl(void*, uint32_t, const char*);
|
|
void* __wrap_u_realloc_impl(void* p, uint32_t size, const char* where) {
|
|
if (realloc_at && strstr(where, "member_sync.c") && ++realloc_calls == realloc_at) return NULL;
|
|
return __real_u_realloc_impl(p, size, where);
|
|
}
|
|
|
|
extern int __real_sqlite3_prepare_v2(sqlite3*, const char*, int, sqlite3_stmt**, const char**);
|
|
int __wrap_sqlite3_prepare_v2(sqlite3* db, const char* sql, int len, sqlite3_stmt** st, const char** tail) {
|
|
if (prepare_fault && strstr(sql, prepare_fault)) { *st = NULL; return SQLITE_IOERR; }
|
|
return __real_sqlite3_prepare_v2(db, sql, len, st, tail);
|
|
}
|
|
extern int __real_sqlite3_bind_int64(sqlite3_stmt*, int, sqlite3_int64);
|
|
int __wrap_sqlite3_bind_int64(sqlite3_stmt* st, int index, sqlite3_int64 value) {
|
|
if (bind_fault && strstr(sqlite3_sql(st), bind_fault)) return SQLITE_NOMEM;
|
|
return __real_sqlite3_bind_int64(st, index, value);
|
|
}
|
|
extern int __real_sqlite3_step(sqlite3_stmt*);
|
|
int __wrap_sqlite3_step(sqlite3_stmt* st) {
|
|
if (step_fault && strstr(sqlite3_sql(st), step_fault) && ++step_calls == step_at) return SQLITE_IOERR;
|
|
return __real_sqlite3_step(st);
|
|
}
|
|
extern int __real_sqlite3_finalize(sqlite3_stmt*);
|
|
int __wrap_sqlite3_finalize(sqlite3_stmt* st) {
|
|
int fail = st && finalize_fault && strstr(sqlite3_sql(st), finalize_fault);
|
|
int rc = __real_sqlite3_finalize(st);
|
|
return fail ? SQLITE_IOERR : rc;
|
|
}
|
|
extern int __real_sqlite3_exec(sqlite3*, const char*, int (*)(void*, int, char**, char**), void*, char**);
|
|
int __wrap_sqlite3_exec(sqlite3* db, const char* sql, int (*fn)(void*, int, char**, char**), void* arg, char** error) {
|
|
if (exec_fault && !strcmp(sql, exec_fault)) return SQLITE_IOERR;
|
|
return __real_sqlite3_exec(db, sql, fn, arg, error);
|
|
}
|
|
|
|
struct purge_fixture {
|
|
struct UTUN_INSTANCE inst;
|
|
struct chat_core_ctx cc;
|
|
struct ETCP_CONN conn;
|
|
uint8_t x[32], pub[32], owner[32], join[64], update[64], tree[64], owner_sig[64];
|
|
struct ms_member_rec member;
|
|
int events, nested;
|
|
};
|
|
|
|
static void reset_faults(void) {
|
|
crypto_fault = crypto_calls = crypto_at = realloc_at = realloc_calls = step_calls = 0;
|
|
prepare_fault = bind_fault = step_fault = finalize_fault = exec_fault = NULL; step_at = 1;
|
|
}
|
|
|
|
static void purge_public_key(const uint8_t priv[32], uint8_t pub[32]) {
|
|
EVP_PKEY* key = EVP_PKEY_new_raw_private_key(EVP_PKEY_ED25519, NULL, priv, 32); assert(key);
|
|
size_t len = 32; assert(EVP_PKEY_get_raw_public_key(key, pub, &len) == 1); EVP_PKEY_free(key);
|
|
}
|
|
|
|
static void purge_setup(struct purge_fixture* f, struct UASYNC* ua, int join_only) {
|
|
memset(f, 0, sizeof(*f)); f->inst.ua = ua; f->inst.node_id = 1;
|
|
f->inst.chat_core = &f->cc; f->cc.inst = &f->inst;
|
|
assert(sqlite3_open(":memory:", &f->inst.topo_sqlite_db) == SQLITE_OK);
|
|
f->cc.db = f->inst.topo_sqlite_db;
|
|
assert(topo_node_sqlite_init(f->cc.db) == 0);
|
|
uint8_t priv[32] = {7}, owner_priv[32] = {8}, zero[64] = {0}, body[256];
|
|
f->x[0] = 9; purge_public_key(priv, f->pub); purge_public_key(owner_priv, f->owner);
|
|
assert(topo_node_sqlite_channel_put(f->cc.db, "1001", "one", 1, f->x, NULL, f->owner, NULL, zero) == 0);
|
|
assert(topo_node_sqlite_channel_put(f->cc.db, "1002", "two", 1, f->x, NULL, f->owner, NULL, zero) == 0);
|
|
assert(member_sync_init(&f->inst) == 0);
|
|
f->member = (struct ms_member_rec){ .node_id = sc_derive_node_id_from_pubkey(f->x), .x25519 = f->x,
|
|
.ed25519 = f->pub, .join_sig = f->join, .join_ts = 1, .userinfo = join_only ? "" : "{\"name\":\"one\"}",
|
|
.signature = f->tree, .adm_tags = "{\"ver\":\"1\",\"storage\":\"yes\"}", .adm_tags_sig = f->owner_sig };
|
|
int len = member_sync_build_join_msg(f->x, f->owner, f->member.node_id, f->x, 1, body, sizeof(body)); assert(len == 112);
|
|
assert(sc_ed25519_sign(priv, body, len, f->join) == SC_OK);
|
|
assert(member_sync_sign_pubkey(owner_priv, f->x, f->tree) == 0);
|
|
if (!join_only) {
|
|
f->member.update_sig = f->update; f->member.update_ts = 2;
|
|
len = member_sync_build_update_msg(f->join, 2, f->member.userinfo, body, sizeof(body)); assert(len > 0);
|
|
assert(sc_ed25519_sign(priv, body, len, f->update) == SC_OK);
|
|
}
|
|
len = member_sync_build_owner_msg(f->member.node_id, f->member.adm_tags, body, sizeof(body)); assert(len > 0);
|
|
assert(sc_ed25519_sign(owner_priv, body, len, f->owner_sig) == SC_OK);
|
|
assert(member_sync_apply_record(&f->inst, "1001", 1, &f->member) == MS_APPLY_CHANGED);
|
|
}
|
|
|
|
static void purge_destroy(struct purge_fixture* f) {
|
|
reset_faults(); member_sync_destroy(&f->inst);
|
|
assert(!sqlite3_next_stmt(f->cc.db, NULL) && sqlite3_get_autocommit(f->cc.db));
|
|
assert(sqlite3_close(f->cc.db) == SQLITE_OK);
|
|
}
|
|
|
|
static void purge_bad(struct purge_fixture* f, uint64_t id) {
|
|
assert(id != f->member.node_id);
|
|
assert(member_sync_put(&f->inst, "1001", id, f->x, f->pub, NULL, 0, NULL, 0, "{}", NULL, NULL, 0, 0, NULL) > 0);
|
|
}
|
|
|
|
static void purge_root(struct purge_fixture* f, uint8_t out[32]) {
|
|
assert(merkle_sync_read_hash(&f->inst, MT_DATA_MEMBERS, "1001", 0, 0, out) == 0);
|
|
}
|
|
|
|
/* Callback видит целый commit и может удалить себя, соседа и вызвать очистку повторно. */
|
|
static void purge_event(uint64_t id, const char* tags, const char* channel, void* arg) {
|
|
(void)id;
|
|
struct purge_fixture* f = arg;
|
|
assert(!tags && !strcmp(channel, "1001") && sqlite3_get_autocommit(f->cc.db));
|
|
assert(!sqlite3_next_stmt(f->cc.db, NULL));
|
|
f->events++;
|
|
if (f->nested) {
|
|
f->nested = 0;
|
|
member_sync_remove_props_cbk(&f->inst, purge_event, f);
|
|
assert(member_sync_verify_and_purge(&f->inst, channel) == 0);
|
|
}
|
|
}
|
|
|
|
/* БД и индекс сравниваются до/после, а затем очистка обязательно повторяется успешно. */
|
|
static void expect_purge_error(struct purge_fixture* f, const uint8_t before[32], int count) {
|
|
assert(member_sync_verify_and_purge(&f->inst, "1001") < 0);
|
|
reset_faults();
|
|
uint8_t after[32]; purge_root(f, after);
|
|
assert(member_sync_count(&f->inst, "1001") == count && !memcmp(before, after, 32) && !f->events);
|
|
assert(sqlite3_get_autocommit(f->cc.db) && !sqlite3_next_stmt(f->cc.db, NULL));
|
|
}
|
|
|
|
static void test_purge_transactions(struct UASYNC* ua) {
|
|
for (int fault = 0; fault < 11; fault++) {
|
|
struct purge_fixture f; purge_setup(&f, ua, 0);
|
|
purge_bad(&f, 123); purge_bad(&f, 124);
|
|
uint8_t before[32]; purge_root(&f, before);
|
|
assert(member_sync_add_props_cbk(&f.inst, purge_event, &f) == 0);
|
|
const char* trigger = NULL;
|
|
switch (fault) {
|
|
case 0: prepare_fault = "SELECT node_id"; break;
|
|
case 1: step_fault = "FROM \"peers_1001\" WHERE source=0"; step_at = 2; break;
|
|
case 2: finalize_fault = "FROM \"peers_1001\" WHERE source=0"; break;
|
|
case 3: bind_fault = "DELETE FROM \"peers_1001\""; break;
|
|
case 4: exec_fault = "BEGIN IMMEDIATE"; break;
|
|
case 5: exec_fault = "COMMIT"; break;
|
|
case 6: realloc_at = 1; break;
|
|
case 7: trigger = "CREATE TRIGGER reject_delete BEFORE DELETE ON peers_1001"
|
|
" WHEN OLD.node_id=124 BEGIN SELECT RAISE(ABORT,'purge second delete'); END"; break;
|
|
case 8: trigger = "CREATE TRIGGER reject_index BEFORE DELETE ON merkle_tree_hash"
|
|
" BEGIN SELECT RAISE(ABORT,'purge index'); END"; break;
|
|
case 9: trigger = "CREATE TRIGGER reject_auto BEFORE DELETE ON peers_1001"
|
|
" WHEN OLD.node_id=124 BEGIN SELECT RAISE(ROLLBACK,'purge automatic rollback'); END"; break;
|
|
case 10: step_fault = "DELETE FROM \"peers_1001\""; step_at = 2; break;
|
|
}
|
|
if (trigger) assert(sqlite3_exec(f.cc.db, trigger, NULL, NULL, NULL) == SQLITE_OK);
|
|
expect_purge_error(&f, before, 3);
|
|
if (trigger) {
|
|
const char* name = fault == 7 ? "DROP TRIGGER reject_delete" : fault == 8 ? "DROP TRIGGER reject_index"
|
|
: "DROP TRIGGER reject_auto";
|
|
assert(sqlite3_exec(f.cc.db, name, NULL, NULL, NULL) == SQLITE_OK);
|
|
}
|
|
assert(member_sync_verify_and_purge(&f.inst, "1001") == 2 && f.events == 2);
|
|
assert(member_sync_count(&f.inst, "1001") == 1);
|
|
purge_destroy(&f);
|
|
}
|
|
DEBUG_INFO(DEBUG_CATEGORY_DEBUG, "PASS purge: read/finalize/bind/begin/commit/allocate/delete/index failures and automatic rollback");
|
|
}
|
|
|
|
static void test_purge_crypto(struct UASYNC* ua) {
|
|
for (int join_only = 0; join_only < 2; join_only++) {
|
|
for (int kind = FAULT_KEY; kind <= FAULT_VERIFY; kind++) {
|
|
for (int at = 1; at <= 3; at++) {
|
|
struct purge_fixture f; purge_setup(&f, ua, join_only);
|
|
purge_bad(&f, 123);
|
|
uint8_t before[32]; purge_root(&f, before);
|
|
assert(member_sync_add_props_cbk(&f.inst, purge_event, &f) == 0);
|
|
crypto_fault = kind; crypto_at = at;
|
|
expect_purge_error(&f, before, 2);
|
|
assert(member_sync_verify_and_purge(&f.inst, "1001") == 1 && f.events == 1);
|
|
purge_destroy(&f);
|
|
}
|
|
}
|
|
}
|
|
struct purge_fixture f; purge_setup(&f, ua, 0);
|
|
uint8_t before[32]; purge_root(&f, before);
|
|
crypto_fault = FAULT_ID; crypto_at = 1;
|
|
expect_purge_error(&f, before, 1); purge_destroy(&f);
|
|
DEBUG_INFO(DEBUG_CATEGORY_DEBUG, "PASS purge: 24 crypto allocation/init/internal failures across join/update/owner/tree; identity hash error");
|
|
}
|
|
|
|
static void test_purge_dependencies(struct UASYNC* ua) {
|
|
struct purge_fixture f; purge_setup(&f, ua, 0);
|
|
sqlite3_stmt* st = NULL;
|
|
assert(sqlite3_prepare_v2(f.cc.db, "UPDATE peers_1001 SET signed_by=999 WHERE node_id=?", -1, &st, NULL) == SQLITE_OK);
|
|
sqlite3_bind_int64(st, 1, (sqlite3_int64)f.member.node_id); assert(sqlite3_step(st) == SQLITE_DONE); sqlite3_finalize(st);
|
|
assert(merkle_sync_recompute_path(&f.inst, MT_DATA_MEMBERS, "1001", f.member.node_id) > 0);
|
|
assert(member_sync_verify_and_purge(&f.inst, "1001") == 0 && member_sync_count(&f.inst, "1001") == 1);
|
|
uint8_t before[32]; purge_root(&f, before);
|
|
prepare_fault = "SELECT ed25519_pubkey FROM nodes"; expect_purge_error(&f, before, 1);
|
|
bind_fault = "SELECT ed25519_pubkey FROM nodes"; expect_purge_error(&f, before, 1);
|
|
step_fault = "SELECT ed25519_pubkey FROM nodes"; expect_purge_error(&f, before, 1);
|
|
finalize_fault = "SELECT ed25519_pubkey FROM nodes"; expect_purge_error(&f, before, 1);
|
|
prepare_fault = "FROM channels"; expect_purge_error(&f, before, 1);
|
|
assert(topo_node_sqlite_member_placeholder_put(f.cc.db, "1001", 321, f.x, f.pub) == 0);
|
|
purge_bad(&f, 123);
|
|
assert(sqlite3_exec(f.cc.db, "BEGIN", NULL, NULL, NULL) == SQLITE_OK);
|
|
assert(member_sync_verify_and_purge(&f.inst, "1001") < 0);
|
|
assert(member_sync_verify_and_purge_all(&f.inst) < 0);
|
|
assert(member_sync_start(&f.inst, 2, "1001", NULL, NULL) < 0);
|
|
assert(!sqlite3_get_autocommit(f.cc.db) && member_sync_count(&f.inst, "1001") == 3);
|
|
assert(sqlite3_exec(f.cc.db, "ROLLBACK", NULL, NULL, NULL) == SQLITE_OK);
|
|
assert(member_sync_add_props_cbk(&f.inst, purge_event, &f) == 0); f.nested = 1;
|
|
assert(member_sync_verify_and_purge_all(&f.inst) == 0 && f.events == 1);
|
|
assert(member_sync_count(&f.inst, "1001") == 2 && !f.cc.props_cbks);
|
|
purge_destroy(&f);
|
|
DEBUG_INFO(DEBUG_CATEGORY_DEBUG, "PASS purge: missing signer and placeholder preserved; key SQL failures; external transaction; callback reentry");
|
|
}
|
|
|
|
static void test_purge_init_and_growth(struct UASYNC* ua) {
|
|
struct purge_fixture f; purge_setup(&f, ua, 0);
|
|
for (uint64_t id = 100; id < 109; id++) purge_bad(&f, id);
|
|
uint8_t before[32]; purge_root(&f, before);
|
|
realloc_at = 2; expect_purge_error(&f, before, 10);
|
|
assert(member_sync_add_props_cbk(&f.inst, purge_event, &f) == 0);
|
|
finalize_fault = "FROM sqlite_master";
|
|
assert(member_sync_verify_and_purge_all(&f.inst) < 0); reset_faults();
|
|
assert(!f.events && member_sync_count(&f.inst, "1001") == 10);
|
|
member_sync_destroy(&f.inst);
|
|
assert(sqlite3_exec(f.cc.db, "BEGIN", NULL, NULL, NULL) == SQLITE_OK);
|
|
assert(member_sync_init(&f.inst) < 0 && !f.inst.msync);
|
|
assert(sqlite3_exec(f.cc.db, "ROLLBACK", NULL, NULL, NULL) == SQLITE_OK);
|
|
prepare_fault = "SELECT node_id";
|
|
assert(member_sync_init(&f.inst) < 0 && !f.inst.msync && !f.inst.conn_status_cbks);
|
|
reset_faults();
|
|
assert(member_sync_count(&f.inst, "1001") == 10 && sqlite3_get_autocommit(f.cc.db));
|
|
assert(member_sync_init(&f.inst) == 0 && member_sync_count(&f.inst, "1001") == 1);
|
|
purge_destroy(&f);
|
|
DEBUG_INFO(DEBUG_CATEGORY_DEBUG, "PASS purge: list growth failure; purge-all read error; init rollback/cleanup/retry");
|
|
}
|
|
|
|
static void purge_done(uint64_t peer, const char* channel, int result, void* arg) {
|
|
(void)peer; (void)channel; (void)result;
|
|
(*(int*)arg)++;
|
|
}
|
|
|
|
static void test_purge_start(struct UASYNC* ua) {
|
|
struct purge_fixture f; purge_setup(&f, ua, 0); purge_bad(&f, 123);
|
|
uint64_t peer = f.member.node_id;
|
|
f.inst.connections = queue_new(ua, 16, 0, 8, "purge test connections"); assert(f.inst.connections);
|
|
f.conn.instance = &f.inst; f.conn.peer_node_id = peer; f.conn.initialized = 1; f.conn.links_up = 1;
|
|
memcpy(f.conn.crypto_ctx.peer_public_key, f.x, 32);
|
|
f.conn.send_input_q = queue_new(ua, 0, 0, 0, "purge test send"); assert(f.conn.send_input_q);
|
|
queue_set_waiter_defer(f.conn.send_input_q, 1);
|
|
struct ll_entry* e = queue_entry_new(sizeof(struct conn_queue_entry)); assert(e);
|
|
struct conn_queue_entry* entry = (struct conn_queue_entry*)e->data; entry->peer_node_id = peer; entry->conn = &f.conn;
|
|
assert(queue_data_put_with_index(f.inst.connections, e) == 0);
|
|
int callbacks = 0;
|
|
exec_fault = "COMMIT";
|
|
assert(member_sync_start(&f.inst, peer, "1001", purge_done, &callbacks) < 0);
|
|
reset_faults();
|
|
assert(!f.conn.ref_count && !callbacks && member_sync_count(&f.inst, "1001") == 2);
|
|
assert(member_sync_start(&f.inst, peer, "1001", purge_done, &callbacks) == 0 && f.conn.ref_count == 1);
|
|
merkle_sync_cancel_peer(&f.inst, peer);
|
|
assert(!f.conn.ref_count && !callbacks && !f.conn.send_input_q->waiter_head);
|
|
e = queue_data_get(f.inst.connections); assert(e); queue_entry_free(e);
|
|
queue_free(f.inst.connections); f.inst.connections = NULL;
|
|
queue_free(f.conn.send_input_q);
|
|
purge_destroy(&f);
|
|
DEBUG_INFO(DEBUG_CATEGORY_DEBUG, "PASS purge: failed start creates no request/callback; retry attaches real session; cancel releases ownership");
|
|
}
|
|
|
|
/* Настоящие неверные подписи удаляются; короткий BLOB никогда не читается как полный ключ. */
|
|
static void test_purge_invalid_records(struct UASYNC* ua) {
|
|
const char* fields[] = { "join_sig", "update_sig", "adm_tags_sig", "signature", "x25519_pubkey" };
|
|
for (unsigned i = 0; i < sizeof(fields) / sizeof(fields[0]); i++) {
|
|
struct purge_fixture f; purge_setup(&f, ua, i == 0);
|
|
uint8_t bad[64]; memset(bad, 1, sizeof(bad));
|
|
char sql[128]; snprintf(sql, sizeof(sql), "UPDATE peers_1001 SET %s=?", fields[i]);
|
|
sqlite3_stmt* st = NULL; assert(sqlite3_prepare_v2(f.cc.db, sql, -1, &st, NULL) == SQLITE_OK);
|
|
sqlite3_bind_blob(st, 1, bad, i == 4 ? 1 : 64, SQLITE_STATIC);
|
|
assert(sqlite3_step(st) == SQLITE_DONE); sqlite3_finalize(st);
|
|
assert(member_sync_add_props_cbk(&f.inst, purge_event, &f) == 0);
|
|
assert(member_sync_verify_and_purge(&f.inst, "1001") == 1 && f.events == 1);
|
|
uint8_t hash[32], empty[32] = {0}; purge_root(&f, hash);
|
|
assert(!member_sync_count(&f.inst, "1001") && !memcmp(hash, empty, 32));
|
|
purge_destroy(&f);
|
|
}
|
|
DEBUG_INFO(DEBUG_CATEGORY_DEBUG, "PASS purge: invalid join/update/owner/invitation and malformed key removed with empty root");
|
|
}
|
|
|
|
/* Отказ любой стадии crypto/SQL не становится INACTIVE/PENDING и не меняет выходной ключ. */
|
|
static void test_author_errors(struct UASYNC* ua) {
|
|
struct purge_fixture f; purge_setup(&f, ua, 1); uint8_t key[32];
|
|
assert(member_sync_author_key(&f.inst, "1001", f.member.node_id, key) == MS_AUTHOR_ACTIVE);
|
|
for (int kind = FAULT_KEY; kind <= FAULT_ID; kind++) {
|
|
int stages = kind == FAULT_ID ? 1 : 2;
|
|
for (int at = 1; at <= stages; at++) {
|
|
memset(key, 0xa5, sizeof(key)); crypto_fault = kind; crypto_at = at; crypto_calls = 0;
|
|
assert(member_sync_author_key(&f.inst, "1001", f.member.node_id, key) == MS_AUTHOR_ERROR);
|
|
assert(crypto_calls >= at); reset_faults();
|
|
for (unsigned i = 0; i < sizeof(key); i++) assert(key[i] == 0xa5);
|
|
}
|
|
}
|
|
for (unsigned kind = 0; kind < 4; kind++) {
|
|
if (kind == 0) prepare_fault = "FROM peers_1001 WHERE node_id=";
|
|
if (kind == 1) bind_fault = "FROM peers_1001 WHERE node_id=";
|
|
if (kind == 2) step_fault = "FROM peers_1001 WHERE node_id=";
|
|
if (kind == 3) finalize_fault = "FROM peers_1001 WHERE node_id=";
|
|
memset(key, 0xa5, sizeof(key));
|
|
assert(member_sync_author_key(&f.inst, "1001", f.member.node_id, key) == MS_AUTHOR_ERROR);
|
|
reset_faults();
|
|
for (unsigned i = 0; i < sizeof(key); i++) assert(key[i] == 0xa5);
|
|
}
|
|
assert(member_sync_author_key(&f.inst, "1001", f.member.node_id, key) == MS_AUTHOR_ACTIVE);
|
|
purge_destroy(&f);
|
|
DEBUG_INFO(DEBUG_CATEGORY_DEBUG, "PASS author: crypto and SQL failures remain operational errors with untouched output key");
|
|
}
|
|
|
|
void test_member_purge(struct UASYNC* ua) {
|
|
size_t timers = ua->timer_alloc_count - ua->timer_free_count; reset_faults();
|
|
debug_set_category_level(DEBUG_CATEGORY_DEBUG, DEBUG_LEVEL_INFO);
|
|
test_purge_transactions(ua); test_purge_crypto(ua); test_purge_dependencies(ua); test_purge_init_and_growth(ua);
|
|
test_purge_start(ua); test_purge_invalid_records(ua);
|
|
test_author_errors(ua);
|
|
uasync_drain_immediate(ua);
|
|
assert(ua->timer_alloc_count - ua->timer_free_count == timers);
|
|
DEBUG_INFO(DEBUG_CATEGORY_DEBUG, "ALL PASS: atomic purge contracts; timer owners released; full allocation check follows uasync destroy");
|
|
debug_set_category_level(DEBUG_CATEGORY_DEBUG, DEBUG_LEVEL_NONE);
|
|
}
|