383 lines
28 KiB
C
383 lines
28 KiB
C
/*
|
||
* 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;
|
||
|
||
// ── Жизненный цикл ──
|
||
|
||
// Запустить чат поверх 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.
|
||
* Для запуска всего чат-сервиса использовать 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-отказ ставит повтор канала. */
|
||
void chat_media_autodownload_backfill(struct UTUN_INSTANCE* inst);
|
||
|
||
/* Подписать завершения Merkle: первый обмен запускает общий scan после 2с тишины; отдельные каналы — batch до16 через2с.
|
||
* Повтор индекса не отменяет MSG_RECEIVED committed-истории; следующий batch может быть поставлен во время текущего.
|
||
* При переполнении очереди и для восстановления допускаются последующие общие scan. Destroy снимает подписки/таймеры. */
|
||
void 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 */
|
||
};
|
||
|
||
// Подписать, сохранить и разослать сообщение. req/data остаются у вызывающего. 0/-1.
|
||
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 запускает транскодирование. Ошибка также приходит событием. */
|
||
void chat_core_submit_media_message(struct UTUN_INSTANCE* inst, struct chat_msg_submit* req);
|
||
|
||
/* Ручная загрузка по локальному номеру строки; внешняя 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.
|
||
* Списки ограничены буфером; out_len — записанные байты, числа в порядке байтов хоста. */
|
||
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; buf_size >= 2. 0/-1.
|
||
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 байта) и список адресов из БД; buf_size >= 65.
|
||
* out_len — размер результата; 0 = прочитано, -1 = нет узла или ошибка. */
|
||
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);
|
||
|
||
// ── Утилиты ──
|
||
|
||
// Задать локальный ID в контексте чата; ключи и идентичность ядра не меняет.
|
||
void chat_core_set_my_node_id(struct UTUN_INSTANCE* inst, uint64_t node_id);
|
||
|
||
// Сохранить имя узла и обновить собственные member-записи каналов.
|
||
void 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 в ui_state (вызывается из uasync-потока)
|
||
void 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).
|
||
* set — сохраняет и рассылает пирам через chat_sync (троттлинг db_sync на сервере). */
|
||
int chat_core_get_sound_on_message(struct UTUN_INSTANCE* inst, const char* ch_id);
|
||
|
||
void 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);
|
||
|
||
struct save_ui_state_arg { struct UTUN_INSTANCE* inst; char data[1024]; };
|
||
void chat_core_save_ui_state_trampoline(void* arg);
|
||
|
||
// Установка chat-настройки (name=value) через chat_setting API (GUI → uasync)
|
||
void chat_core_set_setting(struct UTUN_INSTANCE* inst, const char* name, const char* value);
|
||
|
||
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 для записи (вызывается из uasync-потока)
|
||
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 в 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:arg — заимствованный inst,ответ STATUS_REFRESH без конечного NUL.
|
||
* Динамический буфер живёт до возврата обработчика;отказ памяти/форматирования логируется без частичного события. */
|
||
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; };
|
||
|
||
/* Снять текстовые метрики соединения:arg — u_malloc chat_node_arg,забирается/освобождается,inst заимствован.
|
||
* Ответ CONN_METRICS без NUL,динамический буфер только до возврата обработчика;при OOM нет частичного события. */
|
||
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;
|
||
|
||
// Загрузить текущие adm_tags мембера в память. Возвращает NULL если мембера нет.
|
||
struct chat_member_tags* chat_member_tags_load(struct UTUN_INSTANCE* inst, const char* ch_id, uint64_t node_id);
|
||
|
||
// Установить ключ. val=NULL → удалить ключ. ver — особый, автоинкремент при commit.
|
||
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);
|
||
|
||
// Заменить все теги данными из JSON-строки (плоский, строковые значения).
|
||
int chat_member_tags_replace(struct chat_member_tags* t, const char* json);
|
||
|
||
/* Сохранить: сериализовать в JSON, инкрементировать ver, подписать канальным Ed25519,
|
||
* записать через member_sync_put() → синхронизируется всем. */
|
||
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: arg = struct chat_member_tags_req* (доверенная память).
|
||
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;
|
||
};
|
||
|
||
#endif /* CHAT_CORE_H */
|