fix(chat): commit avatar updates before notification and validate SQL reads
This commit is contained in:
1 parent
bc41bb68b6
commit
e0598ccc22
3 files changed
+212
-108
No files matched your search
+168
-79
@@ -12,6 +12,7 @@
|
||||
#include "../../lib/mem.h"
|
||||
#include "../../lib/u_async.h"
|
||||
#include <openssl/sha.h>
|
||||
#include <openssl/crypto.h>
|
||||
|
||||
#define GA_STATE 1
|
||||
#define GA_GET 2
|
||||
@@ -51,7 +52,8 @@ static int ga_message(uint64_t group, const struct group_avatar_record* record,
|
||||
}
|
||||
|
||||
static int ga_verify(struct UTUN_INSTANCE* inst, uint64_t group, const struct group_avatar_record* r) {
|
||||
if (!inst || !CC(inst) || !group || !r->version || r->version > INT64_MAX || r->size > GROUP_AVATAR_MAX) {
|
||||
if (!inst || !CC(inst) || !CC(inst)->db || !group || group > INT64_MAX || !r ||
|
||||
!r->version || r->version > INT64_MAX || r->size > GROUP_AVATAR_MAX) {
|
||||
DEBUG_WARN(DEBUG_CATEGORY_CHAT, "group avatar: invalid record group=%llu", (unsigned long long)group); return -1;
|
||||
}
|
||||
uint8_t hash[32], pub[32], bytes[GROUP_AVATAR_MAX + 64]; char ch[32]; ga_channel(group, ch);
|
||||
@@ -72,31 +74,40 @@ static int ga_verify(struct UTUN_INSTANCE* inst, uint64_t group, const struct gr
|
||||
}
|
||||
|
||||
int group_avatar_get(struct UTUN_INSTANCE* inst, uint64_t group, struct group_avatar_record* r) {
|
||||
if (!inst || !CC(inst) || !CC(inst)->db || !r) { DEBUG_ERROR(DEBUG_CATEGORY_CHAT, "group avatar: invalid read"); return -1; }
|
||||
memset(r, 0, sizeof(*r)); char ch[32]; ga_channel(group, ch); sqlite3_stmt* st = NULL;
|
||||
const char* sql = "SELECT avatar_version,avatar_data,avatar_hash,avatar_signature FROM channels WHERE channel_id=?";
|
||||
int rc = -1;
|
||||
if (sqlite3_prepare_v2(CC(inst)->db, sql, -1, &st, NULL) == SQLITE_OK) {
|
||||
sqlite3_bind_text(st, 1, ch, -1, SQLITE_STATIC);
|
||||
if (sqlite3_step(st) == SQLITE_ROW) {
|
||||
r->version = (uint64_t)sqlite3_column_int64(st, 0); int size = sqlite3_column_bytes(st, 1);
|
||||
if ((!r->version && !size && !sqlite3_column_bytes(st, 2) && !sqlite3_column_bytes(st, 3)) ||
|
||||
(r->version && r->version <= INT64_MAX && size <= GROUP_AVATAR_MAX &&
|
||||
sqlite3_column_bytes(st, 2) == 32 && sqlite3_column_bytes(st, 3) == 64)) {
|
||||
r->size = (uint16_t)size;
|
||||
if (r->version) {
|
||||
if (size) memcpy(r->data, sqlite3_column_blob(st, 1), size);
|
||||
memcpy(r->hash, sqlite3_column_blob(st, 2), 32); memcpy(r->signature, sqlite3_column_blob(st, 3), 64);
|
||||
}
|
||||
rc = 0;
|
||||
}
|
||||
}
|
||||
sqlite3_finalize(st);
|
||||
if (!inst || !CC(inst) || !CC(inst)->db || !group || group > INT64_MAX || !r) {
|
||||
DEBUG_ERROR(DEBUG_CATEGORY_CHAT, "group avatar: invalid read group=%llu", (unsigned long long)group); return -1;
|
||||
}
|
||||
if (!rc && r->version) rc = ga_verify(inst, group, r);
|
||||
if (rc) DEBUG_ERROR(DEBUG_CATEGORY_CHAT, "group avatar: stored record unavailable/invalid group=%llu: %s",
|
||||
(unsigned long long)group, sqlite3_errmsg(CC(inst)->db));
|
||||
return rc;
|
||||
struct group_avatar_record result = {0}; char ch[32]; ga_channel(group, ch); sqlite3_stmt* st = NULL;
|
||||
const char* sql = "SELECT avatar_version,avatar_data,avatar_hash,avatar_signature FROM channels WHERE channel_id=?";
|
||||
sqlite3* db = CC(inst)->db; const char* stage = "prepare";
|
||||
int rc = sqlite3_prepare_v2(db, sql, -1, &st, NULL);
|
||||
if (rc != SQLITE_OK) goto done;
|
||||
stage = "bind"; rc = sqlite3_bind_text(st, 1, ch, -1, SQLITE_STATIC); if (rc != SQLITE_OK) goto done;
|
||||
stage = "step"; rc = sqlite3_step(st); if (rc != SQLITE_ROW) goto done;
|
||||
result.version = (uint64_t)sqlite3_column_int64(st, 0); int size = sqlite3_column_bytes(st, 1);
|
||||
stage = "record fields"; rc = SQLITE_CORRUPT;
|
||||
if (sqlite3_column_type(st, 0) != SQLITE_INTEGER || result.version > INT64_MAX || size > GROUP_AVATAR_MAX) goto done;
|
||||
if (!result.version) {
|
||||
if (size || sqlite3_column_bytes(st, 2) || sqlite3_column_bytes(st, 3)) goto done;
|
||||
} else {
|
||||
if (sqlite3_column_type(st, 1) != SQLITE_BLOB || sqlite3_column_type(st, 2) != SQLITE_BLOB ||
|
||||
sqlite3_column_type(st, 3) != SQLITE_BLOB || sqlite3_column_bytes(st, 2) != 32 || sqlite3_column_bytes(st, 3) != 64) goto done;
|
||||
const void *data = sqlite3_column_blob(st, 1), *hash = sqlite3_column_blob(st, 2), *signature = sqlite3_column_blob(st, 3);
|
||||
if ((size && !data) || !hash || !signature) { rc = SQLITE_NOMEM; goto done; }
|
||||
result.size = (uint16_t)size; if (size) memcpy(result.data, data, (size_t)size);
|
||||
memcpy(result.hash, hash, 32); memcpy(result.signature, signature, 64);
|
||||
}
|
||||
rc = SQLITE_OK;
|
||||
done:
|
||||
{
|
||||
int final = sqlite3_finalize(st);
|
||||
if (rc != SQLITE_OK || final != SQLITE_OK) {
|
||||
DEBUG_ERROR(DEBUG_CATEGORY_CHAT, "group avatar: read group=%llu stage=%s rc=%d finalize=%d: %s",
|
||||
(unsigned long long)group, stage, rc, final, sqlite3_errmsg(db)); return -1;
|
||||
}
|
||||
}
|
||||
if (result.version && ga_verify(inst, group, &result) < 0) return -1;
|
||||
*r = result; return 0;
|
||||
}
|
||||
|
||||
static int ga_compare(uint64_t a, const uint8_t ah[32], uint64_t b, const uint8_t bh[32]) {
|
||||
@@ -112,59 +123,126 @@ static void ga_changed(struct UTUN_INSTANCE* inst, uint64_t group) {
|
||||
memcpy(event + 1, ch, event[0]); chat_event_post(inst, CHAT_EVT_CHANNEL_UPDATED, event, event[0] + 1);
|
||||
}
|
||||
|
||||
int group_avatar_apply(struct UTUN_INSTANCE* inst, uint64_t group, const struct group_avatar_record* r) {
|
||||
/* Блокировка охватывает проверку допуска/версии и запись; callback здесь не вызывается. */
|
||||
static int ga_begin(struct UTUN_INSTANCE* inst, uint64_t group) {
|
||||
if (!inst || !CC(inst) || !CC(inst)->initialized || !CC(inst)->db || !group || group > INT64_MAX) {
|
||||
DEBUG_ERROR(DEBUG_CATEGORY_CHAT, "group avatar: inactive core/invalid group=%llu", (unsigned long long)group); return -1;
|
||||
}
|
||||
sqlite3* db = CC(inst)->db;
|
||||
if (!sqlite3_get_autocommit(db)) {
|
||||
DEBUG_ERROR(DEBUG_CATEGORY_CHAT, "group avatar: external transaction rejected group=%llu", (unsigned long long)group); return -1;
|
||||
}
|
||||
int rc = sqlite3_exec(db, "BEGIN IMMEDIATE /* group avatar */", NULL, NULL, NULL);
|
||||
if (rc != SQLITE_OK) DEBUG_ERROR(DEBUG_CATEGORY_CHAT, "group avatar: begin group=%llu rc=%d: %s",
|
||||
(unsigned long long)group, rc, sqlite3_errmsg(db));
|
||||
return rc == SQLITE_OK ? 0 : -1;
|
||||
}
|
||||
|
||||
static int ga_finish(struct UTUN_INSTANCE* inst, uint64_t group, int result) {
|
||||
sqlite3* db = CC(inst)->db; int rc;
|
||||
if (result > 0) {
|
||||
rc = sqlite3_exec(db, "COMMIT /* group avatar */", NULL, NULL, NULL);
|
||||
if (rc == SQLITE_OK) return result;
|
||||
DEBUG_ERROR(DEBUG_CATEGORY_CHAT, "group avatar: commit group=%llu rc=%d: %s",
|
||||
(unsigned long long)group, rc, sqlite3_errmsg(db)); result = -1;
|
||||
}
|
||||
if (!sqlite3_get_autocommit(db)) {
|
||||
rc = sqlite3_exec(db, "ROLLBACK", NULL, NULL, NULL);
|
||||
if (rc != SQLITE_OK) {
|
||||
DEBUG_ERROR(DEBUG_CATEGORY_CHAT, "group avatar: rollback group=%llu rc=%d: %s",
|
||||
(unsigned long long)group, rc, sqlite3_errmsg(db)); result = -1;
|
||||
}
|
||||
if (!sqlite3_get_autocommit(db)) {
|
||||
DEBUG_ERROR(DEBUG_CATEGORY_CHAT, "group avatar: transaction remains open group=%llu; requesting owner stop/DB close",
|
||||
(unsigned long long)group); utun_instance_stop(inst); result = -1;
|
||||
}
|
||||
}
|
||||
return result;
|
||||
}
|
||||
|
||||
static int ga_write(struct UTUN_INSTANCE* inst, uint64_t group, const struct group_avatar_record* r) {
|
||||
sqlite3* db = CC(inst)->db; sqlite3_stmt* st = NULL; char ch[32]; ga_channel(group, ch);
|
||||
const char* sql = "UPDATE channels SET avatar_version=?,avatar_data=?,avatar_hash=?,avatar_signature=? WHERE channel_id=?";
|
||||
const char* stage = "prepare"; int rc = sqlite3_prepare_v2(db, sql, -1, &st, NULL);
|
||||
if (rc == SQLITE_OK) { stage = "bind version"; rc = sqlite3_bind_int64(st, 1, (sqlite3_int64)r->version); }
|
||||
if (rc == SQLITE_OK) { stage = "bind data"; rc = sqlite3_bind_blob(st, 2, r->data, r->size, SQLITE_STATIC); }
|
||||
if (rc == SQLITE_OK) { stage = "bind hash"; rc = sqlite3_bind_blob(st, 3, r->hash, 32, SQLITE_STATIC); }
|
||||
if (rc == SQLITE_OK) { stage = "bind signature"; rc = sqlite3_bind_blob(st, 4, r->signature, 64, SQLITE_STATIC); }
|
||||
if (rc == SQLITE_OK) { stage = "bind channel"; rc = sqlite3_bind_text(st, 5, ch, -1, SQLITE_STATIC); }
|
||||
if (rc == SQLITE_OK) { stage = "step"; rc = sqlite3_step(st); }
|
||||
if (rc == SQLITE_DONE && sqlite3_changes(db) != 1) { stage = "changes != 1"; rc = SQLITE_CONSTRAINT; }
|
||||
int final = sqlite3_finalize(st);
|
||||
if (rc != SQLITE_DONE || final != SQLITE_OK) {
|
||||
DEBUG_ERROR(DEBUG_CATEGORY_CHAT, "group avatar: write group=%llu stage=%s rc=%d finalize=%d: %s",
|
||||
(unsigned long long)group, stage, rc, final, sqlite3_errmsg(db)); return -1;
|
||||
}
|
||||
return 1;
|
||||
}
|
||||
|
||||
/* Приём завершает всю работу с ga_peer до публикации события, способного удалить группу. */
|
||||
static int ga_apply_transaction(struct UTUN_INSTANCE* inst, uint64_t group, const struct group_avatar_record* r) {
|
||||
if (!r) { DEBUG_ERROR(DEBUG_CATEGORY_CHAT, "group avatar: missing candidate record"); return -1; }
|
||||
if (ga_verify(inst, group, r) < 0) return -1;
|
||||
struct group_avatar_record current;
|
||||
if (group_avatar_get(inst, group, ¤t) < 0) return -1;
|
||||
if (ga_begin(inst, group) < 0) return -1;
|
||||
int result = -1; struct group_avatar_record current;
|
||||
if (ga_verify(inst, group, r) < 0 || group_avatar_get(inst, group, ¤t) < 0) goto done;
|
||||
int newer = ga_compare(r->version, r->hash, current.version, current.hash);
|
||||
if (r->version == current.version && memcmp(r->hash, current.hash, 32))
|
||||
DEBUG_WARN(DEBUG_CATEGORY_CHAT, "group avatar: concurrent versions group=%llu version=%llu accept=%d",
|
||||
(unsigned long long)group, (unsigned long long)r->version, newer > 0);
|
||||
if (newer <= 0) return 0;
|
||||
sqlite3_stmt* st = NULL; char ch[32]; ga_channel(group, ch); int rc = -1;
|
||||
const char* sql = "UPDATE channels SET avatar_version=?,avatar_data=?,avatar_hash=?,avatar_signature=? WHERE channel_id=?";
|
||||
if (sqlite3_prepare_v2(CC(inst)->db, sql, -1, &st, NULL) == SQLITE_OK) {
|
||||
sqlite3_bind_int64(st, 1, (sqlite3_int64)r->version); sqlite3_bind_blob(st, 2, r->data, r->size, SQLITE_STATIC);
|
||||
sqlite3_bind_blob(st, 3, r->hash, 32, SQLITE_STATIC); sqlite3_bind_blob(st, 4, r->signature, 64, SQLITE_STATIC);
|
||||
sqlite3_bind_text(st, 5, ch, -1, SQLITE_STATIC);
|
||||
if (sqlite3_step(st) == SQLITE_DONE && sqlite3_changes(CC(inst)->db) == 1) rc = 1;
|
||||
sqlite3_finalize(st);
|
||||
}
|
||||
if (rc < 0) DEBUG_ERROR(DEBUG_CATEGORY_CHAT, "group avatar: database update failed group=%llu: %s",
|
||||
(unsigned long long)group, sqlite3_errmsg(CC(inst)->db));
|
||||
else {
|
||||
DEBUG_INFO(DEBUG_CATEGORY_CHAT, "group avatar: saved group=%llu version=%llu bytes=%u",
|
||||
(unsigned long long)group, (unsigned long long)r->version, r->size); ga_changed(inst, group);
|
||||
}
|
||||
return rc;
|
||||
result = newer > 0 ? ga_write(inst, group, r) : 0;
|
||||
done:
|
||||
result = ga_finish(inst, group, result);
|
||||
if (result > 0) DEBUG_INFO(DEBUG_CATEGORY_CHAT, "group avatar: saved group=%llu version=%llu bytes=%u committed=1",
|
||||
(unsigned long long)group, (unsigned long long)r->version, r->size);
|
||||
return result;
|
||||
}
|
||||
|
||||
int group_avatar_apply(struct UTUN_INSTANCE* inst, uint64_t group, const struct group_avatar_record* r) {
|
||||
int result = ga_apply_transaction(inst, group, r);
|
||||
if (result > 0) ga_changed(inst, group);
|
||||
return result;
|
||||
}
|
||||
|
||||
int group_avatar_set(struct UTUN_INSTANCE* inst, uint64_t group, const uint8_t* data, size_t size) {
|
||||
if (!inst || !CC(inst) || !CC(inst)->initialized || !CC(inst)->db || size > GROUP_AVATAR_MAX ||
|
||||
(size && !data) || !member_files_member(inst, group, inst->node_id, 0)) {
|
||||
DEBUG_WARN(DEBUG_CATEGORY_CHAT, "group avatar: publication denied group=%llu bytes=%zu", (unsigned long long)group, size); return -1;
|
||||
}
|
||||
uint8_t key[32], bytes[GROUP_AVATAR_MAX + 64]; char ch[32]; ga_channel(group, ch);
|
||||
if (topo_node_sqlite_channel_get_priv(CC(inst)->db, ch, key) < 0) {
|
||||
DEBUG_WARN(DEBUG_CATEGORY_CHAT, "group avatar: publication requires group key group=%llu", (unsigned long long)group); return -1;
|
||||
if (size > GROUP_AVATAR_MAX || (size && !data)) {
|
||||
DEBUG_WARN(DEBUG_CATEGORY_CHAT, "group avatar: invalid publication group=%llu bytes=%zu", (unsigned long long)group, size); return -1;
|
||||
}
|
||||
if (ga_begin(inst, group) < 0) return -1;
|
||||
int result = -1; uint8_t key[32] = {0}, bytes[GROUP_AVATAR_MAX + 64]; char ch[32]; ga_channel(group, ch);
|
||||
struct group_avatar_record r;
|
||||
if (group_avatar_get(inst, group, &r) < 0 || r.version == INT64_MAX) {
|
||||
DEBUG_ERROR(DEBUG_CATEGORY_CHAT, "group avatar: cannot allocate version group=%llu", (unsigned long long)group); return -1;
|
||||
if (!member_files_member(inst, group, inst->node_id, 0)) {
|
||||
DEBUG_WARN(DEBUG_CATEGORY_CHAT, "group avatar: publication requires active membership group=%llu", (unsigned long long)group); goto done;
|
||||
}
|
||||
if (topo_node_sqlite_channel_get_priv(CC(inst)->db, ch, key) < 0) {
|
||||
DEBUG_WARN(DEBUG_CATEGORY_CHAT, "group avatar: publication requires group key group=%llu", (unsigned long long)group); goto done;
|
||||
}
|
||||
if (group_avatar_get(inst, group, &r) < 0) goto done;
|
||||
uint8_t hash[32]; SHA256(size ? data : r.data, size, hash);
|
||||
if (r.version && r.size == size && !memcmp(r.hash, hash, 32)) return 0;
|
||||
if (r.version && r.size == size && !memcmp(r.hash, hash, 32)) { result = 0; goto done; }
|
||||
if (r.version == INT64_MAX) {
|
||||
DEBUG_ERROR(DEBUG_CATEGORY_CHAT, "group avatar: version exhausted group=%llu", (unsigned long long)group); goto done;
|
||||
}
|
||||
int64_t us = ntp_time_get_us(inst); uint64_t now = us > 0 ? (uint64_t)us / 1000 : 0;
|
||||
r.version = now > r.version ? now : r.version + 1; r.size = (uint16_t)size;
|
||||
memcpy(r.hash, hash, 32); if (size) memcpy(r.data, data, size);
|
||||
int n = ga_message(group, &r, bytes); int signed_ok = sc_ed25519_sign(key, bytes, n, r.signature); memset(key, 0, sizeof(key));
|
||||
if (signed_ok != SC_OK) { DEBUG_ERROR(DEBUG_CATEGORY_CHAT, "group avatar: signing failed group=%llu", (unsigned long long)group); return -1; }
|
||||
return group_avatar_apply(inst, group, &r) > 0 ? 0 : -1;
|
||||
int n = ga_message(group, &r, bytes);
|
||||
if (sc_ed25519_sign(key, bytes, (size_t)n, r.signature) != SC_OK) {
|
||||
DEBUG_ERROR(DEBUG_CATEGORY_CHAT, "group avatar: signing failed group=%llu", (unsigned long long)group); goto done;
|
||||
}
|
||||
if (ga_verify(inst, group, &r) < 0) goto done;
|
||||
result = ga_write(inst, group, &r);
|
||||
done:
|
||||
OPENSSL_cleanse(key, sizeof(key)); result = ga_finish(inst, group, result);
|
||||
if (result > 0) {
|
||||
DEBUG_INFO(DEBUG_CATEGORY_CHAT, "group avatar: saved group=%llu version=%llu bytes=%u committed=1",
|
||||
(unsigned long long)group, (unsigned long long)r.version, r.size); ga_changed(inst, group);
|
||||
}
|
||||
return result < 0 ? -1 : 0;
|
||||
}
|
||||
|
||||
void group_avatar_gui_set(void* arg) {
|
||||
struct group_avatar_gui_request* q = arg;
|
||||
if (!q || !q->inst) { DEBUG_ERROR(DEBUG_CATEGORY_CHAT, "group avatar: invalid GUI request"); u_free(q); return; }
|
||||
int rc = group_avatar_set(q->inst, q->group, q->data, q->size);
|
||||
uint8_t result[17]; mf_put64(result, q->id); mf_put64(result + 8, q->group); result[16] = rc ? 1 : 0;
|
||||
chat_event_post(q->inst, CHAT_EVT_GROUP_AVATAR_RESULT, result, sizeof(result)); u_free(q);
|
||||
@@ -337,9 +415,10 @@ static void ga_receive(struct ETCP_CONN* conn, struct ll_entry* entry) {
|
||||
DEBUG_DEBUG(DEBUG_CATEGORY_CHAT, "group avatar: block received group=%llu peer=%016llx version=%llu offset=%u bytes=%u total=%u",
|
||||
(unsigned long long)group, (unsigned long long)peer, (unsigned long long)version, offset, block, size);
|
||||
if (p->rx_offset == size) {
|
||||
int applied = group_avatar_apply(ctx->inst, group, p->rx); ga_release(p, 0);
|
||||
int applied = ga_apply_transaction(ctx->inst, group, p->rx); ga_release(p, 0);
|
||||
p->requested_at = applied < 0 ? get_time_tb() : 0;
|
||||
p->state_pending = 1; ga_schedule(p);
|
||||
if (applied > 0) ga_changed(ctx->inst, group);
|
||||
}
|
||||
goto out;
|
||||
malformed:
|
||||
@@ -386,29 +465,39 @@ static void ga_tick(void* arg) {
|
||||
|
||||
static int ga_schema(sqlite3* db) {
|
||||
const char* names[] = {"avatar_version", "avatar_data", "avatar_hash", "avatar_signature"}; int present[4] = {0};
|
||||
sqlite3_stmt* st = NULL;
|
||||
if (sqlite3_prepare_v2(db, "PRAGMA table_info(channels)", -1, &st, NULL) != SQLITE_OK) goto error;
|
||||
while (sqlite3_step(st) == SQLITE_ROW) for (unsigned i = 0; i < 4; i++)
|
||||
if (!strcmp((const char*)sqlite3_column_text(st, 1), names[i])) present[i] = 1;
|
||||
sqlite3_finalize(st);
|
||||
if (sqlite3_exec(db, "SAVEPOINT group_avatar_schema", NULL, NULL, NULL) != SQLITE_OK) goto error;
|
||||
if (!db) { DEBUG_ERROR(DEBUG_CATEGORY_CHAT, "group avatar: schema database missing"); return -1; }
|
||||
sqlite3_stmt* st = NULL; const char* stage = "savepoint"; int started = 0, added = 0;
|
||||
int rc = sqlite3_exec(db, "SAVEPOINT group_avatar_schema", NULL, NULL, NULL);
|
||||
if (rc != SQLITE_OK) goto error;
|
||||
started = 1; stage = "table info prepare"; rc = sqlite3_prepare_v2(db, "PRAGMA table_info(channels)", -1, &st, NULL);
|
||||
if (rc != SQLITE_OK) goto error;
|
||||
stage = "table info step";
|
||||
while ((rc = sqlite3_step(st)) == SQLITE_ROW) {
|
||||
const char* name = (const char*)sqlite3_column_text(st, 1);
|
||||
if (!name) { rc = SQLITE_CORRUPT; goto error; }
|
||||
for (unsigned i = 0; i < 4; i++) if (!strcmp(name, names[i])) present[i] = 1;
|
||||
}
|
||||
if (rc != SQLITE_DONE) goto error;
|
||||
stage = "table info finalize"; rc = sqlite3_finalize(st); st = NULL; if (rc != SQLITE_OK) goto error;
|
||||
for (unsigned i = 0; i < 4; i++) if (!present[i]) {
|
||||
char sql[128]; snprintf(sql, sizeof(sql), "ALTER TABLE channels ADD COLUMN %s %s", names[i], i ? "BLOB" : "INTEGER NOT NULL DEFAULT 0");
|
||||
if (sqlite3_exec(db, sql, NULL, NULL, NULL) != SQLITE_OK) {
|
||||
DEBUG_ERROR(DEBUG_CATEGORY_CHAT, "group avatar: cannot add schema column=%s: %s", names[i], sqlite3_errmsg(db));
|
||||
if (sqlite3_exec(db, "ROLLBACK TO group_avatar_schema; RELEASE group_avatar_schema", NULL, NULL, NULL) != SQLITE_OK)
|
||||
DEBUG_ERROR(DEBUG_CATEGORY_CHAT, "group avatar: schema rollback failed: %s", sqlite3_errmsg(db));
|
||||
return -1;
|
||||
}
|
||||
DEBUG_INFO(DEBUG_CATEGORY_CHAT, "group avatar: added schema column=%s", names[i]);
|
||||
stage = names[i]; rc = sqlite3_exec(db, sql, NULL, NULL, NULL); if (rc != SQLITE_OK) goto error;
|
||||
added++;
|
||||
}
|
||||
if (sqlite3_exec(db, "RELEASE group_avatar_schema", NULL, NULL, NULL) == SQLITE_OK) return 0;
|
||||
DEBUG_ERROR(DEBUG_CATEGORY_CHAT, "group avatar: schema commit failed: %s", sqlite3_errmsg(db));
|
||||
if (sqlite3_exec(db, "ROLLBACK TO group_avatar_schema; RELEASE group_avatar_schema", NULL, NULL, NULL) != SQLITE_OK)
|
||||
DEBUG_ERROR(DEBUG_CATEGORY_CHAT, "group avatar: schema rollback after commit error failed: %s", sqlite3_errmsg(db));
|
||||
return -1;
|
||||
stage = "release"; rc = sqlite3_exec(db, "RELEASE group_avatar_schema", NULL, NULL, NULL); if (rc != SQLITE_OK) goto error;
|
||||
if (added) DEBUG_INFO(DEBUG_CATEGORY_CHAT, "group avatar: schema ready added_columns=%d outer_transaction=%d", added, !sqlite3_get_autocommit(db));
|
||||
return 0;
|
||||
error:
|
||||
DEBUG_ERROR(DEBUG_CATEGORY_CHAT, "group avatar: schema initialization failed: %s", sqlite3_errmsg(db)); return -1;
|
||||
DEBUG_ERROR(DEBUG_CATEGORY_CHAT, "group avatar: schema initialization stage=%s rc=%d: %s", stage, rc, sqlite3_errmsg(db));
|
||||
rc = sqlite3_finalize(st);
|
||||
if (rc != SQLITE_OK) DEBUG_ERROR(DEBUG_CATEGORY_CHAT, "group avatar: schema cleanup finalize rc=%d", rc);
|
||||
if (started && !sqlite3_get_autocommit(db)) {
|
||||
rc = sqlite3_exec(db, "ROLLBACK TO group_avatar_schema", NULL, NULL, NULL);
|
||||
if (rc == SQLITE_OK) rc = sqlite3_exec(db, "RELEASE group_avatar_schema", NULL, NULL, NULL);
|
||||
if (rc != SQLITE_OK) DEBUG_ERROR(DEBUG_CATEGORY_CHAT, "group avatar: schema rollback rc=%d;owner must rollback/close DB: %s",
|
||||
rc, sqlite3_errmsg(db));
|
||||
}
|
||||
return -1;
|
||||
}
|
||||
|
||||
int group_avatar_init(struct UTUN_INSTANCE* inst) {
|
||||
|
||||
@@ -10,6 +10,7 @@
|
||||
#include <string.h>
|
||||
#include <stdio.h>
|
||||
#include <stdlib.h>
|
||||
#include <openssl/crypto.h>
|
||||
|
||||
#define PEERS_JOIN_SIG_SIZE 64
|
||||
#define PEERS_JOIN_TS_SIZE 8
|
||||
@@ -984,38 +985,50 @@ done:
|
||||
return result;
|
||||
}
|
||||
|
||||
int topo_node_sqlite_channel_get_priv(sqlite3* db, const char* channel_id,
|
||||
uint8_t* ed25519_priv_out) {
|
||||
if (!db || !channel_id || !ed25519_priv_out) return -1;
|
||||
sqlite3_stmt* stmt = NULL;
|
||||
if (sqlite3_prepare_v2(db,
|
||||
"SELECT ed25519_privkey FROM channels WHERE channel_id=?", -1, &stmt, NULL) != SQLITE_OK) return -1;
|
||||
sqlite3_bind_text(stmt, 1, channel_id, -1, SQLITE_STATIC);
|
||||
if (sqlite3_step(stmt) != SQLITE_ROW) { sqlite3_finalize(stmt); return -1; }
|
||||
const uint8_t* blob = (const uint8_t*)sqlite3_column_blob(stmt, 0);
|
||||
if (!blob || sqlite3_column_bytes(stmt, 0) < 32) { sqlite3_finalize(stmt); return -1; }
|
||||
memcpy(ed25519_priv_out, blob, 32);
|
||||
sqlite3_finalize(stmt);
|
||||
return 0;
|
||||
/* Выходные ключи публикуются только после успешного завершения SELECT. */
|
||||
static int channel_private_keys(sqlite3* db, const char* channel, uint8_t* x25519, uint8_t* ed25519) {
|
||||
if (!db || !channel || !ed25519) {
|
||||
DEBUG_ERROR(DEBUG_CATEGORY_BGP, "channel private keys: missing argument"); return -1;
|
||||
}
|
||||
const char* sql = x25519 ? "SELECT x25519_privkey, ed25519_privkey FROM channels WHERE channel_id=?" :
|
||||
"SELECT ed25519_privkey FROM channels WHERE channel_id=?";
|
||||
sqlite3_stmt* st = NULL; uint8_t keys[64] = {0}; int columns = x25519 ? 2 : 1, result = -1;
|
||||
const char* stage = "prepare"; int rc = sqlite3_prepare_v2(db, sql, -1, &st, NULL);
|
||||
if (rc != SQLITE_OK) goto done;
|
||||
stage = "bind"; rc = sqlite3_bind_text(st, 1, channel, -1, SQLITE_STATIC); if (rc != SQLITE_OK) goto done;
|
||||
stage = "step"; rc = sqlite3_step(st); if (rc != SQLITE_ROW) goto done;
|
||||
for (int i = 0; i < columns; i++) {
|
||||
if (sqlite3_column_type(st, i) == SQLITE_NULL) { rc = SQLITE_DONE; goto done; }
|
||||
stage = "key size/type"; rc = SQLITE_CORRUPT;
|
||||
if (sqlite3_column_type(st, i) != SQLITE_BLOB || sqlite3_column_bytes(st, i) != 32) goto done;
|
||||
const void* key = sqlite3_column_blob(st, i); if (!key) { rc = SQLITE_NOMEM; goto done; }
|
||||
memcpy(keys + i * 32, key, 32);
|
||||
}
|
||||
rc = SQLITE_OK;
|
||||
done:
|
||||
{
|
||||
int final = sqlite3_finalize(st);
|
||||
if (rc == SQLITE_OK && final == SQLITE_OK) {
|
||||
if (x25519) memcpy(x25519, keys, 32);
|
||||
memcpy(ed25519, keys + (columns - 1) * 32, 32); result = 0;
|
||||
} else if (rc == SQLITE_DONE && final == SQLITE_OK) {
|
||||
DEBUG_DEBUG(DEBUG_CATEGORY_BGP, "channel private keys unavailable channel=%s", channel);
|
||||
} else DEBUG_ERROR(DEBUG_CATEGORY_BGP, "channel private keys channel=%s stage=%s rc=%d finalize=%d: %s",
|
||||
channel, stage, rc, final, sqlite3_errmsg(db));
|
||||
}
|
||||
OPENSSL_cleanse(keys, sizeof(keys)); return result;
|
||||
}
|
||||
|
||||
int topo_node_sqlite_channel_get_priv(sqlite3* db, const char* channel_id, uint8_t* ed25519_priv_out) {
|
||||
return channel_private_keys(db, channel_id, NULL, ed25519_priv_out);
|
||||
}
|
||||
|
||||
int topo_node_sqlite_channel_get_privs(sqlite3* db, const char* channel_id,
|
||||
uint8_t* x25519_priv_out, uint8_t* ed25519_priv_out) {
|
||||
if (!db || !channel_id || !x25519_priv_out || !ed25519_priv_out) return -1;
|
||||
sqlite3_stmt* stmt = NULL;
|
||||
if (sqlite3_prepare_v2(db,
|
||||
"SELECT x25519_privkey, ed25519_privkey FROM channels WHERE channel_id=?", -1, &stmt, NULL) != SQLITE_OK) return -1;
|
||||
sqlite3_bind_text(stmt, 1, channel_id, -1, SQLITE_STATIC);
|
||||
if (sqlite3_step(stmt) != SQLITE_ROW) { sqlite3_finalize(stmt); return -1; }
|
||||
const uint8_t* x = (const uint8_t*)sqlite3_column_blob(stmt, 0);
|
||||
const uint8_t* e = (const uint8_t*)sqlite3_column_blob(stmt, 1);
|
||||
if (!x || sqlite3_column_bytes(stmt, 0) < 32 || !e || sqlite3_column_bytes(stmt, 1) < 32) {
|
||||
sqlite3_finalize(stmt); return -1;
|
||||
if (!x25519_priv_out) {
|
||||
DEBUG_ERROR(DEBUG_CATEGORY_BGP, "channel private keys: X25519 output missing"); return -1;
|
||||
}
|
||||
memcpy(x25519_priv_out, x, 32);
|
||||
memcpy(ed25519_priv_out, e, 32);
|
||||
sqlite3_finalize(stmt);
|
||||
return 0;
|
||||
return channel_private_keys(db, channel_id, x25519_priv_out, ed25519_priv_out);
|
||||
}
|
||||
|
||||
/* Импорт пары: savepoint охватывает UPDATE и его завершение, внешний commit остаётся вызывающему. */
|
||||
|
||||
@@ -122,11 +122,13 @@ int topo_node_sqlite_channel_get(sqlite3* db, const char* channel_id,
|
||||
char* name_out, int name_sz, uint64_t* owner_node_id,
|
||||
uint8_t* x25519_pub, uint8_t* ed25519_pub, uint8_t* signature);
|
||||
|
||||
/* Прочитать первые 32 байта Ed25519 privkey; 0/-1, отсутствующий/короткий ключ даёт -1. */
|
||||
/* Прочитать Ed25519 privkey ровно32 байта BLOB; 0/-1, отсутствие/повреждение/SQL-ошибка даёт -1.
|
||||
* Проверяются bind/step/finalize; при отказе выход не меняется. */
|
||||
int topo_node_sqlite_channel_get_priv(sqlite3* db, const char* channel_id,
|
||||
uint8_t* ed25519_priv_out);
|
||||
|
||||
/* Прочитать оба канальных privkey (x25519 + ed25519). Возвращает 0 если оба есть. */
|
||||
/* Прочитать оба канальных privkey (x25519 + ed25519), каждый ровно32 байта BLOB.
|
||||
* 0 — оба прочитаны, -1 — отказ с сохранением обоих выходов. Временные ключи очищаются. */
|
||||
int topo_node_sqlite_channel_get_privs(sqlite3* db, const char* channel_id,
|
||||
uint8_t* x25519_priv_out, uint8_t* ed25519_priv_out);
|
||||
|
||||
|
||||
Reference in new issue
Block a user