chat_join: модуль join-протокола, invite-ссылки v2, синхронизация мемберов

This commit is contained in:
evgeny committed 2026-09-14 19:57:27 +03:00
1 parent a01a4ddba4
commit 75e39d2fee
41 files changed
+2206 -471

No files matched your search

@@ -223,7 +223,7 @@ class MainActivity : ComponentActivity() {
},
onConnect = { data: InviteData ->
val addrs = data.serializeAddrs()
NativeLib.joinChannel(data.channelId, data.nodeId, data.pubkey, addrs, data.addrs.size)
NativeLib.joinChannel(data.channelId, data.nodeId, data.joinKey, data.pubkey, addrs, data.addrs.size)
showJoinDialog = false
},
onDismiss = { showJoinDialog = false }
@@ -386,8 +386,8 @@ class ChatRepository {
NativeLib.photoSend(channelId, filePath, width, height)
fun createChannel(name: String, channelId: String) = NativeLib.createChannel(name, channelId)
fun connectNode(address: String, port: Int, pubkeyHex: String) = NativeLib.connectNode(address, port, pubkeyHex)
fun joinChannel(channelId: Long, nodeId: Long, pubkey: ByteArray, addrs: ByteArray, addrCount: Int) =
NativeLib.joinChannel(channelId, nodeId, pubkey, addrs, addrCount)
fun joinChannel(channelId: Long, nodeId: Long, joinKey: Long, pubkey: ByteArray, addrs: ByteArray, addrCount: Int) =
NativeLib.joinChannel(channelId, nodeId, joinKey, pubkey, addrs, addrCount)
fun connectChannel(channelId: String) = NativeLib.connectChannel(channelId)
fun connectMemberNode(channelId: String, nodeId: Long) = NativeLib.connectMemberNode(channelId, nodeId)
fun isReady() = NativeLib.isReady()
@@ -29,6 +29,7 @@ data class InviteAddr(
data class InviteData(
val channelId: Long,
val joinKey: Long,
val pubkey: ByteArray,
val nodeId: Long,
val addrs: List<InviteAddr>
@@ -98,7 +99,14 @@ object InviteLink {
}
off += 8
LogManager.addLog("DEBUG", "INVITE", "decode: chId=0x%016x".format(chId))
var joinKey = 0L
for (i in 0 until 8) {
if (off + i >= raw.size) return null
joinKey = (joinKey shl 8) or (raw[off + i].toLong() and 0xFF)
}
off += 8
LogManager.addLog("DEBUG", "INVITE", "decode: chId=0x%016x joinKey=0x%016x".format(chId, joinKey))
var pubkey: ByteArray? = null
val addrs = mutableListOf<InviteAddr>()
@@ -135,7 +143,7 @@ object InviteLink {
val pkHex = pubkey.take(8).joinToString("") { "%02x".format(it) }
LogManager.addLog("INFO", "INVITE", "decode: ch=0x%016x node=0x%016x pk=%s... addrs=%d".format(
chId, nodeId, pkHex, addrs.size))
return InviteData(channelId = chId, pubkey = pubkey, nodeId = nodeId, addrs = addrs)
return InviteData(channelId = chId, joinKey = joinKey, pubkey = pubkey, nodeId = nodeId, addrs = addrs)
}
fun addrToString(addr: InviteAddr): String {
@@ -68,9 +68,9 @@ object NativeLib {
fun sendMessage(channelId: String, text: String) { nativeSendMessage(channelId, text) }
fun createChannel(name: String, channelId: String) { nativeCreateChannel(name, channelId) }
fun connectNode(address: String, port: Int, pubkeyHex: String) { nativeConnectNode(address, port, pubkeyHex) }
fun joinChannel(channelId: Long, nodeId: Long, pubkey: ByteArray, addrs: ByteArray, addrCount: Int) {
fun joinChannel(channelId: Long, nodeId: Long, joinKey: Long, pubkey: ByteArray, addrs: ByteArray, addrCount: Int) {
LogManager.addLog("INFO", "JNI", "joinChannel: calling native")
nativeJoinChannel(channelId, nodeId, pubkey, addrs, addrCount)
nativeJoinChannel(channelId, nodeId, joinKey, pubkey, addrs, addrCount)
LogManager.addLog("INFO", "JNI", "joinChannel: native returned")
}
fun deriveNodeId(pubkey: ByteArray): Long = nativeDeriveNodeId(pubkey)
@@ -147,7 +147,7 @@ object NativeLib {
private external fun nativeSendMessage(channelId: String, text: String)
private external fun nativeCreateChannel(name: String, channelId: String)
private external fun nativeConnectNode(address: String, port: Int, pubkeyHex: String)
private external fun nativeJoinChannel(channelId: Long, nodeId: Long, pubkey: ByteArray, addrs: ByteArray, addrCount: Int)
private external fun nativeJoinChannel(channelId: Long, nodeId: Long, joinKey: Long, pubkey: ByteArray, addrs: ByteArray, addrCount: Int)
private external fun nativeDeriveNodeId(pubkey: ByteArray): Long
private external fun nativeConnectChannel(channelId: String)
private external fun nativeConnectMemberNode(channelId: String, nodeId: Long)
@@ -589,11 +589,11 @@ class ChatViewModel : ViewModel() {
r.connectNode(address, port, pubkeyHex)
}
fun joinChannel(channelId: Long, nodeId: Long, pubkey: ByteArray, addrs: ByteArray, addrCount: Int) {
fun joinChannel(channelId: Long, nodeId: Long, joinKey: Long, pubkey: ByteArray, addrs: ByteArray, addrCount: Int) {
val r = repo
if (r == null) { LogManager.addLog("ERROR", "VM", "joinChannel: repo is NULL"); return }
LogManager.addLog("INFO", "VM", "joinChannel: ch=0x%016x node=0x%016x".format(channelId, nodeId))
r.joinChannel(channelId, nodeId, pubkey, addrs, addrCount)
r.joinChannel(channelId, nodeId, joinKey, pubkey, addrs, addrCount)
}
fun startVoiceRecording() {
@@ -315,7 +315,7 @@ static int handle_join(int fd, int id, const char* json) {
}
DEBUG_INFO(DEBUG_CATEGORY_GENERAL, "join: calling chat_sync_connect_from_invite...");
chat_sync_connect_from_invite(d.channelId, d.nodeId, d.pubkey, addrs_buf, d.addrCount, addrs_len, d.password);
chat_sync_connect_from_invite(d.channelId, d.nodeId, d.pubkey, addrs_buf, d.addrCount, addrs_len, d.password, d.join_key);
char resp[512];
snprintf(resp, sizeof(resp),
@@ -223,7 +223,7 @@ void utun_bridge_connect_node(const char* address, int port, const char* pubkey_
void utun_bridge_join_channel(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, uint64_t join_key) {
uint64_t real_node_id = sc_derive_node_id_from_pubkey(pubkey_bin);
if (real_node_id != node_id)
bridge_log(BLEV_WARN, "join: node_id Kotlin=0x%016llx != C=0x%016llx — using C value",
@@ -235,7 +235,7 @@ void utun_bridge_join_channel(uint64_t channel_id, uint64_t node_id,
utun_bridge_event(6, "{\"node_id\":0,\"result\":-7}"); /* CHAT_EVT_CONNECT_RESULT */
return;
}
chat_sync_connect_from_invite(channel_id, real_node_id, pubkey_bin, addrs_data, addr_count, addrs_data_len, NULL);
chat_sync_connect_from_invite(channel_id, real_node_id, pubkey_bin, addrs_data, addr_count, addrs_data_len, NULL, join_key);
}
void utun_bridge_request_invite_candidates(const char* channel_id) {
@@ -1466,7 +1466,7 @@ JNIEXPORT jboolean JNICALL Java_com_utun_chat_data_NativeLib_nativeInit(
/* ── nativeJoinChannel ── */
JNIEXPORT void JNICALL Java_com_utun_chat_data_NativeLib_nativeJoinChannel(
JNIEnv* env, jobject thiz, jlong channelId, jlong nodeId,
JNIEnv* env, jobject thiz, jlong channelId, jlong nodeId, jlong joinKey,
jbyteArray pubkey, jbyteArray addrsData, jint addrCount) {
(void)thiz;
if (!pubkey || !addrsData) {
@@ -1483,7 +1483,7 @@ JNIEXPORT void JNICALL Java_com_utun_chat_data_NativeLib_nativeJoinChannel(
uint8_t* ad = (uint8_t*)(*env)->GetByteArrayElements(env, addrsData, NULL);
bridge_log(BLEV_DEBUG, "nativeJoinChannel ch=%lld node=0x%llx pk=%02x%02x... addrs_sz=%d",
(long long)channelId, (unsigned long long)nodeId, pk[0], pk[1], (int)ad_len);
utun_bridge_join_channel((uint64_t)channelId, (uint64_t)nodeId, pk, ad, (int)addrCount, (int)ad_len);
utun_bridge_join_channel((uint64_t)channelId, (uint64_t)nodeId, pk, ad, (int)addrCount, (int)ad_len, (uint64_t)joinKey);
(*env)->ReleaseByteArrayElements(env, addrsData, (jbyte*)ad, JNI_ABORT);
(*env)->ReleaseByteArrayElements(env, pubkey, (jbyte*)pk, JNI_ABORT);
}
@@ -55,7 +55,7 @@ void utun_bridge_connect_node(const char* address, int port, const char* pubkey_
void utun_bridge_join_channel(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, uint64_t join_key);
/* Connect to all peers of a channel (auto-connect on select) */
void utun_bridge_connect_channel(const char* channel_id);
@@ -104,6 +104,16 @@ int invite_link_decode(const char* link, size_t link_len, struct InviteDataC* ou
for (int i = 0; i < 8; i++)
chId = (chId << 8) | raw[off++];
out->channelId = chId;
if (off + 8 > raw_len) {
if (error_buf && error_buf_size) snprintf(error_buf, error_buf_size, "truncated at join_key");
return -1;
}
uint64_t jk = 0;
for (int i = 0; i < 8; i++)
jk = (jk << 8) | raw[off++];
out->join_key = jk;
memset(out->pubkey, 0, INVITE_PUBKEY_SIZE);
out->addrCount = 0;
@@ -181,6 +191,7 @@ int invite_link_encode(const struct InviteDataC* data, const char* password, cha
memcpy(raw + pos, password, plen); pos += plen;
}
for (int i = 7; i >= 0; i--) raw[pos++] = (uint8_t)((data->channelId >> (i * 8)) & 0xFF);
for (int i = 7; i >= 0; i--) raw[pos++] = (uint8_t)((data->join_key >> (i * 8)) & 0xFF);
uint8_t pk[32]; memcpy(pk, data->pubkey, 32);
int ia = 0;
@@ -27,6 +27,7 @@ struct InviteAddrC {
struct InviteDataC {
uint64_t channelId;
uint64_t join_key; /* 64-битный ключ входа (генерируется инвайтером) */
uint8_t pubkey[INVITE_PUBKEY_SIZE];
uint64_t nodeId;
uint8_t addrCount;
+11
View File
@@ -116,6 +116,17 @@ InviteData decodeInviteLink(const QString& link) {
chId = (chId << 8) | data[off++];
d.channelId = chId;
/* join_key: uint64 big-endian */
if (off + 8 > len) {
d.error = QStringLiteral("truncated at join_key");
GUI_ERROR("decodeInviteLink: truncated at join_key");
return d;
}
uint64_t jk = 0;
for (int i = 0; i < 8; i++)
jk = (jk << 8) | data[off++];
d.joinKey = jk;
bool firstBlock = true;
while (off + 1 <= len) {
uint8_t header = data[off++];
+1
View File
@@ -21,6 +21,7 @@ struct InviteAddr {
struct InviteData {
uint64_t channelId = 0;
uint64_t joinKey = 0; // 64-битный ключ входа (генерируется инвайтером)
QByteArray pubkey;
QList<InviteAddr> addrs;
QString password;
+1 -1
View File
@@ -203,7 +203,7 @@ void JoinDialog::onConnectClicked() {
chat_sync_connect_from_invite(d.channelId, nodeId,
(const uint8_t*)d.pubkey.constData(),
(const uint8_t*)addrsBuf.constData(), addrCount, addrsBuf.size(), pass);
(const uint8_t*)addrsBuf.constData(), addrCount, addrsBuf.size(), pass, d.joinKey);
}
void JoinDialog::onConnectResult(uint64_t nodeId, uint64_t channelId, int result) {