Browse Source

android: voice messages + attachments architecture plan, build fixes

topo_upd
evgeny 2 months ago
parent
commit
e751e6ee69
  1. 2
      lib/Makefile.am
  2. 158
      lib/sha256.c
  3. 41
      lib/sha256.h
  4. 32
      lib/sha256_doc.md
  5. 31
      src/chat/chat_sync.c
  6. 9
      src/chat/db_sync.c
  7. 50
      src/transport_layer/secure_channel.c
  8. 22
      src/utun_instance.c
  9. 103
      tools/chatgui-android/AGENTS.md
  10. 1
      tools/chatgui-android/app/src/main/AndroidManifest.xml
  11. 4
      tools/chatgui-android/app/src/main/java/com/utun/chat/ui/components/MessageBubble.kt
  12. 57
      tools/chatgui-android/app/src/main/java/com/utun/chat/ui/screens/ChannelListScreen.kt
  13. 45
      tools/chatgui-android/app/src/main/java/com/utun/chat/ui/screens/ChatScreen.kt
  14. 51
      tools/chatgui-android/jni_bridge/android_jni_bridge.c
  15. 11
      tools/chatgui-android/libutun_lite/instance_lite.c
  16. 1
      tools/chatgui/src/joindialog.cpp
  17. 8
      tools/chatgui/transport/node_config.cpp

2
lib/Makefile.am

@ -11,8 +11,6 @@ libuasync_a_SOURCES = \
timeout_heap.h \ timeout_heap.h \
memory_pool.c \ memory_pool.c \
memory_pool.h \ memory_pool.h \
sha256.c \
sha256.h \
socket_compat.c \ socket_compat.c \
socket_compat.h \ socket_compat.h \
platform_compat.c \ platform_compat.c \

158
lib/sha256.c

@ -1,158 +0,0 @@
/*********************************************************************
* Filename: sha256.c
* Author: Brad Conte (brad AT bradconte.com)
* Copyright:
* Disclaimer: This code is presented "as is" without any guarantees.
* Details: Implementation of the SHA-256 hashing algorithm.
SHA-256 is one of the three algorithms in the SHA2
specification. The others, SHA-384 and SHA-512, are not
offered in this implementation.
Algorithm specification can be found here:
* http://csrc.nist.gov/publications/fips/fips180-2/fips180-2withchangenotice.pdf
This implementation uses little endian byte order.
*********************************************************************/
/*************************** HEADER FILES ***************************/
#include <stdlib.h>
#include <memory.h>
#include "sha256.h"
/****************************** MACROS ******************************/
#define ROTLEFT(a,b) (((a) << (b)) | ((a) >> (32-(b))))
#define ROTRIGHT(a,b) (((a) >> (b)) | ((a) << (32-(b))))
#define CH(x,y,z) (((x) & (y)) ^ (~(x) & (z)))
#define MAJ(x,y,z) (((x) & (y)) ^ ((x) & (z)) ^ ((y) & (z)))
#define EP0(x) (ROTRIGHT(x,2) ^ ROTRIGHT(x,13) ^ ROTRIGHT(x,22))
#define EP1(x) (ROTRIGHT(x,6) ^ ROTRIGHT(x,11) ^ ROTRIGHT(x,25))
#define SIG0(x) (ROTRIGHT(x,7) ^ ROTRIGHT(x,18) ^ ((x) >> 3))
#define SIG1(x) (ROTRIGHT(x,17) ^ ROTRIGHT(x,19) ^ ((x) >> 10))
/**************************** VARIABLES *****************************/
static const uint32_t k[64] = {
0x428a2f98,0x71374491,0xb5c0fbcf,0xe9b5dba5,0x3956c25b,0x59f111f1,0x923f82a4,0xab1c5ed5,
0xd807aa98,0x12835b01,0x243185be,0x550c7dc3,0x72be5d74,0x80deb1fe,0x9bdc06a7,0xc19bf174,
0xe49b69c1,0xefbe4786,0x0fc19dc6,0x240ca1cc,0x2de92c6f,0x4a7484aa,0x5cb0a9dc,0x76f988da,
0x983e5152,0xa831c66d,0xb00327c8,0xbf597fc7,0xc6e00bf3,0xd5a79147,0x06ca6351,0x14292967,
0x27b70a85,0x2e1b2138,0x4d2c6dfc,0x53380d13,0x650a7354,0x766a0abb,0x81c2c92e,0x92722c85,
0xa2bfe8a1,0xa81a664b,0xc24b8b70,0xc76c51a3,0xd192e819,0xd6990624,0xf40e3585,0x106aa070,
0x19a4c116,0x1e376c08,0x2748774c,0x34b0bcb5,0x391c0cb3,0x4ed8aa4a,0x5b9cca4f,0x682e6ff3,
0x748f82ee,0x78a5636f,0x84c87814,0x8cc70208,0x90befffa,0xa4506ceb,0xbef9a3f7,0xc67178f2
};
/*********************** FUNCTION DEFINITIONS ***********************/
static void sc_sha256_transform(SC_SHA256_CTX *ctx, const uint8_t data[])
{
uint32_t a, b, c, d, e, f, g, h, i, j, t1, t2, m[64];
for (i = 0, j = 0; i < 16; ++i, j += 4)
m[i] = (data[j] << 24) | (data[j + 1] << 16) | (data[j + 2] << 8) | (data[j + 3]);
for ( ; i < 64; ++i)
m[i] = SIG1(m[i - 2]) + m[i - 7] + SIG0(m[i - 15]) + m[i - 16];
a = ctx->state[0];
b = ctx->state[1];
c = ctx->state[2];
d = ctx->state[3];
e = ctx->state[4];
f = ctx->state[5];
g = ctx->state[6];
h = ctx->state[7];
for (i = 0; i < 64; ++i) {
t1 = h + EP1(e) + CH(e,f,g) + k[i] + m[i];
t2 = EP0(a) + MAJ(a,b,c);
h = g;
g = f;
f = e;
e = d + t1;
d = c;
c = b;
b = a;
a = t1 + t2;
}
ctx->state[0] += a;
ctx->state[1] += b;
ctx->state[2] += c;
ctx->state[3] += d;
ctx->state[4] += e;
ctx->state[5] += f;
ctx->state[6] += g;
ctx->state[7] += h;
}
void sc_sha256_init(SC_SHA256_CTX *ctx)
{
ctx->datalen = 0;
ctx->bitlen = 0;
ctx->state[0] = 0x6a09e667;
ctx->state[1] = 0xbb67ae85;
ctx->state[2] = 0x3c6ef372;
ctx->state[3] = 0xa54ff53a;
ctx->state[4] = 0x510e527f;
ctx->state[5] = 0x9b05688c;
ctx->state[6] = 0x1f83d9ab;
ctx->state[7] = 0x5be0cd19;
}
void sc_sha256_update(SC_SHA256_CTX *ctx, const uint8_t data[], size_t len)
{
uint32_t i;
for (i = 0; i < len; ++i) {
ctx->data[ctx->datalen] = data[i];
ctx->datalen++;
if (ctx->datalen == 64) {
sc_sha256_transform(ctx, ctx->data);
ctx->bitlen += 512;
ctx->datalen = 0;
}
}
}
void sc_sha256_final(SC_SHA256_CTX *ctx, uint8_t hash[])
{
uint32_t i;
i = ctx->datalen;
// Pad whatever data is left in the buffer.
if (ctx->datalen < 56) {
ctx->data[i++] = 0x80;
while (i < 56)
ctx->data[i++] = 0x00;
}
else {
ctx->data[i++] = 0x80;
while (i < 64)
ctx->data[i++] = 0x00;
sc_sha256_transform(ctx, ctx->data);
memset(ctx->data, 0, 56);
}
// Append to the padding the total message's length in bits and transform.
ctx->bitlen += ctx->datalen * 8;
ctx->data[63] = ctx->bitlen;
ctx->data[62] = ctx->bitlen >> 8;
ctx->data[61] = ctx->bitlen >> 16;
ctx->data[60] = ctx->bitlen >> 24;
ctx->data[59] = ctx->bitlen >> 32;
ctx->data[58] = ctx->bitlen >> 40;
ctx->data[57] = ctx->bitlen >> 48;
ctx->data[56] = ctx->bitlen >> 56;
sc_sha256_transform(ctx, ctx->data);
// Since this implementation uses little endian byte ordering and SHA uses big endian,
// reverse all the bytes when copying the final state to the output hash.
for (i = 0; i < 4; ++i) {
hash[i] = (ctx->state[0] >> (24 - i * 8)) & 0x000000ff;
hash[i + 4] = (ctx->state[1] >> (24 - i * 8)) & 0x000000ff;
hash[i + 8] = (ctx->state[2] >> (24 - i * 8)) & 0x000000ff;
hash[i + 12] = (ctx->state[3] >> (24 - i * 8)) & 0x000000ff;
hash[i + 16] = (ctx->state[4] >> (24 - i * 8)) & 0x000000ff;
hash[i + 20] = (ctx->state[5] >> (24 - i * 8)) & 0x000000ff;
hash[i + 24] = (ctx->state[6] >> (24 - i * 8)) & 0x000000ff;
hash[i + 28] = (ctx->state[7] >> (24 - i * 8)) & 0x000000ff;
}
}

