|
|
|
|
@ -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; |
|
|
|
|
|