Initialize chat schema before storing and publishing local identity
This commit is contained in:
1 parent
f6005fd96d
commit
5dab86e840
2 files changed
+74
-28
No files matched your search
+36
-28
@@ -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)
|
||||
|
||||
Reference in new issue
Block a user