diff --git a/.github/workflows/build-freebsd.yml b/.github/workflows/build-freebsd.yml new file mode 100644 index 00000000..dbc4028c --- /dev/null +++ b/.github/workflows/build-freebsd.yml @@ -0,0 +1,20 @@ +name: Build on FreeBSD + +on: [push, pull_request] + +jobs: + build: + runs-on: macos-latest + name: with UPnP + steps: + - uses: actions/checkout@v2 + - name: Test in FreeBSD + id: test + uses: vmactions/freebsd-vm@v0.0.9 + with: + usesh: true + prepare: pkg install -y devel/cmake devel/gmake devel/boost-libs security/openssl net/miniupnpc + run: | + cd build + cmake -DWITH_UPNP=ON -DCMAKE_BUILD_TYPE=Release . + gmake -j2 diff --git a/Makefile.linux b/Makefile.linux index ee6a902b..da5f68b4 100644 --- a/Makefile.linux +++ b/Makefile.linux @@ -58,7 +58,7 @@ ifeq ($(USE_UPNP),yes) endif ifeq ($(USE_AESNI),yes) -ifeq (, $(findstring arm, $(SYS))$(findstring aarch64, $(SYS))) # no arm and aarch64 in dumpmachine +ifneq (, $(findstring i386, $(SYS))$(findstring i686, $(SYS))$(findstring x86_64, $(SYS))) # only x86-based CPU supports that NEEDED_CXXFLAGS += -D__AES__ -maes endif endif diff --git a/build/build_mingw.cmd b/build/build_mingw.cmd index aaf25843..e8f1378b 100644 --- a/build/build_mingw.cmd +++ b/build/build_mingw.cmd @@ -64,7 +64,7 @@ call :BUILDING_XP echo. REM compile installer -C:\PROGRA~2\INNOSE~1\ISCC.exe /dI2Pd_ver="%tag%" build\win_installer.iss >> build\build.log 2>&1 +C:\PROGRA~2\INNOSE~1\ISCC.exe /dI2Pd_TextVer="%tag%" /dI2Pd_Ver="%tag%.0" build\win_installer.iss >> build\build.log 2>&1 del README.txt i2pd_x32.exe i2pd_x64.exe i2pd_xp.exe >> nul diff --git a/build/win_installer.iss b/build/win_installer.iss index 8a93a2c7..97cd4408 100644 --- a/build/win_installer.iss +++ b/build/win_installer.iss @@ -6,25 +6,35 @@ [Setup] AppName={#I2Pd_AppName} -AppVersion={#I2Pd_ver} +AppVersion={#I2Pd_TextVer} AppPublisher={#I2Pd_Publisher} + DefaultDirName={pf}\I2Pd DefaultGroupName=I2Pd UninstallDisplayIcon={app}\I2Pd.exe OutputDir=. +OutputBaseFilename=setup_{#I2Pd_AppName}_v{#I2Pd_TextVer} + LicenseFile=..\LICENSE -OutputBaseFilename=setup_{#I2Pd_AppName}_v{#I2Pd_ver} SetupIconFile=..\Win32\mask.ico + InternalCompressLevel=ultra64 Compression=lzma/ultra64 SolidCompression=true + ArchitecturesInstallIn64BitMode=x64 -AppVerName={#I2Pd_AppName} ExtraDiskSpaceRequired=15 + AppID={{621A23E0-3CF4-4BD6-97BC-4835EA5206A2} +AppVerName={#I2Pd_AppName} +AppCopyright=Copyright (c) 2013-2020, The PurpleI2P Project AppPublisherURL=http://i2pd.website/ AppSupportURL=https://github.com/PurpleI2P/i2pd/issues AppUpdatesURL=https://github.com/PurpleI2P/i2pd/releases + +VersionInfoProductVersion={#I2Pd_Ver} +VersionInfoVersion={#I2Pd_Ver} + CloseApplications=yes [Files] diff --git a/contrib/docker/Dockerfile b/contrib/docker/Dockerfile index be3feea7..adb7ba75 100644 --- a/contrib/docker/Dockerfile +++ b/contrib/docker/Dockerfile @@ -11,6 +11,7 @@ ENV REPO_URL=${REPO_URL} ENV I2PD_HOME="/home/i2pd" ENV DATA_DIR="${I2PD_HOME}/data" +ENV DEFAULT_ARGS=" --datadir=$DATA_DIR --reseed.verify=true --upnp.enabled=false --http.enabled=true --http.address=0.0.0.0 --httpproxy.enabled=true --httpproxy.address=0.0.0.0 --socksproxy.enabled=true --socksproxy.address=0.0.0.0 --sam.enabled=true --sam.address=0.0.0.0" RUN mkdir -p "$I2PD_HOME" "$DATA_DIR" \ && adduser -S -h "$I2PD_HOME" i2pd \ diff --git a/contrib/docker/entrypoint.sh b/contrib/docker/entrypoint.sh index ca9dddd4..ce897e3c 100644 --- a/contrib/docker/entrypoint.sh +++ b/contrib/docker/entrypoint.sh @@ -2,7 +2,6 @@ COMMAND=/usr/local/bin/i2pd # To make ports exposeable # Note: $DATA_DIR is defined in /etc/profile -DEFAULT_ARGS=" --datadir=$DATA_DIR --reseed.verify=true --upnp.enabled=false --http.enabled=true --http.address=0.0.0.0 --httpproxy.enabled=true --httpproxy.address=0.0.0.0 --socksproxy.enabled=true --socksproxy.address=0.0.0.0 --sam.enabled=true --sam.address=0.0.0.0" if [ "$1" = "--help" ]; then set -- $COMMAND --help diff --git a/daemon/HTTPServer.cpp b/daemon/HTTPServer.cpp index 9f420cf0..aba30fd7 100644 --- a/daemon/HTTPServer.cpp +++ b/daemon/HTTPServer.cpp @@ -532,7 +532,7 @@ namespace http { } } - static void ShowI2CPLocalDestination (std::stringstream& s, const std::string& id) + void ShowI2CPLocalDestination (std::stringstream& s, const std::string& id) { auto i2cpServer = i2p::client::context.GetI2CPServer (); if (i2cpServer) @@ -820,7 +820,7 @@ namespace http { s << "SAM Sessions: no sessions currently running.
\r\n"; } - static void ShowSAMSession (std::stringstream& s, const std::string& id) + void ShowSAMSession (std::stringstream& s, const std::string& id) { auto sam = i2p::client::context.GetSAMBridge (); if (!sam) { @@ -1312,6 +1312,8 @@ namespace http { void HTTPServer::Run () { + i2p::util::SetThreadName("Webconsole"); + while (m_IsRunning) { try diff --git a/daemon/HTTPServer.h b/daemon/HTTPServer.h index a977e3e8..8e1520b8 100644 --- a/daemon/HTTPServer.h +++ b/daemon/HTTPServer.h @@ -98,6 +98,8 @@ namespace http void ShowSAMSessions (std::stringstream& s); void ShowI2PTunnels (std::stringstream& s); void ShowLocalDestination (std::stringstream& s, const std::string& b32, uint32_t token); + void ShowSAMSession (std::stringstream& s, const std::string& id); + void ShowI2CPLocalDestination (std::stringstream& s, const std::string& id); } // http } // i2p diff --git a/daemon/I2PControl.cpp b/daemon/I2PControl.cpp index e8c6e031..3f0033e5 100644 --- a/daemon/I2PControl.cpp +++ b/daemon/I2PControl.cpp @@ -131,6 +131,8 @@ namespace client void I2PControlService::Run () { + i2p::util::SetThreadName("I2PC"); + while (m_IsRunning) { try { diff --git a/daemon/UPnP.cpp b/daemon/UPnP.cpp index 92a41011..6ea33c46 100644 --- a/daemon/UPnP.cpp +++ b/daemon/UPnP.cpp @@ -60,6 +60,8 @@ namespace transport void UPnP::Run () { + i2p::util::SetThreadName("UPnP"); + while (m_IsRunning) { try diff --git a/libi2pd/Crypto.cpp b/libi2pd/Crypto.cpp index 523828ce..3e6279ff 100644 --- a/libi2pd/Crypto.cpp +++ b/libi2pd/Crypto.cpp @@ -638,7 +638,7 @@ namespace crypto { uint64_t buf[256]; uint64_t hash[12]; // 96 bytes -#if defined(__x86_64__) || defined(__i386__) +#if (defined(__x86_64__) || defined(__i386__)) && defined(__AVX__) // not all X86 targets supports AVX (like old Pentium, see #1600) if(i2p::cpu::avx) { __asm__ @@ -1316,6 +1316,8 @@ namespace crypto #endif } +// Noise + void NoiseSymmetricState::MixHash (const uint8_t * buf, size_t len) { SHA256_CTX ctx; @@ -1331,6 +1333,59 @@ namespace crypto // new ck is m_CK[0:31], key is m_CK[32:63] } + static void InitNoiseState (NoiseSymmetricState& state, const uint8_t * ck, + const uint8_t * hh, const uint8_t * pub) + { + // pub is Bob's public static key, hh = SHA256(h) + memcpy (state.m_CK, ck, 32); + SHA256_CTX ctx; + SHA256_Init (&ctx); + SHA256_Update (&ctx, hh, 32); + SHA256_Update (&ctx, pub, 32); + SHA256_Final (state.m_H, &ctx); // h = MixHash(pub) = SHA256(hh || pub) + } + + void InitNoiseNState (NoiseSymmetricState& state, const uint8_t * pub) + { + static const char protocolName[] = "Noise_N_25519_ChaChaPoly_SHA256"; // 31 chars + static const uint8_t hh[32] = + { + 0x69, 0x4d, 0x52, 0x44, 0x5a, 0x27, 0xd9, 0xad, 0xfa, 0xd2, 0x9c, 0x76, 0x32, 0x39, 0x5d, 0xc1, + 0xe4, 0x35, 0x4c, 0x69, 0xb4, 0xf9, 0x2e, 0xac, 0x8a, 0x1e, 0xe4, 0x6a, 0x9e, 0xd2, 0x15, 0x54 + }; // hh = SHA256(protocol_name || 0) + InitNoiseState (state, (const uint8_t *)protocolName, hh, pub); // ck = protocol_name || 0 + } + + void InitNoiseXKState (NoiseSymmetricState& state, const uint8_t * pub) + { + static const uint8_t protocolNameHash[] = + { + 0x72, 0xe8, 0x42, 0xc5, 0x45, 0xe1, 0x80, 0x80, 0xd3, 0x9c, 0x44, 0x93, 0xbb, 0x91, 0xd7, 0xed, + 0xf2, 0x28, 0x98, 0x17, 0x71, 0x21, 0x8c, 0x1f, 0x62, 0x4e, 0x20, 0x6f, 0x28, 0xd3, 0x2f, 0x71 + }; // SHA256 ("Noise_XKaesobfse+hs2+hs3_25519_ChaChaPoly_SHA256") + static const uint8_t hh[32] = + { + 0x49, 0xff, 0x48, 0x3f, 0xc4, 0x04, 0xb9, 0xb2, 0x6b, 0x11, 0x94, 0x36, 0x72, 0xff, 0x05, 0xb5, + 0x61, 0x27, 0x03, 0x31, 0xba, 0x89, 0xb8, 0xfc, 0x33, 0x15, 0x93, 0x87, 0x57, 0xdd, 0x3d, 0x1e + }; // SHA256 (protocolNameHash) + InitNoiseState (state, protocolNameHash, hh, pub); + } + + void InitNoiseIKState (NoiseSymmetricState& state, const uint8_t * pub) + { + static const uint8_t protocolNameHash[32] = + { + 0x4c, 0xaf, 0x11, 0xef, 0x2c, 0x8e, 0x36, 0x56, 0x4c, 0x53, 0xe8, 0x88, 0x85, 0x06, 0x4d, 0xba, + 0xac, 0xbe, 0x00, 0x54, 0xad, 0x17, 0x8f, 0x80, 0x79, 0xa6, 0x46, 0x82, 0x7e, 0x6e, 0xe4, 0x0c + }; // SHA256("Noise_IKelg2+hs2_25519_ChaChaPoly_SHA256"), 40 bytes + static const uint8_t hh[32] = + { + 0x9c, 0xcf, 0x85, 0x2c, 0xc9, 0x3b, 0xb9, 0x50, 0x44, 0x41, 0xe9, 0x50, 0xe0, 0x1d, 0x52, 0x32, + 0x2e, 0x0d, 0x47, 0xad, 0xd1, 0xe9, 0xa5, 0x55, 0xf7, 0x55, 0xb5, 0x69, 0xae, 0x18, 0x3b, 0x5c + }; // SHA256 (protocolNameHash) + InitNoiseState (state, protocolNameHash, hh, pub); + } + // init and terminate /* std::vector > m_OpenSSLMutexes; diff --git a/libi2pd/Crypto.h b/libi2pd/Crypto.h index da7a4bf4..c63cc45e 100644 --- a/libi2pd/Crypto.h +++ b/libi2pd/Crypto.h @@ -318,6 +318,10 @@ namespace crypto void MixKey (const uint8_t * sharedSecret); }; + void InitNoiseNState (NoiseSymmetricState& state, const uint8_t * pub); // Noise_N (tunnels, router) + void InitNoiseXKState (NoiseSymmetricState& state, const uint8_t * pub); // Noise_XK (NTCP2) + void InitNoiseIKState (NoiseSymmetricState& state, const uint8_t * pub); // Noise_IK (ratchets) + // init and terminate void InitCrypto (bool precomputation, bool aesni, bool avx, bool force); void TerminateCrypto (); diff --git a/libi2pd/Datagram.cpp b/libi2pd/Datagram.cpp index 559b0a8b..9d61f5e0 100644 --- a/libi2pd/Datagram.cpp +++ b/libi2pd/Datagram.cpp @@ -295,11 +295,11 @@ namespace datagram } } - if (!m_RoutingSession || !m_RoutingSession->GetOwner ()) + if (!m_RoutingSession || !m_RoutingSession->GetOwner () || !m_RoutingSession->IsReadyToSend ()) { bool found = false; for (auto& it: m_PendingRoutingSessions) - if (it->GetOwner ()) // found established session + if (it->GetOwner () && m_RoutingSession->IsReadyToSend ()) // found established session { m_RoutingSession = it; m_PendingRoutingSessions.clear (); @@ -309,7 +309,7 @@ namespace datagram if (!found) { m_RoutingSession = m_LocalDestination->GetRoutingSession(m_RemoteLeaseSet, true); - if (!m_RoutingSession->GetOwner ()) + if (!m_RoutingSession->GetOwner () || !m_RoutingSession->IsReadyToSend ()) m_PendingRoutingSessions.push_back (m_RoutingSession); } } diff --git a/libi2pd/Destination.cpp b/libi2pd/Destination.cpp index 44180ae5..52ede959 100644 --- a/libi2pd/Destination.cpp +++ b/libi2pd/Destination.cpp @@ -379,7 +379,8 @@ namespace client LogPrint (eLogDebug, "Destination: Remote LeaseSet"); std::lock_guard lock(m_RemoteLeaseSetsMutex); auto it = m_RemoteLeaseSets.find (key); - if (it != m_RemoteLeaseSets.end ()) + if (it != m_RemoteLeaseSets.end () && + it->second->GetStoreType () == buf[DATABASE_STORE_TYPE_OFFSET]) // update only if same type { leaseSet = it->second; if (leaseSet->IsNewer (buf + offset, len - offset)) @@ -399,6 +400,7 @@ namespace client } else { + // add or replace if (buf[DATABASE_STORE_TYPE_OFFSET] == i2p::data::NETDB_STORE_TYPE_LEASESET) leaseSet = std::make_shared (buf + offset, len - offset); // LeaseSet else @@ -559,9 +561,7 @@ namespace client m_ExcludedFloodfills.insert (floodfill->GetIdentHash ()); LogPrint (eLogDebug, "Destination: Publish LeaseSet of ", GetIdentHash ().ToBase32 ()); RAND_bytes ((uint8_t *)&m_PublishReplyToken, 4); - auto msg = i2p::CreateDatabaseStoreMsg (leaseSet, m_PublishReplyToken, inbound); - if (floodfill->GetIdentity ()->GetCryptoKeyType () != i2p::data::CRYPTO_KEY_TYPE_ECIES_X25519_AEAD) // TODO: remove whan implemented - msg = WrapMessageForRouter (floodfill, msg); + auto msg = WrapMessageForRouter (floodfill, i2p::CreateDatabaseStoreMsg (leaseSet, m_PublishReplyToken, inbound)); m_PublishConfirmationTimer.expires_from_now (boost::posix_time::seconds(PUBLISH_CONFIRMATION_TIMEOUT)); m_PublishConfirmationTimer.async_wait (std::bind (&LeaseSetDestination::HandlePublishConfirmationTimer, shared_from_this (), std::placeholders::_1)); @@ -755,10 +755,8 @@ namespace client AddECIESx25519Key (replyKey, replyTag); else AddSessionKey (replyKey, replyTag); - - auto msg = CreateLeaseSetDatabaseLookupMsg (dest, request->excluded, request->replyTunnel, replyKey, replyTag, isECIES); - if (nextFloodfill->GetIdentity ()->GetCryptoKeyType () != i2p::data::CRYPTO_KEY_TYPE_ECIES_X25519_AEAD) // TODO: remove whan implemented - msg = WrapMessageForRouter (nextFloodfill, msg); + auto msg = WrapMessageForRouter (nextFloodfill, CreateLeaseSetDatabaseLookupMsg (dest, + request->excluded, request->replyTunnel, replyKey, replyTag, isECIES)); request->outboundTunnel->SendTunnelDataMsg ( { i2p::tunnel::TunnelMessageBlock diff --git a/libi2pd/ECIESX25519AEADRatchetSession.cpp b/libi2pd/ECIESX25519AEADRatchetSession.cpp index 00efee48..d2286b15 100644 --- a/libi2pd/ECIESX25519AEADRatchetSession.cpp +++ b/libi2pd/ECIESX25519AEADRatchetSession.cpp @@ -9,6 +9,7 @@ #include #include #include "Log.h" +#include "util.h" #include "Crypto.h" #include "Elligator.h" #include "Tag.h" @@ -45,13 +46,13 @@ namespace garlic uint64_t RatchetTagSet::GetNextSessionTag () { - i2p::crypto::HKDF (m_KeyData.GetSessTagCK (), m_SessTagConstant, 32, "SessionTagKeyGen", m_KeyData.buf); // [sessTag_ck, tag] = HKDF(sessTag_chainkey, SESSTAG_CONSTANT, "SessionTagKeyGen", 64) m_NextIndex++; if (m_NextIndex >= 65535) { LogPrint (eLogError, "Garlic: Tagset ", GetTagSetID (), " is empty"); return 0; } + i2p::crypto::HKDF (m_KeyData.GetSessTagCK (), m_SessTagConstant, 32, "SessionTagKeyGen", m_KeyData.buf); // [sessTag_ck, tag] = HKDF(sessTag_chainkey, SESSTAG_CONSTANT, "SessionTagKeyGen", 64) return m_KeyData.GetTag (); } @@ -153,29 +154,12 @@ namespace garlic GarlicRoutingSession (owner, attachLeaseSet) { RAND_bytes (m_PaddingSizes, 32); m_NextPaddingSize = 0; - ResetKeys (); } ECIESX25519AEADRatchetSession::~ECIESX25519AEADRatchetSession () { } - void ECIESX25519AEADRatchetSession::ResetKeys () - { - static const uint8_t protocolNameHash[32] = - { - 0x4c, 0xaf, 0x11, 0xef, 0x2c, 0x8e, 0x36, 0x56, 0x4c, 0x53, 0xe8, 0x88, 0x85, 0x06, 0x4d, 0xba, - 0xac, 0xbe, 0x00, 0x54, 0xad, 0x17, 0x8f, 0x80, 0x79, 0xa6, 0x46, 0x82, 0x7e, 0x6e, 0xe4, 0x0c - }; // SHA256("Noise_IKelg2+hs2_25519_ChaChaPoly_SHA256"), 40 bytes - static const uint8_t hh[32] = - { - 0x9c, 0xcf, 0x85, 0x2c, 0xc9, 0x3b, 0xb9, 0x50, 0x44, 0x41, 0xe9, 0x50, 0xe0, 0x1d, 0x52, 0x32, - 0x2e, 0x0d, 0x47, 0xad, 0xd1, 0xe9, 0xa5, 0x55, 0xf7, 0x55, 0xb5, 0x69, 0xae, 0x18, 0x3b, 0x5c - }; // SHA256 (protocolNameHash) - memcpy (m_CK, protocolNameHash, 32); - memcpy (m_H, hh, 32); - } - void ECIESX25519AEADRatchetSession::CreateNonce (uint64_t seqn, uint8_t * nonce) { memset (nonce, 0, 4); @@ -236,8 +220,8 @@ namespace garlic if (!GetOwner ()) return false; // we are Bob // KDF1 - MixHash (GetOwner ()->GetEncryptionPublicKey (i2p::data::CRYPTO_KEY_TYPE_ECIES_X25519_AEAD), 32); // h = SHA256(h || bpk) - + i2p::crypto::InitNoiseIKState (*this, GetOwner ()->GetEncryptionPublicKey (i2p::data::CRYPTO_KEY_TYPE_ECIES_X25519_AEAD)); // bpk + if (!i2p::crypto::GetElligator ()->Decode (buf, m_Aepk)) { LogPrint (eLogError, "Garlic: Can't decode elligator"); @@ -315,7 +299,10 @@ namespace garlic break; case eECIESx25519BlkNextKey: LogPrint (eLogDebug, "Garlic: next key"); - HandleNextKey (buf + offset, size, receiveTagset); + if (receiveTagset) + HandleNextKey (buf + offset, size, receiveTagset); + else + LogPrint (eLogError, "Garlic: Unexpected next key block"); break; case eECIESx25519BlkAck: { @@ -448,7 +435,6 @@ namespace garlic bool ECIESX25519AEADRatchetSession::NewOutgoingSessionMessage (const uint8_t * payload, size_t len, uint8_t * out, size_t outLen, bool isStatic) { - ResetKeys (); // we are Alice, bpk is m_RemoteStaticKey size_t offset = 0; if (!GenerateEphemeralKeysAndEncode (out + offset)) @@ -459,7 +445,7 @@ namespace garlic offset += 32; // KDF1 - MixHash (m_RemoteStaticKey, 32); // h = SHA256(h || bpk) + i2p::crypto::InitNoiseIKState (*this, m_RemoteStaticKey); // bpk MixHash (m_EphemeralKeys->GetPublicKey (), 32); // h = SHA256(h || aepk) uint8_t sharedSecret[32]; m_EphemeralKeys->Agree (m_RemoteStaticKey, sharedSecret); // x25519(aesk, bpk) @@ -508,6 +494,31 @@ namespace garlic return true; } + bool ECIESX25519AEADRatchetSession::NewOutgoingMessageForRouter (const uint8_t * payload, size_t len, uint8_t * out, size_t outLen) + { + // we are Alice, router's bpk is m_RemoteStaticKey + i2p::crypto::InitNoiseNState (*this, m_RemoteStaticKey); + size_t offset = 0; + m_EphemeralKeys = i2p::transport::transports.GetNextX25519KeysPair (); + memcpy (out + offset, m_EphemeralKeys->GetPublicKey (), 32); + MixHash (out + offset, 32); // h = SHA256(h || aepk) + offset += 32; + uint8_t sharedSecret[32]; + m_EphemeralKeys->Agree (m_RemoteStaticKey, sharedSecret); // x25519(aesk, bpk) + MixKey (sharedSecret); + uint8_t nonce[12]; + CreateNonce (0, nonce); + // encrypt payload + if (!i2p::crypto::AEADChaCha20Poly1305 (payload, len, m_H, 32, m_CK + 32, nonce, out + offset, len + 16, true)) // encrypt + { + LogPrint (eLogWarning, "Garlic: Payload for router AEAD encryption failed"); + return false; + } + + m_State = eSessionStateNewSessionSent; + return true; + } + bool ECIESX25519AEADRatchetSession::NewSessionReplyMessage (const uint8_t * payload, size_t len, uint8_t * out, size_t outLen) { // we are Bob @@ -567,7 +578,7 @@ namespace garlic return true; } - + bool ECIESX25519AEADRatchetSession::NextNewSessionReplyMessage (const uint8_t * payload, size_t len, uint8_t * out, size_t outLen) { // we are Bob and sent NSR already @@ -609,18 +620,15 @@ namespace garlic } buf += 32; len -= 32; // KDF for Reply Key Section - uint8_t h[32]; memcpy (h, m_H, 32); // save m_H + i2p::util::SaveStateHelper s(*this); // restore noise state on exit MixHash (tag, 8); // h = SHA256(h || tag) MixHash (bepk, 32); // h = SHA256(h || bepk) uint8_t sharedSecret[32]; - if (m_State == eSessionStateNewSessionSent) - { - // only fist time, we assume ephemeral keys the same - m_EphemeralKeys->Agree (bepk, sharedSecret); // sharedSecret = x25519(aesk, bepk) - MixKey (sharedSecret); - GetOwner ()->Decrypt (bepk, sharedSecret, nullptr, i2p::data::CRYPTO_KEY_TYPE_ECIES_X25519_AEAD); // x25519 (ask, bepk) - MixKey (sharedSecret); - } + m_EphemeralKeys->Agree (bepk, sharedSecret); // sharedSecret = x25519(aesk, bepk) + MixKey (sharedSecret); + GetOwner ()->Decrypt (bepk, sharedSecret, nullptr, i2p::data::CRYPTO_KEY_TYPE_ECIES_X25519_AEAD); // x25519 (ask, bepk) + MixKey (sharedSecret); + uint8_t nonce[12]; CreateNonce (0, nonce); // calculate hash for zero length @@ -636,6 +644,7 @@ namespace garlic i2p::crypto::HKDF (m_CK, nullptr, 0, "", keydata); // keydata = HKDF(chainKey, ZEROLEN, "", 64) if (m_State == eSessionStateNewSessionSent) { + // only first time, then we keep using existing tagsets // k_ab = keydata[0:31], k_ba = keydata[32:63] m_SendTagset = std::make_shared(shared_from_this ()); m_SendTagset->DHInitialize (m_CK, keydata); // tagset_ab = DH_INITIALIZE(chainKey, k_ab) @@ -657,11 +666,10 @@ namespace garlic if (m_State == eSessionStateNewSessionSent) { m_State = eSessionStateEstablished; - m_EphemeralKeys = nullptr; + //m_EphemeralKeys = nullptr; // TODO: delete after a while m_SessionCreatedTimestamp = i2p::util::GetSecondsSinceEpoch (); GetOwner ()->AddECIESx25519Session (m_RemoteStaticKey, shared_from_this ()); } - memcpy (m_H, h, 32); // restore m_H HandlePayload (buf, len - 16, nullptr, 0); // we have received reply to NS with LeaseSet in it @@ -677,6 +685,13 @@ namespace garlic auto index = m_SendTagset->GetNextIndex (); CreateNonce (index, nonce); // tag's index uint64_t tag = m_SendTagset->GetNextSessionTag (); + if (!tag) + { + LogPrint (eLogError, "Garlic: can't create new ECIES-X25519-AEAD-Ratchet tag for send tagset"); + if (GetOwner ()) + GetOwner ()->RemoveECIESx25519Session (m_RemoteStaticKey); + return false; + } memcpy (out, &tag, 8); // ad = The session tag, 8 bytes // ciphertext = ENCRYPT(k, n, payload, ad) @@ -714,20 +729,19 @@ namespace garlic { if (receiveTagset->GetNextIndex () - index < GetOwner ()->GetNumRatchetInboundTags ()/2) moreTags = GetOwner ()->GetNumRatchetInboundTags (); + index -= GetOwner ()->GetNumRatchetInboundTags (); // trim behind } else { moreTags = ECIESX25519_MIN_NUM_GENERATED_TAGS + (index >> 2); // N/4 if (moreTags > ECIESX25519_MAX_NUM_GENERATED_TAGS) moreTags = ECIESX25519_MAX_NUM_GENERATED_TAGS; moreTags -= (receiveTagset->GetNextIndex () - index); + index -= ECIESX25519_MAX_NUM_GENERATED_TAGS; // trim behind } if (moreTags > 0) - { GenerateMoreReceiveTags (receiveTagset, moreTags); - index -= (moreTags >> 1); // /2 - if (index > 0) - receiveTagset->SetTrimBehind (index); - } + if (index > 0) + receiveTagset->SetTrimBehind (index); } return true; } @@ -746,12 +760,16 @@ namespace garlic [[fallthrough]]; #endif case eSessionStateEstablished: - if (HandleExistingSessionMessage (buf, len, receiveTagset, index)) return true; - // check NSR just in case - LogPrint (eLogDebug, "Garlic: check for out of order NSR with index ", index); - if (receiveTagset->GetNextIndex () - index < ECIESX25519_NSR_NUM_GENERATED_TAGS/2) - GenerateMoreReceiveTags (receiveTagset, ECIESX25519_NSR_NUM_GENERATED_TAGS); - return HandleNewOutgoingSessionReply (buf, len); + if (receiveTagset->IsNS ()) + { + // our of sequence NSR + LogPrint (eLogDebug, "Garlic: check for out of order NSR with index ", index); + if (receiveTagset->GetNextIndex () - index < ECIESX25519_NSR_NUM_GENERATED_TAGS/2) + GenerateMoreReceiveTags (receiveTagset, ECIESX25519_NSR_NUM_GENERATED_TAGS); + return HandleNewOutgoingSessionReply (buf, len); + } + else + return HandleExistingSessionMessage (buf, len, receiveTagset, index); case eSessionStateNew: return HandleNewIncomingSession (buf, len); case eSessionStateNewSessionSent: @@ -763,6 +781,28 @@ namespace garlic return true; } + bool ECIESX25519AEADRatchetSession::HandleNextMessageForRouter (const uint8_t * buf, size_t len) + { + if (!GetOwner ()) return false; + // we are Bob + i2p::crypto::InitNoiseNState (*this, GetOwner ()->GetEncryptionPublicKey (i2p::data::CRYPTO_KEY_TYPE_ECIES_X25519_AEAD)); // bpk + MixHash (buf, 32); + uint8_t sharedSecret[32]; + GetOwner ()->Decrypt (buf, sharedSecret, nullptr, i2p::data::CRYPTO_KEY_TYPE_ECIES_X25519_AEAD); // x25519(bsk, aepk) + MixKey (sharedSecret); + buf += 32; len -= 32; + uint8_t nonce[12]; + CreateNonce (0, nonce); + std::vector payload (len - 16); + if (!i2p::crypto::AEADChaCha20Poly1305 (buf, len - 16, m_H, 32, m_CK + 32, nonce, payload.data (), len - 16, false)) // decrypt + { + LogPrint (eLogWarning, "Garlic: Payload for router AEAD verification failed"); + return false; + } + HandlePayload (payload.data (), len - 16, nullptr, 0); + return true; + } + std::shared_ptr ECIESX25519AEADRatchetSession::WrapSingleMessage (std::shared_ptr msg) { auto payload = CreatePayload (msg, m_State != eSessionStateEstablished); @@ -799,6 +839,11 @@ namespace garlic return nullptr; len += 96; break; + case eSessionStateForRouter: + if (!NewOutgoingMessageForRouter (payload.data (), payload.size (), buf, m->maxLen)) + return nullptr; + len += 48; + break; default: return nullptr; } @@ -809,9 +854,9 @@ namespace garlic return m; } - std::shared_ptr ECIESX25519AEADRatchetSession::WrapOneTimeMessage (std::shared_ptr msg) + std::shared_ptr ECIESX25519AEADRatchetSession::WrapOneTimeMessage (std::shared_ptr msg, bool isForRouter) { - m_State = eSessionStateOneTime; + m_State = isForRouter ? eSessionStateForRouter : eSessionStateOneTime; return WrapSingleMessage (msg); } @@ -1014,7 +1059,14 @@ namespace garlic if (GetOwner ()) { for (int i = 0; i < numTags; i++) - GetOwner ()->AddECIESx25519SessionNextTag (receiveTagset); + { + auto tag = GetOwner ()->AddECIESx25519SessionNextTag (receiveTagset); + if (!tag) + { + LogPrint (eLogError, "Garlic: can't create new ECIES-X25519-AEAD-Ratchet tag for receive tagset"); + break; + } + } } } diff --git a/libi2pd/ECIESX25519AEADRatchetSession.h b/libi2pd/ECIESX25519AEADRatchetSession.h index 755531a3..86e3ffc0 100644 --- a/libi2pd/ECIESX25519AEADRatchetSession.h +++ b/libi2pd/ECIESX25519AEADRatchetSession.h @@ -33,7 +33,7 @@ namespace garlic const int ECIESX25519_PREVIOUS_TAGSET_EXPIRATION_TIMEOUT = 180; // 180 const int ECIESX25519_TAGSET_MAX_NUM_TAGS = 8192; // number of tags we request new tagset after const int ECIESX25519_MIN_NUM_GENERATED_TAGS = 24; - const int ECIESX25519_MAX_NUM_GENERATED_TAGS = 160; + const int ECIESX25519_MAX_NUM_GENERATED_TAGS = 320; const int ECIESX25519_NSR_NUM_GENERATED_TAGS = 12; const size_t ECIESX25519_OPTIMAL_PAYLOAD_SIZE = 1912; // 1912 = 1956 /* to fit 2 tunnel messages */ @@ -45,7 +45,8 @@ namespace garlic public: RatchetTagSet (std::shared_ptr session): m_Session (session) {}; - + virtual bool IsNS () const { return false; }; + void DHInitialize (const uint8_t * rootKey, const uint8_t * k); void NextSessionTagRatchet (); uint64_t GetNextSessionTag (); @@ -91,6 +92,8 @@ namespace garlic NSRatchetTagSet (std::shared_ptr session): RatchetTagSet (session), m_DummySession (session) {}; + + bool IsNS () const { return true; }; private: @@ -140,7 +143,8 @@ namespace garlic eSessionStateNewSessionSent, eSessionStateNewSessionReplySent, eSessionStateEstablished, - eSessionStateOneTime + eSessionStateOneTime, + eSessionStateForRouter }; struct DHRatchet @@ -157,8 +161,9 @@ namespace garlic ~ECIESX25519AEADRatchetSession (); bool HandleNextMessage (uint8_t * buf, size_t len, std::shared_ptr receiveTagset, int index = 0); + bool HandleNextMessageForRouter (const uint8_t * buf, size_t len); std::shared_ptr WrapSingleMessage (std::shared_ptr msg); - std::shared_ptr WrapOneTimeMessage (std::shared_ptr msg); + std::shared_ptr WrapOneTimeMessage (std::shared_ptr msg, bool isForRouter = false); const uint8_t * GetRemoteStaticKey () const { return m_RemoteStaticKey; } void SetRemoteStaticKey (const uint8_t * key) { memcpy (m_RemoteStaticKey, key, 32); } @@ -178,7 +183,6 @@ namespace garlic private: - void ResetKeys (); void CreateNonce (uint64_t seqn, uint8_t * nonce); bool GenerateEphemeralKeysAndEncode (uint8_t * buf); // buf is 32 bytes std::shared_ptr CreateNewSessionTagset (); @@ -193,7 +197,8 @@ namespace garlic bool NewSessionReplyMessage (const uint8_t * payload, size_t len, uint8_t * out, size_t outLen); bool NextNewSessionReplyMessage (const uint8_t * payload, size_t len, uint8_t * out, size_t outLen); bool NewExistingSessionMessage (const uint8_t * payload, size_t len, uint8_t * out, size_t outLen); - + bool NewOutgoingMessageForRouter (const uint8_t * payload, size_t len, uint8_t * out, size_t outLen); + std::vector CreatePayload (std::shared_ptr msg, bool first); size_t CreateGarlicClove (std::shared_ptr msg, uint8_t * buf, size_t len); size_t CreateLeaseSetClove (std::shared_ptr ls, uint64_t ts, uint8_t * buf, size_t len); diff --git a/libi2pd/Garlic.cpp b/libi2pd/Garlic.cpp index 2fe97156..aff92837 100644 --- a/libi2pd/Garlic.cpp +++ b/libi2pd/Garlic.cpp @@ -485,42 +485,45 @@ namespace garlic } auto mod = length & 0x0f; // %16 buf += 4; // length - auto it = !mod ? m_Tags.find (SessionTag(buf)) : m_Tags.end (); // AES block is multiple of 16 - // AES tag might be used even if encryption type is not ElGamal/AES - if (it != m_Tags.end ()) - { - // tag found. Use AES - auto decryption = it->second; - m_Tags.erase (it); // tag might be used only once - if (length >= 32) - { - uint8_t iv[32]; // IV is first 16 bytes - SHA256(buf, 32, iv); - decryption->SetIV (iv); - decryption->Decrypt (buf + 32, length - 32, buf + 32); - HandleAESBlock (buf + 32, length - 32, decryption, msg->from); - } - else - LogPrint (eLogWarning, "Garlic: message length ", length, " is less than 32 bytes"); - } - else - { - bool found = false; - if (SupportsEncryptionType (i2p::data::CRYPTO_KEY_TYPE_ECIES_X25519_AEAD)) - { - // try ECIESx25519 tag - uint64_t tag; - memcpy (&tag, buf, 8); - auto it1 = m_ECIESx25519Tags.find (tag); - if (it1 != m_ECIESx25519Tags.end ()) - { - found = true; - if (!it1->second.tagset->HandleNextMessage (buf, length, it1->second.index)) - LogPrint (eLogError, "Garlic: can't handle ECIES-X25519-AEAD-Ratchet message"); - m_ECIESx25519Tags.erase (it1); - } - } + bool found = false; + uint64_t tag; + if (SupportsEncryptionType (i2p::data::CRYPTO_KEY_TYPE_ECIES_X25519_AEAD)) + { + // try ECIESx25519 tag + memcpy (&tag, buf, 8); + auto it1 = m_ECIESx25519Tags.find (tag); + if (it1 != m_ECIESx25519Tags.end ()) + { + found = true; + if (it1->second.tagset->HandleNextMessage (buf, length, it1->second.index)) + m_LastTagset = it1->second.tagset; + else + LogPrint (eLogError, "Garlic: can't handle ECIES-X25519-AEAD-Ratchet message"); + m_ECIESx25519Tags.erase (it1); + } + } + if (!found) + { + auto it = !mod ? m_Tags.find (SessionTag(buf)) : m_Tags.end (); // AES block is multiple of 16 + // AES tag might be used even if encryption type is not ElGamal/AES + if (it != m_Tags.end ()) // try AES tag + { + // tag found. Use AES + auto decryption = it->second; + m_Tags.erase (it); // tag might be used only once + if (length >= 32) + { + uint8_t iv[32]; // IV is first 16 bytes + SHA256(buf, 32, iv); + decryption->SetIV (iv); + decryption->Decrypt (buf + 32, length - 32, buf + 32); + HandleAESBlock (buf + 32, length - 32, decryption, msg->from); + found = true; + } + else + LogPrint (eLogWarning, "Garlic: message length ", length, " is less than 32 bytes"); + } if (!found) // assume new session { // AES tag not found. Handle depending on encryption type @@ -541,11 +544,36 @@ namespace garlic // otherwise ECIESx25519 auto session = std::make_shared (this, false); // incoming if (!session->HandleNextMessage (buf, length, nullptr, 0)) - LogPrint (eLogError, "Garlic: can't handle ECIES-X25519-AEAD-Ratchet message"); + { + // try to gererate more tags for last tagset + if (m_LastTagset && m_LastTagset->GetNextIndex () < 2*ECIESX25519_TAGSET_MAX_NUM_TAGS) + { + auto maxTags = std::max (m_NumRatchetInboundTags, ECIESX25519_MAX_NUM_GENERATED_TAGS); + for (int i = 0; i < maxTags; i++) + { + auto nextTag = AddECIESx25519SessionNextTag (m_LastTagset); + if (!nextTag) + { + LogPrint (eLogError, "Garlic: can't create new ECIES-X25519-AEAD-Ratchet tag for last tagset"); + break; + } + if (nextTag == tag) + { + LogPrint (eLogDebug, "Garlic: Missing ECIES-X25519-AEAD-Ratchet tag was generated"); + if (m_LastTagset->HandleNextMessage (buf, length, m_ECIESx25519Tags[tag].index)) + found = true; + break; + } + } + if (!found) m_LastTagset = nullptr; + } + if (!found) + LogPrint (eLogError, "Garlic: can't handle ECIES-X25519-AEAD-Ratchet message"); + } } else LogPrint (eLogError, "Garlic: Failed to decrypt message"); - } + } } } @@ -716,7 +744,7 @@ namespace garlic { auto session = std::make_shared(this, false); session->SetRemoteStaticKey (router->GetIdentity ()->GetEncryptionPublicKey ()); - return session->WrapOneTimeMessage (msg); + return session->WrapOneTimeMessage (msg, true); } else { @@ -844,6 +872,8 @@ namespace garlic } if (numExpiredTags > 0) LogPrint (eLogDebug, "Garlic: ", numExpiredTags, " ECIESx25519 tags expired for ", GetIdentHash().ToBase64 ()); + if (m_LastTagset && m_LastTagset->IsExpired (ts)) + m_LastTagset = nullptr; } void GarlicDestination::RemoveDeliveryStatusSession (uint32_t msgID) @@ -1029,11 +1059,13 @@ namespace garlic } } - void GarlicDestination::AddECIESx25519SessionNextTag (RatchetTagSetPtr tagset) + uint64_t GarlicDestination::AddECIESx25519SessionNextTag (RatchetTagSetPtr tagset) { auto index = tagset->GetNextIndex (); uint64_t tag = tagset->GetNextSessionTag (); - m_ECIESx25519Tags.emplace (tag, ECIESX25519AEADRatchetIndexTagset{index, tagset}); + if (tag) + m_ECIESx25519Tags.emplace (tag, ECIESX25519AEADRatchetIndexTagset{index, tagset}); + return tag; } void GarlicDestination::AddECIESx25519Session (const uint8_t * staticKey, ECIESX25519AEADRatchetSessionPtr session) diff --git a/libi2pd/Garlic.h b/libi2pd/Garlic.h index 8df830c7..379477e8 100644 --- a/libi2pd/Garlic.h +++ b/libi2pd/Garlic.h @@ -245,7 +245,7 @@ namespace garlic void AddECIESx25519Key (const uint8_t * key, const uint8_t * tag); // one tag virtual bool SubmitSessionKey (const uint8_t * key, const uint8_t * tag); // from different thread void DeliveryStatusSent (GarlicRoutingSessionPtr session, uint32_t msgID); - void AddECIESx25519SessionNextTag (RatchetTagSetPtr tagset); + uint64_t AddECIESx25519SessionNextTag (RatchetTagSetPtr tagset); void AddECIESx25519Session (const uint8_t * staticKey, ECIESX25519AEADRatchetSessionPtr session); void RemoveECIESx25519Session (const uint8_t * staticKey); void HandleECIESx25519GarlicClove (const uint8_t * buf, size_t len); @@ -285,6 +285,7 @@ namespace garlic int m_NumRatchetInboundTags; std::unordered_map, std::hash > > m_Tags; std::unordered_map m_ECIESx25519Tags; // session tag -> session + RatchetTagSetPtr m_LastTagset; // tagset last message came for // DeliveryStatus std::mutex m_DeliveryStatusSessionsMutex; std::unordered_map m_DeliveryStatusSessions; // msgID -> session diff --git a/libi2pd/Identity.cpp b/libi2pd/Identity.cpp index 89f6cda0..9dfaa1fc 100644 --- a/libi2pd/Identity.cpp +++ b/libi2pd/Identity.cpp @@ -828,7 +828,7 @@ namespace data XORMetric operator^(const IdentHash& key1, const IdentHash& key2) { XORMetric m; -#if defined(__x86_64__) || defined(__i386__) +#if (defined(__x86_64__) || defined(__i386__)) && defined(__AVX__) // not all X86 targets supports AVX (like old Pentium, see #1600) if(i2p::cpu::avx) { __asm__ diff --git a/libi2pd/Log.cpp b/libi2pd/Log.cpp index a0014841..2b555663 100644 --- a/libi2pd/Log.cpp +++ b/libi2pd/Log.cpp @@ -7,6 +7,7 @@ */ #include "Log.h" +#include "util.h" //for std::transform #include @@ -179,6 +180,8 @@ namespace log { void Log::Run () { + i2p::util::SetThreadName("Logging"); + Reopen (); while (m_IsRunning) { diff --git a/libi2pd/NTCP2.cpp b/libi2pd/NTCP2.cpp index bb54437e..1706399d 100644 --- a/libi2pd/NTCP2.cpp +++ b/libi2pd/NTCP2.cpp @@ -41,23 +41,7 @@ namespace transport void NTCP2Establisher::KeyDerivationFunction1 (const uint8_t * pub, i2p::crypto::X25519Keys& priv, const uint8_t * rs, const uint8_t * epub) { - static const uint8_t protocolNameHash[] = - { - 0x72, 0xe8, 0x42, 0xc5, 0x45, 0xe1, 0x80, 0x80, 0xd3, 0x9c, 0x44, 0x93, 0xbb, 0x91, 0xd7, 0xed, - 0xf2, 0x28, 0x98, 0x17, 0x71, 0x21, 0x8c, 0x1f, 0x62, 0x4e, 0x20, 0x6f, 0x28, 0xd3, 0x2f, 0x71 - }; // SHA256 ("Noise_XKaesobfse+hs2+hs3_25519_ChaChaPoly_SHA256") - static const uint8_t hh[32] = - { - 0x49, 0xff, 0x48, 0x3f, 0xc4, 0x04, 0xb9, 0xb2, 0x6b, 0x11, 0x94, 0x36, 0x72, 0xff, 0x05, 0xb5, - 0x61, 0x27, 0x03, 0x31, 0xba, 0x89, 0xb8, 0xfc, 0x33, 0x15, 0x93, 0x87, 0x57, 0xdd, 0x3d, 0x1e - }; // SHA256 (protocolNameHash) - memcpy (m_CK, protocolNameHash, 32); - // h = SHA256(hh || rs) - SHA256_CTX ctx; - SHA256_Init (&ctx); - SHA256_Update (&ctx, hh, 32); - SHA256_Update (&ctx, rs, 32); - SHA256_Final (m_H, &ctx); + i2p::crypto::InitNoiseXKState (*this, rs); // h = SHA256(h || epub) MixHash (epub, 32); // x25519 between pub and priv diff --git a/libi2pd/NTCP2.h b/libi2pd/NTCP2.h index 351f17b5..5c9ecac9 100644 --- a/libi2pd/NTCP2.h +++ b/libi2pd/NTCP2.h @@ -11,7 +11,6 @@ #include #include -#include #include #include #include diff --git a/libi2pd/NetDb.cpp b/libi2pd/NetDb.cpp index e280e31b..20b54376 100644 --- a/libi2pd/NetDb.cpp +++ b/libi2pd/NetDb.cpp @@ -26,6 +26,7 @@ #include "ECIESX25519AEADRatchetSession.h" #include "Config.h" #include "NetDb.hpp" +#include "util.h" using namespace i2p::transport; @@ -88,6 +89,8 @@ namespace data void NetDb::Run () { + i2p::util::SetThreadName("NetDB"); + uint32_t lastSave = 0, lastPublish = 0, lastExploratory = 0, lastManageRequest = 0, lastDestinationCleanup = 0; while (m_IsRunning) { @@ -113,7 +116,7 @@ namespace data break; case eI2NPDeliveryStatus: HandleDeliveryStatusMsg (msg); - break; + break; case eI2NPDummyMsg: // plain RouterInfo from NTCP2 with flags for now HandleNTCP2RouterInfoMsg (msg); @@ -155,12 +158,12 @@ namespace data if (!m_HiddenMode && i2p::transport::transports.IsOnline ()) { bool publish = false; - if (m_PublishReplyToken) - { + if (m_PublishReplyToken) + { if (ts - lastPublish >= NETDB_PUBLISH_CONFIRMATION_TIMEOUT) publish = true; } else if (i2p::context.GetLastUpdateTime () > lastPublish || - ts - lastPublish >= NETDB_PUBLISH_INTERVAL) publish = true; + ts - lastPublish >= NETDB_PUBLISH_INTERVAL) publish = true; if (publish) // update timestamp and publish { i2p::context.UpdateTimestamp (ts); diff --git a/libi2pd/RouterContext.cpp b/libi2pd/RouterContext.cpp index cdab624c..d258b341 100644 --- a/libi2pd/RouterContext.cpp +++ b/libi2pd/RouterContext.cpp @@ -19,7 +19,7 @@ #include "version.h" #include "Log.h" #include "Family.h" -#include "TunnelConfig.h" +#include "ECIESX25519AEADRatchetSession.h" #include "RouterContext.h" namespace i2p @@ -45,15 +45,15 @@ namespace i2p if (IsECIES ()) { auto initState = new i2p::crypto::NoiseSymmetricState (); - i2p::tunnel::InitBuildRequestRecordNoiseState (*initState); - initState->MixHash (GetIdentity ()->GetEncryptionPublicKey (), 32); // h = SHA256(h || hepk) + i2p::crypto::InitNoiseNState (*initState, GetIdentity ()->GetEncryptionPublicKey ()); m_InitialNoiseState.reset (initState); } } void RouterContext::CreateNewRouter () { - m_Keys = i2p::data::PrivateKeys::CreateRandomKeys (i2p::data::SIGNING_KEY_TYPE_EDDSA_SHA512_ED25519); + m_Keys = i2p::data::PrivateKeys::CreateRandomKeys (i2p::data::SIGNING_KEY_TYPE_EDDSA_SHA512_ED25519, + i2p::data::CRYPTO_KEY_TYPE_ECIES_X25519_AEAD); SaveKeys (); NewRouterInfo (); } @@ -591,7 +591,8 @@ namespace i2p // update keys LogPrint (eLogInfo, "Router: router keys are obsolete. Creating new"); oldIdentity = m_Keys.GetPublic (); - m_Keys = i2p::data::PrivateKeys::CreateRandomKeys (i2p::data::SIGNING_KEY_TYPE_EDDSA_SHA512_ED25519); + m_Keys = i2p::data::PrivateKeys::CreateRandomKeys (i2p::data::SIGNING_KEY_TYPE_EDDSA_SHA512_ED25519, + i2p::data::CRYPTO_KEY_TYPE_ECIES_X25519_AEAD); SaveKeys (); } // read NTCP2 keys if available @@ -674,7 +675,21 @@ namespace i2p void RouterContext::ProcessGarlicMessage (std::shared_ptr msg) { std::unique_lock l(m_GarlicMutex); - i2p::garlic::GarlicDestination::ProcessGarlicMessage (msg); + if (IsECIES ()) + { + uint8_t * buf = msg->GetPayload (); + uint32_t len = bufbe32toh (buf); + if (len > msg->GetLength ()) + { + LogPrint (eLogWarning, "Router: garlic message length ", len, " exceeds I2NP message length ", msg->GetLength ()); + return; + } + buf += 4; + auto session = std::make_shared(this, false); + session->HandleNextMessageForRouter (buf, len); + } + else + i2p::garlic::GarlicDestination::ProcessGarlicMessage (msg); } void RouterContext::ProcessDeliveryStatusMessage (std::shared_ptr msg) diff --git a/libi2pd/SSU.cpp b/libi2pd/SSU.cpp index d29f5cd7..3b6280dd 100644 --- a/libi2pd/SSU.cpp +++ b/libi2pd/SSU.cpp @@ -12,6 +12,7 @@ #include "RouterContext.h" #include "NetDb.hpp" #include "SSU.h" +#include "util.h" #ifdef _WIN32 #include @@ -23,7 +24,7 @@ namespace transport { SSUServer::SSUServer (const boost::asio::ip::address & addr, int port): - m_OnlyV6(true), m_IsRunning(false), m_Thread (nullptr), + m_OnlyV6(true), m_IsRunning(false), m_Thread (nullptr), m_ReceiversThread (nullptr), m_ReceiversThreadV6 (nullptr), m_Work (m_Service), m_ReceiversWork (m_ReceiversService), m_ReceiversWorkV6 (m_ReceiversServiceV6), m_EndpointV6 (addr, port), m_Socket (m_ReceiversService, m_Endpoint), @@ -36,7 +37,7 @@ namespace transport SSUServer::SSUServer (int port): m_OnlyV6(false), m_IsRunning(false), m_Thread (nullptr), - m_ReceiversThread (nullptr), m_ReceiversThreadV6 (nullptr), m_Work (m_Service), + m_ReceiversThread (nullptr), m_ReceiversThreadV6 (nullptr), m_Work (m_Service), m_ReceiversWork (m_ReceiversService), m_ReceiversWorkV6 (m_ReceiversServiceV6), m_Endpoint (boost::asio::ip::udp::v4 (), port), m_EndpointV6 (boost::asio::ip::udp::v6 (), port), m_Socket (m_ReceiversService), m_SocketV6 (m_ReceiversServiceV6), @@ -100,7 +101,7 @@ namespace transport if (context.SupportsV6 ()) { m_ReceiversThreadV6 = new std::thread (std::bind (&SSUServer::RunReceiversV6, this)); - if (!m_Thread) + if (!m_Thread) m_Thread = new std::thread (std::bind (&SSUServer::Run, this)); m_ReceiversServiceV6.post (std::bind (&SSUServer::ReceiveV6, this)); ScheduleTerminationV6 (); @@ -142,6 +143,8 @@ namespace transport void SSUServer::Run () { + i2p::util::SetThreadName("SSU"); + while (m_IsRunning) { try @@ -157,6 +160,8 @@ namespace transport void SSUServer::RunReceivers () { + i2p::util::SetThreadName("SSUv4"); + while (m_IsRunning) { try @@ -179,6 +184,8 @@ namespace transport void SSUServer::RunReceiversV6 () { + i2p::util::SetThreadName("SSUv6"); + while (m_IsRunning) { try diff --git a/libi2pd/SSUData.cpp b/libi2pd/SSUData.cpp index 4e0e712f..5458cc97 100644 --- a/libi2pd/SSUData.cpp +++ b/libi2pd/SSUData.cpp @@ -185,7 +185,12 @@ namespace transport std::unique_ptr(new IncompleteMessage (msg)))).first; } std::unique_ptr& incompleteMessage = it->second; - + // mark fragment as received + if (fragmentNum < 64) + incompleteMessage->receivedFragmentsBits |= (0x01 << fragmentNum); + else + LogPrint (eLogWarning, "SSU: Fragment number ", fragmentNum, " exceeds 64"); + // handle current fragment if (fragmentNum == incompleteMessage->nextFragmentNum) { @@ -220,7 +225,7 @@ namespace transport // missing fragment LogPrint (eLogWarning, "SSU: Missing fragments from ", (int)incompleteMessage->nextFragmentNum, " to ", fragmentNum - 1, " of message ", msgID); auto savedFragment = new Fragment (fragmentNum, buf, fragmentSize, isLast); - if (incompleteMessage->savedFragments.insert (std::unique_ptr(savedFragment)).second) + if (incompleteMessage->savedFragments.insert (std::unique_ptr(savedFragment)).second) incompleteMessage->lastFragmentInsertTime = i2p::util::GetSecondsSinceEpoch (); else LogPrint (eLogWarning, "SSU: Fragment ", (int)fragmentNum, " of message ", msgID, " already saved"); @@ -266,7 +271,7 @@ namespace transport } } else - SendFragmentAck (msgID, fragmentNum); + SendFragmentAck (msgID, incompleteMessage->receivedFragmentsBits); buf += fragmentSize; } } @@ -394,13 +399,9 @@ namespace transport m_Session.Send (buf, 48); } - void SSUData::SendFragmentAck (uint32_t msgID, int fragmentNum) + void SSUData::SendFragmentAck (uint32_t msgID, uint64_t bits) { - if (fragmentNum > 64) - { - LogPrint (eLogWarning, "SSU: Fragment number ", fragmentNum, " exceeds 64"); - return; - } + if (!bits) return; uint8_t buf[64 + 18] = {0}; uint8_t * payload = buf + sizeof (SSUHeader); *payload = DATA_FLAG_ACK_BITFIELDS_INCLUDED; // flag @@ -410,14 +411,16 @@ namespace transport // one ack *(uint32_t *)(payload) = htobe32 (msgID); // msgID payload += 4; - div_t d = div (fragmentNum, 7); - memset (payload, 0x80, d.quot); // 0x80 means non-last - payload += d.quot; - *payload = 0x01 << d.rem; // set corresponding bit - payload++; + size_t len = 0; + while (bits) + { + *payload = (bits & 0x7F); // next 7 bits + bits >>= 7; + if (bits) *payload &= 0x80; // 0x80 means non-last + payload++; len++; + } *payload = 0; // number of fragments - - size_t len = d.quot < 4 ? 48 : 64; // 48 = 37 + 7 + 4 (3+1) + len = (len <= 4) ? 48 : 64; // 48 = 37 + 7 + 4 // encrypt message with session key m_Session.FillHeaderAndEncrypt (PAYLOAD_TYPE_DATA, buf, len); m_Session.Send (buf, len); diff --git a/libi2pd/SSUData.h b/libi2pd/SSUData.h index 2e606053..902c009a 100644 --- a/libi2pd/SSUData.h +++ b/libi2pd/SSUData.h @@ -75,9 +75,11 @@ namespace transport std::shared_ptr msg; int nextFragmentNum; uint32_t lastFragmentInsertTime; // in seconds + uint64_t receivedFragmentsBits; std::set, FragmentCmp> savedFragments; - IncompleteMessage (std::shared_ptr m): msg (m), nextFragmentNum (0), lastFragmentInsertTime (0) {}; + IncompleteMessage (std::shared_ptr m): msg (m), nextFragmentNum (0), + lastFragmentInsertTime (0), receivedFragmentsBits (0) {}; void AttachNextFragment (const uint8_t * fragment, size_t fragmentSize); }; @@ -109,7 +111,7 @@ namespace transport private: void SendMsgAck (uint32_t msgID); - void SendFragmentAck (uint32_t msgID, int fragmentNum); + void SendFragmentAck (uint32_t msgID, uint64_t bits); void ProcessAcks (uint8_t *& buf, uint8_t flag); void ProcessFragments (uint8_t * buf); void ProcessSentMessageAck (uint32_t msgID); diff --git a/libi2pd/Streaming.cpp b/libi2pd/Streaming.cpp index 05b34d9e..f6bf7e8b 100644 --- a/libi2pd/Streaming.cpp +++ b/libi2pd/Streaming.cpp @@ -701,7 +701,7 @@ namespace stream size++; // resend delay htobe16buf (packet + size, PACKET_FLAG_CLOSE | PACKET_FLAG_SIGNATURE_INCLUDED); size += 2; // flags - size_t signatureLen = m_LocalDestination.GetOwner ()->GetIdentity ()->GetSignatureLen (); + size_t signatureLen = m_LocalDestination.GetOwner ()->GetPrivateKeys ().GetSignatureLen (); htobe16buf (packet + size, signatureLen); // signature only size += 2; // options size uint8_t * signature = packet + size; diff --git a/libi2pd/Timestamp.cpp b/libi2pd/Timestamp.cpp index 0490350e..3cd336ed 100644 --- a/libi2pd/Timestamp.cpp +++ b/libi2pd/Timestamp.cpp @@ -18,6 +18,7 @@ #include "Log.h" #include "I2PEndian.h" #include "Timestamp.h" +#include "util.h" #ifdef _WIN32 #ifndef _WIN64 @@ -148,6 +149,8 @@ namespace util void NTPTimeSync::Run () { + i2p::util::SetThreadName("Timesync"); + while (m_IsRunning) { try diff --git a/libi2pd/Transports.cpp b/libi2pd/Transports.cpp index c6e90ad2..5c2fcb6d 100644 --- a/libi2pd/Transports.cpp +++ b/libi2pd/Transports.cpp @@ -14,6 +14,7 @@ #include "Transports.h" #include "Config.h" #include "HTTP.h" +#include "util.h" using namespace i2p::data; @@ -59,6 +60,8 @@ namespace transport template void EphemeralKeysSupplier::Run () { + i2p::util::SetThreadName("Ephemerals"); + while (m_IsRunning) { int num, total = 0; @@ -272,6 +275,8 @@ namespace transport void Transports::Run () { + i2p::util::SetThreadName("Transports"); + while (m_IsRunning && m_Service) { try diff --git a/libi2pd/Tunnel.cpp b/libi2pd/Tunnel.cpp index ad918787..42eeeb5d 100644 --- a/libi2pd/Tunnel.cpp +++ b/libi2pd/Tunnel.cpp @@ -22,6 +22,7 @@ #include "Config.h" #include "Tunnel.h" #include "TunnelPool.h" +#include "util.h" namespace i2p { @@ -472,6 +473,7 @@ namespace tunnel void Tunnels::Run () { + i2p::util::SetThreadName("Tunnels"); std::this_thread::sleep_for (std::chrono::seconds(1)); // wait for other parts are ready uint64_t lastTs = 0, lastPoolsTs = 0; diff --git a/libi2pd/TunnelConfig.cpp b/libi2pd/TunnelConfig.cpp index a1b234a1..d43483c0 100644 --- a/libi2pd/TunnelConfig.cpp +++ b/libi2pd/TunnelConfig.cpp @@ -127,10 +127,9 @@ namespace tunnel void TunnelHopConfig::EncryptECIES (std::shared_ptr& encryptor, const uint8_t * plainText, uint8_t * encrypted, BN_CTX * ctx) { - InitBuildRequestRecordNoiseState (*this); uint8_t hepk[32]; encryptor->Encrypt (nullptr, hepk, nullptr, false); - MixHash (hepk, 32); // h = SHA256(h || hepk) + i2p::crypto::InitNoiseNState (*this, hepk); auto ephemeralKeys = i2p::transport::transports.GetNextX25519KeysPair (); memcpy (encrypted, ephemeralKeys->GetPublicKey (), 32); MixHash (encrypted, 32); // h = SHA256(h || sepk) @@ -148,17 +147,5 @@ namespace tunnel } MixHash (encrypted, ECIES_BUILD_REQUEST_RECORD_CLEAR_TEXT_SIZE + 16); // h = SHA256(h || ciphertext) } - - void InitBuildRequestRecordNoiseState (i2p::crypto::NoiseSymmetricState& state) - { - static const char protocolName[] = "Noise_N_25519_ChaChaPoly_SHA256"; // 31 chars - static const uint8_t hh[32] = - { - 0x69, 0x4d, 0x52, 0x44, 0x5a, 0x27, 0xd9, 0xad, 0xfa, 0xd2, 0x9c, 0x76, 0x32, 0x39, 0x5d, 0xc1, - 0xe4, 0x35, 0x4c, 0x69, 0xb4, 0xf9, 0x2e, 0xac, 0x8a, 0x1e, 0xe4, 0x6a, 0x9e, 0xd2, 0x15, 0x54 - }; // SHA256 (protocol_name || 0) - memcpy (state.m_CK, protocolName, 32); // ck = h = protocol_name || 0 - memcpy (state.m_H, hh, 32); // h = SHA256(h) - } } } \ No newline at end of file diff --git a/libi2pd/TunnelConfig.h b/libi2pd/TunnelConfig.h index 9aed0d25..45693970 100644 --- a/libi2pd/TunnelConfig.h +++ b/libi2pd/TunnelConfig.h @@ -44,8 +44,6 @@ namespace tunnel void EncryptECIES (std::shared_ptr& encryptor, const uint8_t * clearText, uint8_t * encrypted, BN_CTX * ctx); }; - - void InitBuildRequestRecordNoiseState (i2p::crypto::NoiseSymmetricState& state); class TunnelConfig { diff --git a/libi2pd/TunnelPool.cpp b/libi2pd/TunnelPool.cpp index 87413c3f..94462c93 100644 --- a/libi2pd/TunnelPool.cpp +++ b/libi2pd/TunnelPool.cpp @@ -235,7 +235,7 @@ namespace tunnel for (const auto& it : m_InboundTunnels) if (it->IsEstablished ()) num++; } - if (!num && !m_OutboundTunnels.empty ()) + if (!num && !m_OutboundTunnels.empty () && m_NumOutboundHops > 0) { for (auto it: m_OutboundTunnels) { @@ -559,7 +559,7 @@ namespace tunnel { config = std::make_shared(tunnel->GetPeers (), inboundTunnel->GetNextTunnelID (), inboundTunnel->GetNextIdentHash ()); } - if(m_NumOutboundHops == 0 || config) + if (!m_NumOutboundHops || config) { auto newTunnel = tunnels.CreateOutboundTunnel (config); newTunnel->SetTunnelPool (shared_from_this ()); @@ -574,8 +574,12 @@ namespace tunnel void TunnelPool::CreatePairedInboundTunnel (std::shared_ptr outboundTunnel) { LogPrint (eLogDebug, "Tunnels: Creating paired inbound tunnel..."); - auto tunnel = tunnels.CreateInboundTunnel (std::make_shared(outboundTunnel->GetInvertedPeers ()), outboundTunnel); + auto tunnel = tunnels.CreateInboundTunnel ( + m_NumOutboundHops > 0 ? std::make_shared(outboundTunnel->GetInvertedPeers ()) : nullptr, + outboundTunnel); tunnel->SetTunnelPool (shared_from_this ()); + if (tunnel->IsEstablished ()) // zero hops + TunnelCreated (tunnel); } void TunnelPool::SetCustomPeerSelector(ITunnelPeerSelector * selector) diff --git a/libi2pd/util.cpp b/libi2pd/util.cpp index 24814ad3..794a3f14 100644 --- a/libi2pd/util.cpp +++ b/libi2pd/util.cpp @@ -13,6 +13,15 @@ #include "util.h" #include "Log.h" +#if not defined (__FreeBSD__) +#include +#endif + +#if defined(__OpenBSD__) || defined(__FreeBSD__) +#include +#endif + + #ifdef _WIN32 #include #include @@ -32,7 +41,7 @@ // inet_pton exists Windows since Vista, but XP doesn't have that function! // This function was written by Petar Korponai?. See http://stackoverflow.com/questions/15660203/inet-pton-identifier-not-found -int inet_pton_xp(int af, const char *src, void *dst) +int inet_pton_xp (int af, const char *src, void *dst) { struct sockaddr_storage ss; int size = sizeof (ss); @@ -94,6 +103,8 @@ namespace util void RunnableService::Run () { + SetThreadName(m_Name.c_str()); + while (m_IsRunning) { try @@ -107,10 +118,20 @@ namespace util } } + void SetThreadName (const char *name) { +#if defined (__APPLE__) + pthread_setname_np(name); +#elif defined(__FreeBSD__) || defined(__OpenBSD__) + pthread_set_name_np(pthread_self(), name); +#else + pthread_setname_np(pthread_self(), name); +#endif + } + namespace net { #ifdef _WIN32 - bool IsWindowsXPorLater() + bool IsWindowsXPorLater () { static bool isRequested = false; static bool isXP = false; @@ -129,7 +150,7 @@ namespace net return isXP; } - int GetMTUWindowsIpv4(sockaddr_in inputAddress, int fallback) + int GetMTUWindowsIpv4 (sockaddr_in inputAddress, int fallback) { ULONG outBufLen = 0; PIP_ADAPTER_ADDRESSES pAddresses = nullptr; @@ -183,7 +204,7 @@ namespace net return fallback; } - int GetMTUWindowsIpv6(sockaddr_in6 inputAddress, int fallback) + int GetMTUWindowsIpv6 (sockaddr_in6 inputAddress, int fallback) { ULONG outBufLen = 0; PIP_ADAPTER_ADDRESSES pAddresses = nullptr; @@ -248,7 +269,7 @@ namespace net return fallback; } - int GetMTUWindows(const boost::asio::ip::address& localAddress, int fallback) + int GetMTUWindows (const boost::asio::ip::address& localAddress, int fallback) { #ifdef UNICODE string localAddress_temporary = localAddress.to_string(); @@ -280,7 +301,7 @@ namespace net } } #else // assume unix - int GetMTUUnix(const boost::asio::ip::address& localAddress, int fallback) + int GetMTUUnix (const boost::asio::ip::address& localAddress, int fallback) { ifaddrs* ifaddr, *ifa = nullptr; if(getifaddrs(&ifaddr) == -1) @@ -335,7 +356,7 @@ namespace net } #endif // _WIN32 - int GetMTU(const boost::asio::ip::address& localAddress) + int GetMTU (const boost::asio::ip::address& localAddress) { int fallback = localAddress.is_v6 () ? 1280 : 620; // fallback MTU @@ -347,7 +368,7 @@ namespace net return fallback; } - const boost::asio::ip::address GetInterfaceAddress(const std::string & ifname, bool ipv6) + const boost::asio::ip::address GetInterfaceAddress (const std::string & ifname, bool ipv6) { #ifdef _WIN32 LogPrint(eLogError, "NetIface: cannot get address by interface name, not implemented on WIN32"); @@ -395,7 +416,7 @@ namespace net #endif } - bool IsInReservedRange(const boost::asio::ip::address& host) { + bool IsInReservedRange (const boost::asio::ip::address& host) { // https://en.wikipedia.org/wiki/Reserved_IP_addresses if(host.is_v4()) { diff --git a/libi2pd/util.h b/libi2pd/util.h index 56ce1e08..e6de09ed 100644 --- a/libi2pd/util.h +++ b/libi2pd/util.h @@ -168,11 +168,27 @@ namespace util boost::asio::io_service::work m_Work; }; + void SetThreadName (const char *name); + + template + class SaveStateHelper + { + public: + + SaveStateHelper (T& orig): m_Original (orig), m_Copy (orig) {}; + ~SaveStateHelper () { m_Original = m_Copy; }; + + private: + + T& m_Original; + T m_Copy; + }; + namespace net { int GetMTU (const boost::asio::ip::address& localAddress); - const boost::asio::ip::address GetInterfaceAddress(const std::string & ifname, bool ipv6=false); - bool IsInReservedRange(const boost::asio::ip::address& host); + const boost::asio::ip::address GetInterfaceAddress (const std::string & ifname, bool ipv6=false); + bool IsInReservedRange (const boost::asio::ip::address& host); } } } diff --git a/libi2pd_client/AddressBook.cpp b/libi2pd_client/AddressBook.cpp index d695b8eb..a69bd660 100644 --- a/libi2pd_client/AddressBook.cpp +++ b/libi2pd_client/AddressBook.cpp @@ -748,6 +748,8 @@ namespace client void AddressBookSubscription::CheckUpdates () { + i2p::util::SetThreadName("Addressbook"); + bool result = MakeRequest (); m_Book.DownloadComplete (result, m_Ident, m_Etag, m_LastModified); } diff --git a/libi2pd_client/I2CP.cpp b/libi2pd_client/I2CP.cpp index cb618b5d..c9db4a82 100644 --- a/libi2pd_client/I2CP.cpp +++ b/libi2pd_client/I2CP.cpp @@ -26,7 +26,8 @@ namespace client I2CPDestination::I2CPDestination (boost::asio::io_service& service, std::shared_ptr owner, std::shared_ptr identity, bool isPublic, const std::map& params): LeaseSetDestination (service, isPublic, ¶ms), - m_Owner (owner), m_Identity (identity), m_EncryptionKeyType (m_Identity->GetCryptoKeyType ()) + m_Owner (owner), m_Identity (identity), m_EncryptionKeyType (m_Identity->GetCryptoKeyType ()), + m_IsCreatingLeaseSet (false), m_LeaseSetCreationTimer (service) { } @@ -34,6 +35,7 @@ namespace client { LeaseSetDestination::Stop (); m_Owner = nullptr; + m_LeaseSetCreationTimer.cancel (); } void I2CPDestination::SetEncryptionPrivateKey (const uint8_t * key) @@ -84,6 +86,11 @@ namespace client void I2CPDestination::CreateNewLeaseSet (std::vector > tunnels) { + if (m_IsCreatingLeaseSet) + { + LogPrint (eLogInfo, "I2CP: LeaseSet is being created"); + return; + } uint8_t priv[256] = {0}; i2p::data::LocalLeaseSet ls (m_Identity, priv, tunnels); // we don't care about encryption key, we need leases only m_LeaseSetExpirationTime = ls.GetExpirationTime (); @@ -94,15 +101,28 @@ namespace client uint16_t sessionID = m_Owner->GetSessionID (); if (sessionID != 0xFFFF) { + m_IsCreatingLeaseSet = true; htobe16buf (leases - 3, sessionID); size_t l = 2/*sessionID*/ + 1/*num leases*/ + i2p::data::LEASE_SIZE*tunnels.size (); m_Owner->SendI2CPMessage (I2CP_REQUEST_VARIABLE_LEASESET_MESSAGE, leases - 3, l); + m_LeaseSetCreationTimer.expires_from_now (boost::posix_time::seconds (I2CP_LEASESET_CREATION_TIMEOUT)); + auto s = GetSharedFromThis (); + m_LeaseSetCreationTimer.async_wait ([s](const boost::system::error_code& ecode) + { + if (ecode != boost::asio::error::operation_aborted) + { + LogPrint (eLogInfo, "I2CP: LeaseSet creation timeout expired. Terminate"); + if (s->m_Owner) s->m_Owner->Stop (); + } + }); } } } void I2CPDestination::LeaseSetCreated (const uint8_t * buf, size_t len) { + m_IsCreatingLeaseSet = false; + m_LeaseSetCreationTimer.cancel (); auto ls = std::make_shared (m_Identity, buf, len); ls->SetExpirationTime (m_LeaseSetExpirationTime); SetLeaseSet (ls); @@ -110,6 +130,8 @@ namespace client void I2CPDestination::LeaseSet2Created (uint8_t storeType, const uint8_t * buf, size_t len) { + m_IsCreatingLeaseSet = false; + m_LeaseSetCreationTimer.cancel (); auto ls = (storeType == i2p::data::NETDB_STORE_TYPE_ENCRYPTED_LEASESET2) ? std::make_shared (m_Identity, buf, len): std::make_shared (storeType, m_Identity, buf, len); diff --git a/libi2pd_client/I2CP.h b/libi2pd_client/I2CP.h index 32f32221..da7d8ffa 100644 --- a/libi2pd_client/I2CP.h +++ b/libi2pd_client/I2CP.h @@ -27,7 +27,8 @@ namespace client const size_t I2CP_SESSION_BUFFER_SIZE = 4096; const size_t I2CP_MAX_MESSAGE_LENGTH = 65535; const size_t I2CP_MAX_SEND_QUEUE_SIZE = 1024*1024; // in bytes, 1M - + const int I2CP_LEASESET_CREATION_TIMEOUT = 10; // in seconds + const size_t I2CP_HEADER_LENGTH_OFFSET = 0; const size_t I2CP_HEADER_TYPE_OFFSET = I2CP_HEADER_LENGTH_OFFSET + 4; const size_t I2CP_HEADER_SIZE = I2CP_HEADER_TYPE_OFFSET + 1; @@ -109,6 +110,8 @@ namespace client std::shared_ptr m_ECIESx25519Decryptor; uint8_t m_ECIESx25519PrivateKey[32]; uint64_t m_LeaseSetExpirationTime; + bool m_IsCreatingLeaseSet; + boost::asio::deadline_timer m_LeaseSetCreationTimer; }; class RunnableI2CPDestination: private i2p::util::RunnableService, public I2CPDestination diff --git a/libi2pd_client/I2PTunnel.cpp b/libi2pd_client/I2PTunnel.cpp index 081294c6..61c42b24 100644 --- a/libi2pd_client/I2PTunnel.cpp +++ b/libi2pd_client/I2PTunnel.cpp @@ -12,6 +12,7 @@ #include "Destination.h" #include "ClientContext.h" #include "I2PTunnel.h" +#include "util.h" namespace i2p { @@ -862,7 +863,7 @@ namespace client std::placeholders::_3, std::placeholders::_4, std::placeholders::_5)); dgram->SetRawReceiver(std::bind(&I2PUDPClientTunnel::HandleRecvFromI2PRaw, this, - std::placeholders::_1, std::placeholders::_2, std::placeholders::_3, std::placeholders::_4)); + std::placeholders::_1, std::placeholders::_2, std::placeholders::_3, std::placeholders::_4)); } void I2PUDPClientTunnel::Start() { @@ -891,11 +892,11 @@ namespace client } auto remotePort = m_RecvEndpoint.port(); if (!m_LastPort || m_LastPort != remotePort) - { + { auto itr = m_Sessions.find(remotePort); - if (itr != m_Sessions.end()) + if (itr != m_Sessions.end()) m_LastSession = itr->second; - else + else { m_LastSession = std::make_shared(boost::asio::ip::udp::endpoint(m_RecvEndpoint), 0); m_Sessions.emplace (remotePort, m_LastSession); @@ -940,6 +941,7 @@ namespace client } void I2PUDPClientTunnel::TryResolving() { + i2p::util::SetThreadName("UDP Resolver"); LogPrint(eLogInfo, "UDP Tunnel: Trying to resolve ", m_RemoteDest); std::shared_ptr addr; diff --git a/libi2pd_client/SAM.cpp b/libi2pd_client/SAM.cpp index d758f31e..9f7e771e 100644 --- a/libi2pd_client/SAM.cpp +++ b/libi2pd_client/SAM.cpp @@ -708,16 +708,16 @@ namespace client auto session = m_Owner.FindSession(m_ID); auto dest = session == nullptr ? context.GetSharedLocalDestination() : session->localDestination; if (name == "ME") - SendNamingLookupReply (dest->GetIdentity ()); + SendNamingLookupReply (name, dest->GetIdentity ()); else if ((identity = context.GetAddressBook ().GetFullAddress (name)) != nullptr) - SendNamingLookupReply (identity); + SendNamingLookupReply (name, identity); else if ((addr = context.GetAddressBook ().GetAddress (name))) { if (addr->IsIdentHash ()) { auto leaseSet = dest->FindLeaseSet (addr->identHash); if (leaseSet) - SendNamingLookupReply (leaseSet->GetIdentity ()); + SendNamingLookupReply (name, leaseSet->GetIdentity ()); else dest->RequestDestination (addr->identHash, std::bind (&SAMSocket::HandleNamingLookupLeaseSetRequestComplete, @@ -756,7 +756,7 @@ namespace client if (leaseSet) { context.GetAddressBook ().InsertFullAddress (leaseSet->GetIdentity ()); - SendNamingLookupReply (leaseSet->GetIdentity ()); + SendNamingLookupReply (name, leaseSet->GetIdentity ()); } else { @@ -770,13 +770,13 @@ namespace client } } - void SAMSocket::SendNamingLookupReply (std::shared_ptr identity) + void SAMSocket::SendNamingLookupReply (const std::string& name, std::shared_ptr identity) { auto base64 = identity->ToBase64 (); #ifdef _MSC_VER - size_t l = sprintf_s (m_Buffer, SAM_SOCKET_BUFFER_SIZE, SAM_NAMING_REPLY, base64.c_str ()); + size_t l = sprintf_s (m_Buffer, SAM_SOCKET_BUFFER_SIZE, SAM_NAMING_REPLY, name.c_str (), base64.c_str ()); #else - size_t l = snprintf (m_Buffer, SAM_SOCKET_BUFFER_SIZE, SAM_NAMING_REPLY, base64.c_str ()); + size_t l = snprintf (m_Buffer, SAM_SOCKET_BUFFER_SIZE, SAM_NAMING_REPLY, name.c_str (), base64.c_str ()); #endif SendMessageReply (m_Buffer, l, false); } diff --git a/libi2pd_client/SAM.h b/libi2pd_client/SAM.h index fafd7d1c..9495bf6f 100644 --- a/libi2pd_client/SAM.h +++ b/libi2pd_client/SAM.h @@ -55,7 +55,7 @@ namespace client const char SAM_DEST_REPLY[] = "DEST REPLY PUB=%s PRIV=%s\n"; const char SAM_DEST_REPLY_I2P_ERROR[] = "DEST REPLY RESULT=I2P_ERROR\n"; const char SAM_NAMING_LOOKUP[] = "NAMING LOOKUP"; - const char SAM_NAMING_REPLY[] = "NAMING REPLY RESULT=OK NAME=ME VALUE=%s\n"; + const char SAM_NAMING_REPLY[] = "NAMING REPLY RESULT=OK NAME=%s VALUE=%s\n"; const char SAM_DATAGRAM_RECEIVED[] = "DATAGRAM RECEIVED DESTINATION=%s SIZE=%lu\n"; const char SAM_RAW_RECEIVED[] = "RAW RECEIVED SIZE=%lu\n"; const char SAM_NAMING_REPLY_INVALID_KEY[] = "NAMING REPLY RESULT=INVALID_KEY NAME=%s\n"; @@ -140,7 +140,7 @@ namespace client void Connect (std::shared_ptr remote, std::shared_ptr session = nullptr); void HandleConnectLeaseSetRequestComplete (std::shared_ptr leaseSet); - void SendNamingLookupReply (std::shared_ptr identity); + void SendNamingLookupReply (const std::string& name, std::shared_ptr identity); void HandleNamingLookupLeaseSetRequestComplete (std::shared_ptr leaseSet, std::string name); void HandleSessionReadinessCheckTimer (const boost::system::error_code& ecode); void SendSessionCreateReplyOk (); diff --git a/qt/i2pd_qt/AboutDialog.cpp b/qt/i2pd_qt/AboutDialog.cpp new file mode 100644 index 00000000..ac92694a --- /dev/null +++ b/qt/i2pd_qt/AboutDialog.cpp @@ -0,0 +1,21 @@ +#include "AboutDialog.h" +#include "ui_AboutDialog.h" +#include +#include "version.h" +#include "BuildDateTimeQt.h" + +AboutDialog::AboutDialog(QWidget *parent) : + QDialog(parent), + ui(new Ui::AboutDialog) +{ + ui->setupUi(this); + ui->i2pdVersionLabel->setText(I2PD_VERSION); + ui->i2pVersionLabel->setText(I2P_VERSION); + ui->buildDateTimeLabel->setText(BUILD_DATE_TIME_QT); + ui->vcsCommitInfoLabel->setText(VCS_COMMIT_INFO); +} + +AboutDialog::~AboutDialog() +{ + delete ui; +} diff --git a/qt/i2pd_qt/AboutDialog.h b/qt/i2pd_qt/AboutDialog.h new file mode 100644 index 00000000..d462de28 --- /dev/null +++ b/qt/i2pd_qt/AboutDialog.h @@ -0,0 +1,22 @@ +#ifndef ABOUTDIALOG_H +#define ABOUTDIALOG_H + +#include + +namespace Ui { +class AboutDialog; +} + +class AboutDialog : public QDialog +{ + Q_OBJECT + +public: + explicit AboutDialog(QWidget *parent = 0); + ~AboutDialog(); + +private: + Ui::AboutDialog *ui; +}; + +#endif // ABOUTDIALOG_H diff --git a/qt/i2pd_qt/AboutDialog.ui b/qt/i2pd_qt/AboutDialog.ui new file mode 100644 index 00000000..6e662706 --- /dev/null +++ b/qt/i2pd_qt/AboutDialog.ui @@ -0,0 +1,194 @@ + + + AboutDialog + + + Qt::WindowModal + + + + 0 + 0 + 400 + 199 + + + + About i2pd_qt + + + + :/icons/mask:/icons/mask + + + + + 10 + 160 + 381 + 32 + + + + Qt::Horizontal + + + QDialogButtonBox::Ok + + + + + + 10 + 10 + 381 + 17 + + + + <html><head/><body><p><span style=" font-weight:600;">About i2pd_qt</span></p></body></html> + + + + + + 10 + 40 + 131 + 17 + + + + i2pd Version: + + + + + + 10 + 70 + 131 + 17 + + + + Build Date/Time: + + + + + + 150 + 40 + 241 + 20 + + + + I2PD_VERSION_LABEL + + + + + + 10 + 130 + 131 + 17 + + + + I2P Version: + + + + + + 150 + 130 + 241 + 17 + + + + I2P_VERSION_LABEL + + + + + + 150 + 70 + 241 + 20 + + + + BUILD_DATE_TIME_LABEL + + + + + + 10 + 100 + 131 + 17 + + + + Version Control: + + + + + + 150 + 100 + 241 + 17 + + + + VCS_COMMIT_INFO_LABEL + + + + + + + + + buttonBox + accepted() + AboutDialog + accept() + + + 248 + 254 + + + 157 + 274 + + + + + buttonBox + rejected() + AboutDialog + reject() + + + 316 + 260 + + + 286 + 274 + + + + + diff --git a/qt/i2pd_qt/BuildDateTimeQt.h b/qt/i2pd_qt/BuildDateTimeQt.h new file mode 100644 index 00000000..139e2083 --- /dev/null +++ b/qt/i2pd_qt/BuildDateTimeQt.h @@ -0,0 +1,7 @@ +#ifndef BUILDDATETIMEQT_H +#define BUILDDATETIMEQT_H + +#include +const QString BUILD_DATE_TIME_QT = QStringLiteral(__DATE__ " " __TIME__); + +#endif // BUILDDATETIMEQT_H diff --git a/qt/i2pd_qt/ClientTunnelPane.h b/qt/i2pd_qt/ClientTunnelPane.h index 80d331de..f6bf4376 100644 --- a/qt/i2pd_qt/ClientTunnelPane.h +++ b/qt/i2pd_qt/ClientTunnelPane.h @@ -79,6 +79,13 @@ protected: ClientTunnelConfig* ctc=tunnelConfig->asClientTunnelConfig(); assert(ctc!=nullptr); + if(!isValidSingleLine(destinationLineEdit))return false; + if(!isValidSingleLine(portLineEdit))return false; + if(!isValidSingleLine(cryptoTypeLineEdit))return false; + if(!isValidSingleLine(keysLineEdit))return false; + if(!isValidSingleLine(addressLineEdit))return false; + if(!isValidSingleLine(destinationPortLineEdit))return false; + //destination ctc->setdest(destinationLineEdit->text().toStdString()); diff --git a/qt/i2pd_qt/DelayedSaveManager.h b/qt/i2pd_qt/DelayedSaveManager.h index b09aa28f..23821439 100644 --- a/qt/i2pd_qt/DelayedSaveManager.h +++ b/qt/i2pd_qt/DelayedSaveManager.h @@ -2,6 +2,7 @@ #define DELAYEDSAVEMANAGER_H #include "Saver.h" +#include "I2pdQtTypes.h" class DelayedSaveManager { @@ -12,13 +13,14 @@ public: typedef unsigned int DATA_SERIAL_TYPE; - virtual void delayedSave(DATA_SERIAL_TYPE dataSerial, bool needsTunnelFocus, std::string tunnelNameToFocus)=0; + virtual void delayedSave(bool reloadAfterSave, DATA_SERIAL_TYPE dataSerial, FocusEnum focusOn, std::string tunnelNameToFocus, QWidget* widgetToFocus)=0; //returns false iff save failed virtual bool appExiting()=0; - virtual bool needsFocusOnTunnel()=0; + virtual FocusEnum getFocusOn()=0; virtual std::string& getTunnelNameToFocus()=0; + virtual QWidget* getWidgetToFocus()=0; }; #endif // DELAYEDSAVEMANAGER_H diff --git a/qt/i2pd_qt/DelayedSaveManagerImpl.cpp b/qt/i2pd_qt/DelayedSaveManagerImpl.cpp index f6704c17..bad6fdb5 100644 --- a/qt/i2pd_qt/DelayedSaveManagerImpl.cpp +++ b/qt/i2pd_qt/DelayedSaveManagerImpl.cpp @@ -1,6 +1,9 @@ #include "DelayedSaveManagerImpl.h" +#include + DelayedSaveManagerImpl::DelayedSaveManagerImpl() : + widgetToFocus(nullptr), saver(nullptr), lastDataSerialSeen(DelayedSaveManagerImpl::INITIAL_DATA_SERIAL), lastSaveStartedTimestamp(A_VERY_OBSOLETE_TIMESTAMP), @@ -21,10 +24,12 @@ bool DelayedSaveManagerImpl::isSaverValid() { return saver != nullptr; } -void DelayedSaveManagerImpl::delayedSave(DATA_SERIAL_TYPE dataSerial, bool focusOnTunnel, std::string tunnelNameToFocus_) { +void DelayedSaveManagerImpl::delayedSave(bool reloadAfterSave, DATA_SERIAL_TYPE dataSerial, FocusEnum focusOn, std::string tunnelNameToFocus, QWidget* widgetToFocus) { if(lastDataSerialSeen==dataSerial)return; - this->focusOnTunnel = focusOnTunnel; - tunnelNameToFocus = tunnelNameToFocus_; + this->reloadAfterSave = reloadAfterSave; + this->focusOn = focusOn; + this->tunnelNameToFocus = tunnelNameToFocus; + this->widgetToFocus = widgetToFocus; lastDataSerialSeen=dataSerial; assert(isSaverValid()); TIMESTAMP_TYPE now = getTime(); @@ -42,7 +47,7 @@ bool DelayedSaveManagerImpl::appExiting() { exiting=true; thread->wakeThreadAndJoinThread(); assert(isSaverValid()); - saver->save(false, ""); + saver->save(false, FocusEnum::noFocus); return true; } @@ -71,9 +76,10 @@ void DelayedSaveThread::run() { assert(saver!=nullptr); if(saveNow) { saveNow = false; - const bool focusOnTunnel = delayedSaveManagerImpl->needsFocusOnTunnel(); + const FocusEnum focusOn = delayedSaveManagerImpl->getFocusOn(); const std::string tunnelNameToFocus = delayedSaveManagerImpl->getTunnelNameToFocus(); - saver->save(focusOnTunnel, tunnelNameToFocus); + QWidget* widgetToFocus = delayedSaveManagerImpl->getWidgetToFocus(); + saver->save(delayedSaveManagerImpl->isReloadAfterSave(), focusOn, tunnelNameToFocus, widgetToFocus); continue; } if(defer) { @@ -87,9 +93,10 @@ void DelayedSaveThread::run() { if(delayedSaveManagerImpl->isExiting())return; continue; } - const bool focusOnTunnel = delayedSaveManagerImpl->needsFocusOnTunnel(); + const FocusEnum focusOn = delayedSaveManagerImpl->getFocusOn(); const std::string tunnelNameToFocus = delayedSaveManagerImpl->getTunnelNameToFocus(); - saver->save(focusOnTunnel, tunnelNameToFocus); + QWidget* widgetToFocus = delayedSaveManagerImpl->getWidgetToFocus(); + saver->save(delayedSaveManagerImpl->isReloadAfterSave(), focusOn, tunnelNameToFocus, widgetToFocus); break; //break inner loop } } @@ -131,10 +138,3 @@ Saver* DelayedSaveManagerImpl::getSaver() { return saver; } -bool DelayedSaveManagerImpl::needsFocusOnTunnel() { - return focusOnTunnel; -} - -std::string& DelayedSaveManagerImpl::getTunnelNameToFocus() { - return tunnelNameToFocus; -} diff --git a/qt/i2pd_qt/DelayedSaveManagerImpl.h b/qt/i2pd_qt/DelayedSaveManagerImpl.h index cb1f7568..c9a77c31 100644 --- a/qt/i2pd_qt/DelayedSaveManagerImpl.h +++ b/qt/i2pd_qt/DelayedSaveManagerImpl.h @@ -7,14 +7,14 @@ #include #include -#include "mainwindow.h" +#include "I2pdQtTypes.h" #include "DelayedSaveManager.h" #include "Saver.h" class DelayedSaveManagerImpl; +class Saver; -class DelayedSaveThread : public QThread -{ +class DelayedSaveThread : public QThread { Q_OBJECT public: @@ -42,14 +42,17 @@ private: volatile TIMESTAMP_TYPE wakeTime; }; -class DelayedSaveManagerImpl : public DelayedSaveManager -{ +class DelayedSaveManagerImpl : public DelayedSaveManager { + FocusEnum focusOn; + std::string tunnelNameToFocus; + QWidget* widgetToFocus; + bool reloadAfterSave; public: DelayedSaveManagerImpl(); virtual ~DelayedSaveManagerImpl(); virtual void setSaver(Saver* saver); virtual void start(); - virtual void delayedSave(DATA_SERIAL_TYPE dataSerial, bool focusOnTunnel, std::string tunnelNameToFocus); + virtual void delayedSave(bool reloadAfterSave, DATA_SERIAL_TYPE dataSerial, FocusEnum focusOn, std::string tunnelNameToFocus, QWidget* widgetToFocus); virtual bool appExiting(); typedef DelayedSaveThread::TIMESTAMP_TYPE TIMESTAMP_TYPE; @@ -59,8 +62,10 @@ public: Saver* getSaver(); static TIMESTAMP_TYPE getTime(); - bool needsFocusOnTunnel(); - std::string& getTunnelNameToFocus(); + bool isReloadAfterSave() { return reloadAfterSave; } + FocusEnum getFocusOn() { return focusOn; } + std::string& getTunnelNameToFocus() { return tunnelNameToFocus; } + QWidget* getWidgetToFocus() { return widgetToFocus; } private: Saver* saver; @@ -74,9 +79,6 @@ private: bool exiting; DelayedSaveThread* thread; void wakeThreadAndJoinThread(); - - bool focusOnTunnel; - std::string tunnelNameToFocus; }; #endif // DELAYEDSAVEMANAGERIMPL_H diff --git a/qt/i2pd_qt/I2pdQtTypes.h b/qt/i2pd_qt/I2pdQtTypes.h new file mode 100644 index 00000000..c7b1917a --- /dev/null +++ b/qt/i2pd_qt/I2pdQtTypes.h @@ -0,0 +1,7 @@ +#ifndef I2PDQTTYPES_H +#define I2PDQTTYPES_H + +enum WrongInputPageEnum { generalSettingsPage, tunnelsSettingsPage }; +enum FocusEnum { noFocus, focusOnTunnelName, focusOnWidget }; + +#endif // I2PDQTTYPES_H diff --git a/qt/i2pd_qt/I2pdQtUtil.cpp b/qt/i2pd_qt/I2pdQtUtil.cpp new file mode 100644 index 00000000..35a7adff --- /dev/null +++ b/qt/i2pd_qt/I2pdQtUtil.cpp @@ -0,0 +1,12 @@ +#include "I2pdQtUtil.h" + +bool isValidSingleLine(QLineEdit* widget, WrongInputPageEnum inputPage, MainWindow* mainWindow) { + bool correct = !widget->text().contains(QRegularExpression("[\r\n]"), nullptr); + if(!correct) { + mainWindow->highlightWrongInput( + QApplication::tr("Single line input expected, but it's multiline"), + inputPage, + widget); + } + return correct; +} diff --git a/qt/i2pd_qt/I2pdQtUtil.h b/qt/i2pd_qt/I2pdQtUtil.h new file mode 100644 index 00000000..90c3e521 --- /dev/null +++ b/qt/i2pd_qt/I2pdQtUtil.h @@ -0,0 +1,9 @@ +#ifndef I2pdQtUtil_H +#define I2pdQtUtil_H + +class QLineEdit; +#include "mainwindow.h" + +bool isValidSingleLine(QLineEdit* widget, WrongInputPageEnum inputPage, MainWindow* mainWindow); + +#endif // I2pdQtUtil_H diff --git a/qt/i2pd_qt/Saver.h b/qt/i2pd_qt/Saver.h index cefe0220..bf971fb6 100644 --- a/qt/i2pd_qt/Saver.h +++ b/qt/i2pd_qt/Saver.h @@ -4,6 +4,9 @@ #include #include #include +class QWidget; + +#include "I2pdQtTypes.h" class Saver : public QObject { @@ -11,8 +14,8 @@ class Saver : public QObject public: Saver(); - //false iff failures - virtual bool save(const bool focusOnTunnel, const std::string& tunnelNameToFocus)=0; + //FocusEnum::focusNone iff failures //??? wtf + virtual bool save(bool reloadAfterSave, const FocusEnum focusOn, const std::string& tunnelNameToFocus="", QWidget* widgetToFocus=nullptr)=0; signals: void reloadTunnelsConfigAndUISignal(const QString); diff --git a/qt/i2pd_qt/SaverImpl.cpp b/qt/i2pd_qt/SaverImpl.cpp index f35ef5b7..fee2526b 100644 --- a/qt/i2pd_qt/SaverImpl.cpp +++ b/qt/i2pd_qt/SaverImpl.cpp @@ -15,7 +15,7 @@ SaverImpl::SaverImpl(MainWindow *mainWindowPtr_, QList * config SaverImpl::~SaverImpl() {} -bool SaverImpl::save(const bool focusOnTunnel, const std::string& tunnelNameToFocus) { +bool SaverImpl::save(bool reloadAfterSave, const FocusEnum focusOn, const std::string& tunnelNameToFocus, QWidget* widgetToFocus) { //save main config { std::stringstream out; @@ -59,12 +59,14 @@ bool SaverImpl::save(const bool focusOnTunnel, const std::string& tunnelNameToFo outfile.close(); } - //reload saved configs + if(reloadAfterSave) { + //reload saved configs #if 0 - i2p::client::context.ReloadConfig(); + i2p::client::context.ReloadConfig(); #endif - if(focusOnTunnel) emit reloadTunnelsConfigAndUISignal(QString::fromStdString(tunnelNameToFocus)); + if(reloadAfterSave) emit reloadTunnelsConfigAndUISignal(focusOn==FocusEnum::focusOnTunnelName?QString::fromStdString(tunnelNameToFocus):""); + } return true; } diff --git a/qt/i2pd_qt/SaverImpl.h b/qt/i2pd_qt/SaverImpl.h index c44877a2..d20f1bbf 100644 --- a/qt/i2pd_qt/SaverImpl.h +++ b/qt/i2pd_qt/SaverImpl.h @@ -19,7 +19,7 @@ class SaverImpl : public Saver public: SaverImpl(MainWindow *mainWindowPtr_, QList * configItems_, std::map* tunnelConfigs_); virtual ~SaverImpl(); - virtual bool save(const bool focusOnTunnel, const std::string& tunnelNameToFocus); + virtual bool save(bool reloadAfterSave, const FocusEnum focusOn, const std::string& tunnelNameToFocus, QWidget* widgetToFocus); void setConfPath(QString& confpath_); void setTunnelsConfPath(QString& tunconfpath_); private: diff --git a/qt/i2pd_qt/ServerTunnelPane.h b/qt/i2pd_qt/ServerTunnelPane.h index 92ee4da5..de844249 100644 --- a/qt/i2pd_qt/ServerTunnelPane.h +++ b/qt/i2pd_qt/ServerTunnelPane.h @@ -124,6 +124,17 @@ protected: if(!ok)return false; ServerTunnelConfig* stc=tunnelConfig->asServerTunnelConfig(); assert(stc!=nullptr); + + if(!isValidSingleLine(hostLineEdit))return false; + if(!isValidSingleLine(portLineEdit))return false; + if(!isValidSingleLine(cryptoTypeLineEdit))return false; + if(!isValidSingleLine(keysLineEdit))return false; + if(!isValidSingleLine(inPortLineEdit))return false; + if(!isValidSingleLine(accessListLineEdit))return false; + if(!isValidSingleLine(hostOverrideLineEdit))return false; + if(!isValidSingleLine(webIRCPassLineEdit))return false; + if(!isValidSingleLine(addressLineEdit))return false; + stc->sethost(hostLineEdit->text().toStdString()); auto portStr=portLineEdit->text(); diff --git a/qt/i2pd_qt/TunnelPane.cpp b/qt/i2pd_qt/TunnelPane.cpp index 4b873ac1..7e15a6a6 100644 --- a/qt/i2pd_qt/TunnelPane.cpp +++ b/qt/i2pd_qt/TunnelPane.cpp @@ -4,6 +4,8 @@ #include "mainwindow.h" #include "ui_mainwindow.h" +#include "I2pdQtUtil.h" + TunnelPane::TunnelPane(TunnelsPageUpdateListener* tunnelsPageUpdateListener_, TunnelConfig* tunnelConfig_, QWidget* wrongInputPane_, QLabel* wrongInputLabel_, MainWindow* mainWindow_): QObject(), mainWindow(mainWindow_), @@ -47,8 +49,6 @@ void TunnelPane::setupTunnelPane( nameLineEdit->setText(tunnelName); setGroupBoxTitle(tunnelName); - QObject::connect(nameLineEdit, SIGNAL(textChanged(const QString &)), - this, SLOT(setGroupBoxTitle(const QString &))); QObject::connect(nameLineEdit, SIGNAL(textChanged(const QString &)), this, SLOT(updated())); @@ -399,3 +399,7 @@ void TunnelPane::hideWrongInputLabel() const { wrongInputPane->setVisible(false); mainWindow->adjustSizesAccordingToWrongLabel(); } + +bool TunnelPane::isValidSingleLine(QLineEdit* widget) { + return ::isValidSingleLine(widget, WrongInputPageEnum::tunnelsSettingsPage, mainWindow); +} diff --git a/qt/i2pd_qt/TunnelPane.h b/qt/i2pd_qt/TunnelPane.h index 59303afd..ac21181a 100644 --- a/qt/i2pd_qt/TunnelPane.h +++ b/qt/i2pd_qt/TunnelPane.h @@ -119,6 +119,21 @@ protected: public: //returns false when invalid data at UI virtual bool applyDataFromUIToTunnelConfig() { + if(!isValidSingleLine(nameLineEdit)){ + setGroupBoxTitle(QApplication::translate("tunPage", "invalid_tunnel_name")); + return false; + } + if(!isValidSingleLine(inbound_lengthLineEdit))return false; + if(!isValidSingleLine(inbound_quantityLineEdit))return false; + if(!isValidSingleLine(outbound_lengthLineEdit))return false; + if(!isValidSingleLine(outbound_quantityLineEdit))return false; + if(!isValidSingleLine(crypto_tagsToSendLineEdit))return false; + if(!isValidSingleLine(i2cp_leaseSetAuthTypeLineEdit))return false; + if(!isValidSingleLine(i2cp_leaseSetEncTypeLineEdit))return false; + if(!isValidSingleLine(i2cp_leaseSetPrivKeyLineEdit))return false; + if(!isValidSingleLine(i2cp_leaseSetTypeLineEdit))return false; + if(!isValidSingleLine(i2p_streaming_initialAckDelayLineEdit))return false; + setGroupBoxTitle(nameLineEdit->text()); tunnelConfig->setName(nameLineEdit->text().toStdString()); tunnelConfig->setType(readTunnelTypeComboboxData()); I2CPParameters& i2cpParams=tunnelConfig->getI2cpParameters(); @@ -169,6 +184,8 @@ private: i2cp_leaseSetPrivKeyLabel->setText(QApplication::translate("tunForm", "Decryption key for encrypted LeaseSet in base64. PSK or private DH:", 0)); i2cp_leaseSetAuthTypeLabel->setText(QApplication::translate("tunForm", "Auth type for encrypted LeaseSet. 0 - no auth, 1 - DH, 2 - PSK:", 0)); } +protected: + bool isValidSingleLine(QLineEdit* widget); }; #endif // TUNNELPANE_H diff --git a/qt/i2pd_qt/data/.gitignore b/qt/i2pd_qt/data/.gitignore new file mode 100644 index 00000000..dde94bce --- /dev/null +++ b/qt/i2pd_qt/data/.gitignore @@ -0,0 +1,2 @@ +*.tmp.xml + diff --git a/qt/i2pd_qt/generalsettingswidget.ui b/qt/i2pd_qt/generalsettingswidget.ui index 7a35c0a5..e81d6f1e 100644 --- a/qt/i2pd_qt/generalsettingswidget.ui +++ b/qt/i2pd_qt/generalsettingswidget.ui @@ -495,6 +495,11 @@ QGroupBox::title { + + + None + + Error diff --git a/qt/i2pd_qt/i2pd_qt.pro b/qt/i2pd_qt/i2pd_qt.pro index eed4cd7b..5aefe3f5 100644 --- a/qt/i2pd_qt/i2pd_qt.pro +++ b/qt/i2pd_qt/i2pd_qt.pro @@ -9,10 +9,15 @@ CONFIG += strict_c++ c++11 CONFIG(debug, debug|release) { message(Debug build) + + # do not redirect logging to std::ostream and to Log pane DEFINES += DEBUG_WITH_DEFAULT_LOGGING + + DEFINES += I2PD_QT_DEBUG I2PDMAKE += DEBUG=yes } else { message(Release build) + DEFINES += I2PD_QT_RELEASE I2PDMAKE += DEBUG=no } @@ -36,7 +41,9 @@ SOURCES += DaemonQT.cpp mainwindow.cpp \ ../../daemon/HTTPServer.cpp \ ../../daemon/I2PControl.cpp \ ../../daemon/i2pd.cpp \ - ../../daemon/UPnP.cpp + ../../daemon/UPnP.cpp \ + AboutDialog.cpp \ + I2pdQtUtil.cpp HEADERS += DaemonQT.h mainwindow.h \ ClientTunnelPane.h \ @@ -59,8 +66,11 @@ HEADERS += DaemonQT.h mainwindow.h \ ../../daemon/Daemon.h \ ../../daemon/HTTPServer.h \ ../../daemon/I2PControl.h \ - ../../daemon/UPnP.h - + ../../daemon/UPnP.h \ + AboutDialog.h \ + BuildDateTimeQt.h \ + I2pdQtUtil.h \ + I2pdQtTypes.h INCLUDEPATH += ../../libi2pd INCLUDEPATH += ../../libi2pd_client @@ -71,7 +81,8 @@ FORMS += mainwindow.ui \ tunnelform.ui \ statusbuttons.ui \ routercommandswidget.ui \ - generalsettingswidget.ui + generalsettingswidget.ui \ + AboutDialog.ui LIBS += $$PWD/../../libi2pd.a $$PWD/../../libi2pdclient.a -lz @@ -79,13 +90,23 @@ libi2pd.commands = @echo Building i2pd libraries libi2pd.target = $$PWD/../../libi2pd.a libi2pd.depends = i2pd FORCE -i2pd.commands = cd $$PWD/../../ && mkdir -p obj/libi2pd_client && CC=$$QMAKE_CC CXX=$$QMAKE_CXX $(MAKE) USE_UPNP=yes $$I2PDMAKE api_client +i2pd.commands = cd $$PWD/../../ && mkdir -p obj/libi2pd obj/libi2pd_client && CC=$$QMAKE_CC CXX=$$QMAKE_CXX $(MAKE) USE_UPNP=yes $$I2PDMAKE mk_obj_dir api_client i2pd.target += $$PWD/../../libi2pdclient.a i2pd.depends = FORCE cleani2pd.commands = cd $$PWD/../../ && CC=$$QMAKE_CC CXX=$$QMAKE_CXX $(MAKE) clean cleani2pd.depends = clean +BuildDateTimeQtTarget.target = BuildDateTimeQt.h +BuildDateTimeQtTarget.depends = FORCE +# 'touch' is unix-only; will probably break on non-unix, TBD +BuildDateTimeQtTarget.commands = touch $$PWD/BuildDateTimeQt.h +PRE_TARGETDEPS += BuildDateTimeQt.h +QMAKE_EXTRA_TARGETS += BuildDateTimeQtTarget + +# git only, port to other VCS, too. TBD +DEFINES += VCS_COMMIT_INFO="\\\"git:$(shell git -C \""$$_PRO_FILE_PWD_"\" describe)\\\"" + PRE_TARGETDEPS += $$PWD/../../libi2pd.a $$PWD/../../libi2pdclient.a QMAKE_EXTRA_TARGETS += cleani2pd i2pd libi2pd CLEAN_DEPS += cleani2pd diff --git a/qt/i2pd_qt/mainwindow.cpp b/qt/i2pd_qt/mainwindow.cpp index 89178ee0..7aa868fc 100644 --- a/qt/i2pd_qt/mainwindow.cpp +++ b/qt/i2pd_qt/mainwindow.cpp @@ -1,4 +1,6 @@ #include "mainwindow.h" +#include "I2pdQtUtil.h" +#include "AboutDialog.h" #include "ui_mainwindow.h" #include "ui_statusbuttons.h" #include "ui_routercommandswidget.h" @@ -8,6 +10,15 @@ #include #include #include + +#include +#include +#include +#include +#include + +#include + #include "RouterContext.h" #include "Config.h" #include "FS.h" @@ -29,10 +40,12 @@ #include "DelayedSaveManagerImpl.h" #include "SaverImpl.h" + std::string programOptionsWriterCurrentSection; MainWindow::MainWindow(std::shared_ptr logStream_, QWidget *parent) : QMainWindow(parent) + ,currentLocalDestinationB32("") ,logStream(logStream_) ,delayedSaveManagerPtr(new DelayedSaveManagerImpl()) ,dataSerial(DelayedSaveManagerImpl::INITIAL_DATA_SERIAL) @@ -65,6 +78,10 @@ MainWindow::MainWindow(std::shared_ptr logStream_, QWidget *paren statusButtonsUI->setupUi(ui->statusButtonsPane); routerCommandsUI->setupUi(routerCommandsParent); uiSettings->setupUi(ui->settingsContents); + + ui->aboutHrefLabel->setText("

