#include "xrayConfigModel.h" #include "core/utils/protocolEnum.h" #include "core/protocols/protocolUtils.h" #include "core/utils/constants/configKeys.h" #include "core/utils/constants/protocolConstants.h" using namespace amnezia; using namespace ProtocolUtils; XrayConfigModel::XrayConfigModel(QObject* parent) : QAbstractListModel(parent) { } int XrayConfigModel::rowCount(const QModelIndex& parent) const { Q_UNUSED(parent); return 1; } bool XrayConfigModel::setData(const QModelIndex& index, const QVariant& value, int role) { // This model always has a single row (row 0). Using rowCount() avoids // coupling editing ability to global container list size. if (!index.isValid() || index.row() < 0 || index.row() >= rowCount()) { return false; } const bool wasUnsavedChanges = hasUnsavedChanges(); auto& srv = m_protocolConfig.serverConfig; auto& xhttp = srv.xhttp; auto& mkcp = srv.mkcp; auto& pad = xhttp.xPadding; auto& mux = xhttp.xmux; QString str = value.toString(); switch (role) { // ── Main ────────────────────────────────────────────────────────── case Roles::SiteRole: srv.site = str; break; case Roles::PortRole: srv.port = str; break; case Roles::TransportRole: srv.transport = str; break; case Roles::SecurityRole: srv.security = str; break; case Roles::FlowRole: srv.flow = str; break; // ── Security ────────────────────────────────────────────────────── case Roles::FingerprintRole: srv.fingerprint = str; break; case Roles::SniRole: srv.sni = str; break; case Roles::AlpnRole: srv.alpn = str; break; // ── XHTTP ───────────────────────────────────────────────────────── case Roles::XhttpModeRole: xhttp.mode = str; break; case Roles::XhttpHostRole: xhttp.host = str; break; case Roles::XhttpPathRole: xhttp.path = str; break; case Roles::XhttpHeadersTemplateRole: xhttp.headersTemplate = str; break; case Roles::XhttpUplinkMethodRole: xhttp.uplinkMethod = str; break; case Roles::XhttpDisableGrpcRole: xhttp.disableGrpc = value.toBool(); break; case Roles::XhttpDisableSseRole: xhttp.disableSse = value.toBool(); break; case Roles::XhttpSessionPlacementRole: xhttp.sessionPlacement = str; break; case Roles::XhttpSessionKeyRole: xhttp.sessionKey = str; break; case Roles::XhttpSeqPlacementRole: xhttp.seqPlacement = str; break; case Roles::XhttpSeqKeyRole: xhttp.seqKey = str; break; case Roles::XhttpUplinkDataPlacementRole: xhttp.uplinkDataPlacement = str; break; case Roles::XhttpUplinkDataKeyRole: xhttp.uplinkDataKey = str; break; case Roles::XhttpUplinkChunkSizeRole: xhttp.uplinkChunkSize = str; break; case Roles::XhttpScMaxBufferedPostsRole: xhttp.scMaxBufferedPosts = str; break; case Roles::XhttpScMaxEachPostBytesMinRole: xhttp.scMaxEachPostBytesMin = str; break; case Roles::XhttpScMaxEachPostBytesMaxRole: xhttp.scMaxEachPostBytesMax = str; break; case Roles::XhttpScMinPostsIntervalMsMinRole: xhttp.scMinPostsIntervalMsMin = str; break; case Roles::XhttpScMinPostsIntervalMsMaxRole: xhttp.scMinPostsIntervalMsMax = str; break; case Roles::XhttpScStreamUpServerSecsMinRole: xhttp.scStreamUpServerSecsMin = str; break; case Roles::XhttpScStreamUpServerSecsMaxRole: xhttp.scStreamUpServerSecsMax = str; break; // ── mKCP ────────────────────────────────────────────────────────── case Roles::MkcpTtiRole: mkcp.tti = str; break; case Roles::MkcpUplinkCapacityRole: mkcp.uplinkCapacity = str; break; case Roles::MkcpDownlinkCapacityRole: mkcp.downlinkCapacity = str; break; case Roles::MkcpReadBufferSizeRole: mkcp.readBufferSize = str; break; case Roles::MkcpWriteBufferSizeRole: mkcp.writeBufferSize = str; break; case Roles::MkcpCongestionRole: mkcp.congestion = value.toBool(); break; // ── xPadding ────────────────────────────────────────────────────── case Roles::XPaddingBytesMinRole: pad.bytesMin = str; break; case Roles::XPaddingBytesMaxRole: pad.bytesMax = str; break; case Roles::XPaddingObfsModeRole: pad.obfsMode = value.toBool(); break; case Roles::XPaddingKeyRole: pad.key = str; break; case Roles::XPaddingHeaderRole: pad.header = str; break; case Roles::XPaddingPlacementRole: pad.placement = str; break; case Roles::XPaddingMethodRole: pad.method = str; break; // ── xmux ────────────────────────────────────────────────────────── case Roles::XmuxEnabledRole: mux.enabled = value.toBool(); break; case Roles::XmuxMaxConcurrencyMinRole: mux.maxConcurrencyMin = str; break; case Roles::XmuxMaxConcurrencyMaxRole: mux.maxConcurrencyMax = str; break; case Roles::XmuxMaxConnectionsMinRole: mux.maxConnectionsMin = str; break; case Roles::XmuxMaxConnectionsMaxRole: mux.maxConnectionsMax = str; break; case Roles::XmuxCMaxReuseTimesMinRole: mux.cMaxReuseTimesMin = str; break; case Roles::XmuxCMaxReuseTimesMaxRole: mux.cMaxReuseTimesMax = str; break; case Roles::XmuxHMaxRequestTimesMinRole: mux.hMaxRequestTimesMin = str; break; case Roles::XmuxHMaxRequestTimesMaxRole: mux.hMaxRequestTimesMax = str; break; case Roles::XmuxHMaxReusableSecsMinRole: mux.hMaxReusableSecsMin = str; break; case Roles::XmuxHMaxReusableSecsMaxRole: mux.hMaxReusableSecsMax = str; break; case Roles::XmuxHKeepAlivePeriodRole: mux.hKeepAlivePeriod = str; break; default: return false; } emit dataChanged(index, index, QList{role}); if (wasUnsavedChanges != hasUnsavedChanges()) { emit hasUnsavedChangesChanged(); } return true; } QVariant XrayConfigModel::data(const QModelIndex& index, int role) const { if (!index.isValid() || index.row() < 0 || index.row() >= rowCount()) { return QVariant(); } const auto& srv = m_protocolConfig.serverConfig; const auto& xhttp = srv.xhttp; const auto& mkcp = srv.mkcp; const auto& pad = xhttp.xPadding; const auto& mux = xhttp.xmux; switch (role) { // ── Main ────────────────────────────────────────────────────────── case Roles::SiteRole: return srv.site; case Roles::PortRole: return srv.port; case Roles::TransportRole: return srv.transport; case Roles::SecurityRole: return srv.security; case Roles::FlowRole: return srv.flow; // ── Security ────────────────────────────────────────────────────── case Roles::FingerprintRole: return srv.fingerprint; case Roles::SniRole: return srv.sni; case Roles::AlpnRole: return srv.alpn; // ── XHTTP ───────────────────────────────────────────────────────── case Roles::XhttpModeRole: return xhttp.mode; case Roles::XhttpHostRole: return xhttp.host; case Roles::XhttpPathRole: return xhttp.path; case Roles::XhttpHeadersTemplateRole: return xhttp.headersTemplate; case Roles::XhttpUplinkMethodRole: return xhttp.uplinkMethod; case Roles::XhttpDisableGrpcRole: return xhttp.disableGrpc; case Roles::XhttpDisableSseRole: return xhttp.disableSse; case Roles::XhttpSessionPlacementRole: return xhttp.sessionPlacement; case Roles::XhttpSessionKeyRole: return xhttp.sessionKey; case Roles::XhttpSeqPlacementRole: return xhttp.seqPlacement; case Roles::XhttpSeqKeyRole: return xhttp.seqKey; case Roles::XhttpUplinkDataPlacementRole: return xhttp.uplinkDataPlacement; case Roles::XhttpUplinkDataKeyRole: return xhttp.uplinkDataKey; case Roles::XhttpUplinkChunkSizeRole: return xhttp.uplinkChunkSize; case Roles::XhttpScMaxBufferedPostsRole: return xhttp.scMaxBufferedPosts; case Roles::XhttpScMaxEachPostBytesMinRole: return xhttp.scMaxEachPostBytesMin; case Roles::XhttpScMaxEachPostBytesMaxRole: return xhttp.scMaxEachPostBytesMax; case Roles::XhttpScMinPostsIntervalMsMinRole: return xhttp.scMinPostsIntervalMsMin; case Roles::XhttpScMinPostsIntervalMsMaxRole: return xhttp.scMinPostsIntervalMsMax; case Roles::XhttpScStreamUpServerSecsMinRole: return xhttp.scStreamUpServerSecsMin; case Roles::XhttpScStreamUpServerSecsMaxRole: return xhttp.scStreamUpServerSecsMax; // ── mKCP ────────────────────────────────────────────────────────── case Roles::MkcpTtiRole: return mkcp.tti; case Roles::MkcpUplinkCapacityRole: return mkcp.uplinkCapacity; case Roles::MkcpDownlinkCapacityRole: return mkcp.downlinkCapacity; case Roles::MkcpReadBufferSizeRole: return mkcp.readBufferSize; case Roles::MkcpWriteBufferSizeRole: return mkcp.writeBufferSize; case Roles::MkcpCongestionRole: return mkcp.congestion; // ── xPadding ────────────────────────────────────────────────────── case Roles::XPaddingBytesMinRole: return pad.bytesMin; case Roles::XPaddingBytesMaxRole: return pad.bytesMax; case Roles::XPaddingObfsModeRole: return pad.obfsMode; case Roles::XPaddingKeyRole: return pad.key; case Roles::XPaddingHeaderRole: return pad.header; case Roles::XPaddingPlacementRole: return pad.placement; case Roles::XPaddingMethodRole: return pad.method; // ── xmux ────────────────────────────────────────────────────────── case Roles::XmuxEnabledRole: return mux.enabled; case Roles::XmuxMaxConcurrencyMinRole: return mux.maxConcurrencyMin; case Roles::XmuxMaxConcurrencyMaxRole: return mux.maxConcurrencyMax; case Roles::XmuxMaxConnectionsMinRole: return mux.maxConnectionsMin; case Roles::XmuxMaxConnectionsMaxRole: return mux.maxConnectionsMax; case Roles::XmuxCMaxReuseTimesMinRole: return mux.cMaxReuseTimesMin; case Roles::XmuxCMaxReuseTimesMaxRole: return mux.cMaxReuseTimesMax; case Roles::XmuxHMaxRequestTimesMinRole: return mux.hMaxRequestTimesMin; case Roles::XmuxHMaxRequestTimesMaxRole: return mux.hMaxRequestTimesMax; case Roles::XmuxHMaxReusableSecsMinRole: return mux.hMaxReusableSecsMin; case Roles::XmuxHMaxReusableSecsMaxRole: return mux.hMaxReusableSecsMax; case Roles::XmuxHKeepAlivePeriodRole: return mux.hKeepAlivePeriod; } return QVariant(); } void XrayConfigModel::updateModel(amnezia::DockerContainer container, const amnezia::XrayProtocolConfig& protocolConfig) { const bool wasUnsavedChanges = hasUnsavedChanges(); beginResetModel(); m_container = container; m_protocolConfig = protocolConfig; if (m_protocolConfig.needsClientHydration) { m_protocolConfig.hydrateServerConfigFromClientNative(); } if (!m_protocolConfig.serverConfig.isThirdPartyConfig) { applyDefaultsToServerConfig(m_protocolConfig.serverConfig); } m_originalProtocolConfig = m_protocolConfig; endResetModel(); if (wasUnsavedChanges != hasUnsavedChanges()) { emit hasUnsavedChangesChanged(); } } void XrayConfigModel::applyDefaultsToServerConfig(amnezia::XrayServerConfig &config) { if (config.port.isEmpty()) { config.port = protocols::xray::defaultPort; } if (config.transportProto.isEmpty()) { config.transportProto = ProtocolUtils::transportProtoToString( ProtocolUtils::defaultTransportProto(amnezia::Proto::Xray), amnezia::Proto::Xray); } if (config.site.isEmpty()) { config.site = protocols::xray::defaultSite; } if (config.transport.isEmpty()) { config.transport = protocols::xray::defaultTransport; } if (config.security.isEmpty()) { config.security = protocols::xray::defaultSecurity; } if (config.flow.isEmpty()) { config.flow = protocols::xray::defaultFlow; } if (config.fingerprint.isEmpty()) { config.fingerprint = protocols::xray::defaultFingerprint; } else if (config.fingerprint.contains(QLatin1String("Mozilla/5.0"), Qt::CaseInsensitive)) { config.fingerprint = QString::fromLatin1(protocols::xray::defaultFingerprint); } if (config.sni.isEmpty()) { config.sni = protocols::xray::defaultSni; } if (config.alpn.isEmpty()) { config.alpn = protocols::xray::defaultAlpn; } // XHTTP transport defaults if (config.xhttp.host.isEmpty()) { config.xhttp.host = protocols::xray::defaultXhttpHost; } if (config.xhttp.mode.isEmpty()) { config.xhttp.mode = protocols::xray::defaultXhttpMode; } if (config.xhttp.headersTemplate.isEmpty()) { config.xhttp.headersTemplate = protocols::xray::defaultXhttpHeadersTemplate; } if (config.xhttp.uplinkMethod.isEmpty()) { config.xhttp.uplinkMethod = protocols::xray::defaultXhttpUplinkMethod; } if (config.xhttp.sessionPlacement.isEmpty()) { config.xhttp.sessionPlacement = protocols::xray::defaultXhttpSessionPlacement; } if (config.xhttp.sessionKey.isEmpty()) { config.xhttp.sessionKey = protocols::xray::defaultXhttpSessionKey; } if (config.xhttp.seqPlacement.isEmpty()) { config.xhttp.seqPlacement = protocols::xray::defaultXhttpSeqPlacement; } if (config.xhttp.uplinkDataPlacement.isEmpty()) { config.xhttp.uplinkDataPlacement = protocols::xray::defaultXhttpUplinkDataPlacement; } // xPadding defaults if (config.xhttp.xPadding.placement.isEmpty()) { config.xhttp.xPadding.placement = protocols::xray::defaultXPaddingPlacement; } if (config.xhttp.xPadding.method.isEmpty()) { config.xhttp.xPadding.method = protocols::xray::defaultXPaddingMethod; } } amnezia::XrayProtocolConfig XrayConfigModel::getProtocolConfig() { const bool serverSettingsChanged = !m_protocolConfig.serverConfig.hasEqualServerSettings(m_originalProtocolConfig.serverConfig); if (serverSettingsChanged) { m_protocolConfig.clearClientConfig(); } return m_protocolConfig; } bool XrayConfigModel::isServerSettingsEqual() const { return m_protocolConfig.serverConfig.hasEqualServerSettings(m_originalProtocolConfig.serverConfig); } bool XrayConfigModel::hasUnsavedChanges() const { return !isServerSettingsEqual(); } QHash XrayConfigModel::roleNames() const { QHash roles; // Main roles[SiteRole] = "site"; roles[PortRole] = "port"; roles[TransportRole] = "transport"; roles[SecurityRole] = "security"; roles[FlowRole] = "flow"; // Security roles[FingerprintRole] = "fingerprint"; roles[SniRole] = "sni"; roles[AlpnRole] = "alpn"; // XHTTP roles[XhttpModeRole] = "xhttpMode"; roles[XhttpHostRole] = "xhttpHost"; roles[XhttpPathRole] = "xhttpPath"; roles[XhttpHeadersTemplateRole] = "xhttpHeadersTemplate"; roles[XhttpUplinkMethodRole] = "xhttpUplinkMethod"; roles[XhttpDisableGrpcRole] = "xhttpDisableGrpc"; roles[XhttpDisableSseRole] = "xhttpDisableSse"; roles[XhttpSessionPlacementRole] = "xhttpSessionPlacement"; roles[XhttpSessionKeyRole] = "xhttpSessionKey"; roles[XhttpSeqPlacementRole] = "xhttpSeqPlacement"; roles[XhttpSeqKeyRole] = "xhttpSeqKey"; roles[XhttpUplinkDataPlacementRole] = "xhttpUplinkDataPlacement"; roles[XhttpUplinkDataKeyRole] = "xhttpUplinkDataKey"; roles[XhttpUplinkChunkSizeRole] = "xhttpUplinkChunkSize"; roles[XhttpScMaxBufferedPostsRole] = "xhttpScMaxBufferedPosts"; roles[XhttpScMaxEachPostBytesMinRole] = "xhttpScMaxEachPostBytesMin"; roles[XhttpScMaxEachPostBytesMaxRole] = "xhttpScMaxEachPostBytesMax"; roles[XhttpScMinPostsIntervalMsMinRole] = "xhttpScMinPostsIntervalMsMin"; roles[XhttpScMinPostsIntervalMsMaxRole] = "xhttpScMinPostsIntervalMsMax"; roles[XhttpScStreamUpServerSecsMinRole] = "xhttpScStreamUpServerSecsMin"; roles[XhttpScStreamUpServerSecsMaxRole] = "xhttpScStreamUpServerSecsMax"; // mKCP roles[MkcpTtiRole] = "mkcpTti"; roles[MkcpUplinkCapacityRole] = "mkcpUplinkCapacity"; roles[MkcpDownlinkCapacityRole] = "mkcpDownlinkCapacity"; roles[MkcpReadBufferSizeRole] = "mkcpReadBufferSize"; roles[MkcpWriteBufferSizeRole] = "mkcpWriteBufferSize"; roles[MkcpCongestionRole] = "mkcpCongestion"; // xPadding roles[XPaddingBytesMinRole] = "xPaddingBytesMin"; roles[XPaddingBytesMaxRole] = "xPaddingBytesMax"; roles[XPaddingObfsModeRole] = "xPaddingObfsMode"; roles[XPaddingKeyRole] = "xPaddingKey"; roles[XPaddingHeaderRole] = "xPaddingHeader"; roles[XPaddingPlacementRole] = "xPaddingPlacement"; roles[XPaddingMethodRole] = "xPaddingMethod"; // xmux roles[XmuxEnabledRole] = "xmuxEnabled"; roles[XmuxMaxConcurrencyMinRole] = "xmuxMaxConcurrencyMin"; roles[XmuxMaxConcurrencyMaxRole] = "xmuxMaxConcurrencyMax"; roles[XmuxMaxConnectionsMinRole] = "xmuxMaxConnectionsMin"; roles[XmuxMaxConnectionsMaxRole] = "xmuxMaxConnectionsMax"; roles[XmuxCMaxReuseTimesMinRole] = "xmuxCMaxReuseTimesMin"; roles[XmuxCMaxReuseTimesMaxRole] = "xmuxCMaxReuseTimesMax"; roles[XmuxHMaxRequestTimesMinRole] = "xmuxHMaxRequestTimesMin"; roles[XmuxHMaxRequestTimesMaxRole] = "xmuxHMaxRequestTimesMax"; roles[XmuxHMaxReusableSecsMinRole] = "xmuxHMaxReusableSecsMin"; roles[XmuxHMaxReusableSecsMaxRole] = "xmuxHMaxReusableSecsMax"; roles[XmuxHKeepAlivePeriodRole] = "xmuxHKeepAlivePeriod"; return roles; } void XrayConfigModel::resetToDefaults() { const bool wasUnsavedChanges = hasUnsavedChanges(); beginResetModel(); m_protocolConfig.serverConfig = amnezia::XrayServerConfig{}; applyDefaultsToServerConfig(m_protocolConfig.serverConfig); endResetModel(); if (wasUnsavedChanges != hasUnsavedChanges()) { emit hasUnsavedChangesChanged(); } } void XrayConfigModel::applyServerConfig(const amnezia::XrayServerConfig &serverConfig) { const bool wasUnsavedChanges = hasUnsavedChanges(); beginResetModel(); m_protocolConfig.serverConfig = serverConfig; // Clear client config since server settings changed m_protocolConfig.clearClientConfig(); m_originalProtocolConfig = m_protocolConfig; endResetModel(); if (wasUnsavedChanges != hasUnsavedChanges()) { emit hasUnsavedChangesChanged(); } } QStringList XrayConfigModel::flowOptions() { return { "", // Empty (no flow) "xtls-rprx-vision", "xtls-rprx-vision-udp443" }; } QStringList XrayConfigModel::securityOptions() { return { "none", "tls", "reality" }; } QStringList XrayConfigModel::transportOptions() { return { "raw", "xhttp", "mkcp" }; } QStringList XrayConfigModel::fingerprintOptions() { return { "chrome", "firefox", "safari", "ios", "android", "edge", "360", "qq", "random" }; } QStringList XrayConfigModel::alpnOptions() { return { "HTTP/2", "HTTP/1.1", "HTTP/2,HTTP/1.1" }; } QStringList XrayConfigModel::xhttpModeOptions() { return { "Auto", "Packet-up", "Stream-up", "Stream-one" }; } QStringList XrayConfigModel::xhttpHeadersTemplateOptions() { return { "HTTP", "None" }; } QStringList XrayConfigModel::xhttpUplinkMethodOptions() { return { "POST", "PUT", "PATCH" }; } QStringList XrayConfigModel::xhttpSessionPlacementOptions() { return { "Path", "Header", "Cookie", "None" }; } QStringList XrayConfigModel::xhttpSessionKeyOptions() { return { "Path", "Header", "None" }; } QStringList XrayConfigModel::xhttpSeqPlacementOptions() { return { "Path", "Header", "Cookie", "None" }; } QStringList XrayConfigModel::xhttpUplinkDataPlacementOptions() { // Matches splithttp uplink payload placement (packet-up / advanced) return { "Body", "Auto", "Header", "Cookie" }; } QStringList XrayConfigModel::xPaddingPlacementOptions() { // Xray-core: cookie | header | query | queryInHeader (not "body") return { "Cookie", "Header", "Query", "Query in header" }; } QStringList XrayConfigModel::xPaddingMethodOptions() { return { "Repeat-x", "Tokenish" }; } QString XrayConfigModel::mkcpDefaultTti() { return QString::fromLatin1(protocols::xray::defaultMkcpTti); } QString XrayConfigModel::mkcpDefaultUplinkCapacity() { return QString::fromLatin1(protocols::xray::defaultMkcpUplinkCapacity); } QString XrayConfigModel::mkcpDefaultDownlinkCapacity() { return QString::fromLatin1(protocols::xray::defaultMkcpDownlinkCapacity); } QString XrayConfigModel::mkcpDefaultReadBufferSize() { return QString::fromLatin1(protocols::xray::defaultMkcpReadBufferSize); } QString XrayConfigModel::mkcpDefaultWriteBufferSize() { return QString::fromLatin1(protocols::xray::defaultMkcpWriteBufferSize); }