docs: clarify header APIs and preserve originals in .h1

This commit is contained in:
evgeny committed 2026-10-21 15:21:27 +02:00
1 parent abf342534f
commit f5f106c682
38 files changed
+3212 -262

No files matched your search

+8 -8
View File
@@ -181,7 +181,7 @@ struct ll_queue {
#endif
};
/* ==================== Создание / уничтожение ==================== */
// ==================== Создание / уничтожение ====================
/**
* @brief Создаёт новую пустую очередь.
@@ -203,7 +203,7 @@ struct ll_queue* queue_new(struct UASYNC* ua, size_t hash_size, uint16_t index_o
*/
void queue_free(struct ll_queue* q);
/* ==================== Конфигурация ==================== */
// ==================== Конфигурация ====================
/**
* @brief Устанавливает максимальное количество элементов в очереди.
@@ -214,7 +214,7 @@ void queue_free(struct ll_queue* q);
*/
void queue_set_size_limit(struct ll_queue* q, int lim);
/* ==================== Автозабор элементов ==================== */
// ==================== Автозабор элементов ====================
/**
* @brief Устанавливает коллбэк для автоматического извлечения элементов.
@@ -235,7 +235,7 @@ void queue_set_callback(struct ll_queue* q, queue_callback_fn cbk_fn, void* arg)
*/
void queue_resume_callback(struct ll_queue* q);
/* ==================== Пороговое ожидание (backpressure) ==================== */
// ==================== Пороговое ожидание (backpressure) ====================
/**
* @brief Устанавливает общий порог освобождения очереди.
@@ -304,7 +304,7 @@ int queue_waiter_wait(struct ll_queue* q, struct queue_waiter_handle* h,
*/
void queue_waiter_cancel(struct ll_queue* q, struct queue_waiter_handle* h);
/* ==================== Работа с данными ==================== */
// ==================== Работа с данными ====================
/**
* @brief Добавляет элемент в конец очереди (FIFO).
@@ -356,7 +356,7 @@ struct ll_entry* queue_data_get(struct ll_queue* q);
*/
int queue_entry_count(struct ll_queue* q);
/* ==================== Управление памятью элементов ==================== */
// ==================== Управление памятью элементов ====================
// проверить правильность очереди (корректность цепочки, счетчики элементов и байтов)
int queue_check_consistency(struct ll_queue* q);
@@ -396,7 +396,7 @@ void queue_entry_free(struct ll_entry* entry);
*/
void queue_dgram_free(struct ll_entry* entry);
/* ==================== Поиск и удаление ==================== */
// ==================== Поиск и удаление ====================
/**
* @brief Находит элемент по ключу индекса (требуется hash_size > 0 при создании).
@@ -426,7 +426,7 @@ struct ll_entry* queue_find_next_by_index(struct ll_queue* q,
*/
int queue_remove_data(struct ll_queue* q, struct ll_entry* entry);
/* ==================== Утилиты ==================== */
// ==================== Утилиты ====================
/**
* @brief Возвращает суммарный объём данных в очереди.
+444
View File
@@ -0,0 +1,444 @@
#ifndef LL_QUEUE_H
#define LL_QUEUE_H
/* Очередь одного uasync-потока; наличие pthread-заголовков не делает её межпоточной.
* put может синхронно вызвать consumer/waiter. Держать владельцев живыми до завершения callbacks,
* waiter handle обнулять целиком и отменять до освобождения; entry и dgram освобождаются отдельно. */
#ifdef __cplusplus
extern "C" {
#endif
#include <stddef.h> // для size_t
#include <stdint.h> // для uint64_t
#include "memory_pool.h" // для struct memory_pool
#ifdef _WIN32
#include <winsock2.h>
#include <windows.h>
#else
#include <pthread.h>
#endif
//#define QUEUE_DEBUG 1 // раскомментировать для отладки очереди
//#define QUEUE_THREAD_CHECK 1 // 0 to disable
/**
* @file ll_queue.h
* @brief Упрощённая двусвязная очередь с поддержкой автозабора элементов через callback,
* порогового ожидания освобождения места, поиска по ID и гибкого управления памятью.
*
* Основные особенности:
* - FIFO-очередь (с возможностью добавления в начало для приоритета)
* - Автоматический вызов callback при появлении элементов (один элемент за раз)
* - Обязательный вызов queue_resume_callback после обработки элемента
* - Поддержка пулов памяти для entry и отдельно для dgram
* - Хеш-таблица для быстрого поиска по ID (опционально)
* - Связный список waiters для backpressure c общим порогом (round-robin)
* - Использует uasync для отложенного возобновления callback'ов (без рекурсии в стеке)
*
* @note Важные правила использования автозабора (queue_set_callback):
* 1. Коллбэк получает управление только если в очереди есть элементы И !callback_suspended
* 2. Внутри коллбэка ОБЯЗАТЕЛЬНО: queue_data_get() → обработать элемент → queue_resume_callback()
* 3. Без вызова queue_resume_callback очередь навсегда застрянет
* 4. Не извлекайте элементы вручную вне коллбэка — это нарушит логику
* 5. queue_data_get() автоматически suspend'ит коллбэки для предотвращения рекурсии
*
* @note Память:
* - queue_free() освобождает структуру очереди, хеш-таблицу и список waiters
* (handle->internal каждого ожидающего сбрасывается в NULL)
* - Все элементы должны быть извлечены через queue_data_get() и освобождены через queue_entry_free()
* - dgram освобождается отдельно через queue_dgram_free() или кастомную dgram_free_fn
*/
// Forward declarations
struct ll_queue;
struct queue_waiter_handle;
/**
* @struct ll_entry
* @brief Элемент очереди (переменного размера).
*
* Память выделяется одним блоком: [struct ll_entry + data[size]].
* Поле dgram — отдельный блок (malloc / пул / внешняя память).
*/
struct ll_entry {
char* name;
struct ll_queue* owner; // Владелец элемента; NULL после извлечения
struct ll_entry* next; // Следующий элемент в очереди
struct ll_entry* prev; // Предыдущий элемент в очереди
uint16_t size; // Размер пользовательского буфера data[] (байт)
uint16_t len; // Актуальная длина данных в dgram
uint16_t memlen; // Выделенный размер под dgram
uint16_t int_len; // Внутреннее (не использовать)
uint8_t* dgram; // Указатель на данные пакета
void (*dgram_free_fn)(uint8_t* data); // Кастомная функция освобождения dgram
struct memory_pool* dgram_pool; // Пул для dgram (если используется)
uint32_t index_hash; // Хеш по первым 4 байтам индекса
struct ll_entry* hash_next; // Следующий в хеш-цепочке
struct memory_pool* pool; // Пул, из которого выделен сам entry (NULL = malloc)
uint8_t data[0]; // Гибкий массив пользовательских данных (размер = size)
};
/**
* @typedef queue_callback_fn
* @brief Коллбэк автозабора элементов из очереди.
* @param q указатель на очередь
* @param arg пользовательский аргумент (из queue_set_callback)
*
* @note Внутри функции:
* - Вызвать queue_data_get(q) для получения элемента
* - Обработать элемент (можно асинхронно)
* - После завершения обработки вызвать queue_resume_callback(q)
*/
typedef void (*queue_callback_fn)(struct ll_queue* q, void* arg);
/**
* @typedef queue_threshold_callback_fn
* @brief Одноразовый коллбэк при достижении порога очереди.
* @param q указатель на очередь
* @param arg пользовательский аргумент
*/
typedef void (*queue_threshold_callback_fn)(struct ll_queue* q, void* arg);
/**
* @struct queue_waiter
* @brief Внутренний узел в связном списке ожидающих (очередь владеет, аллоцирует/освобождает сама).
*/
struct queue_waiter {
struct queue_waiter* next; ///< Следующий в списке ожидающих
struct queue_waiter_handle* handle; ///< Обратная ссылка на публичную структуру
queue_threshold_callback_fn callback;
void* callback_arg;
};
/**
* @struct queue_waiter_handle
* @brief Публичная управляющая структура (встраивается в структуру вызывающей стороны).
*
* Вызывающая сторона:
* 1. Объявляет поле struct queue_waiter_handle в своей структуре (= {0} или u_calloc)
* 3. Вызывает queue_waiter_wait() когда хочет дождаться освобождения очереди
* 4. Вызывает queue_waiter_cancel() при деинициализации для отмены ожидания
*/
struct queue_waiter_handle {
struct queue_waiter* internal; ///< NULL = не ждём; иначе — внутренний узел
struct ll_queue* defer_q;
queue_threshold_callback_fn defer_cb;
void* defer_arg;
void* call_soon_id; ///< хендл отложенного вызова (для отмены)
struct queue_waiter_handle* defer_next; ///< список отложенных уведомлений очереди
};
/**
* @struct ll_queue
* @brief Структура очереди.
*/
struct ll_queue {
char* name;
struct ll_entry* head; // Голова очереди (отсюда извлекаем)
struct ll_entry* tail; // Хвост очереди (сюда добавляем)
int count; // Текущее количество элементов
size_t total_bytes; // Суммарный объём данных (сумма int_len)
int size_limit; // Максимальное количество элементов (-1 = без лимита)
queue_callback_fn callback; // Коллбэк автозабора
void* callback_arg; // Аргумент коллбэка
int callback_suspended; // 1 = коллбэки временно приостановлены
void* resume_timeout_id; // ID таймера uasync для отложенного resume
struct UASYNC* ua; // Экземпляр uasync (обязателен для таймеров)
struct queue_waiter* waiter_head; // Голова списка ожидающих
struct queue_waiter* waiter_tail; // Хвост списка ожидающих
struct queue_waiter_handle* deferred_waiters; // Уже запланированные уведомления
int threshold_max_packets; // Общий порог: макс. кол-во элементов (по умолчанию 0)
size_t threshold_max_bytes; // Общий порог: макс. объём данных (0 = не проверять)
int waiter_defer; // 0=immediate callback, 1=defer via uasync_call_soon
int callback_defer; // 0=immediate callback из put (default), 1=defer via uasync_call_soon (очистка стека)
queue_callback_fn empty_callback; // одноразовый коллбэк при опустошении очереди
void* empty_callback_arg;
void* empty_call_soon_id; // handle от uasync_call_soon
queue_callback_fn on_get; // коллбэк после каждого queue_data_get (deferred)
void* on_get_arg;
void* on_get_call_soon_id;
size_t on_get_pending;
struct ll_entry** hash_table; // Хеш-таблица для поиска по id (если hash_size > 0)
size_t hash_size; // Размер хеш-таблицы
uint16_t index_offset; // Смещение индекса в data[] (для всех entry очереди)
uint16_t index_size; // Размер индекса (0 = без индекса)
#ifdef QUEUE_THREAD_CHECK
#ifdef _WIN32
DWORD owner_thread;
#else
pthread_t owner_thread;
#endif
#endif
};
/* ==================== Создание / уничтожение ==================== */
/**
* @brief Создаёт новую пустую очередь.
* @param ua экземпляр uasync (обязателен для отложенного resume коллбэков)
* @param hash_size размер хеш-таблицы (0 = поиск по id отключён)
* @param index_offset смещение ключа индекса в data[] каждого entry (0 если hash_size=0)
* @param index_size размер ключа индекса (0 если hash_size=0)
* @param name имя очереди для отладки
* @return указатель на очередь или NULL при ошибке выделения памяти
*/
struct ll_queue* queue_new(struct UASYNC* ua, size_t hash_size, uint16_t index_offset, uint16_t index_size, char* name);
/**
* @brief Освобождает структуру очереди.
* @param q очередь
*
* @warning НЕ ОСВОБОЖДАЕТ элементы в очереди!
* Их нужно извлечь через queue_data_get() и освободить через queue_entry_free().
*/
void queue_free(struct ll_queue* q);
/* ==================== Конфигурация ==================== */
/**
* @brief Устанавливает максимальное количество элементов в очереди.
* @param q очередь
* @param lim лимит (-1 = без ограничения)
*
* При превышении лимита новые элементы автоматически освобождаются.
*/
void queue_set_size_limit(struct ll_queue* q, int lim);
/* ==================== Автозабор элементов ==================== */
/**
* @brief Устанавливает коллбэк для автоматического извлечения элементов.
* @param q очередь
* @param cbk_fn функция-коллбэк
* @param arg пользовательский аргумент
*
* Коллбэк вызывается автоматически при наличии элементов и разрешённом состоянии.
*/
void queue_set_callback(struct ll_queue* q, queue_callback_fn cbk_fn, void* arg);
/**
* @brief Возобновляет работу коллбэка после обработки элемента в следующем event loop.
* @param q очередь
*
* @warning ОБЯЗАТЕЛЬНО вызывать после завершения обработки элемента в коллбэке.
* Иначе автозабор элементов навсегда остановится.
*/
void queue_resume_callback(struct ll_queue* q);
/* ==================== Пороговое ожидание (backpressure) ==================== */
/**
* @brief Устанавливает общий порог освобождения очереди.
* @param q очередь
* @param max_packets максимальное количество элементов (условие: count <= max_packets)
* @param max_bytes максимальный объём данных (0 = не проверять)
*
* Порог — общий на всю очередь. Все waiters в связном списке используют этот порог.
* При достижении порога вызывается следующий waiter из списка (FIFO / round-robin).
* По умолчанию: max_packets=0, max_bytes=0 (пустая очередь).
*/
void queue_set_threshold(struct ll_queue* q, int max_packets, size_t max_bytes);
void queue_set_waiter_defer(struct ll_queue* q, int enable);
/**
* @brief Управляет способом вызова коллбэка автозабора при добавлении элемента.
* @param q очередь
* @param enable 0 — первый запуск коллбэка из queue_data_put* вызывается синхронно (по умолчанию);
* 1 — первый запуск откладывается через uasync_call_soon (очищает стек вызывающего).
*
* @note Влияет только на первый запуск из put. Повторные запуски (queue_resume_callback)
* всегда идут через uasync_call_soon.
*/
void queue_set_callback_defer(struct ll_queue* q, int enable);
/**
* @brief Устанавливает одноразовый коллбэк при опустошении очереди (count==0).
* @param q очередь
* @param cbk_fn коллбэк (NULL для отмены)
* @param arg пользовательский аргумент
*
* Коллбэк вызывается асинхронно (через uasync_call_soon) когда count становится 0.
* После вызова сбрасывается — для повторного использования нужно вызвать снова.
*/
void queue_set_empty_callback(struct ll_queue* q, queue_callback_fn cbk_fn, void* arg);
/**
* @brief Устанавливает коллбэк вызываемый после каждого queue_data_get (deferred через call_soon).
* @param q очередь
* @param cbk_fn коллбэк (NULL для отмены)
* @param arg пользовательский аргумент
*/
void queue_set_on_get(struct ll_queue* q, queue_callback_fn cbk_fn, void* arg);
/**
* @brief Регистрирует ожидание освобождения очереди до общего порога.
* @param q очередь
* @param h полностью обнулённый handle: `struct queue_waiter_handle h = {0}`
* @param callback функция, вызываемая при достижении порога
* @param arg аргумент коллбэка
* @return 1 — условие выполнено (callback вызван или отложен), 0 — ожидает порог, -1 — ошибка
*
* Если handle уже ожидает или имеет отложенный callback — старое ожидание отменяется.
* При queue_free отменяются также уже запланированные уведомления.
*/
int queue_waiter_wait(struct ll_queue* q, struct queue_waiter_handle* h,
queue_threshold_callback_fn callback, void* arg);
/**
* @brief Отменяет ожидание (удаляет из списка, если зарегистрирован).
* @param q очередь
* @param h указатель на handle
*
* Безопасно вызывать в любом состоянии: если waiter не зарегистрирован — ничего не делает.
*/
void queue_waiter_cancel(struct ll_queue* q, struct queue_waiter_handle* h);
/* ==================== Работа с данными ==================== */
/**
* @brief Добавляет элемент в конец очереди (FIFO).
* @param q очередь
* @param entry элемент
* @return 0 — успех, -1 — превышен лимит (элемент освобождён)
*/
int queue_data_put(struct ll_queue* q, struct ll_entry* entry);
/**
* @brief Добавляет элемент в начало очереди (LIFO, высокий приоритет).
* @param q очередь
* @param entry элемент
* @return 0 — успех, -1 — превышен лимит (элемент освобождён)
*/
int queue_data_put_first(struct ll_queue* q, struct ll_entry* entry);
/**
* @brief Добавляет элемент в конец очереди с хеш-индексом (использует index_offset/index_size из очереди).
* @param q очередь
* @param entry элемент
* @return 0 — успех, -1 — ошибка
*/
int queue_data_put_with_index(struct ll_queue* q, struct ll_entry* entry);
/**
* @brief Добавляет элемент в начало очереди с хеш-индексом (использует index_offset/index_size из очереди).
* @param q очередь
* @param entry элемент
* @return 0 — успех, -1 — ошибка
*/
int queue_data_put_first_with_index(struct ll_queue* q, struct ll_entry* entry);
/**
* @brief Извлекает элемент из начала очереди.
* @param q очередь
* @return элемент или NULL, если очередь пуста
*
* @note Автоматически приостанавливает коллбэки (callback_suspended = 1)
* После обработки элемента необходимо вызвать queue_resume_callback().
*/
struct ll_entry* queue_data_get(struct ll_queue* q);
/**
* @brief Возвращает текущее количество элементов в очереди.
* @param q очередь
* @return количество элементов
*/
int queue_entry_count(struct ll_queue* q);
/* ==================== Управление памятью элементов ==================== */
// проверить правильность очереди (корректность цепочки, счетчики элементов и байтов)
int queue_check_consistency(struct ll_queue* q);
/**
* @brief Выделяет элемент с отдельным буфером dgram (malloc).
* @param len требуемый размер dgram
* @return элемент или NULL при ошибке
*/
struct ll_entry* ll_alloc_lldgram(uint16_t len);
/**
* @brief Создаёт элемент с пользовательским буфером data[] (malloc одним блоком).
* @param data_size размер data[]
* @return элемент или NULL при ошибке
*/
struct ll_entry* queue_entry_new(size_t data_size);
/**
* @brief Создаёт элемент из пула памяти.
* @param pool пул
* @return элемент или NULL, если пул пуст
*/
struct ll_entry* queue_entry_new_from_pool(struct memory_pool* pool);
/**
* @brief Освобождает структуру элемента (возвращает в пул или free).
* @param entry элемент
*
* @note НЕ освобождает dgram! Используйте queue_dgram_free() отдельно.
*/
void queue_entry_free(struct ll_entry* entry);
/**
* @brief Освобождает буфер dgram элемента.
* @param entry элемент
*/
void queue_dgram_free(struct ll_entry* entry);
/* ==================== Поиск и удаление ==================== */
/**
* @brief Находит элемент по ключу индекса (требуется hash_size > 0 при создании).
* @param q очередь (размер ключа берётся из q->index_size)
* @param index_key искомый ключ
* @return элемент или NULL
*/
struct ll_entry* queue_find_data_by_index(struct ll_queue* q,
const void* index_key);
/**
* @brief Находит следующий элемент с тем же ключом (продолжение поиска).
* @param q очередь (размер ключа берётся из q->index_size)
* @param index_key искомый ключ
* @param prev_entry предыдущий найденный элемент (от queue_find_data_by_index или предыдущего queue_find_next_by_index)
* @return следующий элемент с тем же ключом или NULL
*/
struct ll_entry* queue_find_next_by_index(struct ll_queue* q,
const void* index_key,
struct ll_entry* prev_entry);
/**
* @brief Удаляет элемент из очереди (не освобождает память).
* @param q очередь
* @param entry элемент
* @return 0 — успех, -1 — элемент не найден в очереди
*/
int queue_remove_data(struct ll_queue* q, struct ll_entry* entry);
/* ==================== Утилиты ==================== */
/**
* @brief Возвращает суммарный объём данных в очереди.
* @param q очередь
* @return количество байт
*/
static inline size_t queue_total_bytes(struct ll_queue* q) {
return q->total_bytes;
}
#ifdef __cplusplus
}
#endif
#endif // LL_QUEUE_H
+23 -11
View File
@@ -23,26 +23,32 @@ struct UTUN_INSTANCE;
extern "C" {
#endif
/* ── жизненный цикл ── */
// ── жизненный цикл ──
int call_init(struct UTUN_INSTANCE* inst); /* из utun_instance.c после dm_core_init */
void call_destroy(struct UTUN_INSTANCE* inst); /* перед dm_core_destroy */
/* ── сигналинг (uasync-поток) ── */
// ── сигналинг (uasync-поток) ──
/* Caller: ch_id — группа либо "dm:<conv_id>" (BGP-группа выбирается при запуске).
* out_call_id — случайный uint64, генерит вызывающий. */
void call_start (struct UTUN_INSTANCE* inst, const char* ch_id, uint64_t peer_node_id, uint64_t out_call_id);
/* Callee: ответить/отклонить. hangup — любая сторона в любой момент. */
// Callee: ответить/отклонить. hangup — любая сторона в любой момент.
void call_accept (struct UTUN_INSTANCE* inst, uint64_t call_id);
void call_decline(struct UTUN_INSTANCE* inst, uint64_t call_id);
void call_hangup (struct UTUN_INSTANCE* inst, uint64_t call_id);
/* Проверка занятости обычным звонком; только uasync, включая входящий/исходящий. */
// Проверка занятости обычным звонком; только uasync, включая входящий/исходящий.
int call_busy(struct UTUN_INSTANCE* inst);
/* Продолжить исходящий вызов после предупреждения о телефонной занятости. Только uasync. */
// Продолжить исходящий вызов после предупреждения о телефонной занятости. Только uasync.
void call_continue(struct UTUN_INSTANCE* inst, uint64_t call_id);
/* Независимые настройки входящих вызовов; запрет выбранного типа приоритетнее проверки занятости. */
// Независимые настройки входящих вызовов; запрет выбранного типа приоритетнее проверки занятости.
enum call_policy {
CALL_POLICY_PUBLISH_PHONE = 1, CALL_POLICY_WARN_PHONE = 2, CALL_POLICY_REJECT_PHONE = 4,
CALL_POLICY_BLOCK_CALLS = 8, CALL_POLICY_BLOCK_RADIO = 16
@@ -50,14 +56,15 @@ enum call_policy {
/* Живое состояние ОС: 0=неизвестно, 1=свободен, 2=занят; policy — маска call_policy.
* Только uasync. Изменение статуса публикуется в BGP, если включён PUBLISH_PHONE. */
void call_set_phone_state(struct UTUN_INSTANCE* inst, int state, unsigned policy);
/* Флаги BGP PHONE_KNOWN/BUSY с учётом звонка приложения и разрешения публикации.
* Desktop без звонка свободен; у mobile состояние ОС может быть неизвестно. Только uasync. */
uint8_t call_phone_status(struct UTUN_INSTANCE* inst);
/* Read-only онлайн-проверка (GUI может звать из любого потока). */
// Read-only онлайн-проверка (GUI может звать из любого потока).
int call_is_online(struct UTUN_INSTANCE* inst, const char* ch_id, uint64_t peer_node_id);
/* ── медиа (релей Opus-кадров, uasync-поток) ── */
// ── медиа (релей Opus-кадров, uasync-поток) ──
/* Отправить закодированный Opus-кадр пиру (оборачивает в CALL_MEDIA).
* Только когда звонок ACTIVE. seq/ts выставляются автоматически. */
@@ -68,6 +75,7 @@ int call_send_media(struct UTUN_INSTANCE* inst, uint64_t call_id, const uint8_t*
* Используется headless-аудиосокетом; opus заимствован до возврата callback. */
typedef void (*call_media_recv_fn)(struct UTUN_INSTANCE* inst, uint64_t call_id,
const uint8_t* opus, int len, void* arg);
void call_set_media_recv_cb(struct UTUN_INSTANCE* inst, call_media_recv_fn fn, void* arg);
/* ── аудио-движок (опционально, GUI-сборки) ──
@@ -76,7 +84,7 @@ void call_set_media_recv_cb(struct UTUN_INSTANCE* inst, call_media_recv_fn fn, v
* и get_stats для события CALL_STATS. Демон без аудио (headless) — ops не ставит. */
struct call_audio_ops {
/* uasync-поток: принятый Opus-кадр пира (декод + jitter-буфер). */
// uasync-поток: принятый Opus-кадр пира (декод + jitter-буфер).
void (*on_media)(struct UTUN_INSTANCE* inst, uint64_t call_id,
uint16_t seq, uint32_t ts_ms, const uint8_t* opus, int len, const struct call_capture_time* timing);
/* uasync-поток: текущие метрики джиттер-буфера (buffer_ms, tempo_x100,
@@ -86,7 +94,7 @@ struct call_audio_ops {
};
void call_set_audio_ops(struct UTUN_INSTANCE* inst, const struct call_audio_ops* ops);
/* Отправить Opus-кадр из ДРУГОГО потока (GUI-аудио): post в uasync через trampoline. */
// Отправить Opus-кадр из ДРУГОГО потока (GUI-аудио): post в uasync через trampoline.
struct call_media_arg {
struct UTUN_INSTANCE* inst;
uint64_t call_id;
@@ -96,7 +104,7 @@ struct call_media_arg {
};
void call_send_media_trampoline(void* arg);
/* ── trampoline-структуры для gui_bridge_post_uasync_fn / uasync_post ── */
// ── trampoline-структуры для gui_bridge_post_uasync_fn / uasync_post ──
struct call_start_arg {
struct UTUN_INSTANCE* inst;
@@ -111,9 +119,13 @@ struct call_id_arg {
};
void call_start_trampoline (void* arg);
void call_accept_trampoline (void* arg);
void call_decline_trampoline(void* arg);
void call_hangup_trampoline (void* arg);
void call_continue_trampoline(void* arg);
#ifdef __cplusplus
+119
View File
@@ -0,0 +1,119 @@
// call.h — P2P аудио-звонок (сигналинг + медиа), per-instance
//
// Модуль оперирует (group_id, peer_node_id): звонок можно инициировать из
// CHAT-группы или из DM (group_id — группа маршрутизации, как в dm_core).
// Сигналинг, аудио и видео идут через etcp_router с обязательными ROUTE_CRYPTO_SIGN|ENCRYPT; соединение
// оптимизируется через параллельные пробы conn_mgr (DIRECT/REVERSE/INDIRECT).
//
// Все функции — в uasync-потоке. GUI/headless вызывают через trampoline
// (gui_bridge_post_uasync_fn / uasync_post).
#ifndef CALL_H
#define CALL_H
#include <stdint.h>
#include "call_jitter.h"
#include "call_clock.h"
struct UTUN_INSTANCE;
#ifdef __cplusplus
extern "C" {
#endif
/* ── жизненный цикл ── */
int call_init(struct UTUN_INSTANCE* inst); /* из utun_instance.c после dm_core_init */
void call_destroy(struct UTUN_INSTANCE* inst); /* перед dm_core_destroy */
/* ── сигналинг (uasync-поток) ── */
/* Caller: ch_id — группа либо "dm:<conv_id>" (BGP-группа выбирается при запуске).
* out_call_id — случайный uint64, генерит вызывающий. */
void call_start (struct UTUN_INSTANCE* inst, const char* ch_id, uint64_t peer_node_id, uint64_t out_call_id);
/* Callee: ответить/отклонить. hangup — любая сторона в любой момент. */
void call_accept (struct UTUN_INSTANCE* inst, uint64_t call_id);
void call_decline(struct UTUN_INSTANCE* inst, uint64_t call_id);
void call_hangup (struct UTUN_INSTANCE* inst, uint64_t call_id);
/* Проверка занятости обычным звонком; только uasync, включая входящий/исходящий. */
int call_busy(struct UTUN_INSTANCE* inst);
/* Продолжить исходящий вызов после предупреждения о телефонной занятости. Только uasync. */
void call_continue(struct UTUN_INSTANCE* inst, uint64_t call_id);
/* Независимые настройки входящих вызовов; запрет выбранного типа приоритетнее проверки занятости. */
enum call_policy {
CALL_POLICY_PUBLISH_PHONE = 1, CALL_POLICY_WARN_PHONE = 2, CALL_POLICY_REJECT_PHONE = 4,
CALL_POLICY_BLOCK_CALLS = 8, CALL_POLICY_BLOCK_RADIO = 16
};
/* Живое состояние ОС: 0=неизвестно, 1=свободен, 2=занят; policy — маска call_policy.
* Только uasync. Изменение статуса публикуется в BGP, если включён PUBLISH_PHONE. */
void call_set_phone_state(struct UTUN_INSTANCE* inst, int state, unsigned policy);
/* Флаги BGP PHONE_KNOWN/BUSY с учётом звонка приложения и разрешения публикации.
* Desktop без звонка свободен; у mobile состояние ОС может быть неизвестно. Только uasync. */
uint8_t call_phone_status(struct UTUN_INSTANCE* inst);
/* Read-only онлайн-проверка (GUI может звать из любого потока). */
int call_is_online(struct UTUN_INSTANCE* inst, const char* ch_id, uint64_t peer_node_id);
/* ── медиа (релей Opus-кадров, uasync-поток) ── */
/* Отправить закодированный Opus-кадр пиру (оборачивает в CALL_MEDIA).
* Только когда звонок ACTIVE. seq/ts выставляются автоматически. */
int call_send_media(struct UTUN_INSTANCE* inst, uint64_t call_id, const uint8_t* opus, int len,
const struct call_capture_time* timing);
/* Коллбэк приёма медиа: вызывается в uasync-потоке на каждый Opus-кадр пира.
* Используется headless-аудио-сокетом (и в фазе 3 — декодером GUI). */
typedef void (*call_media_recv_fn)(struct UTUN_INSTANCE* inst, uint64_t call_id,
const uint8_t* opus, int len, void* arg);
void call_set_media_recv_cb(struct UTUN_INSTANCE* inst, call_media_recv_fn fn, void* arg);
/* ── аудио-движок (опционально, GUI-сборки) ──
* Сервисный аудио-контур (codec + джиттер-буфер + тоны) регистрируется через
* call_set_audio_ops. call.c зовёт on_media на каждый принятый медиа-кадр (с seq/ts)
* и get_stats для события CALL_STATS. Демон без аудио (headless) — ops не ставит. */
struct call_audio_ops {
/* uasync-поток: принятый Opus-кадр пира (декод + jitter-буфер). */
void (*on_media)(struct UTUN_INSTANCE* inst, uint64_t call_id,
uint16_t seq, uint32_t ts_ms, const uint8_t* opus, int len, const struct call_capture_time* timing);
/* uasync-поток: текущие метрики джиттер-буфера (buffer_ms, tempo_x100,
* dropped/underruns — опционально, NULL допустим). */
int (*get_stats)(uint64_t call_id, int* buffer_ms, int* tempo_x100,
uint32_t* dropped, uint32_t* underruns, struct call_jitter_range* range);
};
void call_set_audio_ops(struct UTUN_INSTANCE* inst, const struct call_audio_ops* ops);
/* Отправить Opus-кадр из ДРУГОГО потока (GUI-аудио): post в uasync через trampoline. */
struct call_media_arg {
struct UTUN_INSTANCE* inst;
uint64_t call_id;
uint16_t len;
struct call_capture_time timing;
uint8_t opus[256];
};
void call_send_media_trampoline(void* arg);
/* ── trampoline-структуры для gui_bridge_post_uasync_fn / uasync_post ── */
struct call_start_arg {
struct UTUN_INSTANCE* inst;
char ch_id[64];
uint64_t peer_node_id;
uint64_t call_id;
};
struct call_id_arg {
struct UTUN_INSTANCE* inst;
uint64_t call_id;
};
void call_start_trampoline (void* arg);
void call_accept_trampoline (void* arg);
void call_decline_trampoline(void* arg);
void call_hangup_trampoline (void* arg);
void call_continue_trampoline(void* arg);
#ifdef __cplusplus
}
#endif
#endif /* CALL_H */
+51 -13
View File
@@ -1,8 +1,8 @@
/* Один платформенный видеозвонок процесса. Attach/detach — uasync; остальные API
* потокобезопасны. Detach отменяет post до уничтожения ядра. Worker не хранит inst.
* Один TX mailbox (до восьми кадров, каждый <=2MiB) + восемь RX кадров <=4MiB;
* владение pull.data передаётся через u_free. TX не ждёт накопления пачки.
* Никаких callbacks в UI: status/pull читает worker. Кодирование не блокирует uasync. */
/* call_video_port — обмен готовыми видеокадрами между платформенными workers и ядром звонка.
* Attach подключает ядро; для активного call_id: open → send/pull/status → close. Перед отправкой настроить encoder через format.
* Attach/detach — uasync; остальные API потокобезопасны. До detach остановить пользователей порта.
* Send копирует кадры; pull передаёт буфер вызывающему, освобождение — u_free(). Команды исполняются в uasync.
* В процессе один порт: TX до восьми кадров по 2 МиБ, RX до восьми кадров и 4 МиБ суммарно. */
#ifndef CALL_VIDEO_PORT_H
#define CALL_VIDEO_PORT_H
#include "call_video.h"
@@ -11,40 +11,78 @@ struct UTUN_INSTANCE;
extern "C" {
#endif
struct call_video_port_status {
struct call_video_caps peer;
uint64_t key_serial, rate_generation, rate_request, configured_generation;
uint32_t rate_bps;
uint32_t minimum_bps, maximum_bps;
int have_peer, error;
struct call_video_caps peer; // принятые возможности кодеков пира
uint64_t key_serial, rate_generation, rate_request, configured_generation; // счётчик запросов KEY, ID команды битрейта и настроенное поколение
uint32_t rate_bps; // запрошенный кодеру битрейт, бит/с; результат передать через applied
uint32_t minimum_bps, maximum_bps; // действующий диапазон регулятора, бит/с
int have_peer, error; // возможности пира получены / настройка завершилась ошибкой
int can_encode; // Новые raw входы допустимы; готовый TX всё равно сохраняется и повторяется.
int tx_idle; // Mailbox свободен; новый формат не обгонит кадры прежнего поколения в mailbox.
uint64_t decoded_generation;
int decoded_accepted;
uint64_t decoded_generation; // поколение, для которого удалённый decoder сообщил результат
int decoded_accepted; // 1 — удалённый decoder принял поколение, 0 — отказ
unsigned rx_frames; // Полные encoded кадры порта; pending decoder/GUI учитывает RX worker.
};
// ── Привязка к ядру ──
// Подключить единственное ядро к порту; 0/-1.
int call_video_port_attach(struct UTUN_INSTANCE *inst);
// Отменить ожидающий post и освободить очереди; выполнять до уничтожения ядра.
void call_video_port_detach(struct UTUN_INSTANCE *inst);
// ── Открытие и закрытие видеотрека ──
// Скопировать локальные capabilities и запланировать настройку видеотрека; 0/-1, не подтверждение готовности пира.
int call_video_port_open(uint64_t id, const struct call_video_caps *caps);
// Закрыть текущий id и очистить его очереди; чужой id игнорируется.
void call_video_port_close(uint64_t id);
// ── Состояние и уведомления ──
// Скопировать текущее состояние; 0 — снимок получен, -1 — нет активного id/ядра или out.
int call_video_port_status(uint64_t id, struct call_video_port_status *out);
/* Неблокирующий накопительный счётчик полных принятых RX кадров текущего звонка.
* Pull не уменьшает его. Для общего запаса вычитаются показанные/отклонённые кадры.
* 0 для закрытого/другого звонка; новый open после остановки readers начинает с нуля. */
uint64_t call_video_port_received(uint64_t id);
/* Уведомление RX без обращения к port API из callback; вызывается под mutex порта.
* 1 — установлен, 0 — звонок ещё не открыт/уже закрыт. NULL снимает callback до освобождения arg. */
int call_video_port_set_wake(uint64_t id, void (*wake)(void *), void *arg);
// ── Настройка кодера и исходящие кадры ──
// Передать фактический формат и битрейт открытого encoder; формат копируется. 0 — команда принята, -1 — отказ.
int call_video_port_format(uint64_t id, const struct call_video_format *format, uint32_t bps);
// Подтвердить команду status.rate_generation/rate_request: bps — применённый битрейт, 0 — отказ encoder. 0/-1.
int call_video_port_applied(uint64_t id, uint64_t generation, uint64_t request, uint32_t bps);
#define CALL_VIDEO_PORT_TX_FRAMES 8u
#define CALL_VIDEO_PORT_TX_BYTES (CALL_VIDEO_PORT_TX_FRAMES * CALL_VIDEO_MAX_FRAME)
/* Атомарно копирует готовую пачку одного звонка. 1 — принята целиком, 0 — mailbox занят,
* -1 — ошибка; на 0/-1 ни один кадр не принят. count: 1..CALL_VIDEO_PORT_TX_FRAMES. */
* -1 — ошибка; на 0/-1 ни один кадр не принят. count: 1..CALL_VIDEO_PORT_TX_FRAMES.
* Входные кадры всегда остаются у вызывающего. При 0 повторить ту же пачку после освобождения порта. */
int call_video_port_send(const struct call_video_frame *frames, unsigned count);
// ── Входящие кадры ──
// Забрать старейший полный RX-кадр: 1 — получен, out->data освободить через u_free; 0 — кадра нет, out не меняется.
int call_video_port_pull(uint64_t id, struct call_video_frame *out);
// ── Управление качеством и восстановлением ──
// Запланировать запрос нового KEY удалённому encoder после потери опорных кадров.
void call_video_port_request_key(uint64_t id);
// Задать диапазон битрейта в бит/с: 300000..4000000, кратно 1000; 0 — команда принята, -1 — отказ.
int call_video_port_range(uint64_t id, uint32_t minimum_bps, uint32_t maximum_bps);
// Сообщить результат запуска decoder поколения generation: accepted=0/1; 0 — команда принята, -1 — отказ.
int call_video_port_decoded(uint64_t id, uint64_t generation, int accepted);
#ifdef __cplusplus
}
#endif
+51
View File
@@ -0,0 +1,51 @@
/* Один платформенный видеозвонок процесса. Attach/detach — uasync; остальные API
* потокобезопасны. Detach отменяет post до уничтожения ядра. Worker не хранит inst.
* Один TX mailbox (до восьми кадров, каждый <=2MiB) + восемь RX кадров <=4MiB;
* владение pull.data передаётся через u_free. TX не ждёт накопления пачки.
* Никаких callbacks в UI: status/pull читает worker. Кодирование не блокирует uasync. */
#ifndef CALL_VIDEO_PORT_H
#define CALL_VIDEO_PORT_H
#include "call_video.h"
struct UTUN_INSTANCE;
#ifdef __cplusplus
extern "C" {
#endif
struct call_video_port_status {
struct call_video_caps peer;
uint64_t key_serial, rate_generation, rate_request, configured_generation;
uint32_t rate_bps;
uint32_t minimum_bps, maximum_bps;
int have_peer, error;
int can_encode; // Новые raw входы допустимы; готовый TX всё равно сохраняется и повторяется.
int tx_idle; // Mailbox свободен; новый формат не обгонит кадры прежнего поколения в mailbox.
uint64_t decoded_generation;
int decoded_accepted;
unsigned rx_frames; // Полные encoded кадры порта; pending decoder/GUI учитывает RX worker.
};
int call_video_port_attach(struct UTUN_INSTANCE *inst);
void call_video_port_detach(struct UTUN_INSTANCE *inst);
int call_video_port_open(uint64_t id, const struct call_video_caps *caps);
void call_video_port_close(uint64_t id);
int call_video_port_status(uint64_t id, struct call_video_port_status *out);
/* Неблокирующий накопительный счётчик полных принятых RX кадров текущего звонка.
* Pull не уменьшает его. Для общего запаса вычитаются показанные/отклонённые кадры.
* 0 для закрытого/другого звонка; новый open после остановки readers начинает с нуля. */
uint64_t call_video_port_received(uint64_t id);
/* Уведомление RX без обращения к port API из callback; вызывается под mutex порта.
* 1 — установлен, 0 — звонок ещё не открыт/уже закрыт. NULL снимает callback до освобождения arg. */
int call_video_port_set_wake(uint64_t id, void (*wake)(void *), void *arg);
int call_video_port_format(uint64_t id, const struct call_video_format *format, uint32_t bps);
int call_video_port_applied(uint64_t id, uint64_t generation, uint64_t request, uint32_t bps);
#define CALL_VIDEO_PORT_TX_FRAMES 8u
#define CALL_VIDEO_PORT_TX_BYTES (CALL_VIDEO_PORT_TX_FRAMES * CALL_VIDEO_MAX_FRAME)
/* Атомарно копирует готовую пачку одного звонка. 1 — принята целиком, 0 — mailbox занят,
* -1 — ошибка; на 0/-1 ни один кадр не принят. count: 1..CALL_VIDEO_PORT_TX_FRAMES. */
int call_video_port_send(const struct call_video_frame *frames, unsigned count);
int call_video_port_pull(uint64_t id, struct call_video_frame *out);
void call_video_port_request_key(uint64_t id);
int call_video_port_range(uint64_t id, uint32_t minimum_bps, uint32_t maximum_bps);
int call_video_port_decoded(uint64_t id, uint64_t generation, int accepted);
#ifdef __cplusplus
}
#endif
#endif
+43 -12
View File
@@ -1,8 +1,8 @@
/* Зашифрованные групповые вложения и ручная выдача ключей.
* Все API кроме identity — uasync-поток. Ключи, запросы и outbox локальны.
* init после media_async; quiesce до его destroy, destroy после workers.
* Отдельный router-сервис требует E2E-шифрование и подпись каждого пакета.
* Очереди переживают restart; для доставки нужен живой маршрут обеих сторон. */
/* chat_attachment — вложения канала с шифрованием и ручной выдачей ключа автором.
* send готовит контейнер и публикует сообщение; downloaded/want запускают получение ключа и расшифровку.
* Автор отвечает на запрос через decide. Ключи и очередь доставки сохраняются в локальной БД.
* Все вызовы, кроме identity, — uasync. init после media_async; quiesce до остановки workers, destroy после них.
* Обмен ключами подписан и зашифрован; для доставки нужен живой маршрут обеих сторон. */
#ifndef CHAT_ATTACHMENT_H
#define CHAT_ATTACHMENT_H
#include "../media_async/attachment.h"
@@ -11,25 +11,51 @@
extern "C" {
#endif
struct UTUN_INSTANCE;
// ── Жизненный цикл модуля ──
// Подключить таблицы, router-сервис и обработку сохранённых задач; 0/-1.
int chat_attachment_init(struct UTUN_INSTANCE* inst);
// Запретить новые подготовки и остановить таймер; запущенные workers завершаются отдельно.
void chat_attachment_quiesce(struct UTUN_INSTANCE* inst);
// После завершения workers убрать незавершённые подготовки и отключить router-сервис.
void chat_attachment_destroy(struct UTUN_INSTANCE* inst);
/* source — собственный подготовленный файл приложения, передаётся во владение. */
// ── Отправка и учёт вложений ──
/* Асинхронно отправить подготовленный файл; channel — десятичный ID, reply — 32 байта ID ответа либо нули.
* Аргументы копируются. 0 — задача принята, source передан задаче и будет удалён даже при ошибке подготовки.
* -1 — отказ запуска, source остаётся у вызывающего. Ненулевой request возвращается в CHAT_EVT_MSG_SUBMITTED. */
int chat_attachment_send(struct UTUN_INSTANCE* inst, const char* channel, const char* source,
const struct attachment_info* info, const char* type, const uint8_t reply[32], uint64_t request);
/* UUID из подписанного имени контейнера; принимает канонический base64-prefix тела медиа. */
// Прочитать UUID из канонического base64-префикса тела медиа; 0/-1. Подпись проверяет вызывающий.
int chat_attachment_identity(const char* body, size_t length, uint8_t id[16]);
/* Связать заранее сохранённый авторский ключ с подписанным сообщением до публикации. */
// Связать авторский ключ с ID сообщения и SHA256 контейнера до публикации; 0/-1. Массивы копируются в БД.
int chat_attachment_bind(struct UTUN_INSTANCE* inst, const char* channel, const uint8_t id[16],
const uint8_t message[32], const uint8_t hash[32]);
/* Учесть проверенное db_sync сообщение; обычные сообщения игнорируются. */
// Учесть проверенное db_sync сообщение и сохранить метаданные контейнера; 0/-1. Обычные сообщения игнорируются.
int chat_attachment_observe(struct UTUN_INSTANCE* inst, const char* channel, const uint8_t message[32],
uint64_t author, const char* json, size_t length);
/* После проверенной загрузки: сохранить состояние, запросить ключ либо расшифровать. */
// ── Получение файла и выдача доступа ──
// После проверенной загрузки: сохранить состояние, запросить ключ либо расшифровать.
void chat_attachment_downloaded(struct UTUN_INSTANCE* inst, const char* channel, const uint8_t message[32]);
/* Ручная загрузка на storage-узле также означает запрос пользовательского доступа. */
// Ручная загрузка на storage-узле также означает запрос пользовательского доступа.
void chat_attachment_want(struct UTUN_INSTANCE* inst, const char* channel, const uint8_t message[32]);
/* Сохранить решение автора по ожидающему запросу peer: approve!=0 — выдать ключ, 0 — отказать.
* 0 — решение и исходящий ответ записаны в БД, -1 — ошибка/нет прав или ожидающего запроса; доставка идёт позже. */
int chat_attachment_decide(struct UTUN_INSTANCE* inst, const char* channel, const uint8_t message[32], uint64_t peer, int approve);
// ── Команда интерфейса ──
struct chat_attachment_decision {
struct UTUN_INSTANCE* inst;
char channel[32];
@@ -37,9 +63,14 @@ struct chat_attachment_decision {
uint64_t peer;
int approve;
};
// arg — u_malloc/u_calloc chat_attachment_decision, освобождается здесь; результат — CHAT_EVT_ATTACHMENT_KEYS_CHANGED.
void chat_attachment_decision_trampoline(void* arg);
/* Канал удаляется локально: отменить очереди, стереть ключи и частные копии. */
// ── Локальное удаление канала ──
// Канал удаляется локально: отменить очереди, стереть ключи и частные копии.
void chat_attachment_delete_channel(struct UTUN_INSTANCE* inst, const char* channel);
#ifdef __cplusplus
}
#endif
+46
View File
@@ -0,0 +1,46 @@
/* Зашифрованные групповые вложения и ручная выдача ключей.
* Все API кроме identity — uasync-поток. Ключи, запросы и outbox локальны.
* init после media_async; quiesce до его destroy, destroy после workers.
* Отдельный router-сервис требует E2E-шифрование и подпись каждого пакета.
* Очереди переживают restart; для доставки нужен живой маршрут обеих сторон. */
#ifndef CHAT_ATTACHMENT_H
#define CHAT_ATTACHMENT_H
#include "../media_async/attachment.h"
#include "../media_async/encrypted_file.h"
#ifdef __cplusplus
extern "C" {
#endif
struct UTUN_INSTANCE;
int chat_attachment_init(struct UTUN_INSTANCE* inst);
void chat_attachment_quiesce(struct UTUN_INSTANCE* inst);
void chat_attachment_destroy(struct UTUN_INSTANCE* inst);
/* source — собственный подготовленный файл приложения, передаётся во владение. */
int chat_attachment_send(struct UTUN_INSTANCE* inst, const char* channel, const char* source,
const struct attachment_info* info, const char* type, const uint8_t reply[32], uint64_t request);
/* UUID из подписанного имени контейнера; принимает канонический base64-prefix тела медиа. */
int chat_attachment_identity(const char* body, size_t length, uint8_t id[16]);
/* Связать заранее сохранённый авторский ключ с подписанным сообщением до публикации. */
int chat_attachment_bind(struct UTUN_INSTANCE* inst, const char* channel, const uint8_t id[16],
const uint8_t message[32], const uint8_t hash[32]);
/* Учесть проверенное db_sync сообщение; обычные сообщения игнорируются. */
int chat_attachment_observe(struct UTUN_INSTANCE* inst, const char* channel, const uint8_t message[32],
uint64_t author, const char* json, size_t length);
/* После проверенной загрузки: сохранить состояние, запросить ключ либо расшифровать. */
void chat_attachment_downloaded(struct UTUN_INSTANCE* inst, const char* channel, const uint8_t message[32]);
/* Ручная загрузка на storage-узле также означает запрос пользовательского доступа. */
void chat_attachment_want(struct UTUN_INSTANCE* inst, const char* channel, const uint8_t message[32]);
int chat_attachment_decide(struct UTUN_INSTANCE* inst, const char* channel, const uint8_t message[32], uint64_t peer, int approve);
struct chat_attachment_decision {
struct UTUN_INSTANCE* inst;
char channel[32];
uint8_t message[32];
uint64_t peer;
int approve;
};
void chat_attachment_decision_trampoline(void* arg);
/* Канал удаляется локально: отменить очереди, стереть ключи и частные копии. */
void chat_attachment_delete_channel(struct UTUN_INSTANCE* inst, const char* channel);
#ifdef __cplusplus
}
#endif
#endif
+79 -47
View File
@@ -2,6 +2,8 @@
* 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*.
@@ -19,10 +21,11 @@
struct sqlite3;
struct UTUN_INSTANCE;
/* ── Жизненный цикл ── */
// ── Жизненный цикл ──
/* Запустить чат поверх core_start; UTUN не требуется. Повтор допустим; 0/-1. */
// Запустить чат поверх 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);
@@ -30,7 +33,8 @@ 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 после остановки остальных частей чата; общая БД остаётся у ядра. */
// Освободить chat_core после остановки остальных частей чата; общая БД остаётся у ядра.
void chat_core_destroy(struct UTUN_INSTANCE* inst);
/* Backfill: регистрирует уже скачанные медиафайлы (лежат на диске, но отсутствуют
@@ -44,14 +48,16 @@ void chat_media_autodownload_backfill(struct UTUN_INSTANCE* inst);
/* Регистрирует триггер: после первого SYNC_DONE (+ дебаунс 2с) анонсирует локальные
* блоки суперузлам и запускает chat_media_autodownload_backfill. Один раз за запуск чата. */
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. */
// Заимствованная БД текущего chat_core; NULL до init/после destroy.
struct sqlite3* chat_core_get_db(struct UTUN_INSTANCE* inst);
/* 1 — chat_core инициализирован, 0 — нет; не проверяет доступность сети. */
// 1 — chat_core инициализирован, 0 — нет; не проверяет доступность сети.
int chat_core_is_initialized(struct UTUN_INSTANCE* inst);
/* ── Отправка сообщения (GUI → uasync) ── */
// ── Отправка сообщения (GUI → uasync) ──
struct chat_msg_submit {
struct UTUN_INSTANCE* inst;
@@ -72,31 +78,38 @@ struct chat_msg_submit {
uint64_t request_id; /* ненулевой ID: trampoline/media отправляет CHAT_EVT_MSG_SUBMITTED */
};
/* Подписать, сохранить и разослать сообщение. req/data остаются у вызывающего. 0/-1. */
// Подписать, сохранить и разослать сообщение. req/data остаются у вызывающего. 0/-1.
int chat_core_submit_message(struct UTUN_INSTANCE* inst, struct chat_msg_submit* req);
/* Отправка медиа-сообщения (вложение): src->dest (copy) или видео (transcode). */
/* Подготовить файл в 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);
/* Ручная загрузка вложения; для зашифрованного файла также запрашивает ключ автора. */
// Ручная загрузка вложения; для зашифрованного файла также запрашивает ключ автора.
void chat_core_attachment_download(struct UTUN_INSTANCE* inst, const char* channel_id, int64_t msg_id);
/* ── DB-операции (оставлены для интроспекции) ── */
// ── DB-операции (оставлены для интроспекции) ──
/* Число локальных сообщений; 0 также при отсутствии БД или ошибке запроса. */
// Число локальных сообщений; 0 также при отсутствии БД или ошибке запроса.
uint32_t chat_core_count(struct UTUN_INSTANCE* inst, const char* ch_id);
/* Хеш записи по позиции с нуля в порядке (timestamp, node_id); hash_out — 32 байта.
* 0 = запрос выполнен (нет записи — нулевой хеш), -1 = ошибка. */
int chat_core_chain_hash_at(struct UTUN_INSTANCE* inst, const char* ch_id, uint32_t pos, uint8_t* hash_out);
/* Список 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. */
// Список участников: 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) ── */
// ── Создание канала (GUI → uasync) ──
struct chat_channel_create {
struct UTUN_INSTANCE* inst;
@@ -112,16 +125,20 @@ struct chat_channel_create {
struct chat_create_auto_arg { struct UTUN_INSTANCE* inst; char name[128]; };
/* Подготовить локальные таблицы, группу и синхронизацию канала. Членства на других узлах не даёт. */
// Подготовить локальные таблицы, группу и синхронизацию канала. Членства на других узлах не даёт.
void chat_core_ensure_channel_ready(struct UTUN_INSTANCE* inst, const char* ch_id);
/* Создать канал по готовым ключам и метаданным req; req остаётся у вызывающего. */
// Создать канал по готовым ключам и метаданным req; req остаётся у вызывающего.
void chat_core_create_channel(struct UTUN_INSTANCE* inst, struct chat_channel_create* req);
void chat_core_create_channel_trampoline(void* arg);
/* Сгенерировать ключи и создать канал, добавив себя владельцем. */
// Сгенерировать ключи и создать канал, добавив себя владельцем.
void chat_core_create_channel_auto(struct UTUN_INSTANCE* inst, const char* name);
void chat_core_create_channel_auto_trampoline(void* arg);
/* ── Локальное удаление канала (GUI → uasync) ── */
// ── Локальное удаление канала (GUI → uasync) ──
struct chat_delete_channel_req {
struct UTUN_INSTANCE* inst;
@@ -132,29 +149,34 @@ struct chat_delete_channel_req {
* (соединения/синк), db_sync инстанса, таблиц msg_ и peers_, медиафайлов и
* merkle-хешей. Работает в uasync-потоке. Другие узлы канал не теряют. */
void chat_core_delete_channel(struct UTUN_INSTANCE* inst, const char* ch_id);
void chat_core_delete_channel_trampoline(void* arg);
/* ── Утилиты ── */
// ── Утилиты ──
/* Задать локальный ID в контексте чата; ключи и идентичность ядра не меняет. */
// Задать локальный ID в контексте чата; ключи и идентичность ядра не меняет.
void chat_core_set_my_node_id(struct UTUN_INSTANCE* inst, uint64_t node_id);
/* Сохранить имя узла и обновить собственные member-записи каналов. */
// Сохранить имя узла и обновить собственные 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-записи каналов. */
// Обновить адреса 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-потока) */
// Сохранение 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; };
@@ -163,18 +185,20 @@ 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) */
// Установка 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) */
// Запуск подключения к узлам выбранного канала (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) */
// Однократная попытка подключения к конкретному узлу канала (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 {
@@ -183,10 +207,12 @@ struct chat_connect_node_req {
uint64_t node_id;
};
/* Трамплин для gui_bridge_post_uasync (GUI → uasync) */
/* Передать в uasync блок chat_msg_submit, выделенный u_calloc/u_malloc; trampoline освобождает его.
* data отдельно не освобождается: удобно разместить содержимое в хвосте того же блока.
* Непустой media_src выбирает подготовку вложения, иначе — обычную отправку. Результат — по request_id. */
void chat_core_submit_trampoline(void* arg);
/* Трамплин для ручного запуска загрузки медиа (JNI → uasync) */
// Трамплин для ручного запуска загрузки медиа (JNI → uasync)
struct attachment_dl_req {
struct UTUN_INSTANCE* inst;
char channel_id[64];
@@ -194,14 +220,15 @@ struct attachment_dl_req {
};
void chat_core_attachment_download_trampoline(void* arg);
/* Обновление local_attrs для записи (вызывается из uasync-потока) */
// Обновление 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) */
// Пометить голосовое сообщение как проигранное (добавляет "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 {
@@ -211,26 +238,26 @@ struct voice_played_req {
uint64_t author_node_id;
};
/* Сбор статуса (NTP + connections) и отправка в GUI. arg = struct UTUN_INSTANCE* (inst) */
// Сбор статуса (NTP + connections) и отправка в GUI. arg = struct UTUN_INSTANCE* (inst)
void chat_core_collect_status_trampoline(void* arg);
/* Лёгкий запрос нагрузки uasync для GUI. arg = inst, ответ CHAT_EVT_CPU_LOAD. */
// Лёгкий запрос нагрузки uasync для GUI. arg = inst, ответ CHAT_EVT_CPU_LOAD.
void chat_core_collect_cpu_load_trampoline(void* arg);
/* Сбор списка ETCP-соединений и отправка в GUI (binary packet). arg = inst */
// Сбор списка ETCP-соединений и отправка в GUI (binary packet). arg = inst
void chat_core_collect_conn_list_trampoline(void* arg);
struct chat_node_arg { struct UTUN_INSTANCE* inst; uint64_t node_id; };
/* Сбор полных метрик одного соединения (arg = u_malloc struct chat_node_arg) */
// Сбор полных метрик одного соединения (arg = u_malloc struct chat_node_arg)
void chat_core_collect_conn_metrics_trampoline(void* arg);
/* Сбор live-снапшота для панели деталей участника (arg = u_malloc struct chat_node_arg) */
// Сбор live-снапшота для панели деталей участника (arg = u_malloc struct chat_node_arg)
void chat_core_collect_member_detail_trampoline(void* arg);
/* ── Headless CLI API ── */
// ── Headless CLI API ──
/* Список каналов с метаданными в JSON: [{id,name,owner_id,owner_name,peers,msgs,online}] */
// Список каналов с метаданными в JSON: [{id,name,owner_id,owner_name,peers,msgs,online}]
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}].
@@ -242,20 +269,24 @@ int chat_core_get_messages_json(struct UTUN_INSTANCE* inst, const char* ch_id, i
* connected означает наличие транспорта в реестре, а не его UP или готовность группы. */
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 */
// Имя узла из таблицы nodes
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 удаляет ссылку. */
// Ссылка на описание avatars/<sha256>.json внутри дерева своего UID/канала. server=0 удаляет ссылку.
int chat_core_set_my_avatar(struct UTUN_INSTANCE* inst, const char* ch_id, uint64_t server, const char* file);
int chat_core_name_userinfo(const char* name, char* out, size_t capacity);
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.
* Вызов только в uasync-потоке, вне внешней SQLite-транзакции. */
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;
@@ -263,33 +294,33 @@ struct chat_avatar_req {
};
void chat_core_set_my_avatar_trampoline(void* arg);
/* ── Редактирование adm_tags (владелец канала) ── */
// ── Редактирование adm_tags (владелец канала) ──
#define CHAT_MEMBER_TAGS_MAX_KEYS 16
#define CHAT_MEMBER_TAGS_KEY_SZ 64
struct chat_member_tags;
/* Загрузить текущие adm_tags мембера в память. Возвращает NULL если мембера нет. */
// Загрузить текущие 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. */
// Установить ключ. val=NULL → удалить ключ. ver — особый, автоинкремент при commit.
int chat_member_tags_set(struct chat_member_tags* t, const char* key, const char* val);
/* Получить значение ключа. Возвращает NULL если ключ не найден. */
// Получить значение ключа. Возвращает NULL если ключ не найден.
const char* chat_member_tags_get(struct chat_member_tags* t, const char* key);
/* Заменить все теги данными из JSON-строки (плоский, строковые значения). */
// Заменить все теги данными из 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* (доверенная память). */
// Трамплин для gui_bridge: arg = struct chat_member_tags_req* (доверенная память).
struct chat_member_tags_req {
struct UTUN_INSTANCE* inst;
char ch_id[64];
@@ -298,10 +329,11 @@ struct chat_member_tags_req {
};
void chat_member_tags_commit_trampoline(void* arg);
/* ── Передача прав админа (владелец → другому узлу) ── */
// ── Передача прав админа (владелец → другому узлу) ──
/* Передать оба канальных privkey выбранному узлу (encrypted+signed через etcp_router). */
// Передать оба канальных privkey выбранному узлу (encrypted+signed через etcp_router).
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 {
+313
View File
@@ -0,0 +1,313 @@
/*
* chat_core — API каналов, сообщений, профиля и настроек одного узла.
* chat_service_start/stop управляют всем чат-сервисом поверх работающего ядра.
* Данные хранятся в общей SQLite; события для интерфейса доставляет chat_event.
*
* Все 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);
/* Backfill: регистрирует уже скачанные медиафайлы (лежат на диске, но отсутствуют
* в media_files) — вызывается после загрузки каналов/сообщений при старте. */
void chat_media_backfill(struct UTUN_INSTANCE* inst);
/* Докачка недостающего медиа за последние storage_backfill_days дней. Проходит по
* медиасообщениям каналов и запускает загрузку для тех, чей файл отсутствует на диске. */
void chat_media_autodownload_backfill(struct UTUN_INSTANCE* inst);
/* Регистрирует триггер: после первого SYNC_DONE (+ дебаунс 2с) анонсирует локальные
* блоки суперузлам и запускает chat_media_autodownload_backfill. Один раз за запуск чата. */
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 */
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);
/* Отправка медиа-сообщения (вложение): src->dest (copy) или видео (transcode). */
void chat_core_submit_media_message(struct UTUN_INSTANCE* inst, struct chat_msg_submit* req);
/* Ручная загрузка вложения; для зашифрованного файла также запрашивает ключ автора. */
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);
/* Хеш записи по позиции с нуля в порядке (timestamp, node_id); hash_out — 32 байта.
* 0 = запрос выполнен (нет записи — нулевой хеш), -1 = ошибка. */
int chat_core_chain_hash_at(struct UTUN_INSTANCE* inst, const char* ch_id, uint32_t pos, uint8_t* hash_out);
/* Список 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;
char channel_id[64];
char name[128];
uint64_t owner_node_id;
uint8_t x25519_pubkey[32];
uint8_t x25519_privkey[32];
uint8_t ed25519_pubkey[32];
uint8_t ed25519_privkey[32];
uint8_t signature[64];
};
struct chat_create_auto_arg { struct UTUN_INSTANCE* inst; char name[128]; };
/* Подготовить локальные таблицы, группу и синхронизацию канала. Членства на других узлах не даёт. */
void chat_core_ensure_channel_ready(struct UTUN_INSTANCE* inst, const char* ch_id);
/* Создать канал по готовым ключам и метаданным req; req остаётся у вызывающего. */
void chat_core_create_channel(struct UTUN_INSTANCE* inst, struct chat_channel_create* req);
void chat_core_create_channel_trampoline(void* arg);
/* Сгенерировать ключи и создать канал, добавив себя владельцем. */
void chat_core_create_channel_auto(struct UTUN_INSTANCE* inst, const char* name);
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-потоке. Другие узлы канал не теряют. */
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;
};
/* Трамплин для gui_bridge_post_uasync (GUI → uasync) */
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) и отправка в GUI. arg = struct UTUN_INSTANCE* (inst) */
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);
/* Сбор списка ETCP-соединений и отправка в GUI (binary packet). arg = inst */
void chat_core_collect_conn_list_trampoline(void* arg);
struct chat_node_arg { struct UTUN_INSTANCE* inst; uint64_t node_id; };
/* Сбор полных метрик одного соединения (arg = u_malloc struct chat_node_arg) */
void chat_core_collect_conn_metrics_trampoline(void* arg);
/* Сбор live-снапшота для панели деталей участника (arg = u_malloc struct chat_node_arg) */
void chat_core_collect_member_detail_trampoline(void* arg);
/* ── Headless CLI API ── */
/* Список каналов с метаданными в JSON: [{id,name,owner_id,owner_name,peers,msgs,online}] */
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 — сколько пропустить. */
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 или готовность группы. */
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 */
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 удаляет ссылку. */
int chat_core_set_my_avatar(struct UTUN_INSTANCE* inst, const char* ch_id, uint64_t server, const char* file);
int chat_core_name_userinfo(const char* name, char* out, size_t capacity);
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.
* Вызов только в uasync-потоке, вне внешней SQLite-транзакции. */
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). */
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 */
+12 -5
View File
@@ -22,7 +22,7 @@ struct UTUN_INSTANCE;
extern "C" {
#endif
/* Типы событий (совпадают с GUI_EVT_* в gui_bridge.h для прямой совместимости) */
// Типы событий (совпадают с GUI_EVT_* в gui_bridge.h для прямой совместимости)
#define CHAT_EVT_MSG_RECEIVED 1 /* [ch_id_len:1][ch_id:var][author_node_id:8][initial_sync:1] */
#define CHAT_EVT_CONNECT_RESULT 2 /* [node_id:8][result:4][channel_id:8] */
#define CHAT_EVT_NEW_PEER 3 /* [node_id:8] */
@@ -92,15 +92,22 @@ extern "C" {
#define CHAT_EVT_RADIO_CALL 65 /* [id:8LE][group:8LE][peer:8LE][state:1][reason:1][incoming:1], radio_call.h */
#define CHAT_EVT_CALL_PHONE_BUSY 66 /* [id:8LE][reserved:1]; вызывающий выбирает продолжить либо завершить */
// ── Подключение получателя событий ──
typedef void (*chat_event_handler_fn)(struct UTUN_INSTANCE* inst, int type, const uint8_t* data, int len);
/* Заменить обработчик; NULL отключает доставку. Предыдущий обработчик не вызывается. */
// Заменить обработчик; NULL отключает доставку. Предыдущий обработчик не вызывается.
void chat_event_set_handler(struct UTUN_INSTANCE* inst, chat_event_handler_fn handler);
/* Синхронно передать type и len байт data; память остаётся у отправителя. Формат зависит от CHAT_EVT_*. */
// ── Отправка событий из ядра ──
// Синхронно передать type и len байт data; память остаётся у отправителя. Формат зависит от CHAT_EVT_*.
void chat_event_post(struct UTUN_INSTANCE* inst, int type, const uint8_t* data, int len);
/* Сообщить этап подготовки вложения: state=0 начало, 1 готово, 2 ошибка; target — ID канала или DM. */
// Сообщить этап подготовки вложения: state=0 начало, 1 готово, 2 ошибка; target — ID канала или DM.
void chat_event_attachment(struct UTUN_INSTANCE* inst, int is_dm, const char* target, const uint8_t id[16], int state);
/* request_id=0 отключает подтверждение; вызывается один раз после сохранения либо ошибки подготовки. */
// request_id=0 отключает подтверждение; вызывается один раз после сохранения либо ошибки подготовки.
void chat_event_submitted(struct UTUN_INSTANCE* inst, uint64_t request_id, int success);
#ifdef __cplusplus
+104
View File
@@ -0,0 +1,104 @@
/*
* chat_event.h — система нотификаций о событиях чата
*
* Производители (chat_core, chat_sync, ...) вызывают chat_event_post().
* Потребители (chatgui, headless, будущий HTTP API) регистрируют один обработчик.
*
* Без обработчика — данные логируются через DEBUG_DEBUG + log_dump.
* Все вызовы — из uasync-потока.
*/
#ifndef CHAT_EVENT_H
#define CHAT_EVENT_H
#include <stdint.h>
#include <stddef.h>
struct UTUN_INSTANCE;
#ifdef __cplusplus
extern "C" {
#endif
/* Типы событий (совпадают с GUI_EVT_* в gui_bridge.h для прямой совместимости) */
#define CHAT_EVT_MSG_RECEIVED 1 /* [ch_id_len:1][ch_id:var][author_node_id:8][initial_sync:1] */
#define CHAT_EVT_CONNECT_RESULT 2 /* [node_id:8][result:4][channel_id:8] */
#define CHAT_EVT_NEW_PEER 3 /* [node_id:8] */
#define CHAT_EVT_CHANNEL_UPDATED 4 /* [ch_id_len:1][ch_id:var] */
#define CHAT_EVT_MEMBERS_CHANGED 5 /* [ch_id_len:1][ch_id:var] */
#define CHAT_EVT_MY_NODE_ID 6 /* [node_id:8] */
#define CHAT_EVT_AUTO_CONNECT_STATUS 7 /* [status:1][total_tried:2][node_count:2][success:2] */
#define CHAT_EVT_CHANNEL_PEERS_ONLINE 8 /* [ch_id_len:1][ch_id:var][online_count:2] */
#define CHAT_EVT_DB_READY 9 /* data: none */
#define CHAT_EVT_STATUS_REFRESH 10 /* data: status text */
#define CHAT_EVT_KEYS_GENERATED 11 /* [pub_hex:64] */
#define CHAT_EVT_SERVICE_STARTED 12 /* data: none */
#define CHAT_EVT_SERVICE_STOPPED 13 /* data: none */
#define CHAT_EVT_ATTACHMENT_DOWNLOADED 14 /* [ch_id_len:1][ch_id:var][msg_id:8] */
#define CHAT_EVT_DOWNLOAD_PROGRESS 15 /* [ch_id_len:1][ch_id:var][msg_id:8][blocks_done:4][num_blocks:4] */
#define CHAT_EVT_NODEINFO_UPDATED 16 /* [node_id:8][conn_presence:1][conn_up:1][best_rtt:2][bgp_rtt:2][bgp_nodes:2] */
#define CHAT_EVT_LINK_STATUS_CHANGED 17 /* [peer_id:8][link_id:1][state:1][status:1][links_up:1][initialized:1] */
#define CHAT_EVT_INVITE_RECEIVED 18 /* [ch_id_len:1][ch_id][name_len:1][name][inviter_node_id:8][inviter_name_len:1][inviter_name] */
#define CHAT_EVT_LOCAL_SOCKETS 19 /* JSON: [{"si":id,"nm":"...","ad":"...","po":port,"pt":"TCP|UDP","st":0|1|2,"lk":n},...] */
#define CHAT_EVT_CONN_LIST 20 /* [count:2][entry:46B]* — список ETCP-соединений */
#define CHAT_EVT_CONN_METRICS 21 /* текст — полный дамп метрик выбранного соединения */
#define CHAT_EVT_MEMBER_DETAIL 22 /* [node_id:8][flags:1][own_tcp_active:2][links...][socks...] — live-снапшот для панели деталей участника */
#define CHAT_EVT_MEMBER_LIST 23 /* [ch_id_len:1][ch_id:var][count:2][member:CHAT_MEMBER_DISPLAY_SIZE]* */
#define CHAT_EVT_MEMBER_UPDATED 24 /* [ch_id_len:1][ch_id:var][count:1][member:CHAT_MEMBER_DISPLAY_SIZE] */
#define CHAT_EVT_MEMBER_REMOVED 25 /* [ch_id_len:1][ch_id:var][node_id:8] — мембер удалён из канала */
#define CHAT_EVT_BGP_INFO 26 /* текст: "rtt:XXms hop:X via bgp: [XXXX] name1 [XXXX] name2" */
#define CHAT_EVT_CONNECTING_NODES 27 /* [ch_id_len:1][ch_id:var][count:2][node_id:8]* */
#define CHAT_EVT_ADMIN_KEY_RECEIVED 28 /* [ch_id_len:1][ch_id:var][from_node_id:8] — получен канальный ключ (стал совладельцем) */
#define CHAT_EVT_INVITE_LINK_READY 29 /* [ch_id_len:1][ch_id:var][request_id:8][link_len:2][link:var] — пустой link = ошибка */
#define CHAT_EVT_INVITE_CANDIDATES 30 /* [ch_id_len:1][ch_id:var][auto_node_id:8][count:2][node_id:8]* — достижимые узлы для invite */
#define CHAT_EVT_CHANNEL_DELETED 31 /* [ch_id_len:1][ch_id:var] — канал удалён локально */
#define CHAT_EVT_DM_MSG_RECEIVED 32 /* [conv_id_len:1][conv_id:var][author_node_id:8] */
#define CHAT_EVT_DM_CONV_UPDATED 33 /* [conv_id_len:1][conv_id:var][generation:8LE] — беседа создана/изменена */
#define CHAT_EVT_CALL_INCOMING 34 /* [ch_id_len:1][ch_id:var][caller_node_id:8][call_id:8] */
#define CHAT_EVT_CALL_RINGING 35 /* [call_id:8] */
#define CHAT_EVT_CALL_ACCEPTED 36 /* [call_id:8] */
#define CHAT_EVT_CALL_ENDED 37 /* [call_id:8][reason:1] */
#define CHAT_EVT_CALL_DECLINED 38 /* [call_id:8][reason:1] */
#define CHAT_EVT_CALL_STATS 39 /* [call_id:8][rtt:2][buffer:2][tempo:2][dropped:2][underruns:2][min:2][max:2][reserve:2] */
#define CHAT_EVT_CALL_ERROR 40 /* [call_id:8][err:1][text:var] */
#define CHAT_EVT_DM_MESSAGES 41 /* [conv_id_len:1][conv_id:var][JSON:utf8] — сообщения беседы и состояния доставки */
#define CHAT_EVT_CALL_PATH 42 /* [call_id:8][text:var] — BGP-маршрут до пира звонка */
#define CHAT_EVT_RADIO_TALK 43 /* [group_id:8][src_node_id:8][stream_id:2][on:1] — кто-то начал/закончил говорить */
#define CHAT_EVT_CALL_CONNECTION 44 /* [call_id:8][CONN_TYPE:1][next_hop_node_id:8] — текущий путь отправки звонка */
#define CHAT_EVT_DM_CONVERSATIONS 45 /* [JSON:utf8] — последние 200 личных бесед */
#define CHAT_EVT_ATTACHMENT_PREPARATION 46 /* [target_len:1][target:var][job_uuid:16][state:1], 0=start 1=ready 2=failed */
#define CHAT_EVT_DM_CALL_ROUTE 47 /* [conv_id_len:1][conv_id:var][peer_node_id:8][group_id:8], 0 = нет BGP-пути */
#define CHAT_EVT_DM_INVITE_RESULT 48 /* [request_id:8][result:4][peer_node_id:8][conv_id:8][generation:8], dm_invite_result */
#define CHAT_EVT_MEMBER_FILES_RESPONSE 49 /* [group:8LE][server:8LE][mf wire response] */
#define CHAT_EVT_MEMBER_FILES_TRANSFER 50 /* id/group/server/owner/done/total:8LE,status:2LE,state:1 */
#define CHAT_EVT_AVATAR_RESULT 51 /* request_id/group:8LE, failed:1; результат публикации ссылки профиля */
#define CHAT_EVT_GROUP_AVATAR_RESULT 52 /* request_id/group:8LE, failed:1; результат публикации аватарки группы */
#define CHAT_EVT_FEEDBACK_CHANGED 53 /* [channel_len:1][channel][message_id:32][kind:1] — состояние после commit */
#define CHAT_EVT_FEEDBACK_RESULT 54 /* [request_id:8LE][success:1][JSON] — результат команды интерфейса */
#define CHAT_EVT_RADIO_MUTE_RESULT 55 /* [group_id:8LE][node_id:8LE][success:1] — локальный переключатель рации */
#define CHAT_EVT_MSG_SUBMITTED 56 /* [request_id:8LE][success:1] — результат локального сохранения сообщения */
#define CHAT_EVT_CPU_LOAD 57 /* [valid:1][average_percent:1] — CPU потока uasync, среднее за секунду, 0..100 */
#define CHAT_EVT_STREAM_SOURCES 58 /* [group_id:8BE][valid:1][JSON:utf8] — доступные публикации выбранного канала */
#define CHAT_EVT_DM_DELETE_RESULT 59 /* [request:8LE][conv:8LE][generation:8LE][success:1] — после транзакции */
#define CHAT_EVT_ATTACHMENT_KEYS_CHANGED 60 /* [channel_len:1][channel][success?:1], без секретов; success — результат команды GUI */
#define CHAT_EVT_LOCAL_DEVICES 61 /* [count:2BE][node:8BE,name_len:1,name:UTF8]* */
#define CHAT_EVT_LOCAL_INVITE 62 /* [node:8BE][name_len:1][name:UTF8][invite_link:UTF8], без NUL */
#define CHAT_EVT_LOCAL_DELIVERY 63 /* [request:8BE][node:8BE][status:1], local_delivery_status, не результат JOIN */
#define CHAT_EVT_LOCAL_DISCOVERY_ACTIVE 64 /* [active:1], Android MulticastLock */
#define CHAT_EVT_RADIO_CALL 65 /* [id:8LE][group:8LE][peer:8LE][state:1][reason:1][incoming:1], radio_call.h */
#define CHAT_EVT_CALL_PHONE_BUSY 66 /* [id:8LE][reserved:1]; вызывающий выбирает продолжить либо завершить */
typedef void (*chat_event_handler_fn)(struct UTUN_INSTANCE* inst, int type, const uint8_t* data, int len);
void chat_event_set_handler(struct UTUN_INSTANCE* inst, chat_event_handler_fn handler);
void chat_event_post(struct UTUN_INSTANCE* inst, int type, const uint8_t* data, int len);
void chat_event_attachment(struct UTUN_INSTANCE* inst, int is_dm, const char* target, const uint8_t id[16], int state);
/* request_id=0 отключает подтверждение; вызывается один раз после сохранения либо ошибки подготовки. */
void chat_event_submitted(struct UTUN_INSTANCE* inst, uint64_t request_id, int success);
#ifdef __cplusplus
}
#endif
#endif /* CHAT_EVENT_H */
+33 -7
View File
@@ -1,32 +1,58 @@
/* Подписанная аватарка канала: SQLite, максимум 16 КиБ, router-блоки до 4 КиБ.
* Все API/callbacks — uasync; данные публикуются только после полной проверки подписи. */
/* group_avatar — общая подписанная аватарка CHAT-канала, хранение в SQLite и обмен между участниками.
* После init: get читает текущую версию, set публикует картинку владельца, apply принимает проверяемую запись пира.
* Картинка до 16 КиБ передаётся router-блоками до 4 КиБ; данные публикуются после проверки подписи канала.
* Все вызовы/callbacks — uasync. Изменение картинки приходит событием CHAT_EVT_CHANNEL_UPDATED. */
#ifndef GROUP_AVATAR_H
#define GROUP_AVATAR_H
#include <stdint.h>
#include <stddef.h>
struct UTUN_INSTANCE;
struct sqlite3;
// ── Схема хранения ──
// Создать поля хранения в общей SQLite; 0/-1, БД остаётся у вызывающего.
int group_avatar_schema_init(struct sqlite3* db);
#define GROUP_AVATAR_MAX 16384
#define GROUP_AVATAR_BLOCK 4096
struct group_avatar_record {
uint64_t version;
uint16_t size;
uint8_t hash[32], signature[64], data[GROUP_AVATAR_MAX];
uint64_t version; // 0 — картинка ещё не публиковалась; иначе версия подписанной записи
uint16_t size; // байты картинки; size=0 при ненулевой версии — подписанное удаление
uint8_t hash[32], signature[64], data[GROUP_AVATAR_MAX]; // SHA256, подпись канала Ed25519 и готовая картинка
};
// ── Жизненный цикл модуля ──
// Создать контекст и подключить сетевой обмен; 0/-1. Повтор допустим.
int group_avatar_init(struct UTUN_INSTANCE* inst);
// Остановить обмен и освободить контекст; сохранённая картинка остаётся в БД.
void group_avatar_destroy(struct UTUN_INSTANCE* inst);
// Отменить сетевые задачи канала перед удалением группы.
void group_avatar_cancel_group(struct UTUN_INSTANCE* inst, uint64_t group);
// ── Чтение и изменение аватарки ──
// Скопировать запись и проверить её подпись; 0 — прочитано (version=0, если не публиковалась), -1 — ошибка/нет канала.
int group_avatar_get(struct UTUN_INSTANCE* inst, uint64_t group, struct group_avatar_record* record);
/* 1=изменено, 0=прежняя/старая запись; <0 — ошибка. */
// Проверить подпись и сохранить более новую запись; 1=изменено, 0=прежняя/старая, -1=ошибка. Структура остаётся у вызывающего.
int group_avatar_apply(struct UTUN_INSTANCE* inst, uint64_t group, const struct group_avatar_record* record);
/* Публикация своей картинки требует приватный Ed25519 ключ канала. size=0 — подписанное удаление. */
/* Подписать и сохранить картинку: нужен приватный Ed25519 ключ канала. size=0 — удаление; data копируется.
* 0 — сохранено или картинка прежняя, -1 — ошибка/нет прав. Доставка пирам выполняется отдельно. */
int group_avatar_set(struct UTUN_INSTANCE* inst, uint64_t group, const uint8_t* data, size_t size);
// ── Команда интерфейса ──
struct group_avatar_gui_request {
struct UTUN_INSTANCE* inst;
uint64_t id, group;
uint16_t size;
uint8_t data[GROUP_AVATAR_MAX];
};
// arg — u_malloc/u_calloc group_avatar_gui_request, освобождается здесь; результат — CHAT_EVT_GROUP_AVATAR_RESULT.
void group_avatar_gui_set(void* arg);
#endif
+32
View File
@@ -0,0 +1,32 @@
/* Подписанная аватарка канала: SQLite, максимум 16 КиБ, router-блоки до 4 КиБ.
* Все API/callbacks — uasync; данные публикуются только после полной проверки подписи. */
#ifndef GROUP_AVATAR_H
#define GROUP_AVATAR_H
#include <stdint.h>
#include <stddef.h>
struct UTUN_INSTANCE;
struct sqlite3;
int group_avatar_schema_init(struct sqlite3* db);
#define GROUP_AVATAR_MAX 16384
#define GROUP_AVATAR_BLOCK 4096
struct group_avatar_record {
uint64_t version;
uint16_t size;
uint8_t hash[32], signature[64], data[GROUP_AVATAR_MAX];
};
int group_avatar_init(struct UTUN_INSTANCE* inst);
void group_avatar_destroy(struct UTUN_INSTANCE* inst);
void group_avatar_cancel_group(struct UTUN_INSTANCE* inst, uint64_t group);
int group_avatar_get(struct UTUN_INSTANCE* inst, uint64_t group, struct group_avatar_record* record);
/* 1=изменено, 0=прежняя/старая запись; <0 — ошибка. */
int group_avatar_apply(struct UTUN_INSTANCE* inst, uint64_t group, const struct group_avatar_record* record);
/* Публикация своей картинки требует приватный Ed25519 ключ канала. size=0 — подписанное удаление. */
int group_avatar_set(struct UTUN_INSTANCE* inst, uint64_t group, const uint8_t* data, size_t size);
struct group_avatar_gui_request {
struct UTUN_INSTANCE* inst;
uint64_t id, group;
uint16_t size;
uint8_t data[GROUP_AVATAR_MAX];
};
void group_avatar_gui_set(void* arg);
#endif
+31 -10
View File
@@ -1,8 +1,8 @@
/* media_delivery — канальные медиафайлы: поиск держателей блоков, выдача/relay и репликация доступности.
* Суперузлы обмениваются block_availability, держатели отдают подписанные блоки, media_download собирает файл.
* init создаёт per-instance состояние, bind подключает протокол к router; destroy отменяет сетевую работу.
* Все операции — uasync-поток; долгие файловые/crypto-задачи выполняет media_async.
* E2E-файлы личных бесед используют dm_media/file_transfer, а не канальную таблицу доступности. */
/* media_delivery — поиск держателей и доставка блоков файлов CHAT-канала, включая промежуточную раздачу (relay).
* После регистрации файла через media_index автор вызывает announce_media; получатель загружает его через media_download.
* Суперузлы обмениваются сведениями о наличии блоков; держатели выдают подписанные блоки, загрузчик собирает файл.
* Чат-сервис вызывает init и затем destroy; bind — другое имя init, вызывать оба последовательно нельзя.
* Все операции — uasync; долгие файловые/crypto-задачи выполняет media_async. Для файлов DM используется dm_media/file_transfer. */
#ifndef MEDIA_DELIVERY_H
#define MEDIA_DELIVERY_H
@@ -16,7 +16,7 @@ extern "C" {
struct UTUN_INSTANCE;
/* ── константы ── */
// ── константы ──
#define MEDIA_MAX_REPL_INFLIGHT 4
#define MEDIA_REPL_TIMEOUT_TB 20000 // начальный таймаут 2s (0.1ms)
@@ -29,7 +29,7 @@ struct UTUN_INSTANCE;
#define MEDIA_DL_STALL_TIMEOUT_TB 20000 // таймаут простоя загрузки 2s (0.1ms)
#define MEDIA_DL_MAX_ATTEMPTS 3 // попыток на блок до ошибки
/* ── структуры ── */
// ── структуры ──
struct media_super_peer {
struct ll_entry ll; // индекс по peer_node_id (8 байт)
@@ -45,7 +45,7 @@ struct media_super_peer {
struct media_delivery_ctx* md; // обратная ссылка
};
/* per-file download tracking (source node admission control) */
// per-file download tracking (source node admission control)
struct md_file_load {
struct ll_entry ll; // индекс по media_id (16 байт)
@@ -132,12 +132,27 @@ struct media_delivery_ctx {
struct UTUN_INSTANCE* inst;
};
// ── Жизненный цикл и роль узла ──
// Создать таблицы/контекст и подключить router и подписки; 0/-1. Общая БД и группы должны быть готовы.
int media_delivery_init(struct UTUN_INSTANCE* inst);
// Отменить загрузки/выдачи/репликацию и снять подписки; вызывать до удаления групп и общей БД.
void media_delivery_destroy(struct UTUN_INSTANCE* inst);
// Вызывает init целиком; использовать вместо init, а не как следующий этап. 0/-1.
int media_delivery_bind(struct UTUN_INSTANCE* inst);
// Включить/выключить локальную работу суперузла и обмен доступностью блоков.
void media_delivery_set_supernode(struct UTUN_INSTANCE* inst, int is_supernode);
// ── Внутренний учёт выдач ──
// Внутреннее завершение исходящей выдачи: снять единицу active_streams и отметить завершение для диагностики.
void media_delivery_stream_done(struct UTUN_INSTANCE* inst);
// ── Анонсы доступности блоков ──
/* Автор анонсирует свои блоки суперузлам CHAT-группы (HAVE_BLOCK), чтобы первый
* скачивающий мог найти держателя через QUERY. Вызывается после регистрации медиа. */
void media_delivery_announce_media(struct UTUN_INSTANCE* inst, uint64_t group_id,
@@ -154,19 +169,25 @@ void media_delivery_send_have_block(struct UTUN_INSTANCE* inst, uint64_t group_i
* block_availability для уже скачанных ранее медиа. Идемпотентно на суперузле. */
void media_delivery_announce_local_blocks(struct UTUN_INSTANCE* inst);
// ── Отмена работы канала ──
/* Отменить всё активное, что связано с каналом (локальное удаление канала):
* загрузки с group_id, их relay-контексты и super_peers этой группы. */
void media_delivery_cancel_channel(struct UTUN_INSTANCE* inst, uint64_t group_id);
/* relay block context helpers (used by media_download.c) */
// ── Внутренние операции загрузчика: relay ──
struct relay_block_ctx* md_relay_find(struct media_delivery_ctx* md, const uint8_t* block_id);
struct relay_block_ctx* md_relay_add(struct media_delivery_ctx* md,
const uint8_t* block_id, const uint8_t* media_id, uint32_t chunk);
void md_relay_remove(struct media_delivery_ctx* md, const uint8_t* block_id);
/* file_load helpers (per-file source download tracking) */
// ── Внутренний учёт нагрузки на файл ──
struct md_file_load* md_file_load_find(struct media_delivery_ctx* md, const uint8_t* media_id);
int md_file_load_inc(struct media_delivery_ctx* md, const uint8_t* media_id, uint64_t node_id);
void md_file_load_dec(struct media_delivery_ctx* md, const uint8_t* media_id, uint64_t node_id);
#ifdef __cplusplus
+175
View File
@@ -0,0 +1,175 @@
/* media_delivery — канальные медиафайлы: поиск держателей блоков, выдача/relay и репликация доступности.
* Суперузлы обмениваются block_availability, держатели отдают подписанные блоки, media_download собирает файл.
* init создаёт per-instance состояние, bind подключает протокол к router; destroy отменяет сетевую работу.
* Все операции — uasync-поток; долгие файловые/crypto-задачи выполняет media_async.
* E2E-файлы личных бесед используют dm_media/file_transfer, а не канальную таблицу доступности. */
#ifndef MEDIA_DELIVERY_H
#define MEDIA_DELIVERY_H
#ifdef __cplusplus
extern "C" {
#endif
#include <stdint.h>
#include "../lib/ll_queue.h"
#include "../lib/sqlite3.h"
struct UTUN_INSTANCE;
/* ── константы ── */
#define MEDIA_MAX_REPL_INFLIGHT 4
#define MEDIA_REPL_TIMEOUT_TB 20000 // начальный таймаут 2s (0.1ms)
#define MEDIA_REPL_TIMEOUT_MAX_TB 6000000 // макс 10 min
#define MEDIA_MAX_DOWNLOADS 10
#define MEDIA_QUERY_TIMEOUT_TB 20000 // таймаут QUERY 2s
#define MEDIA_HAVE_BLOCK_TIMEOUT_TB 20000 // таймаут HAVE_BLOCK_ACK 2s
#define MEDIA_RECONNECT_COOLDOWN_TB 360000000 // 1 час (0.1ms)
#define MEDIA_HELLO_TIMEOUT_TB 20000 // таймаут SUPER_HELLO 2s
#define MEDIA_DL_STALL_TIMEOUT_TB 20000 // таймаут простоя загрузки 2s (0.1ms)
#define MEDIA_DL_MAX_ATTEMPTS 3 // попыток на блок до ошибки
/* ── структуры ── */
struct media_super_peer {
struct ll_entry ll; // индекс по peer_node_id (8 байт)
uint64_t peer_node_id;
uint64_t group_id; // CHAT-группа, в которой установлена связь с пиром
uint64_t peer_last_recv_id; // что пир говорит он получил от нас (из SUPER_HELLO / SUPER_ACK)
uint32_t timeout_tb; // текущий таймаут (растёт при ошибках)
uint8_t inflight_count; // пакетов в полёте (макс 4)
uint8_t connected; // 1 = прямое подключение установлено
uint8_t hello_done; // 1 = SUPER_HELLO обмен завершён, можно реплицировать
void* timeout_timer; // таймер ретрансмита
struct CONN_MGR_HANDLE* connection; // собственное CM-владение; восстановлением пути управляет CM
struct media_delivery_ctx* md; // обратная ссылка
};
/* per-file download tracking (source node admission control) */
struct md_file_load {
struct ll_entry ll; // индекс по media_id (16 байт)
uint8_t media_id[16];
int active_downloads; // текущее число исходящих стримов
uint64_t downloader_ids[10]; // кто сейчас качает (для редиректа)
};
/*
* block_availability: жизненный цикл записей и гарантия уникальности id
*
* Каждое событие создаёт новую запись с новым id (AUTOINCREMENT).
* Старая запись удаляется — id НЕ переиспользуется.
* Это критично для репликации: курсор super_sync.last_recv_id
* всегда монотонно растёт и не пропускает события.
*
* BLOCK_PROCESSING (subcmd 0x10):
* md_ba_insert(... , status=0) — чистый INSERT, новый id=X.
*
* HAVE_BLOCK (subcmd 0x06):
* md_ba_complete_block():
* 1. DELETE FROM block_availability WHERE block_uuid=? AND node_id=? AND status=0
* (удаляет processing-запись id=X, если была)
* 2. Если completed уже есть → no-op (возврат 0)
* 3. INSERT ... status=1 — новый id=Y.
*
* SUPER_REPL (subcmd 0x08):
* Принимающая сторона:
* - status=0: INSERT OR IGNORE (дубликаты при репликации — норма)
* - status=1: md_ba_complete_block() — та же логика что HAVE_BLOCK
*
* Итог: processing id=X удаляется, completed вставляется с новым id=Y>X.
* Репликация через md_ba_get_since(id > last_recv_id) всегда видит Y.
*/
struct media_served_node {
struct ll_entry ll; // индекс по node_id (8 байт)
uint64_t node_id;
uint64_t group_id;
int64_t joined_at; // timestamp регистрации
};
#define MD_MAX_RELAY_DOWNSTREAM 5
struct relay_downstream {
uint64_t node_id;
uint64_t group_id;
uint64_t sent_offset; // сколько байт уже отправлено этому пиру
struct queue_waiter_handle waiter;
};
struct relay_block_ctx {
struct ll_entry ll; // индекс по block_id (16 байт)
uint8_t block_id[16];
uint8_t media_id[16];
uint32_t chunk;
uint64_t file_offset; // всего байт получено от апстрима (размер .chunk_N)
char chunk_file[2048]; // путь к .chunk_N файлу для relay-чтения
struct relay_downstream downstream[MD_MAX_RELAY_DOWNSTREAM];
int downstream_count;
struct UTUN_INSTANCE* inst;
};
struct stream_ctx;
struct relay_fwd_ctx;
struct media_delivery_ctx {
struct stream_ctx* streams;
struct relay_fwd_ctx* forwards;
uint64_t self_node_id;
int initialized;
uint8_t is_supernode; // из adm_tags в peers_*
uint8_t active_streams; // текущее число исходящих стримов (admission)
uint8_t max_downloads_per_file; // лимит параллельных загрузок с одного файла (source node), default 3
uint8_t stream_completed; // 1 = хотя бы один стрим завершился (для тестов)
uint8_t streams_started; // монотонный счётчик запущенных стримов (для тестов)
int dl_stall_timeout_tb; // таймаут простоя загрузки (по умолч. MEDIA_DL_STALL_TIMEOUT_TB)
int dl_max_attempts; // попыток на блок (по умолч. MEDIA_DL_MAX_ATTEMPTS)
sqlite3* db; // = inst->topo_sqlite_db (кешируем для удобства)
struct ll_queue* served_nodes; // media_served_node — только суперузел
struct ll_queue* super_peers; // media_super_peer — только суперузел
struct ll_queue* downloads; // media_download — активные загрузки
struct ll_queue* relay_blocks; // relay_block_ctx — relay в процессе загрузки
struct ll_queue* file_loads; // md_file_load — per-file download count (source node)
struct UTUN_INSTANCE* inst;
};
int media_delivery_init(struct UTUN_INSTANCE* inst);
void media_delivery_destroy(struct UTUN_INSTANCE* inst);
int media_delivery_bind(struct UTUN_INSTANCE* inst);
void media_delivery_set_supernode(struct UTUN_INSTANCE* inst, int is_supernode);
void media_delivery_stream_done(struct UTUN_INSTANCE* inst);
/* Автор анонсирует свои блоки суперузлам CHAT-группы (HAVE_BLOCK), чтобы первый
* скачивающий мог найти держателя через QUERY. Вызывается после регистрации медиа. */
void media_delivery_announce_media(struct UTUN_INSTANCE* inst, uint64_t group_id,
const uint8_t* media_id, const uint8_t* block_ids,
int num_blocks);
/* Отправить один HAVE_BLOCK суперузлу (подпись Ed25519 + md_send). Общий helper
* для announce_media / announce_local_blocks / media_download. */
void media_delivery_send_have_block(struct UTUN_INSTANCE* inst, uint64_t group_id, uint64_t dst_node_id,
const uint8_t* media_id, const uint8_t* block_id, uint32_t chunk);
/* Переанонсировать суперузлам ВСЕ локальные блоки (media_files WHERE node_id=self).
* Вызывается при старте после синхронизации, чтобы суперузел восстановил свой
* block_availability для уже скачанных ранее медиа. Идемпотентно на суперузле. */
void media_delivery_announce_local_blocks(struct UTUN_INSTANCE* inst);
/* Отменить всё активное, что связано с каналом (локальное удаление канала):
* загрузки с group_id, их relay-контексты и super_peers этой группы. */
void media_delivery_cancel_channel(struct UTUN_INSTANCE* inst, uint64_t group_id);
/* relay block context helpers (used by media_download.c) */
struct relay_block_ctx* md_relay_find(struct media_delivery_ctx* md, const uint8_t* block_id);
struct relay_block_ctx* md_relay_add(struct media_delivery_ctx* md,
const uint8_t* block_id, const uint8_t* media_id, uint32_t chunk);
void md_relay_remove(struct media_delivery_ctx* md, const uint8_t* block_id);
/* file_load helpers (per-file source download tracking) */
struct md_file_load* md_file_load_find(struct media_delivery_ctx* md, const uint8_t* media_id);
int md_file_load_inc(struct media_delivery_ctx* md, const uint8_t* media_id, uint64_t node_id);
void md_file_load_dec(struct media_delivery_ctx* md, const uint8_t* media_id, uint64_t node_id);
#ifdef __cplusplus
}
#endif
#endif
+27 -6
View File
@@ -1,5 +1,6 @@
/* media_index — индекс блоков канального файла в SQLite media_files: UUID, SHA256, Ed25519-подписи и location.
* init создаёт таблицу; register_async копирует/хеширует/подписывает файл в worker, затем commit и callback в uasync.
/* media_index — регистрация файлов канала и их блоков в SQLite для последующей раздачи и сообщений чата.
* Порядок: init → register_async → callback с UUID, хешем и подписями блоков; затем можно анонсировать файл.
* Register_async копирует/хеширует/подписывает файл в worker, затем commit и callback выполняются в uasync.
* db/ma/ua заимствованы и должны жить до завершения; доступ к БД выполняется в потоке ядра.
* Результат callback заимствован только до возврата. result_free освобождает block_ids/block_sigs, но не саму структуру. */
#ifndef MEDIA_INDEX_H
@@ -22,15 +23,23 @@ struct UASYNC;
struct media_index_result {
uint8_t media_id[16];
uint8_t content_hash[32];
int64_t file_size;
int64_t block_size;
int num_blocks;
int64_t file_size; // полный размер файла, байты
int64_t block_size; // размер обычного блока, байты; последний может быть короче
int num_blocks; // число блоков и элементов block_ids/block_sigs
uint8_t* block_ids; // num_blocks * 16
uint8_t* block_sigs; // num_blocks * 64
};
// ── Инициализация таблицы ──
// Создать таблицу media_files и её индексы, сохранив существующие записи; 0/-1.
int media_index_init(sqlite3* db);
// ── Регистрация файлов и блоков ──
/* Записать подготовленные блоки и подписанные сведения о держателе; 0/-1, result остаётся у вызывающего.
* chat_id — десятичный ID канала; dest_path — готовый файл, media_base — база для относительного location.
* Выполняется в uasync, сама функция не копирует/не хеширует файл и не анонсирует блоки. */
int media_index_commit(
sqlite3* db, const struct media_index_result* result,
uint64_t node_id, const uint8_t* ed25519_privkey,
@@ -43,7 +52,10 @@ int media_index_register_downloaded(sqlite3* db, const uint8_t* media_id, const
const char* location, uint64_t node_id,
int64_t file_size, int64_t chunk_size, int chunk, int64_t offset);
/* Callback может быть синхронным при отказе запуска. err=0 даёт result, иначе result=NULL. */
/* Подготовить и зарегистрировать файл; пути/ключ/chat_id копируются до возврата, cb_arg заимствован до callback.
* При copy_file!=0 копирует src_path в dest_path; при 0 dest_path уже должен содержать готовый файл.
* Файлы и db/ma/ua должны жить до callback. Callback может быть синхронным при отказе запуска.
* err=0 даёт result только на время callback; иначе result=NULL. Сохранить нужные поля/массивы отдельной копией. */
void media_index_register_async(
struct media_async* ma, struct UASYNC* ua, sqlite3* db,
uint64_t node_id, const uint8_t* ed25519_privkey,
@@ -53,10 +65,19 @@ void media_index_register_async(
void (*cb)(void* arg, int err, const struct media_index_result* result),
void* cb_arg);
// ── Вспомогательные операции ──
// Заполнить 16 байт идентификатора; возвращает 0.
int media_index_generate_uuid(uint8_t uuid_out[16]);
// Освободить только массивы собственного result и обнулить их указатели; callback-result освобождает сам модуль.
void media_index_result_free(struct media_index_result* result);
// Выбрать размер блока в байтах: 10..25 МиБ для непустого файла, 0 для file_size=0.
int media_calc_block_size(uint64_t file_size);
// ── Локальное удаление канала ──
/* Удалить медиа канала (локальное удаление канала): стереть файлы на диске
* (media_base + location) и строки media_files с chat_id. Возвращает 0/ошибка. */
int media_index_delete_channel(sqlite3* db, const char* chat_id, const char* media_base);
+67
View File
@@ -0,0 +1,67 @@
/* media_index — индекс блоков канального файла в SQLite media_files: UUID, SHA256, Ed25519-подписи и location.
* init создаёт таблицу; register_async копирует/хеширует/подписывает файл в worker, затем commit и callback в uasync.
* db/ma/ua заимствованы и должны жить до завершения; доступ к БД выполняется в потоке ядра.
* Результат callback заимствован только до возврата. result_free освобождает block_ids/block_sigs, но не саму структуру. */
#ifndef MEDIA_INDEX_H
#define MEDIA_INDEX_H
#ifdef __cplusplus
extern "C" {
#endif
#include <stdint.h>
#include <stdint.h>
#include <sqlite3.h>
struct media_async;
struct UASYNC;
#define MEDIA_ID_SIZE 16
#define MEDIA_HASH_SIZE 32
struct media_index_result {
uint8_t media_id[16];
uint8_t content_hash[32];
int64_t file_size;
int64_t block_size;
int num_blocks;
uint8_t* block_ids; // num_blocks * 16
uint8_t* block_sigs; // num_blocks * 64
};
int media_index_init(sqlite3* db);
int media_index_commit(
sqlite3* db, const struct media_index_result* result,
uint64_t node_id, const uint8_t* ed25519_privkey,
const char* chat_id, const char* dest_path, const char* media_base);
/* Зарегистрировать скачанный блок в media_files (location — путь к собранному файлу),
* чтобы узел мог отдавать его как держатель. node_sign = нули (при отдаче не проверяется). */
int media_index_register_downloaded(sqlite3* db, const uint8_t* media_id, const uint8_t* block_id,
const uint8_t* content_hash, const char* chat_id,
const char* location, uint64_t node_id,
int64_t file_size, int64_t chunk_size, int chunk, int64_t offset);
/* Callback может быть синхронным при отказе запуска. err=0 даёт result, иначе result=NULL. */
void media_index_register_async(
struct media_async* ma, struct UASYNC* ua, sqlite3* db,
uint64_t node_id, const uint8_t* ed25519_privkey,
const char* chat_id,
const char* src_path, const char* dest_path, int copy_file,
const char* media_base,
void (*cb)(void* arg, int err, const struct media_index_result* result),
void* cb_arg);
int media_index_generate_uuid(uint8_t uuid_out[16]);
void media_index_result_free(struct media_index_result* result);
int media_calc_block_size(uint64_t file_size);
/* Удалить медиа канала (локальное удаление канала): стереть файлы на диске
* (media_base + location) и строки media_files с chat_id. Возвращает 0/ошибка. */
int media_index_delete_channel(sqlite3* db, const char* chat_id, const char* media_base);
#ifdef __cplusplus
}
#endif
#endif
+51 -11
View File
@@ -1,7 +1,8 @@
/* Хранилище участников и асинхронный клиент поверх подписанного/зашифрованного router.
* Все API — uasync. callback получает заимствованный response ровно один раз; операция
* уже освобождена. NULL при отказе запуска, callback не вызывается. request копируется.
* quiesce запрещает новые команды и отменяет producers; destroy вне callbacks ждёт
/* member_files — файловые каталоги участников на storage-узлах CHAT-группы и клиент доступа к ним.
* Для списка/операций: request с командой из protocol.h → callback с ответом. Для файла: transfer_start → progress/finished.
* Клиент указывает группу, storage-сервер, владельца каталога и путь; права проверяются по членству и роли узла.
* Все API — uasync. Запрос копируется; callback получает заимствованный ответ, действительный только до возврата.
* Чат-сервис вызывает init; quiesce запрещает новые команды и отменяет producers; destroy вне callbacks ждёт
* собственный дисковый поток до удаления CHAT-групп. Cancel не удаляет опубликованные файлы.
* GUI-команды используют trampolines; результат — CHAT_EVT_MEMBER_FILES_RESPONSE/TRANSFER. */
#ifndef MEMBER_FILES_H
@@ -13,43 +14,82 @@ extern "C" {
struct UTUN_INSTANCE;
struct mf_pending;
typedef void (*mf_response_fn)(void* arg, const struct mf_message* response);
// ── Жизненный цикл модуля ──
// Создать состояние сервера/клиента и запустить дисковый worker; 0/-1.
int member_files_init(struct UTUN_INSTANCE* inst);
// Запретить новые команды и начать отмену запросов/передач; запущенный дисковый I/O завершается отдельно.
void member_files_quiesce(struct UTUN_INSTANCE* inst);
// Дождаться дискового worker и освободить контекст; вне callbacks, до удаления CHAT-групп.
void member_files_destroy(struct UTUN_INSTANCE* inst);
// Отменить запросы и передачи одной группы перед её удалением.
void member_files_cancel_group(struct UTUN_INSTANCE* inst, uint64_t group);
// ── Команды файлового сервера ──
/* Отправить одну команду; request_id=0 выбирает случайный ID. NULL — отказ запуска, callback не вызывается.
* При успехе callback ровно один, включая отмену; handle освобождён до callback, arg принадлежит вызывающему. */
struct mf_pending* member_files_request(struct UTUN_INSTANCE* inst, uint64_t group, uint64_t server,
const struct mf_message* request, mf_response_fn callback, void* arg);
// Синхронно завершить свой запрос ответом MF_CANCELLED; handle после вызова недействителен, NULL допустим.
void member_files_cancel(struct mf_pending* pending);
// Отменить pending-запрос с этим request_id; не отменяет потоковую передачу файла.
void member_files_cancel_request_id(struct UTUN_INSTANCE* inst, uint64_t id);
/* 1 — действующий мембер; storage!=0 дополнительно требует подписанную роль хранителя. */
// ── Проверка доступа ──
// 1 — действующий мембер; storage!=0 дополнительно требует подписанную роль хранителя.
int member_files_member(struct UTUN_INSTANCE* inst, uint64_t group, uint64_t node, int storage);
// ── Команда интерфейса ──
struct mf_gui_request { struct UTUN_INSTANCE* inst; uint64_t group, server; struct mf_message message; };
// arg — u_malloc/u_calloc mf_gui_request; освобождается после ответа CHAT_EVT_MEMBER_FILES_RESPONSE.
void member_files_gui_request(void* arg);
// ── Потоковая передача файлов ──
struct mf_transfer_request {
struct UTUN_INSTANCE* inst;
uint64_t id, group, server, owner;
struct UTUN_INSTANCE* inst; // ядро с работающим чат-сервисом
uint64_t id, group, server, owner; // ID операции (0=авто), CHAT-группа, storage-узел и UID владельца каталога
uint64_t max_bytes; // 0 — без лимита; проверяется до открытия локального файла и передачи данных.
int upload;
uint8_t version[16];
char remote[MF_PATH_MAX + 1], local[MF_PATH_MAX + 1];
int upload; // ненулевой — загрузить local на сервер, 0 — скачать remote
uint8_t version[16]; // версия из STAT; нули при создании нового серверного файла
char remote[MF_PATH_MAX + 1], local[MF_PATH_MAX + 1]; // путь в каталоге owner и локальный путь приложения
};
struct mf_transfer;
struct mf_transfer_progress { uint64_t id, done, total; uint16_t status; uint8_t finished; };
typedef void (*mf_transfer_fn)(void* arg, const struct mf_transfer_progress* progress);
// Один handle на файл; до 16 клиентских передач одновременно, включая upload/download к одному пиру.
// Все вызовы/callbacks — uasync. id=0 выбирает случайный ID. NULL: отказ запуска, callback не вызывается.
// finished приходит ровно один раз после закрытия/публикации; handle уже освобождён до callback.
// Прогресс заимствован на время callback. Cancel асинхронно ждёт свой I/O и не затрагивает другие handles.
// Request копируется; исходную структуру можно освободить сразу, arg должен жить до finished.
struct mf_transfer* member_files_transfer_start(const struct mf_transfer_request* request, mf_transfer_fn callback, void* arg);
// Запросить отмену своей передачи; завершение приходит через finished после закрытия её файлового I/O.
void member_files_transfer_cancel(struct mf_transfer* transfer);
// ── Команды интерфейса для передачи и отмены ──
/* id выбирает вызывающий. События: id/group/server/owner/done/total:8LE, status:2LE,state:1;
* state=1 progress, 2 finished. Загрузка/скачивание выполняет общий клиент, без Qt I/O. */
* state=1 progress, 2 finished. arg — u_malloc/u_calloc mf_transfer_request, освобождается после finished. */
void member_files_gui_transfer(void* arg);
struct mf_cancel_request { struct UTUN_INSTANCE* inst; uint64_t id; };
// arg — u_malloc/u_calloc mf_cancel_request, освобождается здесь; отменяет передачу либо одиночный запрос по id.
void member_files_gui_cancel(void* arg);
#ifdef __cplusplus
}
#endif
+56
View File
@@ -0,0 +1,56 @@
/* Хранилище участников и асинхронный клиент поверх подписанного/зашифрованного router.
* Все API — uasync. callback получает заимствованный response ровно один раз; операция
* уже освобождена. NULL при отказе запуска, callback не вызывается. request копируется.
* quiesce запрещает новые команды и отменяет producers; destroy вне callbacks ждёт
* собственный дисковый поток до удаления CHAT-групп. Cancel не удаляет опубликованные файлы.
* GUI-команды используют trampolines; результат — CHAT_EVT_MEMBER_FILES_RESPONSE/TRANSFER. */
#ifndef MEMBER_FILES_H
#define MEMBER_FILES_H
#include "protocol.h"
#ifdef __cplusplus
extern "C" {
#endif
struct UTUN_INSTANCE;
struct mf_pending;
typedef void (*mf_response_fn)(void* arg, const struct mf_message* response);
int member_files_init(struct UTUN_INSTANCE* inst);
void member_files_quiesce(struct UTUN_INSTANCE* inst);
void member_files_destroy(struct UTUN_INSTANCE* inst);
void member_files_cancel_group(struct UTUN_INSTANCE* inst, uint64_t group);
struct mf_pending* member_files_request(struct UTUN_INSTANCE* inst, uint64_t group, uint64_t server,
const struct mf_message* request, mf_response_fn callback, void* arg);
void member_files_cancel(struct mf_pending* pending);
void member_files_cancel_request_id(struct UTUN_INSTANCE* inst, uint64_t id);
/* 1 — действующий мембер; storage!=0 дополнительно требует подписанную роль хранителя. */
int member_files_member(struct UTUN_INSTANCE* inst, uint64_t group, uint64_t node, int storage);
struct mf_gui_request { struct UTUN_INSTANCE* inst; uint64_t group, server; struct mf_message message; };
void member_files_gui_request(void* arg);
struct mf_transfer_request {
struct UTUN_INSTANCE* inst;
uint64_t id, group, server, owner;
uint64_t max_bytes; // 0 — без лимита; проверяется до открытия локального файла и передачи данных.
int upload;
uint8_t version[16];
char remote[MF_PATH_MAX + 1], local[MF_PATH_MAX + 1];
};
struct mf_transfer;
struct mf_transfer_progress { uint64_t id, done, total; uint16_t status; uint8_t finished; };
typedef void (*mf_transfer_fn)(void* arg, const struct mf_transfer_progress* progress);
// Один handle на файл; до 16 клиентских передач одновременно, включая upload/download к одному пиру.
// Все вызовы/callbacks — uasync. id=0 выбирает случайный ID. NULL: отказ запуска, callback не вызывается.
// finished приходит ровно один раз после закрытия/публикации; handle уже освобождён до callback.
// Прогресс заимствован на время callback. Cancel асинхронно ждёт свой I/O и не затрагивает другие handles.
struct mf_transfer* member_files_transfer_start(const struct mf_transfer_request* request, mf_transfer_fn callback, void* arg);
void member_files_transfer_cancel(struct mf_transfer* transfer);
/* id выбирает вызывающий. События: id/group/server/owner/done/total:8LE, status:2LE,state:1;
* state=1 progress, 2 finished. Загрузка/скачивание выполняет общий клиент, без Qt I/O. */
void member_files_gui_transfer(void* arg);
struct mf_cancel_request { struct UTUN_INSTANCE* inst; uint64_t id; };
void member_files_gui_cancel(void* arg);
#ifdef __cplusplus
}
#endif
#endif
+20 -4
View File
@@ -1,8 +1,13 @@
/* multicast — отправка одного payload явно указанному списку узлов topo-группы.
* Общая часть пути передаётся одним пакетом; при расхождении маршрутов пакет
* разделяется на ветки с соответствующими подмножествами получателей.
*
* Как отправлять:
* Источник: open → сообщить свой дескриптор получателям → send → close.
* Получатель: subscribe на источник/дескриптор до отправки DATA → on_data → unsubscribe.
* Все операции — uasync; callbacks отложены, их данные заимствованы до возврата.
* Доставка без сквозных ACK/повторов; потери приходят через on_loss, но сам отчёт тоже может потеряться. */
/* Подробные правила дескрипторов, подписок и владения.
* Отправка:
* 1. multicast_open(group, on_loss, arg) выделяет исходящий дескриптор.
* Номер 1..65535 уникален среди открытых дескрипторов данного ядра.
* Выдача последовательная: 1, 2, ... 65535, затем снова с 1, пропуская
@@ -85,7 +90,7 @@ struct multicast_subscription; /* непрозрачный handle локаль
#define MULTICAST_REPORT_BYTES (16u * 1024u) /* общий бюджет накопленных отчётов и окон троттлинга */
#define MULTICAST_REPORT_INTERVAL_TB 3000 /* 300 мс; timebase = 0.1 мс */
/* Размеры служебной оболочки; payload вместе с ней должен помещаться в 16 КиБ ETCP. */
// Размеры служебной оболочки; payload вместе с ней должен помещаться в 16 КиБ ETCP.
#define MULTICAST_MAX_VISITED 64
#define MULTICAST_HEADER_SIZE 21
#define MULTICAST_PATH_RESERVE (8 * MULTICAST_MAX_VISITED + 1)
@@ -143,6 +148,8 @@ struct multicast_stats {
unsigned subscriptions; /* активные входящие подписки */
};
// ── API отправителя ──
/* Открыть исходящий дескриптор в работающей group. on_loss обязателен; arg может быть NULL.
* Выдать следующий свободный номер по циклическому порядку 1..65535, начиная после
* последнего выданного; 0 не выдаётся. Закрытые номера используются только при обходе.
@@ -167,6 +174,8 @@ int multicast_send(struct UTUN_INSTANCE* inst, uint16_t descriptor, const uint64
* Для отсутствующего дескриптора — диагностируемая ошибка без побочных действий. */
void multicast_close(struct UTUN_INSTANCE* inst, uint16_t descriptor);
// ── API получателя ──
/* Подписаться на локальную доставку (group_id, src_node, src_descriptor).
* src_node!=0, src_descriptor!=0, on_data обязателен; arg может быть NULL.
* Возврат: принадлежащий вызывающему handle либо NULL с диагностикой ошибки.
@@ -181,18 +190,25 @@ struct multicast_subscription* multicast_subscribe(struct TOPO_GROUP* group, uin
* Из собственного on_data допускается; память освобождается после его завершения. */
void multicast_unsubscribe(struct multicast_subscription* subscription);
// ── Диагностика ──
/* Снимок общих счётчиков ядра в *out. 0=успех, <0=неверный аргумент/модуль не создан.
* Не открывает дескрипторов или подписок, счётчики не сбрасывает. */
int multicast_get_stats(struct UTUN_INSTANCE* inst, struct multicast_stats* out);
// ── Внутренняя интеграция с ядром ──
/* Далее — интеграция с ядром; сервисы используют open/send/close и subscribe/unsubscribe.
* init вызывается ядром один раз после etcp_router_init; 0=успех, <0=ошибка.
* destroy отменяет работу, закрывает дескрипторы, деактивирует подписки; повтор допустим.
* Вызывать до уничтожения router/ETCP и вне callbacks модуля. */
int multicast_init(struct UTUN_INSTANCE* inst);
void multicast_destroy(struct UTUN_INSTANCE* inst);
/* Запланировать пересмотр ожидающих DATA после изменения маршрутов; повторные вызовы объединяются. */
// Запланировать пересмотр ожидающих DATA после изменения маршрутов; повторные вызовы объединяются.
void multicast_routes_changed(struct UTUN_INSTANCE* inst);
/* Перед удалением группы: закрыть её дескрипторы, деактивировать подписки,
* убрать её DATA/отчёты из общих очередей, сохранив работу остальных групп. */
void multicast_close_group(struct UTUN_INSTANCE* inst, uint64_t group_id);
+219
View File
@@ -0,0 +1,219 @@
/* multicast — отправка одного payload явно указанному списку узлов topo-группы.
* Общая часть пути передаётся одним пакетом; при расхождении маршрутов пакет
* разделяется на ветки с соответствующими подмножествами получателей.
*
* Как отправлять:
* 1. multicast_open(group, on_loss, arg) выделяет исходящий дескриптор.
* Номер 1..65535 уникален среди открытых дескрипторов данного ядра.
* Выдача последовательная: 1, 2, ... 65535, затем снова с 1, пропуская
* занятые номера. Счётчик общий для всех групп ядра; close его не откатывает.
* Группа и обработчик потерь закрепляются за ним до закрытия.
* 2. Передать получателям свой node_id и номер дескриптора через протокол
* использующего сервиса. Получатели должны установить подписки до DATA.
* 3. multicast_send(inst, descriptor, recipients, count, payload, len).
* Можно отправлять много пакетов; список адресатов задаётся для каждого.
* Список и payload копируются: после возврата их можно менять/освобождать.
* 4. Перегрузки и локальные ошибки приходят в on_loss этого дескриптора.
* loss->descriptor указывает исходящий дескриптор, loss->recipients —
* только пострадавшую часть списка. Решение о снижении нагрузки принимает сервис.
* 5. multicast_close(inst, descriptor) завершает использование дескриптора.
*
* Как принимать:
* 1. Получить node_id источника и ЕГО исходящий дескриптор от сервиса.
* 2. multicast_subscribe(group, src_node, src_descriptor, on_data, arg).
* Подписка локальная, ключ — (group_id, src_node, src_descriptor).
* Она не посылает JOIN и не добавляет нас в список адресатов у источника.
* Собственный исходящий дескриптор для приёма выделять не требуется.
* 3. on_data вызывается только для пакетов, где мы указаны получателем.
* Без подписки локальная доставка тихо отбрасывается, без лога и отчёта.
* Транзит другим получателям продолжается независимо от наших подписок.
* 4. multicast_unsubscribe(subscription) прекращает приём и освобождает handle.
*
* Минимальный порядок (на разных узлах; on_loss/on_data — callbacks сервиса):
* int fd = multicast_open(send_group, on_loss, sender_ctx);
* if (fd < 0) ...; // дескриптор не создан
* // сервис передаёт source_node_id и (uint16_t)fd получателю
* struct multicast_subscription* sub =
* multicast_subscribe(recv_group, source_node_id, (uint16_t)fd, on_data, receiver_ctx);
* if (!sub) ...; // подписка не создана
* // после готовности подписки, на источнике:
* int rc = multicast_send(send_inst, (uint16_t)fd, receivers, count, data, size);
* if (rc < 0) ...; // этот вызов не принят
* // при завершении, каждый на своём узле:
* multicast_unsubscribe(sub);
* multicast_close(send_inst, (uint16_t)fd);
*
* Условия использования:
* - Все вызовы и callbacks выполняются в uasync-потоке соответствующего ядра.
* - Callbacks отложены: open/subscribe/send/close/unsubscribe их не вызывают.
* В callback можно отправлять, закрывать свой дескриптор и отписывать себя.
* Уничтожать ядро/группу/модуль из callback нельзя.
* - payload, loss и loss->recipients заимствованы до возврата из callback.
* arg принадлежит сервису: он должен жить до close/unsubscribe и завершения
* уже выполняющегося callback. После закрытия новых callbacks не будет.
* - Подпиской владеет вызывающий: unsubscribe обязателен даже после удаления
* группы. Удаление группы/ядра деактивирует подписку, но handle остаётся
* валиден до unsubscribe; после unsubscribe использовать его нельзя.
* - Удаление группы автоматически закрывает её исходящие дескрипторы.
* - Успех send означает принятие к обработке, а не доставку. Возможны частичные
* потери; сквозных ACK/повторов нет, порядок при смене маршрута не гарантируется.
* Сам отчёт тоже может потеряться: отсутствие on_loss не подтверждает доставку.
*
* Локальные callbacks DATA тоже отложены; их общая очередь ограничена 256 КиБ
* памяти. Отказ её выделения/переполнение считается MULTICAST_INTERNAL_ERROR.
* Поколений нет: DATA, отчёты и подписки используют 16-битный номер дескриптора.
* Новое ядро начинает выдачу с 1. После повторной выдачи номера запоздалый
* сетевой пакет с тем же источником и номером неотличим от пакета нового владельца.
*/
#ifndef UTUN_MULTICAST_H
#define UTUN_MULTICAST_H
#include <stddef.h>
#include <stdint.h>
#ifdef __cplusplus
extern "C" {
#endif
struct UTUN_INSTANCE;
struct TOPO_GROUP;
struct multicast_ctx;
struct multicast_subscription; /* непрозрачный handle локальной подписки; освобождает вызывающий */
#define MULTICAST_DESCRIPTOR_INVALID 0
#define MULTICAST_QUEUE_BYTES (256u * 1024u) /* одна FIFO на ETCP-соединение для всех рассылок */
#define MULTICAST_REPORT_BYTES (16u * 1024u) /* общий бюджет накопленных отчётов и окон троттлинга */
#define MULTICAST_REPORT_INTERVAL_TB 3000 /* 300 мс; timebase = 0.1 мс */
/* Размеры служебной оболочки; payload вместе с ней должен помещаться в 16 КиБ ETCP. */
#define MULTICAST_MAX_VISITED 64
#define MULTICAST_HEADER_SIZE 21
#define MULTICAST_PATH_RESERVE (8 * MULTICAST_MAX_VISITED + 1)
#define MULTICAST_REPORT_HEADER_SIZE 41
enum multicast_loss_reason {
MULTICAST_OVERLOAD = 1, /* очередь выбранного исходящего соединения заполнена */
MULTICAST_NO_ROUTE, /* только локальное уведомление источнику */
MULTICAST_PATH_LOOP, /* локальное уведомление; транзит логирует потерю */
MULTICAST_INTERNAL_ERROR /* выделение памяти / отказ транспорта */
};
struct multicast_loss {
uint64_t group_id; /* группа исходной рассылки */
uint64_t source_node_id; /* источник исходного DATA */
uint64_t src, dst; /* узел потери и выбранный next hop; dst=0 без маршрута */
uint16_t descriptor; /* исходящий дескриптор, callback которого получает этот отчёт */
uint16_t recipient_count; /* число ID в recipients */
const uint64_t* recipients; /* заимствованный массив в host byte order */
uint32_t packets; /* потери для каждого получателя этого списка */
enum multicast_loss_reason reason; /* перегрузка либо локальная причина потери */
uint8_t prefix_len; /* 0..16 значимых байт */
uint8_t prefix[16]; /* начало payload первого потерянного пакета */
};
/* Приём для конкретной подписки. len может быть 0; payload нельзя сохранять без копирования.
* subscription можно передать в unsubscribe прямо здесь; после этого handle недействителен.
* src_node/src_descriptor/group известны сервису из subscribe и могут храниться в arg. */
typedef void (*multicast_recv_fn)(struct multicast_subscription* subscription, const uint8_t* payload, size_t len, void* arg);
/* Уведомление конкретному исходящему дескриптору. loss->src/dst — перегруженный
* участок, не пара «исходный отправитель / конечный получатель».
* packets относится к каждому ID в recipients. При разделении отчёта на части
* нельзя складывать packets как число уникальных исходных пакетов.
* Допустим multicast_close(inst, loss->descriptor) из этого callback. */
typedef void (*multicast_loss_fn)(struct UTUN_INSTANCE* inst, const struct multicast_loss* loss, void* arg);
struct multicast_stats {
uint64_t sent; /* ветки DATA, переданные ETCP */
uint64_t delivered; /* вызовы обработчиков входящих подписок */
uint64_t unsubscribed; /* тихие отбрасывания локальной доставки без подписки */
uint64_t overload_packets; /* ветки, отброшенные из-за лимита исходящей FIFO */
uint64_t no_route_packets; /* ветки без доступного маршрута */
uint64_t loop_packets; /* петли и превышения длины пройденного пути */
uint64_t invalid_packets; /* некорректные DATA/отчёты */
uint64_t internal_errors; /* отказы выделения памяти или передачи транспорту */
uint64_t reports_sent; /* переданные части отчётов, включая локальные уведомления */
uint64_t reports_received; /* принятые части отчётов */
uint64_t reports_evicted; /* записи отчётов, вытесненные из бюджета 16 КиБ */
size_t queued_bytes; /* DATA в FIFO модуля, без уже переданного ETCP */
size_t report_bytes; /* память накопленных отчётов и окон, без переданного router */
size_t delivery_bytes; /* память DATA, ожидающих локальные callbacks; максимум 256 КиБ */
unsigned queues; /* лениво созданные FIFO, в том числе временно опустевшие */
unsigned descriptors; /* открытые исходящие дескрипторы */
unsigned subscriptions; /* активные входящие подписки */
};
/* Открыть исходящий дескриптор в работающей group. on_loss обязателен; arg может быть NULL.
* Выдать следующий свободный номер по циклическому порядку 1..65535, начиная после
* последнего выданного; 0 не выдаётся. Закрытые номера используются только при обходе.
* Возврат: 1..65535 — выделенный номер, <0 — ошибка (включая отсутствие свободных номеров).
* До проверки результата не приводить его к uint16_t. Подписку на приём не создаёт. */
int multicast_open(struct TOPO_GROUP* group, multicast_loss_fn on_loss, void* arg);
/* Отправить через открытый descriptor, принадлежащий inst; группа берётся из дескриптора.
* recipients: count>0 ненулевых node_id; повторы удаляются, собственный ID допустим.
* len может быть 0; при len>0 payload обязателен. Размер payload, списка и полного
* резерва пройденного пути должен помещаться в 16 КиБ.
* 0=принято к обработке; <0=вызов не принят, ничего не отправлено.
* После принятия отдельные ветки могут потеряться; уведомления идут в on_loss.
* Обе входные области копируются и после возврата больше не используются. */
int multicast_send(struct UTUN_INSTANCE* inst, uint16_t descriptor, const uint64_t* recipients,
uint16_t count, const void* payload, size_t len);
/* Закрыть исходящий descriptor данного inst: убрать его из поиска, отменить
* ещё не переданные DATA и ожидающие локальные callbacks этого дескриптора.
* Уже переданное транспорту/другим узлам отозвать нельзя. Удалённые подписки
* автоматически не закрываются. Сохранённый номер после close не использовать.
* Для отсутствующего дескриптора — диагностируемая ошибка без побочных действий. */
void multicast_close(struct UTUN_INSTANCE* inst, uint16_t descriptor);
/* Подписаться на локальную доставку (group_id, src_node, src_descriptor).
* src_node!=0, src_descriptor!=0, on_data обязателен; arg может быть NULL.
* Возврат: принадлежащий вызывающему handle либо NULL с диагностикой ошибки.
* Повторная подписка на тот же ключ запрещена; сначала unsubscribe прежней.
* Никаких сетевых запросов не отправляет и пропущенные ранее DATA не воспроизводит. */
struct multicast_subscription* multicast_subscribe(struct TOPO_GROUP* group, uint64_t src_node, uint16_t src_descriptor,
multicast_recv_fn on_data, void* arg);
/* Прекратить доставку, отменить ожидающие callbacks и освободить handle.
* NULL допустим. Работает и для подписки, деактивированной удалением группы/ядра.
* После вызова handle недействителен; повторный вызов с тем же указателем запрещён.
* Из собственного on_data допускается; память освобождается после его завершения. */
void multicast_unsubscribe(struct multicast_subscription* subscription);
/* Снимок общих счётчиков ядра в *out. 0=успех, <0=неверный аргумент/модуль не создан.
* Не открывает дескрипторов или подписок, счётчики не сбрасывает. */
int multicast_get_stats(struct UTUN_INSTANCE* inst, struct multicast_stats* out);
/* Далее — интеграция с ядром; сервисы используют open/send/close и subscribe/unsubscribe.
* init вызывается ядром один раз после etcp_router_init; 0=успех, <0=ошибка.
* destroy отменяет работу, закрывает дескрипторы, деактивирует подписки; повтор допустим.
* Вызывать до уничтожения router/ETCP и вне callbacks модуля. */
int multicast_init(struct UTUN_INSTANCE* inst);
void multicast_destroy(struct UTUN_INSTANCE* inst);
/* Запланировать пересмотр ожидающих DATA после изменения маршрутов; повторные вызовы объединяются. */
void multicast_routes_changed(struct UTUN_INSTANCE* inst);
/* Перед удалением группы: закрыть её дескрипторы, деактивировать подписки,
* убрать её DATA/отчёты из общих очередей, сохранив работу остальных групп. */
void multicast_close_group(struct UTUN_INSTANCE* inst, uint64_t group_id);
/* Wire DATA, все числа little-endian:
* cmd:1 | group:8 | descriptor:2 | count:2 | source:8 | recipients:8*count |
* payload | visited:8*hops | hops:1.
* Получатели строго возрастают. Первый visited=source, последний=ETCP peer.
* Транзит добавляет себя один раз; повторное посещение запрещено.
*
* Wire OVERLOAD — payload отдельного router-сервиса:
* descriptor:2 | src:8 | dst:8 | packets:4 | count:2 | prefix_len:1 |
* prefix:16 | recipients:8*count.
* src/dst — концы перегруженного участка; router dst — исходный источник DATA.
* Разные наборы потерянных получателей не смешиваются. Префикс относится
* к первому потерянному пакету записи; для payload короче 16 байт остаток нулевой.
* Первая потеря сообщается сразу, последующие накапливаются до конца окна 300 мс.
* Таймер отправляет остаток независимо от поступления новых DATA.
*/
#ifdef __cplusplus
}
#endif
#endif
+87 -24
View File
@@ -1,5 +1,11 @@
/* Стриминг CHAT-сервиса: локальные handles владеют общим экземпляром трансляции.
* Все API — uasync-поток. Последний reader close освобождает экземпляр; publisher close после FIN
/* stream — публикация и приём трансляций CHAT-группы с подписью источника и восстановлением потерь через Reed–Solomon.
* Источник: publish_open → publish_write или publish_block → publish_finish → close.
* Приём: subscribe_open для событий байтового потока либо media_open → media_read для аудио/видео; затем close.
* Данные хранятся в общем кольце; у каждого локального читателя свой handle. Все вызовы — uasync-поток.
* Коды результатов — STREAM_* из stream_proto.h; STREAM_OK означает успех, STREAM_WAIT — данные ещё не готовы. */
/* Владение и callbacks:
* Последний reader close освобождает экземпляр; publisher close после FIN
* сохраняет кольцо для локальных readers, без FIN отсоединяет их. Отсоединённый handle также закрывает вызывающий.
* DATA/GAP/END/CLOSED/AVAILABLE callback отложен, data/описание заимствованы до его возврата.
* Close handles из callback допустим; остановка сервиса/группы и destroy ядра — вне callbacks.
@@ -18,7 +24,7 @@ struct UTUN_INSTANCE;
struct STREAM_HANDLE;
struct stream_service;
struct stream_parameters { uint8_t n, r; uint16_t block_size; }; /* block_size — максимум RS-части. */
struct stream_parameters { uint8_t n, r; uint16_t block_size; }; // n — всего RS-частей, r — проверочных; block_size — максимум части.
enum stream_event_type { STREAM_EVENT_DATA, STREAM_EVENT_GAP, STREAM_EVENT_END, STREAM_EVENT_CLOSED, STREAM_EVENT_AVAILABLE };
enum stream_close_reason {
@@ -27,21 +33,21 @@ enum stream_close_reason {
};
struct stream_event {
struct stream_descriptor descriptor;
enum stream_event_type type;
uint64_t first_seq, last_seq;
const uint8_t *data;
size_t length;
int reason;
struct stream_descriptor descriptor; // источник, группа и эпоха трансляции
enum stream_event_type type; // DATA, GAP, END, CLOSED или готовность медиа AVAILABLE
uint64_t first_seq, last_seq; // DATA/GAP: включительный диапазон пачек; END: последняя пачка, UINT64_MAX при пустом потоке
const uint8_t *data; // DATA: восстановленные байты, заимствованы до возврата callback
size_t length; // размер data в байтах
int reason; // CLOSED: stream_close_reason
};
typedef void (*stream_event_fn)(struct UTUN_INSTANCE *inst, const struct stream_event *event, void *arg);
struct stream_info {
struct stream_descriptor descriptor;
struct stream_buffer_stats buffer;
unsigned owners;
int publisher;
int finished;
struct stream_descriptor descriptor; // источник, эпоха и параметры RS
struct stream_buffer_stats buffer; // текущее состояние общего кольца
unsigned owners; // число локальных handles общей сессии
int publisher; // 1 — сессия содержит локальный источник
int finished; // 1 — источник завершён; оставшиеся данные ещё могут читаться
};
struct stream_candidate {
@@ -57,64 +63,121 @@ struct stream_selection_info {
int automatic, protected_by_fec;
};
// ── Жизненный цикл сервиса ──
// Чат-сервис создаёт общий контекст до открытия handles; STREAM_OK или отрицательная ошибка.
int stream_service_init(struct UTUN_INSTANCE *inst);
// Остановить трансляции и отсоединить handles; вызывающий закрывает оставшиеся handles сам. Вне callbacks.
void stream_service_destroy(struct UTUN_INSTANCE *inst);
// Отсоединить трансляции группы при её удалении; handles остаются у владельцев до close.
void stream_cancel_group(struct UTUN_INSTANCE *inst, uint64_t group_id);
// ── События и каталог источников ──
// Единственный общий обработчик событий экземпляра, arg живёт до снятия; callback=NULL отключает доставку.
void stream_set_event_callback(struct UTUN_INSTANCE *inst, stream_event_fn callback, void *arg);
/* Проверенные online-публикации: JSON с id(decimal string), epoch(hex string), name, self.
* Только uasync; *out заранее NULL, результат освобождается u_free(). WAIT — группа недоступна. */
int stream_sources_json(struct UTUN_INSTANCE *inst, uint64_t group_id, char **out);
/* Независимое наблюдение owner: начальный снимок и изменения через CHAT_EVT_STREAM_SOURCES.
* group_id=0 снимает только этого owner; owner=0 разрешён для выбранного канала frontend.
* Недоступный каталог имеет valid=0 и не означает завершение источников.
* Не создаёт подписку/буфер и не удерживает ACTIVE. Только uasync. */
int stream_watch_sources(struct UTUN_INSTANCE *inst, uint64_t group_id, uint64_t owner);
/* Локальный owner удерживает эффективный ACTIVE; желаемый frontend-режим ядро восстанавливает после последнего close. */
// ── Согласование с активностью ядра и рацией ──
// Локальный owner удерживает эффективный ACTIVE; желаемый frontend-режим ядро восстанавливает после последнего close.
int stream_service_needs_activity(const struct UTUN_INSTANCE *inst);
/* Только локальный viewer этой группы/эпохи подавляет рацию источника со включённым звуком. */
// Только локальный viewer этой группы/эпохи подавляет рацию источника со включённым звуком.
int stream_radio_muted(struct UTUN_INSTANCE *inst, uint64_t group_id, uint64_t source_id);
/* out заранее NULL. parameters=NULL — 6+2, часть до 1300 байт и одна подпись пачки внутри RS.
* Одна публикация этого узла в группе; новый запуск получает новый source_epoch. */
// ── Открытие источника и подписок ──
/* Открыть источник; *out заранее NULL. parameters=NULL использует STREAM_DEFAULT_N/R: 8 частей без избыточности.
* Максимальный размер части задаёт STREAM_RS_DEFAULT_SIZE; одна подпись защищает всю пачку внутри RS.
* Одна публикация этого узла в группе; новый запуск получает новый source_epoch. При успехе handle закрывает вызывающий. */
int stream_publish_open(struct UTUN_INSTANCE *inst, uint64_t group_id, const struct stream_parameters *parameters,
struct STREAM_HANDLE **out);
/* Меняет подписанное состояние звука, не формат медиатреков. Только uasync; raw publisher начинает с 0. */
// Меняет подписанное состояние звука, не формат медиатреков. Только uasync; raw publisher начинает с 0.
int stream_publish_set_audio_enabled(struct STREAM_HANDLE *handle, int enabled);
/* Параметры берутся из подписанного профиля источника; дубликаты локальных владельцев делят один буфер.
* WAIT — источник ещё не объявил/не подтвердил трансляцию. Offline не создаёт ложного онлайн-кандидата. */
int stream_subscribe_open(struct UTUN_INSTANCE *inst, uint64_t group_id, uint64_t source_id, struct STREAM_HANDLE **out);
/* Ручные выдачи для диагностики/явного управления; режим общий для всех owners экземпляра и не смешивается с automatic. */
// Ручные выдачи для диагностики/явного управления; режим общий для всех owners экземпляра и не смешивается с automatic.
int stream_subscribe_manual_open(struct UTUN_INSTANCE *inst, uint64_t group_id, uint64_t source_id, struct STREAM_HANDLE **out);
/* По умолчанию: подтверждение улучшения 2000 мс, удержание 5000 мс, гистерезис интегральной оценки 20 мс. */
// ── Выбор поставщиков и состояние handles ──
// По умолчанию: подтверждение улучшения 2000 мс, удержание 5000 мс, гистерезис интегральной оценки 20 мс.
int stream_selection_configure(struct STREAM_HANDLE *handle, const struct stream_selection_settings *settings);
// Скопировать статистику выбора; для сессии без автоматического планировщика — нули. STREAM_OK/STREAM_ERR_STATE.
int stream_selection_get_info(const struct STREAM_HANDLE *handle, struct stream_selection_info *info);
// Освободить свой handle, в том числе после CLOSED/удаления группы; NULL допустим. После close указатель недействителен.
void stream_close(struct STREAM_HANDLE *handle);
// 1 — handle ещё связан с рабочей сессией; 0 — NULL или отсоединён. Не проверяет наличие свежих данных.
int stream_handle_active(const struct STREAM_HANDLE *handle);
// Причина отсоединения из stream_close_reason; для NULL — STREAM_CLOSE_STOPPED.
int stream_handle_reason(const struct STREAM_HANDLE *handle);
// Скопировать состояние общей сессии и кольца; STREAM_OK или STREAM_ERR_STATE.
int stream_get_info(const struct STREAM_HANDLE *handle, struct stream_info *info);
// ── Чтение аудио/видео ──
/* Медиаплеер использует тот же RS-buffer, но собственные позиции чтения.
* tracks=0 включает только общий индекс; VIDEO или VIDEO|AUDIO создаёт cursor этого handle.
* Все вызовы — uasync. Заимствованный packet действует до следующего read/close.
* LIVE DATA/GAP/END callbacks описывают сетевой прогресс; конец отложенного просмотра получает media_read. */
int stream_media_enable(struct STREAM_HANDLE *handle, unsigned tracks);
/* Атомарно открыть viewer выбранной эпохи с собственным cursor. CONFLICT означает смену источника.
* tracks=VIDEO или VIDEO|AUDIO; out заранее NULL, при отказе остаётся NULL. Закрытие через stream_close. */
int stream_media_open(struct UTUN_INSTANCE *inst, uint64_t group_id, uint64_t source_id, uint64_t epoch,
unsigned tracks, struct STREAM_HANDLE **out);
/* Один следующий видеокадр при want_video=1; звук ограничен position_us+audio_ahead_us. */
/* Прочитать ближайшую медиазапись: want_video=1 разрешает видео, звук ограничен position_us+audio_ahead_us.
* STREAM_OK — packet заполнен, его kind также сообщает конец трека/потока; WAIT/MORE — повторить позже.
* GAP/EXPIRED требуют новой точки KEY через resync; отрицательный результат — ошибка. Буфер packet заимствован. */
int stream_media_read(struct STREAM_HANDLE *handle, int64_t position_us, int want_video, int64_t audio_ahead_us,
struct stream_media_packet *packet);
// Снимок курсора и доступных медиаданных; STREAM_OK или отрицательная ошибка.
int stream_media_get_status(struct STREAM_HANDLE *handle, struct stream_media_status *status);
// Сбросить только свой медиакурсор для поиска новой точки KEY; STREAM_OK или STREAM_ERR_STATE.
int stream_media_resync(struct STREAM_HANDLE *handle);
// ── Публикация байтового потока ──
// Максимальная порция одного write/block в байтах; 0 — неактивный handle или не источник.
size_t stream_publish_capacity(const struct STREAM_HANDLE *handle);
/* Скопировать length <= stream_publish_capacity(handle) байт в источник; accepted обязателен даже при ошибке.
* Входной буфер остаётся у вызывающего. STREAM_OK — принято; отрицательный результат — ошибка. */
int stream_publish_write(struct STREAM_HANDLE *handle, const uint8_t *data, size_t length, size_t *accepted);
/* Публикует одну непустую FEC-пачку, без склейки/таймера. Не смешивать с publish_write в одном запуске. */
/* Скопировать одну непустую порцию размером <= capacity как отдельную FEC-пачку; STREAM_OK или отрицательная ошибка.
* Входной буфер остаётся у вызывающего. Не смешивать с publish_write в одном запуске. */
int stream_publish_block(struct STREAM_HANDLE *handle, const uint8_t *data, size_t length);
/* Сбросить остаток, снять статус публикации; DATA перед END. Буфер живёт до close. */
// Сбросить остаток, снять статус публикации; DATA перед END. Буфер живёт до close.
int stream_publish_finish(struct STREAM_HANDLE *handle);
// ── Кандидаты для сетевых выдач ──
/* Только подходящие online-подписчики этой группы/эпохи; мобильные relay исключены, источник допускается.
* out заранее NULL; массив освобождается u_free(). Наличие кандидата не гарантирует прямой транспорт. */
int stream_get_candidates(const struct STREAM_HANDLE *handle, struct stream_candidate **out, size_t *count);
+125
View File
@@ -0,0 +1,125 @@
/* Стриминг CHAT-сервиса: локальные handles владеют общим экземпляром трансляции.
* Все API — uasync-поток. Последний reader close освобождает экземпляр; publisher close после FIN
* сохраняет кольцо для локальных readers, без FIN отсоединяет их. Отсоединённый handle также закрывает вызывающий.
* DATA/GAP/END/CLOSED/AVAILABLE callback отложен, data/описание заимствованы до его возврата.
* Close handles из callback допустим; остановка сервиса/группы и destroy ядра — вне callbacks.
* Источник и локальные подписки публикуются атомарно через опции собственного мембера. */
#ifndef STREAM_H
#define STREAM_H
#include "stream_buffer.h"
#include "stream_media_buffer.h"
#ifdef __cplusplus
extern "C" {
#endif
struct UTUN_INSTANCE;
struct STREAM_HANDLE;
struct stream_service;
struct stream_parameters { uint8_t n, r; uint16_t block_size; }; /* block_size — максимум RS-части. */
enum stream_event_type { STREAM_EVENT_DATA, STREAM_EVENT_GAP, STREAM_EVENT_END, STREAM_EVENT_CLOSED, STREAM_EVENT_AVAILABLE };
enum stream_close_reason {
STREAM_CLOSE_STOPPED, STREAM_CLOSE_GROUP, STREAM_CLOSE_SERVICE, STREAM_CLOSE_SOURCE_CHANGED,
STREAM_CLOSE_MEMBER, STREAM_CLOSE_FAILED
};
struct stream_event {
struct stream_descriptor descriptor;
enum stream_event_type type;
uint64_t first_seq, last_seq;
const uint8_t *data;
size_t length;
int reason;
};
typedef void (*stream_event_fn)(struct UTUN_INSTANCE *inst, const struct stream_event *event, void *arg);
struct stream_info {
struct stream_descriptor descriptor;
struct stream_buffer_stats buffer;
unsigned owners;
int publisher;
int finished;
};
struct stream_candidate {
uint64_t node_id;
uint8_t client_type;
uint8_t source;
};
struct stream_selection_settings { uint32_t migration_confirm_ms, minimum_hold_ms, score_hysteresis_ms; };
struct stream_selection_info {
uint64_t probes_started, cycles, migrations, failures, restarts;
unsigned candidates, probes_active, subflows, overlaps, providers;
int automatic, protected_by_fec;
};
int stream_service_init(struct UTUN_INSTANCE *inst);
void stream_service_destroy(struct UTUN_INSTANCE *inst);
void stream_cancel_group(struct UTUN_INSTANCE *inst, uint64_t group_id);
void stream_set_event_callback(struct UTUN_INSTANCE *inst, stream_event_fn callback, void *arg);
/* Проверенные online-публикации: JSON с id(decimal string), epoch(hex string), name, self.
* Только uasync; *out заранее NULL, результат освобождается u_free(). WAIT — группа недоступна. */
int stream_sources_json(struct UTUN_INSTANCE *inst, uint64_t group_id, char **out);
/* Независимое наблюдение owner: начальный снимок и изменения через CHAT_EVT_STREAM_SOURCES.
* group_id=0 снимает только этого owner; owner=0 разрешён для выбранного канала frontend.
* Недоступный каталог имеет valid=0 и не означает завершение источников.
* Не создаёт подписку/буфер и не удерживает ACTIVE. Только uasync. */
int stream_watch_sources(struct UTUN_INSTANCE *inst, uint64_t group_id, uint64_t owner);
/* Локальный owner удерживает эффективный ACTIVE; желаемый frontend-режим ядро восстанавливает после последнего close. */
int stream_service_needs_activity(const struct UTUN_INSTANCE *inst);
/* Только локальный viewer этой группы/эпохи подавляет рацию источника со включённым звуком. */
int stream_radio_muted(struct UTUN_INSTANCE *inst, uint64_t group_id, uint64_t source_id);
/* out заранее NULL. parameters=NULL — 6+2, часть до 1300 байт и одна подпись пачки внутри RS.
* Одна публикация этого узла в группе; новый запуск получает новый source_epoch. */
int stream_publish_open(struct UTUN_INSTANCE *inst, uint64_t group_id, const struct stream_parameters *parameters,
struct STREAM_HANDLE **out);
/* Меняет подписанное состояние звука, не формат медиатреков. Только uasync; raw publisher начинает с 0. */
int stream_publish_set_audio_enabled(struct STREAM_HANDLE *handle, int enabled);
/* Параметры берутся из подписанного профиля источника; дубликаты локальных владельцев делят один буфер.
* WAIT — источник ещё не объявил/не подтвердил трансляцию. Offline не создаёт ложного онлайн-кандидата. */
int stream_subscribe_open(struct UTUN_INSTANCE *inst, uint64_t group_id, uint64_t source_id, struct STREAM_HANDLE **out);
/* Ручные выдачи для диагностики/явного управления; режим общий для всех owners экземпляра и не смешивается с automatic. */
int stream_subscribe_manual_open(struct UTUN_INSTANCE *inst, uint64_t group_id, uint64_t source_id, struct STREAM_HANDLE **out);
/* По умолчанию: подтверждение улучшения 2000 мс, удержание 5000 мс, гистерезис интегральной оценки 20 мс. */
int stream_selection_configure(struct STREAM_HANDLE *handle, const struct stream_selection_settings *settings);
int stream_selection_get_info(const struct STREAM_HANDLE *handle, struct stream_selection_info *info);
void stream_close(struct STREAM_HANDLE *handle);
int stream_handle_active(const struct STREAM_HANDLE *handle);
int stream_handle_reason(const struct STREAM_HANDLE *handle);
int stream_get_info(const struct STREAM_HANDLE *handle, struct stream_info *info);
/* Медиаплеер использует тот же RS-buffer, но собственные позиции чтения.
* tracks=0 включает только общий индекс; VIDEO или VIDEO|AUDIO создаёт cursor этого handle.
* Все вызовы — uasync. Заимствованный packet действует до следующего read/close.
* LIVE DATA/GAP/END callbacks описывают сетевой прогресс; конец отложенного просмотра получает media_read. */
int stream_media_enable(struct STREAM_HANDLE *handle, unsigned tracks);
/* Атомарно открыть viewer выбранной эпохи с собственным cursor. CONFLICT означает смену источника.
* tracks=VIDEO или VIDEO|AUDIO; out заранее NULL, при отказе остаётся NULL. Закрытие через stream_close. */
int stream_media_open(struct UTUN_INSTANCE *inst, uint64_t group_id, uint64_t source_id, uint64_t epoch,
unsigned tracks, struct STREAM_HANDLE **out);
/* Один следующий видеокадр при want_video=1; звук ограничен position_us+audio_ahead_us. */
int stream_media_read(struct STREAM_HANDLE *handle, int64_t position_us, int want_video, int64_t audio_ahead_us,
struct stream_media_packet *packet);
int stream_media_get_status(struct STREAM_HANDLE *handle, struct stream_media_status *status);
int stream_media_resync(struct STREAM_HANDLE *handle);
size_t stream_publish_capacity(const struct STREAM_HANDLE *handle);
int stream_publish_write(struct STREAM_HANDLE *handle, const uint8_t *data, size_t length, size_t *accepted);
/* Публикует одну непустую FEC-пачку, без склейки/таймера. Не смешивать с publish_write в одном запуске. */
int stream_publish_block(struct STREAM_HANDLE *handle, const uint8_t *data, size_t length);
/* Сбросить остаток, снять статус публикации; DATA перед END. Буфер живёт до close. */
int stream_publish_finish(struct STREAM_HANDLE *handle);
/* Только подходящие online-подписчики этой группы/эпохи; мобильные relay исключены, источник допускается.
* out заранее NULL; массив освобождается u_free(). Наличие кандидата не гарантирует прямой транспорт. */
int stream_get_candidates(const struct STREAM_HANDLE *handle, struct stream_candidate **out, size_t *count);
#ifdef __cplusplus
}
#endif
#endif
+29 -5
View File
@@ -1,5 +1,7 @@
/* Один планировщик на локальный экземпляр; handles владельцев не размножают пробы/выдачи.
* tick вызывается отложенным stream network проходом и таймером, destroy отменяет все свои receive/probe handles. */
/* stream_optimizer — автоматический выбор узлов, от которых получать части трансляции.
* Проверяет поставщиков, сравнивает задержку и переносит выдачи на лучший доступный путь; при необходимости просит relay.
* Внутренний модуль stream: create для одной трансляции → tick при сетевом событии/по таймеру → destroy.
* Все вызовы — uasync. Планировщик общий для локальных читателей; destroy отменяет принадлежащие ему пробы и выдачи. */
#ifndef STREAM_OPTIMIZER_H
#define STREAM_OPTIMIZER_H
@@ -7,20 +9,42 @@
struct stream_optimizer;
struct stream_receive_info;
/* Меньшая оценка лучше: chain RTT + 2 * peer RTT + 3 мс/hop + штраф 0..1000 мс; неизвестный RTT = UINT64_MAX. */
// ── Оценка поставщиков и расписание проб ──
// Меньшая оценка лучше: chain RTT + 2 * peer RTT + 3 мс/hop + штраф 0..1000 мс; неизвестный RTT = UINT64_MAX.
uint64_t stream_supplier_score(const struct stream_receive_info *info, const struct stream_metric_snapshot *metrics, int valid);
/* Только монотонное расписание; pending/active — незавершённый обход/пробы текущего цикла.
* Возвращает допустимое число новых узлов, не адресов/субпотоков: 32 один раз, затем 0/1. */
struct stream_probe_schedule { uint64_t cycle, started_ms, next_ms; };
unsigned stream_probe_schedule_tick(struct stream_probe_schedule *schedule, uint64_t now_ms, unsigned pending, unsigned active);
// ── Жизненный цикл планировщика ──
// Скопировать descriptor и создать планировщик; *out заранее NULL. STREAM_OK или отрицательная ошибка.
int stream_optimizer_create(struct UTUN_INSTANCE *inst, const struct stream_descriptor *descriptor, struct stream_optimizer **out);
// Остановить пробы/выдачи и освободить планировщик; NULL допустим, instance ещё должен быть жив.
void stream_optimizer_destroy(struct stream_optimizer *optimizer);
// ── Выбор поставщиков и настройки ──
// Выполнить очередной проход выбора для живой подписки; now_ms — монотонное время в миллисекундах.
void stream_optimizer_tick(struct stream_optimizer *optimizer, struct STREAM_HANDLE *subscription, uint64_t now_ms);
// Скопировать пороги подтверждения, удержания и гистерезиса в мс (каждый <=60000); STREAM_OK/STREAM_ERR_ARGUMENT.
int stream_optimizer_configure(struct stream_optimizer *optimizer, const struct stream_selection_settings *settings);
// Скопировать статистику; optimizer и info обязательны.
void stream_optimizer_info(struct stream_optimizer *optimizer, struct stream_selection_info *info);
/* Merkle/BGP помечает список поставщиков устаревшим; пробы/цикл/backoff сохраняются до отложенной сверки. */
// ── Уведомления от Merkle/BGP и relay ──
// Merkle/BGP помечает список поставщиков устаревшим; пробы/цикл/backoff сохраняются до отложенной сверки.
void stream_optimizer_roster_changed(struct stream_optimizer *optimizer, uint64_t peer);
/* Подтверждённая помощь немедленно обновляет кандидатов и снимает старый backoff конкретного пира. */
// Подтверждённая помощь немедленно обновляет кандидатов и снимает старый backoff конкретного пира.
void stream_optimizer_refresh(struct stream_optimizer *optimizer, uint64_t peer);
#endif
+26
View File
@@ -0,0 +1,26 @@
/* Один планировщик на локальный экземпляр; handles владельцев не размножают пробы/выдачи.
* tick вызывается отложенным stream network проходом и таймером, destroy отменяет все свои receive/probe handles. */
#ifndef STREAM_OPTIMIZER_H
#define STREAM_OPTIMIZER_H
#include "stream.h"
struct stream_optimizer;
struct stream_receive_info;
/* Меньшая оценка лучше: chain RTT + 2 * peer RTT + 3 мс/hop + штраф 0..1000 мс; неизвестный RTT = UINT64_MAX. */
uint64_t stream_supplier_score(const struct stream_receive_info *info, const struct stream_metric_snapshot *metrics, int valid);
/* Только монотонное расписание; pending/active — незавершённый обход/пробы текущего цикла.
* Возвращает допустимое число новых узлов, не адресов/субпотоков: 32 один раз, затем 0/1. */
struct stream_probe_schedule { uint64_t cycle, started_ms, next_ms; };
unsigned stream_probe_schedule_tick(struct stream_probe_schedule *schedule, uint64_t now_ms, unsigned pending, unsigned active);
int stream_optimizer_create(struct UTUN_INSTANCE *inst, const struct stream_descriptor *descriptor, struct stream_optimizer **out);
void stream_optimizer_destroy(struct stream_optimizer *optimizer);
void stream_optimizer_tick(struct stream_optimizer *optimizer, struct STREAM_HANDLE *subscription, uint64_t now_ms);
int stream_optimizer_configure(struct stream_optimizer *optimizer, const struct stream_selection_settings *settings);
void stream_optimizer_info(struct stream_optimizer *optimizer, struct stream_selection_info *info);
/* Merkle/BGP помечает список поставщиков устаревшим; пробы/цикл/backoff сохраняются до отложенной сверки. */
void stream_optimizer_roster_changed(struct stream_optimizer *optimizer, uint64_t peer);
/* Подтверждённая помощь немедленно обновляет кандидатов и снимает старый backoff конкретного пира. */
void stream_optimizer_refresh(struct stream_optimizer *optimizer, uint64_t peer);
#endif
+16 -6
View File
@@ -1,11 +1,13 @@
/* Общая адаптация воспроизведения desktop/Android, без сетевых ресурсов и очередей медиа.
* Один поток либо внешняя блокировка. position_us — фактические общие часы исходного материала;
* ready_us — непрерывная готовая граница, включая данные в кольце/кодеке/устройстве.
/* stream_playback_clock — выбор темпа воспроизведения для плавного сокращения задержки live-трансляции.
* Reset начинает новую шкалу; update получает проигранную позицию и границу готовых данных, возвращает темп и запас.
* Временная нехватка сохраняет историю; новая эпоха, GAP или перемотка требуют reset.
* Общий модуль desktop/Android; не владеет сетью или PCM. Один поток либо внешняя блокировка. */
/* Правила адаптации:
* Минимумы короткого окна нормализованы по смещению wall-clock относительно media-clock:
* простой увеличивает все прежние запасы, догон уменьшает их без переписывания истории.
* После 200 мс без фактической недостачи избыток погашается плавным ускорением до 1.6.
* guard_us и уже поставленный устройству pending_gain_us защищают остаток при снижении темпа.
* reset нужен при новой эпохе/реальном GAP/смене позиции; временная нехватка историю сохраняет. */
* guard_us и уже поставленный устройству pending_gain_us защищают остаток при снижении темпа. */
#ifndef STREAM_PLAYBACK_CLOCK_H
#define STREAM_PLAYBACK_CLOCK_H
@@ -35,8 +37,16 @@ struct stream_playback_state {
int tempo_milli, waiting, history_ready;
};
// ── Управление временной шкалой ──
// Обнулить историю и вернуть темп 1x; id — идентификатор плеера для диагностики.
void stream_playback_clock_reset(struct stream_playback_clock *clock, uint64_t id);
/* Результат не меняется при ошибке. starved отмечает фактическую остановку подготовки/вывода.
/* now_us — монотонное время, position_us — фактически проигранная позиция исходного материала;
* ready_us — непрерывная готовая граница, включая кольцо, кодек и устройство. Все времена — микросекунды.
* guard_us — неприкосновенный запас; pending_gain_us — ещё не проигранное сокращение длительности PCM в устройстве.
* starved отмечает фактическую остановку подготовки/вывода; state получает темп (tempo_milli: 1000=1x) и запас.
* Результат не меняется при ошибке.
* Временное уменьшение фактического position_us запрещено; после перемотки вызывают reset.
* Возврат 0 — успех, отрицательный — неверные аргументы/часы. */
int stream_playback_clock_update(struct stream_playback_clock *clock, int64_t now_us, int64_t position_us,
+49
View File
@@ -0,0 +1,49 @@
/* Общая адаптация воспроизведения desktop/Android, без сетевых ресурсов и очередей медиа.
* Один поток либо внешняя блокировка. position_us — фактические общие часы исходного материала;
* ready_us — непрерывная готовая граница, включая данные в кольце/кодеке/устройстве.
* Минимумы короткого окна нормализованы по смещению wall-clock относительно media-clock:
* простой увеличивает все прежние запасы, догон уменьшает их без переписывания истории.
* После 200 мс без фактической недостачи избыток погашается плавным ускорением до 1.6.
* guard_us и уже поставленный устройству pending_gain_us защищают остаток при снижении темпа.
* reset нужен при новой эпохе/реальном GAP/смене позиции; временная нехватка историю сохраняет. */
#ifndef STREAM_PLAYBACK_CLOCK_H
#define STREAM_PLAYBACK_CLOCK_H
#include <stdint.h>
#ifdef __cplusplus
extern "C" {
#endif
#define STREAM_PLAYBACK_HISTORY 4
#define STREAM_PLAYBACK_BUCKET_US 50000
#define STREAM_PLAYBACK_SETTLE_US 200000
#define STREAM_PLAYBACK_MAX_TEMPO 1600
#define STREAM_PLAYBACK_RAMP_US 500000
struct stream_playback_bucket { int64_t period, minimum_us; };
struct stream_playback_clock {
struct stream_playback_bucket history[STREAM_PLAYBACK_HISTORY];
uint64_t id, pauses;
int64_t first_us, last_us, last_position_us, correction_us, last_bad_us, pause_us, report_us;
int64_t ramp_remainder;
int tempo_milli, initialized, waiting;
};
struct stream_playback_state {
int64_t position_us, reserve_us, minimum_us, correction_us, stable_us, removable_us;
int tempo_milli, waiting, history_ready;
};
void stream_playback_clock_reset(struct stream_playback_clock *clock, uint64_t id);
/* Результат не меняется при ошибке. starved отмечает фактическую остановку подготовки/вывода.
* Временное уменьшение фактического position_us запрещено; после перемотки вызывают reset.
* Возврат 0 — успех, отрицательный — неверные аргументы/часы. */
int stream_playback_clock_update(struct stream_playback_clock *clock, int64_t now_us, int64_t position_us,
int64_t ready_us, int64_t guard_us, int64_t pending_gain_us,
int starved, struct stream_playback_state *state);
#ifdef __cplusplus
}
#endif
#endif
+9 -32
View File
@@ -1,3 +1,9 @@
/* auto_socket — создание UDP/TCP-сокетов и линков при появлении сетевых интерфейсов.
* При auto_sockets=yes init сканирует интерфейсы и включает мониторинг Linux/BSD/Windows;
* адреса и набор сокетов затем обновляются автоматически. Порты сохраняются в SQLite для повторного использования.
* Destroy отключает мониторинг и удаляет созданные ресурсы. В режиме android сокеты приходят из конфигурации frontend.
* Основной API — uasync-поток. Из другого потока вызывать только on_network_change, пока instance жив.
* Add/remove_interface предназначены для платформенного мониторинга; смена сети сама не создаёт членство в группе. */
#ifndef AUTO_SOCKET_H
#define AUTO_SOCKET_H
@@ -5,35 +11,6 @@
extern "C" {
#endif
/*
* auto_socket.h — автоматическое управление сокетами при изменении сетевых интерфейсов
*
* === Зачем нужен модуль ===
* При auto_sockets=yes не нужно вручную прописывать секции [server] в конфиге.
* Модуль сам создаёт UDP+TCP сокеты на всех не-loopback интерфейсах с IP-адресами,
* и сразу добавляет линки ко всем активным ETCP-соединениям. При пропадании
* интерфейса линки удаляются, сокеты закрываются.
*
* === Что делает ===
* - Для каждого интерфейса (UP, с v4/v6 адресами) создаёт пару сокетов:
* UDP (случайный порт) + TCP (stcp_server на случайном порту)
* - Тип сокета: PUBLIC если на интерфейсе есть глобальный адрес, иначе NAT
* - Порт сохраняется в SQLite для переиспользования при перезапуске
* - Для каждого нового сокета: etcp_link_new ко всем соединениям (inst->connections),
* случайно выбирая один совместимый адрес пира (отдельно для v4 и v6)
* - При изменении адреса на интерфейсе — обновляет interface_addr и оповещает
* подписчиков (ETCP_SOCKET_EVENT_ADDR_CHANGED)
* - При смене типа (NAT↔PUBLIC) — пересоздаёт сокет с новым типом
* - При пропадании интерфейса — удаляет линки, закрывает сокет, чистит БД
*
* === Слои ===
* 1. Platform-agnostic API: auto_socket_init / auto_socket_destroy
* auto_socket_add_interface / auto_socket_remove_interface
* (можно вызывать из любого платформенного мониторинга: netlink, JNI и т.д.)
* 2. Platform monitoring: встроенные обработчики netlink (Linux),
* route socket (BSD), IP Helper (Windows) — работают автоматически
*/
#include <stdint.h>
struct UTUN_INSTANCE;
@@ -45,14 +22,14 @@ struct utun_config;
* handle'ы для мониторинга (netlink fd, route socket fd, IP Helper handles). */
struct AUTO_SOCKET;
/* ─── Жизненный цикл ─── */
// ─── Жизненный цикл ───
/* Инициализирует модуль: создаёт таблицу портов в БД, запускает платформенный
* мониторинг интерфейсов, сканирует все текущие UP-интерфейсы и создаёт сокеты.
* Если auto_sockets=no в конфиге — ничего не делает, возвращает 0. */
int auto_socket_init(struct UTUN_INSTANCE* inst);
/* Останавливает мониторинг, удаляет все созданные сокеты и линки, освобождает память. */
// Останавливает мониторинг, удаляет все созданные сокеты и линки, освобождает память.
void auto_socket_destroy(struct UTUN_INSTANCE* inst);
/* Вызвать при изменении сетевых интерфейсов из любого потока (JNI, ConnectivityManager).
@@ -64,7 +41,7 @@ void auto_socket_on_network_change(struct UTUN_INSTANCE* inst);
* inst->config, остальное освобождается. Должен вызываться в uasync-потоке. */
void auto_socket_reload_config(struct UTUN_INSTANCE* inst, struct utun_config* new_cfg);
/* ─── Platform-agnostic API: управление сокетами на конкретном интерфейсе ─── */
// ─── Внутренний API платформенного мониторинга ───
/*
* Создать UDP+TCP сокеты для интерфейса и добавить линки ко всем активным соединениям.
+95
View File
@@ -0,0 +1,95 @@
#ifndef AUTO_SOCKET_H
#define AUTO_SOCKET_H
#ifdef __cplusplus
extern "C" {
#endif
/*
* auto_socket.h — автоматическое управление сокетами при изменении сетевых интерфейсов
*
* === Зачем нужен модуль ===
* При auto_sockets=yes не нужно вручную прописывать секции [server] в конфиге.
* Модуль сам создаёт UDP+TCP сокеты на всех не-loopback интерфейсах с IP-адресами,
* и сразу добавляет линки ко всем активным ETCP-соединениям. При пропадании
* интерфейса линки удаляются, сокеты закрываются.
*
* === Что делает ===
* - Для каждого интерфейса (UP, с v4/v6 адресами) создаёт пару сокетов:
* UDP (случайный порт) + TCP (stcp_server на случайном порту)
* - Тип сокета: PUBLIC если на интерфейсе есть глобальный адрес, иначе NAT
* - Порт сохраняется в SQLite для переиспользования при перезапуске
* - Для каждого нового сокета: etcp_link_new ко всем соединениям (inst->connections),
* случайно выбирая один совместимый адрес пира (отдельно для v4 и v6)
* - При изменении адреса на интерфейсе — обновляет interface_addr и оповещает
* подписчиков (ETCP_SOCKET_EVENT_ADDR_CHANGED)
* - При смене типа (NAT↔PUBLIC) — пересоздаёт сокет с новым типом
* - При пропадании интерфейса — удаляет линки, закрывает сокет, чистит БД
*
* === Слои ===
* 1. Platform-agnostic API: auto_socket_init / auto_socket_destroy
* auto_socket_add_interface / auto_socket_remove_interface
* (можно вызывать из любого платформенного мониторинга: netlink, JNI и т.д.)
* 2. Platform monitoring: встроенные обработчики netlink (Linux),
* route socket (BSD), IP Helper (Windows) — работают автоматически
*/
#include <stdint.h>
struct UTUN_INSTANCE;
struct ETCP_SOCKET;
struct utun_config;
/* Внутреннее состояние модуля — хранится в inst->auto_socket_state.
* Содержит связный список отслеживаемых интерфейсов (ifaces) и платформенные
* handle'ы для мониторинга (netlink fd, route socket fd, IP Helper handles). */
struct AUTO_SOCKET;
/* ─── Жизненный цикл ─── */
/* Инициализирует модуль: создаёт таблицу портов в БД, запускает платформенный
* мониторинг интерфейсов, сканирует все текущие UP-интерфейсы и создаёт сокеты.
* Если auto_sockets=no в конфиге — ничего не делает, возвращает 0. */
int auto_socket_init(struct UTUN_INSTANCE* inst);
/* Останавливает мониторинг, удаляет все созданные сокеты и линки, освобождает память. */
void auto_socket_destroy(struct UTUN_INSTANCE* inst);
/* Вызвать при изменении сетевых интерфейсов из любого потока (JNI, ConnectivityManager).
* Откладывает полное сканирование (auto_socket_scan_all) в uasync-поток. */
void auto_socket_on_network_change(struct UTUN_INSTANCE* inst);
/* Android-режим (auto_sockets=android): обновить сокеты из [server] секций нового
* конфига. new_cfg — уже распарсенный конфиг; его список servers переносится в
* inst->config, остальное освобождается. Должен вызываться в uasync-потоке. */
void auto_socket_reload_config(struct UTUN_INSTANCE* inst, struct utun_config* new_cfg);
/* ─── Platform-agnostic API: управление сокетами на конкретном интерфейсе ─── */
/*
* Создать UDP+TCP сокеты для интерфейса и добавить линки ко всем активным соединениям.
* ifindex — индекс интерфейса (0 = автоопределение по ifname)
* ifname — имя интерфейса для SO_BINDTODEVICE
* has_v4 — есть ли IPv4 адреса на интерфейсе
* has_v6 — есть ли IPv6 адреса на интерфейсе
* v4_type — CFG_SERVER_TYPE_PUBLIC или CFG_SERVER_TYPE_NAT
* v6_type — CFG_SERVER_TYPE_PUBLIC или CFG_SERVER_TYPE_NAT
* Возвращает количество созданных сокетов (0, 1, 2, 3 или 4 — до 4: v4 UDP+TCP, v6 UDP+TCP).
* Если сокеты для этого интерфейса уже есть — ничего не делает, возвращает 0.
*/
int auto_socket_add_interface(struct UTUN_INSTANCE* inst, uint32_t ifindex,
const char* ifname, int has_v4, int has_v6,
uint8_t v4_type, uint8_t v6_type);
/*
* Удалить все сокеты и линки для интерфейса.
* Закрывает UDP/TCP сокеты, удаляет etcp-линки через ncd_remove_socket_links,
* чистит запись порта в БД.
* ifindex — индекс интерфейса, сокеты которого удаляем
*/
void auto_socket_remove_interface(struct UTUN_INSTANCE* inst, uint32_t ifindex);
#ifdef __cplusplus
}
#endif
#endif
+49 -18
View File
@@ -1,3 +1,9 @@
/* etcp_connections — сокеты и физические линки прямого ETCP-соединения с пиром.
* Принимает/отправляет пакеты, выполняет INIT, PING и подключает UDP/STCP-линки к общему ETCP_CONN.
* Ядро создаёт сокеты через init_sockets/init_connections; NCD управляет линками к конкретным узлам.
* Сервисы открывают транспорт через node_conn_direct.h, обмениваются данными через etcp_api.h или etcp_router.h.
* Этот заголовок содержит внутренние структуры и API стека. Все операции — uasync-поток instance.
* Сокет обслуживает несколько пиров; линк соединяет один сокет с одним адресом пира. */
#ifndef ETCP_CONNECTIONS_H
#define ETCP_CONNECTIONS_H
@@ -6,8 +12,6 @@ extern "C" {
#endif
// подмодуль ETCP который обслуживает сокеты ETCP для приёма-передачи пакетов и одно ETCP подключение через несколько каналов связи (failover)
#include "secure_channel.h"
#include "etcp_session.h"
#include "reality.h"
@@ -52,7 +56,7 @@ extern "C" {
#define ETCP_PING_FLAG_ADDRESS_CHECK 0x08 // прямое входящее испытание endpoint
#define ETCP_PING_FLAG_NO_REPLY 0x10 // NAT-проба: результат через router, без автоматического UDP-ответа
/* Адаптивный keepalive */
// Адаптивный keepalive
#define KA_PERIOD_MIN_MS 200
#define KA_PERIOD_MAX_MS 10000
#define KA_TIMEOUT_MULT 10 /* timeout = period * mult */
@@ -69,10 +73,10 @@ struct ETCP_DGRAM {// пакет (незашифрованный)
};
#pragma pack(pop)
/* Размер зашифрованного заголовка ETCP_DGRAM (timestamp + flag_up + padding) */
// Размер зашифрованного заголовка ETCP_DGRAM (timestamp + flag_up + padding)
#define ETCP_ENCRYPTED_HDR_SIZE (offsetof(struct ETCP_DGRAM, data) - offsetof(struct ETCP_DGRAM, timestamp))
/* Размер базового ACK: type(1) + count(1) + last_delivered_id(4) + rx_dup_count(2) */
// Размер базового ACK: type(1) + count(1) + last_delivered_id(4) + rx_dup_count(2)
#define ETCP_ACK_BASE_SIZE (2 + 4 + 2)
// --- INIT packet wire formats (inside encrypted payload, before padding) ---
@@ -97,6 +101,7 @@ struct ETCP_INIT_REQUEST_PKT {
// V4 fields:
uint8_t device_type; // 66: CLIENT_TYPE_SERVER/DESKTOP/MOBILE
} __attribute__((packed));
#define ETCP_INIT_REQ_SIZE sizeof(struct ETCP_INIT_REQUEST_PKT)
// INIT RESPONSE: server → client
@@ -118,10 +123,12 @@ struct ETCP_INIT_RESPONSE_PKT {
uint8_t device_type; // 61: CLIENT_TYPE_SERVER/DESKTOP/MOBILE
uint8_t keepalive[2]; // 62: server keepalive interval (big-endian)
} __attribute__((packed));
#define ETCP_INIT_RESP_SIZE sizeof(struct ETCP_INIT_RESPONSE_PKT)
typedef void (*etcp_ping_callback_t)(int success, uint16_t rtt, void* arg, uint64_t nonce,
const uint8_t* resp_data, size_t resp_data_len);
struct PING_CONTEXT {
struct PING_CONTEXT* next;
struct UTUN_INSTANCE* instance;
@@ -165,10 +172,10 @@ struct ETCP_SOCKET {
struct sockaddr_storage interface_addr;
struct sockaddr_storage nat_addr;
/* ── uasync handle (UDP read_cb / TCP accept_cb) ── */
// ── uasync handle (UDP read_cb / TCP accept_cb) ──
void* socket_id;
/* ── UDP-only ── */
// ── UDP-only ──
struct ll_queue* links_queue; // hash-очередь линков
int errorcode;
size_t pkt_format_errors;
@@ -332,27 +339,34 @@ struct ETCP_LINK {
uint32_t tcp_reconnect_delay_ms; // задержка реконнекта (экспоненциальный backoff)
};
// ── Внутренняя отправка пакетов ──
// Единая точка отправки всех ETCP-пакетов.
ssize_t etcp_udp_send(struct ETCP_LINK* link, socket_t fd, const void* buf, size_t len,
const struct sockaddr* addr, socklen_t addr_len);
// INITIALIZATION
// Создаёт только listen-сокеты из конфига (серверы для incoming connections)
// ── Запуск транспорта ядра ──
// Создать listen-сокеты из [server]; при auto_sockets их создаёт отдельный модуль.
int init_sockets(struct UTUN_INSTANCE* instance);
// Создаёт listen-сокеты и client connections из конфига
// Подготовить listen-сокеты, NCD control и keepalive; исходящие запросы UTUN запускаются отдельно. 0/-1.
int init_connections(struct UTUN_INSTANCE* instance);
// Заменяет принадлежащие конфигурации NCD handles. Новые приобретаются до освобождения старых.
// Заменить запросы к пирам конфигурации работающей UTUN-группы. Новые открываются до закрытия старых; 0/-1.
int init_client_connections(struct UTUN_INSTANCE* instance, struct CFG_CLIENT* clients);
// SOCKET FUNCTIONS
// добавляет новый версер (сокет для приёма и отправки кодограмм. обслуживает много подключений)
// ── Сокеты UDP и TCP ──
// Создать UDP-сокет приёма/отправки из server; один сокет обслуживает линки нескольких пиров.
struct ETCP_SOCKET* etcp_socket_add(struct UTUN_INSTANCE* instance, struct CFG_SERVER* server);
// удаляет сокет и освобождает ресурсы (грохает все его подключения и сокет)
// Закрыть UDP-сокет и все привязанные к нему линки, освободить ETCP_SOCKET.
void etcp_socket_remove(struct ETCP_SOCKET* conn);
// TCP socket functions (uses unified ETCP_SOCKET with is_tcp=1)
struct ETCP_SOCKET* tcp_socket_add(struct UTUN_INSTANCE* instance, struct CFG_SERVER* server);
void tcp_socket_remove(struct ETCP_SOCKET* sock);
// Создаёт один серверный сокет (UDP или TCP) из CFG_SERVER. Линки не добавляет.
@@ -362,14 +376,19 @@ struct stcp_link;
struct stcp_client;
void tcp_server_on_link(struct stcp_link *link, struct ETCP_SOCKET *tcp_sock);
// connection functions
// создает новый канал связи для etcp подключения (ETCP_CONN)
// ── Создание, поиск и закрытие линков ──
// Создать линк между сокетом и адресом пира для ETCP_CONN.
struct ETCP_LINK* etcp_link_new(struct ETCP_CONN* etcp, struct ETCP_SOCKET* conn,
const struct sockaddr_storage* remote_addr, uint8_t is_server);
void etcp_link_update_inflight_lim(struct ETCP_LINK* link, uint32_t new_lim);
void etcp_link_close(struct ETCP_LINK* link);
//int etcp_input_cbk(struct packet_buffer* pkt, struct ETCP_SOCKET* conn);// получает расшифрованный пакет
int etcp_encrypt_send(struct ETCP_DGRAM* dgram);// зашифровывает и отправляет пакет
// find link by address
struct ETCP_LINK* etcp_link_find_by_addr(struct ETCP_SOCKET* e_sock, const struct sockaddr_storage* addr, int is_tcp);
@@ -383,20 +402,30 @@ struct ETCP_LINK* etcp_select_collision_link(struct ETCP_CONN* conn, struct ETCP
// returns first free id (0-255) or -1 if all occupied
int etcp_find_free_local_link_id(struct ETCP_CONN* etcp);
// Burst measurement functions
// ── Внутренние измерения пропускной способности ──
void etcp_link_burst_start(struct ETCP_LINK* link);
void etcp_link_burst_check(struct ETCP_LINK* link);
void etcp_link_burst_finish(struct ETCP_LINK* link);
// ── Внутренние переходы состояния и приём ──
void etcp_link_enter_init(struct ETCP_LINK *link);
void etcp_link_enter_reinit(struct ETCP_LINK *link);
void etcp_link_enter_ready_tcp(struct ETCP_LINK *link);
void etcp_tcp_link_start_connect(struct ETCP_LINK *link, struct sockaddr_storage *addr, uint16_t port);
void etcp_tcp_link_start_reconnect(struct ETCP_LINK *link);
int etcp_packet_decrypted(struct ETCP_SOCKET* e_sock, struct ETCP_DGRAM* pkt,
struct ETCP_LINK* link, size_t pkt_len);
// ── Пробы доступности и RTT ──
int etcp_send_ping(struct UTUN_INSTANCE* instance, const uint8_t* peer_pubkey_bin,
const struct sockaddr_storage* addr, int timeout_ms,
etcp_ping_callback_t cb, void* user_arg,
@@ -410,7 +439,7 @@ int etcp_send_ping_to_socket(struct UTUN_INSTANCE* instance, struct ETCP_SOCKET*
const uint8_t* user_data, size_t user_data_len,
uint8_t flags);
/* Отмена без callback: вызывающий владеет arg и освобождает его после отмены. */
// Отмена без callback: вызывающий владеет arg и освобождает его после отмены.
void etcp_cancel_pings(struct UTUN_INSTANCE *instance, etcp_ping_callback_t callback, void *arg);
// TCP-ping: подключение к TCP-адресу peer + хендшейк с флагом PING. RTT меряется
@@ -421,6 +450,8 @@ int etcp_send_tcp_ping(struct UTUN_INSTANCE* instance, const uint8_t* peer_pubke
etcp_ping_callback_t cb, void* user_arg,
struct stcp_client** out_cli, const struct reality_client_config *reality);
// ── Внутренний callback uasync ──
void etcp_connections_read_callback_socket(socket_t sock, void* arg);
+430
View File
@@ -0,0 +1,430 @@
#ifndef ETCP_CONNECTIONS_H
#define ETCP_CONNECTIONS_H
#ifdef __cplusplus
extern "C" {
#endif
// подмодуль ETCP который обслуживает сокеты ETCP для приёма-передачи пакетов и одно ETCP подключение через несколько каналов связи (failover)
#include "secure_channel.h"
#include "etcp_session.h"
#include "reality.h"
#include "utun_instance.h"
#include "etcp_bbr.h"
#include "../routing_layer/address_profile.h"
#include "../lib/socket_compat.h"
#include "../lib/ll_queue.h"
#include <stdint.h>
#include <stddef.h>
#define IP_UDP_OVERHEAD_V4 (20 + 8) // IP(20)+UDP(8)
#define IP_UDP_OVERHEAD_V6 (40 + 8) // IP(40)+UDP(8)
#define SC_ENCRYPT_OVERHEAD (ETCP_ENCRYPTED_HDR_SIZE + ETCP_SESSION_HEADER_SIZE + SC_NONCE_SIZE + SC_CRC32_SIZE + SC_TAG_SIZE)
#define UDP_SC_HDR_SIZE (SC_NONCE_SIZE + SC_TAG_SIZE + SC_CRC32_SIZE + 5)
#define ACK_REZERV 100// сколько байт резервировать под ack и прочие заголовки
#define WIRE_OVERHEAD(fam) ((fam) == AF_INET6 ? IP_UDP_OVERHEAD_V6 + SC_ENCRYPT_OVERHEAD : IP_UDP_OVERHEAD_V4 + SC_ENCRYPT_OVERHEAD)
#define INFLIGHT_LIM_MIN 8192 // 8K
#define INFLIGHT_LIM_MAX 1048576 // 1M
#define PACKET_DATA_SIZE 2048
// Wire-MTU кап: пакет на проводе не должен превышать 1500 (согласовано с TUN_MAX_PACKET_SIZE).
// PACKET_DATA_SIZE (2048) — это внутренний буфер аллокации/приёма, а не wire-MTU.
#define PACKET_DATA_MAX_MTU 1500
#define ETCP_MAX_PAYLOAD_SIZE (PACKET_DATA_SIZE - ETCP_ACK_BASE_SIZE - 5) /* 2035 */
#define ETCP_RFC791_MIN_MTU 576
// Типы кодограмм протокола
#define ETCP_INIT_REQUEST 0x02
#define ETCP_INIT_RESPONSE 0x03
#define ETCP_INIT_REQUEST_NOINIT 0x04
#define ETCP_INIT_RESPONSE_NOINIT 0x05
#define ETCP_PING 0x06
#define ETCP_PONG 0x07
#define ETCP_KEEPALIVE 0x08
#define ETCP_TCP_ADDRESS 0x0d // STCP control: code + family(4/6) + observed IP(16), без порта
#define ETCP_PING_FLAG_WANT_RTT 0x01 // в PONG: хочу получить RTT от пингера
#define ETCP_PING_FLAG_SEND_RTT 0x02 // в PING: несу RTT для получателя
#define ETCP_PING_FLAG_OBSERVE 0x04 // PONG несёт фактически наблюдаемый IP:port
#define ETCP_PING_FLAG_ADDRESS_CHECK 0x08 // прямое входящее испытание endpoint
#define ETCP_PING_FLAG_NO_REPLY 0x10 // NAT-проба: результат через router, без автоматического UDP-ответа
/* Адаптивный keepalive */
#define KA_PERIOD_MIN_MS 200
#define KA_PERIOD_MAX_MS 10000
#define KA_TIMEOUT_MULT 10 /* timeout = period * mult */
#pragma pack(push, 1)
struct ETCP_DGRAM {// пакет (незашифрованный)
struct ETCP_LINK* link;// откуда получена или куда отправялем
uint16_t data_len;// общий размер пакета не включая timestamp
uint16_t noencrypt_len;// число байт (с конца) которые не надо шифровать. для передачи pubkey
uint16_t timestamp;// timestamp отправляющего узла при отправке пакета
uint8_t flag_up:1;// bit0 = up/down (recv_keepalive)
uint8_t data[];// данные пакета (без timestamp)
};
#pragma pack(pop)
/* Размер зашифрованного заголовка ETCP_DGRAM (timestamp + flag_up + padding) */
#define ETCP_ENCRYPTED_HDR_SIZE (offsetof(struct ETCP_DGRAM, data) - offsetof(struct ETCP_DGRAM, timestamp))
/* Размер базового ACK: type(1) + count(1) + last_delivered_id(4) + rx_dup_count(2) */
#define ETCP_ACK_BASE_SIZE (2 + 4 + 2)
// --- INIT packet wire formats (inside encrypted payload, before padding) ---
// INIT REQUEST: client → server
struct ETCP_INIT_REQUEST_PKT {
uint8_t code; // 0: ETCP_INIT_REQUEST (0x02) или ETCP_INIT_REQUEST_NOINIT (0x04)
uint8_t node_id[8]; // 1: sender node_id (big-endian)
uint8_t reset_id[8]; // 9: reset epoch id (big-endian)
uint8_t mtu[2]; // 17: client MTU (big-endian)
uint8_t keepalive[2]; // 19: keepalive interval (big-endian)
uint8_t recovery[2]; // 21: recovery interval/100 (big-endian)
uint8_t link_id; // 23: client's local link id
uint8_t socket_id; // 24: client's socket id
uint8_t only_local; // 25: client only_local flag
uint8_t type; // 26: client socket type (CFG_SERVER_TYPE_*)
// V2 fields (NAT_DIRECT detection):
uint8_t src_ipv4[4]; // 27: client interface_addr IPv4 (big-endian, 0 if N/A)
uint8_t src_port[2]; // 31: client interface_addr port (big-endian)
uint8_t collision; // 33: 1 = cross-connect, remote claims master
uint8_t ed25519_pubkey[SC_PUBKEY_SIZE]; // 34: client Ed25519 pubkey (32 bytes)
// V4 fields:
uint8_t device_type; // 66: CLIENT_TYPE_SERVER/DESKTOP/MOBILE
} __attribute__((packed));
#define ETCP_INIT_REQ_SIZE sizeof(struct ETCP_INIT_REQUEST_PKT)
// INIT RESPONSE: server → client
struct ETCP_INIT_RESPONSE_PKT {
uint8_t code; // 0: ETCP_INIT_RESPONSE (0x03) или ETCP_INIT_RESPONSE_NOINIT (0x05)
uint8_t node_id[8]; // 1: server node_id (big-endian)
uint8_t reset_id[8]; // 9: reset epoch id (big-endian)
uint8_t mtu[2]; // 17: server MTU (big-endian)
uint8_t link_id; // 19: server's local link id
uint8_t remote_socket_id; // 20: echo of client's socket_id
uint8_t only_local; // 21: server only_local flag
uint8_t type; // 22: server socket type (CFG_SERVER_TYPE_*)
// V2 fields:
uint8_t peer_ipv4[4]; // 23: client's external NAT address (big-endian)
uint8_t peer_port[2]; // 27: client's external NAT port (big-endian)
// V3 fields:
uint8_t ed25519_pubkey[SC_PUBKEY_SIZE]; // 29: server Ed25519 pubkey (32 bytes)
// V4 fields:
uint8_t device_type; // 61: CLIENT_TYPE_SERVER/DESKTOP/MOBILE
uint8_t keepalive[2]; // 62: server keepalive interval (big-endian)
} __attribute__((packed));
#define ETCP_INIT_RESP_SIZE sizeof(struct ETCP_INIT_RESPONSE_PKT)
typedef void (*etcp_ping_callback_t)(int success, uint16_t rtt, void* arg, uint64_t nonce,
const uint8_t* resp_data, size_t resp_data_len);
struct PING_CONTEXT {
struct PING_CONTEXT* next;
struct UTUN_INSTANCE* instance;
etcp_ping_callback_t cb;
void* arg;
uint64_t nonce;
void* timeout_timer;
uint64_t send_time; // время отправки пинга в 0.1ms
uint8_t peer_pubkey[SC_PUBKEY_SIZE];
uint8_t socket_id;
uint64_t socket_generation;
struct sockaddr_storage peer_addr;
uint8_t* user_data;
size_t user_data_len;
};
#define LINK_ADDR_KEY_SIZE 19 // port(2) + addr(16) + family(1)
struct link_queue_entry {
uint8_t key[LINK_ADDR_KEY_SIZE];
struct ETCP_LINK* link;
};
// список активных подключений которые обслуживает сокет. каждый сокет может обслуживать много подключений
struct ETCP_SOCKET {
struct ETCP_SOCKET* next; // Linked list для всех соединений
struct UTUN_INSTANCE* instance;
char name[MAX_CONN_NAME_LEN];
socket_t fd; // UDP socket / TCP listen socket
int mtu;
uint8_t type; // CFG_SERVER_TYPE_*
uint8_t sock_id; // unique id (0-255) for NAT matching
uint8_t is_tcp; // 1=TCP (STCP), 0=UDP
uint8_t probe_only; // вспомогательный UDP-порт наблюдателя, не транспорт NCD
uint64_t generation; // случайная эпоха endpoint; sock_id может переиспользоваться
uint8_t reality_enabled; // 1=TCP-порт с REALITY-камуфляжем
uint8_t only_local; // 1 = only local, no forwarding (UDP)
uint32_t netif_index; // для sin6_scope_id
struct sockaddr_storage local_addr;
struct sockaddr_storage interface_addr;
struct sockaddr_storage nat_addr;
/* ── uasync handle (UDP read_cb / TCP accept_cb) ── */
void* socket_id;
/* ── UDP-only ── */
struct ll_queue* links_queue; // hash-очередь линков
int errorcode;
size_t pkt_format_errors;
struct address_profile address_profile;
uint32_t local_defaultroute_ip; // auto-detected IPv4 (network byte order)
uint8_t local_defaultroute_ip6[16];
struct socks_udp* socks_udp; // SOCKS5 UDP ASSOCIATE (NULL = напрямую)
};
#define LINK_STATE_HANDSHAKE 1
#define LINK_STATE_TRY_RECONNECT 2
#define LINK_STATE_CONNECTED 3
// Тип функции-перехватчика отправки UDP.
// Если link->send_hook != NULL, etcp_udp_send вызывает её вместо socket_sendto.
struct ETCP_LINK;
typedef ssize_t (*etcp_udp_send_fn_t)(socket_t fd, const void* buf, size_t len,
const struct sockaddr* addr, socklen_t addr_len,
struct ETCP_LINK* link, void* context);
struct socks_udp;
// ETCP Link - одно динамическое соединение (один путь)
struct ETCP_LINK {
struct ll_entry* link_queue_entry; // элемент в socket->links_queue
struct ETCP_LINK* next; // Linked list подключений для ETCP_CONN (каждое подключение это child для ETCP_CONN)
struct ETCP_CONN* etcp; // подключение (parent)
struct ETCP_SOCKET* conn; // сокет через который работаем
// Путь соединения
struct sockaddr_storage remote_addr; // Удалённый адрес
// Параметры соединения
uint16_t mtu; // MTU общий
uint16_t mtu_local; // MTU моего зла
uint16_t mtu_remote; // MTU удаленного узла
uint16_t keepalive_interval; // Keepalive интервал (x1ms)
uint32_t recovery_interval; // recovery интервал (x0.1ms)
uint8_t is_server; // инициирует подключение клиент
uint8_t initialized; // Флаг инициализации (1=подтверждено или получен request)
uint8_t local_link_id; // id моего линка
uint8_t remote_link_id; // id этого линка на peer (устанавливается в момент initialized)
uint8_t remote_socket_id; // socket id peer
uint8_t remote_only_local; // only_local flag from peer
uint8_t remote_type; // CFG_SERVER_TYPE_* (type of peer's socket)
uint8_t recv_keepalive; // 1 - up, 0 - down (принимаются ли пакеты)
uint8_t remote_keepalive; // 1 - up, 0 - down (удаленная сторона сообщает - принимаются ли у нее пакеты)
uint8_t link_status; // 1 - up, 0 - down (итоговый статус - если есть проблемы на любой стороне - линк down)
uint8_t link_state; // 0 - just init, 1 - handshake, 2 - try reconnect, 3 - connected
// Состояние установки соединения (только для клиентов)
void* init_timer; // Таймер для повторов INIT (NULL=не подключается)
uint16_t init_timeout; // Текущий таймаут в мс
uint16_t init_retry_count; // Счетчик попыток
// uint64_t last_activity; // Время последней активности
uint64_t last_recv_local_time; // x0.1 ms
uint64_t rx_deadline_tb; // конечный срок ожидания RX, включая объявленный сон
void* rx_timeout_timer; // проверка жизни независимо от отправки keepalive
uint16_t last_recv_timestamp;
uint8_t last_recv_updated; // =1 при обновлении timestamp, =0 при отправке (чтобы не дублировать отправки при отсутствии обновлений)
uint64_t shaper_load_time_tb; // основной в 0.1 мс units
uint64_t shaper_sub_nanotime; // sub 0-999999 для 0.1 нс - 0.1 мс
uint8_t shaper_state;
void* shaper_timer;
uint32_t inflight_bytes;
uint32_t inflight_packets;
uint32_t inflight_lim_bytes;
// statistics
size_t encrypt_errors;
size_t decrypt_errors;
size_t send_errors;
size_t recv_errors;
size_t total_encrypted;
size_t total_decrypted;
uint32_t total_retransmissions;
uint64_t acked_bytes; // подтверждённых байт на этом линке
uint64_t acked_packets; // подтверждённых пакетов на этом линке
uint16_t rtt_last; // round trip (время отправки + приёма)
uint32_t jitter; // Current jitter [>>16] x0.1 ms
// rtt_avg10 удалён — заменён BBR (bbr->min_rtt_us/100)
uint32_t recv_dt_avg_tx; // дельта времени для отправленных пакетов (относительное время отправки) x256
uint32_t recv_dt_avg_rx; // дельта времени для принятых пакетов (относительное время отправки) x256
uint16_t tt_last; // transmit time (время доставки отправленных пакетов)
uint16_t rt_last; // recv time (время доставки принятых пакетов)
uint32_t bandwidth; // Link bandwidth in Kbits/sec
// NAT address tracking (from INIT_RESPONSE)
struct sockaddr_storage local_as_seen_by_peer; // только наш endpoint, наблюдаемый удалённым пиром
uint32_t nat_changes_count; // Counter of NAT address changes
uint32_t nat_hits_count; // Counter of NAT address matches (new init response with same IP:port)
// Keepalive state
void* keepalive_timer; // Таймер обычного keepalive (только в normal-режиме)
uint32_t keepalive_timeout; // таймаут (ms)
uint16_t ka_period_ms; // текущий период отправки keepalive
uint16_t ka_sleep_period_ms; // период keepalive в спячке (мс; настройка клиента / ka_period_ms пира)
uint8_t ka_my_sleeping; // 1 = я объявил сон (standby)
uint8_t ka_peer_sleeping; // 1 = пир объявил сон
uint8_t ka_sleep_acked; // получен ACK моего анонса сна
uint16_t ka_active_remaining; // сколько до SLEEP-фазы (x0.1с), 0 = уже спим
uint16_t ka_sleep_duration; // длительность SLEEP-фазы (x0.1с)
void* ka_sleep_announce_timer; // таймер ретрая анонса спячки (ACTIVE-окно)
uint8_t ka_announce_attempts; // попыток анонса в текущем окне
uint8_t ka_announce_max_attempts; // лимит попыток на окно (сколько влезет, но ≥ min)
uint8_t ka_fail_count; // кумулятивные провалы (сброс по ACK), ≥ KA_SLEEP_MAX_FAILS → down
uint8_t pkt_sent_since_keepalive; // Флаг: был ли отправлен пакет с последнего keepalive тика
uint32_t keepalive_sent_count; // Счётчик отправленных keepalive
uint32_t keepalive_recv_count; // Счётчик полученных keepalive
uint8_t peer_device_type; // CLIENT_TYPE_* удалённой стороны (из handshake)
uint16_t handshake_minsize; // минимальный размер udp при handshake
uint16_t handshake_maxsize; // мax размер udp при handshake (выбирает рандом)
// === Burst-измерение bandwidth ===
// Sender-сторона
uint16_t burst_id; // монотонно возрастающий ID burst
uint8_t burst_active; // 1 = burst в процессе отправки
uint8_t burst_seq; // текущий номер пакета в burst (0..burst_count-1)
uint8_t burst_count; // общее число пакетов в burst
uint64_t burst_last_time_tb; // время последнего burst (0.1ms), для MIN_BURST_INTERVAL
void* burst_resp_timer; // таймер ожидания ответа на burst
uint32_t burst_target_bdp; // BDP из последнего успешного burst (байты)
uint16_t burst_pkt_size; // сохранённый размер пакета для вычисления BW
// Receiver-сторона
uint16_t burst_recv_id; // ID отслеживаемого burst
uint8_t burst_recv_count; // ожидаемое число пакетов в burst
uint8_t burst_recv_next_seq; // следующий ожидаемый seq
uint8_t burst_recv_valid; // 1 = порядок пакетов не нарушен
uint8_t burst_recv_received; // сколько пакетов уже принято
uint64_t burst_recv_times[16]; // время прихода каждого пакета (µs)
uint16_t burst_recv_pkt_size; // размер пакета из MEAS_TS (для ответа)
uint8_t burst_resp_pending; // 1 = есть готовый ответ для piggyback
uint32_t burst_resp_gap_avg; // средний inter-packet gap (µs)
uint32_t burst_resp_gap_min; // минимальный inter-packet gap (µs)
uint8_t burst_resp_pkt_count; // число пакетов в измерении
uint8_t burst_resp_valid; // валидность измерения
// === BBR congestion control ===
struct bbr* bbr; // BBR state (per-link, создаётся в etcp_link_new)
uint64_t delivered_bytes; // кумулятивно delivered bytes на линке (для round detection)
uint64_t last_ack_time_tb; // 0.1ms timestamp последнего ACK (для interval_us)
uint32_t bbr_pacing_rate; // bytes/sec output BBR → shaper
uint32_t bbr_loss_since_ack; // счётчик потерь на линке с последнего ACK (для rs->lost)
etcp_udp_send_fn_t send_hook; // NULL = socket_sendto напрямую
void* send_hook_ctx;
struct sockaddr_storage local_bound_addr; // локальный адрес к которому привязан линк (interface_addr на момент создания)
uint8_t is_tcp; // 1 = TCP транспорт (STCP)
uint8_t reality_set; // 1 = link->reality валиден (реклама пира из BGP)
struct reality_client_config reality; // reality-параметры пира (для исходящего TCP)
struct stcp_link *tcp_link; // STCP линк (для TCP)
void *tcp_reconnect_timer; // таймер реконнекта (TCP клиент)
uint32_t tcp_reconnect_delay_ms; // задержка реконнекта (экспоненциальный backoff)
};
// Единая точка отправки всех ETCP-пакетов.
ssize_t etcp_udp_send(struct ETCP_LINK* link, socket_t fd, const void* buf, size_t len,
const struct sockaddr* addr, socklen_t addr_len);
// INITIALIZATION
// Создаёт только listen-сокеты из конфига (серверы для incoming connections)
int init_sockets(struct UTUN_INSTANCE* instance);
// Создаёт listen-сокеты и client connections из конфига
int init_connections(struct UTUN_INSTANCE* instance);
// Заменяет принадлежащие конфигурации NCD handles. Новые приобретаются до освобождения старых.
int init_client_connections(struct UTUN_INSTANCE* instance, struct CFG_CLIENT* clients);
// SOCKET FUNCTIONS
// добавляет новый версер (сокет для приёма и отправки кодограмм. обслуживает много подключений)
struct ETCP_SOCKET* etcp_socket_add(struct UTUN_INSTANCE* instance, struct CFG_SERVER* server);
// удаляет сокет и освобождает ресурсы (грохает все его подключения и сокет)
void etcp_socket_remove(struct ETCP_SOCKET* conn);
// TCP socket functions (uses unified ETCP_SOCKET with is_tcp=1)
struct ETCP_SOCKET* tcp_socket_add(struct UTUN_INSTANCE* instance, struct CFG_SERVER* server);
void tcp_socket_remove(struct ETCP_SOCKET* sock);
// Создаёт один серверный сокет (UDP или TCP) из CFG_SERVER. Линки не добавляет.
int etcp_create_server_socket(struct UTUN_INSTANCE* instance, struct CFG_SERVER* server, struct ETCP_SOCKET** out_sock);
struct stcp_link;
struct stcp_client;
void tcp_server_on_link(struct stcp_link *link, struct ETCP_SOCKET *tcp_sock);
// connection functions
// создает новый канал связи для etcp подключения (ETCP_CONN)
struct ETCP_LINK* etcp_link_new(struct ETCP_CONN* etcp, struct ETCP_SOCKET* conn,
const struct sockaddr_storage* remote_addr, uint8_t is_server);
void etcp_link_update_inflight_lim(struct ETCP_LINK* link, uint32_t new_lim);
void etcp_link_close(struct ETCP_LINK* link);
//int etcp_input_cbk(struct packet_buffer* pkt, struct ETCP_SOCKET* conn);// получает расшифрованный пакет
int etcp_encrypt_send(struct ETCP_DGRAM* dgram);// зашифровывает и отправляет пакет
// find link by address
struct ETCP_LINK* etcp_link_find_by_addr(struct ETCP_SOCKET* e_sock, const struct sockaddr_storage* addr, int is_tcp);
// Выбор исходящего линка для collision INIT по принимающему сокету и адресу отправителя.
// Линк через другой локальный сокет не подходит. NULL — встречной попытки на этой паре нет.
struct ETCP_LINK* etcp_select_collision_link(struct ETCP_CONN* conn, struct ETCP_SOCKET* e_sock,
const struct sockaddr_storage* addr);
// find free local_link_id for connection
// scans all links in connection, marks used ids in bit array
// returns first free id (0-255) or -1 if all occupied
int etcp_find_free_local_link_id(struct ETCP_CONN* etcp);
// Burst measurement functions
void etcp_link_burst_start(struct ETCP_LINK* link);
void etcp_link_burst_check(struct ETCP_LINK* link);
void etcp_link_burst_finish(struct ETCP_LINK* link);
void etcp_link_enter_init(struct ETCP_LINK *link);
void etcp_link_enter_reinit(struct ETCP_LINK *link);
void etcp_link_enter_ready_tcp(struct ETCP_LINK *link);
void etcp_tcp_link_start_connect(struct ETCP_LINK *link, struct sockaddr_storage *addr, uint16_t port);
void etcp_tcp_link_start_reconnect(struct ETCP_LINK *link);
int etcp_packet_decrypted(struct ETCP_SOCKET* e_sock, struct ETCP_DGRAM* pkt,
struct ETCP_LINK* link, size_t pkt_len);
int etcp_send_ping(struct UTUN_INSTANCE* instance, const uint8_t* peer_pubkey_bin,
const struct sockaddr_storage* addr, int timeout_ms,
etcp_ping_callback_t cb, void* user_arg,
const uint8_t* user_data, size_t user_data_len);
// шлёт пинг на addr используя pubkey. в пакет помещает user_data[user_len]. при получении ответа или таймауте вызывает callback.
// flags: ETCP_PING_FLAG_SEND_RTT — user_data содержит RTT для peer
int etcp_send_ping_to_socket(struct UTUN_INSTANCE* instance, struct ETCP_SOCKET* e_sock,
const uint8_t* peer_pubkey_bin, const struct sockaddr_storage* addr,
int timeout_ms, etcp_ping_callback_t cb, void* user_arg,
const uint8_t* user_data, size_t user_data_len,
uint8_t flags);
/* Отмена без callback: вызывающий владеет arg и освобождает его после отмены. */
void etcp_cancel_pings(struct UTUN_INSTANCE *instance, etcp_ping_callback_t callback, void *arg);
// TCP-ping: подключение к TCP-адресу peer + хендшейк с флагом PING. RTT меряется
// по хендшейку (send→response). cb вызывается один раз (success=1/rtt или success=0).
// out_cli (опционально) — возвращает хендл stcp_client для досрочной отмены (stcp_client_destroy).
int etcp_send_tcp_ping(struct UTUN_INSTANCE* instance, const uint8_t* peer_pubkey_bin,
const struct sockaddr_storage* addr, int timeout_ms,
etcp_ping_callback_t cb, void* user_arg,
struct stcp_client** out_cli, const struct reality_client_config *reality);
void etcp_connections_read_callback_socket(socket_t sock, void* arg);
#ifdef __cplusplus
}
#endif
#endif // ETCP_CONNECTIONS_H
+20 -7
View File
@@ -1,4 +1,7 @@
/* Независимый диапазон padding владельца; таймер и выбор длины работают в его uasync-потоке. */
/* etcp_padding — выбор длины случайного дополнения сетевых пакетов для instance или ETCP-соединения.
* Обнулить ETCP_PADDING → start → size перед отправкой → stop до освобождения владельца.
* Start выбирает диапазон длин и включает таймер его обновления; size учитывает свободное место пакета.
* Все вызовы — в uasync-потоке владельца. Модуль выбирает длину, байты дополнения заполняет отправитель. */
#ifndef ETCP_PADDING_H
#define ETCP_PADDING_H
@@ -15,18 +18,28 @@ struct UTUN_INSTANCE;
#define ETCP_PADDING_MAX 158
struct ETCP_PADDING {
uint16_t min, max;
void* timer;
struct UASYNC* ua;
uint16_t min, max; // включительные границы длины дополнения, байты
void* timer; // принадлежащий владельцу таймер обновления диапазона
struct UASYNC* ua; // заимствованный цикл событий до stop
const char* name; // заимствованное имя владельца для диагностики
uint64_t next_refresh_tb;
uint64_t next_refresh_tb; // следующий срок обновления в единицах 0.1 мс; 0 после stop
};
// ── Жизненный цикл диапазона ──
// Запустить обновление для обнулённого/остановленного состояния; name заимствован до stop. 0/-1.
int etcp_padding_start(struct ETCP_PADDING* padding, struct UASYNC* ua, const char* name);
// Отменить таймер и освободить ссылку на ua; NULL и повтор допустимы.
void etcp_padding_stop(struct ETCP_PADDING* padding);
/* NULL допустим только для автономного STCP без instance: диапазон генерируется на операцию. */
// ── Выбор длины и диапазона владельца ──
/* Выбрать длину в байтах, не превышающую available; -1 при неверном диапазоне или нехватке места для min.
* NULL допустим только для автономного STCP без instance: диапазон генерируется на операцию. */
int etcp_padding_size(const struct ETCP_PADDING* padding, size_t available);
/* Существующий незакрываемый ETCP_CONN владеет своим диапазоном; иначе используется instance. */
// Заимствованный диапазон существующего незакрываемого ETCP_CONN, иначе instance; NULL при неверных аргументах.
const struct ETCP_PADDING* etcp_padding_for_peer(struct UTUN_INSTANCE* inst, const uint8_t* public_key);
#ifdef __cplusplus
+36
View File
@@ -0,0 +1,36 @@
/* Независимый диапазон padding владельца; таймер и выбор длины работают в его uasync-потоке. */
#ifndef ETCP_PADDING_H
#define ETCP_PADDING_H
#include <stddef.h>
#include <stdint.h>
#ifdef __cplusplus
extern "C" {
#endif
struct UASYNC;
struct UTUN_INSTANCE;
#define ETCP_PADDING_MAX 158
struct ETCP_PADDING {
uint16_t min, max;
void* timer;
struct UASYNC* ua;
const char* name; // заимствованное имя владельца для диагностики
uint64_t next_refresh_tb;
};
int etcp_padding_start(struct ETCP_PADDING* padding, struct UASYNC* ua, const char* name);
void etcp_padding_stop(struct ETCP_PADDING* padding);
/* NULL допустим только для автономного STCP без instance: диапазон генерируется на операцию. */
int etcp_padding_size(const struct ETCP_PADDING* padding, size_t available);
/* Существующий незакрываемый ETCP_CONN владеет своим диапазоном; иначе используется instance. */
const struct ETCP_PADDING* etcp_padding_for_peer(struct UTUN_INSTANCE* inst, const uint8_t* public_key);
#ifdef __cplusplus
}
#endif
#endif
+24 -34
View File
@@ -1,30 +1,20 @@
/*
* node_conn_direct — общий прямой мультиадресный транспорт к узлу по node_id для BGP/topo_group.
* Адреса узла используются для создания и обновления линков общего ETCP-соединения.
*
* Архитектурная роль NODE_CONN_DIRECT (NCD handle):
* - выражает владение прямым транспортом, в том числе во время установления связи;
* - группа удерживает его для своей BGP/topo_group-сессии;
* - временный handle попытки подключения после установления связи должен перейти группе
* либо быть закрыт; передачу владения выполняют через node_conn_direct_transfer();
* - не выражает длительность звонка или передачи файла: эту потребность хранит CONN_MGR_HANDLE.
* CM подбирает оптимальный P2P-путь для больших объёмов данных (медиа, файлы, звонки),
* а NCD управляет прямым транспортом и его адресами независимо от конкретной медиа-операции.
*
* Модуль управляет ETCP-соединениями через непрозрачные handle'ы (NODE_CONN_DIRECT).
* Каждый владелец открывает свой handle; несколько handle разделяют ETCP_CONN.
* close снимает только своё владение. Последний handle работающего транспорта начинает
* CLOSE/KEEP_ALIVE (5 s либо срок пробуждения пира); DOWN удаляется по таймауту ETCP.
*
* Кто использует:
* - conn_mgr (Connection Manager) — временные handle'ы попыток установления прямого транспорта
* - topo_group — владеет транспортом групповой сессии с начала CONNECTING
* - протокол join — временный транспорт до добавления участника
* - любой модуль, кому нужно надёжное ETCP-соединение с конкретным node_id
*
* node_conn_direct (NCD) — прямой ETCP-транспорт к узлу по его ключу и сетевым адресам.
* Порядок: open → дождаться UP → get_conn → close. Каждый владелец открывает свой handle.
* Несколько handles делят один ETCP_CONN; close снимает только своё владение.
* NCD создаёт и обновляет UDP/STCP-линки, но не проверяет членство и готовность topo-группы.
* Для групповой сессии используйте topo_group_peer_open, для звонка/файла и выбора пути — conn_mgr_open.
* Все операции — в потоке uasync.
*/
/* Владение транспортом:
* Группа держит NCD с начала CONNECTING; отдельные handles могут принадлежать CM и протоколу join.
* Временный handle закрывают либо передают новому владельцу через transfer.
* Потребность сервиса в доставке на всё время звонка/файла выражает CONN_MGR_HANDLE (conn_mgr.h).
* Последний handle работающего транспорта начинает CLOSE/KEEP_ALIVE (5 с либо срок пробуждения пира);
* DOWN-транспорт удаляется по таймауту ETCP.
* Переходы UP/DOWN/TIMEOUT/CLOSED доставляются синхронно. Закрытие любого handle
* из callback безопасно. Начальное UP при open готового conn откладывается через uasync_call_soon.
* Все операции — в потоке uasync. NCD не проверяет членство и готовность группы.
*/
#ifndef NODE_CONN_DIRECT_H
#define NODE_CONN_DIRECT_H
@@ -36,18 +26,18 @@ struct ETCP_CONN;
struct TOPO_NODE;
struct ETCP_SOCKET;
/* Привязать CLOSE/KEEP_ALIVE при запуске ядра, в том числе без исходящих handles. */
// Привязать CLOSE/KEEP_ALIVE при запуске ядра, в том числе без исходящих handles.
int node_conn_direct_init(struct UTUN_INSTANCE* inst);
/* Число локальных владельцев конкретного транспорта; внешние ETCP refs сюда не входят. */
// Число локальных владельцев конкретного транспорта; внешние ETCP refs сюда не входят.
int node_conn_direct_handle_count(const struct ETCP_CONN* conn);
/* ─── Return codes ─── */
// ─── Return codes ───
#define NCD_NEW 0 /* создано новое соединение */
#define NCD_REUSED 1 /* переиспользовано существующее */
#define NCD_ERR -1 /* ошибка */
/* ─── Opaque handle ─── */
// ─── Opaque handle ───
struct NODE_CONN_DIRECT;
/* Рабочий ETCP, совместимая исходящая пара либо возможность reverse на наш опубликованный адрес.
@@ -55,7 +45,7 @@ struct NODE_CONN_DIRECT;
* Учитывает IPv6 и исходящий TCP fallback; возможность попытки не доказывает достижимость. */
int node_conn_direct_compatible(struct UTUN_INSTANCE* inst, uint64_t node_id);
/* ─── Callback events (единый callback на handle) ─── */
// ─── Callback events (единый callback на handle) ───
enum ncd_event {
NCD_EVENT_UP = 0, /* соединение поднялось (линки работают) */
NCD_EVENT_DOWN = 1, /* соединение упало */
@@ -88,7 +78,7 @@ int node_conn_direct_open_node(struct UTUN_INSTANCE* inst, uint64_t node_id,
struct TOPO_NODE* ni, /* публичный ключ + адреса пира (не владеем) */
struct ETCP_SOCKET* specific_sock); /* NULL — авто-подбор */
/* Освободить handle и отменить его callbacks; другие владельцы сохраняют транспорт. */
// Освободить handle и отменить его callbacks; другие владельцы сохраняют транспорт.
void node_conn_direct_close(struct NODE_CONN_DIRECT* h);
/* Применить принятую запись из общего реестра к уже существующему соединению.
@@ -108,13 +98,13 @@ void node_conn_direct_set_callback(struct NODE_CONN_DIRECT* h, ncd_callback cb,
* должен забыть указатель. Соединение не трогается. */
void node_conn_direct_transfer(struct NODE_CONN_DIRECT* h, ncd_callback new_cb, void* new_cb_arg);
/* Идентификатор узла; 0 для NULL. */
// Идентификатор узла; 0 для NULL.
uint64_t node_conn_direct_node_id(const struct NODE_CONN_DIRECT* h);
/* Заимствованный транспорт, в том числе до UP; NULL после CLOSED. Не закрывать напрямую. */
// Заимствованный транспорт, в том числе до UP; NULL после CLOSED. Не закрывать напрямую.
struct ETCP_CONN* node_conn_direct_get_conn(struct NODE_CONN_DIRECT* h);
/* ─── Протокол CLOSE / KEEP_ALIVE (ETCP_ID_NCD_CONTROL = 0x12) ─── */
// ─── Протокол CLOSE / KEEP_ALIVE (ETCP_ID_NCD_CONTROL = 0x12) ───
#define NCD_SUBCMD_CLOSE 0x01
#define NCD_SUBCMD_KEEP_ALIVE 0x02
@@ -129,7 +119,7 @@ struct ncd_control_msg {
#define NCD_CONTROL_MSG_SIZE sizeof(struct ncd_control_msg)
/* ─── Управление линками на конкретном сокете ─── */
// ─── Управление линками на конкретном сокете ───
/*
* ncd_add_socket_links — итерирует все активные NCD-соединения (ncd_registry),
* для каждого загружает TOPO_NODE с адресами пира и создаёт линки (etcp_link_new)
+149
View File
@@ -0,0 +1,149 @@
/*
* node_conn_direct — общий прямой мультиадресный транспорт к узлу по node_id для BGP/topo_group.
* Адреса узла используются для создания и обновления линков общего ETCP-соединения.
*
* Архитектурная роль NODE_CONN_DIRECT (NCD handle):
* - выражает владение прямым транспортом, в том числе во время установления связи;
* - группа удерживает его для своей BGP/topo_group-сессии;
* - временный handle попытки подключения после установления связи должен перейти группе
* либо быть закрыт; передачу владения выполняют через node_conn_direct_transfer();
* - не выражает длительность звонка или передачи файла: эту потребность хранит CONN_MGR_HANDLE.
* CM подбирает оптимальный P2P-путь для больших объёмов данных (медиа, файлы, звонки),
* а NCD управляет прямым транспортом и его адресами независимо от конкретной медиа-операции.
*
* Модуль управляет ETCP-соединениями через непрозрачные handle'ы (NODE_CONN_DIRECT).
* Каждый владелец открывает свой handle; несколько handle разделяют ETCP_CONN.
* close снимает только своё владение. Последний handle работающего транспорта начинает
* CLOSE/KEEP_ALIVE (5 s либо срок пробуждения пира); DOWN удаляется по таймауту ETCP.
*
* Кто использует:
* - conn_mgr (Connection Manager) — временные handle'ы попыток установления прямого транспорта
* - topo_group — владеет транспортом групповой сессии с начала CONNECTING
* - протокол join — временный транспорт до добавления участника
* - любой модуль, кому нужно надёжное ETCP-соединение с конкретным node_id
*
* Переходы UP/DOWN/TIMEOUT/CLOSED доставляются синхронно. Закрытие любого handle
* из callback безопасно. Начальное UP при open готового conn откладывается через uasync_call_soon.
* Все операции — в потоке uasync. NCD не проверяет членство и готовность группы.
*/
#ifndef NODE_CONN_DIRECT_H
#define NODE_CONN_DIRECT_H
#include <stdint.h>
struct UTUN_INSTANCE;
struct ETCP_CONN;
struct TOPO_NODE;
struct ETCP_SOCKET;
/* Привязать CLOSE/KEEP_ALIVE при запуске ядра, в том числе без исходящих handles. */
int node_conn_direct_init(struct UTUN_INSTANCE* inst);
/* Число локальных владельцев конкретного транспорта; внешние ETCP refs сюда не входят. */
int node_conn_direct_handle_count(const struct ETCP_CONN* conn);
/* ─── Return codes ─── */
#define NCD_NEW 0 /* создано новое соединение */
#define NCD_REUSED 1 /* переиспользовано существующее */
#define NCD_ERR -1 /* ошибка */
/* ─── Opaque handle ─── */
struct NODE_CONN_DIRECT;
/* Рабочий ETCP, совместимая исходящая пара либо возможность reverse на наш опубликованный адрес.
* Без линков/handles/таймеров/проб. Исходящие пары — тот же обход, что open; reverse — sock_match/публикуемые адреса.
* Учитывает IPv6 и исходящий TCP fallback; возможность попытки не доказывает достижимость. */
int node_conn_direct_compatible(struct UTUN_INSTANCE* inst, uint64_t node_id);
/* ─── Callback events (единый callback на handle) ─── */
enum ncd_event {
NCD_EVENT_UP = 0, /* соединение поднялось (линки работают) */
NCD_EVENT_DOWN = 1, /* соединение упало */
NCD_EVENT_CLOSED = 3, /* транспорт окончательно удалён; get_conn=NULL, владелец закрывает handle */
NCD_EVENT_TIMEOUT = 2, /* срок первой попытки истёк; удерживаемый handle может получить поздний UP */
};
typedef void (*ncd_callback)(struct NODE_CONN_DIRECT* h, enum ncd_event event, void* arg);
/*
* Открыть handle к узлу по node_id.
* Node info ищется через node_registry, fallback — SQLite.
* cb вызывается при изменении статуса / таймауте.
* Если conn уже готов — cb(NCD_EVENT_UP) через uasync_call_soon (не синхронно).
* До первой отправки дождаться UP. Результат: NCD_NEW/NCD_REUSED или NCD_ERR.
* При успехе вызывающий обязан закрыть *out_handle, в том числе после CLOSED.
*/
int node_conn_direct_open(struct UTUN_INSTANCE* inst, uint64_t node_id,
ncd_callback cb, void* cb_arg,
struct NODE_CONN_DIRECT** out_handle,
struct ETCP_SOCKET* specific_sock); /* NULL — авто-подбор всех совместимых сокетов для линков */
/*
* То же, но с уже загруженным TOPO_NODE (ni не владеем — можно передать временный на стеке).
* ni должен содержать public_key, v4_addrs/v6_addrs для создания линков.
*/
int node_conn_direct_open_node(struct UTUN_INSTANCE* inst, uint64_t node_id,
ncd_callback cb, void* cb_arg,
struct NODE_CONN_DIRECT** out_handle,
struct TOPO_NODE* ni, /* публичный ключ + адреса пира (не владеем) */
struct ETCP_SOCKET* specific_sock); /* NULL — авто-подбор */
/* Освободить handle и отменить его callbacks; другие владельцы сохраняют транспорт. */
void node_conn_direct_close(struct NODE_CONN_DIRECT* h);
/* Применить принятую запись из общего реестра к уже существующему соединению.
* Не создаёт соединение и не приобретает handle. Число новых линков или -1 при ошибке. */
int node_conn_direct_update_node(struct UTUN_INSTANCE* inst, uint64_t node_id);
/* Как close, но последний handle закрывает транспорт сразу, без fin_wait.
* При наличии других handle общий транспорт и их callbacks сохраняются. */
void node_conn_direct_force_close(struct NODE_CONN_DIRECT* h);
/* Сменить или сбросить (cb=NULL) callback на уже открытом handle.
* Не влияет на refcounting и состояние conn. */
void node_conn_direct_set_callback(struct NODE_CONN_DIRECT* h, ncd_callback cb, void* cb_arg);
/* Передать владение handle новому владельцу (смена callback + arg).
* После вызова handle принадлежит new_cb_arg, прежний владелец
* должен забыть указатель. Соединение не трогается. */
void node_conn_direct_transfer(struct NODE_CONN_DIRECT* h, ncd_callback new_cb, void* new_cb_arg);
/* Идентификатор узла; 0 для NULL. */
uint64_t node_conn_direct_node_id(const struct NODE_CONN_DIRECT* h);
/* Заимствованный транспорт, в том числе до UP; NULL после CLOSED. Не закрывать напрямую. */
struct ETCP_CONN* node_conn_direct_get_conn(struct NODE_CONN_DIRECT* h);
/* ─── Протокол CLOSE / KEEP_ALIVE (ETCP_ID_NCD_CONTROL = 0x12) ─── */
#define NCD_SUBCMD_CLOSE 0x01
#define NCD_SUBCMD_KEEP_ALIVE 0x02
#pragma pack(push, 1)
struct ncd_control_msg {
uint8_t cmd; /* ETCP_ID_NCD_CONTROL */
uint8_t subcmd; /* NCD_SUBCMD_CLOSE / KEEP_ALIVE */
uint64_t node_id; /* node_id отправителя */
};
#pragma pack(pop)
#define NCD_CONTROL_MSG_SIZE sizeof(struct ncd_control_msg)
/* ─── Управление линками на конкретном сокете ─── */
/*
* ncd_add_socket_links — итерирует все активные NCD-соединения (ncd_registry),
* для каждого загружает TOPO_NODE с адресами пира и создаёт линки (etcp_link_new)
* на переданном сокете (только совместимые family: v4/v6). Аналог ncd_create_links
* но вызывается извне (auto_socket) при добавлении нового интерфейса.
* Возвращает количество созданных линков (0 если нечего добавить или ошибка).
*/
int ncd_add_socket_links(struct UTUN_INSTANCE* inst, struct ETCP_SOCKET* sock);
/*
* ncd_remove_socket_links — итерирует все активные NCD-соединения,
* для каждого закрывает линки (etcp_link_close) привязанные к переданному сокету.
* Вызывается при удалении сокета (интерфейс пропал). Возвращает количество закрытых линков.
*/
int ncd_remove_socket_links(struct UTUN_INSTANCE* inst, struct ETCP_SOCKET* sock);
#endif
+24 -2
View File
@@ -1,4 +1,9 @@
/* Поток записей отделён от внутреннего буфера STCP; все вызовы — в uasync-потоке владельца.
/* reality_io — защищённая внешняя оболочка STCP с TLS-подобным handshake и записями.
* STCP запускает client_start/server_start; ready разрешает внутренний STCP-handshake и последующий обмен через read/send.
* Cleanup освобождает состояние соединения; owner_cleanup — общие ресурсы instance. Все вызовы — uasync-поток владельца.
* Буфер оболочки отделён от внутреннего буфера STCP; TLS-совместимость этим модулем не обеспечивается. */
/* Протокол внешней оболочки:
* CH -> SH + CCS + encrypted(EE/cover Certificate/cover CertificateVerify/Server Finished)
* -> CCS + encrypted(Client Finished) -> encrypted(STCP handshake и последующий поток).
* Проверка стороны использует Finished, cover Certificate/CertificateVerify не являются PKI.
@@ -6,7 +11,7 @@
* "uTun-reality-record-v2"); в transcript входят handshake-заголовки, но не record-заголовки.
* От root раздельно выводятся client-traffic/server-traffic и их key/iv/finished-ключи.
* После собственного SH fallback запрещён. До него relay получает весь исходный буфер без нормализации.
* Таймеры/списки/кеш replay принадлежат instance; destroy принудительно закрывает I/O без ожидания callbacks. */
* Таймеры/списки/кеш replay принадлежат instance; owner_cleanup принудительно закрывает I/O без ожидания callbacks. */
#ifndef REALITY_IO_H
#define REALITY_IO_H
#include "reality.h"
@@ -23,17 +28,34 @@ struct reality_owner {
struct reality_relay *relays;
unsigned relay_count;
};
// ── Запуск защищённого соединения ──
int reality_io_client_start(struct stcp_conn *c, const struct reality_client_config *cfg, uint32_t now,
const struct ETCP_PADDING *padding, void (*ready)(struct stcp_conn *));
int reality_io_server_start(struct stcp_conn *c, void (*ready)(struct stcp_conn *));
// ── Состояние и обмен данными ──
size_t reality_io_buffered(const struct stcp_conn *c);
const char* reality_io_state_name(const struct stcp_conn *c);
int reality_io_read(struct stcp_conn *c);
void reality_io_resume(struct stcp_conn *c);
int reality_io_send(struct stcp_conn *c, uint8_t *data, size_t len);
int reality_io_close_notify(struct stcp_conn *c);
// ── Освобождение соединения и общих ресурсов ──
void reality_io_cleanup(struct stcp_conn *c);
void reality_owner_cleanup(struct reality_owner *owner);
#ifdef __cplusplus
}
#endif
+40
View File
@@ -0,0 +1,40 @@
/* Поток записей отделён от внутреннего буфера STCP; все вызовы — в uasync-потоке владельца.
* CH -> SH + CCS + encrypted(EE/cover Certificate/cover CertificateVerify/Server Finished)
* -> CCS + encrypted(Client Finished) -> encrypted(STCP handshake и последующий поток).
* Проверка стороны использует Finished, cover Certificate/CertificateVerify не являются PKI.
* Root = HKDF-SHA256(AuthKey || X25519(client_ephemeral, server_ephemeral), SHA256(CH || SH),
* "uTun-reality-record-v2"); в transcript входят handshake-заголовки, но не record-заголовки.
* От root раздельно выводятся client-traffic/server-traffic и их key/iv/finished-ключи.
* После собственного SH fallback запрещён. До него relay получает весь исходный буфер без нормализации.
* Таймеры/списки/кеш replay принадлежат instance; destroy принудительно закрывает I/O без ожидания callbacks. */
#ifndef REALITY_IO_H
#define REALITY_IO_H
#include "reality.h"
#include "etcp_padding.h"
#ifdef __cplusplus
extern "C" {
#endif
struct stcp_conn;
struct reality_io;
struct reality_replay_cache;
struct reality_relay;
struct reality_owner {
struct reality_replay_cache *replay;
struct reality_relay *relays;
unsigned relay_count;
};
int reality_io_client_start(struct stcp_conn *c, const struct reality_client_config *cfg, uint32_t now,
const struct ETCP_PADDING *padding, void (*ready)(struct stcp_conn *));
int reality_io_server_start(struct stcp_conn *c, void (*ready)(struct stcp_conn *));
size_t reality_io_buffered(const struct stcp_conn *c);
const char* reality_io_state_name(const struct stcp_conn *c);
int reality_io_read(struct stcp_conn *c);
void reality_io_resume(struct stcp_conn *c);
int reality_io_send(struct stcp_conn *c, uint8_t *data, size_t len);
int reality_io_close_notify(struct stcp_conn *c);
void reality_io_cleanup(struct stcp_conn *c);
void reality_owner_cleanup(struct reality_owner *owner);
#ifdef __cplusplus
}
#endif
#endif