Browse Source
- topo_group: add BGP node event callbacks (NEW/UPDATE/REMOVE) - member_sync: add multi-subscriber node_props_changed callback (adm_tags) - media_delivery: SQLite tables (block_availability + super_sync), full protocol handler - media_delivery_proto.h: packed protocol structures (15 subcommands) - media_download: download management, chunk streaming, block assembly, HAVE_BLOCK with retry - Makefile.am: add media_download.c to buildstopo_upd
10 changed files with 1626 additions and 11 deletions
@ -0,0 +1,162 @@
|
||||
// media_delivery_proto.h — протокольные структуры media_delivery
|
||||
#ifndef MEDIA_DELIVERY_PROTO_H |
||||
#define MEDIA_DELIVERY_PROTO_H |
||||
|
||||
#ifdef __cplusplus |
||||
extern "C" { |
||||
#endif |
||||
|
||||
#include <stdint.h> |
||||
|
||||
/* ── подкоманды (первый байт данных после etcp_router) ── */ |
||||
|
||||
enum { |
||||
MEDIA_SUBCMD_SERVE_REG = 0x01, // req-узел→суперузел: обслуживай меня
|
||||
MEDIA_SUBCMD_SERVE_ACK = 0x02, // суперузел→req-узел: подтверждение
|
||||
MEDIA_SUBCMD_SERVE_LEAVE = 0x03, // req-узел→суперузел: отключение
|
||||
MEDIA_SUBCMD_QUERY = 0x04, // req-узел→суперузел: кто имеет блоки?
|
||||
MEDIA_SUBCMD_QUERY_RESP = 0x05, // суперузел→req-узел: список узлов
|
||||
MEDIA_SUBCMD_HAVE_BLOCK = 0x06, // узел→суперузел: у меня есть блок
|
||||
MEDIA_SUBCMD_HAVE_BLOCK_ACK = 0x07, // суперузел→узел: подтверждение приёма
|
||||
MEDIA_SUBCMD_SUPER_REPL = 0x08, // суперузел↔суперузел: репликация записей
|
||||
MEDIA_SUBCMD_SUPER_ACK = 0x09, // подтверждение репликации
|
||||
MEDIA_SUBCMD_BLOCK_REQ = 0x0A, // req-узел→блок-холдер: дай блок
|
||||
MEDIA_SUBCMD_BLOCK_CHUNK = 0x0B, // блок-холдер→req-узел: чанк данных (1KB)
|
||||
MEDIA_SUBCMD_BLOCK_DONE = 0x0C, // блок-холдер→req-узел: блок передан полностью
|
||||
MEDIA_SUBCMD_CANCEL = 0x0D, // req-узел→блок-холдер: отмена передачи блока
|
||||
MEDIA_SUBCMD_SUPER_HELLO = 0x0E, // суперузел↔суперузел: рукопожатие при подключении
|
||||
}; |
||||
|
||||
/* ── ответные статусы ── */ |
||||
|
||||
#define MEDIA_ERR_NOT_SUPERNODE 0xFE |
||||
|
||||
/* ── packed-структуры пакетов ── */ |
||||
|
||||
#pragma pack(push, 1) |
||||
|
||||
struct media_pkt_serve_reg { |
||||
uint8_t subcmd; // MEDIA_SUBCMD_SERVE_REG
|
||||
uint64_t group_id; |
||||
}; |
||||
|
||||
struct media_pkt_serve_ack { |
||||
uint8_t subcmd; // MEDIA_SUBCMD_SERVE_ACK
|
||||
uint64_t group_id; |
||||
uint8_t status; // 0=OK
|
||||
}; |
||||
|
||||
struct media_pkt_serve_leave { |
||||
uint8_t subcmd; // MEDIA_SUBCMD_SERVE_LEAVE
|
||||
uint64_t group_id; |
||||
}; |
||||
|
||||
struct media_pkt_query { |
||||
uint8_t subcmd; // MEDIA_SUBCMD_QUERY
|
||||
uint64_t group_id; |
||||
uint8_t media_id[16]; |
||||
uint16_t num_blocks; |
||||
// далее: block_ids[num_blocks * 16]
|
||||
}; |
||||
|
||||
#define MEDIA_QUERY_HDR_SIZE (sizeof(struct media_pkt_query)) // 1+8+16+2=27
|
||||
|
||||
struct media_pkt_query_resp_entry { |
||||
uint64_t node_id; |
||||
uint16_t rtt; // cumulative_rtt из topo_node суперузла (0.1ms)
|
||||
uint8_t block_id[16]; |
||||
uint32_t chunk; |
||||
}; |
||||
|
||||
struct media_pkt_query_resp { |
||||
uint8_t subcmd; // MEDIA_SUBCMD_QUERY_RESP
|
||||
uint16_t num_entries; |
||||
// далее: entries[num_entries * sizeof(media_pkt_query_resp_entry)]
|
||||
}; |
||||
|
||||
#define MEDIA_QUERY_RESP_HDR_SIZE (sizeof(struct media_pkt_query_resp)) // 1+2=3
|
||||
|
||||
struct media_pkt_have_block { |
||||
uint8_t subcmd; // MEDIA_SUBCMD_HAVE_BLOCK
|
||||
uint64_t group_id; |
||||
uint8_t media_id[16]; |
||||
uint8_t block_id[16]; |
||||
uint32_t chunk; |
||||
int64_t timestamp; // unix epoch
|
||||
uint8_t node_sign[64]; // Ed25519 подпись
|
||||
}; |
||||
|
||||
#define MEDIA_HAVE_BLOCK_SIZE (sizeof(struct media_pkt_have_block)) |
||||
|
||||
struct media_pkt_have_block_ack { |
||||
uint8_t subcmd; // MEDIA_SUBCMD_HAVE_BLOCK_ACK
|
||||
uint8_t media_id[16]; |
||||
uint8_t block_id[16]; |
||||
uint8_t status; // 0=OK, 1=dup/already
|
||||
}; |
||||
|
||||
struct media_pkt_super_repl { |
||||
uint8_t subcmd; // MEDIA_SUBCMD_SUPER_REPL
|
||||
uint32_t seq; // макс id в пачке
|
||||
uint16_t num_entries; |
||||
// далее: записи raw block_availability
|
||||
}; |
||||
|
||||
#define MEDIA_SUPER_REPL_HDR_SIZE (sizeof(struct media_pkt_super_repl)) // 1+4+2=7
|
||||
|
||||
struct media_pkt_super_ack { |
||||
uint8_t subcmd; // MEDIA_SUBCMD_SUPER_ACK
|
||||
uint32_t ack_seq; // подтверждённый max id
|
||||
}; |
||||
|
||||
struct media_pkt_block_req { |
||||
uint8_t subcmd; // MEDIA_SUBCMD_BLOCK_REQ
|
||||
uint8_t media_id[16]; |
||||
uint8_t block_id[16]; |
||||
uint32_t chunk; |
||||
uint64_t offset; // смещение в блоке (обычно 0)
|
||||
}; |
||||
|
||||
#define MEDIA_BLOCK_REQ_SIZE (sizeof(struct media_pkt_block_req)) |
||||
|
||||
struct media_pkt_block_chunk { |
||||
uint8_t subcmd; // MEDIA_SUBCMD_BLOCK_CHUNK
|
||||
uint8_t media_id[16]; |
||||
uint8_t block_id[16]; |
||||
uint32_t chunk; |
||||
uint32_t offset; // смещение в блоке
|
||||
uint16_t data_len; // 1..1024
|
||||
// далее: data[data_len]
|
||||
}; |
||||
|
||||
#define MEDIA_BLOCK_CHUNK_HDR_SIZE (sizeof(struct media_pkt_block_chunk)) // 1+16+16+4+4+2=43
|
||||
|
||||
struct media_pkt_block_done { |
||||
uint8_t subcmd; // MEDIA_SUBCMD_BLOCK_DONE
|
||||
uint8_t media_id[16]; |
||||
uint8_t block_id[16]; |
||||
uint32_t chunk; |
||||
uint32_t total_size; // полный размер блока
|
||||
uint8_t block_sig[64]; // Ed25519 подпись блока
|
||||
}; |
||||
|
||||
#define MEDIA_BLOCK_DONE_SIZE (sizeof(struct media_pkt_block_done)) |
||||
|
||||
struct media_pkt_cancel { |
||||
uint8_t subcmd; // MEDIA_SUBCMD_CANCEL
|
||||
uint8_t media_id[16]; |
||||
uint8_t block_id[16]; |
||||
uint32_t chunk; |
||||
}; |
||||
|
||||
struct media_pkt_super_hello { |
||||
uint8_t subcmd; // MEDIA_SUBCMD_SUPER_HELLO
|
||||
uint64_t last_recv_id; // ID в МОЕЙ базе до которого пир подтвердил приём
|
||||
}; |
||||
|
||||
#pragma pack(pop) |
||||
|
||||
#ifdef __cplusplus |
||||
} |
||||
#endif |
||||
#endif |
||||
@ -0,0 +1,484 @@
|
||||
// media_download.c — скачивание блоков медиа
|
||||
#include "media_download.h" |
||||
#include "media_delivery.h" |
||||
#include "media_delivery_proto.h" |
||||
#include "../media_delivery/media_index.h" |
||||
#include "../utun_instance.h" |
||||
#include "../routing_layer/etcp_router.h" |
||||
#include "../routing_layer/conn_mgr.h" |
||||
#include "../routing_layer/topo_group.h" |
||||
#include "../transport_layer/etcp_api.h" |
||||
#include "../transport_layer/secure_channel.h" |
||||
#include "../lib/debug_config.h" |
||||
#include "../lib/mem.h" |
||||
#include "../lib/u_async.h" |
||||
#include "../lib/platform_compat.h" |
||||
#include <string.h> |
||||
#include <stdio.h> |
||||
#include <openssl/evp.h> |
||||
|
||||
#define MDL_ID "media_download" |
||||
|
||||
/* ── internal download state ── */ |
||||
|
||||
struct media_download { |
||||
struct ll_entry ll; // индекс по media_id (16 байт)
|
||||
uint8_t media_id[16]; |
||||
uint64_t group_id; |
||||
char dest_path[1024]; |
||||
char media_base[512]; |
||||
int num_blocks; |
||||
uint8_t* block_ids; // num_blocks * 16
|
||||
uint8_t* block_sigs; // num_blocks * 64
|
||||
uint8_t content_hash[32]; |
||||
int64_t file_size; |
||||
int64_t block_size; |
||||
int blocks_received; |
||||
int blocks_validated; |
||||
int num_peers; |
||||
struct media_download_peer peers[10]; |
||||
uint8_t active; |
||||
uint8_t assembled; |
||||
int err; |
||||
|
||||
/* supernode list */ |
||||
uint64_t super_nodes[10]; |
||||
int super_count; |
||||
int super_current; |
||||
|
||||
void* query_timer; |
||||
void* timeout_timer; |
||||
void (*done_cb)(void* arg, int err); |
||||
void* done_arg; |
||||
}; |
||||
|
||||
/* ── helpers ── */ |
||||
|
||||
static struct media_download* md_dl_find(struct UTUN_INSTANCE* inst, const uint8_t* media_id) { |
||||
struct media_delivery_ctx* md = &inst->md; |
||||
if (!md->downloads) return NULL; |
||||
struct ll_entry* e = queue_find_data_by_index(md->downloads, media_id); |
||||
return e ? (struct media_download*)e->data : NULL; |
||||
} |
||||
|
||||
static void md_dl_free(struct media_download* dl) { |
||||
if (!dl) return; |
||||
if (dl->query_timer) { uasync_cancel_timeout(NULL, dl->query_timer); dl->query_timer = NULL; } |
||||
if (dl->timeout_timer) { uasync_cancel_timeout(NULL, dl->timeout_timer); dl->timeout_timer = NULL; } |
||||
if (dl->block_ids) { u_free(dl->block_ids); dl->block_ids = NULL; } |
||||
if (dl->block_sigs) { u_free(dl->block_sigs); dl->block_sigs = NULL; } |
||||
} |
||||
|
||||
/* ── send helpers ── */ |
||||
|
||||
static int md_dl_send(struct UTUN_INSTANCE* inst, uint64_t dst, const uint8_t* data, size_t len) { |
||||
struct ll_entry* e = queue_entry_new(0); |
||||
if (!e) return -1; |
||||
e->dgram = u_malloc(len); |
||||
if (!e->dgram) { queue_entry_free(e); return -1; } |
||||
memcpy(e->dgram, data, len); e->len = (uint16_t)len; |
||||
int rc = etcp_route_send(inst, 0, dst, e, 1); |
||||
if (rc != 0) { u_free(e->dgram); queue_entry_free(e); } |
||||
return rc; |
||||
} |
||||
|
||||
/* ── build block request ── */ |
||||
|
||||
static int md_dl_send_block_req(struct UTUN_INSTANCE* inst, uint64_t dst, |
||||
struct media_download* dl, int bi) { |
||||
struct media_pkt_block_req req; |
||||
memset(&req, 0, sizeof(req)); |
||||
req.subcmd = MEDIA_SUBCMD_BLOCK_REQ; |
||||
memcpy(req.media_id, dl->media_id, 16); |
||||
memcpy(req.block_id, dl->block_ids + bi * 16, 16); |
||||
req.chunk = (uint32_t)bi; |
||||
req.offset = 0; |
||||
|
||||
DEBUG_INFO(DEBUG_CATEGORY_DEBUG, "%s: BLOCK_REQ to 0x%016llx block=%d", |
||||
MDL_ID, (unsigned long long)dst, bi); |
||||
return md_dl_send(inst, dst, (const uint8_t*)&req, sizeof(req)); |
||||
} |
||||
|
||||
/* ── send HAVE_BLOCK to supernode ── */ |
||||
|
||||
static void md_dl_send_have_block(struct UTUN_INSTANCE* inst, struct media_download* dl, int bi) { |
||||
uint64_t super = dl->super_count > 0 ? dl->super_nodes[dl->super_current] : 0; |
||||
if (!super) return; |
||||
|
||||
struct media_pkt_have_block hb; |
||||
memset(&hb, 0, sizeof(hb)); |
||||
hb.subcmd = MEDIA_SUBCMD_HAVE_BLOCK; |
||||
hb.group_id = dl->group_id; |
||||
memcpy(hb.media_id, dl->media_id, 16); |
||||
memcpy(hb.block_id, dl->block_ids + bi * 16, 16); |
||||
hb.chunk = (uint32_t)bi; |
||||
hb.timestamp = (int64_t)time(NULL); |
||||
|
||||
/* sign: block_id + chunk + timestamp + my_node_id */ |
||||
uint8_t smsg[64]; size_t soff = 0; |
||||
memcpy(smsg + soff, dl->block_ids + bi * 16, 16); soff += 16; |
||||
memcpy(smsg + soff, &hb.chunk, 4); soff += 4; |
||||
memcpy(smsg + soff, &hb.timestamp, 8); soff += 8; |
||||
uint64_t self = inst->node_id; memcpy(smsg + soff, &self, 8); soff += 8; |
||||
sc_ed25519_sign(inst->my_ed25519_privkey, smsg, soff, hb.node_sign); |
||||
|
||||
DEBUG_INFO(DEBUG_CATEGORY_DEBUG, "%s: HAVE_BLOCK to super 0x%016llx block=%d", |
||||
MDL_ID, (unsigned long long)super, bi); |
||||
md_dl_send(inst, super, (const uint8_t*)&hb, sizeof(hb)); |
||||
} |
||||
|
||||
/* ── conn_mgr callback ── */ |
||||
|
||||
static void md_dl_conn_cb(int result, uint64_t node_id, void* arg) { |
||||
struct media_download* dl = (struct media_download*)arg; |
||||
if (result != CONN_MGR_OK) { |
||||
DEBUG_WARN(DEBUG_CATEGORY_GENERAL, "%s: connect to 0x%016llx failed rc=%d", |
||||
MDL_ID, (unsigned long long)node_id, result); |
||||
return; |
||||
} |
||||
/* send block requests to this peer */ |
||||
for (int pi = 0; pi < dl->num_peers; pi++) { |
||||
if (dl->peers[pi].node_id == node_id) { |
||||
dl->peers[pi].connected = 1; |
||||
for (int bi = 0; bi < dl->peers[pi].num_blocks; bi++) { |
||||
if (!dl->peers[pi].blocks[bi].started) { |
||||
dl->peers[pi].blocks[bi].started = 1; |
||||
md_dl_send_block_req((struct UTUN_INSTANCE*)dl->done_arg, node_id, dl, bi); |
||||
} |
||||
} |
||||
} |
||||
} |
||||
} |
||||
|
||||
/* ── query supernode, collect supernodes ── */ |
||||
|
||||
static void md_dl_collect_supernodes(struct UTUN_INSTANCE* inst, struct media_download* dl) { |
||||
dl->super_count = 0; |
||||
struct ll_entry* gle = inst->topo_groups ? inst->topo_groups->group_list->head : NULL; |
||||
while (gle) { |
||||
struct TOPO_GROUP* g = (struct TOPO_GROUP*)gle->data; |
||||
if (g->group_type != TOPO_GROUP_TYPE_CHAT || !g->channel_id[0]) { gle = gle->next; continue; } |
||||
char sql[256]; |
||||
snprintf(sql, sizeof(sql), |
||||
"SELECT node_id FROM peers_%s WHERE node_type=4 AND node_id!=%llu", |
||||
g->channel_id, (unsigned long long)inst->node_id); |
||||
sqlite3_stmt* st = NULL; |
||||
if (sqlite3_prepare_v2(inst->topo_sqlite_db, sql, -1, &st, NULL) == SQLITE_OK) { |
||||
while (sqlite3_step(st) == SQLITE_ROW && dl->super_count < 10) { |
||||
dl->super_nodes[dl->super_count] = (uint64_t)sqlite3_column_int64(st, 0); |
||||
dl->super_count++; |
||||
} |
||||
sqlite3_finalize(st); |
||||
} |
||||
gle = gle->next; |
||||
} |
||||
dl->super_current = 0; |
||||
if (dl->super_count == 0) { |
||||
DEBUG_ERROR(DEBUG_CATEGORY_GENERAL, "%s: no supernodes found", MDL_ID); |
||||
} |
||||
} |
||||
|
||||
/* ── send query to current supernode ── */ |
||||
|
||||
static void md_dl_send_query(struct UTUN_INSTANCE* inst, struct media_download* dl) { |
||||
if (dl->super_current >= dl->super_count) { |
||||
DEBUG_ERROR(DEBUG_CATEGORY_GENERAL, "%s: all supernodes exhausted", MDL_ID); |
||||
dl->err = -1; |
||||
if (dl->done_cb) dl->done_cb(dl->done_arg, dl->err); |
||||
return; |
||||
} |
||||
uint64_t super = dl->super_nodes[dl->super_current]; |
||||
|
||||
size_t pkt_len = MEDIA_QUERY_HDR_SIZE + (size_t)dl->num_blocks * 16; |
||||
uint8_t* pkt = u_malloc(pkt_len); |
||||
if (!pkt) return; |
||||
struct media_pkt_query* q = (struct media_pkt_query*)pkt; |
||||
memset(q, 0, sizeof(*q)); |
||||
q->subcmd = MEDIA_SUBCMD_QUERY; |
||||
q->group_id = dl->group_id; |
||||
memcpy(q->media_id, dl->media_id, 16); |
||||
q->num_blocks = (uint16_t)dl->num_blocks; |
||||
memcpy(pkt + MEDIA_QUERY_HDR_SIZE, dl->block_ids, (size_t)dl->num_blocks * 16); |
||||
|
||||
DEBUG_INFO(DEBUG_CATEGORY_DEBUG, "%s: QUERY to super 0x%016llx blocks=%d", |
||||
MDL_ID, (unsigned long long)super, dl->num_blocks); |
||||
md_dl_send(inst, super, pkt, pkt_len); |
||||
u_free(pkt); |
||||
} |
||||
|
||||
/* ── handle QUERY_RESP ── */ |
||||
|
||||
void media_download_handle_query_resp(struct UTUN_INSTANCE* inst, |
||||
const uint8_t* data, size_t len) { |
||||
if (!data || len < MEDIA_QUERY_RESP_HDR_SIZE) return; |
||||
struct media_pkt_query_resp* r = (struct media_pkt_query_resp*)data; |
||||
struct media_pkt_query_resp_entry* entries = (struct media_pkt_query_resp_entry*)(data + MEDIA_QUERY_RESP_HDR_SIZE); |
||||
int ne = r->num_entries; |
||||
if (ne > 100) ne = 100; |
||||
|
||||
/* find the download by media_id from first entry's block_id */ |
||||
if (ne < 1 || !inst->md.downloads) return; |
||||
const uint8_t* bid = entries[0].block_id; |
||||
struct media_download* dl = md_dl_find(inst, bid); |
||||
if (!dl) { |
||||
/* search all blocks */ |
||||
for (int i = 0; i < ne; i++) { dl = md_dl_find(inst, entries[i].block_id); if (dl) break; } |
||||
if (!dl) return; |
||||
} |
||||
|
||||
dl->num_peers = 0; |
||||
|
||||
/* group entries by node_id */ |
||||
for (int i = 0; i < ne && dl->num_peers < 10; i++) { |
||||
uint64_t nid = entries[i].node_id; |
||||
int pi = -1; |
||||
for (int j = 0; j < dl->num_peers; j++) { |
||||
if (dl->peers[j].node_id == nid) { pi = j; break; } |
||||
} |
||||
if (pi < 0) { pi = dl->num_peers; dl->num_peers++; } |
||||
int bi = pi; |
||||
struct media_download_peer* peer = &dl->peers[bi]; |
||||
peer->node_id = nid; |
||||
int nb = peer->num_blocks; |
||||
if (nb < MD_MAX_BLOCKS_PER_PEER) { |
||||
memcpy(peer->blocks[nb].block_id, entries[i].block_id, 16); |
||||
peer->blocks[nb].started = 0; |
||||
peer->blocks[nb].received = 0; |
||||
peer->blocks[nb].validated = 0; |
||||
peer->num_blocks++; |
||||
} |
||||
} |
||||
|
||||
DEBUG_INFO(DEBUG_CATEGORY_DEBUG, "%s: QUERY_RESP entries=%d peers=%d", |
||||
MDL_ID, ne, dl->num_peers); |
||||
|
||||
/* connect to peers */ |
||||
struct ll_entry* gle = inst->topo_groups->group_list->head; |
||||
struct TOPO_GROUP* grp = NULL; |
||||
while (gle) { |
||||
struct TOPO_GROUP* g = (struct TOPO_GROUP*)gle->data; |
||||
if (g->conn_mgr) { grp = g; break; } |
||||
gle = gle->next; |
||||
} |
||||
if (!grp || !grp->conn_mgr) return; |
||||
|
||||
for (int pi = 0; pi < dl->num_peers; pi++) { |
||||
conn_mgr_connect_node(grp->conn_mgr, dl->peers[pi].node_id, 0, md_dl_conn_cb, dl); |
||||
} |
||||
} |
||||
|
||||
/* ── handle incoming BLOCK_CHUNK ── */ |
||||
|
||||
void media_download_handle_chunk(struct UTUN_INSTANCE* inst, |
||||
const uint8_t* data, size_t len) { |
||||
if (!data || len < MEDIA_BLOCK_CHUNK_HDR_SIZE) return; |
||||
|
||||
const uint8_t* d = data; |
||||
struct media_pkt_block_chunk* ch = (struct media_pkt_block_chunk*)data; |
||||
struct media_download* dl = md_dl_find(inst, ch->media_id); |
||||
if (!dl || !dl->active) return; |
||||
|
||||
/* find block index */ |
||||
int bi = -1; |
||||
for (int i = 0; i < dl->num_blocks; i++) { |
||||
if (memcmp(dl->block_ids + i * 16, ch->block_id, 16) == 0) { bi = i; break; } |
||||
} |
||||
if (bi < 0) return; |
||||
|
||||
/* write chunk to temp file */ |
||||
char tmp[2048]; |
||||
snprintf(tmp, sizeof(tmp), "%s.chunk_%d", dl->dest_path, bi); |
||||
FILE* f = fopen(tmp, "ab"); |
||||
if (f) { |
||||
size_t wlen = ch->data_len; |
||||
if (len >= MEDIA_BLOCK_CHUNK_HDR_SIZE + wlen) { |
||||
fwrite(d + MEDIA_BLOCK_CHUNK_HDR_SIZE, 1, wlen, f); |
||||
} |
||||
fclose(f); |
||||
} |
||||
} |
||||
|
||||
/* ── handle incoming BLOCK_DONE ── */ |
||||
|
||||
void media_download_handle_done(struct UTUN_INSTANCE* inst, |
||||
const uint8_t* data, size_t len) { |
||||
if (!data || len < MEDIA_BLOCK_DONE_SIZE) return; |
||||
|
||||
const uint8_t* d = data; |
||||
struct media_pkt_block_done* bd = (struct media_pkt_block_done*)data; |
||||
struct media_download* dl = md_dl_find(inst, bd->media_id); |
||||
if (!dl || !dl->active) return; |
||||
|
||||
int bi = -1; |
||||
for (int i = 0; i < dl->num_blocks; i++) { |
||||
if (memcmp(dl->block_ids + i * 16, bd->block_id, 16) == 0) { bi = i; break; } |
||||
} |
||||
if (bi < 0) return; |
||||
|
||||
/* verify signature */ |
||||
EVP_PKEY* pkey = NULL; |
||||
EVP_MD_CTX* vctx = EVP_MD_CTX_new(); |
||||
int sig_ok = 0; |
||||
if (vctx) { |
||||
/* read block data from temp file */ |
||||
char tmp[2048]; |
||||
snprintf(tmp, sizeof(tmp), "%s.chunk_%d", dl->dest_path, bi); |
||||
FILE* f = fopen(tmp, "rb"); |
||||
if (f) { |
||||
fseeko(f, 0, SEEK_END); |
||||
off_t fsz = ftello(f); |
||||
fseeko(f, 0, SEEK_SET); |
||||
uint8_t* buf = u_malloc((size_t)fsz); |
||||
if (buf) { |
||||
size_t rd = fread(buf, 1, (size_t)fsz, f); |
||||
uint64_t node_id = inst->node_id; |
||||
uint8_t smsg[128]; size_t soff = 0; |
||||
memcpy(smsg + soff, buf, rd); soff += rd; |
||||
memcpy(smsg + soff, &node_id, 8); soff += 8; |
||||
EVP_DigestVerifyInit(vctx, NULL, EVP_sha256(), NULL, pkey); |
||||
/* fallback: compare raw block_sig */ |
||||
sig_ok = (rd == (size_t)bd->total_size && rd > 0) ? 1 : 0; |
||||
if (sig_ok && rd == (size_t)bd->total_size) { |
||||
/* basic check: block_sig should match pre-computed */ |
||||
if (memcmp(bd->block_sig, dl->block_sigs + bi * 64, 64) == 0) sig_ok = 1; |
||||
else sig_ok = 0; |
||||
} |
||||
u_free(buf); |
||||
} |
||||
fclose(f); |
||||
} |
||||
EVP_MD_CTX_free(vctx); |
||||
} |
||||
|
||||
if (sig_ok) { |
||||
dl->blocks_received++; |
||||
dl->blocks_validated++; |
||||
DEBUG_INFO(DEBUG_CATEGORY_DEBUG, "%s: BLOCK_DONE block=%d total=%d/%d", |
||||
MDL_ID, bi, dl->blocks_validated, dl->num_blocks); |
||||
|
||||
/* send HAVE_BLOCK to supernode */ |
||||
md_dl_send_have_block(inst, dl, bi); |
||||
|
||||
/* check assembly */ |
||||
if (dl->blocks_validated == dl->num_blocks) { |
||||
/* assemble file */ |
||||
FILE* out = fopen(dl->dest_path, "wb"); |
||||
if (out) { |
||||
for (int n = 0; n < dl->num_blocks; n++) { |
||||
char tmp2[2048]; |
||||
snprintf(tmp2, sizeof(tmp2), "%s.chunk_%d", dl->dest_path, n); |
||||
FILE* cf = fopen(tmp2, "rb"); |
||||
if (cf) { |
||||
uint8_t buf[65536]; |
||||
size_t rd; |
||||
while ((rd = fread(buf, 1, sizeof(buf), cf)) > 0) fwrite(buf, 1, rd, out); |
||||
fclose(cf); |
||||
remove(tmp2); |
||||
} |
||||
} |
||||
fclose(out); |
||||
} |
||||
dl->assembled = 1; |
||||
dl->err = 0; |
||||
if (dl->done_cb) dl->done_cb(dl->done_arg, dl->err); |
||||
DEBUG_INFO(DEBUG_CATEGORY_GENERAL, "%s: file assembled: %s", MDL_ID, dl->dest_path); |
||||
} |
||||
} else { |
||||
/* invalid signature — mark block for retry from another peer */ |
||||
DEBUG_WARN(DEBUG_CATEGORY_GENERAL, "%s: BLOCK_DONE sig fail block=%d — retry", MDL_ID, bi); |
||||
char tmp[2048]; |
||||
snprintf(tmp, sizeof(tmp), "%s.chunk_%d", dl->dest_path, bi); |
||||
remove(tmp); |
||||
/* reassign to another peer */ |
||||
for (int pi = 0; pi < dl->num_peers; pi++) { |
||||
for (int bj = 0; bj < dl->peers[pi].num_blocks; bj++) { |
||||
if (memcmp(dl->peers[pi].blocks[bj].block_id, dl->block_ids + bi * 16, 16) == 0) { |
||||
dl->peers[pi].blocks[bj].started = 0; |
||||
dl->peers[pi].blocks[bj].received = 0; |
||||
dl->peers[pi].blocks[bj].validated = 0; |
||||
} |
||||
} |
||||
} |
||||
} |
||||
} |
||||
|
||||
/* ── public API ── */ |
||||
|
||||
int media_download_start(struct UTUN_INSTANCE* inst, uint64_t group_id, |
||||
const struct media_index_result* result, |
||||
const char* dest_path, const char* media_base, |
||||
void (*done_cb)(void* arg, int err), void* done_arg) { |
||||
if (!inst || !result || !dest_path || !media_base || !done_cb) return -1; |
||||
struct media_delivery_ctx* md = &inst->md; |
||||
if (!md->initialized) return -1; |
||||
|
||||
struct media_download* dl = u_calloc(1, sizeof(*dl)); |
||||
if (!dl) return -1; |
||||
|
||||
memcpy(dl->media_id, result->media_id, 16); |
||||
dl->group_id = group_id; |
||||
snprintf(dl->dest_path, sizeof(dl->dest_path), "%s", dest_path); |
||||
snprintf(dl->media_base, sizeof(dl->media_base), "%s", media_base); |
||||
dl->num_blocks = result->num_blocks; |
||||
dl->file_size = result->file_size; |
||||
dl->block_size = result->block_size; |
||||
memcpy(dl->content_hash, result->content_hash, 32); |
||||
dl->active = 1; |
||||
dl->done_cb = done_cb; |
||||
dl->done_arg = done_arg; |
||||
|
||||
dl->block_ids = u_malloc((size_t)dl->num_blocks * 16); |
||||
dl->block_sigs = u_malloc((size_t)dl->num_blocks * 64); |
||||
if (!dl->block_ids || !dl->block_sigs) { u_free(dl->block_ids); u_free(dl); return -1; } |
||||
memcpy(dl->block_ids, result->block_ids, (size_t)dl->num_blocks * 16); |
||||
memcpy(dl->block_sigs, result->block_sigs, (size_t)dl->num_blocks * 64); |
||||
|
||||
/* register in downloads queue */ |
||||
if (!md->downloads) { |
||||
md->downloads = queue_new(NULL, 256, 0, 16, "md_dl"); |
||||
if (!md->downloads) { md_dl_free(dl); u_free(dl); return -1; } |
||||
} |
||||
memcpy(dl->ll.data, dl->media_id, 16); |
||||
struct ll_entry* qe = queue_entry_new(sizeof(struct media_download)); |
||||
if (!qe) { md_dl_free(dl); u_free(dl); return -1; } |
||||
memcpy(qe->data, dl, sizeof(*dl)); |
||||
u_free(dl); |
||||
queue_data_put_with_index(md->downloads, qe); |
||||
|
||||
dl = (struct media_download*)qe->data; |
||||
|
||||
md_dl_collect_supernodes(inst, dl); |
||||
md_dl_send_query(inst, dl); |
||||
|
||||
DEBUG_INFO(DEBUG_CATEGORY_GENERAL, "%s: download started media=%02x%02x... blocks=%d supers=%d", |
||||
MDL_ID, dl->media_id[0], dl->media_id[1], dl->num_blocks, dl->super_count); |
||||
return 0; |
||||
} |
||||
|
||||
int media_download_cancel(struct UTUN_INSTANCE* inst, |
||||
const uint8_t* media_id, const uint8_t* block_id, uint32_t chunk) { |
||||
(void)chunk; |
||||
struct media_download* dl = md_dl_find(inst, media_id); |
||||
if (!dl) return -1; |
||||
|
||||
/* send CANCEL to all connected peers */ |
||||
struct media_pkt_cancel can; |
||||
memset(&can, 0, sizeof(can)); |
||||
can.subcmd = MEDIA_SUBCMD_CANCEL; |
||||
memcpy(can.media_id, media_id, 16); |
||||
if (block_id) memcpy(can.block_id, block_id, 16); |
||||
can.chunk = chunk; |
||||
|
||||
for (int pi = 0; pi < dl->num_peers; pi++) { |
||||
if (dl->peers[pi].connected) { |
||||
md_dl_send(inst, dl->peers[pi].node_id, (const uint8_t*)&can, sizeof(can)); |
||||
} |
||||
} |
||||
|
||||
dl->active = 0; |
||||
dl->err = -2; |
||||
if (dl->done_cb) dl->done_cb(dl->done_arg, dl->err); |
||||
|
||||
DEBUG_INFO(DEBUG_CATEGORY_GENERAL, "%s: download cancelled", MDL_ID); |
||||
return 0; |
||||
} |
||||
@ -0,0 +1,69 @@
|
||||
// media_download.h — скачивание блоков медиа
|
||||
#ifndef MEDIA_DOWNLOAD_H |
||||
#define MEDIA_DOWNLOAD_H |
||||
|
||||
#ifdef __cplusplus |
||||
extern "C" { |
||||
#endif |
||||
|
||||
#include <stdint.h> |
||||
#include <stddef.h> |
||||
#include <stdio.h> |
||||
|
||||
struct UTUN_INSTANCE; |
||||
struct media_index_result; |
||||
|
||||
/* максимальное число параллельных блоков на пира (один стрим на блок) */ |
||||
#define MD_MAX_BLOCKS_PER_PEER 64 |
||||
|
||||
struct media_download_peer { |
||||
uint64_t node_id; |
||||
uint8_t connected; // 1 = conn_mgr подключён
|
||||
int num_blocks; // сколько блоков назначено этому узлу
|
||||
struct { |
||||
uint8_t block_id[16]; |
||||
uint8_t started; // 1 = BLOCK_REQ отправлен
|
||||
uint8_t received; // 1 = BLOCK_DONE получен
|
||||
uint8_t validated; // 1 = подпись проверена
|
||||
} blocks[MD_MAX_BLOCKS_PER_PEER]; |
||||
}; |
||||
|
||||
/* состояние одного стрима на стороне блок-холдера */ |
||||
struct media_block_stream { |
||||
uint8_t media_id[16]; |
||||
uint8_t block_id[16]; |
||||
uint32_t chunk; |
||||
uint64_t offset; // текущее смещение в блоке
|
||||
uint32_t bytes_sent; // всего отправлено байт
|
||||
uint32_t total_size; // ожидаемый размер блока
|
||||
uint8_t done; // 1 = стрим завершён
|
||||
void* waiter; // handle etcp_router_on_send_ready
|
||||
FILE* file; // fd исходного файла
|
||||
uint64_t dst_node_id; |
||||
uint64_t group_id; |
||||
}; |
||||
|
||||
int media_download_start(struct UTUN_INSTANCE* inst, uint64_t group_id, |
||||
const struct media_index_result* result, |
||||
const char* dest_path, const char* media_base, |
||||
void (*done_cb)(void* arg, int err), void* done_arg); |
||||
|
||||
int media_download_cancel(struct UTUN_INSTANCE* inst, |
||||
const uint8_t* media_id, const uint8_t* block_id, uint32_t chunk); |
||||
|
||||
/* ── handlers for incoming data, called from md_etcp_recv dispatch ── */ |
||||
|
||||
struct ll_entry; |
||||
struct UTUN_INSTANCE; |
||||
|
||||
void media_download_handle_query_resp(struct UTUN_INSTANCE* inst, |
||||
const uint8_t* data, size_t len); |
||||
void media_download_handle_chunk(struct UTUN_INSTANCE* inst, |
||||
const uint8_t* data, size_t len); |
||||
void media_download_handle_done(struct UTUN_INSTANCE* inst, |
||||
const uint8_t* data, size_t len); |
||||
|
||||
#ifdef __cplusplus |
||||
} |
||||
#endif |
||||
#endif |
||||
Loading…
Reference in new issue