Files
utun2/tests/test_group_avatar_sql.c
T

133 lines
7.5 KiB
C

/* Аватарка: настоящий commit и подпись, отказ SQL не меняет запись и не публикует событие. */
#define _GNU_SOURCE
#include "../src/chat/group_avatar.c"
#include "../lib/debug_config.h"
#include <assert.h>
#include <stdlib.h>
static int fail_hash;
static unsigned events;
static sqlite3* reader;
static uint64_t group_id;
enum fault { F_NONE, F_PREPARE, F_BIND, F_STEP, F_CHANGES, F_FINALIZE, F_EXEC };
static enum fault fault;
static const char* target;
static int bind_index, last_target, fail_rollback, fail_schema_rollback, releases_after_failed_rollback;
static unsigned image_number;
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 || (sql && !matches(sql)) || (point == F_BIND && index != bind_index)) return 0;
fault = F_NONE; DEBUG_WARN(DEBUG_CATEGORY_CHAT, "avatar test: SQL fault stage=%d bind=%d target=%s", point, index, target); return 1;
}
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_schema_rollback && !strcmp(sql, "ROLLBACK TO group_avatar_schema")) { fail_schema_rollback = 0; return SQLITE_IOERR; }
if (!fail_schema_rollback && releases_after_failed_rollback && !strcmp(sql, "RELEASE group_avatar_schema"))
releases_after_failed_rollback++;
if (take(F_EXEC, sql, 0)) return SQLITE_IOERR;
return __real_sqlite3_exec(db, sql, callback, arg, error);
}
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_blob(sqlite3_stmt*, int, const void*, int, void (*)(void*));
int __wrap_sqlite3_bind_blob(sqlite3_stmt* st, int index, const void* value, int size, void (*destroy)(void*)) {
const char* update = "UPDATE channels SET avatar_version=";
if (fail_hash && index == 3 && !strncmp(sqlite3_sql(st), update, strlen(update))) {
fail_hash = 0; DEBUG_WARN(DEBUG_CATEGORY_CHAT, "avatar test: injected hash bind failure"); return SQLITE_NOMEM;
}
if (take(F_BIND, sqlite3_sql(st), index)) return SQLITE_NOMEM;
return __real_sqlite3_bind_blob(st, index, value, size, 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_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_step(sqlite3_stmt*);
int __wrap_sqlite3_step(sqlite3_stmt* st) {
last_target = matches(sqlite3_sql(st)); if (take(F_STEP, sqlite3_sql(st), 0)) return SQLITE_IOERR;
return __real_sqlite3_step(st);
}
extern int __real_sqlite3_changes(sqlite3*);
int __wrap_sqlite3_changes(sqlite3* db) {
if (last_target && take(F_CHANGES, NULL, 0)) return 0;
return __real_sqlite3_changes(db);
}
extern int __real_sqlite3_finalize(sqlite3_stmt*);
int __wrap_sqlite3_finalize(sqlite3_stmt* st) {
int fail = st && take(F_FINALIZE, sqlite3_sql(st), 0);
int rc = __real_sqlite3_finalize(st); return fail ? SQLITE_IOERR : rc;
}
static void snapshot(sqlite3* db, uint8_t out[32]) {
sqlite3_stmt* st = NULL;
assert(sqlite3_prepare_v2(db, "SELECT channel_id,avatar_version,avatar_data,avatar_hash,avatar_signature,"
"x25519_privkey,ed25519_privkey FROM channels ORDER BY channel_id", -1, &st, NULL) == SQLITE_OK);
EVP_MD_CTX* ctx = EVP_MD_CTX_new(); assert(ctx && EVP_DigestInit_ex(ctx, EVP_sha256(), NULL) == 1); int rc;
while ((rc = sqlite3_step(st)) == SQLITE_ROW) for (int i = 0; i < 7; i++) {
int type = sqlite3_column_type(st, i), bytes = sqlite3_column_bytes(st, i); const void* value = sqlite3_column_blob(st, i);
assert(!bytes || value);
assert(EVP_DigestUpdate(ctx, &type, sizeof(type)) == 1 && EVP_DigestUpdate(ctx, &bytes, sizeof(bytes)) == 1);
if (bytes) assert(EVP_DigestUpdate(ctx, value, (size_t)bytes) == 1);
}
assert(rc == SQLITE_DONE && sqlite3_finalize(st) == SQLITE_OK); unsigned length;
assert(EVP_DigestFinal_ex(ctx, out, &length) == 1 && length == 32); EVP_MD_CTX_free(ctx);
}
static void event(struct UTUN_INSTANCE* inst, int type, const uint8_t* data, int size) {
if (type != CHAT_EVT_CHANNEL_UPDATED) return;
assert(size > 1 && size == data[0] + 1 && sqlite3_get_autocommit(CC(inst)->db));
struct group_avatar_record record; assert(!group_avatar_get(inst, group_id, &record) && record.version);
uint8_t writer[32], committed[32]; snapshot(CC(inst)->db, writer); snapshot(reader, committed);
assert(!memcmp(writer, committed, 32)); events++;
}
static int publish(struct UTUN_INSTANCE* inst) {
uint32_t image = ++image_number;
return group_avatar_set(inst, group_id, (const uint8_t*)&image, sizeof(image));
}
static void refused(struct UTUN_INSTANCE* inst) {
uint8_t before[32], after[32]; snapshot(reader, before); unsigned count = events;
assert(publish(inst) < 0 && fault == F_NONE && sqlite3_get_autocommit(CC(inst)->db));
snapshot(reader, after); assert(!memcmp(before, after, 32) && events == count);
}
static void retry(struct UTUN_INSTANCE* inst) {
unsigned before = events; assert(!publish(inst) && events == before + 1);
}
int main(void) {
debug_config_init(); debug_set_level(DEBUG_LEVEL_ERROR); debug_set_category_level(DEBUG_CATEGORY_CHAT, DEBUG_LEVEL_INFO);
size_t baseline = u_get_allocated_count(); utun_instance_set_tun_init_enabled(0);
char directory[] = "/tmp/utun-avatar-sql-XXXXXX"; assert(mkdtemp(directory));
char config[512]; snprintf(config, sizeof(config), "[global]\ndb_path=%s\n[ntp]\nenabled=no\n"
"[chatserver]\ngroup_autoconnect=0\nstorage_autoload=0\n", directory);
struct UASYNC* ua = uasync_create(); assert(ua);
struct UTUN_INSTANCE* inst = utun_instance_create_from_str(ua, config);
assert(inst && !utun_core_start(inst) && !chat_service_start(inst));
uint64_t group = chat_core_create_channel_auto(inst, "avatar SQL"); assert(group);
chat_event_set_handler(inst, event);
const uint8_t image[] = {1, 2, 3}; fail_hash = 1;
int rc = group_avatar_set(inst, group, image, sizeof(image));
DEBUG_INFO(DEBUG_CATEGORY_CHAT, "avatar test: hash bind failure result=%d events=%u fault_pending=%d", rc, events, fail_hash);
assert(rc < 0 && !events && !fail_hash);
struct group_avatar_record current; assert(!group_avatar_get(inst, group, &current) && !current.version);
assert(!group_avatar_set(inst, group, image, sizeof(image)) && events == 1);
utun_instance_destroy(inst); uasync_poll(ua, 0); uasync_destroy(ua, 0); assert(u_get_allocated_count() == baseline);
DEBUG_INFO(DEBUG_CATEGORY_CHAT, "PASS avatar SQL rollback and successful retry"); return 0;
}