Initialize chat schema before storing and publishing local identity

This commit is contained in:
evgeny committed 2026-10-26 22:11:39 +01:00
1 parent f6005fd96d
commit 5dab86e840
2 files changed
+74 -28

No files matched your search

+36 -28
View File
@@ -120,33 +120,8 @@ int chat_core_init(struct UTUN_INSTANCE* inst, const char* db_path) {
sqlite3_int64 now_sec = (sqlite3_int64)ntp_time_get_seconds(inst);
/* записать себя в nodes + local_identity */
{
const char* my_name = inst->name[0] ? inst->name : "Me";
topo_node_sqlite_node_update_verified(cc->db, cc->my_node_id,
my_name, inst->my_keys.public_key, inst->my_ed25519_pubkey,
(uint64_t)now_sec, (time_t)now_sec);
sqlite3_stmt* st = NULL;
sqlite3_prepare_v2(cc->db,
"INSERT OR REPLACE INTO local_identity(id,node_id,name,x25519_pubkey,ed25519_pubkey,created_at,updated_at)"
" VALUES(1,?,?,?,?,?,?)", -1, &st, NULL);
if (st) {
sqlite3_bind_int64(st, 1, (sqlite3_int64)cc->my_node_id);
sqlite3_bind_text(st, 2, my_name, -1, SQLITE_STATIC);
sqlite3_bind_blob(st, 3, inst->my_keys.public_key, 32, SQLITE_STATIC);
sqlite3_bind_blob(st, 4, inst->my_ed25519_pubkey, 32, SQLITE_STATIC);
sqlite3_bind_int64(st, 5, now_sec);
sqlite3_bind_int64(st, 6, now_sec);
sqlite3_step(st); sqlite3_finalize(st);
}
}
cc->initialized = 1;
/* register adm_tags change callback → CHAT_EVT_MEMBER_UPDATED */
chat_member_init(inst);
db_exec(cc,
/* Схема нужна до первой записи идентичности, в том числе для новой пустой БД. */
if (db_exec(cc,
"CREATE TABLE IF NOT EXISTS local_identity ("
" id INTEGER PRIMARY KEY CHECK (id = 1),"
" node_id INTEGER NOT NULL UNIQUE,"
@@ -171,7 +146,37 @@ int chat_core_init(struct UTUN_INSTANCE* inst, const char* db_path) {
" key TEXT PRIMARY KEY,"
" value TEXT"
");"
);
) != SQLITE_OK) goto fail;
/* Идентичность публикуется в GUI только после успешной записи узла и локального профиля. */
{
const char* my_name = inst->name[0] ? inst->name : "Me";
if (topo_node_sqlite_node_update_verified(cc->db, cc->my_node_id,
my_name, inst->my_keys.public_key, inst->my_ed25519_pubkey, (uint64_t)now_sec, (time_t)now_sec) < 0) {
DEBUG_ERROR(DEBUG_CATEGORY_CHAT_SYNC, "%s: local node registration failed", CC_ID); goto fail;
}
sqlite3_stmt* st = NULL;
int rc = sqlite3_prepare_v2(cc->db,
"INSERT OR REPLACE INTO local_identity(id,node_id,name,x25519_pubkey,ed25519_pubkey,created_at,updated_at)"
" VALUES(1,?,?,?,?,?,?)", -1, &st, NULL);
if (rc == SQLITE_OK) rc = sqlite3_bind_int64(st, 1, (sqlite3_int64)cc->my_node_id);
if (rc == SQLITE_OK) rc = sqlite3_bind_text(st, 2, my_name, -1, SQLITE_STATIC);
if (rc == SQLITE_OK) rc = sqlite3_bind_blob(st, 3, inst->my_keys.public_key, 32, SQLITE_STATIC);
if (rc == SQLITE_OK) rc = sqlite3_bind_blob(st, 4, inst->my_ed25519_pubkey, 32, SQLITE_STATIC);
if (rc == SQLITE_OK) rc = sqlite3_bind_int64(st, 5, now_sec);
if (rc == SQLITE_OK) rc = sqlite3_bind_int64(st, 6, now_sec);
if (rc == SQLITE_OK) rc = sqlite3_step(st);
if (rc != SQLITE_DONE) DEBUG_ERROR(DEBUG_CATEGORY_CHAT_SYNC, "%s: local identity write rc=%d: %s", CC_ID, rc, sqlite3_errmsg(cc->db));
int final = sqlite3_finalize(st);
if (final != SQLITE_OK) DEBUG_ERROR(DEBUG_CATEGORY_CHAT_SYNC, "%s: local identity finalize rc=%d: %s", CC_ID, final, sqlite3_errmsg(cc->db));
if (rc != SQLITE_DONE || final != SQLITE_OK) goto fail;
DEBUG_INFO(DEBUG_CATEGORY_CHAT_SYNC, "%s: local identity stored node=%016llx", CC_ID, (unsigned long long)cc->my_node_id);
}
cc->initialized = 1;
/* register adm_tags change callback → CHAT_EVT_MEMBER_UPDATED */
chat_member_init(inst);
/* store media_base for block_req handler */
{
char mbase[MEDIA_BASE_MAX];
@@ -248,6 +253,9 @@ int chat_core_init(struct UTUN_INSTANCE* inst, const char* db_path) {
chat_event_post(inst, CHAT_EVT_DB_READY, NULL, 0);
return 0;
fail:
if (!cc->shared_db) sqlite3_close(cc->db);
inst->chat_core = NULL; u_free(cc); return -1;
}
void chat_core_destroy(struct UTUN_INSTANCE* inst) {
@@ -43,6 +43,7 @@ class ChannelRepliesTest : public QObject {
private slots:
void initTestCase() {
debug_config_init(); debug_set_level(DEBUG_LEVEL_WARN);
debug_set_category_level(DEBUG_CATEGORY_DEBUG, DEBUG_LEVEL_INFO);
QVERIFY(directory.isValid()); ua = uasync_create(); QVERIFY(ua);
const QByteArray config = "[global]\ndb_path=" + directory.path().toUtf8()
+ "\n[chatserver]\ngroup_autoconnect=0\nstorage_autoload=0\n";
@@ -56,6 +57,18 @@ private slots:
}
void signedReferenceAndRestart() {
sqlite3_stmt* identity = nullptr;
QCOMPARE(sqlite3_prepare_v2(inst->topo_sqlite_db, "SELECT node_id,x25519_pubkey,ed25519_pubkey FROM local_identity WHERE id=1",
-1, &identity, nullptr), SQLITE_OK);
const int identityRc = sqlite3_step(identity);
const auto local = quint64(sqlite3_column_int64(identity, 0));
const auto x25519 = DbManager::colBlob(identity, 1), ed25519 = DbManager::colBlob(identity, 2);
sqlite3_finalize(identity);
DEBUG_INFO(DEBUG_CATEGORY_DEBUG, "first chat startup: local_identity rc=%d node=%016llx expected=%016llx x25519=%d ed25519=%d",
identityRc, (unsigned long long)local, (unsigned long long)inst->node_id, int(x25519.size()), int(ed25519.size()));
QCOMPARE(identityRc, SQLITE_ROW); QCOMPARE(local, quint64(inst->node_id));
QCOMPARE(x25519, QByteArray(reinterpret_cast<const char*>(inst->my_keys.public_key), 32));
QCOMPARE(ed25519, QByteArray(reinterpret_cast<const char*>(inst->my_ed25519_pubkey), 32));
const auto reply = send("Answer", original); QCOMPARE(reply.size(), 32);
QCOMPARE(db.getMessageByKey(channel, reply).replyTo, original);
const auto sql = QString("SELECT timestamp,data,author_signature FROM msg_%1 WHERE message_id=?").arg(channel).toUtf8();
@@ -67,6 +80,21 @@ private slots:
QByteArray body(reinterpret_cast<const char*>(&ts), 8); body += DbManager::colBlob(statement, 1);
QByteArray signature = DbManager::colBlob(statement, 2); sqlite3_finalize(statement);
QCOMPARE(sc_ed25519_verify(inst->my_ed25519_pubkey, (uint8_t*)body.data(), body.size(), (uint8_t*)signature.data()), SC_OK);
const auto byKey = db.getMessageByKey(channel, reply);
const auto latest = db.getMessages(channel, 1); QCOMPARE(latest.size(), 1);
const auto since = db.getMessagesSince(channel, byKey.id - 1); QCOMPARE(since.size(), 1);
const auto byId = db.getMessageById(channel, byKey.id);
const MessageRow readers[] = {byKey, byId, latest.first(), since.first()};
const char* names[] = {"key", "id", "latest", "since"};
for (unsigned i = 0; i < 4; i++) {
const auto& row = readers[i];
DEBUG_INFO(DEBUG_CATEGORY_DEBUG, "history reader=%s author=%016llx local=%016llx outgoing=%d signature=%d id=%d attrs=%d",
names[i], (unsigned long long)row.authorNodeId, (unsigned long long)db.myNodeId(), row.isOutgoing,
int(row.signature.size()), int(row.messageId.size()), int(row.localAttrs.size()));
QCOMPARE(row.messageId, reply); QCOMPARE(row.replyTo, original); QCOMPARE(row.signature, signature);
QCOMPARE(row.timestamp, qint64(ts)); QCOMPARE(row.data, QByteArray("Answer"));
QVERIFY(row.isOutgoing); QVERIFY(row.localAttrs.isEmpty());
}
const int reference = body.indexOf(original.toHex()); QVERIFY(reference >= 0); body[reference] = body[reference] == 'a' ? 'b' : 'a';
QVERIFY(sc_ed25519_verify(inst->my_ed25519_pubkey, (uint8_t*)body.data(), body.size(), (uint8_t*)signature.data()) != SC_OK);
chat_service_stop(inst); QCOMPARE(chat_service_start(inst), 0);
@@ -143,6 +171,16 @@ private slots:
void cleanupTestCase() {
db.setDb(nullptr); if (inst) utun_instance_destroy(inst); if (ua) uasync_destroy(ua, 0);
}
void identityWriteFailureRejectsStartup() {
chat_service_stop(inst);
QCOMPARE(sqlite3_exec(inst->topo_sqlite_db, "CREATE TEMP TRIGGER reject_identity BEFORE INSERT ON local_identity"
" BEGIN SELECT RAISE(ABORT,'injected local identity failure'); END", nullptr, nullptr, nullptr), SQLITE_OK);
QVERIFY(chat_service_start(inst) < 0);
QVERIFY(!inst->chat_started); QVERIFY(!inst->chat_core);
QCOMPARE(sqlite3_exec(inst->topo_sqlite_db, "DROP TRIGGER reject_identity", nullptr, nullptr, nullptr), SQLITE_OK);
QCOMPARE(chat_service_start(inst), 0); QVERIFY(inst->chat_started);
}
};
QTEST_MAIN(ChannelRepliesTest)