176 lines
12 KiB
C
176 lines
12 KiB
C
/*
|
||
* 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 */
|