429 lines
34 KiB
C
429 lines
34 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;
|
||
|
||
#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 */
|