41
lib/sha256.h

@ -1,41 +0,0 @@
/*********************************************************************
* Filename: sha256.h
* Author: Brad Conte (brad AT bradconte.com)
* Copyright:
* Disclaimer: This code is presented "as is" without any guarantees.
* Details: Defines the API for the corresponding SHA1 implementation.
*********************************************************************/
#ifndef SC_SHA256_H
#define SC_SHA256_H
#ifdef __cplusplus
extern "C" {
#endif
/*************************** HEADER FILES ***************************/
#include <stddef.h>
#include <stdint.h>
/****************************** MACROS ******************************/
#define SC_SHA256_BLOCK_SIZE 32 // SHA256 outputs a 32 byte digest
/**************************** DATA TYPES ****************************/
typedef struct {
uint8_t data[64];
uint32_t datalen;
unsigned long long bitlen;
uint32_t state[8];
} SC_SHA256_CTX;
/*********************** FUNCTION DECLARATIONS **********************/
void sc_sha256_init(SC_SHA256_CTX *ctx);
void sc_sha256_update(SC_SHA256_CTX *ctx, const uint8_t data[], size_t len);
void sc_sha256_final(SC_SHA256_CTX *ctx, uint8_t hash[]);
#ifdef __cplusplus
}
#endif
#endif // SC_SHA256_H

32
lib/sha256_doc.md

@ -1,32 +0,0 @@
# SHA-256 (sha256.h)
## 1. Назначение
Чистая C-реализация SHA-256 по FIPS 180-2, без внешних зависимостей. Используется как fallback при отключенной OpenSSL (`USE_OPENSSL` не определён) — в `secure_channel.c` для деривации session key, stream nonce, хеширования pubkey для обфускации и в `db_sync.c` для контрольных сумм БД.
## 2. Как пользоваться
Трёхфазный API: init → update (можно много раз) → final.
```c
SC_SHA256_CTX ctx;
uint8_t hash[SC_SHA256_BLOCK_SIZE]; // 32 байта
sc_sha256_init(&ctx);
sc_sha256_update(&ctx, data1, len1);
sc_sha256_update(&ctx, data2, len2); // можно добавлять данные порциями
sc_sha256_final(&ctx, hash); // hash — 32 байта, после этого ctx непригоден
```
После `sc_sha256_final` контекст нельзя переиспользовать без повторного `sc_sha256_init`.
## 3. API
### Структура
- **`SC_SHA256_CTX`** — контекст хеширования: буфер 64 байта, счётчик данных, счётчик бит, 8 слов состояния.
### Функции
- **`sc_sha256_init(ctx)`** — инициализирует контекст начальными значениями IV (первые 32 бита дробных частей квадратных корней первых 8 простых чисел).
- **`sc_sha256_update(ctx, data, len)`** — добавляет данные в хеш; обрабатывает блоки по 64 байта через `sc_sha256_transform` (внутренняя, раунды SHA-256).
- **`sc_sha256_final(ctx, hash)`** — завершает хеширование: дополняет данные до кратных 512 бит (padding с 0x80 и длиной сообщения), выполняет трансформацию, выдаёт 32-байтовый дайджест в big-endian порядке.
### Константа
- **`SC_SHA256_BLOCK_SIZE`** (32) — размер выходного дайджеста в байтах.

31
src/chat/chat_sync.c

