feat: media_async + media_index modules, media_files table, tests

- media_async: thread-per-task async engine with primitives (sha256, sign, copy, uuid, file_size)
- media_index: media_files DB table with register_async for indexing chunks
- chat_msg: async media submission via register_async, simplified chat_msg_submit fields
- voice_recorder/attachment_sender: write single file (no chunk splitting on caller side)
- inputbar/messagelist: updated to new simplified submit interface
- test_media_async: 4-thread stress test (200K ops / 2 sec, 0 errors)
- test_media_index: 9 cases covering commit edge cases + async flow
- build: Makefile.am, CMakeLists.txt, utun_sources.cmake updated
This commit is contained in:
evgeny committed 2026-07-29 14:15:10 +03:00
1 parent ce5a84da24
commit 65dbfa6813
43 files changed
+1983 -391

No files matched your search

+8
View File
@@ -45,6 +45,9 @@ utun_CORE_SOURCES = \
firewall.c \
eim_nat.c \
nat_transport.c \
media_delivery/media_delivery.c \
media_delivery/media_index.c \
media_async/media_async.c \
transport_layer/dummynet.c \
ntp_time.c \
ntp_node_time.c \
@@ -111,6 +114,9 @@ libutun_a_SOURCES = \
firewall.c \
eim_nat.c \
nat_transport.c \
media_delivery/media_delivery.c \
media_delivery/media_index.c \
media_async/media_async.c \
transport_layer/dummynet.c \
ntp_time.c \
ntp_node_time.c \
@@ -146,6 +152,8 @@ utun_CFLAGS = \
-I$(top_srcdir)/src \
-I$(top_srcdir)/src/transport_layer \
-I$(top_srcdir)/src/routing_layer \
-I$(top_srcdir)/src/media_delivery \
-I$(top_srcdir)/src/media_async \
-I$(top_srcdir)/src/uip \
-g \
-DUSE_SQLITE \
+4
View File
@@ -16,6 +16,7 @@
#include "../utun_instance.h"
#include "../ntp_time.h"
#include "../routing_layer/topo_node_sqlite.h"
#include "../media_delivery/media_index.h"
#include "../../lib/mem.h"
#include <string.h>
@@ -135,6 +136,9 @@ int chat_core_init(struct UTUN_INSTANCE* inst, const char* db_path) {
chat_core_sync_my_addresses();
/* create media_files table */
media_index_init(g_cc.db);
db_exec(
"CREATE TABLE IF NOT EXISTS local_identity ("
" id INTEGER PRIMARY KEY CHECK (id = 1),"
+3 -5
View File
@@ -28,11 +28,9 @@ int chat_core_is_initialized(void);
struct chat_msg_submit {
char channel_id[64];
char content_type[32];
char media_dt[32];
char media_basename[256];
char media_suffix[8];
char media_ext[16];
uint32_t media_num_blocks;
char media_src[1024];
char media_dest[1024];
uint8_t media_copy;
uint8_t* data;
uint32_t data_len;
uint64_t timestamp;
+100 -139
View File
@@ -9,16 +9,11 @@
#include "../utun_instance.h"
#include "../transport_layer/secure_channel.h"
#include "../media_delivery/media_index.h"
#include "../../lib/mem.h"
#include "../../lib/platform_compat.h"
#include <openssl/sha.h>
#include <unistd.h>
#include <sys/stat.h>
#define MEDIA_BLOCK_MIN (10 * 1024 * 1024)
#define MEDIA_BLOCK_MAX (25 * 1024 * 1024)
#define MEDIA_BLOCK_TARGET 30
/* forward decl */
static void chat_core_submit_media_message(struct chat_msg_submit* req);
@@ -72,174 +67,140 @@ void chat_core_submit_message(struct chat_msg_submit* req) {
void chat_core_submit_trampoline(void* arg) {
struct chat_msg_submit* req = (struct chat_msg_submit*)arg;
if (req->media_num_blocks > 0)
if (req->media_src[0] != '\0')
chat_core_submit_media_message(req);
else
chat_core_submit_message(req);
u_free(req);
}
/* ─── Медиа-сообщения: подпись блоков + сборка в единый файл ─── */
/* ─── Медиа-сообщения: async регистрация в media_files + отправка ─── */
static uint32_t media_calc_block_size(uint64_t file_size) {
if (file_size == 0) return 0;
uint64_t target = (file_size + MEDIA_BLOCK_TARGET - 1) / MEDIA_BLOCK_TARGET;
if (target < MEDIA_BLOCK_MIN) target = MEDIA_BLOCK_MIN;
if (target > MEDIA_BLOCK_MAX) target = MEDIA_BLOCK_MAX;
return (uint32_t)target;
}
struct media_submit_ctx {
char channel_id[64];
char content_type[32];
char media_base[512];
uint8_t msg_data[4096];
uint32_t msg_data_len;
uint64_t timestamp;
};
static void chat_core_submit_media_message(struct chat_msg_submit* req) {
if (!g_cc.initialized) { DEBUG_ERROR(DEBUG_CATEGORY_DB_SYNC, "%s: media submit before init", CC_ID); return; }
if (!req || req->media_num_blocks == 0) return;
static void on_media_registered(void* arg, int err, const struct media_index_result* result) {
struct media_submit_ctx* mctx = (struct media_submit_ctx*)arg;
DEBUG_INFO(DEBUG_CATEGORY_DB_SYNC, "%s: MEDIA SUBMIT ch=%s ct=%s dt=%s stem=%s ext=%s blocks=%u",
CC_ID, req->channel_id, req->content_type,
req->media_dt, req->media_basename, req->media_ext, req->media_num_blocks);
DEBUG_DEBUG(DEBUG_CATEGORY_DB_SYNC, "%s: db_path='%s' initialized=%d",
CC_ID, g_cc.db_path, g_cc.initialized);
struct DB_SYNC_INSTANCE* si = si_find(req->channel_id);
if (!si) { DEBUG_ERROR(DEBUG_CATEGORY_DB_SYNC, "%s: no db_sync instance for ch=%s", CC_ID, req->channel_id); return; }
char media_dir[1024];
{
/* g_cc.db_path is a file path (e.g. /data/.../chats.db).
Extract directory portion for media path. */
const char* last_slash = strrchr(g_cc.db_path, '/');
if (last_slash)
snprintf(media_dir, sizeof(media_dir), "%.*s/media/%s",
(int)(last_slash - g_cc.db_path), g_cc.db_path, req->channel_id);
else
snprintf(media_dir, sizeof(media_dir), "%s/media/%s", g_cc.db_path, req->channel_id);
if (err || !result) {
DEBUG_ERROR(DEBUG_CATEGORY_DB_SYNC, "%s: media registration failed ch=%s err=%d",
CC_ID, mctx->channel_id, err);
u_free(mctx);
return;
}
DEBUG_DEBUG(DEBUG_CATEGORY_DB_SYNC, "%s: media_dir='%s' dt='%s' stem='%s' ext='%s'",
CC_ID, media_dir, req->media_dt, req->media_basename, req->media_ext);
uint32_t num_blocks = req->media_num_blocks;
uint64_t block_size = 0;
uint64_t file_size = 0;
/* allocate sig array */
uint8_t (*sigs)[64] = (uint8_t(*)[64])u_malloc(num_blocks * 64);
if (!sigs) { DEBUG_ERROR(DEBUG_CATEGORY_DB_SYNC, "%s: sig alloc failed blocks=%u", CC_ID, num_blocks); return; }
/* read blocks, sign, compute sizes */
for (uint32_t n = 0; n < num_blocks; n++) {
char path[1280];
snprintf(path, sizeof(path), "%s/%s_%u_%s_%s.%s",
media_dir, req->media_dt, n, req->media_basename, req->media_suffix, req->media_ext);
FILE* f = fopen(path, "rb");
if (!f) {
DEBUG_ERROR(DEBUG_CATEGORY_DB_SYNC, "%s: cannot open block %u: %s", CC_ID, n, path);
u_free(sigs); return;
}
fseek(f, 0, SEEK_END); long fsz = ftell(f); fseek(f, 0, SEEK_SET);
if (fsz <= 0) { DEBUG_ERROR(DEBUG_CATEGORY_DB_SYNC, "%s: empty block %u: %s", CC_ID, n, path); fclose(f); u_free(sigs); return; }
uint8_t* buf = (uint8_t*)u_malloc((size_t)fsz);
if (!buf || fread(buf, 1, (size_t)fsz, f) != (size_t)fsz) {
DEBUG_ERROR(DEBUG_CATEGORY_DB_SYNC, "%s: read fail block %u", CC_ID, n);
if (buf) u_free(buf); fclose(f); u_free(sigs); return;
}
fclose(f);
if (sc_ed25519_sign(g_cc.inst->my_ed25519_privkey, buf, (size_t)fsz, sigs[n]) != SC_OK) {
DEBUG_ERROR(DEBUG_CATEGORY_DB_SYNC, "%s: Ed25519 sign fail block %u", CC_ID, n);
u_free(buf); u_free(sigs); return;
}
u_free(buf);
file_size += (uint64_t)fsz;
if (n == 0) block_size = (uint64_t)fsz;
struct DB_SYNC_INSTANCE* si = si_find(mctx->channel_id);
if (!si) {
DEBUG_ERROR(DEBUG_CATEGORY_DB_SYNC, "%s: no db_sync instance for ch=%s", CC_ID, mctx->channel_id);
u_free(mctx);
return;
}
if (file_size == 0) { DEBUG_ERROR(DEBUG_CATEGORY_DB_SYNC, "%s: zero file size", CC_ID); u_free(sigs); return; }
/* build final data: <base>|<file_size>|<block_size>|<num_blocks>|<sig_hex>,... */
size_t base_len = req->data_len;
size_t sigs_hex_len = num_blocks * 129; /* 128 hex + comma */
size_t full_data_cap = base_len + 128 + sigs_hex_len;
char* full_data = (char*)u_malloc(full_data_cap + 1);
if (!full_data) { DEBUG_ERROR(DEBUG_CATEGORY_DB_SYNC, "%s: data alloc fail", CC_ID); u_free(sigs); return; }
/* build full_data: <base>|<file_size>|<block_size>|<num_blocks>|<media_id_hex>|<content_hash_hex>|<id0,sig0,...> */
size_t base_len = mctx->msg_data_len;
int nb = result->num_blocks;
size_t pairs_len = (size_t)nb * (32 + 1 + 128 + 1); /* id_hex, sig_hex, commas */
size_t extra = 128 + 32 + 64 + pairs_len + 256;
size_t fdcap = base_len + extra;
char* full_data = u_malloc(fdcap);
if (!full_data) { DEBUG_ERROR(DEBUG_CATEGORY_DB_SYNC, "%s: data alloc fail", CC_ID); u_free(mctx); return; }
size_t off = 0;
if (base_len > 0) { memcpy(full_data + off, req->data, base_len); off += base_len; }
off += snprintf(full_data + off, full_data_cap - off, "|%llu|%llu|%u|",
(unsigned long long)file_size, (unsigned long long)block_size, num_blocks);
for (uint32_t n = 0; n < num_blocks; n++) {
if (base_len > 0) { memcpy(full_data + off, mctx->msg_data, base_len); off += base_len; }
off += snprintf(full_data + off, fdcap - off, "|%lld|%lld|%d|",
(long long)result->file_size, (long long)result->block_size, nb);
for (int i = 0; i < 16; i++) off += snprintf(full_data + off, fdcap - off, "%02x", result->media_id[i]);
off += snprintf(full_data + off, fdcap - off, "|");
for (int i = 0; i < 32; i++) off += snprintf(full_data + off, fdcap - off, "%02x", result->content_hash[i]);
off += snprintf(full_data + off, fdcap - off, "|");
for (int n = 0; n < nb; n++) {
if (n > 0) full_data[off++] = ',';
for (int b = 0; b < 64; b++)
off += snprintf(full_data + off, full_data_cap - off, "%02x", sigs[n][b]);
for (int i = 0; i < 16; i++)
off += snprintf(full_data + off, fdcap - off, "%02x", result->block_ids[n * 16 + i]);
full_data[off++] = ',';
for (int i = 0; i < 64; i++)
off += snprintf(full_data + off, fdcap - off, "%02x", result->block_sigs[n * 64 + i]);
}
size_t full_data_len = off;
/* build JSON with dynamic buffer */
size_t json_cap = full_data_len + 256;
char* json = (char*)u_malloc(json_cap);
if (!json) { DEBUG_ERROR(DEBUG_CATEGORY_DB_SYNC, "%s: json alloc fail", CC_ID); u_free(full_data); u_free(sigs); return; }
char* json = u_malloc(json_cap);
if (!json) { DEBUG_ERROR(DEBUG_CATEGORY_DB_SYNC, "%s: json alloc fail", CC_ID); u_free(full_data); u_free(mctx); return; }
snprintf(json, json_cap,
"{\"n\":%llu,\"ch\":\"%s\",\"ct\":\"%s\",\"d\":\"%.*s\"}",
(unsigned long long)g_cc.my_node_id, req->channel_id,
req->content_type, (int)full_data_len, full_data);
(unsigned long long)g_cc.my_node_id, mctx->channel_id,
mctx->content_type, (int)full_data_len, full_data);
uint64_t ts = db_sync_next_timestamp(si);
size_t sig_msg_len = 8 + strlen(json);
uint8_t* sig_msg = (uint8_t*)u_malloc(sig_msg_len);
if (!sig_msg) { DEBUG_ERROR(DEBUG_CATEGORY_DB_SYNC, "%s: sig_msg alloc fail", CC_ID); u_free(json); u_free(full_data); u_free(sigs); return; }
uint8_t* sig_msg = u_malloc(sig_msg_len);
if (!sig_msg) { DEBUG_ERROR(DEBUG_CATEGORY_DB_SYNC, "%s: sig_msg alloc fail", CC_ID); u_free(json); u_free(full_data); u_free(mctx); return; }
memcpy(sig_msg, &ts, 8);
memcpy(sig_msg + 8, json, sig_msg_len - 8);
uint8_t json_sig[64];
if (sc_ed25519_sign(g_cc.inst->my_ed25519_privkey, sig_msg, sig_msg_len, json_sig) != SC_OK) {
DEBUG_ERROR(DEBUG_CATEGORY_DB_SYNC, "%s: Ed25519 sign JSON failed", CC_ID);
u_free(sig_msg); u_free(json); u_free(full_data); u_free(sigs); return;
u_free(sig_msg); u_free(json); u_free(full_data); u_free(mctx); return;
}
/* assemble single file from blocks, then delete blocks */
{
char assembled_path[1280];
snprintf(assembled_path, sizeof(assembled_path), "%s/%s_%s_%s.%s",
media_dir, req->media_dt, req->media_basename, req->media_suffix, req->media_ext);
FILE* af = fopen(assembled_path, "wb");
if (af) {
for (uint32_t n = 0; n < num_blocks; n++) {
char block_path[1280];
snprintf(block_path, sizeof(block_path), "%s/%s_%u_%s_%s.%s",
media_dir, req->media_dt, n, req->media_basename, req->media_suffix, req->media_ext);
FILE* bf = fopen(block_path, "rb");
if (bf) {
uint8_t buf[65536]; size_t rd;
while ((rd = fread(buf, 1, sizeof(buf), bf)) > 0) fwrite(buf, 1, rd, af);
fclose(bf);
unlink(block_path);
}
}
fclose(af);
DEBUG_INFO(DEBUG_CATEGORY_DB_SYNC, "%s: assembled %s (%llu bytes)", CC_ID, assembled_path, (unsigned long long)file_size);
} else {
DEBUG_WARN(DEBUG_CATEGORY_DB_SYNC, "%s: cannot create assembled file %s", CC_ID, assembled_path);
}
}
/* build local_attrs JSON */
char local_attrs_json[512];
{
char bl_list[256] = "0"; size_t bl_off = 1;
for (uint32_t n = 1; n < num_blocks; n++)
bl_off += snprintf(bl_list + bl_off, sizeof(bl_list) - bl_off, ",%u", n);
snprintf(local_attrs_json, sizeof(local_attrs_json),
"{\"st\":\"fl\",\"fp\":\"%s_%s_%s.%s\",\"bl\":[%s]}",
req->media_dt, req->media_basename, req->media_suffix, req->media_ext, bl_list);
}
int ret = db_sync_insert_signed(si, json, strlen(json), json_sig, 64, ts, local_attrs_json);
int ret = db_sync_insert_signed(si, json, strlen(json), json_sig, 64, ts, NULL);
if (ret != 0)
DEBUG_ERROR(DEBUG_CATEGORY_DB_SYNC, "%s: media insert failed ret=%d", CC_ID, ret);
else
DEBUG_INFO(DEBUG_CATEGORY_DB_SYNC, "%s: media message inserted ch=%s ts=%llu",
CC_ID, mctx->channel_id, (unsigned long long)ts);
u_free(sig_msg); u_free(json); u_free(full_data); u_free(sigs);
u_free(sig_msg); u_free(json); u_free(full_data); u_free(mctx);
}
static void chat_core_submit_media_message(struct chat_msg_submit* req) {
if (!g_cc.initialized) { DEBUG_ERROR(DEBUG_CATEGORY_DB_SYNC, "%s: media submit before init", CC_ID); return; }
if (!req || req->media_src[0] == '\0') return;
DEBUG_INFO(DEBUG_CATEGORY_DB_SYNC, "%s: MEDIA SUBMIT ch=%s ct=%s src=%s dst=%s copy=%d",
CC_ID, req->channel_id, req->content_type,
req->media_src, req->media_dest, req->media_copy);
if (!g_cc.inst->media_async) {
DEBUG_ERROR(DEBUG_CATEGORY_DB_SYNC, "%s: media_async not initialized", CC_ID);
return;
}
/* build media_base from db_path: dirname(db_path) e.g. /path/to/data */
char media_base[1024];
{
const char* last_slash = strrchr(g_cc.db_path, '/');
if (last_slash)
snprintf(media_base, sizeof(media_base), "%.*s", (int)(last_slash - g_cc.db_path), g_cc.db_path);
else
snprintf(media_base, sizeof(media_base), "%s", g_cc.db_path);
}
/* copy fields for async callback (req is freed by trampoline immediately) */
struct media_submit_ctx* mctx = u_calloc(1, sizeof(*mctx));
if (!mctx) return;
snprintf(mctx->channel_id, sizeof(mctx->channel_id), "%s", req->channel_id);
snprintf(mctx->content_type, sizeof(mctx->content_type), "%s", req->content_type);
snprintf(mctx->media_base, sizeof(mctx->media_base), "%s", media_base);
mctx->timestamp = req->timestamp;
if (req->data && req->data_len > 0) {
mctx->msg_data_len = req->data_len < sizeof(mctx->msg_data) ? req->data_len : sizeof(mctx->msg_data) - 1;
memcpy(mctx->msg_data, req->data, mctx->msg_data_len);
}
media_index_register_async(
g_cc.inst->media_async, g_cc.inst->ua, g_cc.db,
g_cc.my_node_id, g_cc.inst->my_ed25519_privkey,
req->channel_id,
req->media_src, req->media_dest, req->media_copy ? 1 : 0,
media_base,
on_media_registered, mctx);
}
/* ─── Обновление local_attrs (для будущей приёмной стороны) ─── */
+154
View File
@@ -0,0 +1,154 @@
// media_async.c — thread-per-task async engine + crypto/file primitives
#include "media_async.h"
#include "../../lib/u_async.h"
#include "../../lib/mem.h"
#include "../../lib/debug_config.h"
#include <openssl/sha.h>
#include <openssl/evp.h>
#include <openssl/rand.h>
#include <pthread.h>
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <sys/stat.h>
struct media_async {
int initialized;
};
struct ma_thread_ctx {
struct UASYNC* ua;
ma_work_fn work;
void* data;
ma_done_fn done;
void* arg;
};
static void ma_done_trampoline(void* raw) {
struct ma_thread_ctx* ctx = (struct ma_thread_ctx*)raw;
ctx->done(ctx->arg, 0);
u_free(ctx);
}
static void* ma_thread_entry(void* raw) {
struct ma_thread_ctx* ctx = (struct ma_thread_ctx*)raw;
ctx->work(ctx->data);
uasync_post(ctx->ua, ma_done_trampoline, ctx);
return NULL;
}
struct media_async* media_async_create(void) {
struct media_async* ma = u_malloc(sizeof(*ma));
if (!ma) return NULL;
ma->initialized = 1;
DEBUG_INFO(DEBUG_CATEGORY_DEBUG, "media_async: created");
return ma;
}
void media_async_destroy(struct media_async* ma) {
if (!ma) return;
ma->initialized = 0;
u_free(ma);
DEBUG_INFO(DEBUG_CATEGORY_DEBUG, "media_async: destroyed");
}
void media_async_submit(struct media_async* ma, struct UASYNC* ua,
ma_work_fn work, void* data,
ma_done_fn done, void* arg) {
(void)ma;
if (!ua || !work || !done) return;
struct ma_thread_ctx* ctx = u_malloc(sizeof(*ctx));
if (!ctx) { done(arg, -1); return; }
ctx->ua = ua;
ctx->work = work;
ctx->data = data;
ctx->done = done;
ctx->arg = arg;
pthread_t tid;
int rc = pthread_create(&tid, NULL, ma_thread_entry, ctx);
if (rc != 0) {
DEBUG_ERROR(DEBUG_CATEGORY_DEBUG, "media_async: pthread_create failed rc=%d", rc);
u_free(ctx);
done(arg, -1);
return;
}
pthread_detach(tid);
}
/* ─── примитивы (вызываются из worker thread) ─── */
int ma_sha256_file(const char* path, uint8_t hash_out[32]) {
FILE* f = fopen(path, "rb");
if (!f) { DEBUG_ERROR(DEBUG_CATEGORY_DEBUG, "ma_sha256_file: cannot open %s", path); return -1; }
SHA256_CTX ctx;
SHA256_Init(&ctx);
uint8_t buf[65536];
size_t rd;
while ((rd = fread(buf, 1, sizeof(buf), f)) > 0) SHA256_Update(&ctx, buf, rd);
fclose(f);
SHA256_Final(hash_out, &ctx);
return 0;
}
int ma_sign_block(const uint8_t* ed25519_privkey, const uint8_t* data, size_t len, uint8_t sig_out[64]) {
EVP_PKEY* pkey = EVP_PKEY_new_raw_private_key(EVP_PKEY_ED25519, NULL, ed25519_privkey, 32);
if (!pkey) { DEBUG_ERROR(DEBUG_CATEGORY_DEBUG, "ma_sign_block: EVP_PKEY_new_raw_private_key failed"); return -1; }
EVP_MD_CTX* mdctx = EVP_MD_CTX_new();
if (!mdctx) { EVP_PKEY_free(pkey); return -1; }
if (EVP_DigestSignInit(mdctx, NULL, NULL, NULL, pkey) != 1) {
DEBUG_ERROR(DEBUG_CATEGORY_DEBUG, "ma_sign_block: EVP_DigestSignInit failed");
EVP_MD_CTX_free(mdctx); EVP_PKEY_free(pkey); return -1;
}
size_t siglen = 64;
int rc = EVP_DigestSign(mdctx, sig_out, &siglen, data, len);
EVP_MD_CTX_free(mdctx);
EVP_PKEY_free(pkey);
if (rc != 1 || siglen != 64) {
DEBUG_ERROR(DEBUG_CATEGORY_DEBUG, "ma_sign_block: EVP_DigestSign failed rc=%d len=%zu", rc, siglen);
return -1;
}
return 0;
}
int ma_copy_file(const char* src, const char* dst) {
if (!src || !dst) return -1;
if (strcmp(src, dst) == 0) return 0;
FILE* s = fopen(src, "rb");
if (!s) { DEBUG_ERROR(DEBUG_CATEGORY_DEBUG, "ma_copy_file: cannot open src %s", src); return -1; }
FILE* d = fopen(dst, "wb");
if (!d) { DEBUG_ERROR(DEBUG_CATEGORY_DEBUG, "ma_copy_file: cannot create dst %s", dst); fclose(s); return -1; }
uint8_t buf[65536];
size_t rd;
while ((rd = fread(buf, 1, sizeof(buf), s)) > 0) {
if (fwrite(buf, 1, rd, d) != rd) {
DEBUG_ERROR(DEBUG_CATEGORY_DEBUG, "ma_copy_file: write error at %s", dst);
fclose(s); fclose(d); return -1;
}
}
fclose(s); fclose(d);
return 0;
}
int ma_file_size(const char* path) {
struct stat st;
if (stat(path, &st) != 0) return -1;
return (int)(st.st_size > 0 ? st.st_size : 0);
}
void ma_uuid(uint8_t uuid_out[16]) {
RAND_bytes(uuid_out, 16);
}
+36
View File
@@ -0,0 +1,36 @@
// media_async.h — асинхронный движок thread-per-task + примитивы (хеш, подпись, копия файла)
#ifndef MEDIA_ASYNC_H
#define MEDIA_ASYNC_H
#ifdef __cplusplus
extern "C" {
#endif
#include <stdint.h>
#include <stddef.h>
struct UASYNC;
struct media_async;
typedef void (*ma_work_fn)(void* data);
typedef void (*ma_done_fn)(void* arg, int err);
struct media_async* media_async_create(void);
void media_async_destroy(struct media_async* ma);
void media_async_submit(struct media_async* ma, struct UASYNC* ua,
ma_work_fn work, void* data,
ma_done_fn done, void* arg);
int ma_sha256_file(const char* path, uint8_t hash_out[32]);
int ma_sign_block(const uint8_t* ed25519_privkey,
const uint8_t* data, size_t len, uint8_t sig_out[64]);
int ma_copy_file(const char* src, const char* dst);
int ma_file_size(const char* path);
void ma_uuid(uint8_t uuid_out[16]);
#ifdef __cplusplus
}
#endif
#endif
+41
View File
@@ -0,0 +1,41 @@
#include "media_delivery.h"
#include "utun_instance.h"
#include "etcp_router.h"
#include "../lib/debug_config.h"
#include <string.h>
static void media_delivery_etcp_recv_cb(struct ETCP_CONN* conn, struct ll_entry* entry) {
(void)conn; (void)entry;
}
int media_delivery_init(struct UTUN_INSTANCE* inst) {
if (!inst) return -1;
struct media_delivery_ctx* md = &inst->md;
memset(md, 0, sizeof(*md));
md->self_node_id = inst->node_id;
if (etcp_router_bind(inst, ETCP_RT_ID_MEDIA_DELIVERY, media_delivery_etcp_recv_cb) != 0) {
DEBUG_ERROR(DEBUG_CATEGORY_GENERAL, "media_delivery: failed to bind ETCP_RT_ID_MEDIA_DELIVERY");
return -1;
}
md->initialized = 1;
DEBUG_INFO(DEBUG_CATEGORY_GENERAL, "media_delivery initialized, node_id=%016llx",
(unsigned long long)md->self_node_id);
return 0;
}
void media_delivery_destroy(struct UTUN_INSTANCE* inst) {
if (!inst || !inst->md.initialized) return;
struct media_delivery_ctx* md = &inst->md;
etcp_router_unbind(inst, ETCP_RT_ID_MEDIA_DELIVERY);
memset(md, 0, sizeof(*md));
DEBUG_INFO(DEBUG_CATEGORY_GENERAL, "media_delivery destroyed");
}
int media_delivery_bind(struct UTUN_INSTANCE* inst) {
return media_delivery_init(inst);
}
+27
View File
@@ -0,0 +1,27 @@
// media_delivery.h — распространение медиа (аудио/видео стриминг)
#ifndef MEDIA_DELIVERY_H
#define MEDIA_DELIVERY_H
#ifdef __cplusplus
extern "C" {
#endif
#include <stdint.h>
struct UTUN_INSTANCE;
struct media_delivery_ctx {
uint64_t self_node_id;
int initialized;
};
int media_delivery_init(struct UTUN_INSTANCE* inst);
void media_delivery_destroy(struct UTUN_INSTANCE* inst);
int media_delivery_bind(struct UTUN_INSTANCE* inst);
#ifdef __cplusplus
}
#endif
#endif
+297
View File
@@ -0,0 +1,297 @@
// media_index.c — таблица media_files: init, commit, register_async
#include "media_index.h"
#include "../media_async/media_async.h"
#include "../../lib/u_async.h"
#include "../../lib/mem.h"
#include "../../lib/debug_config.h"
#include <openssl/evp.h>
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <time.h>
#include <unistd.h>
#define MEDIA_BLOCK_MIN (10 * 1024 * 1024)
#define MEDIA_BLOCK_MAX (25 * 1024 * 1024)
#define MEDIA_BLOCK_TARGET 30
static int mi_insert(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, const uint8_t* node_sign,
int64_t timestamp, int64_t file_size,
int64_t chunk_size, int chunk, int64_t offset) {
const char* sql =
"INSERT INTO media_files(media_id,block_id,content_hash,chat_id,location,"
"node_id,node_sign,timestamp,file_size,chunk_size,chunk,offset)"
" VALUES(?,?,?,?,?,?,?,?,?,?,?,?)";
sqlite3_stmt* stmt = NULL;
if (sqlite3_prepare_v2(db, sql, -1, &stmt, NULL) != SQLITE_OK) {
DEBUG_ERROR(DEBUG_CATEGORY_DEBUG, "media_index: insert prep fail: %s", sqlite3_errmsg(db));
return -1;
}
sqlite3_bind_blob(stmt, 1, media_id, 16, SQLITE_STATIC);
sqlite3_bind_blob(stmt, 2, block_id, 16, SQLITE_STATIC);
sqlite3_bind_blob(stmt, 3, content_hash, 32, SQLITE_STATIC);
sqlite3_bind_text(stmt, 4, chat_id, -1, SQLITE_STATIC);
sqlite3_bind_text(stmt, 5, location, -1, SQLITE_STATIC);
sqlite3_bind_int64(stmt, 6, (sqlite3_int64)node_id);
sqlite3_bind_blob(stmt, 7, node_sign, 64, SQLITE_STATIC);
sqlite3_bind_int64(stmt, 8, timestamp);
sqlite3_bind_int64(stmt, 9, file_size);
sqlite3_bind_int64(stmt, 10, chunk_size);
sqlite3_bind_int(stmt, 11, chunk);
sqlite3_bind_int64(stmt, 12, offset);
int rc = sqlite3_step(stmt);
sqlite3_finalize(stmt);
if (rc != SQLITE_DONE) {
DEBUG_ERROR(DEBUG_CATEGORY_DEBUG, "media_index: insert fail rc=%d: %s", rc, sqlite3_errmsg(db));
return -1;
}
return 0;
}
int media_index_init(sqlite3* db) {
if (!db) return -1;
const char* sql =
"CREATE TABLE IF NOT EXISTS media_files ("
" id INTEGER PRIMARY KEY AUTOINCREMENT,"
" media_id BLOB NOT NULL,"
" block_id BLOB NOT NULL,"
" content_hash BLOB NOT NULL,"
" chat_id TEXT NOT NULL,"
" location TEXT NOT NULL,"
" node_id INTEGER NOT NULL,"
" node_sign BLOB NOT NULL,"
" timestamp INTEGER NOT NULL,"
" file_size INTEGER NOT NULL,"
" chunk_size INTEGER NOT NULL,"
" chunk INTEGER NOT NULL,"
" offset INTEGER NOT NULL);";
char* err = NULL;
int rc = sqlite3_exec(db, sql, NULL, NULL, &err);
if (rc != SQLITE_OK) {
DEBUG_ERROR(DEBUG_CATEGORY_DEBUG, "media_index: CREATE TABLE failed: %s", err ? err : "?");
sqlite3_free(err);
return -1;
}
sqlite3_exec(db,
"CREATE UNIQUE INDEX IF NOT EXISTS idx_media_files_block_id ON media_files(block_id);",
NULL, NULL, NULL);
sqlite3_exec(db,
"CREATE INDEX IF NOT EXISTS idx_media_files_media_id ON media_files(media_id);",
NULL, NULL, NULL);
sqlite3_exec(db,
"CREATE INDEX IF NOT EXISTS idx_media_files_chat_id ON media_files(chat_id);",
NULL, NULL, NULL);
sqlite3_exec(db,
"CREATE INDEX IF NOT EXISTS idx_media_files_node_id ON media_files(node_id);",
NULL, NULL, NULL);
sqlite3_exec(db,
"CREATE INDEX IF NOT EXISTS idx_media_files_content_hash ON media_files(content_hash);",
NULL, NULL, NULL);
DEBUG_INFO(DEBUG_CATEGORY_DEBUG, "media_index: table+indices created");
return 0;
}
int media_calc_block_size(uint64_t file_size) {
if (file_size == 0) return 0;
uint64_t target = (file_size + MEDIA_BLOCK_TARGET - 1) / MEDIA_BLOCK_TARGET;
if (target < MEDIA_BLOCK_MIN) target = MEDIA_BLOCK_MIN;
if (target > MEDIA_BLOCK_MAX) target = MEDIA_BLOCK_MAX;
return (int)target;
}
int media_index_generate_uuid(uint8_t uuid_out[16]) {
ma_uuid(uuid_out);
return 0;
}
void media_index_result_free(struct media_index_result* result) {
if (!result) return;
if (result->block_ids) { u_free(result->block_ids); result->block_ids = NULL; }
if (result->block_sigs) { u_free(result->block_sigs); result->block_sigs = NULL; }
}
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) {
if (!db || !result || !chat_id || !dest_path || !media_base) return -1;
int nb = result->num_blocks;
int64_t bs = result->block_size;
int64_t fs = result->file_size;
int64_t ts = (int64_t)time(NULL);
size_t base_len = strlen(media_base);
if (base_len > 0 && media_base[base_len - 1] == '/') base_len--;
const char* rel = dest_path;
if (strncmp(rel, media_base, base_len) == 0 && rel[base_len] == '/') rel += base_len + 1;
for (int n = 0; n < nb; n++) {
int64_t cs = (n == nb - 1) ? fs - n * bs : bs;
int64_t off = n * bs;
char location[512];
snprintf(location, sizeof(location), "%s", rel);
uint8_t sig_msg[92];
size_t soff = 0;
memcpy(sig_msg + soff, result->media_id, 16); soff += 16;
memcpy(sig_msg + soff, result->content_hash, 32); soff += 32;
memcpy(sig_msg + soff, &ts, 8); soff += 8;
memcpy(sig_msg + soff, &fs, 8); soff += 8;
memcpy(sig_msg + soff, &cs, 8); soff += 8;
int32_t cn = (int32_t)n; memcpy(sig_msg + soff, &cn, 4); soff += 4;
memcpy(sig_msg + soff, &off, 8); soff += 8;
memcpy(sig_msg + soff, &node_id, 8); soff += 8;
uint8_t node_sign[64];
if (ma_sign_block(ed25519_privkey, sig_msg, soff, node_sign) != 0) {
DEBUG_ERROR(DEBUG_CATEGORY_DEBUG, "media_index: node_sign failed chunk=%d", n);
return -1;
}
if (mi_insert(db, result->media_id, result->block_ids + n * 16,
result->content_hash, chat_id, location,
node_id, node_sign, ts, fs, cs, n, off) != 0) {
DEBUG_ERROR(DEBUG_CATEGORY_DEBUG, "media_index: insert failed chunk=%d", n);
return -1;
}
}
DEBUG_INFO(DEBUG_CATEGORY_DEBUG, "media_index: committed %d chunks for chat=%s path=%s",
nb, chat_id, rel);
return 0;
}
/* ─── register_async (worker + uasync callback) ─── */
struct mi_reg_ctx {
struct UASYNC* ua;
uint8_t ed25519_privkey[32];
char src_path[1024];
char dest_path[1024];
int copy_file;
uint64_t node_id;
char chat_id[64];
char media_base[512];
sqlite3* db;
void (*cb)(void* arg, int err, const struct media_index_result* result);
void* cb_arg;
struct media_index_result result;
int err;
};
static void mi_reg_work(void* data) {
struct mi_reg_ctx* ctx = (struct mi_reg_ctx*)data;
if (ctx->copy_file && ma_copy_file(ctx->src_path, ctx->dest_path) != 0) {
DEBUG_ERROR(DEBUG_CATEGORY_DEBUG, "media_index: copy failed %s -> %s", ctx->src_path, ctx->dest_path);
ctx->err = -10; return;
}
int64_t fsize = (int64_t)ma_file_size(ctx->dest_path);
if (fsize <= 0) { ctx->err = -20; return; }
ctx->result.file_size = fsize;
int64_t bs = (int64_t)media_calc_block_size((uint64_t)fsize);
int nb = (int)((fsize + bs - 1) / bs);
ctx->result.block_size = bs;
ctx->result.num_blocks = nb;
if (ma_sha256_file(ctx->dest_path, ctx->result.content_hash) != 0) {
ctx->err = -30; return;
}
ma_uuid(ctx->result.media_id);
ctx->result.block_ids = u_malloc((size_t)nb * 16);
ctx->result.block_sigs = u_malloc((size_t)nb * 64);
if (!ctx->result.block_ids || !ctx->result.block_sigs) { ctx->err = -40; return; }
FILE* f = fopen(ctx->dest_path, "rb");
if (!f) { ctx->err = -50; return; }
for (int n = 0; n < nb; n++) {
int64_t off = n * bs;
int64_t sz = (n == nb - 1) ? fsize - off : bs;
uint8_t* buf = u_malloc((size_t)sz);
if (!buf) { fclose(f); ctx->err = -60; return; }
fseeko(f, (off_t)off, SEEK_SET);
size_t rd = fread(buf, 1, (size_t)sz, f);
if (rd != (size_t)sz) { u_free(buf); fclose(f); ctx->err = -70; return; }
ma_uuid(ctx->result.block_ids + n * 16);
if (ma_sign_block(ctx->ed25519_privkey, buf, (size_t)sz,
ctx->result.block_sigs + n * 64) != 0) {
u_free(buf); fclose(f); ctx->err = -80; return;
}
u_free(buf);
}
fclose(f);
ctx->err = 0;
}
static void mi_reg_done(void* arg, int err) {
(void)err;
struct mi_reg_ctx* ctx = (struct mi_reg_ctx*)arg;
if (ctx->err == 0) {
int rc = media_index_commit(ctx->db, &ctx->result, ctx->node_id,
ctx->ed25519_privkey, ctx->chat_id,
ctx->dest_path, ctx->media_base);
if (rc != 0) ctx->err = -90;
}
if (ctx->cb) ctx->cb(ctx->cb_arg, ctx->err, ctx->err ? NULL : &ctx->result);
media_index_result_free(&ctx->result);
u_free(ctx);
}
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) {
if (!ma || !ua || !db || !ed25519_privkey || !chat_id || !src_path || !dest_path || !media_base || !cb) {
DEBUG_ERROR(DEBUG_CATEGORY_DEBUG, "media_index: register_async invalid args");
if (cb) cb(cb_arg, -1, NULL);
return;
}
struct mi_reg_ctx* ctx = u_calloc(1, sizeof(*ctx));
if (!ctx) { cb(cb_arg, -1, NULL); return; }
ctx->ua = ua;
ctx->db = db;
ctx->node_id = node_id;
ctx->copy_file = copy_file;
ctx->cb = cb;
ctx->cb_arg = cb_arg;
memcpy(ctx->ed25519_privkey, ed25519_privkey, 32);
snprintf(ctx->src_path, sizeof(ctx->src_path), "%s", src_path);
snprintf(ctx->dest_path, sizeof(ctx->dest_path), "%s", dest_path);
snprintf(ctx->chat_id, sizeof(ctx->chat_id), "%s", chat_id);
snprintf(ctx->media_base, sizeof(ctx->media_base), "%s", media_base);
media_async_submit(ma, ua, mi_reg_work, ctx, mi_reg_done, ctx);
}
+52
View File
@@ -0,0 +1,52 @@
// media_index.h — таблица media_files + регистрация медиа с async-обработкой
#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);
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);
#ifdef __cplusplus
}
#endif
#endif
+1
View File
@@ -41,6 +41,7 @@ extern "C" {
#define ETCP_RT_ID_TCP_PROXY 0x04 // TCP proxy (клиент ↔ exit)
#define ETCP_RT_ID_UDP_PROXY 0x05 // UDP datagram прокси (client ↔ exit)
#define ETCP_RT_ID_ICMP_PROXY 0x06 // ICMP echo прокси (ping через exit)
#define ETCP_RT_ID_MEDIA_DELIVERY 0x07 // распространение медиа (аудио/видео стриминг)
#define ETCP_RT_ID_CONN_MGR 0x11 // Connection Manager — management connections
+20
View File
@@ -509,6 +509,15 @@ void utun_instance_destroy(struct UTUN_INSTANCE *instance) {
// Cleanup routing module (unbinds from etcp_router before etcp_router_destroy)
routing_destroy(instance);
// Cleanup media delivery (unbinds from etcp_router before etcp_router_destroy)
if (instance->md.initialized) {
media_delivery_destroy(instance);
}
// Cleanup media async engine
media_async_destroy(instance->media_async);
instance->media_async = NULL;
// Cleanup NAT (unbinds from etcp_router before etcp_router_destroy)
if (instance->nat_tr.initialized) {
nat_transport_destroy(instance);
@@ -636,6 +645,17 @@ int utun_instance_init(struct UTUN_INSTANCE *instance) {
DEBUG_WARN(DEBUG_CATEGORY_NAT, "NAT transport init failed (non-fatal)");
}
}
// Initialize media delivery
if (media_delivery_init(instance) != 0) {
DEBUG_WARN(DEBUG_CATEGORY_GENERAL, "media_delivery init failed (non-fatal)");
}
// Initialize media async engine
instance->media_async = media_async_create();
if (!instance->media_async) {
DEBUG_ERROR(DEBUG_CATEGORY_GENERAL, "media_async create failed");
}
// Note: TUN socket is already registered in tun_init()
+8
View File
@@ -18,6 +18,8 @@ extern "C" {
#include "firewall.h"
#include "eim_nat.h"
#include "nat_transport.h"
#include "media_delivery/media_delivery.h"
#include "media_async/media_async.h"
#include "proxy/tcp_proxy_client.h"
#include "etcp_router.h"
#include "proxy/tcp_proxy_server.h"
@@ -144,6 +146,12 @@ struct UTUN_INSTANCE {
// EIM NAT
struct eim_nat_ctx nat;
struct nat_transport_ctx nat_tr;
// Media delivery
struct media_delivery_ctx md;
// Media async engine (thread-per-task for crypto/file ops)
struct media_async* media_async;
// Socket initialization status: 0=OK, 1=partial (some sockets failed), -1=error (none created)
int socket_init_status;