1
This commit is contained in:
1 parent
26f4da1b7c
commit
712d1b53df
29 files changed
+1029
-1138
No files matched your search
@@ -36,6 +36,7 @@ extern "C" {
|
||||
#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] */
|
||||
#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] */
|
||||
|
||||
typedef void (*chat_event_handler_fn)(int type, const uint8_t* data, int len);
|
||||
|
||||
|
||||
@@ -42,6 +42,7 @@ static const struct chat_setting_def g_setting_defs[] = {
|
||||
{"compressor_max_gain_db", CHAT_SETTING_INT, 25, 1, 100},
|
||||
{"compressor_rise_rate", CHAT_SETTING_INT, 10, 1, 100},
|
||||
{"media_download_max_peers", CHAT_SETTING_INT, 3, 1, 16},
|
||||
{"join_policy", CHAT_SETTING_INT, 1, 0, 2}, /* 0=autojoin, 1=ask, 2=deny */
|
||||
};
|
||||
|
||||
#define CHAT_SETTING_COUNT (sizeof(g_setting_defs) / sizeof(g_setting_defs[0]))
|
||||
|
||||
+271
-11
@@ -1,5 +1,6 @@
|
||||
#include "chat_sync.h"
|
||||
#include "chat_core.h"
|
||||
#include "chat_setting.h"
|
||||
#include "chat_event.h"
|
||||
#include "../routing_layer/topo_node_sqlite.h"
|
||||
#include "../routing_layer/topo_group.h"
|
||||
@@ -50,6 +51,11 @@ struct chat_sync {
|
||||
uint8_t sync_scheduled;
|
||||
uint64_t pending_invite_ch_id;
|
||||
uint64_t pending_invite_node_id;
|
||||
char pending_password[128]; /* пароль для CHANNEL_INFO_REQ */
|
||||
/* Pending incoming invites (для ask/deny) */
|
||||
uint64_t pending_inv_in_ch_id[4];
|
||||
uint64_t pending_inv_in_node_id[4];
|
||||
int pending_inv_in_count;
|
||||
};
|
||||
|
||||
#define CS_ID "chat_sync"
|
||||
@@ -72,6 +78,7 @@ static const char* cs_msg_name(uint8_t type) {
|
||||
case CS_MSG_PEER_UPSERT: return "PEER_UPSERT";
|
||||
case CS_MSG_ERROR: return "ERROR";
|
||||
case CS_MSG_PEER_REMOVE: return "PEER_REMOVE";
|
||||
case CS_MSG_CHANNEL_INVITE: return "CHANNEL_INVITE";
|
||||
default: return "???";
|
||||
}
|
||||
}
|
||||
@@ -136,6 +143,8 @@ static void cs_handle_peer_upsert(struct chat_sync* cs, uint64_t peer,
|
||||
const char* ch_id, const uint8_t* pl, size_t len);
|
||||
static void cs_handle_peer_remove(struct chat_sync* cs, uint64_t peer,
|
||||
const char* ch_id, const uint8_t* pl, size_t len);
|
||||
static void cs_handle_channel_invite(struct chat_sync* cs, uint64_t peer,
|
||||
const char* ch_id, const uint8_t* pl, size_t len);
|
||||
|
||||
/* ── Recv dispatcher ── */
|
||||
|
||||
@@ -178,6 +187,7 @@ static void chat_sync_recv_cb(struct ETCP_CONN* conn, struct ll_entry* entry) {
|
||||
break;
|
||||
}
|
||||
case CS_MSG_PEER_REMOVE: cs_handle_peer_remove(g_cs, peer, ch_id, pl, plen); break;
|
||||
case CS_MSG_CHANNEL_INVITE: cs_handle_channel_invite(g_cs, peer, ch_id, pl, plen); break;
|
||||
default: DEBUG_WARN(DEBUG_CATEGORY_DB_SYNC, "%s: UNKNOWN msg type=%02x from=%016llx", CS_ID, type, (unsigned long long)peer); break;
|
||||
}
|
||||
u_free(entry->dgram); queue_entry_free(entry);
|
||||
@@ -405,12 +415,16 @@ static void cs_start_channel_join(struct chat_sync* cs, uint64_t peer) {
|
||||
if (!cs || cs->info_req_timer) return;
|
||||
char ch_id_str[64];
|
||||
snprintf(ch_id_str, sizeof(ch_id_str), "%llu", (unsigned long long)cs->pending_invite_ch_id);
|
||||
uint8_t req[1] = { CS_MSG_CHANNEL_INFO_REQ };
|
||||
cs_send(cs, ch_id_str, peer, req, 1);
|
||||
size_t plen = strlen(cs->pending_password);
|
||||
uint8_t req[130];
|
||||
req[0] = CS_MSG_CHANNEL_INFO_REQ;
|
||||
req[1] = (uint8_t)(plen > 127 ? 127 : plen);
|
||||
if (plen > 0) memcpy(req + 2, cs->pending_password, plen > 127 ? 127 : plen);
|
||||
cs_send(cs, ch_id_str, peer, req, 2 + (plen > 127 ? 127 : (int)plen));
|
||||
cs->info_req_timer = uasync_set_timeout(cs->inst->ua,
|
||||
CS_INFO_REQ_TIMEOUT_MS * 10, cs, cs_info_req_timeout_cb, "cs_info_req");
|
||||
DEBUG_INFO(DEBUG_CATEGORY_DB_SYNC, "%s: sent CHANNEL_INFO_REQ ch=%llu peer=0x%016llx",
|
||||
CS_ID, (unsigned long long)cs->pending_invite_ch_id, (unsigned long long)peer);
|
||||
DEBUG_INFO(DEBUG_CATEGORY_DB_SYNC, "%s: sent CHANNEL_INFO_REQ ch=%llu peer=0x%016llx pass_len=%zu",
|
||||
CS_ID, (unsigned long long)cs->pending_invite_ch_id, (unsigned long long)peer, plen);
|
||||
}
|
||||
|
||||
/* ── Connection callbacks ── */
|
||||
@@ -695,7 +709,8 @@ static void cm_invite_trampoline(void* arg) {
|
||||
void chat_sync_connect_from_invite(uint64_t channel_id, uint64_t node_id,
|
||||
const uint8_t* pubkey_bin,
|
||||
const uint8_t* addrs_data, int addr_count,
|
||||
int addrs_data_len) {
|
||||
int addrs_data_len,
|
||||
const char* password) {
|
||||
if (!g_cs || !g_cs->inst || !g_cs->inst->ua) {
|
||||
int r = -7;
|
||||
#ifdef __ANDROID__
|
||||
@@ -724,13 +739,21 @@ void chat_sync_connect_from_invite(uint64_t channel_id, uint64_t node_id,
|
||||
|
||||
g_cs->pending_invite_ch_id = channel_id;
|
||||
g_cs->pending_invite_node_id = node_id;
|
||||
if (password && password[0]) {
|
||||
size_t pl = strlen(password);
|
||||
if (pl >= sizeof(g_cs->pending_password)) pl = sizeof(g_cs->pending_password) - 1;
|
||||
memcpy(g_cs->pending_password, password, pl);
|
||||
g_cs->pending_password[pl] = '\0';
|
||||
} else {
|
||||
g_cs->pending_password[0] = '\0';
|
||||
}
|
||||
#ifdef __ANDROID__
|
||||
__android_log_print(ANDROID_LOG_INFO, "utun-chat", "invite ch=%llu node=%016llx addrs=%d",
|
||||
(long long)channel_id, (long long)node_id, addr_count);
|
||||
__android_log_print(ANDROID_LOG_INFO, "utun-chat", "invite ch=%llu node=%016llx addrs=%d pass=%s",
|
||||
(long long)channel_id, (long long)node_id, addr_count, password ? "yes" : "no");
|
||||
#endif
|
||||
#ifndef __ANDROID__
|
||||
DEBUG_INFO(DEBUG_CATEGORY_DB_SYNC, "%s: invite start ch=%llu node=0x%016llx pubkey=%016llx... addrs=%d",
|
||||
CS_ID, channel_id, node_id, *(const uint64_t*)pubkey_bin, addr_count);
|
||||
DEBUG_INFO(DEBUG_CATEGORY_DB_SYNC, "%s: invite start ch=%llu node=0x%016llx pubkey=%016llx... addrs=%d pass=%s",
|
||||
CS_ID, channel_id, node_id, *(const uint64_t*)pubkey_bin, addr_count, password && password[0] ? "yes" : "no");
|
||||
#endif
|
||||
|
||||
struct cm_invite_wrap { struct chat_invite inv; }* w = u_malloc(sizeof(struct cm_invite_wrap));
|
||||
@@ -825,8 +848,32 @@ static int _get_node_name(sqlite3* db, uint64_t node_id, char* out, size_t sz) {
|
||||
|
||||
static void cs_handle_channel_info_req(struct chat_sync* cs, uint64_t peer,
|
||||
const char* ch_id, const uint8_t* pl, size_t len) {
|
||||
(void)pl; (void)len;
|
||||
DEBUG_TRACE(DEBUG_CATEGORY_DB_SYNC, "%s: INFO_REQ ch=%s from=%016llx", CS_ID, ch_id, (unsigned long long)peer);
|
||||
DEBUG_TRACE(DEBUG_CATEGORY_DB_SYNC, "%s: INFO_REQ ch=%s from=%016llx len=%zu", CS_ID, ch_id, (unsigned long long)peer, len);
|
||||
|
||||
/* Parse password from request (v2 format: type + pass_len + optional_password) */
|
||||
char req_password[128] = "";
|
||||
if (len >= 1) {
|
||||
uint8_t plen2 = pl[0];
|
||||
if (plen2 > 0 && plen2 <= len - 1) {
|
||||
if (plen2 > 127) plen2 = 127;
|
||||
memcpy(req_password, pl + 1, plen2); req_password[plen2] = '\0';
|
||||
}
|
||||
}
|
||||
|
||||
/* Check password if server has one configured */
|
||||
{
|
||||
struct global_config *gcfg = &cs->inst->config->global;
|
||||
if (gcfg->chatserver_enabled && gcfg->chatserver_join_password[0]) {
|
||||
if (strcmp(req_password, gcfg->chatserver_join_password) != 0) {
|
||||
DEBUG_WARN(DEBUG_CATEGORY_DB_SYNC, "%s: CHANNEL_INFO_REQ wrong password ch=%s from=%016llx (got '%s', expected '%s')",
|
||||
CS_ID, ch_id, (unsigned long long)peer, req_password, gcfg->chatserver_join_password);
|
||||
uint8_t err[2] = { CS_MSG_ERROR, 0x01 }; /* 0x01 = wrong password */
|
||||
cs_send(cs, ch_id, peer, err, 2);
|
||||
return;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
char name[128]; uint64_t owner; uint8_t x25519[32], ed_pub[32], ch_sig[64];
|
||||
if (topo_node_sqlite_channel_get(cs->inst->topo_sqlite_db,
|
||||
ch_id, name, (int)sizeof(name), &owner, x25519, ed_pub, ch_sig) != 0) {
|
||||
@@ -1439,3 +1486,216 @@ static void cs_handle_peer_remove(struct chat_sync* cs, uint64_t peer,
|
||||
DEBUG_INFO(DEBUG_CATEGORY_DB_SYNC, "%s: PEER_REMOVE node=0x%016llx ch=%s",
|
||||
CS_ID, (unsigned long long)node_id, ch_id);
|
||||
}
|
||||
|
||||
/* ─── CHANNEL_INVITE (0x0F): inviter → joiner (push-приглашение в канал) ─── */
|
||||
|
||||
static void cs_handle_channel_invite(struct chat_sync* cs, uint64_t peer,
|
||||
const char* ch_id, const uint8_t* pl, size_t len) {
|
||||
DEBUG_TRACE(DEBUG_CATEGORY_DB_SYNC, "%s: CHANNEL_INVITE ch=%s from=%016llx len=%zu",
|
||||
CS_ID, ch_id, (unsigned long long)peer, len);
|
||||
if (len < 1 + 8 + 32 + 32 + 64 + 1 + 64 + 8 + 1) {
|
||||
DEBUG_WARN(DEBUG_CATEGORY_DB_SYNC, "%s: CHANNEL_INVITE too short len=%zu peer=%016llx", CS_ID, len, (unsigned long long)peer);
|
||||
return;
|
||||
}
|
||||
/* Same payload format as CHANNEL_INFO_RESP */
|
||||
const uint8_t* p = pl;
|
||||
uint8_t nl = *p++;
|
||||
if (1 + nl + 8 + 32 + 32 + 64 + 1 + 64 + 8 + 1 > (int)len) { DEBUG_WARN(DEBUG_CATEGORY_DB_SYNC, "%s: CHANNEL_INVITE truncated", CS_ID); return; }
|
||||
char name[128]; memcpy(name, p, nl); name[nl] = '\0'; p += nl;
|
||||
uint64_t owner; memcpy(&owner, p, 8); p += 8;
|
||||
const uint8_t* x25519 = p; p += 32;
|
||||
const uint8_t* ed_pub = p; p += 32;
|
||||
const uint8_t* ch_sig = p; p += 64;
|
||||
uint8_t inv_flags = *p++;
|
||||
const uint8_t* inviter_join_sig = NULL; uint64_t inviter_join_ts = 0;
|
||||
if (inv_flags & PEERS_FLAG_HAS_JOIN) {
|
||||
if (p + 72 > pl + len) return; inviter_join_sig = p; p += 64; memcpy(&inviter_join_ts, p, 8); p += 8; }
|
||||
/* skip inviter_update_sig/ts/userinfo for now — verify channel sig only */
|
||||
p += 64; uint64_t inviter_update_ts; memcpy(&inviter_update_ts, p, 8); p += 8;
|
||||
uint8_t userinfo_len = *p++; p += userinfo_len;
|
||||
|
||||
/* Verify channel signature */
|
||||
uint8_t vmsg[1024]; size_t vlen = 0;
|
||||
vlen += snprintf((char*)vmsg + vlen, sizeof(vmsg) - vlen, "%s", ch_id) + 1;
|
||||
vlen += snprintf((char*)vmsg + vlen, sizeof(vmsg) - vlen, "%s", name) + 1;
|
||||
memcpy(vmsg + vlen, &owner, 8); vlen += 8;
|
||||
memcpy(vmsg + vlen, x25519, 32); vlen += 32;
|
||||
memcpy(vmsg + vlen, ed_pub, 32); vlen += 32;
|
||||
if (cs_ed25519_verify(ed_pub, vmsg, vlen, ch_sig) != 0) {
|
||||
DEBUG_ERROR(DEBUG_CATEGORY_DB_SYNC, "%s: CHANNEL_INVITE invalid ch_sig ch=%s from=%016llx", CS_ID, ch_id, (unsigned long long)peer);
|
||||
return;
|
||||
}
|
||||
|
||||
/* Check join_policy: server always autojoin, GUI uses chat_setting */
|
||||
int policy;
|
||||
{
|
||||
struct global_config *gcfg = &cs->inst->config->global;
|
||||
policy = gcfg->chatserver_enabled ? 0 : chat_setting_get_int("join_policy", 1);
|
||||
}
|
||||
DEBUG_DEBUG(DEBUG_CATEGORY_DB_SYNC, "%s: CHANNEL_INVITE policy=%d (chatserver=%d) ch=%s from=%016llx",
|
||||
CS_ID, policy, cs->inst->config->global.chatserver_enabled, ch_id, (unsigned long long)peer);
|
||||
|
||||
if (policy == 2) { /* deny */
|
||||
DEBUG_INFO(DEBUG_CATEGORY_DB_SYNC, "%s: CHANNEL_INVITE denied (policy=deny) ch=%s from=%016llx",
|
||||
CS_ID, ch_id, (unsigned long long)peer);
|
||||
return;
|
||||
}
|
||||
|
||||
if (policy == 0) { /* autojoin */
|
||||
DEBUG_INFO(DEBUG_CATEGORY_DB_SYNC, "%s: CHANNEL_INVITE autojoin ch=%s from=%016llx",
|
||||
CS_ID, ch_id, (unsigned long long)peer);
|
||||
/* Save channel and start join handshake */
|
||||
topo_node_sqlite_channel_put(cs->inst->topo_sqlite_db,
|
||||
ch_id, name, owner, x25519, NULL, ed_pub, NULL, ch_sig);
|
||||
chat_core_ensure_channel_ready(ch_id);
|
||||
/* Initiate join: connect back and send CHANNEL_INFO_REQ */
|
||||
chat_sync_join_channel(cs->inst, ch_id, peer);
|
||||
return;
|
||||
}
|
||||
|
||||
/* policy == ask — post event to GUI */
|
||||
{
|
||||
size_t inv_name_len = 0; char inv_name[128] = "";
|
||||
{ sqlite3* vdb = cs->inst->topo_sqlite_db;
|
||||
sqlite3_stmt* st = NULL;
|
||||
sqlite3_prepare_v2(vdb, "SELECT name FROM nodes WHERE node_id=?", -1, &st, NULL);
|
||||
if (st) {
|
||||
sqlite3_bind_int64(st, 1, (sqlite3_int64)peer);
|
||||
if (sqlite3_step(st) == SQLITE_ROW) {
|
||||
const unsigned char* nm = sqlite3_column_text(st, 0);
|
||||
if (nm) { snprintf(inv_name, sizeof(inv_name), "%s", nm); inv_name_len = strlen(inv_name); }
|
||||
}
|
||||
sqlite3_finalize(st);
|
||||
}
|
||||
}
|
||||
uint8_t evt[512]; int epos = 0;
|
||||
uint8_t cl = (uint8_t)strlen(ch_id); evt[epos++] = cl; memcpy(evt + epos, ch_id, cl); epos += cl;
|
||||
evt[epos++] = nl; memcpy(evt + epos, name, nl); epos += nl;
|
||||
memcpy(evt + epos, &peer, 8); epos += 8;
|
||||
evt[epos++] = (uint8_t)inv_name_len;
|
||||
if (inv_name_len) { memcpy(evt + epos, inv_name, inv_name_len); epos += inv_name_len; }
|
||||
chat_event_post(CHAT_EVT_INVITE_RECEIVED, evt, epos);
|
||||
DEBUG_INFO(DEBUG_CATEGORY_DB_SYNC, "%s: CHANNEL_INVITE ask posted to GUI ch=%s name=%s from=%016llx (%s)",
|
||||
CS_ID, ch_id, name, (unsigned long long)peer, inv_name);
|
||||
}
|
||||
}
|
||||
|
||||
/* ─── chat_sync_invite_to_channel: пригласить узел в наш канал ─── */
|
||||
|
||||
void chat_sync_invite_to_channel(struct UTUN_INSTANCE* inst, const char* ch_id, uint64_t target_node_id) {
|
||||
if (!g_cs || !inst || !ch_id || !ch_id[0]) return;
|
||||
if (target_node_id == 0 || target_node_id == inst->node_id) return;
|
||||
|
||||
uint64_t channel_id = strtoull(ch_id, NULL, 10);
|
||||
if (channel_id == 0) { DEBUG_ERROR(DEBUG_CATEGORY_DB_SYNC, "%s: invite_to_channel invalid ch_id='%s'", CS_ID, ch_id); return; }
|
||||
|
||||
/* Get channel data from DB */
|
||||
char ch_name[128]; uint64_t owner; uint8_t ch_x25519[32], ch_ed[32], ch_sig[64];
|
||||
if (topo_node_sqlite_channel_get(inst->topo_sqlite_db,
|
||||
ch_id, ch_name, (int)sizeof(ch_name), &owner, ch_x25519, ch_ed, ch_sig) != 0) {
|
||||
DEBUG_ERROR(DEBUG_CATEGORY_DB_SYNC, "%s: invite_to_channel channel not found: %s", CS_ID, ch_id);
|
||||
return;
|
||||
}
|
||||
|
||||
/* Check if already connected */
|
||||
struct ETCP_CONN* conn = cs_find_conn_for_node(inst, target_node_id);
|
||||
if (!conn || !conn->links_up || !conn->initialized) {
|
||||
/* Try to connect via conn_mgr */
|
||||
if (inst->topo_groups) {
|
||||
struct TOPO_GROUP* g = topo_groups_find(inst->topo_groups, channel_id);
|
||||
if (g) {
|
||||
struct TOPO_GROUP_NODE* nq = topo_node_find_by_id(g, target_node_id);
|
||||
if (nq && nq->handle) {
|
||||
/* Already have a handle — connection in progress or established */
|
||||
DEBUG_INFO(DEBUG_CATEGORY_DB_SYNC, "%s: invite_to_channel — existing handle for node 0x%016llx, waiting for conn_up",
|
||||
CS_ID, (unsigned long long)target_node_id);
|
||||
g_cs->pending_invite_ch_id = channel_id;
|
||||
g_cs->pending_invite_node_id = target_node_id;
|
||||
return;
|
||||
}
|
||||
}
|
||||
/* Use conn_mgr_open_invite with channel group context */
|
||||
struct TOPO_NODE* ni = u_calloc(1, sizeof(*ni));
|
||||
if (ni) {
|
||||
ni->node_id = target_node_id;
|
||||
/* Addresses loaded from node_addresses in DB by conn_mgr */
|
||||
int r = conn_mgr_open_invite(inst, channel_id, ni, target_node_id, NULL, NULL, NULL);
|
||||
if (r < 0) {
|
||||
DEBUG_ERROR(DEBUG_CATEGORY_DB_SYNC, "%s: invite_to_channel conn_mgr_open_invite failed rc=%d", CS_ID, r);
|
||||
}
|
||||
u_free(ni);
|
||||
}
|
||||
} else {
|
||||
DEBUG_WARN(DEBUG_CATEGORY_DB_SYNC, "%s: invite_to_channel — no topo_groups, cannot connect", CS_ID);
|
||||
return;
|
||||
}
|
||||
/* Mark pending: after conn_up, we send CHANNEL_INVITE.
|
||||
But we need to distinguish this from the joiner-side invite flow.
|
||||
Use a separate flag. */
|
||||
g_cs->pending_invite_ch_id = channel_id;
|
||||
g_cs->pending_invite_node_id = target_node_id;
|
||||
return;
|
||||
}
|
||||
|
||||
/* Already connected — send invite now */
|
||||
uint8_t join_sig[64] = {0}; uint64_t join_ts = 0;
|
||||
{ sqlite3* vdb = inst->topo_sqlite_db;
|
||||
topo_node_sqlite_member_get_join(vdb, ch_id, inst->node_id, join_sig, &join_ts); }
|
||||
uint8_t upd_sig[64] = {0}; uint64_t upd_ts = (uint64_t)ntp_time_get_seconds(inst);
|
||||
{
|
||||
char juser[256]; snprintf(juser, sizeof(juser), "{\"name\":\"%s\"}", inst->name[0] ? inst->name : "");
|
||||
uint8_t umsg[256]; size_t ulen = 0;
|
||||
memcpy(umsg + ulen, join_sig, 64); ulen += 64;
|
||||
memcpy(umsg + ulen, &upd_ts, 8); ulen += 8;
|
||||
size_t jl = strlen(juser); memcpy(umsg + ulen, juser, jl); umsg[ulen + jl] = '\0'; ulen += jl + 1;
|
||||
cs_ed25519_sign(inst->my_ed25519_privkey, umsg, ulen, upd_sig);
|
||||
}
|
||||
|
||||
uint8_t buf[1024]; size_t boff = 0;
|
||||
buf[boff++] = CS_MSG_CHANNEL_INVITE;
|
||||
uint8_t cnl = (uint8_t)strlen(ch_name);
|
||||
buf[boff++] = cnl; memcpy(buf + boff, ch_name, cnl); boff += cnl;
|
||||
memcpy(buf + boff, &owner, 8); boff += 8;
|
||||
memcpy(buf + boff, ch_x25519, 32); boff += 32;
|
||||
memcpy(buf + boff, ch_ed, 32); boff += 32;
|
||||
memcpy(buf + boff, ch_sig, 64); boff += 64;
|
||||
uint8_t inv_flags = (join_sig[0] || join_ts) ? PEERS_FLAG_HAS_JOIN : 0;
|
||||
buf[boff++] = inv_flags;
|
||||
if (inv_flags & PEERS_FLAG_HAS_JOIN) {
|
||||
memcpy(buf + boff, join_sig, 64); boff += 64;
|
||||
memcpy(buf + boff, &join_ts, 8); boff += 8;
|
||||
}
|
||||
memcpy(buf + boff, upd_sig, 64); boff += 64;
|
||||
memcpy(buf + boff, &upd_ts, 8); boff += 8;
|
||||
char juser[256]; snprintf(juser, sizeof(juser), "{\"name\":\"%s\"}", inst->name[0] ? inst->name : "");
|
||||
uint8_t il = (uint8_t)strlen(juser);
|
||||
buf[boff++] = il; memcpy(buf + boff, juser, il); boff += il;
|
||||
|
||||
cs_send(g_cs, ch_id, target_node_id, buf, boff);
|
||||
DEBUG_INFO(DEBUG_CATEGORY_DB_SYNC, "%s: sent CHANNEL_INVITE to 0x%016llx ch=%s",
|
||||
CS_ID, (unsigned long long)target_node_id, ch_id);
|
||||
}
|
||||
|
||||
/* ─── accept/deny incoming invite ─── */
|
||||
|
||||
void chat_sync_accept_invite(struct UTUN_INSTANCE* inst, uint64_t channel_id, uint64_t inviter_node_id) {
|
||||
if (!g_cs || !inst) return;
|
||||
char ch_id[64]; snprintf(ch_id, sizeof(ch_id), "%llu", (unsigned long long)channel_id);
|
||||
if (channel_id == 0 || inviter_node_id == 0) {
|
||||
DEBUG_ERROR(DEBUG_CATEGORY_DB_SYNC, "%s: accept_invite invalid params ch=%llu node=%016llx",
|
||||
CS_ID, (unsigned long long)channel_id, (unsigned long long)inviter_node_id);
|
||||
return;
|
||||
}
|
||||
DEBUG_INFO(DEBUG_CATEGORY_DB_SYNC, "%s: accept_invite ch=%llu inviter=0x%016llx",
|
||||
CS_ID, (unsigned long long)channel_id, (unsigned long long)inviter_node_id);
|
||||
chat_core_ensure_channel_ready(ch_id);
|
||||
chat_sync_join_channel(inst, ch_id, inviter_node_id);
|
||||
}
|
||||
|
||||
void chat_sync_deny_invite(struct UTUN_INSTANCE* inst, uint64_t channel_id, uint64_t inviter_node_id) {
|
||||
(void)inst;
|
||||
if (!g_cs) return;
|
||||
DEBUG_INFO(DEBUG_CATEGORY_DB_SYNC, "%s: deny_invite ch=%llu inviter=0x%016llx",
|
||||
CS_ID, (unsigned long long)channel_id, (unsigned long long)inviter_node_id);
|
||||
/* silently ignore — no response needed */
|
||||
}
|
||||
+15
-2
@@ -53,6 +53,7 @@ struct UASYNC;
|
||||
#define CS_MSG_WELCOME 0x0C
|
||||
#define CS_MSG_PEER_UPSERT 0x0D
|
||||
#define CS_MSG_PEER_REMOVE 0x0E
|
||||
#define CS_MSG_CHANNEL_INVITE 0x0F /* push-invite: inviter → joiner */
|
||||
|
||||
/* Protocol constants */
|
||||
#define CS_SEND_DATA_MAX 32
|
||||
@@ -72,11 +73,23 @@ void chat_sync_destroy(struct UTUN_INSTANCE* inst);
|
||||
/* Прямое ETCP-подключение к узлу (pubkey+адреса из БД) */
|
||||
void chat_sync_connect_node(struct UTUN_INSTANCE* inst, uint64_t node_id);
|
||||
|
||||
/* Подключиться к пиру по данным invite-ссылки (вызывается из uasync-потока) */
|
||||
/* Подключиться к пиру по данным invite-ссылки (вызывается из uasync-потока).
|
||||
password может быть NULL — тогда отправляется без пароля (совместимость с v1) */
|
||||
void chat_sync_connect_from_invite(uint64_t channel_id, uint64_t node_id,
|
||||
const uint8_t* pubkey_bin,
|
||||
const uint8_t* addrs_data, int addr_count,
|
||||
int addrs_data_len);
|
||||
int addrs_data_len,
|
||||
const char* password);
|
||||
|
||||
/* Пригласить target_node_id в канал ch_id (inviter → joiner).
|
||||
Отправляет CS_MSG_CHANNEL_INVITE с полной информацией о канале и своих адресах */
|
||||
void chat_sync_invite_to_channel(struct UTUN_INSTANCE* inst, const char* ch_id, uint64_t target_node_id);
|
||||
|
||||
/* Принять входящее приглашение — продолжаем join-протокол (CHANNEL_JOIN → WELCOME) */
|
||||
void chat_sync_accept_invite(struct UTUN_INSTANCE* inst, uint64_t channel_id, uint64_t inviter_node_id);
|
||||
|
||||
/* Отклонить входящее приглашение */
|
||||
void chat_sync_deny_invite(struct UTUN_INSTANCE* inst, uint64_t channel_id, uint64_t inviter_node_id);
|
||||
|
||||
/* Присоединиться к каналу через уже подключённый узел (uasync-поток).
|
||||
Соединение с target_node_id должно быть установлено (links_up && initialized).
|
||||
|
||||
@@ -52,6 +52,7 @@ typedef enum {
|
||||
SECTION_GUI,
|
||||
SECTION_CHAT,
|
||||
SECTION_NTP,
|
||||
SECTION_CHATSERVER,
|
||||
SECTION_LOG_UDP
|
||||
} section_type_t;
|
||||
|
||||
@@ -718,6 +719,51 @@ static int parse_client(const char *key, const char *value, struct CFG_CLIENT *c
|
||||
return -1;
|
||||
}
|
||||
|
||||
static int parse_size_with_suffix(const char *value, uint64_t *out_bytes) {
|
||||
if (!value || !out_bytes) return -1;
|
||||
char *end = NULL;
|
||||
uint64_t num = strtoull(value, &end, 10);
|
||||
if (end == value) return -1;
|
||||
while (*end && isspace((unsigned char)*end)) end++;
|
||||
if (*end == '\0') { *out_bytes = num; return 0; }
|
||||
if (*end == 'M' || *end == 'm') { *out_bytes = num * 1024ULL * 1024ULL; return 0; }
|
||||
if (*end == 'G' || *end == 'g') { *out_bytes = num * 1024ULL * 1024ULL * 1024ULL; return 0; }
|
||||
return -1;
|
||||
}
|
||||
|
||||
static int parse_chatserver(const char *key, const char *value, struct global_config *global, const char *filename, int line_num) {
|
||||
if (strcmp(key, "join_password") == 0) {
|
||||
strncpy(global->chatserver_join_password, value, sizeof(global->chatserver_join_password) - 1);
|
||||
global->chatserver_join_password[sizeof(global->chatserver_join_password) - 1] = '\0';
|
||||
return 0;
|
||||
}
|
||||
if (strcmp(key, "allowed_groups") == 0) {
|
||||
strncpy(global->chatserver_allowed_groups, value, sizeof(global->chatserver_allowed_groups) - 1);
|
||||
global->chatserver_allowed_groups[sizeof(global->chatserver_allowed_groups) - 1] = '\0';
|
||||
return 0;
|
||||
}
|
||||
if (strcmp(key, "storage_total_size") == 0) {
|
||||
uint64_t sz;
|
||||
if (parse_size_with_suffix(value, &sz) < 0) {
|
||||
DEBUG_ERROR(DEBUG_CATEGORY_CONFIG, "%s:%d: Invalid chatserver storage_total_size '%s' (expected number with M/G suffix)", filename, line_num, value);
|
||||
return -1;
|
||||
}
|
||||
global->chatserver_storage_total_size = sz;
|
||||
return 0;
|
||||
}
|
||||
if (strcmp(key, "storage_unit_size") == 0) {
|
||||
uint64_t sz;
|
||||
if (parse_size_with_suffix(value, &sz) < 0) {
|
||||
DEBUG_ERROR(DEBUG_CATEGORY_CONFIG, "%s:%d: Invalid chatserver storage_unit_size '%s' (expected number with M/G suffix)", filename, line_num, value);
|
||||
return -1;
|
||||
}
|
||||
global->chatserver_storage_unit_size = sz;
|
||||
return 0;
|
||||
}
|
||||
DEBUG_ERROR(DEBUG_CATEGORY_CONFIG, "%s:%d: Unknown chatserver option '%s'. Valid: join_policy, join_password, allowed_groups, storage_total_size, storage_unit_size", filename, line_num, key);
|
||||
return -1;
|
||||
}
|
||||
|
||||
static int parse_network(const char *key, const char *value, struct CFG_NETWORK *net, const char *filename, int line_num) {
|
||||
if (strcmp(key, "id") == 0) {
|
||||
net->id = strtoull(value, NULL, 16);
|
||||
@@ -758,6 +804,7 @@ static section_type_t parse_section_header(const char *line, char *name, size_t
|
||||
if (strcasecmp(section, "ntp") == 0) return SECTION_NTP;
|
||||
if (strcasecmp(section, "gui") == 0) return SECTION_GUI;
|
||||
if (strcasecmp(section, "chat") == 0) return SECTION_CHAT;
|
||||
if (strcasecmp(section, "chatserver") == 0) return SECTION_CHATSERVER;
|
||||
if (strcasecmp(section, "log_udp") == 0) return SECTION_LOG_UDP;
|
||||
|
||||
char *colon = strchr(section, ':');
|
||||
@@ -802,6 +849,11 @@ static struct utun_config* parse_config_internal(FILE *fp, const char *filename)
|
||||
cfg->global.ntp_servers = NULL;
|
||||
cfg->global.ntp_server_count = 0;
|
||||
cfg->global.ntp_resync_interval = 3600;
|
||||
cfg->global.chatserver_enabled = 0;
|
||||
cfg->global.chatserver_join_password[0] = '\0';
|
||||
cfg->global.chatserver_allowed_groups[0] = '\0';
|
||||
cfg->global.chatserver_storage_total_size = 0;
|
||||
cfg->global.chatserver_storage_unit_size = 0;
|
||||
|
||||
section_type_t cur_section = SECTION_UNKNOWN;
|
||||
struct CFG_SERVER *cur_server = NULL;
|
||||
@@ -993,6 +1045,12 @@ static struct utun_config* parse_config_internal(FILE *fp, const char *filename)
|
||||
/* chat_setting_set уже напечатал ошибку */
|
||||
}
|
||||
break;
|
||||
case SECTION_CHATSERVER:
|
||||
cfg->global.chatserver_enabled = 1;
|
||||
if (parse_chatserver(key, value, &cfg->global, filename, line_num) < 0) {
|
||||
/* parse_chatserver already printed the error */
|
||||
}
|
||||
break;
|
||||
case SECTION_GUI:
|
||||
break;
|
||||
default:
|
||||
@@ -1076,6 +1134,11 @@ struct utun_config* parse_config_from_buf(const char *buf, size_t len, const cha
|
||||
cfg->global.ntp_servers = NULL;
|
||||
cfg->global.ntp_server_count = 0;
|
||||
cfg->global.ntp_resync_interval = 3600;
|
||||
cfg->global.chatserver_enabled = 0;
|
||||
cfg->global.chatserver_join_password[0] = '\0';
|
||||
cfg->global.chatserver_allowed_groups[0] = '\0';
|
||||
cfg->global.chatserver_storage_total_size = 0;
|
||||
cfg->global.chatserver_storage_unit_size = 0;
|
||||
|
||||
section_type_t cur_section = SECTION_UNKNOWN;
|
||||
struct CFG_SERVER *cur_server = NULL;
|
||||
@@ -1117,6 +1180,7 @@ struct utun_config* parse_config_from_buf(const char *buf, size_t len, const cha
|
||||
case SECTION_NETWORK: if (cur_network) parse_network(key, value, cur_network, filename, line_num); break;
|
||||
case SECTION_NTP: if (strcmp(key, "enabled") == 0) cfg->global.ntp_enabled = strcasecmp(value, "yes") == 0 || strcasecmp(value, "1") == 0 || strcasecmp(value, "true") == 0; else if (strcmp(key, "server") == 0) { struct CFG_NTP_SERVER *ns = u_calloc(1, sizeof(struct CFG_NTP_SERVER)); if (ns) { strncpy(ns->name, value, sizeof(ns->name) - 1); ns->name[sizeof(ns->name) - 1] = '\0'; ns->next = cfg->global.ntp_servers; cfg->global.ntp_servers = ns; cfg->global.ntp_server_count++; } } else if (strcmp(key, "interval") == 0) { cfg->global.ntp_resync_interval = atoi(value); if (cfg->global.ntp_resync_interval < 60) cfg->global.ntp_resync_interval = 60; } else DEBUG_ERROR(DEBUG_CATEGORY_CONFIG, "%s:%d: Unknown ntp option '%s'", filename, line_num, key); break;
|
||||
case SECTION_CHAT: chat_setting_set(key, value); break;
|
||||
case SECTION_CHATSERVER: cfg->global.chatserver_enabled = 1; parse_chatserver(key, value, &cfg->global, filename, line_num); break;
|
||||
case SECTION_GUI: break;
|
||||
case SECTION_LOG_UDP:
|
||||
if (strcmp(key, "ip") == 0) strncpy(cfg->global.log_udp_ip, value, sizeof(cfg->global.log_udp_ip) - 1);
|
||||
@@ -1323,6 +1387,112 @@ void print_config(const struct utun_config *cfg) {
|
||||
}
|
||||
}
|
||||
|
||||
static const char invite_b64[] = "ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789+/";
|
||||
|
||||
void print_chatserver_invite_link(const struct utun_config *config, const struct CFG_SERVER *servers) {
|
||||
if (!config || !servers) return;
|
||||
const struct global_config *g = &config->global;
|
||||
if (!g->chatserver_enabled) return;
|
||||
if (strlen(g->my_public_key_hex) < SC_PUBKEY_SIZE * 2) {
|
||||
DEBUG_ERROR(DEBUG_CATEGORY_CONFIG, "print_chatserver_invite_link: no public key");
|
||||
return;
|
||||
}
|
||||
|
||||
uint8_t pub_bin[SC_PUBKEY_SIZE];
|
||||
/* Use first SC_PUBKEY_SIZE*2 hex chars (X25519 pubkey, may be part of longer combined key) */
|
||||
{
|
||||
char pub_hex_first[SC_PUBKEY_SIZE * 2 + 1];
|
||||
memcpy(pub_hex_first, g->my_public_key_hex, SC_PUBKEY_SIZE * 2);
|
||||
pub_hex_first[SC_PUBKEY_SIZE * 2] = '\0';
|
||||
if (hex_to_binary(pub_hex_first, pub_bin, SC_PUBKEY_SIZE) < 0) {
|
||||
DEBUG_ERROR(DEBUG_CATEGORY_CONFIG, "print_chatserver_invite_link: invalid pubkey hex");
|
||||
return;
|
||||
}
|
||||
}
|
||||
|
||||
/* Collect server addresses */
|
||||
struct {
|
||||
int family;
|
||||
uint8_t addr[16];
|
||||
uint16_t port;
|
||||
uint8_t proto;
|
||||
} addrs[32];
|
||||
int addr_count = 0;
|
||||
for (const struct CFG_SERVER *s = servers; s && addr_count < 32; s = s->next) {
|
||||
if (s->ip.ss_family == AF_INET) {
|
||||
struct sockaddr_in *sin = (struct sockaddr_in *)&s->ip;
|
||||
addrs[addr_count].family = 4;
|
||||
memcpy(addrs[addr_count].addr, &sin->sin_addr, 4);
|
||||
addrs[addr_count].port = ntohs(sin->sin_port);
|
||||
addrs[addr_count].proto = (s->transport == 1) ? 0x02 : 0x01;
|
||||
addr_count++;
|
||||
} else if (s->ip.ss_family == AF_INET6) {
|
||||
struct sockaddr_in6 *sin6 = (struct sockaddr_in6 *)&s->ip;
|
||||
addrs[addr_count].family = 6;
|
||||
memcpy(addrs[addr_count].addr, &sin6->sin6_addr, 16);
|
||||
addrs[addr_count].port = ntohs(sin6->sin6_port);
|
||||
addrs[addr_count].proto = (s->transport == 1) ? 0x02 : 0x01;
|
||||
addr_count++;
|
||||
}
|
||||
}
|
||||
if (addr_count == 0) {
|
||||
DEBUG_ERROR(DEBUG_CATEGORY_CONFIG, "print_chatserver_invite_link: no server addresses");
|
||||
return;
|
||||
}
|
||||
|
||||
/* Build binary blob */
|
||||
uint8_t raw[4096];
|
||||
size_t pos = 0;
|
||||
int has_pass = g->chatserver_join_password[0] ? 1 : 0;
|
||||
raw[pos++] = has_pass ? 0x02 : 0x01; /* version */
|
||||
if (has_pass) {
|
||||
size_t plen = strlen(g->chatserver_join_password);
|
||||
raw[pos++] = (uint8_t)plen;
|
||||
memcpy(raw + pos, g->chatserver_join_password, plen); pos += plen;
|
||||
}
|
||||
/* channel_id = 0 (8 bytes BE) */
|
||||
for (int i = 0; i < 8; i++) raw[pos++] = 0;
|
||||
|
||||
int ia = 0;
|
||||
while (ia < addr_count) {
|
||||
int rem = addr_count - ia;
|
||||
int cnt = (rem > 4) ? 4 : rem;
|
||||
uint8_t header = (uint8_t)((cnt - 1) & 0x03);
|
||||
for (int j = 0; j < cnt; j++) if (addrs[ia + j].family == 6) header |= (uint8_t)(1 << (2 + j));
|
||||
raw[pos++] = header;
|
||||
memcpy(raw + pos, pub_bin, SC_PUBKEY_SIZE); pos += SC_PUBKEY_SIZE;
|
||||
for (int j = 0; j < cnt; j++) {
|
||||
int ip_len = (addrs[ia + j].family == 6) ? 16 : 4;
|
||||
raw[pos++] = 0; /* socketId */
|
||||
raw[pos++] = addrs[ia + j].proto; /* proto */
|
||||
memcpy(raw + pos, addrs[ia + j].addr, (size_t)ip_len); pos += (size_t)ip_len;
|
||||
raw[pos++] = (uint8_t)(addrs[ia + j].port >> 8);
|
||||
raw[pos++] = (uint8_t)(addrs[ia + j].port & 0xFF);
|
||||
}
|
||||
ia += cnt;
|
||||
}
|
||||
|
||||
/* Base64 encode */
|
||||
char link[2048];
|
||||
memcpy(link, "utun://", 7);
|
||||
size_t opos = 7;
|
||||
size_t i = 0;
|
||||
while (i < pos) {
|
||||
uint32_t val = (uint32_t)raw[i] << 16;
|
||||
val |= (i + 1 < pos) ? (uint32_t)raw[i + 1] << 8 : 0;
|
||||
val |= (i + 2 < pos) ? (uint32_t)raw[i + 2] : 0;
|
||||
link[opos++] = invite_b64[(val >> 18) & 0x3F];
|
||||
link[opos++] = invite_b64[(val >> 12) & 0x3F];
|
||||
link[opos++] = (i + 1 < pos) ? invite_b64[(val >> 6) & 0x3F] : '=';
|
||||
link[opos++] = (i + 2 < pos) ? invite_b64[val & 0x3F] : '=';
|
||||
i += 3;
|
||||
}
|
||||
link[opos] = '\0';
|
||||
|
||||
DEBUG_INFO(DEBUG_CATEGORY_CONFIG, "Chatserver invite link: %s", link);
|
||||
fprintf(stderr, "Chatserver invite link: %s\n", link);
|
||||
}
|
||||
|
||||
int update_config_keys(const char *filename, const char *priv_key, const char *pub_key) {
|
||||
// Minimal implementation: just append to file
|
||||
FILE *fp = fopen(filename, "a");
|
||||
|
||||
@@ -194,6 +194,13 @@ struct global_config {
|
||||
struct CFG_NTP_SERVER *ntp_servers; // linked list, unlimited
|
||||
int ntp_server_count;
|
||||
int ntp_resync_interval; // seconds, default 3600
|
||||
|
||||
// Chatserver configuration ([chatserver] section)
|
||||
int chatserver_enabled; // 1 = section present
|
||||
char chatserver_join_password[128]; // пароль для входа в группу
|
||||
char chatserver_allowed_groups[1024]; // "all" или hex-ID через запятую
|
||||
uint64_t chatserver_storage_total_size; // bytes, общий лимит хранилища (0 = безлимит)
|
||||
uint64_t chatserver_storage_unit_size; // bytes, лимит одного файла (0 = отключено)
|
||||
};
|
||||
|
||||
struct utun_config {
|
||||
@@ -209,6 +216,7 @@ struct utun_config* parse_config(const char *filename);
|
||||
struct utun_config* parse_config_from_buf(const char *buf, size_t len, const char *filename);
|
||||
void free_config(struct utun_config *config);
|
||||
void print_config(const struct utun_config *config);
|
||||
void print_chatserver_invite_link(const struct utun_config *config, const struct CFG_SERVER *servers);
|
||||
int update_config_keys(const char *filename, const char *priv_key, const char *pub_key);
|
||||
|
||||
#ifdef __cplusplus
|
||||
|
||||
@@ -348,6 +348,7 @@ int main(int argc, char *argv[]) {
|
||||
// Print config for debugging
|
||||
if (args.foreground) {
|
||||
print_config(instance->config);
|
||||
print_chatserver_invite_link(instance->config, instance->config->servers);
|
||||
}
|
||||
|
||||
// Initialize all components (TUN, routing, connections)
|
||||
|
||||
Reference in new issue
Block a user