Add atomic updates for signed member profile options
This commit is contained in:
1 parent
ec82d95a03
commit
a33666b778
2 files changed
+60
-6
No files matched your search
@@ -248,6 +248,12 @@ int chat_core_set_my_avatar(struct UTUN_INSTANCE* inst, const char* ch_id, uint6
|
||||
int chat_core_name_userinfo(const char* name, char* out, size_t capacity);
|
||||
int chat_core_get_my_profile(struct UTUN_INSTANCE* inst, const char* channel, char* out, size_t capacity,
|
||||
uint8_t signature[64], uint64_t* timestamp);
|
||||
/* Одно подписанное обновление нескольких собственных строковых опций. NULL value удаляет ключ.
|
||||
* Остальные поля сохраняются; имя/аватар меняются своими API. Ошибка не меняет БД/Merkle.
|
||||
* Вызов только в uasync-потоке, вне внешней SQLite-транзакции. */
|
||||
struct chat_profile_option { const char* key; const char* value; };
|
||||
int chat_core_set_my_profile_options(struct UTUN_INSTANCE* inst, const char* channel,
|
||||
const struct chat_profile_option* options, size_t count);
|
||||
struct chat_avatar_req {
|
||||
struct UTUN_INSTANCE* inst;
|
||||
uint64_t id, group, server;
|
||||
|
||||
+54
-6
@@ -49,7 +49,13 @@ void chat_core_update_my_name_trampoline(void* arg) {
|
||||
/* ── Обновление своего мембера (userinfo + update_ts + подпись + синк).
|
||||
Адреса больше не здесь — их синхронизирует BGP/topo_group. ── */
|
||||
|
||||
struct profile_json { char text[MS_USERINFO_MAX + 1]; size_t used; int avatar; };
|
||||
struct profile_json {
|
||||
char text[MS_USERINFO_MAX + 1];
|
||||
size_t used;
|
||||
int avatar;
|
||||
const struct chat_profile_option* options;
|
||||
size_t option_count;
|
||||
};
|
||||
|
||||
static int profile_string(struct profile_json* p, const char* text) {
|
||||
if (p->used >= MS_USERINFO_MAX) return -1;
|
||||
@@ -82,6 +88,7 @@ static int profile_pair(struct profile_json* p, const char* key, const char* val
|
||||
static int profile_copy_pair(const char* key, const char* value, void* arg) {
|
||||
struct profile_json* p = arg;
|
||||
if (!strcmp(key, "name") || (p->avatar && (!strcmp(key, "avatar_server") || !strcmp(key, "avatar_file")))) return 0;
|
||||
for (size_t i = 0; i < p->option_count; i++) if (!strcmp(key, p->options[i].key)) return 0;
|
||||
return profile_pair(p, key, value);
|
||||
}
|
||||
|
||||
@@ -117,7 +124,9 @@ int chat_core_get_my_profile(struct UTUN_INSTANCE* inst, const char* channel, ch
|
||||
return result;
|
||||
}
|
||||
|
||||
static int profile_update_channel(struct UTUN_INSTANCE* inst, const char* ch, int avatar, uint64_t server, const char* file) {
|
||||
/* Обновить профиль целиком одной подписанной записью, сохранив не затронутые поля. */
|
||||
static int profile_update_channel(struct UTUN_INSTANCE* inst, const char* ch, int avatar, uint64_t server, const char* file,
|
||||
const struct chat_profile_option* options, size_t option_count) {
|
||||
struct chat_core_ctx* cc = CC(inst);
|
||||
if (!cc || !cc->initialized || !cc->db) {
|
||||
DEBUG_ERROR(DEBUG_CATEGORY_CHAT, "profile: update before initialization"); return -1;
|
||||
@@ -128,7 +137,7 @@ static int profile_update_channel(struct UTUN_INSTANCE* inst, const char* ch, in
|
||||
if (topo_node_sqlite_member_get_join(cc->db, ch, myid, join_sig, &join_ts) != 0) {
|
||||
DEBUG_WARN(DEBUG_CATEGORY_CHAT, "profile: own signed member missing channel=%s", ch); return -1;
|
||||
}
|
||||
struct profile_json profile = { .text = "{", .used = 1, .avatar = avatar };
|
||||
struct profile_json profile = { .text = "{", .used = 1, .avatar = avatar, .options = options, .option_count = option_count };
|
||||
if (profile_pair(&profile, "name", inst->name) < 0) {
|
||||
DEBUG_ERROR(DEBUG_CATEGORY_CHAT, "profile: display name exceeds profile limit channel=%s", ch); return -1;
|
||||
}
|
||||
@@ -144,11 +153,23 @@ static int profile_update_channel(struct UTUN_INSTANCE* inst, const char* ch, in
|
||||
}
|
||||
uint64_t local_uts = (uint64_t)sqlite3_column_int64(st, 0);
|
||||
const char* old = (const char*)sqlite3_column_text(st, 1);
|
||||
char before[MS_USERINFO_MAX + 1] = "";
|
||||
if (old && strlen(old) > MS_USERINFO_MAX) {
|
||||
DEBUG_ERROR(DEBUG_CATEGORY_CHAT, "profile: stored attributes exceed limit channel=%s", ch);
|
||||
sqlite3_finalize(st); return -1;
|
||||
}
|
||||
if (old) memcpy(before, old, strlen(old) + 1);
|
||||
if (old && old[0] && json_flat_parse(old, profile_copy_pair, &profile) < 0) {
|
||||
DEBUG_ERROR(DEBUG_CATEGORY_CHAT, "profile: cannot preserve attributes channel=%s", ch);
|
||||
sqlite3_finalize(st); return -1;
|
||||
}
|
||||
sqlite3_finalize(st);
|
||||
for (size_t i = 0; i < option_count; i++) {
|
||||
if (options[i].value && profile_pair(&profile, options[i].key, options[i].value) < 0) {
|
||||
DEBUG_ERROR(DEBUG_CATEGORY_CHAT, "profile: option exceeds profile limit channel=%s key=%s", ch, options[i].key);
|
||||
return -1;
|
||||
}
|
||||
}
|
||||
if (avatar && server) {
|
||||
char id[17]; snprintf(id, sizeof(id), "%016llx", (unsigned long long)server);
|
||||
if (profile_pair(&profile, "avatar_server", id) < 0 || profile_pair(&profile, "avatar_file", file) < 0) {
|
||||
@@ -159,7 +180,12 @@ static int profile_update_channel(struct UTUN_INSTANCE* inst, const char* ch, in
|
||||
DEBUG_ERROR(DEBUG_CATEGORY_CHAT, "profile: attributes exceed profile limit channel=%s", ch); return -1;
|
||||
}
|
||||
profile.text[profile.used++] = '}'; profile.text[profile.used] = 0;
|
||||
uint64_t now = (uint64_t)ntp_time_get_seconds(inst);
|
||||
if (option_count && !strcmp(before, profile.text)) {
|
||||
DEBUG_DEBUG(DEBUG_CATEGORY_CHAT, "profile: options unchanged channel=%s", ch); return 0;
|
||||
}
|
||||
if (local_uts >= INT64_MAX) { DEBUG_ERROR(DEBUG_CATEGORY_CHAT, "profile: version exhausted channel=%s", ch); return -1; }
|
||||
time_t seconds = ntp_time_get_seconds(inst);
|
||||
uint64_t now = seconds > 0 ? (uint64_t)seconds : 0;
|
||||
uint64_t update_ts = now > local_uts ? now : local_uts + 1;
|
||||
uint8_t umsg[MS_USERINFO_MAX + 73], update_sig[64];
|
||||
int ulen = member_sync_build_update_msg(join_sig, update_ts, profile.text, umsg, sizeof(umsg));
|
||||
@@ -174,6 +200,28 @@ static int profile_update_channel(struct UTUN_INSTANCE* inst, const char* ch, in
|
||||
return 0;
|
||||
}
|
||||
|
||||
/* Проверить заменяемые ключи до построения/подписи нового блока мембера. */
|
||||
int chat_core_set_my_profile_options(struct UTUN_INSTANCE* inst, const char* channel,
|
||||
const struct chat_profile_option* options, size_t count) {
|
||||
if (!inst || !channel || !channel[0] || !options || !count || count > MS_USERINFO_MAX / 4) {
|
||||
DEBUG_ERROR(DEBUG_CATEGORY_CHAT, "profile: invalid options update count=%zu", count); return -1;
|
||||
}
|
||||
if (!chat_core_get_db(inst) || !sqlite3_get_autocommit(chat_core_get_db(inst))) {
|
||||
DEBUG_ERROR(DEBUG_CATEGORY_CHAT, "profile: options update requires initialized DB without outer transaction"); return -1;
|
||||
}
|
||||
for (size_t i = 0; i < count; i++) {
|
||||
const char* key = options[i].key;
|
||||
if (!key || !key[0] || strlen(key) > MS_USERINFO_MAX || (options[i].value && strlen(options[i].value) > MS_USERINFO_MAX)
|
||||
|| !strcmp(key, "name") || !strcmp(key, "avatar_server") || !strcmp(key, "avatar_file")) {
|
||||
DEBUG_ERROR(DEBUG_CATEGORY_CHAT, "profile: invalid/reserved option index=%zu key=%s", i, key ? key : "null"); return -1;
|
||||
}
|
||||
for (size_t j = 0; j < i; j++) if (!strcmp(key, options[j].key)) {
|
||||
DEBUG_ERROR(DEBUG_CATEGORY_CHAT, "profile: duplicate option key=%s", key); return -1;
|
||||
}
|
||||
}
|
||||
return profile_update_channel(inst, channel, 0, 0, NULL, options, count);
|
||||
}
|
||||
|
||||
void chat_core_update_my_member(struct UTUN_INSTANCE* inst) {
|
||||
struct chat_core_ctx* cc = CC(inst);
|
||||
if (!cc || !cc->initialized || !cc->db) return;
|
||||
@@ -183,7 +231,7 @@ void chat_core_update_my_member(struct UTUN_INSTANCE* inst) {
|
||||
}
|
||||
while (sqlite3_step(cs) == SQLITE_ROW) {
|
||||
const char* ch = (const char*)sqlite3_column_text(cs, 0);
|
||||
if (ch) profile_update_channel(inst, ch, 0, 0, NULL);
|
||||
if (ch) profile_update_channel(inst, ch, 0, 0, NULL, NULL, 0);
|
||||
}
|
||||
sqlite3_finalize(cs);
|
||||
}
|
||||
@@ -201,7 +249,7 @@ int chat_core_set_my_avatar(struct UTUN_INSTANCE* inst, const char* ch_id, uint6
|
||||
DEBUG_WARN(DEBUG_CATEGORY_CHAT, "profile: avatar server has no storage role channel=%s", ch_id); return -1;
|
||||
}
|
||||
}
|
||||
int rc = profile_update_channel(inst, ch_id, 1, server, file);
|
||||
int rc = profile_update_channel(inst, ch_id, 1, server, file, NULL, 0);
|
||||
if (!rc) DEBUG_INFO(DEBUG_CATEGORY_CHAT, "profile: avatar published channel=%s server=%016llx file=%s",
|
||||
ch_id, (unsigned long long)server, server ? file : "none");
|
||||
return rc;
|
||||
|
||||
Reference in new issue
Block a user