You can not select more than 25 topics
Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.
791 lines
29 KiB
791 lines
29 KiB
#include "chat_sync.h" |
|
#include "gui_bridge.h" |
|
|
|
#include "../../../src/utun_instance.h" |
|
#include "../../../src/etcp_router.h" |
|
#include "../../../src/etcp_api.h" |
|
#include "../../../src/etcp.h" |
|
#include "../../../src/conn_mgr.h" |
|
#include "../../../src/route_bgp.h" |
|
#include "../../../src/route_node.h" |
|
#include "../../../lib/u_async.h" |
|
#include "../../../lib/ll_queue.h" |
|
#include "../../../lib/debug_config.h" |
|
#include "../../../lib/mem.h" |
|
#include "../../../lib/sha256.h" |
|
#include "../../../lib/platform_compat.h" |
|
|
|
#include <string.h> |
|
|
|
static struct chat_sync* g_cs = NULL; |
|
|
|
/* ── Per-channel cache ── */ |
|
|
|
struct channel_cache { |
|
char channel_id[64]; |
|
uint64_t* peer_ids; |
|
int peer_count; |
|
uint32_t msg_count; |
|
uint8_t last_chain_hash[32]; |
|
uint8_t synced; |
|
}; |
|
|
|
struct chat_sync { |
|
struct UTUN_INSTANCE* inst; |
|
struct channel_cache* channels; |
|
int channel_count; |
|
void* refresh_timer; |
|
void* ttl_timer; |
|
uint8_t initialized; |
|
}; |
|
|
|
#define CS_ID "chat_sync" |
|
|
|
/* ── Send ── */ |
|
|
|
static int cs_send(struct chat_sync* cs, const char* ch_id, uint64_t dst, |
|
const uint8_t* payload, size_t len) { |
|
uint8_t ch_len = (uint8_t)strlen(ch_id); |
|
if (ch_len > 63) return -1; |
|
size_t total = 1 + 1 + ch_len + len; |
|
uint8_t* buf = u_malloc(total); |
|
if (!buf) return -1; |
|
uint8_t* p = buf; |
|
*p++ = ETCP_RT_ID_CHAT_SYNC; |
|
*p++ = ch_len; |
|
memcpy(p, ch_id, ch_len); p += ch_len; |
|
memcpy(p, payload, len); |
|
struct ll_entry* entry = queue_entry_new(0); |
|
if (!entry) { u_free(buf); return -1; } |
|
entry->dgram = buf; entry->len = total; |
|
return etcp_route_send(cs->inst, dst, entry, 0); |
|
} |
|
|
|
/* ── Channel cache ── */ |
|
|
|
static struct channel_cache* cs_find(struct chat_sync* cs, const char* ch_id) { |
|
for (int i = 0; i < cs->channel_count; i++) |
|
if (strcmp(cs->channels[i].channel_id, ch_id) == 0) return &cs->channels[i]; |
|
return NULL; |
|
} |
|
|
|
/* ── Protocol message context (async state machine) ── */ |
|
|
|
struct cs_ctx { |
|
struct chat_sync* sync; |
|
uint64_t peer; |
|
char ch_id[64]; |
|
union { |
|
struct { |
|
uint32_t peer_count; |
|
uint8_t peer_last_ch[32]; |
|
uint32_t my_count; |
|
uint32_t test_pos; |
|
} init_sync; |
|
struct { |
|
uint32_t peer_count; |
|
uint32_t test_pos; |
|
uint8_t peer_ch[37]; /* 1 byte for sparse_count + 36 for first sparse */ |
|
int parse_off; /* offset in original payload for sparse data */ |
|
size_t parse_len; |
|
} init_resp; |
|
struct { |
|
uint32_t cursor_id; |
|
uint32_t from_pos; |
|
uint16_t sent_count; |
|
} send_data; |
|
struct { |
|
uint64_t ts; |
|
uint64_t dh; |
|
} push; |
|
}; |
|
}; |
|
|
|
static struct cs_ctx* cs_ctx_new(struct chat_sync* cs, uint64_t peer, const char* ch_id) { |
|
struct cs_ctx* ctx = u_calloc(1, sizeof(*ctx)); |
|
if (!ctx) return NULL; |
|
ctx->sync = cs; ctx->peer = peer; |
|
size_t n = strlen(ch_id); if (n > 63) n = 63; |
|
memcpy(ctx->ch_id, ch_id, n); |
|
return ctx; |
|
} |
|
|
|
/* ── gui_bridge async callbacks ── */ |
|
|
|
static void on_init_sync_count(void* ud, int op, const uint8_t* r, int rl); |
|
static void on_init_sync_hash(void* ud, int op, const uint8_t* r, int rl); |
|
static void on_init_sync_sparse(void* ud, int op, const uint8_t* r, int rl); |
|
static void on_init_resp_hash(void* ud, int op, const uint8_t* r, int rl); |
|
static void on_send_data_cursor_open(void* ud, int op, const uint8_t* r, int rl); |
|
static void on_send_data_cursor_next(void* ud, int op, const uint8_t* r, int rl); |
|
static void on_push_inserted(void* ud, int op, const uint8_t* r, int rl); |
|
static void on_refresh_list(void* ud, int op, const uint8_t* r, int rl); |
|
static void on_refresh_peers_and_count(void* ud, int op, const uint8_t* r, int rl); |
|
|
|
/* helper: build request [ch_id_len:1][ch_id:var] */ |
|
static int req_ch(const char* ch_id, uint8_t* buf) { |
|
uint8_t n = (uint8_t)strlen(ch_id); |
|
buf[0] = n; memcpy(buf + 1, ch_id, n); |
|
return 1 + n; |
|
} |
|
|
|
/* ── INIT_SYNC handler ── */ |
|
|
|
static void cs_handle_init_sync(struct chat_sync* cs, uint64_t peer, |
|
const char* ch_id, const uint8_t* pl, size_t len) { |
|
if (len < 36) return; |
|
struct cs_ctx* ctx = cs_ctx_new(cs, peer, ch_id); |
|
if (!ctx) return; |
|
memcpy(&ctx->init_sync.peer_count, pl, 4); |
|
memcpy(ctx->init_sync.peer_last_ch, pl + 4, 32); |
|
uint8_t req[65]; int rl = req_ch(ch_id, req); |
|
gui_bridge_call(GUI_OP_COUNT, req, rl, ctx, on_init_sync_count); |
|
} |
|
|
|
static void on_init_sync_count(void* ud, int op, const uint8_t* r, int rl) { |
|
(void)op; |
|
struct cs_ctx* ctx = (struct cs_ctx*)ud; |
|
uint32_t my_count = (rl >= 4) ? *(const uint32_t*)r : 0; |
|
ctx->init_sync.my_count = my_count; |
|
|
|
uint32_t tp = ctx->init_sync.peer_count < my_count ? ctx->init_sync.peer_count : my_count; |
|
if (tp > 0) tp--; |
|
ctx->init_sync.test_pos = tp; |
|
|
|
uint8_t req[69]; int nr = req_ch(ctx->ch_id, req); |
|
memcpy(req + nr, &tp, 4); nr += 4; |
|
gui_bridge_call(GUI_OP_CHAIN_HASH_AT, req, nr, ctx, on_init_sync_hash); |
|
} |
|
|
|
static void on_init_sync_hash(void* ud, int op, const uint8_t* r, int rl) { |
|
(void)op; |
|
struct cs_ctx* ctx = (struct cs_ctx*)ud; |
|
uint32_t tp = ctx->init_sync.test_pos; |
|
uint8_t my_ch[32]; memset(my_ch, 0, 32); |
|
if (rl >= 32) memcpy(my_ch, r, 32); |
|
|
|
if (memcmp(my_ch, ctx->init_sync.peer_last_ch, 32) == 0) { |
|
/* synced */ |
|
uint8_t resp[64]; resp[0] = CS_MSG_INIT_RESP; |
|
memcpy(resp + 1, &ctx->init_sync.my_count, 4); resp[5] = 0; |
|
cs_send(ctx->sync, ctx->ch_id, ctx->peer, resp, 6); |
|
struct channel_cache* ch = cs_find(ctx->sync, ctx->ch_id); |
|
if (ch) ch->synced = CS_SYNC_DONE; |
|
u_free(ctx); return; |
|
} |
|
|
|
/* Build sparse hashes: async chain */ |
|
uint8_t resp[1024]; uint32_t off = 0; |
|
resp[off++] = CS_MSG_INIT_RESP; |
|
memcpy(resp + off, &ctx->init_sync.my_count, 4); off += 4; |
|
memcpy(resp + off, &tp, 4); off += 4; |
|
memcpy(resp + off, my_ch, 32); off += 32; |
|
uint8_t sparse_pos = off; off++; /* placeholder */ |
|
uint8_t sparse_count = 0; |
|
|
|
/* First sparse hash to start chain */ |
|
uint32_t first_step = 1; |
|
if (tp >= first_step) { |
|
uint32_t pos = tp - first_step; |
|
ctx->init_sync.test_pos = tp; /* сохраняем для продолжения */ |
|
/* Запрашиваем первый sparse hash */ |
|
uint8_t req2[69]; int nr2 = req_ch(ctx->ch_id, req2); |
|
memcpy(req2 + nr2, &pos, 4); nr2 += 4; |
|
gui_bridge_call(GUI_OP_CHAIN_HASH_AT, req2, nr2, ctx, on_init_sync_sparse); |
|
return; |
|
} |
|
resp[sparse_pos] = 0; |
|
cs_send(ctx->sync, ctx->ch_id, ctx->peer, resp, off); |
|
u_free(ctx); |
|
} |
|
|
|
static void on_init_sync_sparse(void* ud, int op, const uint8_t* r, int rl) { |
|
(void)op; |
|
struct cs_ctx* ctx = (struct cs_ctx*)ud; |
|
/* For initial version: just send INIT_RESP with 0 sparse. |
|
Peer will handle divergence. */ |
|
uint32_t tp = ctx->init_sync.test_pos; |
|
uint8_t resp[64]; resp[0] = CS_MSG_INIT_RESP; |
|
memcpy(resp + 1, &ctx->init_sync.my_count, 4); |
|
resp[5] = 0; /* sparse_count=0 */ |
|
cs_send(ctx->sync, ctx->ch_id, ctx->peer, resp, 6); |
|
u_free(ctx); |
|
} |
|
|
|
/* ── INIT_RESP handler ── */ |
|
|
|
static void cs_handle_init_resp(struct chat_sync* cs, uint64_t peer, |
|
const char* ch_id, const uint8_t* pl, size_t len) { |
|
if (len < 37) return; |
|
struct cs_ctx* ctx = cs_ctx_new(cs, peer, ch_id); |
|
if (!ctx) return; |
|
ctx->init_resp.peer_count = *(const uint32_t*)pl; |
|
ctx->init_resp.test_pos = *(const uint32_t*)(pl + 4); |
|
memcpy(ctx->init_resp.peer_ch, pl + 8, 37); /* hash:32 + sparse_count:1 + maybe more */ |
|
|
|
uint8_t req[69]; int nr = req_ch(ch_id, req); |
|
memcpy(req + nr, &ctx->init_resp.test_pos, 4); nr += 4; |
|
gui_bridge_call(GUI_OP_CHAIN_HASH_AT, req, nr, ctx, on_init_resp_hash); |
|
} |
|
|
|
static void on_init_resp_hash(void* ud, int op, const uint8_t* r, int rl) { |
|
(void)op; |
|
struct cs_ctx* ctx = (struct cs_ctx*)ud; |
|
uint8_t my_ch[32]; memset(my_ch, 0, 32); |
|
if (rl >= 32) memcpy(my_ch, r, 32); |
|
|
|
uint8_t sparse_count = ctx->init_resp.peer_ch[32]; |
|
|
|
/* Check match */ |
|
if (memcmp(my_ch, ctx->init_resp.peer_ch, 32) == 0) { |
|
/* Divergence is in our extra records (or peer's) */ |
|
/* Request data from peer starting at test_pos+1 if peer has more */ |
|
struct channel_cache* ch = cs_find(ctx->sync, ctx->ch_id); |
|
if (ch && ctx->init_resp.peer_count > ctx->init_resp.test_pos + 1) { |
|
/* We need peer's extra records */ |
|
uint32_t from = ctx->init_resp.test_pos + 1; |
|
uint8_t snd[7]; snd[0] = CS_MSG_SEND_DATA; |
|
memcpy(snd + 1, &from, 4); uint16_t z = 0; memcpy(snd + 5, &z, 2); |
|
cs_send(ctx->sync, ctx->ch_id, ctx->peer, snd, 7); |
|
} else { |
|
ch->synced = CS_SYNC_DONE; |
|
} |
|
u_free(ctx); return; |
|
} |
|
|
|
/* Mismatch — request data from start */ |
|
uint32_t from = 0; |
|
uint8_t snd[7]; snd[0] = CS_MSG_SEND_DATA; |
|
memcpy(snd + 1, &from, 4); uint16_t z = 0; memcpy(snd + 5, &z, 2); |
|
cs_send(ctx->sync, ctx->ch_id, ctx->peer, snd, 7); |
|
u_free(ctx); |
|
} |
|
|
|
/* ── SEND_DATA handler ── */ |
|
|
|
static void cs_handle_send_data(struct chat_sync* cs, uint64_t peer, |
|
const char* ch_id, const uint8_t* pl, size_t len) { |
|
if (len < 6) return; |
|
uint32_t from = *(const uint32_t*)pl; |
|
uint16_t count = *(const uint16_t*)(pl + 4); |
|
|
|
if (count == 0) { |
|
/* Peer requests OUR data starting from 'from' */ |
|
struct cs_ctx* ctx = cs_ctx_new(cs, peer, ch_id); |
|
if (!ctx) return; |
|
ctx->send_data.from_pos = from; |
|
ctx->send_data.sent_count = 0; |
|
|
|
uint8_t req[65]; int nr = req_ch(ch_id, req); |
|
gui_bridge_call(GUI_OP_CURSOR_OPEN, req, nr, ctx, on_send_data_cursor_open); |
|
return; |
|
} |
|
|
|
/* Peer sent US data — insert each record */ |
|
const uint8_t* ptr = pl + 6; |
|
size_t remain = len - 6; |
|
|
|
for (uint16_t i = 0; i < count && remain > 0; i++) { |
|
/* record: [ts:8][dh:8][node_id:8][ct_len:1][ct:var][data_len:4][data:var] */ |
|
/* For this, we call GUI_OP_INSERT_RECORD with the full record + chain_hash */ |
|
/* But SEND_DATA doesn't include chain_hash — only PUSH does. |
|
For SEND_DATA: skip chain_hash, just insert the content fields */ |
|
if (remain < 29) break; |
|
const uint8_t* rec_start = ptr; |
|
ptr += 8 + 8 + 8; remain -= 24; /* ts, dh, nid */ |
|
if (remain < 1) break; |
|
uint8_t ct_len = *ptr; ptr++; remain--; |
|
if (remain < ct_len) break; |
|
ptr += ct_len; remain -= ct_len; |
|
if (remain < 4) break; |
|
uint32_t dlen; memcpy(&dlen, ptr, 4); ptr += 4; remain -= 4; |
|
if (remain < dlen) break; |
|
ptr += dlen; remain -= dlen; |
|
|
|
size_t reclen = (size_t)(ptr - rec_start); |
|
|
|
/* Insert via GUI */ |
|
uint8_t req2[2048]; int nr2 = req_ch(ch_id, req2); |
|
if (nr2 + (int)reclen <= (int)sizeof(req2)) { |
|
memcpy(req2 + nr2, rec_start, reclen); nr2 += (int)reclen; |
|
/* Append zero chain_hash (not verified for SEND_DATA) */ |
|
uint8_t zero_ch[32]; memset(zero_ch, 0, 32); |
|
if (nr2 + 32 <= (int)sizeof(req2)) { |
|
memcpy(req2 + nr2, zero_ch, 32); nr2 += 32; |
|
} |
|
gui_bridge_call(GUI_OP_INSERT_RECORD, req2, nr2, NULL, NULL); |
|
} |
|
} |
|
|
|
/* Request more if needed */ |
|
uint32_t next = from + count; |
|
uint8_t snd[7]; snd[0] = CS_MSG_SEND_DATA; |
|
memcpy(snd + 1, &next, 4); uint16_t z = 0; memcpy(snd + 5, &z, 2); |
|
cs_send(cs, ch_id, peer, snd, 7); |
|
|
|
struct channel_cache* ch = cs_find(cs, ch_id); |
|
if (ch) ch->synced = CS_SYNC_DONE; |
|
} |
|
|
|
static void on_send_data_cursor_open(void* ud, int op, const uint8_t* r, int rl) { |
|
(void)op; |
|
struct cs_ctx* ctx = (struct cs_ctx*)ud; |
|
if (rl < 4) { u_free(ctx); return; } |
|
memcpy(&ctx->send_data.cursor_id, r, 4); |
|
gui_bridge_call(GUI_OP_CURSOR_NEXT, (const uint8_t*)&ctx->send_data.cursor_id, 4, ctx, on_send_data_cursor_next); |
|
} |
|
|
|
static void on_send_data_cursor_next(void* ud, int op, const uint8_t* r, int rl) { |
|
(void)op; |
|
struct cs_ctx* ctx = (struct cs_ctx*)ud; |
|
if (rl == 0) { |
|
gui_bridge_call(GUI_OP_CURSOR_CLOSE, (const uint8_t*)&ctx->send_data.cursor_id, 4, NULL, NULL); |
|
u_free(ctx); return; |
|
} |
|
|
|
/* Send record to peer */ |
|
uint8_t snd[8192]; uint32_t off = 0; |
|
snd[off++] = CS_MSG_SEND_DATA; |
|
uint32_t pos = ctx->send_data.from_pos + ctx->send_data.sent_count; |
|
memcpy(snd + off, &pos, 4); off += 4; |
|
uint16_t c = 1; memcpy(snd + off, &c, 2); off += 2; |
|
if (off + rl <= (uint32_t)sizeof(snd)) { memcpy(snd + off, r, rl); off += rl; } |
|
cs_send(ctx->sync, ctx->ch_id, ctx->peer, snd, off); |
|
ctx->send_data.sent_count++; |
|
|
|
gui_bridge_call(GUI_OP_CURSOR_NEXT, (const uint8_t*)&ctx->send_data.cursor_id, 4, ctx, on_send_data_cursor_next); |
|
} |
|
|
|
/* ── PUSH handler ── */ |
|
|
|
static void cs_handle_push(struct chat_sync* cs, uint64_t peer, |
|
const char* ch_id, const uint8_t* pl, size_t len) { |
|
if (len < 24 + 1 + 4) return; |
|
uint64_t ts, dh, nid; |
|
memcpy(&ts, pl, 8); memcpy(&dh, pl + 8, 8); memcpy(&nid, pl + 16, 8); |
|
|
|
struct cs_ctx* ctx = cs_ctx_new(cs, peer, ch_id); |
|
if (!ctx) return; |
|
ctx->push.ts = ts; ctx->push.dh = dh; |
|
|
|
/* Request: [ch_len][ch_id] + [rest of PUSH payload starting from ct_len] */ |
|
/* PUSH payload: [ts:8][dh:8][node_id:8][ct_len:1][ct:var][data_len:4][data:var][chain_hash:32] */ |
|
uint8_t req[2048]; int nr = req_ch(ch_id, req); |
|
const uint8_t* rec = pl + 24; /* skip ts,dh,nid */ |
|
int reclen = (int)(len - 24); |
|
if (nr + reclen <= (int)sizeof(req)) { |
|
memcpy(req + nr, rec, reclen); nr += reclen; |
|
} |
|
gui_bridge_call(GUI_OP_INSERT_RECORD, req, nr, ctx, on_push_inserted); |
|
} |
|
|
|
static void on_push_inserted(void* ud, int op, const uint8_t* r, int rl) { |
|
(void)op; |
|
struct cs_ctx* ctx = (struct cs_ctx*)ud; |
|
int rc = (rl >= 1) ? (int8_t)r[0] : -1; |
|
if (rc == 0) { |
|
uint8_t ack[17]; ack[0] = CS_MSG_ACK_PUSH; |
|
memcpy(ack + 1, &ctx->push.dh, 8); |
|
memcpy(ack + 9, &ctx->push.ts, 8); |
|
cs_send(ctx->sync, ctx->ch_id, ctx->peer, ack, 17); |
|
|
|
uint8_t evt[65]; evt[0] = (uint8_t)strlen(ctx->ch_id); |
|
memcpy(evt + 1, ctx->ch_id, evt[0]); |
|
gui_bridge_post(GUI_EVT_MSG_RECEIVED, evt, 1 + evt[0]); |
|
|
|
struct channel_cache* ch = cs_find(ctx->sync, ctx->ch_id); |
|
if (ch) ch->msg_count++; |
|
} |
|
u_free(ctx); |
|
} |
|
|
|
/* ── ACK_PUSH handler ── */ |
|
|
|
static void cs_handle_ack_push(struct chat_sync* cs, uint64_t peer, |
|
const char* ch_id, const uint8_t* pl, size_t len) { |
|
if (len < 16) return; |
|
uint64_t ts, dh; memcpy(&dh, pl, 8); memcpy(&ts, pl + 8, 8); |
|
uint8_t req[1 + 64 + 24]; int nr = req_ch(ch_id, req); |
|
memcpy(req + nr, &ts, 8); nr += 8; |
|
memcpy(req + nr, &dh, 8); nr += 8; |
|
uint64_t myid = cs->inst->node_id; |
|
memcpy(req + nr, &myid, 8); nr += 8; |
|
gui_bridge_call(GUI_OP_MARK_SENT, req, nr, NULL, NULL); |
|
} |
|
|
|
/* ── SYNC_DONE handler ── */ |
|
|
|
static void cs_handle_sync_done(struct chat_sync* cs, uint64_t peer, |
|
const char* ch_id, const uint8_t* pl, size_t len) { |
|
if (len < 36) return; |
|
uint32_t pc; memcpy(&pc, pl, 4); |
|
struct channel_cache* ch = cs_find(cs, ch_id); |
|
if (ch) { ch->msg_count = pc; ch->synced = CS_SYNC_DONE; } |
|
} |
|
|
|
/* ── Recv dispatcher ── */ |
|
|
|
static void chat_sync_recv_cb(struct ETCP_CONN* conn, struct ll_entry* entry) { |
|
if (!entry || entry->len < 4) { if (entry) { if (entry->dgram) u_free(entry->dgram); queue_entry_free(entry); } return; } |
|
if (!g_cs || !g_cs->initialized) { u_free(entry->dgram); queue_entry_free(entry); return; } |
|
|
|
uint64_t peer = conn ? conn->peer_node_id : 0; |
|
const uint8_t* d = entry->dgram; |
|
size_t dlen = entry->len; |
|
|
|
if (dlen < 4) { u_free(entry->dgram); queue_entry_free(entry); return; } |
|
uint8_t ch_len = d[1]; |
|
if (dlen < (size_t)(2 + ch_len + 1)) { u_free(entry->dgram); queue_entry_free(entry); return; } |
|
|
|
char ch_id[64]; memcpy(ch_id, d + 2, ch_len); ch_id[ch_len] = '\0'; |
|
uint8_t type = d[2 + ch_len]; |
|
const uint8_t* pl = d + 3 + ch_len; |
|
size_t plen = dlen - 3 - ch_len; |
|
|
|
switch (type) { |
|
case CS_MSG_INIT_SYNC: cs_handle_init_sync(g_cs, peer, ch_id, pl, plen); break; |
|
case CS_MSG_INIT_RESP: cs_handle_init_resp(g_cs, peer, ch_id, pl, plen); break; |
|
case CS_MSG_SEND_DATA: cs_handle_send_data(g_cs, peer, ch_id, pl, plen); break; |
|
case CS_MSG_PUSH: cs_handle_push(g_cs, peer, ch_id, pl, plen); break; |
|
case CS_MSG_ACK_PUSH: cs_handle_ack_push(g_cs, peer, ch_id, pl, plen); break; |
|
case CS_MSG_SYNC_DONE: cs_handle_sync_done(g_cs, peer, ch_id, pl, plen); break; |
|
default: break; |
|
} |
|
u_free(entry->dgram); queue_entry_free(entry); |
|
} |
|
|
|
/* ── Connection callbacks ── */ |
|
|
|
static void cs_on_conn_up(struct ETCP_CONN* conn, void* arg) { |
|
(void)arg; |
|
if (!conn || !g_cs) return; |
|
uint64_t peer = conn->peer_node_id; |
|
if (peer == 0 || peer == g_cs->inst->node_id) return; |
|
for (int i = 0; i < g_cs->channel_count; i++) { |
|
struct channel_cache* ch = &g_cs->channels[i]; |
|
int found = 0; |
|
for (int j = 0; j < ch->peer_count; j++) if (ch->peer_ids[j] == peer) { found = 1; break; } |
|
if (!found) continue; |
|
ch->synced = CS_SYNC_IN_PROGRESS; |
|
uint8_t msg[37]; msg[0] = CS_MSG_INIT_SYNC; |
|
memcpy(msg + 1, &ch->msg_count, 4); memcpy(msg + 5, ch->last_chain_hash, 32); |
|
cs_send(g_cs, ch->channel_id, peer, msg, 37); |
|
} |
|
} |
|
|
|
static void cs_on_conn_down(struct ETCP_CONN* conn, void* arg) { |
|
(void)arg; |
|
if (!conn || !g_cs) return; |
|
uint64_t peer = conn->peer_node_id; |
|
for (int i = 0; i < g_cs->channel_count; i++) { |
|
int found = 0; |
|
for (int j = 0; j < g_cs->channels[i].peer_count; j++) |
|
if (g_cs->channels[i].peer_ids[j] == peer) { found = 1; break; } |
|
if (found) g_cs->channels[i].synced = CS_SYNC_NONE; |
|
} |
|
} |
|
|
|
static void cs_on_new_conn(struct ETCP_CONN* conn, void* arg) { |
|
(void)arg; |
|
if (!conn) return; |
|
etcp_conn_add_up_cbk(conn, cs_on_conn_up, NULL); |
|
etcp_conn_add_down_cbk(conn, cs_on_conn_down, NULL); |
|
} |
|
|
|
/* ── Periodic refresh from DB ── */ |
|
|
|
static void on_refresh_list(void* ud, int op, const uint8_t* r, int rl) { |
|
(void)op; |
|
if (!g_cs || !r || rl < 2) return; |
|
uint16_t cnt; memcpy(&cnt, r, 2); |
|
const uint8_t* p = r + 2; int rem = rl - 2; |
|
|
|
for (int i = 0; i < g_cs->channel_count; i++) |
|
if (g_cs->channels[i].peer_ids) u_free(g_cs->channels[i].peer_ids); |
|
if (g_cs->channels) u_free(g_cs->channels); |
|
g_cs->channels = u_calloc(cnt, sizeof(struct channel_cache)); |
|
g_cs->channel_count = cnt; |
|
if (!g_cs->channels) { g_cs->channel_count = 0; return; } |
|
|
|
for (int i = 0; i < (int)cnt && rem >= 1; i++) { |
|
uint8_t id_len = *p++; |
|
rem--; |
|
if (rem < id_len) break; |
|
memcpy(g_cs->channels[i].channel_id, p, id_len); |
|
g_cs->channels[i].channel_id[id_len] = '\0'; |
|
p += id_len; rem -= id_len; |
|
|
|
uint8_t req[65]; req[0] = id_len; |
|
memcpy(req + 1, g_cs->channels[i].channel_id, id_len); |
|
gui_bridge_call(GUI_OP_LIST_PEERS, req, 1 + id_len, g_cs, on_refresh_peers_and_count); |
|
} |
|
} |
|
|
|
static void on_refresh_peers_and_count(void* ud, int op, const uint8_t* r, int rl) { |
|
(void)op; (void)ud; |
|
if (!g_cs || !r || rl < 2) return; |
|
uint16_t cnt; memcpy(&cnt, r, 2); |
|
/* Find first channel without peers (simplified: assumes sequential refresh) */ |
|
for (int i = 0; i < g_cs->channel_count; i++) { |
|
if (g_cs->channels[i].peer_ids) continue; |
|
g_cs->channels[i].peer_ids = u_calloc(cnt, sizeof(uint64_t)); |
|
g_cs->channels[i].peer_count = cnt; |
|
for (uint16_t j = 0; j < cnt && 2 + j * 8 < (uint32_t)rl; j++) |
|
memcpy(&g_cs->channels[i].peer_ids[j], r + 2 + j * 8, 8); |
|
|
|
/* Also get count */ |
|
uint8_t req[65]; req[0] = (uint8_t)strlen(g_cs->channels[i].channel_id); |
|
memcpy(req + 1, g_cs->channels[i].channel_id, req[0]); |
|
gui_bridge_call(GUI_OP_COUNT, req, 1 + req[0], NULL, NULL); |
|
break; |
|
} |
|
} |
|
|
|
static void refresh_timer_cb(void* arg) { |
|
(void)arg; |
|
if (!g_cs || !g_cs->initialized) return; |
|
gui_bridge_call(GUI_OP_LIST_CHANNELS, NULL, 0, g_cs, on_refresh_list); |
|
g_cs->refresh_timer = uasync_set_timeout(g_cs->inst->ua, 30u * 10000u, g_cs, refresh_timer_cb, "cs_refresh"); |
|
} |
|
|
|
/* ── TTL cleanup ── */ |
|
|
|
static void ttl_timer_cb(void* arg) { |
|
(void)arg; |
|
if (!g_cs || !g_cs->initialized) return; |
|
uint64_t cutoff = get_time_us() - 86400000000ULL; |
|
uint64_t myid = g_cs->inst->node_id; |
|
for (int i = 0; i < g_cs->channel_count; i++) { |
|
uint8_t req[1 + 64 + 16]; int nr = req_ch(g_cs->channels[i].channel_id, req); |
|
memcpy(req + nr, &myid, 8); nr += 8; |
|
memcpy(req + nr, &cutoff, 8); nr += 8; |
|
gui_bridge_call(GUI_OP_TTL_DELETE, req, nr, NULL, NULL); |
|
} |
|
g_cs->ttl_timer = uasync_set_timeout(g_cs->inst->ua, 3600u * 10000u, g_cs, ttl_timer_cb, "cs_ttl"); |
|
} |
|
|
|
/* ── Public API ── */ |
|
|
|
int chat_sync_init(struct UTUN_INSTANCE* inst, |
|
void (*gui_cb)(void*, int, const uint8_t*, int)) { |
|
(void)gui_cb; |
|
if (!inst) return -1; |
|
struct chat_sync* cs = u_calloc(1, sizeof(*cs)); |
|
if (!cs) return -1; |
|
cs->inst = inst; |
|
cs->initialized = 1; |
|
g_cs = cs; |
|
|
|
etcp_router_bind(inst, ETCP_RT_ID_CHAT_SYNC, chat_sync_recv_cb); |
|
etcp_add_new_conn_cbk(inst, cs_on_new_conn, NULL); |
|
|
|
struct ETCP_CONN* c = inst->connections; |
|
while (c) { |
|
etcp_conn_add_up_cbk(c, cs_on_conn_up, NULL); |
|
etcp_conn_add_down_cbk(c, cs_on_conn_down, NULL); |
|
c = c->next; |
|
} |
|
|
|
cs->refresh_timer = uasync_set_timeout(inst->ua, 50000u, cs, refresh_timer_cb, "cs_refresh"); |
|
cs->ttl_timer = uasync_set_timeout(inst->ua, 3600u * 10000u, cs, ttl_timer_cb, "cs_ttl"); |
|
|
|
DEBUG_INFO(DEBUG_CATEGORY_DEBUG, "%s: initialized", CS_ID); |
|
return 0; |
|
} |
|
|
|
void chat_sync_destroy(struct UTUN_INSTANCE* inst) { |
|
struct chat_sync* cs = g_cs; |
|
if (!cs || !inst) return; |
|
cs->initialized = 0; g_cs = NULL; |
|
|
|
etcp_router_unbind(inst, ETCP_RT_ID_CHAT_SYNC); |
|
if (cs->refresh_timer) { uasync_cancel_timeout(inst->ua, cs->refresh_timer); cs->refresh_timer = NULL; } |
|
if (cs->ttl_timer) { uasync_cancel_timeout(inst->ua, cs->ttl_timer); cs->ttl_timer = NULL; } |
|
|
|
struct ETCP_CONN* c = inst->connections; |
|
while (c) { |
|
etcp_conn_remove_up_cbk(c, cs_on_conn_up, NULL); |
|
etcp_conn_remove_down_cbk(c, cs_on_conn_down, NULL); |
|
c = c->next; |
|
} |
|
|
|
for (int i = 0; i < cs->channel_count; i++) |
|
if (cs->channels[i].peer_ids) u_free(cs->channels[i].peer_ids); |
|
if (cs->channels) u_free(cs->channels); |
|
u_free(cs); |
|
} |
|
|
|
int chat_sync_push(struct UTUN_INSTANCE* inst, |
|
const char* ch_id, uint64_t node_id, |
|
const char* content_type, const uint8_t* data, uint32_t data_len, |
|
uint64_t timestamp, uint64_t datahash) { |
|
if (!g_cs || !g_cs->initialized) return -1; |
|
struct channel_cache* ch = cs_find(g_cs, ch_id); |
|
|
|
uint8_t ct_len = content_type ? (uint8_t)strlen(content_type) : 0; |
|
if (ct_len > 63) ct_len = 63; |
|
size_t total = 1 + 8 + 8 + 8 + 1 + ct_len + 4 + data_len + 32; |
|
uint8_t* buf = u_malloc(total); |
|
if (!buf) return -1; |
|
|
|
uint8_t* p = buf; |
|
*p++ = CS_MSG_PUSH; |
|
memcpy(p, ×tamp, 8); p += 8; |
|
memcpy(p, &datahash, 8); p += 8; |
|
memcpy(p, &node_id, 8); p += 8; |
|
*p++ = ct_len; |
|
if (ct_len) { memcpy(p, content_type, ct_len); p += ct_len; } |
|
uint32_t dlen = data_len; |
|
memcpy(p, &dlen, 4); p += 4; |
|
if (data_len) { memcpy(p, data, data_len); p += data_len; } |
|
memset(p, 0, 32); /* chain_hash placeholder */ |
|
|
|
int sent = 0; |
|
uint64_t myid = g_cs->inst->node_id; |
|
if (ch) { |
|
for (int i = 0; i < ch->peer_count; i++) { |
|
if (ch->peer_ids[i] == node_id || ch->peer_ids[i] == myid) continue; |
|
cs_send(g_cs, ch_id, ch->peer_ids[i], buf, (size_t)(p + 32 - buf)); |
|
sent++; |
|
} |
|
} |
|
u_free(buf); |
|
return sent; |
|
} |
|
|
|
void chat_sync_connect_node(struct UTUN_INSTANCE* inst, uint64_t node_id) { |
|
/* Placeholder — full impl requires parsing nodeinfo and calling conn_mgr_connect_node */ |
|
uint8_t req[8]; memcpy(req, &node_id, 8); |
|
gui_bridge_call(GUI_OP_LOAD_NODEINFO, req, 8, NULL, NULL); |
|
} |
|
|
|
/* ── Invite link connect ── */ |
|
|
|
struct cs_invite_arg { |
|
uint64_t node_id; |
|
uint8_t pubkey[SC_PUBKEY_SIZE]; |
|
uint8_t* addrs_data; |
|
int addr_count; |
|
uint8_t* ch_id; |
|
int ch_id_len; |
|
}; |
|
|
|
static void cs_connect_result_cb(int result, uint64_t node_id, void* arg) { |
|
(void)arg; |
|
uint8_t data[12]; |
|
memcpy(data, &node_id, 8); |
|
memcpy(data + 8, &result, 4); |
|
gui_bridge_post(GUI_EVT_CONNECT_RESULT, data, 12); |
|
} |
|
|
|
static void cs_connect_from_invite_impl(void* arg) { |
|
struct cs_invite_arg* a = (struct cs_invite_arg*)arg; |
|
uint64_t node_id = a->node_id; |
|
if (!g_cs || !g_cs->inst || !g_cs->inst->bgp || !g_cs->inst->conn_mgr) { |
|
uint8_t err[12]; int r = -7; |
|
memcpy(err, &node_id, 8); memcpy(err + 8, &r, 4); |
|
gui_bridge_post(GUI_EVT_CONNECT_RESULT, err, 12); |
|
goto cleanup; |
|
} |
|
|
|
struct ROUTE_BGP* bgp = g_cs->inst->bgp; |
|
if (nodeinfo_find_by_id(bgp, node_id)) { |
|
DEBUG_INFO(DEBUG_CATEGORY_DEBUG, "chat_sync: node 0x%016llx already in BGP, connecting", (unsigned long long)node_id); |
|
conn_mgr_connect_node(g_cs->inst->conn_mgr, node_id, 30000, cs_connect_result_cb, NULL); |
|
goto cleanup; |
|
} |
|
|
|
size_t dyn_sz = (size_t)a->addr_count * sizeof(struct NODEINFO_IPV4_ADDR); |
|
size_t data_sz = sizeof(struct NODEINFO_Q) - sizeof(struct ll_entry) + dyn_sz + 8; |
|
struct NODEINFO_Q* nq = (struct NODEINFO_Q*)queue_entry_new(data_sz); |
|
if (!nq) { |
|
DEBUG_ERROR(DEBUG_CATEGORY_DEBUG, "chat_sync: failed to alloc NODEINFO_Q"); |
|
uint8_t err[12]; int r = -7; |
|
memcpy(err, &node_id, 8); memcpy(err + 8, &r, 4); |
|
gui_bridge_post(GUI_EVT_CONNECT_RESULT, err, 12); |
|
goto cleanup; |
|
} |
|
|
|
struct NODEINFO* ni = &nq->node; |
|
ni->node_id = node_id; |
|
ni->ver = 0; |
|
memcpy(ni->public_key, a->pubkey, SC_PUBKEY_SIZE); |
|
memset(ni->ed25519_public_key, 0, SC_PUBKEY_SIZE); |
|
ni->node_name_len = 0; |
|
ni->local_v4_sockets = 0; |
|
ni->local_v4_addrs = (uint8_t)a->addr_count; |
|
ni->local_v6_sockets = 0; |
|
ni->local_v6_addrs = 0; |
|
ni->local_v4_subnets = 0; |
|
ni->local_v6_subnets = 0; |
|
ni->tranzit_nodes = 0; |
|
ni->hop_count = 0; |
|
|
|
uint8_t* dyn = (uint8_t*)ni + sizeof(struct NODEINFO); |
|
const uint8_t* src = a->addrs_data; |
|
for (int i = 0; i < a->addr_count; i++) { |
|
struct NODEINFO_IPV4_ADDR* addr = (struct NODEINFO_IPV4_ADDR*)(dyn + i * sizeof(struct NODEINFO_IPV4_ADDR)); |
|
memset(addr, 0, sizeof(*addr)); |
|
uint8_t family = *src++; |
|
if (family == 4) { |
|
memcpy(addr->addr, src, 4); src += 4; |
|
uint16_t port_be = ((uint16_t)src[0] << 8) | src[1]; src += 2; |
|
addr->port = port_be; |
|
addr->type = ADDR_TYPE_REAL; |
|
addr->socket_id = 0; |
|
addr->protocol = NODE_PROTO_UDP; |
|
} else { |
|
src += 18; |
|
continue; |
|
} |
|
} |
|
|
|
nq->alien = 0; |
|
nq->dirty = 0; |
|
nq->last_ver = 0; |
|
nq->conn_mgr_type = CONN_TYPE_NONE; |
|
nq->conn_mgr_intermediariy_count = 0; |
|
memset(&nq->connectivity, 0, sizeof(nq->connectivity)); |
|
nq->best_socket = NULL; |
|
|
|
queue_data_put_with_index(bgp->nodes, &nq->ll); |
|
|
|
DEBUG_INFO(DEBUG_CATEGORY_DEBUG, "chat_sync: created NODEINFO for 0x%016llx, %d addrs, connecting", |
|
(unsigned long long)node_id, a->addr_count); |
|
conn_mgr_connect_node(g_cs->inst->conn_mgr, node_id, 30000, cs_connect_result_cb, NULL); |
|
|
|
cleanup: |
|
u_free(a->addrs_data); |
|
u_free(a->ch_id); |
|
u_free(a); |
|
} |
|
|
|
void chat_sync_connect_from_invite(uint64_t node_id, const uint8_t* pubkey_bin, |
|
const uint8_t* addrs_data, int addr_count, |
|
const uint8_t* channel_id, int ch_id_len) { |
|
if (!g_cs || !g_cs->inst || !g_cs->inst->ua) { |
|
int r = -7; |
|
uint8_t err[12]; memcpy(err, &node_id, 8); memcpy(err + 8, &r, 4); |
|
gui_bridge_post(GUI_EVT_CONNECT_RESULT, err, 12); |
|
return; |
|
} |
|
|
|
struct cs_invite_arg* a = u_calloc(1, sizeof(struct cs_invite_arg)); |
|
if (!a) return; |
|
|
|
a->node_id = node_id; |
|
memcpy(a->pubkey, pubkey_bin, SC_PUBKEY_SIZE); |
|
|
|
size_t addrs_sz = (size_t)addr_count * 7; |
|
a->addrs_data = u_malloc(addrs_sz); |
|
if (!a->addrs_data) { u_free(a); return; } |
|
memcpy(a->addrs_data, addrs_data, addrs_sz); |
|
a->addr_count = addr_count; |
|
|
|
a->ch_id = u_malloc((size_t)ch_id_len + 1); |
|
if (!a->ch_id) { u_free(a->addrs_data); u_free(a); return; } |
|
memcpy(a->ch_id, channel_id, (size_t)ch_id_len); |
|
a->ch_id_len = ch_id_len; |
|
|
|
gui_bridge_post_uasync(g_cs->inst->ua, cs_connect_from_invite_impl, a); |
|
}
|
|
|