" + "i2pd_qt
Version " I2PD_VERSION " · About...

"); + routerCommandsParent->hide(); ui->verticalLayout_2->addWidget(routerCommandsParent); //,statusHtmlUI(new Ui::StatusHtmlPaneForm) @@ -76,15 +93,16 @@ MainWindow::MainWindow(std::shared_ptr logStream_, QWidget *paren setWindowTitle(QApplication::translate("AppTitle","I2PD")); //TODO handle resizes and change the below into resize() call - setFixedHeight(550); - ui->centralWidget->setFixedHeight(550); + constexpr auto WINDOW_HEIGHT = 610; + setFixedHeight(WINDOW_HEIGHT); + ui->centralWidget->setFixedHeight(WINDOW_HEIGHT); onResize(); ui->stackedWidget->setCurrentIndex(0); ui->settingsScrollArea->resize(uiSettings->settingsContentsQVBoxLayout->sizeHint().width()+10,380); //QScrollBar* const barSett = ui->settingsScrollArea->verticalScrollBar(); - int w = 683; - int h = 4550; + constexpr auto w = 683; + constexpr auto h = 4550; ui->settingsContents->setFixedSize(w, h); ui->settingsContents->setGeometry(QRect(0,0,w,h)); @@ -118,6 +136,7 @@ MainWindow::MainWindow(std::shared_ptr logStream_, QWidget *paren //childTextBrowser->setOpenExternalLinks(false); childTextBrowser->setOpenLinks(false); connect(textBrowser, SIGNAL(anchorClicked(const QUrl&)), this, SLOT(anchorClickedHandler(const QUrl&))); + connect(childTextBrowser, SIGNAL(anchorClicked(const QUrl&)), this, SLOT(anchorClickedHandler(const QUrl&))); pageWithBackButton = new PageWithBackButton(this, childTextBrowser); ui->verticalLayout_2->addWidget(pageWithBackButton); pageWithBackButton->hide(); @@ -143,6 +162,8 @@ MainWindow::MainWindow(std::shared_ptr logStream_, QWidget *paren QObject::connect(routerCommandsUI->acceptTransitTunnelsPushButton, SIGNAL(released()), this, SLOT(enableTransit())); QObject::connect(routerCommandsUI->declineTransitTunnelsPushButton, SIGNAL(released()), this, SLOT(disableTransit())); + QObject::connect(ui->aboutHrefLabel, SIGNAL(linkActivated(const QString &)), this, SLOT(showAboutBox(const QString &))); + QObject::connect(ui->logViewerPushButton, SIGNAL(released()), this, SLOT(showLogViewerPage())); QObject::connect(ui->settingsPagePushButton, SIGNAL(released()), this, SLOT(showSettingsPage())); @@ -158,9 +179,9 @@ MainWindow::MainWindow(std::shared_ptr logStream_, QWidget *paren # define OPTION(section,option,defaultValueGetter) ConfigOption(QString(section),QString(option)) - initFileChooser( OPTION("","conf",[](){return "";}), uiSettings->configFileLineEdit, uiSettings->configFileBrowsePushButton); - initFileChooser( OPTION("","tunconf",[](){return "";}), uiSettings->tunnelsConfigFileLineEdit, uiSettings->tunnelsConfigFileBrowsePushButton); - initFileChooser( OPTION("","pidfile",[]{return "";}), uiSettings->pidFileLineEdit, uiSettings->pidFileBrowsePushButton); + initFileChooser( OPTION("","conf",[](){return "";}), uiSettings->configFileLineEdit, uiSettings->configFileBrowsePushButton, false, true); + initFileChooser( OPTION("","tunconf",[](){return "";}), uiSettings->tunnelsConfigFileLineEdit, uiSettings->tunnelsConfigFileBrowsePushButton, false); + initFileChooser( OPTION("","pidfile",[]{return "";}), uiSettings->pidFileLineEdit, uiSettings->pidFileBrowsePushButton, false); uiSettings->logDestinationComboBox->clear(); uiSettings->logDestinationComboBox->insertItems(0, QStringList() @@ -169,9 +190,15 @@ MainWindow::MainWindow(std::shared_ptr logStream_, QWidget *paren << QApplication::translate("MainWindow", "file", 0) ); initLogDestinationCombobox( OPTION("","log",[]{return "";}), uiSettings->logDestinationComboBox); +#ifdef I2PD_QT_RELEASE + uiSettings->logDestinationComboBox->setEnabled(false); // #1593 +#endif - logFileNameOption=initFileChooser( OPTION("","logfile",[]{return "";}), uiSettings->logFileLineEdit, uiSettings->logFileBrowsePushButton); + logFileNameOption=initFileChooser( OPTION("","logfile",[]{return "";}), uiSettings->logFileLineEdit, uiSettings->logFileBrowsePushButton, false); initLogLevelCombobox(OPTION("","loglevel",[]{return "";}), uiSettings->logLevelComboBox); + + QObject::connect(uiSettings->logLevelComboBox, SIGNAL(currentIndexChanged(int)), this, SLOT(syncLogLevel(int))); + initCheckBox( OPTION("","logclftime",[]{return "false";}), uiSettings->logclftimeCheckBox);//"Write full CLF-formatted date and time to log (default: write only time)" initFolderChooser( OPTION("","datadir",[]{return "";}), uiSettings->dataFolderLineEdit, uiSettings->dataFolderBrowsePushButton); initIPAddressBox( OPTION("","host",[]{return "";}), uiSettings->routerExternalHostLineEdit, tr("Router external address -> Host")); @@ -213,7 +240,7 @@ MainWindow::MainWindow(std::shared_ptr logStream_, QWidget *paren initIPAddressBox( OPTION("httpproxy","address",[]{return "";}), uiSettings->httpProxyAddressLineEdit, tr("HTTP proxy -> IP address")); initTCPPortBox( OPTION("httpproxy","port",[]{return "4444";}), uiSettings->httpProxyPortLineEdit, tr("HTTP proxy -> Port")); initCheckBox( OPTION("httpproxy","addresshelper",[]{return "true";}), uiSettings->httpProxyAddressHelperCheckBox);//Enable address helper (jump). true by default - initFileChooser( OPTION("httpproxy","keys",[]{return "";}), uiSettings->httpProxyKeyFileLineEdit, uiSettings->httpProxyKeyFilePushButton); + initFileChooser( OPTION("httpproxy","keys",[]{return "";}), uiSettings->httpProxyKeyFileLineEdit, uiSettings->httpProxyKeyFilePushButton, false); initSignatureTypeCombobox(OPTION("httpproxy","signaturetype",[]{return "7";}), uiSettings->comboBox_httpPorxySignatureType); initStringBox( OPTION("httpproxy","inbound.length",[]{return "3";}), uiSettings->httpProxyInboundTunnelsLenLineEdit); initStringBox( OPTION("httpproxy","inbound.quantity",[]{return "5";}), uiSettings->httpProxyInboundTunnQuantityLineEdit); @@ -226,7 +253,7 @@ MainWindow::MainWindow(std::shared_ptr logStream_, QWidget *paren initCheckBox( OPTION("socksproxy","enabled",[]{return "";}), uiSettings->socksProxyEnabledCheckBox); initIPAddressBox( OPTION("socksproxy","address",[]{return "";}), uiSettings->socksProxyAddressLineEdit, tr("Socks proxy -> IP address")); initTCPPortBox( OPTION("socksproxy","port",[]{return "4447";}), uiSettings->socksProxyPortLineEdit, tr("Socks proxy -> Port")); - initFileChooser( OPTION("socksproxy","keys",[]{return "";}), uiSettings->socksProxyKeyFileLineEdit, uiSettings->socksProxyKeyFilePushButton); + initFileChooser( OPTION("socksproxy","keys",[]{return "";}), uiSettings->socksProxyKeyFileLineEdit, uiSettings->socksProxyKeyFilePushButton, false); initSignatureTypeCombobox(OPTION("socksproxy","signaturetype",[]{return "7";}), uiSettings->comboBox_socksProxySignatureType); initStringBox( OPTION("socksproxy","inbound.length",[]{return "";}), uiSettings->socksProxyInboundTunnelsLenLineEdit); initStringBox( OPTION("socksproxy","inbound.quantity",[]{return "";}), uiSettings->socksProxyInboundTunnQuantityLineEdit); @@ -256,8 +283,8 @@ MainWindow::MainWindow(std::shared_ptr logStream_, QWidget *paren initIPAddressBox( OPTION("i2pcontrol","address",[]{return "";}), uiSettings->i2pControlAddressLineEdit, tr("I2PControl -> IP address")); initTCPPortBox( OPTION("i2pcontrol","port",[]{return "7650";}), uiSettings->i2pControlPortLineEdit, tr("I2PControl -> Port")); initStringBox( OPTION("i2pcontrol","password",[]{return "";}), uiSettings->i2pControlPasswordLineEdit); - initFileChooser( OPTION("i2pcontrol","cert",[]{return "i2pcontrol.crt.pem";}), uiSettings->i2pControlCertFileLineEdit, uiSettings->i2pControlCertFileBrowsePushButton); - initFileChooser( OPTION("i2pcontrol","key",[]{return "i2pcontrol.key.pem";}), uiSettings->i2pControlKeyFileLineEdit, uiSettings->i2pControlKeyFileBrowsePushButton); + initFileChooser( OPTION("i2pcontrol","cert",[]{return "i2pcontrol.crt.pem";}), uiSettings->i2pControlCertFileLineEdit, uiSettings->i2pControlCertFileBrowsePushButton, true); + initFileChooser( OPTION("i2pcontrol","key",[]{return "i2pcontrol.key.pem";}), uiSettings->i2pControlKeyFileLineEdit, uiSettings->i2pControlKeyFileBrowsePushButton, true); initCheckBox( OPTION("upnp","enabled",[]{return "true";}), uiSettings->enableUPnPCheckBox); initStringBox( OPTION("upnp","name",[]{return "I2Pd";}), uiSettings->upnpNameLineEdit); @@ -265,9 +292,9 @@ MainWindow::MainWindow(std::shared_ptr logStream_, QWidget *paren initCheckBox( OPTION("precomputation","elgamal",[]{return "false";}), uiSettings->useElGamalPrecomputedTablesCheckBox); initCheckBox( OPTION("reseed","verify",[]{return "";}), uiSettings->reseedVerifyCheckBox); - initFileChooser( OPTION("reseed","file",[]{return "";}), uiSettings->reseedFileLineEdit, uiSettings->reseedFileBrowsePushButton); + initFileChooser( OPTION("reseed","file",[]{return "";}), uiSettings->reseedFileLineEdit, uiSettings->reseedFileBrowsePushButton, true); initStringBox( OPTION("reseed","urls",[]{return "";}), uiSettings->reseedURLsLineEdit); - initFileChooser( OPTION("reseed","zipfile",[]{return "";}), uiSettings->reseedZipFileLineEdit, uiSettings->reseedZipFileBrowsePushButton); //Path to local .zip file to reseed from + initFileChooser( OPTION("reseed","zipfile",[]{return "";}), uiSettings->reseedZipFileLineEdit, uiSettings->reseedZipFileBrowsePushButton, true); //Path to local .zip file to reseed from initUInt16Box( OPTION("reseed","threshold",[]{return "25";}), uiSettings->reseedThresholdNumberLineEdit, tr("reseedThreshold")); //Minimum number of known routers before requesting reseed. 25 by default initStringBox( OPTION("reseed","proxy",[]{return "";}), uiSettings->reseedProxyLineEdit);//URL for https/socks reseed proxy @@ -306,7 +333,15 @@ MainWindow::MainWindow(std::shared_ptr logStream_, QWidget *paren # undef OPTION //widgetlocks.add(new widgetlock(widget,lockbtn)); + + + // #1593 +#ifdef I2PD_QT_RELEASE + uiSettings->logDestComboEditPushButton->setEnabled(false); +#else widgetlocks.add(new widgetlock(uiSettings->logDestinationComboBox,uiSettings->logDestComboEditPushButton)); +#endif + widgetlocks.add(new widgetlock(uiSettings->logLevelComboBox,uiSettings->logLevelComboEditPushButton)); widgetlocks.add(new widgetlock(uiSettings->comboBox_httpPorxySignatureType,uiSettings->httpProxySignTypeComboEditPushButton)); widgetlocks.add(new widgetlock(uiSettings->comboBox_socksProxySignatureType,uiSettings->socksProxySignTypeComboEditPushButton)); @@ -400,6 +435,29 @@ void MainWindow::showStatusPage(StatusPage newStatusPage){ } wasSelectingAtStatusMainPage=false; } + +void MainWindow::showAboutBox(const QString & href) { + AboutDialog dialog(this); + + /* + //doesn't work on older qt5: ‘class QStyleHints’ has no member named ‘showIsMaximized’ + if (!QGuiApplication::styleHints()->showIsFullScreen() && !QGuiApplication::styleHints()->showIsMaximized()) { + const QWindow * windowHandle = dialog.windowHandle(); + qDebug()<<"AboutDialog windowHandle ptr: "<<(size_t)windowHandle<screen():nullptr; //Qt 5.14+: dialog.screen() + qDebug()<<"AboutDialog screen ptr: "<<(size_t)screen<availableGeometry(); + //dialog.resize(availableGeometry.width() / 3, availableGeometry.height() * 2 / 3); + dialog.move((availableGeometry.width() - dialog.width()) / 2, + (availableGeometry.height() - dialog.height()) / 2); + } + } + */ + + (void) dialog.exec(); +} + void MainWindow::showLogViewerPage(){ui->stackedWidget->setCurrentIndex(1);setStatusButtonsVisible(false);} void MainWindow::showSettingsPage(){ui->stackedWidget->setCurrentIndex(2);setStatusButtonsVisible(false);} void MainWindow::showTunnelsPage(){ui->stackedWidget->setCurrentIndex(3);setStatusButtonsVisible(false);} @@ -607,15 +665,15 @@ MainWindow::~MainWindow() //QMessageBox::information(0, "Debug", "mw destructor 2"); } -FileChooserItem* MainWindow::initFileChooser(ConfigOption option, QLineEdit* fileNameLineEdit, QPushButton* fileBrowsePushButton){ +FileChooserItem* MainWindow::initFileChooser(ConfigOption option, QLineEdit* fileNameLineEdit, QPushButton* fileBrowsePushButton, bool requireExistingFile, bool readOnly){ FileChooserItem* retVal; - retVal=new FileChooserItem(option, fileNameLineEdit, fileBrowsePushButton); + retVal=new FileChooserItem(option, fileNameLineEdit, fileBrowsePushButton, this, requireExistingFile, readOnly); MainWindowItem* super=retVal; configItems.append(super); return retVal; } void MainWindow::initFolderChooser(ConfigOption option, QLineEdit* folderLineEdit, QPushButton* folderBrowsePushButton){ - configItems.append(new FolderChooserItem(option, folderLineEdit, folderBrowsePushButton)); + configItems.append(new FolderChooserItem(option, folderLineEdit, folderBrowsePushButton, this, true)); } /*void MainWindow::initCombobox(ConfigOption option, QComboBox* comboBox){ configItems.append(new ComboBoxItem(option, comboBox)); @@ -631,25 +689,25 @@ void MainWindow::initSignatureTypeCombobox(ConfigOption option, QComboBox* combo configItems.append(new SignatureTypeComboBoxItem(option, comboBox)); } void MainWindow::initIPAddressBox(ConfigOption option, QLineEdit* addressLineEdit, QString fieldNameTranslated){ - configItems.append(new IPAddressStringItem(option, addressLineEdit, fieldNameTranslated)); + configItems.append(new IPAddressStringItem(option, addressLineEdit, fieldNameTranslated, this)); } void MainWindow::initTCPPortBox(ConfigOption option, QLineEdit* portLineEdit, QString fieldNameTranslated){ - configItems.append(new TCPPortStringItem(option, portLineEdit, fieldNameTranslated)); + configItems.append(new TCPPortStringItem(option, portLineEdit, fieldNameTranslated, this)); } void MainWindow::initCheckBox(ConfigOption option, QCheckBox* checkBox) { configItems.append(new CheckBoxItem(option, checkBox)); } void MainWindow::initIntegerBox(ConfigOption option, QLineEdit* numberLineEdit, QString fieldNameTranslated){ - configItems.append(new IntegerStringItem(option, numberLineEdit, fieldNameTranslated)); + configItems.append(new IntegerStringItem(option, numberLineEdit, fieldNameTranslated, this)); } void MainWindow::initUInt32Box(ConfigOption option, QLineEdit* numberLineEdit, QString fieldNameTranslated){ - configItems.append(new UInt32StringItem(option, numberLineEdit, fieldNameTranslated)); + configItems.append(new UInt32StringItem(option, numberLineEdit, fieldNameTranslated, this)); } void MainWindow::initUInt16Box(ConfigOption option, QLineEdit* numberLineEdit, QString fieldNameTranslated){ - configItems.append(new UInt16StringItem(option, numberLineEdit, fieldNameTranslated)); + configItems.append(new UInt16StringItem(option, numberLineEdit, fieldNameTranslated, this)); } void MainWindow::initStringBox(ConfigOption option, QLineEdit* lineEdit){ - configItems.append(new BaseStringItem(option, lineEdit, QString())); + configItems.append(new BaseStringItem(option, lineEdit, QString(), this)); } NonGUIOptionItem* MainWindow::initNonGUIOption(ConfigOption option) { NonGUIOptionItem * retValue; @@ -741,7 +799,7 @@ void MainWindow::deleteTunnelFromUI(std::string tunnelName, TunnelConfig* cnf) { } /** returns false iff not valid items present and save was aborted */ -bool MainWindow::saveAllConfigs(bool focusOnTunnel, std::string tunnelNameToFocus){ +bool MainWindow::saveAllConfigs(bool reloadAfterSave, FocusEnum focusOn, std::string tunnelNameToFocus, QWidget* widgetToFocus){ QString cannotSaveSettings = QApplication::tr("Cannot save settings."); programOptionsWriterCurrentSection=""; /*if(!logFileNameOption->lineEdit->text().trimmed().isEmpty())logOption->optionValue=boost::any(std::string("file")); @@ -751,12 +809,17 @@ bool MainWindow::saveAllConfigs(bool focusOnTunnel, std::string tunnelNameToFocu for(QList::iterator it = configItems.begin(); it!= configItems.end(); ++it) { MainWindowItem* item = *it; - if(!item->isValid()){ - highlightWrongInput(QApplication::tr("Invalid value for")+" "+item->getConfigOption().section+"::"+item->getConfigOption().option+". "+item->getRequirementToBeValid()+" "+cannotSaveSettings, item->getWidgetToFocus()); + bool alreadyDisplayedIfWrong=false; + if(!item->isValid(alreadyDisplayedIfWrong)){ + if(!alreadyDisplayedIfWrong) + highlightWrongInput( + QApplication::tr("Invalid value for")+" "+item->getConfigOption().section+"::"+item->getConfigOption().option+". "+item->getRequirementToBeValid()+" "+cannotSaveSettings, + WrongInputPageEnum::generalSettingsPage, + item->getWidgetToFocus()); return false; } } - delayedSaveManagerPtr->delayedSave(++dataSerial, focusOnTunnel, tunnelNameToFocus); + delayedSaveManagerPtr->delayedSave(reloadAfterSave, ++dataSerial, focusOn, tunnelNameToFocus, widgetToFocus);//TODO does dataSerial work? //FIXME //onLoggingOptionsChange(); return true; @@ -764,11 +827,14 @@ bool MainWindow::saveAllConfigs(bool focusOnTunnel, std::string tunnelNameToFocu void FileChooserItem::pushButtonReleased() { QString fileName = lineEdit->text().trimmed(); - fileName = QFileDialog::getOpenFileName(nullptr, tr("Open File"), fileName, tr("All Files (*.*)")); + fileName = requireExistingFile ? + QFileDialog::getOpenFileName(nullptr, tr("Open File"), fileName, tr("All Files (*.*)")) : + QFileDialog::getSaveFileName(nullptr, tr("Open File"), fileName, tr("All Files (*.*)")); if(fileName.length()>0)lineEdit->setText(fileName); } void FolderChooserItem::pushButtonReleased() { QString fileName = lineEdit->text().trimmed(); + assert(requireExistingFile); fileName = QFileDialog::getExistingDirectory(nullptr, tr("Open Folder"), fileName); if(fileName.length()>0)lineEdit->setText(fileName); } @@ -776,6 +842,11 @@ void FolderChooserItem::pushButtonReleased() { void BaseStringItem::installListeners(MainWindow *mainWindow) { QObject::connect(lineEdit, SIGNAL(textChanged(const QString &)), mainWindow, SLOT(updated())); } +bool BaseStringItem::isValid(bool & alreadyDisplayedIfWrong) { + alreadyDisplayedIfWrong=true; + return ::isValidSingleLine(lineEdit, WrongInputPageEnum::generalSettingsPage, mainWindow); +} + void ComboBoxItem::installListeners(MainWindow *mainWindow) { QObject::connect(comboBox, SIGNAL(currentIndexChanged(int)), mainWindow, SLOT(updated())); } @@ -787,8 +858,9 @@ void MainWindow::updated() { ui->wrongInputLabel->setVisible(false); adjustSizesAccordingToWrongLabel(); - applyTunnelsUiToConfigs(); - saveAllConfigs(false); + bool correct = applyTunnelsUiToConfigs(); + if(!correct) return; + saveAllConfigs(false, FocusEnum::noFocus); } void MainWindowItem::installListeners(MainWindow *mainWindow) {} @@ -863,11 +935,11 @@ bool MainWindow::applyTunnelsUiToConfigs() { return true; } -void MainWindow::reloadTunnelsConfigAndUI_QString(const QString tunnelNameToFocus) { - reloadTunnelsConfigAndUI(tunnelNameToFocus.toStdString()); +void MainWindow::reloadTunnelsConfigAndUI_QString(QString tunnelNameToFocus) { + reloadTunnelsConfigAndUI(tunnelNameToFocus.toStdString(), nullptr); } -void MainWindow::reloadTunnelsConfigAndUI(std::string tunnelNameToFocus) { +void MainWindow::reloadTunnelsConfigAndUI(std::string tunnelNameToFocus, QWidget* widgetToFocus) { deleteTunnelForms(); for (std::map::iterator it=tunnelConfigs.begin(); it!=tunnelConfigs.end(); ++it) { TunnelConfig* tunconf = it->second; @@ -884,8 +956,10 @@ void MainWindow::TunnelsPageUpdateListenerMainWindowImpl::updated(std::string ol std::map::const_iterator it=mainWindow->tunnelConfigs.find(oldName); if(it!=mainWindow->tunnelConfigs.end())mainWindow->tunnelConfigs.erase(it); mainWindow->tunnelConfigs[tunConf->getName()]=tunConf; + mainWindow->saveAllConfigs(true, FocusEnum::focusOnTunnelName, tunConf->getName()); } - mainWindow->saveAllConfigs(true, tunConf->getName()); + else + mainWindow->saveAllConfigs(false, FocusEnum::noFocus); } void MainWindow::TunnelsPageUpdateListenerMainWindowImpl::needsDeleting(std::string oldName){ @@ -923,20 +997,71 @@ void MainWindow::anchorClickedHandler(const QUrl & link) { qDebug()< params; + i2p::http::URL url; + url.parse(str.toStdString()); + url.parse_query(params); + const std::string page = params["page"]; + const std::string cmd = params["cmd"]; + if (page == "sam_session") { pageWithBackButton->show(); textBrowser->hide(); std::stringstream s; - std::string strstd = str.toStdString(); + i2p::http::ShowSAMSession (s, params["sam_id"]); + childTextBrowser->setHtml(QString::fromStdString(s.str())); + } else if (page == "local_destination") { + std::string b32 = params["b32"]; + currentLocalDestinationB32 = b32; + pageWithBackButton->show(); + textBrowser->hide(); + std::stringstream s; + std::string strstd = currentLocalDestinationB32; i2p::http::ShowLocalDestination(s,strstd,0); childTextBrowser->setHtml(QString::fromStdString(s.str())); - } + } else if (page == "i2cp_local_destination") { + pageWithBackButton->show(); + textBrowser->hide(); + std::stringstream s; + i2p::http::ShowI2CPLocalDestination (s, params["i2cp_id"]); + childTextBrowser->setHtml(QString::fromStdString(s.str())); + } else if(cmd == "closestream") { + std::string b32 = params["b32"]; + uint32_t streamID = std::stoul(params["streamID"], nullptr); + + i2p::data::IdentHash ident; + ident.FromBase32 (b32); + auto dest = i2p::client::context.FindLocalDestination (ident); + + if (streamID) { + if (dest) { + if(dest->DeleteStream (streamID)) + QMessageBox::information( + this, + QApplication::tr("Success"), + QApplication::tr("SUCCESS: Stream closed")); + else + QMessageBox::critical( + this, + QApplication::tr("Error"), + QApplication::tr("ERROR: Stream not found or already was closed")); + } + else + QMessageBox::critical( + this, + QApplication::tr("Error"), + QApplication::tr("ERROR: Destination not found")); + } + else + QMessageBox::critical( + this, + QApplication::tr("Error"), + QApplication::tr("ERROR: StreamID is null")); + std::stringstream s; + std::string strstd = currentLocalDestinationB32; + i2p::http::ShowLocalDestination(s,strstd,0); + childTextBrowser->setHtml(QString::fromStdString(s.str())); + } } void MainWindow::backClickedFromChild() { @@ -944,39 +1069,58 @@ void MainWindow::backClickedFromChild() { } void MainWindow::adjustSizesAccordingToWrongLabel() { + constexpr auto HEIGHT = 581; + constexpr auto WIDTH = 707; if(ui->wrongInputLabel->isVisible()) { int dh = ui->wrongInputLabel->height()+ui->verticalLayout_7->layout()->spacing(); ui->verticalLayout_7->invalidate(); ui->wrongInputLabel->adjustSize(); ui->stackedWidget->adjustSize(); - ui->stackedWidget->setFixedHeight(531-dh); - ui->settingsPage->setFixedHeight(531-dh); - ui->verticalLayoutWidget_4->setGeometry(QRect(0, 0, 711, 531-dh)); - ui->stackedWidget->setFixedHeight(531-dh); - ui->settingsScrollArea->setFixedHeight(531-dh-settingsTitleLabelNominalHeight-ui->verticalLayout_4->spacing()); + const auto height = HEIGHT - dh; + ui->stackedWidget->setFixedHeight(height); + ui->settingsPage->setFixedHeight(height); + ui->verticalLayoutWidget_4->setGeometry(QRect(0, 0, WIDTH, height)); + ui->stackedWidget->setFixedHeight(height); + ui->settingsScrollArea->setFixedHeight(height-settingsTitleLabelNominalHeight-ui->verticalLayout_4->spacing()); ui->settingsTitleLabel->setFixedHeight(settingsTitleLabelNominalHeight); - ui->tunnelsScrollArea->setFixedHeight(531-dh-settingsTitleLabelNominalHeight-ui->horizontalLayout_42->geometry().height()-2*ui->verticalLayout_4->spacing()); + ui->tunnelsScrollArea->setFixedHeight(height-settingsTitleLabelNominalHeight-ui->horizontalLayout_42->geometry().height()-2*ui->verticalLayout_4->spacing()); ui->tunnelsTitleLabel->setFixedHeight(settingsTitleLabelNominalHeight); }else{ ui->verticalLayout_7->invalidate(); ui->wrongInputLabel->adjustSize(); ui->stackedWidget->adjustSize(); - ui->stackedWidget->setFixedHeight(531); - ui->settingsPage->setFixedHeight(531); - ui->verticalLayoutWidget_4->setGeometry(QRect(0, 0, 711, 531)); - ui->stackedWidget->setFixedHeight(531); - ui->settingsScrollArea->setFixedHeight(531-settingsTitleLabelNominalHeight-ui->verticalLayout_4->spacing()); + ui->stackedWidget->setFixedHeight(HEIGHT); + ui->settingsPage->setFixedHeight(HEIGHT); + ui->verticalLayoutWidget_4->setGeometry(QRect(0, 0, WIDTH, HEIGHT)); + ui->stackedWidget->setFixedHeight(HEIGHT); + ui->settingsScrollArea->setFixedHeight(HEIGHT-settingsTitleLabelNominalHeight-ui->verticalLayout_4->spacing()); ui->settingsTitleLabel->setFixedHeight(settingsTitleLabelNominalHeight); - ui->tunnelsScrollArea->setFixedHeight(531-settingsTitleLabelNominalHeight-ui->horizontalLayout_42->geometry().height()-2*ui->verticalLayout_4->spacing()); + ui->tunnelsScrollArea->setFixedHeight(HEIGHT-settingsTitleLabelNominalHeight-ui->horizontalLayout_42->geometry().height()-2*ui->verticalLayout_4->spacing()); ui->tunnelsTitleLabel->setFixedHeight(settingsTitleLabelNominalHeight); } } -void MainWindow::highlightWrongInput(QString warningText, QWidget* widgetToFocus) { +void MainWindow::highlightWrongInput(QString warningText, WrongInputPageEnum inputPage, QWidget* widgetToFocus) { bool redVisible = ui->wrongInputLabel->isVisible(); ui->wrongInputLabel->setVisible(true); ui->wrongInputLabel->setText(warningText); if(!redVisible)adjustSizesAccordingToWrongLabel(); if(widgetToFocus){ui->settingsScrollArea->ensureWidgetVisible(widgetToFocus);widgetToFocus->setFocus();} - showSettingsPage(); + switch(inputPage) { + case WrongInputPageEnum::generalSettingsPage: showSettingsPage(); break; + case WrongInputPageEnum::tunnelsSettingsPage: showTunnelsPage(); break; + default: assert(false); break; + } } + +void MainWindow::syncLogLevel (int /*comboBoxIndex*/) { + std::string level = uiSettings->logLevelComboBox->currentText().toLower().toStdString(); + if (level == "none" || level == "error" || level == "warn" || level == "info" || level == "debug") + i2p::log::Logger().SetLogLevel(level); + else { + LogPrint(eLogError, "unknown loglevel set attempted"); + return; + } + i2p::log::Logger().Reopen (); +} + diff --git a/qt/i2pd_qt/mainwindow.h b/qt/i2pd_qt/mainwindow.h index 77c8826b..e1ddcc6e 100644 --- a/qt/i2pd_qt/mainwindow.h +++ b/qt/i2pd_qt/mainwindow.h @@ -66,6 +66,8 @@ #include "DelayedSaveManagerImpl.h" #include "SaverImpl.h" +#include "I2pdQtUtil.h" + class SaverImpl; class LogViewerManager; @@ -100,12 +102,14 @@ class MainWindow; class MainWindowItem : public QObject { Q_OBJECT +private: ConfigOption option; QWidget* widgetToFocus; QString requirementToBeValid; + const bool readOnly; public: - MainWindowItem(ConfigOption option_, QWidget* widgetToFocus_, QString requirementToBeValid_) : - option(option_), widgetToFocus(widgetToFocus_), requirementToBeValid(requirementToBeValid_) {} + MainWindowItem(ConfigOption option_, QWidget* widgetToFocus_, QString requirementToBeValid_, bool readOnly_=false) : + option(option_), widgetToFocus(widgetToFocus_), requirementToBeValid(requirementToBeValid_), readOnly(readOnly_) {} QWidget* getWidgetToFocus(){return widgetToFocus;} QString& getRequirementToBeValid() { return requirementToBeValid; } ConfigOption& getConfigOption() { return option; } @@ -116,13 +120,14 @@ public: std::string optName=""; if(!option.section.isEmpty())optName=option.section.toStdString()+std::string("."); optName+=option.option.toStdString(); - qDebug() << "loadFromConfigOption[" << optName.c_str() << "]"; + //qDebug() << "loadFromConfigOption[" << optName.c_str() << "]"; boost::any programOption; i2p::config::GetOptionAsAny(optName, programOption); optionValue=programOption.empty()?boost::any(std::string("")) :boost::any_cast(programOption).value(); } virtual void saveToStringStream(std::stringstream& out){ + if(readOnly)return; //should readOnly items (conf=) error somewhere, instead of silently skipping save? if(isType(optionValue)) { std::string v = boost::any_cast(optionValue); if(v.empty())return; @@ -132,7 +137,7 @@ public: std::string optName=""; if(!option.section.isEmpty())optName=option.section.toStdString()+std::string("."); optName+=option.option.toStdString(); - qDebug() << "Writing option" << optName.c_str() << "of type" << rtti.c_str(); + //qDebug() << "Writing option" << optName.c_str() << "of type" << rtti.c_str(); std::string sectionAsStdStr = option.section.toStdString(); if(!option.section.isEmpty() && sectionAsStdStr!=programOptionsWriterCurrentSection) { @@ -155,19 +160,24 @@ public: }else out << boost::any_cast(optionValue); //let it throw out << "\n\n"; } - virtual bool isValid(){return true;} + virtual bool isValid(bool & alreadyDisplayedIfWrong){alreadyDisplayedIfWrong=false;return true;} }; class NonGUIOptionItem : public MainWindowItem { public: - NonGUIOptionItem(ConfigOption option_) : MainWindowItem(option_, nullptr, QString()) {}; + NonGUIOptionItem(ConfigOption option_) : MainWindowItem(option_, nullptr, QString()) {} virtual ~NonGUIOptionItem(){} - virtual bool isValid() { return true; } + //virtual bool isValid(bool & alreadyDisplayedIfWrong) { return true; } }; class BaseStringItem : public MainWindowItem { Q_OBJECT public: QLineEdit* lineEdit; - BaseStringItem(ConfigOption option_, QLineEdit* lineEdit_, QString requirementToBeValid_) : MainWindowItem(option_, lineEdit_, requirementToBeValid_), lineEdit(lineEdit_){}; + MainWindow *mainWindow; + BaseStringItem(ConfigOption option_, QLineEdit* lineEdit_, QString requirementToBeValid_, MainWindow* mainWindow_, bool readOnly=false): + MainWindowItem(option_, lineEdit_, requirementToBeValid_, readOnly), + lineEdit(lineEdit_), + mainWindow(mainWindow_) + {}; virtual ~BaseStringItem(){} virtual void installListeners(MainWindow *mainWindow); virtual QString toString(){ @@ -183,13 +193,15 @@ public: optionValue=fromString(lineEdit->text()); MainWindowItem::saveToStringStream(out); } - virtual bool isValid() { return true; } + virtual bool isValid(bool & alreadyDisplayedIfWrong); }; class FileOrFolderChooserItem : public BaseStringItem { +protected: + const bool requireExistingFile; public: QPushButton* browsePushButton; - FileOrFolderChooserItem(ConfigOption option_, QLineEdit* lineEdit_, QPushButton* browsePushButton_) : - BaseStringItem(option_, lineEdit_, QString()), browsePushButton(browsePushButton_) {} + FileOrFolderChooserItem(ConfigOption option_, QLineEdit* lineEdit_, QPushButton* browsePushButton_, MainWindow* mw, bool requireExistingFile_, bool readOnly) : + BaseStringItem(option_, lineEdit_, QString(), mw, readOnly), requireExistingFile(requireExistingFile_), browsePushButton(browsePushButton_) {} virtual ~FileOrFolderChooserItem(){} }; class FileChooserItem : public FileOrFolderChooserItem { @@ -197,8 +209,8 @@ class FileChooserItem : public FileOrFolderChooserItem { private slots: void pushButtonReleased(); public: - FileChooserItem(ConfigOption option_, QLineEdit* lineEdit_, QPushButton* browsePushButton_) : - FileOrFolderChooserItem(option_, lineEdit_, browsePushButton_) { + FileChooserItem(ConfigOption option_, QLineEdit* lineEdit_, QPushButton* browsePushButton_, MainWindow* mw, bool requireExistingFile, bool readOnly) : + FileOrFolderChooserItem(option_, lineEdit_, browsePushButton_, mw, requireExistingFile, readOnly) { QObject::connect(browsePushButton, SIGNAL(released()), this, SLOT(pushButtonReleased())); } }; @@ -207,20 +219,20 @@ class FolderChooserItem : public FileOrFolderChooserItem{ private slots: void pushButtonReleased(); public: - FolderChooserItem(ConfigOption option_, QLineEdit* lineEdit_, QPushButton* browsePushButton_) : - FileOrFolderChooserItem(option_, lineEdit_, browsePushButton_) { + FolderChooserItem(ConfigOption option_, QLineEdit* lineEdit_, QPushButton* browsePushButton_, MainWindow* mw, bool requireExistingFolder) : + FileOrFolderChooserItem(option_, lineEdit_, browsePushButton_, mw, requireExistingFolder, false) { QObject::connect(browsePushButton, SIGNAL(released()), this, SLOT(pushButtonReleased())); } }; class ComboBoxItem : public MainWindowItem { public: QComboBox* comboBox; - ComboBoxItem(ConfigOption option_, QComboBox* comboBox_) : MainWindowItem(option_,comboBox_,QString()), comboBox(comboBox_){}; + ComboBoxItem(ConfigOption option_, QComboBox* comboBox_) : MainWindowItem(option_,comboBox_,QString()), comboBox(comboBox_){} virtual ~ComboBoxItem(){} virtual void installListeners(MainWindow *mainWindow); virtual void loadFromConfigOption()=0; virtual void saveToStringStream(std::stringstream& out)=0; - virtual bool isValid() { return true; } + //virtual bool isValid(bool & alreadyDisplayedIfWrong) { return ; } }; class LogDestinationComboBoxItem : public ComboBoxItem { public: @@ -237,13 +249,13 @@ public: optionValue=logDest; MainWindowItem::saveToStringStream(out); } - virtual bool isValid() { return true; } + //virtual bool isValid(bool & alreadyDisplayedIfWrong) { return true; } Q_OBJECT }; class LogLevelComboBoxItem : public ComboBoxItem { public: - LogLevelComboBoxItem(ConfigOption option_, QComboBox* comboBox_) : ComboBoxItem(option_, comboBox_) {}; + LogLevelComboBoxItem(ConfigOption option_, QComboBox* comboBox_) : ComboBoxItem(option_, comboBox_) {} virtual ~LogLevelComboBoxItem(){} virtual void loadFromConfigOption(){ MainWindowItem::loadFromConfigOption(); @@ -254,11 +266,11 @@ public: optionValue=comboBox->currentText().toStdString(); MainWindowItem::saveToStringStream(out); } - virtual bool isValid() { return true; } + //virtual bool isValid(bool & alreadyDisplayedIfWrong) { return true; } }; class SignatureTypeComboBoxItem : public ComboBoxItem { public: - SignatureTypeComboBoxItem(ConfigOption option_, QComboBox* comboBox_) : ComboBoxItem(option_, comboBox_) {}; + SignatureTypeComboBoxItem(ConfigOption option_, QComboBox* comboBox_) : ComboBoxItem(option_, comboBox_) {} virtual ~SignatureTypeComboBoxItem(){} virtual void loadFromConfigOption(){ MainWindowItem::loadFromConfigOption(); @@ -271,39 +283,42 @@ public: optionValue=(unsigned short)selected; MainWindowItem::saveToStringStream(out); } - virtual bool isValid() { return true; } + //virtual bool isValid(bool & alreadyDisplayedIfWrong) { return true; } }; class CheckBoxItem : public MainWindowItem { public: QCheckBox* checkBox; - CheckBoxItem(ConfigOption option_, QCheckBox* checkBox_) : MainWindowItem(option_,checkBox_,QString()), checkBox(checkBox_){}; + CheckBoxItem(ConfigOption option_, QCheckBox* checkBox_) : MainWindowItem(option_,checkBox_,QString()), checkBox(checkBox_){} virtual ~CheckBoxItem(){} virtual void installListeners(MainWindow *mainWindow); virtual void loadFromConfigOption(){ MainWindowItem::loadFromConfigOption(); - qDebug() << "setting value for checkbox " << checkBox->text(); + //qDebug() << "setting value for checkbox " << checkBox->text(); checkBox->setChecked(boost::any_cast(optionValue)); } virtual void saveToStringStream(std::stringstream& out){ optionValue=checkBox->isChecked(); MainWindowItem::saveToStringStream(out); } - virtual bool isValid() { return true; } + //virtual bool isValid(bool & alreadyDisplayedIfWrong) { return true; } }; class BaseFormattedStringItem : public BaseStringItem { public: QString fieldNameTranslated; - BaseFormattedStringItem(ConfigOption option_, QLineEdit* lineEdit_, QString fieldNameTranslated_, QString requirementToBeValid_) : - BaseStringItem(option_, lineEdit_, requirementToBeValid_), fieldNameTranslated(fieldNameTranslated_) {}; + BaseFormattedStringItem(ConfigOption option_, QLineEdit* lineEdit_, QString fieldNameTranslated_, QString requirementToBeValid_, MainWindow* mw) : + BaseStringItem(option_, lineEdit_, requirementToBeValid_, mw), fieldNameTranslated(fieldNameTranslated_) {} virtual ~BaseFormattedStringItem(){} - virtual bool isValid()=0; + //virtual bool isValid(bool & alreadyDisplayedIfWrong)=0; }; class IntegerStringItem : public BaseFormattedStringItem { public: - IntegerStringItem(ConfigOption option_, QLineEdit* lineEdit_, QString fieldNameTranslated_) : - BaseFormattedStringItem(option_, lineEdit_, fieldNameTranslated_, QApplication::tr("Must be a valid integer.")) {}; + IntegerStringItem(ConfigOption option_, QLineEdit* lineEdit_, QString fieldNameTranslated_, MainWindow* mw) : + BaseFormattedStringItem(option_, lineEdit_, fieldNameTranslated_, QApplication::tr("Must be a valid integer."), mw) {} virtual ~IntegerStringItem(){} - virtual bool isValid(){ + virtual bool isValid(bool & alreadyDisplayedIfWrong){ + bool correct = BaseFormattedStringItem::isValid(alreadyDisplayedIfWrong); + if(!correct)return false; + alreadyDisplayedIfWrong = false; auto str=lineEdit->text(); bool ok; str.toInt(&ok); @@ -314,10 +329,13 @@ public: }; class UShortStringItem : public BaseFormattedStringItem { public: - UShortStringItem(ConfigOption option_, QLineEdit* lineEdit_, QString fieldNameTranslated_) : - BaseFormattedStringItem(option_, lineEdit_, fieldNameTranslated_, QApplication::tr("Must be unsigned short integer.")) {}; + UShortStringItem(ConfigOption option_, QLineEdit* lineEdit_, QString fieldNameTranslated_, MainWindow* mw) : + BaseFormattedStringItem(option_, lineEdit_, fieldNameTranslated_, QApplication::tr("Must be unsigned short integer."), mw) {} virtual ~UShortStringItem(){} - virtual bool isValid(){ + virtual bool isValid(bool & alreadyDisplayedIfWrong){ + bool correct = BaseFormattedStringItem::isValid(alreadyDisplayedIfWrong); + if(!correct)return false; + alreadyDisplayedIfWrong = false; auto str=lineEdit->text(); bool ok; str.toUShort(&ok); @@ -328,10 +346,13 @@ public: }; class UInt32StringItem : public BaseFormattedStringItem { public: - UInt32StringItem(ConfigOption option_, QLineEdit* lineEdit_, QString fieldNameTranslated_) : - BaseFormattedStringItem(option_, lineEdit_, fieldNameTranslated_, QApplication::tr("Must be unsigned 32-bit integer.")) {}; + UInt32StringItem(ConfigOption option_, QLineEdit* lineEdit_, QString fieldNameTranslated_, MainWindow* mw) : + BaseFormattedStringItem(option_, lineEdit_, fieldNameTranslated_, QApplication::tr("Must be unsigned 32-bit integer."), mw) {} virtual ~UInt32StringItem(){} - virtual bool isValid(){ + virtual bool isValid(bool & alreadyDisplayedIfWrong){ + bool correct = BaseFormattedStringItem::isValid(alreadyDisplayedIfWrong); + if(!correct)return false; + alreadyDisplayedIfWrong = false; auto str=lineEdit->text(); bool ok; str.toUInt(&ok); @@ -342,10 +363,13 @@ public: }; class UInt16StringItem : public BaseFormattedStringItem { public: - UInt16StringItem(ConfigOption option_, QLineEdit* lineEdit_, QString fieldNameTranslated_) : - BaseFormattedStringItem(option_, lineEdit_, fieldNameTranslated_, QApplication::tr("Must be unsigned 16-bit integer.")) {}; + UInt16StringItem(ConfigOption option_, QLineEdit* lineEdit_, QString fieldNameTranslated_, MainWindow* mw) : + BaseFormattedStringItem(option_, lineEdit_, fieldNameTranslated_, QApplication::tr("Must be unsigned 16-bit integer."), mw) {} virtual ~UInt16StringItem(){} - virtual bool isValid(){ + virtual bool isValid(bool & alreadyDisplayedIfWrong){ + bool correct = BaseFormattedStringItem::isValid(alreadyDisplayedIfWrong); + if(!correct)return false; + alreadyDisplayedIfWrong = false; auto str=lineEdit->text(); bool ok; str.toUShort(&ok); @@ -356,14 +380,14 @@ public: }; class IPAddressStringItem : public BaseFormattedStringItem { public: - IPAddressStringItem(ConfigOption option_, QLineEdit* lineEdit_, QString fieldNameTranslated_) : - BaseFormattedStringItem(option_, lineEdit_, fieldNameTranslated_, QApplication::tr("Must be an IPv4 address")) {}; - virtual bool isValid(){return true;}//todo + IPAddressStringItem(ConfigOption option_, QLineEdit* lineEdit_, QString fieldNameTranslated_, MainWindow* mw) : + BaseFormattedStringItem(option_, lineEdit_, fieldNameTranslated_, QApplication::tr("Must be an IPv4 address"), mw) {} + //virtual bool isValid(bool & alreadyDisplayedIfWrong){return true;}//todo }; class TCPPortStringItem : public UShortStringItem { public: - TCPPortStringItem(ConfigOption option_, QLineEdit* lineEdit_, QString fieldNameTranslated_) : - UShortStringItem(option_, lineEdit_, fieldNameTranslated_) {}; + TCPPortStringItem(ConfigOption option_, QLineEdit* lineEdit_, QString fieldNameTranslated_, MainWindow* mw) : + UShortStringItem(option_, lineEdit_, fieldNameTranslated_,mw) {} }; namespace Ui { @@ -386,6 +410,7 @@ class DelayedSaveManagerImpl; class MainWindow : public QMainWindow { Q_OBJECT private: + std::string currentLocalDestinationB32; std::shared_ptr logStream; DelayedSaveManagerImpl* delayedSaveManagerPtr; DelayedSaveManager::DATA_SERIAL_TYPE dataSerial; @@ -395,7 +420,7 @@ public: void setI2PController(i2p::qt::Controller* controller_); - void highlightWrongInput(QString warningText, QWidget* widgetToFocus); + void highlightWrongInput(QString warningText, WrongInputPageEnum inputPage, QWidget* widgetToFocus); //typedef std::function DefaultValueGetter; @@ -428,7 +453,10 @@ private slots: void runPeerTest(); void enableTransit(); void disableTransit(); + public slots: + void syncLogLevel (int comboBoxIndex); + void showStatus_local_destinations_Page(); void showStatus_leasesets_Page(); void showStatus_tunnels_Page(); @@ -442,6 +470,7 @@ public slots: void showTunnelsPage(); void showRestartPage(); void showQuitPage(); + void showAboutBox(const QString & href); private: StatusPage statusPage; @@ -499,7 +528,7 @@ protected: //LogDestinationComboBoxItem* logOption; FileChooserItem* logFileNameOption; - FileChooserItem* initFileChooser(ConfigOption option, QLineEdit* fileNameLineEdit, QPushButton* fileBrowsePushButton); + FileChooserItem* initFileChooser(ConfigOption option, QLineEdit* fileNameLineEdit, QPushButton* fileBrowsePushButton, bool requireExistingFile, bool readOnly=false); void initFolderChooser(ConfigOption option, QLineEdit* folderLineEdit, QPushButton* folderBrowsePushButton); //void initCombobox(ConfigOption option, QComboBox* comboBox); void initLogDestinationCombobox(ConfigOption option, QComboBox* comboBox); @@ -519,12 +548,12 @@ protected: public slots: /** returns false iff not valid items present and save was aborted */ - bool saveAllConfigs(bool focusOnTunnel, std::string tunnelNameToFocus=""); - void reloadTunnelsConfigAndUI(std::string tunnelNameToFocus); + bool saveAllConfigs(bool reloadAfterSave, FocusEnum focusOn, std::string tunnelNameToFocus="", QWidget* widgetToFocus=nullptr); + void reloadTunnelsConfigAndUI(std::string tunnelNameToFocus, QWidget* widgetToFocus); + void reloadTunnelsConfigAndUI() { reloadTunnelsConfigAndUI("", nullptr); } //focus none - void reloadTunnelsConfigAndUI() { reloadTunnelsConfigAndUI(""); } - void reloadTunnelsConfigAndUI_QString(const QString tunnelNameToFocus); + void reloadTunnelsConfigAndUI_QString(QString tunnelNameToFocus); void addServerTunnelPushButtonReleased(); void addClientTunnelPushButtonReleased(); @@ -629,7 +658,7 @@ private: tunnelConfigs.erase(it); delete tc; } - saveAllConfigs(false); + saveAllConfigs(true, FocusEnum::noFocus); } std::string GenerateNewTunnelName() { @@ -666,7 +695,7 @@ private: sigType, cryptoType); - saveAllConfigs(true, name); + saveAllConfigs(true, FocusEnum::focusOnTunnelName, name); } void CreateDefaultServerTunnel() {//TODO dedup default values with ReadTunnelsConfig() and with ClientContext.cpp::ReadTunnels () @@ -704,7 +733,7 @@ private: cryptoType); - saveAllConfigs(true, name); + saveAllConfigs(true, FocusEnum::focusOnTunnelName, name); } void ReadTunnelsConfig() //TODO deduplicate the code with ClientContext.cpp::ReadTunnels () @@ -747,16 +776,13 @@ private: std::string dest; if (type == I2P_TUNNELS_SECTION_TYPE_CLIENT || type == I2P_TUNNELS_SECTION_TYPE_UDPCLIENT) { dest = section.second.get (I2P_CLIENT_TUNNEL_DESTINATION); - std::cout << "had read tunnel dest: " << dest << std::endl; } int port = section.second.get (I2P_CLIENT_TUNNEL_PORT); - std::cout << "had read tunnel port: " << port << std::endl; // optional params std::string keys = section.second.get (I2P_CLIENT_TUNNEL_KEYS, ""); std::string address = section.second.get (I2P_CLIENT_TUNNEL_ADDRESS, "127.0.0.1"); int cryptoType = section.second.get(I2P_CLIENT_TUNNEL_CRYPTO_TYPE, 0); int destinationPort = section.second.get(I2P_CLIENT_TUNNEL_DESTINATION_PORT, 0); - std::cout << "had read tunnel destinationPort: " << destinationPort << std::endl; i2p::data::SigningKeyType sigType = section.second.get (I2P_CLIENT_TUNNEL_SIGNATURE_TYPE, i2p::data::SIGNING_KEY_TYPE_ECDSA_SHA256_P256); // I2CP std::map options; diff --git a/qt/i2pd_qt/mainwindow.ui b/qt/i2pd_qt/mainwindow.ui index dcdf88bd..8f942b08 100644 --- a/qt/i2pd_qt/mainwindow.ui +++ b/qt/i2pd_qt/mainwindow.ui @@ -7,7 +7,7 @@ 0 0 908 - 554 + 604
@@ -35,13 +35,13 @@ 908 - 550 + 600 908 - 550 + 600 @@ -50,7 +50,7 @@ 10 10 888 - 531 + 596 @@ -58,7 +58,7 @@ QLayout::SetMaximumSize - + QLayout::SetMinimumSize @@ -67,7 +67,7 @@ 0 0 170 - 496 + 596 @@ -172,6 +172,33 @@ + + + + + 9 + + + + Qt::NoContextMenu + + + Show app name, version and build date + + + <html><head/><body><p><a href="about:i2pd_qt"><span style="text-decoration: none; color:#a0a0a0;"><span style="font-weight: 500;">i2pd_qt</span><br/>Version SHORT_VERSION · About...</span></a></p></body></html> + + + Qt::AlignLeading|Qt::AlignLeft|Qt::AlignVCenter + + + 6 + + + 0 + + + @@ -629,7 +656,7 @@ - 713 + 707 713 @@ -648,8 +675,8 @@ 0 0 - 713 - 531 + 707 + 586 @@ -690,8 +717,8 @@ 0 0 - 711 - 531 + 707 + 586 @@ -753,8 +780,8 @@ 0 0 - 711 - 531 + 707 + 586 @@ -798,8 +825,8 @@ 0 0 - 81 - 28 + 693 + 498 @@ -820,8 +847,8 @@ 0 0 - 711 - 531 + 707 + 586 @@ -903,8 +930,8 @@ 0 0 - 711 - 531 + 707 + 586 @@ -958,7 +985,7 @@ 0 0 - 711 + 707 531 diff --git a/qt/i2pd_qt/routercommandswidget.ui b/qt/i2pd_qt/routercommandswidget.ui index c5098e8e..f95db1fd 100644 --- a/qt/i2pd_qt/routercommandswidget.ui +++ b/qt/i2pd_qt/routercommandswidget.ui @@ -6,7 +6,7 @@ 0 0 - 711 + 707 300 @@ -24,7 +24,7 @@ 0 0 - 711 + 707 301