docs: remove reviewed header backup files

This commit is contained in:
evgeny committed 2026-10-21 15:22:42 +02:00
1 parent f5f106c682
commit f59f64ea48
19 files changed
-2576

No files matched your search

-444
View File
@@ -1,444 +0,0 @@
#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
-119
View File
@@ -1,119 +0,0 @@
// 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
View File
@@ -1,51 +0,0 @@
/* Один платформенный видеозвонок процесса. 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
-46
View File
@@ -1,46 +0,0 @@
/* Зашифрованные групповые вложения и ручная выдача ключей.
* Все 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
-313
View File
@@ -1,313 +0,0 @@
/*
* 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 */
-104
View File
@@ -1,104 +0,0 @@
/*
* 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 */
-32
View File
@@ -1,32 +0,0 @@
/* Подписанная аватарка канала: 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
-175
View File
@@ -1,175 +0,0 @@
/* 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
-67
View File
@@ -1,67 +0,0 @@
/* 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
-56
View File
@@ -1,56 +0,0 @@
/* Хранилище участников и асинхронный клиент поверх подписанного/зашифрованного 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
-219
View File
@@ -1,219 +0,0 @@
/* 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
-125
View File
@@ -1,125 +0,0 @@
/* Стриминг 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
-26
View File
@@ -1,26 +0,0 @@
/* Один планировщик на локальный экземпляр; 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
-49
View File
@@ -1,49 +0,0 @@
/* Общая адаптация воспроизведения 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
-95
View File
@@ -1,95 +0,0 @@
#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
-430
View File
@@ -1,430 +0,0 @@
#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
-36
View File
@@ -1,36 +0,0 @@
/* Независимый диапазон 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
-149
View File
@@ -1,149 +0,0 @@
/*
* 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
-40
View File
@@ -1,40 +0,0 @@
/* Поток записей отделён от внутреннего буфера 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