// node_config.cpp — Simplified INI config manager for embedded uTun node #include "node_config.h" extern "C" { #include #include } #include #include #include static QString formatKeyHex(const uint8_t* bin, size_t len) { return QByteArray(reinterpret_cast(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(&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; } } f.close(); if (!controlIp.isEmpty() && !controlPort.isEmpty()) m_controlAddr = controlIp + ":" + controlPort; return !m_nodeId.isEmpty(); } 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\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(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; }