Files
utun2/src/chat/chat_core_priv.h
T

176 lines
12 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_priv.h — внутренний заголовок для под-модулей chat_core
*
* Предоставляет per-instance chat_core_ctx через CC(inst), общие SQL/сериализационные хелперы
* и внутренние операции частей chat_core/member_sync. Не является frontend API.
* Все обращения — в uasync; контекст создаёт/освобождает chat_core. Сначала закрываются
* приём команд, callbacks и media workers, затем освобождается контекст; БД остаётся ядру.
*/
#ifndef CHAT_CORE_PRIV_H
#define CHAT_CORE_PRIV_H
#include "chat_core.h"
#include "db_sync.h"
#include "../../lib/debug_config.h"
#include <sqlite3.h>
#include <string.h>
#include <stdio.h>
#include <stdint.h>
#define CC_ID "chat_core"
#define CHAT_MEDIA_BACKFILL_CHANNELS 16
#define CHAT_CHANNEL_DESCRIPTION_MAX (20 + 256 + 8 + 32 + 32)
/* Base64url для префикса поля d:заимствованные непересекающиеся буферы.
* encode возвращает записанные байты,дописывает '=' для неполной тройки,без NUL;
* при нехватке места останавливается перед очередной четверкой.
* decode пропускает чужие символы,останавливается на '=',принимает хвост из2/3 символов;
* возвращает ограниченный dst_cap префикс,не валидирует каноничность или весь вход.
* Строгие потребители отдельно сверяют длину и повторное кодирование (chat_attachment_identity). */
static const char B64_ALPHABET[] = "ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789-_";
static inline int b64_decode_char(char c) {
if (c >= 'A' && c <= 'Z') return c - 'A';
if (c >= 'a' && c <= 'z') return c - 'a' + 26;
if (c >= '0' && c <= '9') return c - '0' + 52;
if (c == '-') return 62;
if (c == '_') return 63;
return -1;
}
static inline size_t b64_encode(const uint8_t* src, size_t src_len, char* dst, size_t dst_cap) {
size_t out = 0;
for (size_t i = 0; i < src_len; i += 3) {
if (out + 4 > dst_cap) break;
uint32_t v = (uint32_t)src[i] << 16;
if (i + 1 < src_len) v |= (uint32_t)src[i + 1] << 8;
if (i + 2 < src_len) v |= (uint32_t)src[i + 2];
dst[out++] = B64_ALPHABET[(v >> 18) & 0x3F];
dst[out++] = B64_ALPHABET[(v >> 12) & 0x3F];
dst[out++] = (i + 1 < src_len) ? B64_ALPHABET[(v >> 6) & 0x3F] : '=';
dst[out++] = (i + 2 < src_len) ? B64_ALPHABET[v & 0x3F] : '=';
}
return out;
}
static inline size_t b64_decode(const char* src, size_t src_len, uint8_t* dst, size_t dst_cap) {
size_t out = 0;
int vals[4]; size_t vi = 0;
for (size_t i = 0; i < src_len && out < dst_cap; i++) {
if (src[i] == '=') break;
int v = b64_decode_char(src[i]);
if (v < 0) continue;
vals[vi++] = v;
if (vi == 4) {
dst[out++] = (uint8_t)((vals[0] << 2) | (vals[1] >> 4));
if (out < dst_cap) dst[out++] = (uint8_t)((vals[1] << 4) | (vals[2] >> 2));
if (out < dst_cap)
dst[out++] = (uint8_t)((vals[2] << 6) | vals[3]);
vi = 0;
}
}
if (vi == 3 && out < dst_cap) {
dst[out++] = (uint8_t)((vals[0] << 2) | (vals[1] >> 4));
if (out < dst_cap) dst[out++] = (uint8_t)((vals[1] << 4) | (vals[2] >> 2));
} else if (vi == 2 && out < dst_cap) {
dst[out++] = (uint8_t)((vals[0] << 2) | (vals[1] >> 4));
}
return out;
}
/* ── Состояние одного экземпляра (создаётся в chat_core.c) ── */
struct ms_props_cbk; /* member_sync.c */
struct media_index_result;
struct ms_apply_cbk; /* member_sync.c */
struct group_avatar;
struct media_submit_ctx;
struct ak_file;
struct chat_whisper;
struct member_route_event;
struct chat_core_ctx {
struct UTUN_INSTANCE* inst; /* Заимствованный владелец;не уничтожается из callbacks чата. */
sqlite3* db; /* Заимствована у ядра;chat_core её не открывает/не закрывает. */
char db_path[512]; /* Копия пути init для идентичности запуска/default media base;не отдельное соединение БД. */
uint64_t my_node_id; /* node_id владельца,снимок при init. */
uint8_t initialized; /* Метаданные committed,подписки установлены;не означает готовность всего chat_service. */
struct chat_whisper* whisper; /* Ленивый владелец модели/одной задачи; stop отсоединяет до ожидания workers. */
struct member_route_event* member_route_events; /* Отложенные UI-снимки топологии по (канал,узел), без TOPO_* указателей. */
struct ms_props_cbk* props_cbks; /* Владение списком подписчиков свойств member_sync. */
unsigned props_dispatch_depth; /* снятие props-подписки откладывает free до выхода из вложенных callbacks */
struct ms_apply_cbk* apply_cbks; /* Владение списком уведомлений о принятии мембера. */
unsigned apply_dispatch_depth; /* снятые apply callbacks освобождаются после выхода из всех вложенных вызовов */
struct group_avatar* group_avatar; /* Контекст запросов/передач аватарки группы;освобождает group_avatar_destroy. */
uint64_t media_cache_bytes; /* Производный размер уникальных файлов/расшифрованных копий;сверяется полным scan. */
struct media_submit_ctx* media_submissions; /* исходящие задачи до последнего callback; удаление канала ждёт их */
void* media_backfill_timer;/* Общий scan после debounce истории либо повтор при отказе/переполнении batch. */
int media_backfill_ran; /* Первый общий scan уже запущен;последующие завершения не переносят его. */
struct UTUN_INSTANCE* media_backfill_inst; /* inst при установленных done-подписках,NULL после destroy;заимствован. */
void* media_channel_backfill_timer; /* ограниченный batch докачки/восстановления индекса через 2с от первой заявки */
char media_channel_backfill[CHAT_MEDIA_BACKFILL_CHANNELS][64]; /* pending ch_id */
int media_channel_backfill_pending; /* Число занятых строк batch,0..16. */
void* attachment_timer; /* Периодический pump запросов ключей/отложенных completion,1с. */
int attachment_closing; /* Quiesce запрещает новые операции и публикацию worker-результатов. */
int attachment_initialized; /* Зарегистрированы модели/обработчики attachment;не признак наличия ключей. */
struct ak_file* attachment_files; /* workers и завершения, ожидающие SQL commit; владеет chat_attachment */
unsigned attachment_file_count; /* максимум MEDIA_ASYNC_MAX_TASKS, включая отложенные завершения */
};
/* Заимствованный контекст или NULL;inst обязан быть ненулевым,макрос не проверяет lifecycle. */
#define CC(inst) ((inst)->chat_core)
/* ── Хелперы ── */
/* Внутренние имена таблиц:не валидатор ID. Ненулевые строки/буферы,out_sz>0;
* sanitize сохраняет ASCII alnum/_,остальное заменяет '_',обрезает до out_sz-1 и ставит NUL.
* table_name предварительно ограничивает очищенный ID63 байтами,добавляет msg_/peers_ через snprintf. */
void sanitize_ch_id(const char* ch_id, char* out, size_t out_sz);
void msg_table_name(const char* ch_id, char* buf, size_t sz);
void peers_table_name(const char* ch_id, char* buf, size_t sz);
/* Тихая проверка канонического десятичного ID1..INT64_MAX;0 — отказ,NULL также недопустим.
* Использующий модуль логирует ошибку с контекстом своей операции. */
uint64_t chat_channel_id(const char* text);
/* Проверить весь JSON-object<=limit:уникальные ключи,только строковые значения,без скрытого NUL.
* SQLite заимствована;0/-1 с логом,callback может быть NULL. Callback получает строки до следующего step,
* строит только временный результат и возвращает0/-1;при поздней ошибке caller отбрасывает весь результат. */
int chat_json_strings(sqlite3* db, const char* text, size_t limit,
int (*visit)(const char* key, const char* value, void* arg), void* arg);
/* Байты подписи описания:канонический ID+NUL,имя<=255байт+NUL,owner native-u64,X25519/Ed25519 public[32].
* Возвращает длину/-1;аргументы обязательны,при отказе out не меняется. Общий формат create/JOIN/invite. */
int chat_channel_description_data(const char* channel, const char* name, uint64_t owner,
const uint8_t x25519[32], const uint8_t ed25519[32], uint8_t* out, size_t capacity);
/* ── db_sync registry (chat_core.c) ── */
/* Заимствованный зарегистрированный history instance канонического канала;NULL при отсутствии/неверном ID. */
struct DB_SYNC_INSTANCE* si_find(struct UTUN_INSTANCE* inst, const char* ch_id);
/* ── db_sync callback (chat_msg.c) ── */
/* db_sync callback после commit:производный attachment-индекс,feedback/UI,live download и last_msg_at.
* data/message_id заимствованы до возврата;updated даёт MSG_UPDATED без звука/unread/повторной загрузки. */
void on_msg_inserted(struct DB_SYNC_INSTANCE* si, const uint8_t message_id[32], uint64_t record_ts,
const char* data, size_t len, uint64_t author, int initial_sync, int updated, void* arg);
/* Проверить весь NUL-terminated manifest<=DB_SYNC_MESSAGE_MAX,hex,разделители и геометрию1..100 блоков.
* 0 — result владеет block_ids/block_sigs до media_index_result_free,-1 — формат/аргументы,-2 — память.
* При допустимых аргументах result обнуляется до разбора;подписи/содержимое файла здесь не проверяются. */
int chat_msg_parse_media_body(const char* body, struct media_index_result* result);
/* Учесть также расшифрованные производные файлы и применить общую квоту кеша. */
void chat_media_cache_enforce(struct UTUN_INSTANCE* inst);
/* Пометить текущие исходящие задачи канала; 1 — удаление продолжит последний completion, 0 — задач нет. */
int chat_media_defer_channel_delete(struct UTUN_INSTANCE* inst, const char* channel);
/* ── применить настройку group_autoconnect ко всем CHAT-группам (chat_channel.c) ── */
void chat_core_apply_group_autoconnect(struct UTUN_INSTANCE* inst);
#endif /* CHAT_CORE_PRIV_H */