Files
utun2/src/chat/chat_core.h
T

429 lines
34 KiB
C
Raw Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
/*
* chat_core — API каналов, сообщений, профиля и настроек одного узла.
* chat_service_start/stop управляют всем чат-сервисом поверх работающего ядра.
* Данные хранятся в общей SQLite; события для интерфейса доставляет chat_event.
* Порядок: utun_core_start → chat_service_start → команды чата → chat_service_stop.
* Сообщения отправляются через submit_message или submit_media_message; результат GUI — CHAT_EVT_MSG_SUBMITTED.
*
* Все DB-операции и сетевые функции выполняются в uasync-потоке.
* Все функции per-instance: первым аргументом struct UTUN_INSTANCE*.
* GUI вызывает через gui_bridge_post_uasync() trampoline-функции,
* чей arg-структура несёт inst.
*/
#ifndef CHAT_CORE_H
#define CHAT_CORE_H
#include <stdint.h>
#include <stddef.h>
#include <sqlite3.h>
struct sqlite3;
struct UTUN_INSTANCE;
#ifdef __cplusplus
extern "C" {
#endif
// ── Жизненный цикл ──
// Запустить чат поверх core_start; UTUN не требуется. Повтор допустим; 0/-1.
int chat_service_start(struct UTUN_INSTANCE* inst);
/* Остановить чат, сохранив ядро, UTUN и БД. Повтор допустим; вызывать вне callbacks чата.
* Отменяет доставку и ждёт native media workers; длительная задача может задержать stop. */
void chat_service_stop(struct UTUN_INSTANCE* inst);
/* Внутренняя часть запуска:заимствовать БД работающего ядра,сохранить метаданные и подготовить каналы. 0/-1.
* Собственная транзакция включает schema/identity/media_base и установку обязательных подписок;чужая запрещена.
* db_path<=511 байт задаёт default media base,отдельная БД не открывается. Готовность публикуется после commit/activation.
* Activation error сохраняет committed метаданные;caller откатывает runtime через chat_service_stop и может повторить старт.
* Для запуска всего чат-сервиса использовать chat_service_start(). */
int chat_core_init(struct UTUN_INSTANCE* inst, const char* db_path);
// Освободить chat_core после остановки остальных частей чата; общая БД остаётся у ядра.
void chat_core_destroy(struct UTUN_INSTANCE* inst);
/* Startup: восстановить производный attachment-индекс из подписанной истории и зарегистрировать сохранённые медиа.
* Вызывать после инициализации attachment и startup_backfill, чтобы SQL-отказ мог поставить повтор. Ключи из истории не восстанавливаются. */
void chat_media_backfill(struct UTUN_INSTANCE* inst);
/* Докачка недостающего медиа за последние storage_backfill_days дней и применение квоты.
* Также восстанавливает attachment-индекс/доступность сохранённых контейнеров; SQL-отказ ставит повтор.
* Перед удалением по квоте/очисткой сирот требуется полный снимок ссылок;ошибка SQL/памяти отменяет удаление.
* Один путь учитывается один раз,с самой новой ссылкой;derived plaintext входит в общий кеш. */
void chat_media_autodownload_backfill(struct UTUN_INSTANCE* inst);
/* Подписать завершения Merkle: первый обмен запускает общий scan после 2с тишины; отдельные каналы — batch до16 через2с.
* Повтор индекса не отменяет MSG_RECEIVED committed-истории; следующий batch может быть поставлен во время текущего.
* При переполнении очереди и для восстановления допускаются последующие общие scan.
* init:0 — обе подписки установлены/повтор,-1 — отказ с отменой частичной установки.
* Destroy снимает подписки/таймеры;вызывать до освобождения chat_core. */
int chat_media_startup_backfill_init(struct UTUN_INSTANCE* inst);
void chat_media_startup_backfill_destroy(struct UTUN_INSTANCE* inst);
// Заимствованная БД текущего chat_core; NULL до init/после destroy.
struct sqlite3* chat_core_get_db(struct UTUN_INSTANCE* inst);
// 1 — chat_core инициализирован, 0 — нет; не проверяет доступность сети.
int chat_core_is_initialized(struct UTUN_INSTANCE* inst);
// ── Отправка сообщения (GUI → uasync) ──
struct chat_msg_submit {
struct UTUN_INSTANCE* inst;
char channel_id[64]; /* десятичный ID канала */
char content_type[32]; /* тип содержимого сообщения */
char media_src[1024]; /* исходный файл вложения */
char media_dest[1024]; /* путь подготовленного локального файла */
uint8_t media_copy; /* 1 = скопировать исходный файл при регистрации */
uint8_t media_video; /* 1 = подготовить как видео (пробинг+транскод) */
int duration_ms; /* видео: длительность (0 = неизвестно) */
int width; /* видео: ширина */
int height; /* видео: высота */
uint8_t* data; /* содержимое сообщения, заимствовано обычным submit */
uint32_t data_len; /* размер data в байтах */
uint64_t timestamp; /* успешная отправка возвращает назначенное время */
uint8_t message_id[32]; /* успешная обычная отправка возвращает общий ID */
uint64_t expected_ver; /* >0: trampoline редактирует message_id вместо новой отправки. */
uint8_t reply_to[32]; /* общий ID оригинала в этом канале; нули = не ответ */
uint64_t request_id; /* ненулевой ID: trampoline/media отправляет CHAT_EVT_MSG_SUBMITTED */
};
/* Подписать,сохранить и разослать:0/-1,req/data остаются у caller.
* channel/content_type — NUL-строки в пределах массивов,канал канонический;data<=4000 байт без NUL.
* Итоговый экранированный JSON также должен помещаться в4000 байт. SQL JSON/bind/finalize проверяются до подписи/записи. */
int chat_core_submit_message(struct UTUN_INSTANCE* inst, struct chat_msg_submit* req);
/* Правка собственного text: ожидаемая версия защищает открытый редактор от устаревшего сохранения.
* 0 — сохранено/текст прежний, -1 — ошибка, -2 — версия изменилась. ID и порядок сообщения сохраняются. */
int chat_core_edit_message(struct UTUN_INSTANCE* inst, const char* channel, const uint8_t message_id[32],
uint64_t expected_ver, const char* text, size_t len);
/* Подготовить файл в worker, затем сохранить и разослать сообщение; результат — CHAT_EVT_MSG_SUBMITTED при request_id!=0.
* req и data копируются до возврата и остаются у вызывающего; файлы должны жить до завершения подготовки.
* media_copy копирует src в dest, media_video запускает транскодирование. Ошибка также приходит событием.
* Необязательный complete(arg,success) вызывается ровно один раз после сохранения/отказа и освобождения задачи,
* в том числе синхронно при отказе запуска;arg принадлежит caller и живёт до callback. */
typedef void (*chat_media_submitted_fn)(void* arg, int success);
void chat_core_submit_media_message(struct UTUN_INSTANCE* inst, struct chat_msg_submit* req,
chat_media_submitted_fn complete, void* arg);
/* Ручная загрузка по локальному номеру строки; внешняя SQL-транзакция запрещена.
* Manifest ограничен общим DB_SYNC_MESSAGE_MAX. Зашифрованный индекс восстанавливается из истории до wanted/загрузки;
* отсутствие plaintext сбрасывает ready с сохранением ключа. Отказ SQL логируется, состояние модуля атомарно сохраняется. */
void chat_core_attachment_download(struct UTUN_INSTANCE* inst, const char* channel_id, int64_t msg_id);
// ── DB-операции (оставлены для интроспекции) ──
// Число локальных сообщений; 0 также при отсутствии БД или ошибке запроса.
uint32_t chat_core_count(struct UTUN_INSTANCE* inst, const char* ch_id);
/* Список ID: count:u16, затем [длина:u8, строка]; buf_size >= 2. 0/-1.
* Списки ограничены буфером/count<=65535;out_len — записанные байты,числа native.
* SQL читается целиком даже после заполнения буфера;отказ сохраняет buf и обнуляет допустимый out_len.
* Каналы идут по created_at,channel_id. */
int chat_core_list_channels(struct UTUN_INSTANCE* inst, uint8_t* buf, size_t buf_size, size_t* out_len);
// Неудалённые участники:count:u16,node_id:u64;unsigned ID по возрастанию;канал канонический,остальной контракт как у list_channels.
int chat_core_list_peers(struct UTUN_INSTANCE* inst, const char* ch_id, uint8_t* buf, size_t buf_size, size_t* out_len);
/* Ключи X25519/Ed25519 (по32 байта,NULL в БД→нули),count:u8,затем адреса:
* family:u8,protocol:u8,address_len:u8,address[4/16],port:u16,rtt:i16;числа native,RTT насыщается.
* buf_size>=65;не более255 целых первых адресов в детерминированном порядке БД.
* 0 — прочитано,-1 — нет узла/ошибка,buf сохраняется,out_len=0. Проверяется весь SQL-снимок. */
int chat_core_load_nodeinfo(struct UTUN_INSTANCE* inst, uint64_t node_id, uint8_t* buf, size_t buf_size, size_t* out_len);
// ── Создание канала (GUI → uasync) ──
struct chat_channel_create {
struct UTUN_INSTANCE* inst; /* Заимствован trampoline; прямой вызов использует свой аргумент inst. */
char channel_id[64]; /* NUL-строка: каноническое десятичное число 1..INT64_MAX. */
char name[128]; /* NUL-строка до127 байт, без молчаливого обрезания. */
uint64_t owner_node_id; /* node_id создающего instance. */
uint8_t x25519_pubkey[32]; /* Канальный ключ шифрования; должен соответствовать privkey. */
uint8_t x25519_privkey[32];/* Независимый приватный X25519-ключ канала. */
uint8_t ed25519_pubkey[32];/* Канальный ключ подписи; должен соответствовать raw seed. */
uint8_t ed25519_privkey[32];/* Raw Ed25519 seed, без преобразования sc_derive_ed25519_pubkey. */
uint8_t signature[64]; /* Подпись id\0 || name\0 || owner(native u64) || X25519 || Ed25519. */
};
struct chat_create_auto_arg { struct UTUN_INSTANCE* inst; char name[128]; };
/* Подготовить таблицы, группу и синхронизацию; 0/-1. Ошибка удаляет новые runtime-ресурсы,
* проверенные данные БД сохраняются. Членства на других узлах не даёт. */
int chat_core_ensure_channel_ready(struct UTUN_INSTANCE* inst, const char* ch_id);
/* Создать новый канал:проверить пары ключей/подпись,одним commit сохранить описание,root JOIN/профиль/приглашение и Merkle.
* 0 — сохранено,-1 — отказ без публикации;req заимствован и не меняется. Владелец — этот instance.
* Внешняя транзакция и занятый ID запрещены. CHANNEL_UPDATED — после commit и попытки runtime-подготовки.
* Отказ подготовки не отменяет созданный канал:можно повторить ensure_channel_ready. Неудачный rollback
* с открытой транзакцией запрашивает stop/закрытие БД владельцем вне callback. */
int chat_core_create_channel(struct UTUN_INSTANCE* inst, const struct chat_channel_create* req);
// Забирает u_malloc-структуру chat_channel_create, очищает ключи и освобождает её после вызова.
void chat_core_create_channel_trampoline(void* arg);
/* Сгенерировать независимые пары X25519/Ed25519 и создать канал. ID>0 — сохранён,0 — отказ.
* name — NUL-строка<=127байт,не обрезается. Проверяет RNG/генерацию/извлечение/подписи;
* временные приватные ключи очищаются. Внешняя транзакция отклоняется до генерации. */
uint64_t chat_core_create_channel_auto(struct UTUN_INSTANCE* inst, const char* name);
// Забирает и освобождает u_malloc-структуру chat_create_auto_arg; instance должен дожить до выполнения.
void chat_core_create_channel_auto_trampoline(void* arg);
// ── Локальное удаление канала (GUI → uasync) ──
struct chat_delete_channel_req {
struct UTUN_INSTANCE* inst;
char ch_id[64];
};
/* Удалить канал с этого устройства: отмена медиа-загрузок, удаление группы
* (соединения/синк), db_sync инстанса, таблиц msg_ и peers_, медиафайлов и
* merkle-хешей. Работает в uasync-потоке. Другие узлы канал не теряют.
* Исходящие video/register задачи сначала завершаются; удаление продолжит последний completion.
* Новые исходящие медиа этого канала в это время отклоняются. Внешняя SQL-транзакция запрещена до отмены ресурсов.
* Ключи/запросы/outbox удаляются триггером атомарно с метаданными; при SQL-ошибке данные и файлы сохраняются,
* сетевые ресурсы уже могут быть отменены — требуется повтор. Открытая после ошибки транзакция требует stop владельцем.
* CHANNEL_DELETED приходит после commit метаданных и попытки файловой очистки. Её I/O-ошибка логируется отдельно;
* остатки media_files сохраняются, собственный каталог plaintext можно очистить повторно после commit. */
void chat_core_delete_channel(struct UTUN_INSTANCE* inst, const char* ch_id);
void chat_core_delete_channel_trampoline(void* arg);
// ── Утилиты ──
/* Имя (NUL,<=127байт,включая пустое):0 — committed/no-op,-1 — отказ без изменения runtime.
* Один BEGIN IMMEDIATE сохраняет local_identity,nodes,подписанный NODEINFO и собственные полные member-блоки/Merkle.
* Ожидающий JOIN без полного member пропускается. No-op не меняет версии и не вызывает commit hook.
* Только после commit обновляются inst/LAN и идут NODEINFO/member уведомления;внешняя транзакция запрещена.
* Неудачный rollback с открытой транзакцией запрашивает stop/закрытие БД владельцем. Конфиг не сохраняется. */
int chat_core_update_my_name(struct UTUN_INSTANCE* inst, const char* name);
struct update_my_name_arg { struct UTUN_INSTANCE* inst; char name[128]; };
void chat_core_update_my_name_trampoline(void* arg);
// Обновить адреса topo_node и собственные member-записи каналов.
void chat_core_sync_my_addresses(struct UTUN_INSTANCE* inst);
/* При изменении своего userinfo в каналах увеличить update_ts, подписать и запустить sync.
* Адреса обновляются отдельно через topo_node; подпись member-блока их не содержит. */
void chat_core_update_my_member(struct UTUN_INSTANCE* inst);
/* Сохранить key/value в uasync:0 — committed/no-op,-1 — отказ;key непустой,value допускает пустую строку.
* Собственная транзакция проверяет все SQL-этапы;внешняя запрещена,no-op не вызывает commit hook.
* Ошибка сохраняет прежнее значение;unrecovered rollback запрашивает owner stop/DB close. */
int chat_core_save_ui_state(struct UTUN_INSTANCE* inst, const char* key, const char* value);
/* Per-channel «play sound on new message» (ui_state: ch_<id>_sound_on_message, default 1=on).
* get возвращает0/1,-1 при ошибке;отсутствие записи —1. set возвращает0/-1,
* после собственного commit/no-op рассылает желаемое значение через chat_sync best effort.
* SQL-отказ не рассылается,идентичный повтор повторяет доставку. ch_id — канонический ID канала. */
int chat_core_get_sound_on_message(struct UTUN_INSTANCE* inst, const char* ch_id);
int chat_core_set_sound_on_message(struct UTUN_INSTANCE* inst, const char* ch_id, int on);
struct chat_sound_arg { struct UTUN_INSTANCE* inst; char ch_id[64]; int on; };
void chat_core_set_sound_on_message_trampoline(void* arg);
/* Полный request принадлежит trampoline/u_free;data содержит key+NUL,value+NUL в пределах массива. */
struct save_ui_state_arg { struct UTUN_INSTANCE* inst; char data[1024]; };
void chat_core_save_ui_state_trampoline(void* arg);
/* Установить через chat_setting:0 — успех,-1 — неизвестное имя,-2 — валидация,-3 — LAN apply pending.
* Только успешный group_autoconnect запускает пересмотр групп;остальные эффекты — в chat_setting.h. */
int chat_core_set_setting(struct UTUN_INSTANCE* inst, const char* name, const char* value);
/* Trampoline владеет полным request/u_free;data — name+NUL,value+NUL,обе строки проверяются в границах. */
struct chat_setting_arg { struct UTUN_INSTANCE* inst; char data[256]; };
void chat_core_set_setting_trampoline(void* arg);
// Запуск подключения к узлам выбранного канала (GUI → uasync)
void chat_core_connect_channel(struct UTUN_INSTANCE* inst, const char* ch_id);
void chat_core_connect_channel_trampoline(void* arg);
// Однократная попытка подключения к конкретному узлу канала (GUI → uasync)
void chat_core_connect_node(struct UTUN_INSTANCE* inst, const char* ch_id, uint64_t node_id);
void chat_core_connect_node_trampoline(void* arg);
struct chat_connect_node_req {
struct UTUN_INSTANCE* inst;
char ch_id[64];
uint64_t node_id;
};
/* Передать в uasync блок chat_msg_submit, выделенный u_calloc/u_malloc; trampoline освобождает его.
* data отдельно не освобождается: удобно разместить содержимое в хвосте того же блока.
* Непустой media_src выбирает подготовку вложения, иначе — обычную отправку. Результат — по request_id. */
void chat_core_submit_trampoline(void* arg);
// Трамплин для ручного запуска загрузки медиа (JNI → uasync)
struct attachment_dl_req {
struct UTUN_INSTANCE* inst;
char channel_id[64];
int64_t msg_id;
};
void chat_core_attachment_download_trampoline(void* arg);
/* Заменить local_attrs указанной подписи/timestamp:0 — committed,-1 — отказ/нет записи/не JSON-object.
* Канал канонический,timestamp1..INT64_MAX,подпись64 байта. Собственная транзакция,чужая запрещена;
* ошибка bind/step/finalize/commit откатывается,незавершённый rollback запрашивает owner stop. */
int chat_core_update_local_attrs(struct UTUN_INSTANCE* inst, const char* ch_id, uint64_t ts,
const uint8_t* author_sig,
const char* local_attrs_json);
/* Добавить/заменить pl=1 через JSON,сохранив остальные local_attrs любой допустимой длины.
* NULL/пустые attrs становятся объектом;неверный объект не перезаписывается. Первая строка по id
* с timestamp/author;собственная транзакция как у update_local_attrs,результат/ошибки логируются. */
void chat_core_mark_voice_played(struct UTUN_INSTANCE* inst, const char* channel_id, uint64_t ts,
uint64_t author_node_id);
void chat_core_mark_voice_played_trampoline(void* arg);
struct voice_played_req {
struct UTUN_INSTANCE* inst;
char channel_id[64];
uint64_t ts;
uint64_t author_node_id;
};
/* Сбор статуса (NTP + connections) в uasync. arg — заимствованный inst.
* Ответ CHAT_EVT_STATUS_REFRESH: UTF-8 без завершающего NUL в длине; размер растёт по данным.
* Текст заимствован только на время синхронного event callback; отказ памяти/форматирования логируется без события. */
void chat_core_collect_status_trampoline(void* arg);
// Лёгкий запрос нагрузки uasync для GUI. arg = inst, ответ CHAT_EVT_CPU_LOAD.
void chat_core_collect_cpu_load_trampoline(void* arg);
/* Список до250 ETCP-соединений:arg — заимствованный inst,ответ CONN_LIST [count:u16 native][entry:46]*.
* Entry:peer:u64,flags:u8,links:u8,rtt:u16,inflight_KiB:u16,name:32;числа native,links/inflight насыщаются. */
void chat_core_collect_conn_list_trampoline(void* arg);
struct chat_node_arg { struct UTUN_INSTANCE* inst; uint64_t node_id; };
/* Полные метрики одного соединения, вызывается в uasync. arg — u_malloc struct chat_node_arg,
* trampoline освобождает arg; inst остаётся заимствованным. CHAT_EVT_CONN_METRICS содержит UTF-8
* без завершающего NUL в длине; буфер растёт по числу линков, при OOM нет частичного события.
* Данные ответа заимствованы только на время синхронного event callback. */
void chat_core_collect_conn_metrics_trampoline(void* arg);
/* Диагностика карточки участника: запрос/codec находятся в chat_member.h. */
// ── Headless CLI API ──
/* JSON-списки:0 — полный JSON с NUL,-1 — ошибка;out_len без NUL,при отказе0,допустимый buf очищается.
* buf/out_len обязательны,buf_size>=3. SQL/память/переполнение не дают успешный префикс;
* Строки экранируются целиком;channels/members отклоняют скрытый NUL в канальных/профильных SQLite TEXT.
* Каналы:[{id,name,owner_id,owner_name,peers,msgs,online}];отложенная history-таблица означает msgs=0. */
int chat_core_get_channels_json(struct UTUN_INSTANCE* inst, char* buf, size_t buf_size, size_t* out_len);
/* Сообщения канала в JSON: [{id,ts,author_id,author_name,content_type,data,local_attrs}].
* От новых к старым;count<=0 — до100 записей,offset>=0 — сколько пропустить.
* Канал — канонический ID1..INT64_MAX;общий контракт буфера как у channels_json. */
int chat_core_get_messages_json(struct UTUN_INSTANCE* inst, const char* ch_id, int count, int offset,
char* buf, size_t buf_size, size_t* out_len);
/* Мемберы в JSON: [{node_id,name,online,connected,adm_tags,x25519,ed25519,addrs}].
* connected означает наличие транспорта в реестре,а не его UP/готовность группы;deleted показывается offline.
* addrs.proto=UDP/TCP/TCP+REALITY,IPv4/IPv6 текстовые;канал/буфер как у messages/channels_json. */
int chat_core_get_members_json(struct UTUN_INSTANCE* inst, const char* ch_id, char* buf, size_t buf_size, size_t* out_len);
/* Имя nodes с NUL,при необходимости обрезанное до sz-1 байт:0 — прочитано/узел отсутствует,-1 — ошибка.
* out/sz>0 обязательны;отсутствие узла и ошибка оставляют пустую строку (если буфер допустим). */
int chat_core_get_node_name(struct UTUN_INSTANCE* inst, uint64_t node_id, char* out, size_t sz);
/* Ссылка avatars/<sha256>.json внутри дерева своего UID/канала;server=0 удаляет ссылку,file остаётся обязательной NUL-строкой.
* 0 — committed/семантически прежнее,-1 — отказ;чужая транзакция запрещена. Storage-роль сервера проверяется под блокировкой. */
int chat_core_set_my_avatar(struct UTUN_INSTANCE* inst, const char* ch_id, uint64_t server, const char* file);
/* Построить {name:string},включая Unicode/control escaping;0/-1,при ошибке out не меняется. */
int chat_core_name_userinfo(const char* name, char* out, size_t capacity);
/* Прочитать собственный подписанный профиль:0/-1,при отказе все выходы сохраняются.
* Проверяет SQL,размеры,JOIN/update-подписи и целый плоский JSON с уникальными строковыми полями.
* out требует capacity>=длина+1,signature64,timestamp — версия update_ts в секундах/монотонных шагах. */
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 защищены одним BEGIN IMMEDIATE.
* Семантический no-op сохраняет подпись/версию независимо от порядка ключей/escaping;ошибка не меняет БД/Merkle.
* Уведомления идут после commit,без SQLite cursor. Вызов в uasync вне чужой транзакции;
* неудачный rollback с открытой транзакцией запрашивает stop/закрытие БД владельцем. */
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;
char file[70];
};
void chat_core_set_my_avatar_trampoline(void* arg);
// ── Редактирование adm_tags (владелец канала) ──
#define CHAT_MEMBER_TAGS_MAX_KEYS 16
#define CHAT_MEMBER_TAGS_KEY_SZ 64
struct chat_member_tags;
/* Загрузить проверенный owner-снимок полного участника:NULL при отсутствии/placeholder/SQL/подписи/формате.
* Объект принадлежит caller до free,используется в uasync и освобождается до остановки chat-сервиса. */
struct chat_member_tags* chat_member_tags_load(struct UTUN_INSTANCE* inst, const char* ch_id, uint64_t node_id);
/* Изменить желаемое поле:0/-1;отказ не меняет editor. val=NULL удаляет поле;admin/moder принимают yes/no.
* Пустой ключ/ver запрещены,версию выбирает commit;место для ver резервируется в лимите16 ключей. */
int chat_member_tags_set(struct chat_member_tags* t, const char* key, const char* val);
// Получить значение ключа. Возвращает NULL если ключ не найден.
const char* chat_member_tags_get(struct chat_member_tags* t, const char* key);
/* Заменить желаемые теги:0/-1,при отказе editor не меняется. Весь объект<=8192 байта,уникальные
* непустые ключи,только строки;admin/moder yes/no. Необязательный ver обязан совпадать с исходным снимком. */
int chat_member_tags_replace(struct chat_member_tags* t, const char* json);
/* Собственный BEGIN IMMEDIATE проверяет исходный owner-снимок и фиксирует новую подпись/версию/Merkle.
* 0 — committed/no-op,-2 — конфликт с изменившимся owner-блоком,-1 — прочий отказ;чужая транзакция запрещена.
* Отказ сохраняет editor/БД,успех обновляет editor для следующей правки. No-op сохраняет версию/подпись.
* Версия1..INT_MAX;admin/moder сохраняют историю,отключение дописывает d<seconds>. Приватный ключ очищается.
* Уведомления после commit без cursor;при неудачном rollback запрашивается stop/закрытие БД владельцем. */
int chat_member_tags_commit(struct UTUN_INSTANCE* inst, struct chat_member_tags* t);
// Освободить без сохранения.
void chat_member_tags_free(struct chat_member_tags* t);
/* Трамплин для gui_bridge:владеет req/u_free. json — частичный объект желаемых полей,неуказанные сохраняются;
* необязательный ver проверяет снимок формы. Пустая строка — no-op;ошибки логируются без commit. */
struct chat_member_tags_req {
struct UTUN_INSTANCE* inst;
char ch_id[64];
uint64_t node_id;
char json[512];
};
void chat_member_tags_commit_trampoline(void* arg);
// ── Передача прав админа (владелец → другому узлу) ──
// Передать оба канальных privkey выбранному узлу (encrypted+signed через etcp_router).
// uasync-поток; NULL/неинициализированный inst отклоняется с ERROR. Постановка в транспорт не подтверждает импорт.
void chat_core_transfer_admin(struct UTUN_INSTANCE* inst, const char* ch_id, uint64_t target_node_id);
void chat_core_transfer_admin_trampoline(void* arg);
struct chat_transfer_admin_req {
struct UTUN_INSTANCE* inst;
char ch_id[64];
uint64_t target_node_id;
};
#ifdef __cplusplus
}
#endif
#endif /* CHAT_CORE_H */