invite: add proto field (UDP/TCP/BOTH) to invite link binary format
Format per v4 addr: family(1) + socketId(1) + proto(1) + addr(4) + port(2) = 9 bytes (was 8) Proto values: 0x01=UDP, 0x02=TCP, 0x03=BOTH Affects: invite_link_c.h/c (Android), invite_link.h/cpp (desktop), chat_sync.c, conn_mgr.c, joindialog.cpp, utun_node.cpp
This commit is contained in:
1 parent
5276c1288c
commit
d0cca27edd
23 files changed
+329
-61
No files matched your search
@@ -1,13 +1,8 @@
|
||||
package com.utun.chat
|
||||
|
||||
import android.app.Application
|
||||
import android.content.Intent
|
||||
import com.utun.chat.data.LogManager
|
||||
import com.utun.chat.data.ConfigProvider
|
||||
import com.utun.chat.headless.HeadlessService
|
||||
import kotlinx.coroutines.CoroutineScope
|
||||
import kotlinx.coroutines.Dispatchers
|
||||
import kotlinx.coroutines.launch
|
||||
|
||||
class ChatApplication : Application() {
|
||||
lateinit var logManager: LogManager
|
||||
@@ -19,19 +14,15 @@ class ChatApplication : Application() {
|
||||
logManager = LogManager
|
||||
configProvider = ConfigProvider(this)
|
||||
|
||||
// First launch: generate keys + detect interfaces
|
||||
val scope = CoroutineScope(Dispatchers.IO)
|
||||
scope.launch {
|
||||
if (configProvider.isFirstLaunch()) {
|
||||
/* First launch config */
|
||||
if (configProvider.isFirstLaunch()) {
|
||||
kotlinx.coroutines.runBlocking {
|
||||
LogManager.addLog("INFO", "APP", "First launch — configuring")
|
||||
configProvider.generateKeys()
|
||||
configProvider.detectInterfaces()
|
||||
configProvider.markLaunched()
|
||||
LogManager.addLog("INFO", "APP", "First launch done, ${configProvider.loadServers().size} interfaces detected")
|
||||
}
|
||||
// Start uTun service
|
||||
val intent = Intent(this@ChatApplication, HeadlessService::class.java)
|
||||
startService(intent)
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -5,12 +5,15 @@ import androidx.activity.ComponentActivity
|
||||
import androidx.activity.compose.BackHandler
|
||||
import androidx.activity.compose.setContent
|
||||
import androidx.compose.runtime.*
|
||||
import androidx.core.content.ContextCompat
|
||||
import androidx.lifecycle.viewmodel.compose.viewModel
|
||||
import com.utun.chat.data.ChatRepository
|
||||
import com.utun.chat.data.InviteData
|
||||
import com.utun.chat.data.NativeLib
|
||||
import com.utun.chat.headless.HeadlessService
|
||||
import com.utun.chat.ui.screens.*
|
||||
import com.utun.chat.viewmodel.ChatViewModel
|
||||
import android.content.Intent
|
||||
import java.io.File
|
||||
|
||||
class MainActivity : ComponentActivity() {
|
||||
@@ -19,6 +22,9 @@ class MainActivity : ComponentActivity() {
|
||||
override fun onCreate(savedInstanceState: Bundle?) {
|
||||
super.onCreate(savedInstanceState)
|
||||
|
||||
/* Start uTun headless service (must be from foreground Activity on API 34+) */
|
||||
ContextCompat.startForegroundService(this, Intent(this, HeadlessService::class.java))
|
||||
|
||||
val dbFile = File(filesDir, "chats.db")
|
||||
val repo = ChatRepository(dbFile.absolutePath)
|
||||
|
||||
|
||||
@@ -3,7 +3,8 @@ package com.utun.chat.data
|
||||
import android.database.sqlite.SQLiteDatabase
|
||||
import org.json.JSONObject
|
||||
|
||||
data class Channel(val id: String, val name: String, val lastMsgAt: Long = 0, val lastMsg: String = "")
|
||||
data class Channel(val id: String, val name: String, val lastMsgAt: Long = 0, val lastMsg: String = "",
|
||||
val peersOnline: Int = 0)
|
||||
data class Message(val id: Long, val author: String, val text: String, val ts: Long, val isOutgoing: Boolean = false)
|
||||
|
||||
class ChatRepository(dbPath: String) {
|
||||
@@ -11,12 +12,10 @@ class ChatRepository(dbPath: String) {
|
||||
private var myNodeId: Long? = null
|
||||
|
||||
init {
|
||||
/* Ensure DB file exists — wait up to 5 seconds for service to create it */
|
||||
val f = java.io.File(dbPath)
|
||||
var tries = 0
|
||||
while (tries < 50 && !f.exists()) { Thread.sleep(100); tries++ }
|
||||
db = SQLiteDatabase.openDatabase(dbPath, null, SQLiteDatabase.OPEN_READONLY)
|
||||
/* Don't crash if tables aren't created yet */
|
||||
try {
|
||||
db.rawQuery("SELECT node_id FROM local_identity WHERE id=1", null).use { cur ->
|
||||
if (cur.moveToFirst()) myNodeId = cur.getLong(0)
|
||||
@@ -31,18 +30,29 @@ class ChatRepository(dbPath: String) {
|
||||
"SELECT channel_id, name, last_msg_at FROM channels ORDER BY last_msg_at DESC, name ASC",
|
||||
null
|
||||
).use { cur ->
|
||||
while (cur.moveToNext()) {
|
||||
val chId = cur.getString(0) ?: continue
|
||||
val name = cur.getString(1) ?: chId
|
||||
val lastAt = cur.getLong(2)
|
||||
val last = getLastMsgText(chId)
|
||||
list.add(Channel(id = chId, name = name, lastMsgAt = lastAt, lastMsg = last))
|
||||
while (cur.moveToNext()) {
|
||||
val chId = cur.getString(0) ?: continue
|
||||
val name = cur.getString(1) ?: chId
|
||||
val lastAt = cur.getLong(2)
|
||||
val last = getLastMsgText(chId)
|
||||
val online = getPeersOnline(chId)
|
||||
list.add(Channel(id = chId, name = name, lastMsgAt = lastAt, lastMsg = last, peersOnline = online))
|
||||
}
|
||||
}
|
||||
}
|
||||
} catch (_: Exception) {}
|
||||
return list
|
||||
}
|
||||
|
||||
fun getPeersOnline(channelId: String): Int {
|
||||
val tbl = peersTableName(channelId)
|
||||
try {
|
||||
db.rawQuery("SELECT COUNT(*) FROM \"$tbl\"", null).use { cur ->
|
||||
if (cur.moveToFirst()) return cur.getInt(0)
|
||||
}
|
||||
} catch (_: Exception) {}
|
||||
return 0
|
||||
}
|
||||
|
||||
fun getMessages(channelId: String, limit: Int = 50): List<Message> {
|
||||
val tbl = msgTableName(channelId)
|
||||
val list = mutableListOf<Message>()
|
||||
@@ -63,17 +73,14 @@ class ChatRepository(dbPath: String) {
|
||||
isOutgoing = mid != null && nodeId == mid))
|
||||
}
|
||||
}
|
||||
} catch (_: Exception) { /* table may not exist yet */ }
|
||||
} catch (_: Exception) {}
|
||||
return list
|
||||
}
|
||||
|
||||
private fun getLastMsgText(channelId: String): String {
|
||||
val tbl = msgTableName(channelId)
|
||||
try {
|
||||
db.rawQuery(
|
||||
"SELECT node_id, data FROM \"$tbl\" ORDER BY id DESC LIMIT 1",
|
||||
null
|
||||
).use { cur ->
|
||||
db.rawQuery("SELECT node_id, data FROM \"$tbl\" ORDER BY id DESC LIMIT 1", null).use { cur ->
|
||||
if (cur.moveToFirst()) {
|
||||
val author = cur.getLong(0).toHex()
|
||||
val text = parseMsgText(cur.getBlob(1))
|
||||
@@ -89,16 +96,20 @@ class ChatRepository(dbPath: String) {
|
||||
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 connectChannel(channelId: String) = NativeLib.connectChannel(channelId)
|
||||
fun isReady() = NativeLib.isReady()
|
||||
|
||||
companion object {
|
||||
fun msgTableName(chId: String): String {
|
||||
val san = StringBuilder()
|
||||
fun msgTableName(chId: String): String = "msg_" + sanitize(chId)
|
||||
fun peersTableName(chId: String): String = "peers_" + sanitize(chId)
|
||||
|
||||
private fun sanitize(chId: String): String {
|
||||
val sb = StringBuilder()
|
||||
for (c in chId) {
|
||||
if (c in 'a'..'z' || c in 'A'..'Z' || c in '0'..'9' || c == '_') san.append(c)
|
||||
else san.append('_')
|
||||
if (c in 'a'..'z' || c in 'A'..'Z' || c in '0'..'9' || c == '_') sb.append(c)
|
||||
else sb.append('_')
|
||||
}
|
||||
return "msg_$san"
|
||||
return sb.toString()
|
||||
}
|
||||
|
||||
fun parseMsgText(data: ByteArray?): String {
|
||||
|
||||
@@ -65,6 +65,7 @@ object NativeLib {
|
||||
fun joinChannel(channelId: Long, nodeId: Long, pubkey: ByteArray, addrs: ByteArray, addrCount: Int) {
|
||||
nativeJoinChannel(channelId, nodeId, pubkey, addrs, addrCount)
|
||||
}
|
||||
fun connectChannel(channelId: String) { nativeConnectChannel(channelId) }
|
||||
fun getChannels(): String = nativeGetChannels() ?: "[]"
|
||||
fun getMessages(channelId: String, limit: Int = 50): String = nativeGetMessages(channelId, limit) ?: "[]"
|
||||
fun setDebugLevel(category: String, level: String) { nativeSetDebugLevel(category, level) }
|
||||
@@ -81,6 +82,7 @@ object NativeLib {
|
||||
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 nativeConnectChannel(channelId: String)
|
||||
private external fun nativeGetChannels(): String?
|
||||
private external fun nativeGetMessages(channelId: String, limit: Int): String?
|
||||
private external fun nativeSetDebugLevel(category: String, level: String)
|
||||
|
||||
@@ -40,6 +40,7 @@ class HeadlessService : Service() {
|
||||
val listenPort = provider.getInt("node.listen_port")
|
||||
|
||||
val configText = buildString {
|
||||
appendLine("[global]")
|
||||
if (nodeName.isNotBlank()) appendLine("my_node_name=$nodeName")
|
||||
if (pubKey.length == 64) appendLine("my_public_key=$pubKey")
|
||||
if (privKey.length == 64) appendLine("my_private_key=$privKey")
|
||||
@@ -64,12 +65,6 @@ class HeadlessService : Service() {
|
||||
/* Init callbacks first (log + config) — required before start */
|
||||
NativeLib.init(dbDir, 9999)
|
||||
|
||||
NativeLib.setEventCallback(object : com.utun.chat.data.ChatEventCallback {
|
||||
override fun onEvent(type: Int, data: ByteArray?) {
|
||||
LogManager.addLog("DEBUG", "EVENT", "type=$type len=${data?.size ?: 0}")
|
||||
}
|
||||
})
|
||||
|
||||
val ok = NativeLib.start(configText)
|
||||
if (ok) {
|
||||
LogManager.addLog("INFO", "SRV", "uTun core started")
|
||||
|
||||
+8
-7
@@ -26,18 +26,18 @@ fun MessageBubble(message: Message) {
|
||||
) {
|
||||
Column(
|
||||
modifier = Modifier
|
||||
.widthIn(max = 280.dp)
|
||||
.widthIn(max = 300.dp)
|
||||
.clip(RoundedCornerShape(12.dp))
|
||||
.background(bgColor)
|
||||
.padding(10.dp)
|
||||
.padding(12.dp)
|
||||
) {
|
||||
Text(message.author, fontSize = 12.sp, fontWeight = FontWeight.Bold, color = textColor.copy(alpha = 0.7f))
|
||||
Text(message.author, fontSize = 13.sp, fontWeight = FontWeight.Bold, color = textColor.copy(alpha = 0.7f))
|
||||
Spacer(Modifier.height(2.dp))
|
||||
Text(message.text, fontSize = 15.sp, color = textColor)
|
||||
Text(message.text, fontSize = 17.sp, color = textColor)
|
||||
Spacer(Modifier.height(2.dp))
|
||||
Text(
|
||||
formatTime(message.ts),
|
||||
fontSize = 10.sp,
|
||||
fontSize = 11.sp,
|
||||
color = textColor.copy(alpha = 0.5f),
|
||||
modifier = Modifier.align(Alignment.End)
|
||||
)
|
||||
@@ -50,10 +50,11 @@ fun InputBar(
|
||||
text: String,
|
||||
onTextChange: (String) -> Unit,
|
||||
onSend: () -> Unit,
|
||||
enabled: Boolean = true
|
||||
enabled: Boolean = true,
|
||||
modifier: Modifier = Modifier
|
||||
) {
|
||||
Row(
|
||||
modifier = Modifier.fillMaxWidth().padding(8.dp),
|
||||
modifier = modifier.fillMaxWidth().padding(8.dp),
|
||||
verticalAlignment = Alignment.CenterVertically
|
||||
) {
|
||||
OutlinedTextField(
|
||||
|
||||
+11
-3
@@ -36,8 +36,8 @@ fun ChannelListScreen(
|
||||
title = { Text("uTun Chat") },
|
||||
actions = {
|
||||
Text(
|
||||
if (connected) "ON" else "OFF",
|
||||
color = if (connected) MaterialTheme.colorScheme.primary else MaterialTheme.colorScheme.error,
|
||||
"ON",
|
||||
color = MaterialTheme.colorScheme.primary,
|
||||
modifier = Modifier.padding(end = 8.dp)
|
||||
)
|
||||
IconButton(onClick = onJoinChannel) { Icon(Icons.Default.QrCode, "Join") }
|
||||
@@ -73,7 +73,15 @@ fun ChannelListScreen(
|
||||
}
|
||||
Spacer(Modifier.width(12.dp))
|
||||
Column {
|
||||
Text(ch.name, fontWeight = FontWeight.Bold, fontSize = 16.sp)
|
||||
Row {
|
||||
Text(ch.name, fontWeight = FontWeight.Bold, fontSize = 16.sp)
|
||||
if (ch.peersOnline > 0) {
|
||||
Text(" (${ch.peersOnline})",
|
||||
fontSize = 13.sp,
|
||||
color = MaterialTheme.colorScheme.primary,
|
||||
modifier = Modifier.padding(start = 4.dp))
|
||||
}
|
||||
}
|
||||
if (ch.lastMsg.isNotEmpty())
|
||||
Text(ch.lastMsg, fontSize = 13.sp, color = MaterialTheme.colorScheme.onSurfaceVariant, maxLines = 1)
|
||||
}
|
||||
|
||||
@@ -3,6 +3,7 @@ package com.utun.chat.ui.screens
|
||||
import androidx.compose.foundation.layout.*
|
||||
import androidx.compose.foundation.lazy.LazyColumn
|
||||
import androidx.compose.foundation.lazy.items
|
||||
import androidx.compose.foundation.lazy.rememberLazyListState
|
||||
import androidx.compose.material.icons.Icons
|
||||
import androidx.compose.material.icons.filled.ArrowBack
|
||||
import androidx.compose.material3.*
|
||||
@@ -24,6 +25,11 @@ fun ChatScreen(
|
||||
onBack: () -> Unit
|
||||
) {
|
||||
var text by remember { mutableStateOf("") }
|
||||
val listState = rememberLazyListState()
|
||||
|
||||
LaunchedEffect(messages.size) {
|
||||
if (messages.isNotEmpty()) listState.animateScrollToItem(messages.size - 1)
|
||||
}
|
||||
|
||||
Scaffold(
|
||||
topBar = {
|
||||
@@ -42,15 +48,18 @@ fun ChatScreen(
|
||||
onSendMessage(text)
|
||||
text = ""
|
||||
},
|
||||
enabled = connected
|
||||
enabled = true,
|
||||
modifier = Modifier.navigationBarsPadding()
|
||||
)
|
||||
}
|
||||
) { padding ->
|
||||
LazyColumn(
|
||||
modifier = Modifier.fillMaxSize().padding(padding),
|
||||
reverseLayout = false
|
||||
modifier = Modifier.fillMaxSize().padding(padding).imePadding(),
|
||||
state = listState
|
||||
) {
|
||||
items(messages) { msg -> MessageBubble(msg) }
|
||||
items(messages.size) { idx ->
|
||||
MessageBubble(messages[idx])
|
||||
}
|
||||
item { Spacer(Modifier.height(8.dp)) }
|
||||
}
|
||||
}
|
||||
|
||||
@@ -35,6 +35,7 @@ class ChatViewModel(private val repo: ChatRepository) : ViewModel() {
|
||||
}
|
||||
}
|
||||
}
|
||||
8 -> refreshChannels() /* CHANNEL_PEERS_ONLINE */
|
||||
2 -> { /* CONNECT_RESULT: [node_id:8][result:4][channel_id:8] */
|
||||
if (data != null && data.size >= 20) {
|
||||
val buf = java.nio.ByteBuffer.wrap(data).order(java.nio.ByteOrder.LITTLE_ENDIAN)
|
||||
@@ -68,6 +69,7 @@ class ChatViewModel(private val repo: ChatRepository) : ViewModel() {
|
||||
|
||||
fun selectChannel(channel: Channel) {
|
||||
_currentChannel.value = channel
|
||||
repo.connectChannel(channel.id)
|
||||
refreshMessages(channel.id)
|
||||
}
|
||||
|
||||
|
||||
@@ -11,6 +11,8 @@
|
||||
#include "../libutun_lite/invite_link_c.h"
|
||||
#include "../libutun_lite/instance_lite.h"
|
||||
#include "../../../lib/debug_config.h"
|
||||
#include "../../../lib/u_async.h"
|
||||
#include "../../../lib/mem.h"
|
||||
#include <stdio.h>
|
||||
#include <stdlib.h>
|
||||
#include <string.h>
|
||||
@@ -173,6 +175,7 @@ void utun_bridge_connect_node(const char* address, int port, const char* pubkey_
|
||||
|
||||
/* Real implementation: calls chat_sync_connect_from_invite() */
|
||||
#include "chat/chat_sync.h"
|
||||
#include "chat/chat_core.h"
|
||||
|
||||
void utun_bridge_join_channel(uint64_t channel_id, uint64_t node_id,
|
||||
const uint8_t* pubkey_bin,
|
||||
@@ -187,6 +190,22 @@ void utun_bridge_join_channel(uint64_t channel_id, uint64_t node_id,
|
||||
chat_sync_connect_from_invite(channel_id, node_id, pubkey_bin, addrs_data, addr_count);
|
||||
}
|
||||
|
||||
void utun_bridge_connect_channel(const char* channel_id) {
|
||||
if (!channel_id || !channel_id[0]) {
|
||||
bridge_log(BLEV_ERROR, "connect_channel: null ch_id");
|
||||
return;
|
||||
}
|
||||
struct UASYNC* ua = instance_lite_get_uasync();
|
||||
if (!ua) {
|
||||
bridge_log(BLEV_ERROR, "connect_channel: ua not ready, ch=%s", channel_id);
|
||||
return;
|
||||
}
|
||||
char* cid = u_strdup(channel_id);
|
||||
if (!cid) return;
|
||||
bridge_log(BLEV_INFO, "connect_channel ch=%s", channel_id);
|
||||
uasync_post(ua, chat_core_connect_channel_trampoline, cid);
|
||||
}
|
||||
|
||||
char* utun_bridge_get_channels_json(void) {
|
||||
return strdup("[]");
|
||||
}
|
||||
@@ -451,6 +470,14 @@ JNIEXPORT jstring JNICALL Java_com_utun_chat_data_NativeLib_nativeGetMessages(
|
||||
return result;
|
||||
}
|
||||
|
||||
JNIEXPORT void JNICALL Java_com_utun_chat_data_NativeLib_nativeConnectChannel(
|
||||
JNIEnv* env, jobject thiz, jstring channelId) {
|
||||
(void)thiz;
|
||||
const char* ch = (*env)->GetStringUTFChars(env, channelId, NULL);
|
||||
utun_bridge_connect_channel(ch);
|
||||
if (ch) (*env)->ReleaseStringUTFChars(env, channelId, ch);
|
||||
}
|
||||
|
||||
JNIEXPORT void JNICALL Java_com_utun_chat_data_NativeLib_nativeSetDebugLevel(
|
||||
JNIEnv* env, jobject thiz, jstring category, jstring level) {
|
||||
(void)thiz;
|
||||
|
||||
@@ -56,6 +56,9 @@ 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);
|
||||
|
||||
/* Connect to all peers of a channel (auto-connect on select) */
|
||||
void utun_bridge_connect_channel(const char* channel_id);
|
||||
|
||||
/* ── Queries ── */
|
||||
|
||||
char* utun_bridge_get_channels_json(void);
|
||||
|
||||
@@ -105,13 +105,14 @@ int invite_link_decode(const char* link, size_t link_len, struct InviteDataC* ou
|
||||
for (int j = 0; j < cnt; j++) {
|
||||
int is_v6 = (header >> (2 + j)) & 1;
|
||||
int ip_len = is_v6 ? 16 : 4;
|
||||
if (off + 1 + ip_len + 2 > raw_len) {
|
||||
if (off + 2 + ip_len + 2 > raw_len) {
|
||||
if (error_buf && error_buf_size) snprintf(error_buf, error_buf_size, "truncated at addr %d", j);
|
||||
return -1;
|
||||
}
|
||||
if (out->addrCount >= INVITE_ADDR_MAX) break;
|
||||
struct InviteAddrC* a = &out->addrs[out->addrCount++];
|
||||
a->socketId = raw[off++];
|
||||
a->proto = raw[off++];
|
||||
a->family = is_v6 ? 6 : 4;
|
||||
memcpy(a->address, raw + off, (size_t)ip_len);
|
||||
off += ip_len;
|
||||
@@ -126,7 +127,7 @@ int invite_serialize_addrs(const struct InviteDataC* data, uint8_t* buf, size_t
|
||||
if (!data || !buf) return -1;
|
||||
size_t needed = 0;
|
||||
for (int i = 0; i < (int)data->addrCount; i++)
|
||||
needed += 8U; /* family(1) + sockId(1) + ip4(4) + port(2) = 8; IPv6 skipped */
|
||||
needed += 9U; /* family(1) + sockId(1) + proto(1) + ip4(4) + port(2) = 9; IPv6 skipped */
|
||||
if (needed > buf_size) return -1;
|
||||
size_t pos = 0;
|
||||
for (int i = 0; i < (int)data->addrCount; i++) {
|
||||
@@ -134,6 +135,7 @@ int invite_serialize_addrs(const struct InviteDataC* data, uint8_t* buf, size_t
|
||||
if (a->family != 4) continue;
|
||||
buf[pos++] = (uint8_t)a->family;
|
||||
buf[pos++] = a->socketId;
|
||||
buf[pos++] = a->proto;
|
||||
memcpy(buf + pos, a->address, 4); pos += 4;
|
||||
buf[pos++] = (uint8_t)(a->port >> 8);
|
||||
buf[pos++] = (uint8_t)(a->port & 0xFF);
|
||||
@@ -165,6 +167,7 @@ int invite_link_encode(const struct InviteDataC* data, char* out, size_t out_siz
|
||||
const struct InviteAddrC* a = &data->addrs[ia + j];
|
||||
int ip_len = (a->family == 6) ? 16 : 4;
|
||||
raw[pos++] = a->socketId;
|
||||
raw[pos++] = a->proto;
|
||||
memcpy(raw + pos, a->address, (size_t)ip_len); pos += (size_t)ip_len;
|
||||
raw[pos++] = (uint8_t)(a->port >> 8);
|
||||
raw[pos++] = (uint8_t)(a->port & 0xFF);
|
||||
|
||||
@@ -8,6 +8,10 @@
|
||||
#define INVITE_PUBKEY_SIZE 32
|
||||
#define INVITE_ADDR_MAX 32
|
||||
|
||||
#define INVITE_PROTO_UDP 0x01
|
||||
#define INVITE_PROTO_TCP 0x02
|
||||
#define INVITE_PROTO_BOTH 0x03
|
||||
|
||||
#ifdef __cplusplus
|
||||
extern "C" {
|
||||
#endif
|
||||
@@ -16,6 +20,7 @@ struct InviteAddrC {
|
||||
int family;
|
||||
uint8_t address[16];
|
||||
uint8_t socketId;
|
||||
uint8_t proto;
|
||||
uint16_t port;
|
||||
};
|
||||
|
||||
@@ -38,7 +43,7 @@ int invite_link_decode(const char* link, size_t link_len, struct InviteDataC* ou
|
||||
int invite_link_encode(const struct InviteDataC* data, char* out, size_t out_size);
|
||||
|
||||
/* serialize per-addr format for chat_sync_connect_from_invite():
|
||||
family(1) + socketId(1) + address(4|16) + port(2 BE)
|
||||
family(1) + socketId(1) + proto(1) + address(4|16) + port(2 BE)
|
||||
returns total bytes written, or -1 if buf too small */
|
||||
int invite_serialize_addrs(const struct InviteDataC* data, uint8_t* buf, size_t buf_size);
|
||||
|
||||
|
||||
Reference in new issue
Block a user