@ -226,6 +226,27 @@ static struct ETCP_CONN* cs_find_conn_for_node(struct UTUN_INSTANCE* inst, uint6
return NULL; return NULL;
} }
/* ── helper: find active ETCP_CONN by pubkey (for invite: same physical node, different node_id) ── */
static struct ETCP_CONN* cs_find_conn_by_pubkey(struct UTUN_INSTANCE* inst,
const uint8_t* pubkey_bin,
uint64_t* out_node_id) {
struct ll_entry* entry = inst->connections ? inst->connections->head : NULL;
while (entry) {
struct conn_queue_entry* ce = (struct conn_queue_entry*)entry->data;
struct ETCP_CONN* conn = ce->conn;
if (conn && conn->initialized && conn->links_up
&& memcmp(conn->crypto_ctx.peer_public_key, pubkey_bin, 32) == 0) {
*out_node_id = conn->peer_node_id;
DEBUG_INFO(DEBUG_CATEGORY_DB_SYNC, "%s: find_conn_by_pubkey MATCH conn=%s peer=0x%016llx",
CS_ID, conn->log_name, (unsigned long long)conn->peer_node_id);
return conn;
}
entry = entry->next;
}
return NULL;
}
/* ── helper: check if peer has active ETCP link ── */ /* ── helper: check if peer has active ETCP link ── */
static int cs_is_peer_online(struct UTUN_INSTANCE* inst, uint64_t peer_id) { static int cs_is_peer_online(struct UTUN_INSTANCE* inst, uint64_t peer_id) {
@ -642,11 +663,17 @@ static void cm_invite_trampoline(void* arg) {
} }
struct ETCP_CONN* existing = instance_find_conn(inst, node_id); struct ETCP_CONN* existing = instance_find_conn(inst, node_id);
if (!existing) existing = cs_find_conn_by_pubkey(inst, inv->pubkey, &node_id);
if (existing) { if (existing) {
if (existing->links_up) { if (existing->links_up) {
DEBUG_INFO(DEBUG_CATEGORY_DB_SYNC, "%s: invite conn already UP node=0x%016llx, reusing", CS_ID, (unsigned long long)node_id); if (g_cs && g_cs->pending_invite_ch_id != 0 && !g_cs->info_req_timer) {
if (g_cs && g_cs->pending_invite_ch_id != 0 && !g_cs->info_req_timer) if (node_id != existing->peer_node_id)
DEBUG_WARN(DEBUG_CATEGORY_DB_SYNC,
"%s: invite node_id mismatch: derived=%016llx ETCP=%016llx — using ETCP",
CS_ID, (unsigned long long)node_id, (unsigned long long)existing->peer_node_id);
cs_on_conn_up(existing, NULL); cs_on_conn_up(existing, NULL);
}
} else { } else {
DEBUG_INFO(DEBUG_CATEGORY_DB_SYNC, "%s: invite conn in progress node=0x%016llx, waiting UP", CS_ID, (unsigned long long)node_id); DEBUG_INFO(DEBUG_CATEGORY_DB_SYNC, "%s: invite conn in progress node=0x%016llx, waiting UP", CS_ID, (unsigned long long)node_id);
} }

9
src/chat/db_sync.c

@ -10,7 +10,7 @@
#include "../../lib/debug_config.h" #include "../../lib/debug_config.h"
#include "../../lib/mem.h" #include "../../lib/mem.h"
#include "../../lib/u_async.h" #include "../../lib/u_async.h"
#include "../../lib/sha256.h" #include <openssl/sha.h>
#include "../../lib/sqlite3.h" #include "../../lib/sqlite3.h"
#include "../../lib/platform_compat.h" #include "../../lib/platform_compat.h"
#include <string.h> #include <string.h>
@ -131,10 +131,7 @@ static void db_sqlite_close(struct DB_SYNC* db)
static void db_sha256(const uint8_t* data, size_t len, uint8_t hash[32]) static void db_sha256(const uint8_t* data, size_t len, uint8_t hash[32])
{ {
SC_SHA256_CTX ctx; SHA256(data, len, hash);
sc_sha256_init(&ctx);
sc_sha256_update(&ctx, data, len);
sc_sha256_final(&ctx, hash);
} }
static uint64_t db_hash64(const uint8_t* data, size_t len) static uint64_t db_hash64(const uint8_t* data, size_t len)
@ -1220,7 +1217,7 @@ static void db_sync_recv_cb(struct ETCP_CONN* conn, struct ll_entry* entry)
const char* tbl = (const char*)sqlite3_column_text(st, 0); const char* tbl = (const char*)sqlite3_column_text(st, 0);
const char* ch_id = (const char*)sqlite3_column_text(st, 1); const char* ch_id = (const char*)sqlite3_column_text(st, 1);
if (!ch_id || !ch_id[0]) continue; if (!ch_id || !ch_id[0]) continue;
uint64_t h; { uint8_t sh[32]; SC_SHA256_CTX ctx; sc_sha256_init(&ctx); sc_sha256_update(&ctx, (const uint8_t*)ch_id, strlen(ch_id)); sc_sha256_final(&ctx, sh); memcpy(&h, sh, 8); } uint64_t h; { uint8_t sh[32]; SHA256((const uint8_t*)ch_id, strlen(ch_id), sh); memcpy(&h, sh, 8); }
if (h == hash) { if (h == hash) {
si = db_sync_instance_add(db->inst, tbl, hash, 1); si = db_sync_instance_add(db->inst, tbl, hash, 1);
if (si) { si_peer_add(si, src); } if (si) { si_peer_add(si, src); }

50
src/transport_layer/secure_channel.c

@ -17,7 +17,6 @@
#include <stdio.h> #include <stdio.h>
#include <fcntl.h> #include <fcntl.h>
#include "crc32.h" #include "crc32.h"
#include "../lib/sha256.h"
#include <openssl/evp.h> #include <openssl/evp.h>
#include <openssl/sha.h> #include <openssl/sha.h>
@ -99,29 +98,29 @@ static sc_status_t verify_and_strip_crc32(uint8_t *plaintext_with_crc, size_t to
} }
static void sc_derive_session_key(const uint8_t *shared_secret, uint8_t *session_key) { static void sc_derive_session_key(const uint8_t *shared_secret, uint8_t *session_key) {
SC_SHA256_CTX sha_ctx; SHA256_CTX sha_ctx;
uint8_t hash[SC_HASH_SIZE]; uint8_t hash[SC_HASH_SIZE];
sc_sha256_init(&sha_ctx); SHA256_Init(&sha_ctx);
sc_sha256_update(&sha_ctx, shared_secret, SC_SHARED_SECRET_SIZE); SHA256_Update(&sha_ctx, shared_secret, SC_SHARED_SECRET_SIZE);
sc_sha256_update(&sha_ctx, (const uint8_t *)"uTun-v3-session", 15); SHA256_Update(&sha_ctx, (const uint8_t *)"uTun-v3-session", 15);
sc_sha256_final(&sha_ctx, hash); SHA256_Final(hash, &sha_ctx);
memcpy(session_key, hash, SC_SESSION_KEY_SIZE); memcpy(session_key, hash, SC_SESSION_KEY_SIZE);
} }
// Derive 12-byte nonce for stream cipher from session key + stream_id // Derive 12-byte nonce for stream cipher from session key + stream_id
static void sc_stream_derive_nonce(const uint8_t *session_key, uint32_t stream_id, uint8_t *nonce_out) { static void sc_stream_derive_nonce(const uint8_t *session_key, uint32_t stream_id, uint8_t *nonce_out) {
SC_SHA256_CTX sha_ctx; SHA256_CTX sha_ctx;
uint8_t hash[SC_HASH_SIZE]; uint8_t hash[SC_HASH_SIZE];
uint8_t stream_id_le[4]; uint8_t stream_id_le[4];
stream_id_le[0] = (uint8_t)(stream_id >> 0); stream_id_le[0] = (uint8_t)(stream_id >> 0);
stream_id_le[1] = (uint8_t)(stream_id >> 8); stream_id_le[1] = (uint8_t)(stream_id >> 8);
stream_id_le[2] = (uint8_t)(stream_id >> 16); stream_id_le[2] = (uint8_t)(stream_id >> 16);
stream_id_le[3] = (uint8_t)(stream_id >> 24); stream_id_le[3] = (uint8_t)(stream_id >> 24);
sc_sha256_init(&sha_ctx); SHA256_Init(&sha_ctx);
sc_sha256_update(&sha_ctx, session_key, SC_SESSION_KEY_SIZE); SHA256_Update(&sha_ctx, session_key, SC_SESSION_KEY_SIZE);
sc_sha256_update(&sha_ctx, stream_id_le, sizeof(stream_id_le)); SHA256_Update(&sha_ctx, stream_id_le, sizeof(stream_id_le));
sc_sha256_update(&sha_ctx, (const uint8_t *)"uTun3-stream", 12); SHA256_Update(&sha_ctx, (const uint8_t *)"uTun3-stream", 12);
sc_sha256_final(&sha_ctx, hash); SHA256_Final(hash, &sha_ctx);
memcpy(nonce_out, hash, SC_STREAM_NONCE_SIZE); memcpy(nonce_out, hash, SC_STREAM_NONCE_SIZE);
} }
@ -424,8 +423,8 @@ sc_status_t sc_derive_ed25519_pubkey(const uint8_t *x25519_privkey, uint8_t *ed2
uint64_t sc_derive_node_id_from_pubkey(const uint8_t *public_key) { uint64_t sc_derive_node_id_from_pubkey(const uint8_t *public_key) {
if (!public_key) { DEBUG_ERROR(DEBUG_CATEGORY_CRYPTO, "sc_derive_node_id_from_pubkey: NULL public_key"); return 0; } if (!public_key) { DEBUG_ERROR(DEBUG_CATEGORY_CRYPTO, "sc_derive_node_id_from_pubkey: NULL public_key"); return 0; }
uint8_t sha_hash[SC_HASH_SIZE]; SC_SHA256_CTX ctx; uint8_t sha_hash[SC_HASH_SIZE];
sc_sha256_init(&ctx); sc_sha256_update(&ctx, public_key, SC_PUBKEY_SIZE); sc_sha256_final(&ctx, sha_hash); SHA256(public_key, SC_PUBKEY_SIZE, sha_hash);
uint64_t node_id; uint64_t node_id;
memcpy(&node_id, sha_hash, 8); memcpy(&node_id, sha_hash, 8);
node_id &= 0x7FFFFFFFFFFFFFFFULL; node_id &= 0x7FFFFFFFFFFFFFFFULL;
@ -572,10 +571,7 @@ sc_status_t sc_ed25519_verify(const uint8_t pubkey[32], const uint8_t* msg, size
sc_status_t sc_sha_transcode(const uint8_t *key, size_t key_len, uint8_t *data, size_t data_len) { sc_status_t sc_sha_transcode(const uint8_t *key, size_t key_len, uint8_t *data, size_t data_len) {
if (!key || !data || key_len == 0 || data_len == 0) return SC_ERR_INVALID_ARG; if (!key || !data || key_len == 0 || data_len == 0) return SC_ERR_INVALID_ARG;
uint8_t sha_hash[SC_HASH_SIZE]; uint8_t sha_hash[SC_HASH_SIZE];
SC_SHA256_CTX ctx; SHA256(key, key_len, sha_hash);
sc_sha256_init(&ctx);
sc_sha256_update(&ctx, key, key_len);
sc_sha256_final(&ctx, sha_hash);
for (size_t i = 0; i < data_len; i++) data[i] ^= sha_hash[i % SC_HASH_SIZE]; for (size_t i = 0; i < data_len; i++) data[i] ^= sha_hash[i % SC_HASH_SIZE];
return SC_OK; return SC_OK;
} }
@ -583,15 +579,15 @@ sc_status_t sc_sha_transcode(const uint8_t *key, size_t key_len, uint8_t *data,
sc_status_t sc_obfuscate_pubkey(const uint8_t *salt, const uint8_t *peer_pubkey, const uint8_t *pubkey, uint8_t *output) { sc_status_t sc_obfuscate_pubkey(const uint8_t *salt, const uint8_t *peer_pubkey, const uint8_t *pubkey, uint8_t *output) {
if (!salt || !peer_pubkey || !pubkey || !output) return SC_ERR_INVALID_ARG; if (!salt || !peer_pubkey || !pubkey || !output) return SC_ERR_INVALID_ARG;
uint8_t sha[SC_HASH_SIZE*2]; uint8_t sha[SC_HASH_SIZE*2];
SC_SHA256_CTX ctx; SHA256_CTX ctx;
sc_sha256_init(&ctx); SHA256_Init(&ctx);
sc_sha256_update(&ctx, salt, SC_PUBKEY_ENC_SALT_SIZE); SHA256_Update(&ctx, salt, SC_PUBKEY_ENC_SALT_SIZE);
sc_sha256_update(&ctx, peer_pubkey, SC_PUBKEY_SIZE); SHA256_Update(&ctx, peer_pubkey, SC_PUBKEY_SIZE);
sc_sha256_final(&ctx, sha); SHA256_Final(sha, &ctx);
sc_sha256_init(&ctx); SHA256_Init(&ctx);
sc_sha256_update(&ctx, peer_pubkey, SC_PUBKEY_SIZE); SHA256_Update(&ctx, peer_pubkey, SC_PUBKEY_SIZE);
sc_sha256_update(&ctx, salt, SC_PUBKEY_ENC_SALT_SIZE); SHA256_Update(&ctx, salt, SC_PUBKEY_ENC_SALT_SIZE);
sc_sha256_final(&ctx, sha+SC_HASH_SIZE); SHA256_Final(sha + SC_HASH_SIZE, &ctx);
for (size_t i = 0; i < SC_PUBKEY_SIZE; i++) output[i] = pubkey[i] ^ sha[i]; for (size_t i = 0; i < SC_PUBKEY_SIZE; i++) output[i] = pubkey[i] ^ sha[i];
return SC_OK; return SC_OK;
} }

22
src/utun_instance.c

@ -21,7 +21,6 @@
#include "../lib/u_async.h" #include "../lib/u_async.h"
#include "../lib/debug_config.h" #include "../lib/debug_config.h"
#include "../lib/sha256.h"
#include <stdlib.h> #include <stdlib.h>
#include <stdio.h> #include <stdio.h>
#include <string.h> #include <string.h>
@ -101,12 +100,21 @@ static int instance_init_common(struct UTUN_INSTANCE* instance, struct UASYNC* u
return -1; return -1;
} }
// Derive node_id from pubkey if not set in config /* Always verify node_id matches pubkey, fix if mismatched */
if (!instance->node_id) { {
uint8_t pubkey[SC_PUBKEY_SIZE]; uint8_t pub_bin[SC_PUBKEY_SIZE];
sc_compute_public_key_from_private(instance->my_keys.private_key, pubkey); sc_compute_public_key_from_private(instance->my_keys.private_key, pub_bin);
instance->node_id = sc_derive_node_id_from_pubkey(pubkey); uint64_t derived = sc_derive_node_id_from_pubkey(pub_bin);
DEBUG_INFO(DEBUG_CATEGORY_CONFIG, "node_id derived from pubkey: %016llx", (unsigned long long)instance->node_id); DEBUG_INFO(DEBUG_CATEGORY_CONFIG, "node_id check: config=%016llx derived=%016llx match=%d",
(unsigned long long)instance->node_id, (unsigned long long)derived,
(instance->node_id == derived));
if (!instance->node_id || instance->node_id != derived) {
if (instance->node_id)
DEBUG_WARN(DEBUG_CATEGORY_CONFIG, "node_id mismatch: config=%016llx derived=%016llx — fixing",
(unsigned long long)instance->node_id, (unsigned long long)derived);
instance->node_id = derived;
config->global.my_node_id = derived;
}
} }
if (sc_derive_ed25519_pubkey(instance->my_keys.private_key, instance->my_ed25519_pubkey) != SC_OK) { if (sc_derive_ed25519_pubkey(instance->my_keys.private_key, instance->my_ed25519_pubkey) != SC_OK) {

103
tools/chatgui-android/AGENTS.md

@ -71,16 +71,64 @@ tools/chatgui-android/
| Сборка C | CMake 3.16+ | GCC (Linux) / Clang (Android NDK) | | Сборка C | CMake 3.16+ | GCC (Linux) / Clang (Android NDK) |
| Сборка APK | Gradle 8.x | AGP 8.x, Kotlin 1.9 | | Сборка APK | Gradle 8.x | AGP 8.x, Kotlin 1.9 |
## Сборка ## Зависимости
### Headless (Linux, для отладки сетевого стека) ### Для сборки APK (Android)
| Компонент | Версия | Установка |
|-----------|--------|-----------|
| JDK | 17+ | `apt install openjdk-17-jdk` |
| CMake | 3.22+ | `apt install cmake` |
| Android SDK | API 36 | через [sdkmanager](https://developer.android.com/studio#command-line-tools-only) или Android Studio |
| Android NDK | 29.0.14206865 | `sdkmanager "ndk;29.0.14206865"` |
| Gradle | 8.5 (wrapper) | `./gradlew` скачает сам |
| Kotlin | 1.9.20 | встроен в Gradle плагин |
| adb | platform-tools | `apt install adb` (для прошивки) |
OpenSSL для APK **предсобран** под `arm64-v8a` и `x86_64`, лежит в `app/src/main/cpp/openssl/`.
Собирать отдельно не нужно.
### Для Headless (Linux, отладка без телефона)
| Компонент | Установка |
|-----------|-----------|
| CMake 3.16+ | `apt install cmake` |
| GCC / Clang | `apt install build-essential` |
| OpenSSL dev | `apt install libssl-dev` |
| pthread | встроен в libc |
### Настройка окружения
Создать `local.properties` в корне `tools/chatgui-android/`:
```properties
sdk.dir=/home/user/Android/Sdk
```
Путь `ndk.dir` можно не указывать — Gradle скачает NDK сам (версия из `ndkVersion` в `app/build.gradle.kts`).
Если NDK уже установлен отдельно:
```properties
ndk.dir=/home/user/Android/Sdk/ndk/29.0.14206865
```
### Установка Android SDK (без Android Studio)
```bash ```bash
cd tools/chatgui-android && mkdir -p build && cd build cd /tmp && wget https://dl.google.com/android/repository/commandlinetools-linux-11076708_latest.zip
cmake .. && make -j4 unzip commandlinetools-linux-*.zip -d ~/Android
./headless/headless mkdir -p ~/Android/Sdk/cmdline-tools
mv ~/Android/cmdline-tools ~/Android/Sdk/cmdline-tools/latest
export ANDROID_HOME=~/Android/Sdk
export PATH=$ANDROID_HOME/cmdline-tools/latest/bin:$PATH
yes | sdkmanager --licenses
sdkmanager "platforms;android-36" "build-tools;36.0.0" "ndk;29.0.14206865"
``` ```
## Сборка
### APK (Android) ### APK (Android)
```bash ```bash
@ -90,6 +138,14 @@ export ANDROID_HOME=/home/user/Android/Sdk
# APK: app/build/outputs/apk/debug/app-debug.apk # APK: app/build/outputs/apk/debug/app-debug.apk
``` ```
### Headless (Linux, для отладки сетевого стека)
```bash
cd tools/chatgui-android && mkdir -p build && cd build
cmake .. && make -j4
./headless/headless
```
## Прошивка на телефон ## Прошивка на телефон
```bash ```bash
@ -109,6 +165,43 @@ nc localhost 9999
> {"id":2,"cmd":"join","link":"utun://..."} > {"id":2,"cmd":"join","link":"utun://..."}
``` ```
## Конфиг
Конфиг хранится в Android DataStore (`ConfigProvider.kt`) и передаётся в C-ядро
при старте как INI-текст через `nativeStart(configText)`.
Формат (генерируется автоматически, пользователь не редактирует):
```ini
[global]
my_node_name=MyDevice
my_public_key=<64 hex chars X25519>
my_private_key=<64 hex chars X25519>
db_path=/data/data/com.utun.chat/files
db_sync_enabled=1
[server:main]
addr=0.0.0.0:<listen_port>
[allowed_keys]
allow_all=yes
[ntp]
enabled=no
[debug]
console_level=info
# Опционально — UDP-лог
[log_udp]
ip=<ip хоста>
port=9999
```
Ключи (`my_public_key`, `my_private_key`) генерируются автоматически при первом запуске
и сохраняются в `keys.conf` в `db_path`. Имя узла (`my_node_name`) берётся из
настроек DataStore или `Build.MODEL`.
## Логирование через logreceiver (только Android) ## Логирование через logreceiver (только Android)
Debug-сообщения с телефона дублируются через UDP на приёмник `tools/logreceiver/`. Debug-сообщения с телефона дублируются через UDP на приёмник `tools/logreceiver/`.

1
tools/chatgui-android/app/src/main/AndroidManifest.xml

@ -16,6 +16,7 @@
<activity <activity
android:name=".MainActivity" android:name=".MainActivity"
android:exported="true" android:exported="true"
android:configChanges="orientation|screenSize|screenLayout|keyboardHidden"
android:windowSoftInputMode="adjustResize"> android:windowSoftInputMode="adjustResize">
<intent-filter> <intent-filter>
<action android:name="android.intent.action.MAIN" /> <action android:name="android.intent.action.MAIN" />

4
tools/chatgui-android/app/src/main/java/com/utun/chat/ui/components/MessageBubble.kt

@ -9,6 +9,7 @@ import androidx.compose.ui.Alignment
import androidx.compose.ui.Modifier import androidx.compose.ui.Modifier
import androidx.compose.ui.draw.clip import androidx.compose.ui.draw.clip
import androidx.compose.ui.graphics.Color import androidx.compose.ui.graphics.Color
import androidx.compose.ui.platform.LocalConfiguration
import androidx.compose.ui.text.font.FontWeight import androidx.compose.ui.text.font.FontWeight
import androidx.compose.ui.unit.dp import androidx.compose.ui.unit.dp
import androidx.compose.ui.unit.sp import androidx.compose.ui.unit.sp
@ -19,6 +20,7 @@ fun MessageBubble(message: Message) {
val bgColor = if (message.isOutgoing) Color(0xFF4A90D9) else Color(0xFFE8E8E8) val bgColor = if (message.isOutgoing) Color(0xFF4A90D9) else Color(0xFFE8E8E8)
val textColor = if (message.isOutgoing) Color.White else Color.Black val textColor = if (message.isOutgoing) Color.White else Color.Black
val alignment = if (message.isOutgoing) Arrangement.End else Arrangement.Start val alignment = if (message.isOutgoing) Arrangement.End else Arrangement.Start
val minBubbleWidth = (LocalConfiguration.current.screenWidthDp / 3).dp
Row( Row(
modifier = Modifier.fillMaxWidth().padding(horizontal = 8.dp, vertical = 2.dp), modifier = Modifier.fillMaxWidth().padding(horizontal = 8.dp, vertical = 2.dp),
@ -26,7 +28,7 @@ fun MessageBubble(message: Message) {
) { ) {
Column( Column(
modifier = Modifier modifier = Modifier
.widthIn(max = 300.dp) .widthIn(min = minBubbleWidth, max = 300.dp)
.clip(RoundedCornerShape(12.dp)) .clip(RoundedCornerShape(12.dp))
.background(bgColor) .background(bgColor)
.padding(12.dp) .padding(12.dp)

57
tools/chatgui-android/app/src/main/java/com/utun/chat/ui/screens/ChannelListScreen.kt

@ -6,6 +6,7 @@ import androidx.compose.foundation.layout.*
import androidx.compose.foundation.lazy.LazyColumn import androidx.compose.foundation.lazy.LazyColumn
import androidx.compose.foundation.lazy.items import androidx.compose.foundation.lazy.items
import androidx.compose.foundation.shape.CircleShape import androidx.compose.foundation.shape.CircleShape
import androidx.compose.foundation.shape.RoundedCornerShape
import androidx.compose.material.icons.Icons import androidx.compose.material.icons.Icons
import androidx.compose.material.icons.filled.Add import androidx.compose.material.icons.filled.Add
import androidx.compose.material.icons.filled.QrCode import androidx.compose.material.icons.filled.QrCode
@ -15,8 +16,10 @@ import androidx.compose.material3.*
import androidx.compose.runtime.* import androidx.compose.runtime.*
import androidx.compose.ui.Alignment import androidx.compose.ui.Alignment
import androidx.compose.ui.Modifier import androidx.compose.ui.Modifier
import androidx.compose.ui.draw.clip
import androidx.compose.ui.graphics.Color import androidx.compose.ui.graphics.Color
import androidx.compose.ui.text.font.FontWeight import androidx.compose.ui.text.font.FontWeight
import androidx.compose.ui.text.style.TextAlign
import androidx.compose.ui.unit.dp import androidx.compose.ui.unit.dp
import androidx.compose.ui.unit.sp import androidx.compose.ui.unit.sp
import com.utun.chat.data.Channel import com.utun.chat.data.Channel
@ -72,26 +75,48 @@ fun ChannelListScreen(
} else { } else {
LazyColumn { LazyColumn {
items(channels) { ch -> items(channels) { ch ->
val barColor = if (ch.peersOnline > 0) Color(0xFF4CAF50) else Color(0xFF9E9E9E)
val badgeText = when {
ch.peersOnline > 99 -> "99+"
ch.peersOnline > 0 -> ch.peersOnline.toString()
else -> ""
}
Surface(modifier = Modifier.clickable { onSelectChannel(ch) }) { Surface(modifier = Modifier.clickable { onSelectChannel(ch) }) {
Row(Modifier.fillMaxWidth().padding(16.dp)) { Row(Modifier.fillMaxWidth().height(IntrinsicSize.Min)) {
Box(Modifier.size(48.dp), contentAlignment = androidx.compose.ui.Alignment.Center) { Box(Modifier.width(4.dp).fillMaxHeight().background(barColor))
Surface(shape = MaterialTheme.shapes.small) { Row(Modifier.fillMaxWidth().padding(12.dp)) {
Text("#", fontSize = 24.sp, modifier = Modifier.padding(8.dp)) Box(Modifier.size(44.dp), contentAlignment = Alignment.Center) {
Surface(shape = MaterialTheme.shapes.small) {
Text("#", fontSize = 22.sp, modifier = Modifier.padding(8.dp))
}
if (badgeText.isNotEmpty()) {
val badgeSize = 18.dp
Box(
Modifier
.align(Alignment.TopStart)
.offset(x = (-4).dp, y = (-6).dp)
.size(badgeSize)
.clip(CircleShape)
.background(Color(0xFF4CAF50)),
contentAlignment = Alignment.Center
) {
Text(
badgeText, fontSize = 9.sp, color = Color.White,
fontWeight = FontWeight.Bold, textAlign = TextAlign.Center
)
}
}
} }
} Spacer(Modifier.width(10.dp))
Spacer(Modifier.width(12.dp)) Column {
Column { Row(verticalAlignment = Alignment.CenterVertically) {
Row { Text(ch.name, fontWeight = FontWeight.Bold, fontSize = 16.sp)
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)
} }
if (ch.lastMsg.isNotEmpty())
Text(ch.lastMsg, fontSize = 13.sp, color = MaterialTheme.colorScheme.onSurfaceVariant, maxLines = 1)
} }
} }
} }

45
tools/chatgui-android/app/src/main/java/com/utun/chat/ui/screens/ChatScreen.kt

@ -5,7 +5,7 @@ import androidx.compose.foundation.lazy.LazyColumn
import androidx.compose.foundation.lazy.items import androidx.compose.foundation.lazy.items
import androidx.compose.foundation.lazy.rememberLazyListState import androidx.compose.foundation.lazy.rememberLazyListState
import androidx.compose.material.icons.Icons import androidx.compose.material.icons.Icons
import androidx.compose.material.icons.filled.ArrowBack import androidx.compose.material.icons.automirrored.filled.ArrowBack
import androidx.compose.material3.* import androidx.compose.material3.*
import androidx.compose.runtime.* import androidx.compose.runtime.*
import androidx.compose.ui.Modifier import androidx.compose.ui.Modifier
@ -31,30 +31,17 @@ fun ChatScreen(
if (messages.isNotEmpty()) listState.animateScrollToItem(messages.size - 1) if (messages.isNotEmpty()) listState.animateScrollToItem(messages.size - 1)
} }
Scaffold( Column(
topBar = { modifier = Modifier.fillMaxSize().imePadding()
TopAppBar( ) {
title = { Text(channel.name) }, TopAppBar(
navigationIcon = { title = { Text(channel.name) },
IconButton(onClick = onBack) { Icon(Icons.Default.ArrowBack, "Back") } navigationIcon = {
} IconButton(onClick = onBack) { Icon(Icons.AutoMirrored.Filled.ArrowBack, "Back") }
) }
}, )
bottomBar = {
InputBar(
text = text,
onTextChange = { text = it },
onSend = {
onSendMessage(text)
text = ""
},
enabled = true,
modifier = Modifier.navigationBarsPadding()
)
}
) { padding ->
LazyColumn( LazyColumn(
modifier = Modifier.fillMaxSize().padding(padding).imePadding(), modifier = Modifier.weight(1f).fillMaxWidth(),
state = listState state = listState
) { ) {
items(messages.size) { idx -> items(messages.size) { idx ->
@ -62,5 +49,15 @@ fun ChatScreen(
} }
item { Spacer(Modifier.height(8.dp)) } item { Spacer(Modifier.height(8.dp)) }
} }
InputBar(
text = text,
onTextChange = { text = it },
onSend = {
onSendMessage(text)
text = ""
},
enabled = true,
modifier = Modifier.navigationBarsPadding()
)
} }
} }

51
tools/chatgui-android/jni_bridge/android_jni_bridge.c

@ -15,6 +15,7 @@
#include "../../../lib/u_async.h" #include "../../../lib/u_async.h"
#include "../../../lib/mem.h" #include "../../../lib/mem.h"
#include "../../../lib/sqlite3.h" #include "../../../lib/sqlite3.h"
#include "../../../src/chat/chat_core.h"
#include <stdio.h> #include <stdio.h>
#include <stdlib.h> #include <stdlib.h>
#include <string.h> #include <string.h>
@ -168,6 +169,24 @@ void utun_bridge_poll(void) {
void utun_bridge_send_message(const char* channel_id, const char* text) { void utun_bridge_send_message(const char* channel_id, const char* text) {
bridge_log(BLEV_DEBUG, "send ch=%s text=%s", channel_id ? channel_id : "", text ? text : ""); bridge_log(BLEV_DEBUG, "send ch=%s text=%s", channel_id ? channel_id : "", text ? text : "");
if (!channel_id || !text) return;
struct UASYNC* ua = instance_lite_get_uasync();
if (!ua) { bridge_log(BLEV_ERROR, "send: ua not ready"); return; }
size_t datalen = strlen(text);
struct chat_msg_submit* req = u_calloc(1, sizeof(struct chat_msg_submit) + datalen);
if (!req) return;
strncpy(req->channel_id, channel_id, sizeof(req->channel_id) - 1);
strncpy(req->content_type, "text/plain", sizeof(req->content_type) - 1);
req->data = (uint8_t*)(req + 1);
req->data_len = (uint32_t)datalen;
memcpy(req->data, text, datalen);
req->timestamp = 0;
uasync_post(ua, chat_core_submit_trampoline, req);
bridge_log(BLEV_DEBUG, "send queued ch=%s len=%zu", channel_id, datalen);
} }
void utun_bridge_create_channel(const char* name, const char* channel_id) { void utun_bridge_create_channel(const char* name, const char* channel_id) {
@ -232,6 +251,19 @@ static char* json_escape_alloc(const char* str) {
return out; return out;
} }
static void bridge_get_node_name(sqlite3* db, uint64_t node_id, char* out, size_t sz) {
out[0] = '\0';
if (!db || node_id == 0) return;
sqlite3_stmt* st = NULL;
if (sqlite3_prepare_v2(db, "SELECT name FROM nodes WHERE node_id=?", -1, &st, NULL) != SQLITE_OK) return;
sqlite3_bind_int64(st, 1, (sqlite3_int64)node_id);
if (sqlite3_step(st) == SQLITE_ROW) {
const char* n = (const char*)sqlite3_column_text(st, 0);
if (n && n[0]) snprintf(out, sz, "%s", n);
}
sqlite3_finalize(st);
}
char* utun_bridge_get_channels_json(void) { char* utun_bridge_get_channels_json(void) {
sqlite3* db = chat_core_get_db(); sqlite3* db = chat_core_get_db();
if (!chat_core_is_initialized() || !db) { if (!chat_core_is_initialized() || !db) {
@ -274,7 +306,11 @@ char* utun_bridge_get_channels_json(void) {
const char* ds = strstr(dp, "\"d\":\""); const char* ds = strstr(dp, "\"d\":\"");
if (ds) { ds += 5; char* de = strchr((char*)ds, '"'); if (de) { size_t tl = (size_t)(de - ds); if (tl > 120) tl = 120; memcpy(txt, ds, tl); txt[tl] = '\0'; } } if (ds) { ds += 5; char* de = strchr((char*)ds, '"'); if (de) { size_t tl = (size_t)(de - ds); if (tl > 120) tl = 120; memcpy(txt, ds, tl); txt[tl] = '\0'; } }
} }
snprintf(last_msg, sizeof(last_msg), "0x%016llx: %s", (unsigned long long)author, txt); char author_name[128];
bridge_get_node_name(db, (uint64_t)author, author_name, sizeof(author_name));
if (!author_name[0])
snprintf(author_name, sizeof(author_name), "0x%016llx", (unsigned long long)author);
snprintf(last_msg, sizeof(last_msg), "%s: %s", author_name, txt);
} }
sqlite3_finalize(lm); sqlite3_finalize(lm);
} }
@ -344,12 +380,17 @@ char* utun_bridge_get_messages_json(const char* channel_id, int limit) {
const char* sep = first ? "" : ","; const char* sep = first ? "" : ",";
first = 0; first = 0;
size_t needed = snprintf(NULL, 0, "%s{\"id\":%lld,\"author\":\"0x%016llx\",\"text\":\"%s\",\"ts\":%lld,\"isOutgoing\":%s}", char author_name[128];
sep, (long long)msg_id, (unsigned long long)node_id, esc_txt, (long long)ts, is_out ? "true" : "false"); bridge_get_node_name(db, (uint64_t)node_id, author_name, sizeof(author_name));
if (!author_name[0])
snprintf(author_name, sizeof(author_name), "0x%016llx", (unsigned long long)node_id);
size_t needed = snprintf(NULL, 0, "%s{\"id\":%lld,\"author\":\"%s\",\"text\":\"%s\",\"ts\":%lld,\"isOutgoing\":%s}",
sep, (long long)msg_id, author_name, esc_txt, (long long)ts, is_out ? "true" : "false");
while (pos + needed + 2 > cap) { cap *= 2; char* tmp = u_realloc(json, cap); if (!tmp) break; json = tmp; } while (pos + needed + 2 > cap) { cap *= 2; char* tmp = u_realloc(json, cap); if (!tmp) break; json = tmp; }
if (pos + needed + 2 <= cap) if (pos + needed + 2 <= cap)
pos += (size_t)snprintf(json + pos, cap - pos, "%s{\"id\":%lld,\"author\":\"0x%016llx\",\"text\":\"%s\",\"ts\":%lld,\"isOutgoing\":%s}", pos += (size_t)snprintf(json + pos, cap - pos, "%s{\"id\":%lld,\"author\":\"%s\",\"text\":\"%s\",\"ts\":%lld,\"isOutgoing\":%s}",
sep, (long long)msg_id, (unsigned long long)node_id, esc_txt, (long long)ts, is_out ? "true" : "false"); sep, (long long)msg_id, author_name, esc_txt, (long long)ts, is_out ? "true" : "false");
u_free(esc_txt); u_free(esc_txt);
} }
sqlite3_finalize(st); sqlite3_finalize(st);

11
tools/chatgui-android/libutun_lite/instance_lite.c

@ -198,6 +198,12 @@ static void* instance_thread(void* arg) {
/* Bind chat sync via etcp_router */ /* Bind chat sync via etcp_router */
etcp_router_bind(g_inst, ETCP_RT_ID_CHAT_SYNC, NULL); etcp_router_bind(g_inst, ETCP_RT_ID_CHAT_SYNC, NULL);
/* Set my_name from config */
if (g_inst->config->global.name[0]) {
chat_core_update_my_name(g_inst->config->global.name);
IL_LOGI("my_name set to '%s'", g_inst->config->global.name);
}
IL_LOGI("all init done, entering poll loop"); IL_LOGI("all init done, entering poll loop");
/* Notify GUI of current public key (initial generation or existing) */ /* Notify GUI of current public key (initial generation or existing) */
@ -331,6 +337,11 @@ static void restart_trampoline(void* arg) {
return; return;
} }
if (g_inst->config->global.name[0]) {
chat_core_update_my_name(g_inst->config->global.name);
IL_LOGI("restart: my_name set to '%s'", g_inst->config->global.name);
}
IL_LOGI("restart: done"); IL_LOGI("restart: done");
} }

1
tools/chatgui/src/joindialog.cpp

@ -15,7 +15,6 @@
extern "C" { extern "C" {
#include "secure_channel.h" #include "secure_channel.h"
#include "../../../lib/sha256.h"
} }
JoinDialog::JoinDialog(UtunNode* node, DbManager* db, QWidget* parent, JoinDialog::JoinDialog(UtunNode* node, DbManager* db, QWidget* parent,

8
tools/chatgui/transport/node_config.cpp

@ -1,6 +1,7 @@
// node_config.cpp — Simplified INI config manager for embedded uTun node // node_config.cpp — Simplified INI config manager for embedded uTun node
#include "node_config.h" #include "node_config.h"
#include "config_updater.h" #include "config_updater.h"
#include <QHostInfo>
extern "C" { extern "C" {
#include <openssl/evp.h> #include <openssl/evp.h>
@ -165,7 +166,12 @@ void NodeConfig::generateIdentity() {
} }
bool NodeConfig::create(const QString& nodeName, quint16 listenPort) { bool NodeConfig::create(const QString& nodeName, quint16 listenPort) {
m_nodeName = nodeName.isEmpty() ? "chatgui" : nodeName; if (nodeName.isEmpty()) {
QString host = QHostInfo::localHostName();
m_nodeName = host.isEmpty() ? "chatgui" : host;
} else {
m_nodeName = nodeName;
}
if (m_pubKey.isEmpty()) generateIdentity(); if (m_pubKey.isEmpty()) generateIdentity();
if (m_controlAddr.isEmpty()) if (m_controlAddr.isEmpty())
m_controlAddr = "127.0.0.1:9999"; m_controlAddr = "127.0.0.1:9999";

Loading…
Cancel
Save