test(chat): add profile transaction faults and committed notification observer

This commit is contained in:
evgeny committed 2026-10-27 06:39:57 +01:00
1 parent 70a4c669dc
commit ebb89e1bc3
2 files changed
+138 -1

No files matched your search

+4 -1
View File
@@ -340,7 +340,10 @@ test_chat_json_LDFLAGS = -Wl,--wrap=sqlite3_prepare_v2 -Wl,--wrap=sqlite3_bind_t
-Wl,--wrap=u_realloc_impl -Wl,--wrap=u_calloc_impl
test_chat_profile_SOURCES = test_chat_profile.c
test_chat_profile_LDADD = $(LIBUTUN) $(COMMON_LIBS)
test_chat_profile_LDFLAGS = -Wl,--wrap=sqlite3_finalize
test_chat_profile_LDFLAGS = -Wl,--wrap=sqlite3_finalize -Wl,--wrap=sqlite3_prepare_v2 -Wl,--wrap=sqlite3_bind_text \
-Wl,--wrap=sqlite3_bind_blob -Wl,--wrap=sqlite3_bind_int64 -Wl,--wrap=sqlite3_bind_int -Wl,--wrap=sqlite3_bind_null \
-Wl,--wrap=sqlite3_step -Wl,--wrap=sqlite3_column_text -Wl,--wrap=sqlite3_exec -Wl,--wrap=sc_ed25519_sign \
-Wl,--wrap=sc_ed25519_verify -Wl,--wrap=u_malloc_impl -Wl,--wrap=member_sync_add_apply_cbk
else
test_socket_monitor_LDFLAGS += -Wl,--wrap=read
endif
+134
View File
@@ -1,14 +1,147 @@
/* Подписанный профиль: ошибка чтения не даёт успешный/частичный снимок. */
#define _GNU_SOURCE
#include "../src/utun_instance.h"
#include "../src/chat/chat_core.h"
#include "../src/chat/member_sync.h"
#include "../src/chat/chat_event.h"
#include "../src/chat/chat_core_priv.h"
#include "../src/routing_layer/topo_node_sqlite.h"
#include "../lib/debug_config.h"
#include "../lib/mem.h"
#include "../lib/json_flat.h"
#include <assert.h>
#include <string.h>
#include <stdlib.h>
#include <openssl/evp.h>
#include <openssl/crypto.h>
static int fail_read;
enum fault { F_NONE, F_PREPARE, F_BIND, F_STEP, F_CHANGES, F_FINALIZE, F_EXEC, F_TEXT };
static enum fault fault;
static const char* target;
static int index_number, fail_rollback, fail_member_rollback, member_rollback_failed, releases_after_failure;
static int fail_sign, fail_verify, fail_member_malloc, fail_apply_registration, delete_on_event;
static unsigned events, sequence;
static sqlite3* reader;
static char channel[32];
static struct UTUN_INSTANCE* active;
static uint8_t original_join[64];
static int matches(const char* sql) { return target && !strncmp(sql, target, strlen(target)); }
static int take(enum fault point, const char* sql, int index) {
if (fault != point || !matches(sql) || ((point == F_BIND || point == F_TEXT) && index != index_number)) return 0;
fault = F_NONE; DEBUG_WARN(DEBUG_CATEGORY_CHAT, "profile test: SQL fault point=%d index=%d target=%s", point, index, target); return 1;
}
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 bytes, sqlite3_stmt** st, const char** tail) {
if (take(F_PREPARE, sql, 0)) { *st = NULL; return SQLITE_NOMEM; }
return __real_sqlite3_prepare_v2(db, sql, bytes, st, tail);
}
extern int __real_sqlite3_bind_text(sqlite3_stmt*, int, const char*, int, void (*)(void*));
int __wrap_sqlite3_bind_text(sqlite3_stmt* st, int index, const char* value, int bytes, void (*destroy)(void*)) {
if (take(F_BIND, sqlite3_sql(st), index)) return SQLITE_NOMEM;
return __real_sqlite3_bind_text(st, index, value, bytes, destroy);
}
extern int __real_sqlite3_bind_blob(sqlite3_stmt*, int, const void*, int, void (*)(void*));
int __wrap_sqlite3_bind_blob(sqlite3_stmt* st, int index, const void* value, int bytes, void (*destroy)(void*)) {
if (take(F_BIND, sqlite3_sql(st), index)) return SQLITE_NOMEM;
return __real_sqlite3_bind_blob(st, index, value, bytes, destroy);
}
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 (take(F_BIND, sqlite3_sql(st), index)) return SQLITE_NOMEM;
return __real_sqlite3_bind_int64(st, index, value);
}
extern int __real_sqlite3_bind_int(sqlite3_stmt*, int, int);
int __wrap_sqlite3_bind_int(sqlite3_stmt* st, int index, int value) {
if (take(F_BIND, sqlite3_sql(st), index)) return SQLITE_NOMEM;
return __real_sqlite3_bind_int(st, index, value);
}
extern int __real_sqlite3_bind_null(sqlite3_stmt*, int);
int __wrap_sqlite3_bind_null(sqlite3_stmt* st, int index) {
if (take(F_BIND, sqlite3_sql(st), index)) return SQLITE_NOMEM;
return __real_sqlite3_bind_null(st, index);
}
extern int __real_sqlite3_step(sqlite3_stmt*);
int __wrap_sqlite3_step(sqlite3_stmt* st) {
if (take(F_STEP, sqlite3_sql(st), 0)) return SQLITE_IOERR;
return __real_sqlite3_step(st);
}
extern const unsigned char* __real_sqlite3_column_text(sqlite3_stmt*, int);
const unsigned char* __wrap_sqlite3_column_text(sqlite3_stmt* st, int column) {
if (take(F_TEXT, sqlite3_sql(st), column)) return NULL;
return __real_sqlite3_column_text(st, column);
}
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 (*callback)(void*, int, char**, char**), void* arg, char** error) {
if (fail_rollback && !strcmp(sql, "ROLLBACK")) { fail_rollback = 0; return SQLITE_IOERR; }
if (fail_member_rollback && !strcmp(sql, "ROLLBACK TO member_record")) {
fail_member_rollback = 0; member_rollback_failed = 1; return SQLITE_IOERR;
}
if (member_rollback_failed && !strcmp(sql, "RELEASE member_record")) releases_after_failure++;
if (take(F_EXEC, sql, 0)) return SQLITE_IOERR;
return __real_sqlite3_exec(db, sql, callback, arg, error);
}
extern int __real_sc_ed25519_sign(const uint8_t*, const uint8_t*, size_t, uint8_t*);
int __wrap_sc_ed25519_sign(const uint8_t* key, const uint8_t* bytes, size_t size, uint8_t* signature) {
if (fail_sign) { fail_sign = 0; return SC_ERR_CRYPTO; }
return __real_sc_ed25519_sign(key, bytes, size, signature);
}
extern int __real_sc_ed25519_verify(const uint8_t*, const uint8_t*, size_t, const uint8_t*);
int __wrap_sc_ed25519_verify(const uint8_t* key, const uint8_t* bytes, size_t size, const uint8_t* signature) {
int join = active && size == 112 && !memcmp(bytes + 72, active->my_keys.public_key, 32);
int update = size >= 73 && !memcmp(bytes, original_join, 64);
if ((fail_verify == 1 && join) || (fail_verify == 2 && update)) { fail_verify = 0; return SC_ERR_CRYPTO; }
return __real_sc_ed25519_verify(key, bytes, size, signature);
}
extern void* __real_u_malloc_impl(uint32_t, const char*);
void* __wrap_u_malloc_impl(uint32_t bytes, const char* location) {
if (fail_member_malloc && strstr(location, "member_sync.c")) { fail_member_malloc = 0; return NULL; }
return __real_u_malloc_impl(bytes, location);
}
extern int __real_member_sync_add_apply_cbk(struct UTUN_INSTANCE*, member_apply_cbk_fn, void*);
int __wrap_member_sync_add_apply_cbk(struct UTUN_INSTANCE* inst, member_apply_cbk_fn callback, void* arg) {
if (fail_apply_registration) { fail_apply_registration = 0; fail_member_malloc = 1; }
return __real_member_sync_add_apply_cbk(inst, callback, arg);
}
static void snapshot(sqlite3* db, uint8_t out[32]) {
char peers[128]; snprintf(peers, sizeof(peers), "SELECT * FROM peers_%s ORDER BY node_id", channel);
const char* queries[] = {peers, "SELECT * FROM nodes ORDER BY node_id",
"SELECT namespace,level,prefix64,hash FROM merkle_tree_hash ORDER BY namespace,level,prefix64"};
EVP_MD_CTX* hash = EVP_MD_CTX_new(); assert(hash && EVP_DigestInit_ex(hash, EVP_sha256(), NULL) == 1);
for (unsigned i = 0; i < 3; i++) {
sqlite3_stmt* st = NULL; assert(sqlite3_prepare_v2(db, queries[i], -1, &st, NULL) == SQLITE_OK); int rc;
while ((rc = sqlite3_step(st)) == SQLITE_ROW) for (int c = 0; c < sqlite3_column_count(st); c++) {
int type = sqlite3_column_type(st, c), bytes = sqlite3_column_bytes(st, c); const void* value = sqlite3_column_blob(st, c);
assert(!bytes || value);
assert(EVP_DigestUpdate(hash, &type, sizeof(type)) == 1 && EVP_DigestUpdate(hash, &bytes, sizeof(bytes)) == 1);
if (bytes) assert(EVP_DigestUpdate(hash, value, (size_t)bytes) == 1);
}
assert(rc == SQLITE_DONE && sqlite3_finalize(st) == SQLITE_OK);
}
unsigned length; assert(EVP_DigestFinal_ex(hash, out, &length) == 1 && length == 32); EVP_MD_CTX_free(hash);
}
static void event(struct UTUN_INSTANCE* inst, int type, const uint8_t* data, int bytes) {
if (type != CHAT_EVT_MEMBER_UPDATED) return;
(void)data; (void)bytes; assert(sqlite3_get_autocommit(inst->topo_sqlite_db));
char profile[MS_USERINFO_MAX + 1]; uint8_t signature[64], writer[32], committed[32]; uint64_t timestamp;
assert(!chat_core_get_my_profile(inst, channel, profile, sizeof(profile), signature, &timestamp));
snapshot(inst->topo_sqlite_db, writer); snapshot(reader, committed); assert(!memcmp(writer, committed, 32)); events++;
if (delete_on_event) { delete_on_event = 0; chat_core_delete_channel(inst, channel); }
}
static int update(struct UTUN_INSTANCE* inst) {
char value[32]; snprintf(value, sizeof(value), "%u", ++sequence);
const struct chat_profile_option option = {"test", value}; return chat_core_set_my_profile_options(inst, channel, &option, 1);
}
static void refused(struct UTUN_INSTANCE* inst) {
uint8_t before[32], after[32]; snapshot(reader, before); unsigned count = events;
assert(update(inst) < 0 && fault == F_NONE && !fail_sign && !fail_verify && sqlite3_get_autocommit(inst->topo_sqlite_db));
snapshot(reader, after); assert(!memcmp(before, after, 32) && events == count);
}
static void retry(struct UTUN_INSTANCE* inst) { unsigned count = events; assert(!update(inst) && events == count + 1); }
static void unicode(void) {
const char* json = "{\"name\":\"\\u0410\\uD83D\\uDE00\",\"protect\":\"yes\",\"control\":\"\\u0001\"}";
@@ -25,6 +158,7 @@ int __wrap_sqlite3_finalize(sqlite3_stmt* st) {
const char* prefix = "SELECT userinfo,update_sig,update_ts";
int fail = fail_read && st && !strncmp(sqlite3_sql(st), prefix, strlen(prefix));
if (fail) { fail_read = 0; DEBUG_WARN(DEBUG_CATEGORY_CHAT, "profile test: injected read finalize failure"); }
if (st && take(F_FINALIZE, sqlite3_sql(st), 0)) fail = 1;
int rc = __real_sqlite3_finalize(st); return fail ? SQLITE_IOERR : rc;
}