228 lines
7.6 KiB
C++
228 lines
7.6 KiB
C++
// node_config.cpp — Simplified INI config manager for embedded uTun node
|
|
#include "node_config.h"
|
|
|
|
extern "C" {
|
|
#include <openssl/evp.h>
|
|
#include <openssl/rand.h>
|
|
}
|
|
#include <QFile>
|
|
#include <QTextStream>
|
|
#include <QFileInfo>
|
|
|
|
static QString formatKeyHex(const uint8_t* bin, size_t len) {
|
|
return QByteArray(reinterpret_cast<const char*>(bin), len).toHex();
|
|
}
|
|
|
|
static bool parseKeyHex(const QString& hex, uint8_t* bin, size_t len) {
|
|
QByteArray ba = QByteArray::fromHex(hex.toLatin1());
|
|
if (ba.size() != (int)len) return false;
|
|
memcpy(bin, ba.constData(), len);
|
|
return true;
|
|
}
|
|
|
|
// =====================================================================
|
|
// NodeConfig
|
|
// =====================================================================
|
|
|
|
NodeConfig::NodeConfig(const QString& path) : m_path(path) {}
|
|
|
|
bool NodeConfig::exists() const {
|
|
return QFileInfo::exists(m_path);
|
|
}
|
|
|
|
void NodeConfig::generateIdentity() {
|
|
// X25519 keypair
|
|
EVP_PKEY* pkey = nullptr;
|
|
EVP_PKEY_CTX* ctx = EVP_PKEY_CTX_new_id(EVP_PKEY_X25519, nullptr);
|
|
if (ctx) {
|
|
EVP_PKEY_keygen_init(ctx);
|
|
EVP_PKEY_generate(ctx, &pkey);
|
|
EVP_PKEY_CTX_free(ctx);
|
|
}
|
|
if (pkey) {
|
|
size_t pkLen = 32, skLen = 32;
|
|
uint8_t pk[32], sk[32];
|
|
EVP_PKEY_get_raw_public_key(pkey, pk, &pkLen);
|
|
EVP_PKEY_get_raw_private_key(pkey, sk, &skLen);
|
|
m_pubKey = formatKeyHex(pk, 32);
|
|
m_privKey = formatKeyHex(sk, 32);
|
|
EVP_PKEY_free(pkey);
|
|
}
|
|
|
|
// Random 63-bit node_id
|
|
uint64_t id = 0;
|
|
RAND_bytes(reinterpret_cast<uint8_t*>(&id), sizeof(id));
|
|
id &= 0x7FFFFFFFFFFFFFFFULL;
|
|
m_nodeId = QString("%1").arg(id, 16, 16, QChar('0')).right(16);
|
|
}
|
|
|
|
bool NodeConfig::create(const QString& nodeName, quint16 listenPort) {
|
|
m_nodeName = nodeName.isEmpty() ? "chatgui" : nodeName;
|
|
if (m_pubKey.isEmpty()) generateIdentity();
|
|
if (m_controlAddr.isEmpty())
|
|
m_controlAddr = "127.0.0.1:9999";
|
|
m_servers.clear();
|
|
m_clients.clear();
|
|
if (listenPort > 0)
|
|
m_servers.append({"lan", QString("0.0.0.0:%1").arg(listenPort)});
|
|
if (m_dbPath.isEmpty())
|
|
m_dbPath = "chats.db";
|
|
return save();
|
|
}
|
|
|
|
bool NodeConfig::load() {
|
|
QFile f(m_path);
|
|
if (!f.open(QIODevice::ReadOnly | QIODevice::Text)) return false;
|
|
|
|
m_servers.clear();
|
|
m_clients.clear();
|
|
|
|
QString section, controlIp, controlPort;
|
|
QTextStream in(&f);
|
|
while (!in.atEnd()) {
|
|
QString line = in.readLine().trimmed();
|
|
if (line.isEmpty() || line.startsWith('#')) continue;
|
|
|
|
if (line.startsWith('[') && line.endsWith(']')) {
|
|
section = line.mid(1, line.length() - 2);
|
|
continue;
|
|
}
|
|
|
|
int eq = line.indexOf('=');
|
|
if (eq < 0) continue;
|
|
QString key = line.left(eq).trimmed();
|
|
QString val = line.mid(eq + 1).trimmed();
|
|
|
|
if (section == "global") {
|
|
if (key == "my_node_name") m_nodeName = val;
|
|
else if (key == "my_node_id") m_nodeId = val;
|
|
else if (key == "my_public_key") m_pubKey = val;
|
|
else if (key == "my_private_key") m_privKey = val;
|
|
} else if (section.startsWith("server:")) {
|
|
QString srvName = section.mid(7);
|
|
if (key == "addr") m_servers.append({srvName, val});
|
|
} else if (section.startsWith("client:")) {
|
|
QString cliName = section.mid(7);
|
|
if (key == "peer_public_key") {
|
|
NodeClient nc;
|
|
nc.name = cliName;
|
|
nc.pubKeyHex = val;
|
|
m_clients.append(nc);
|
|
} else if (key == "link") {
|
|
if (!m_clients.isEmpty() && m_clients.last().name == cliName) {
|
|
// link=server:addr:port
|
|
int sep1 = val.indexOf(':');
|
|
int sep2 = val.lastIndexOf(':');
|
|
if (sep1 > 0 && sep2 > sep1) {
|
|
m_clients.last().serverName = val.left(sep1);
|
|
m_clients.last().remoteAddr = val.mid(sep1 + 1);
|
|
}
|
|
}
|
|
}
|
|
} else if (section == "control") {
|
|
if (key == "ip" || key == "control_ip") controlIp = val;
|
|
else if (key == "port" || key == "control_port") controlPort = val;
|
|
} else if (section == "gui") {
|
|
if (key == "db_path") m_dbPath = val;
|
|
else if (key == "debug_file") m_debugFile = val;
|
|
else if (key == "debug_level") m_debugLevel = val;
|
|
else if (key == "debug_categories") m_debugCategories = val;
|
|
}
|
|
}
|
|
f.close();
|
|
|
|
if (!controlIp.isEmpty() && !controlPort.isEmpty())
|
|
m_controlAddr = controlIp + ":" + controlPort;
|
|
|
|
return true;
|
|
}
|
|
|
|
bool NodeConfig::save() {
|
|
QFile f(m_path);
|
|
if (!f.open(QIODevice::WriteOnly | QIODevice::Text)) return false;
|
|
|
|
QTextStream out(&f);
|
|
out << "[global]\n";
|
|
if (!m_nodeName.isEmpty()) out << "my_node_name=" << m_nodeName << "\n";
|
|
if (!m_nodeId.isEmpty()) out << "my_node_id=" << m_nodeId << "\n";
|
|
if (!m_privKey.isEmpty()) out << "my_private_key=" << m_privKey << "\n";
|
|
if (!m_pubKey.isEmpty()) out << "my_public_key=" << m_pubKey << "\n";
|
|
out << "\n";
|
|
|
|
for (auto& srv : m_servers) {
|
|
out << "[server:" << srv.first << "]\n";
|
|
out << "addr=" << srv.second << "\n\n";
|
|
}
|
|
|
|
for (auto& cli : m_clients) {
|
|
out << "[client:" << cli.name << "]\n";
|
|
out << "peer_public_key=" << cli.pubKeyHex << "\n";
|
|
out << "link=" << cli.serverName << ":" << cli.remoteAddr << "\n\n";
|
|
}
|
|
|
|
if (!m_controlAddr.isEmpty()) {
|
|
out << "[control]\n";
|
|
int sep = m_controlAddr.lastIndexOf(':');
|
|
if (sep > 0) {
|
|
out << "ip=" << m_controlAddr.left(sep) << "\n";
|
|
out << "port=" << m_controlAddr.mid(sep + 1) << "\n\n";
|
|
}
|
|
}
|
|
|
|
out << "[gui]\n";
|
|
out << "db_path=" << (m_dbPath.isEmpty() ? "chats.db" : m_dbPath) << "\n";
|
|
out << "debug_file=" << m_debugFile << "\n";
|
|
out << "debug_level=" << (m_debugLevel.isEmpty() ? "info" : m_debugLevel) << "\n";
|
|
out << "debug_categories=" << m_debugCategories << "\n\n";
|
|
|
|
f.close();
|
|
return true;
|
|
}
|
|
|
|
uint64_t NodeConfig::nodeIdU64() const {
|
|
if (m_nodeId.length() != 16) return 0;
|
|
return m_nodeId.toULongLong(nullptr, 16);
|
|
}
|
|
|
|
quint16 NodeConfig::controlPort() const {
|
|
if (m_controlAddr.isEmpty()) return 0;
|
|
int sep = m_controlAddr.lastIndexOf(':');
|
|
if (sep < 0) return 0;
|
|
return static_cast<quint16>(m_controlAddr.mid(sep + 1).toUInt());
|
|
}
|
|
|
|
void NodeConfig::setControlPort(quint16 p) {
|
|
int sep = m_controlAddr.lastIndexOf(':');
|
|
if (sep >= 0)
|
|
m_controlAddr = m_controlAddr.left(sep) + ":" + QString::number(p);
|
|
else
|
|
m_controlAddr = "127.0.0.1:" + QString::number(p);
|
|
}
|
|
|
|
bool NodeConfig::addServer(const QString& name, const QString& addr) {
|
|
for (auto& s : m_servers) { if (s.first == name) return false; }
|
|
m_servers.append({name, addr});
|
|
return save();
|
|
}
|
|
|
|
bool NodeConfig::removeServer(const QString& name) {
|
|
for (int i = 0; i < m_servers.size(); ++i) {
|
|
if (m_servers[i].first == name) { m_servers.removeAt(i); return save(); }
|
|
}
|
|
return false;
|
|
}
|
|
|
|
bool NodeConfig::addClient(const QString& name, const QString& pubKeyHex,
|
|
const QString& serverName, const QString& remoteAddr) {
|
|
for (auto& c : m_clients) { if (c.name == name) return false; }
|
|
m_clients.append({name, pubKeyHex, serverName, remoteAddr});
|
|
return save();
|
|
}
|
|
|
|
bool NodeConfig::removeClient(const QString& name) {
|
|
for (int i = 0; i < m_clients.size(); ++i) {
|
|
if (m_clients[i].name == name) { m_clients.removeAt(i); return save(); }
|
|
}
|
|
return false;
|
|
}
|