diff --git a/src/lib/client/CMakeLists.txt b/src/lib/client/CMakeLists.txt index 18836e4c0..f9f81fb48 100644 --- a/src/lib/client/CMakeLists.txt +++ b/src/lib/client/CMakeLists.txt @@ -6,8 +6,6 @@ add_library(client STATIC Client.cpp Client.h - HelloBack.cpp - HelloBack.h ServerProxy.cpp ServerProxy.h ) diff --git a/src/lib/client/Client.cpp b/src/lib/client/Client.cpp index 736cd8c02..b12f2a3cd 100644 --- a/src/lib/client/Client.cpp +++ b/src/lib/client/Client.cpp @@ -1,7 +1,7 @@ /* * Deskflow -- mouse and keyboard sharing utility * SPDX-FileCopyrightText: (C) 2025 Deskflow Developers - * SPDX-FileCopyrightText: (C) 2012 - 2016 Symless Ltd. + * SPDX-FileCopyrightText: (C) 2012 - 2016, 2026 Symless Ltd. * SPDX-FileCopyrightText: (C) 2002 Chris Schoeneman * SPDX-License-Identifier: GPL-2.0-only WITH LicenseRef-OpenSSL-Exception */ @@ -14,9 +14,9 @@ #include "client/ServerProxy.h" #include "common/Settings.h" #include "deskflow/Clipboard.h" -#include "deskflow/DeskflowException.h" #include "deskflow/IPlatformScreen.h" #include "deskflow/PacketStreamFilter.h" +#include "deskflow/ProtocolTypes.h" #include "deskflow/ProtocolUtil.h" #include "deskflow/Screen.h" #include "deskflow/StreamChunker.h" @@ -27,9 +27,6 @@ #include #include -#include - -using namespace deskflow::client; // // Client @@ -52,19 +49,6 @@ Client::Client( // register suspend/resume event handlers m_events->addHandler(EventTypes::ScreenSuspend, getEventTarget(), [this](const auto &) { handleSuspend(); }); m_events->addHandler(EventTypes::ScreenResume, getEventTarget(), [this](const auto &) { handleResume(); }); - - m_pHelloBack = std::make_unique(std::make_shared( - [this]() { - sendConnectionFailedEvent("got invalid hello message from server"); - cleanupTimer(); - cleanupConnection(); - }, - [this](int major, int minor) { - sendConnectionFailedEvent(IncompatibleClientException(major, minor).what()); - cleanupTimer(); - cleanupConnection(); - } - )); } Client::~Client() @@ -584,7 +568,32 @@ void Client::handleClipboardGrabbed(const Event &event) void Client::handleHello() { - m_pHelloBack->handleHello(m_stream, m_name); + int16_t serverMajor; + int16_t serverMinor; + + // as luck would have it, both "Synergy" and "Barrier" are 7 chars, + // so we eat 7 chars and then test for either protocol name. + // we cannot re-use `readf` to check for various hello messages, + // as `readf` eats bytes (advances the stream position reference). + std::string protocolName; + ProtocolUtil::readf(m_stream, kMsgHello, &protocolName, &serverMajor, &serverMinor); + + if (protocolName != kSynergyProtocolName && protocolName != kBarrierProtocolName) { + LOG_WARN("hello back received with protocol: '%s'", protocolName.c_str()); + sendConnectionFailedEvent("got invalid hello message from server"); + cleanupTimer(); + cleanupConnection(); + return; + } + + LOG_DEBUG( + "saying hello back with version %s %d.%d", protocolName.c_str(), kProtocolMajorVersion, kProtocolMinorVersion + ); + + // dynamically build write format for hello back since `ProtocolUtil::writef` + // doesn't support formatting fixed length strings yet. + std::string helloBackMessage = protocolName + kMsgHelloBackArgs; + ProtocolUtil::writef(m_stream, helloBackMessage.c_str(), kProtocolMajorVersion, kProtocolMinorVersion, &m_name); // now connected but waiting to complete handshake setupScreen(); diff --git a/src/lib/client/Client.h b/src/lib/client/Client.h index b92ed77b1..42430d0d1 100644 --- a/src/lib/client/Client.h +++ b/src/lib/client/Client.h @@ -1,7 +1,7 @@ /* * Deskflow -- mouse and keyboard sharing utility * SPDX-FileCopyrightText: (C) 2025 Deskflow Developers - * SPDX-FileCopyrightText: (C) 2012 - 2016 Symless Ltd. + * SPDX-FileCopyrightText: (C) 2012 - 2016, 2026 Symless Ltd. * SPDX-FileCopyrightText: (C) 2002 Chris Schoeneman * SPDX-License-Identifier: GPL-2.0-only WITH LicenseRef-OpenSSL-Exception */ @@ -10,13 +10,11 @@ #include "deskflow/IClient.h" -#include "HelloBack.h" #include "base/EventTypes.h" #include "deskflow/IClipboard.h" #include "net/NetworkAddress.h" #include -#include class Event; class EventQueueTimer; @@ -203,5 +201,4 @@ private: bool m_enableClipboard = true; size_t m_maximumClipboardSize = INT_MAX; size_t m_resolvedAddressesCount = 0; - std::unique_ptr m_pHelloBack; }; diff --git a/src/lib/client/HelloBack.cpp b/src/lib/client/HelloBack.cpp deleted file mode 100644 index 210b6decb..000000000 --- a/src/lib/client/HelloBack.cpp +++ /dev/null @@ -1,99 +0,0 @@ -/* - * Deskflow -- mouse and keyboard sharing utility - * SPDX-FileCopyrightText: (C) 2024 Symless Ltd. - * SPDX-License-Identifier: GPL-2.0-only WITH LicenseRef-OpenSSL-Exception - */ - -#include "HelloBack.h" - -#include "base/Log.h" -#include "deskflow/ProtocolTypes.h" -#include "deskflow/ProtocolUtil.h" - -#include -#include - -namespace deskflow::client { - -// -// HelloBack::Deps -// - -void HelloBack::Deps::invalidHello() -{ - m_invalidHello(); -} - -void HelloBack::Deps::incompatible(int major, int minor) -{ - m_incompatible(major, minor); -} - -// -// HelloBack -// - -void HelloBack::handleHello(deskflow::IStream *stream, const std::string &clientName) const -{ - int16_t serverMajor; - int16_t serverMinor; - - // as luck would have it, both "Synergy" and "Barrier" are 7 chars, - // so we eat 7 chars and then test for either protocol name. - // we cannot re-use `readf` to check for various hello messages, - // as `readf` eats bytes (advances the stream position reference). - std::string protocolName; - ProtocolUtil::readf(stream, kMsgHello, &protocolName, &serverMajor, &serverMinor); - - if (protocolName != kSynergyProtocolName && protocolName != kBarrierProtocolName) { - m_deps->invalidHello(); - return; - } - - // check versions - LOG_DEBUG("got hello from %s, protocol v%d.%d", protocolName.c_str(), serverMajor, serverMinor); - - const auto helloBackMajor = m_majorVersion; - auto helloBackMinor = m_minorVersion; - - if (shouldDowngrade(serverMajor, serverMinor)) { - LOG_NOTE("downgrading to %d.%d protocol for server", serverMajor, serverMinor); - helloBackMinor = serverMinor; - } else if (serverMajor < m_majorVersion || (serverMajor == m_majorVersion && serverMinor < m_minorVersion)) { - m_deps->incompatible(serverMajor, serverMinor); - return; - } - - // say hello back with same protocol name and version - LOG_DEBUG("saying hello back with version %s %d.%d", protocolName.c_str(), helloBackMajor, helloBackMinor); - - // dynamically build write format for hello back since `ProtocolUtil::writef` - // doesn't support formatting fixed length strings yet. - std::string helloBackMessage = protocolName + kMsgHelloBackArgs; - ProtocolUtil::writef(stream, helloBackMessage.c_str(), helloBackMajor, helloBackMinor, &clientName); -} - -bool HelloBack::shouldDowngrade(int major, int minor) const -{ - const std::map> map{ - // 1.6 is compatible with 1.7 and 1.8 - {6, {7, 8}}, - - // 1.7 is compatible with 1.8 - {7, {8}}, - }; - - if (major == m_majorVersion) { - auto versions = map.find(minor); - if (versions != map.end()) { - auto compatibleVersions = versions->second; - if (compatibleVersions.contains(m_minorVersion)) { - return true; - } - } - } - - return false; -} - -} // namespace deskflow::client diff --git a/src/lib/client/HelloBack.h b/src/lib/client/HelloBack.h deleted file mode 100644 index 5897e14dd..000000000 --- a/src/lib/client/HelloBack.h +++ /dev/null @@ -1,71 +0,0 @@ -/* - * Deskflow -- mouse and keyboard sharing utility - * SPDX-FileCopyrightText: (C) 2024 Symless Ltd. - * SPDX-License-Identifier: GPL-2.0-only WITH LicenseRef-OpenSSL-Exception - */ - -#pragma once - -#include "deskflow/ProtocolTypes.h" -#include "io/IStream.h" - -#include -#include -#include //IWYU - -namespace deskflow::client { - -class HelloBack -{ -public: - struct Deps - { - Deps() = default; - explicit Deps(std::function invalidHello, std::function incompatible) - : m_invalidHello(std::move(invalidHello)), - m_incompatible(std::move(incompatible)) - { - // do nothing - } - virtual ~Deps() = default; - - /** - * @brief Call when invalid hello message received from server. - */ - virtual void invalidHello(); - - /** - * @brief Call when the client is incompatible with the server. - */ - virtual void incompatible(int major, int minor); - - private: - std::function m_invalidHello; - std::function m_incompatible; - }; - - explicit HelloBack( - std::shared_ptr deps, const int16_t majorVersion = kProtocolMajorVersion, - const int16_t minorVersion = kProtocolMinorVersion - ) - : m_deps(deps), - m_majorVersion(majorVersion), - m_minorVersion(minorVersion) - { - // do nothing - } - - /** - * @brief Handle hello message from server and reply with hello back. - */ - void handleHello(deskflow::IStream *stream, const std::string &clientName) const; - -private: - bool shouldDowngrade(int major, int minor) const; - - std::shared_ptr m_deps; - int16_t m_majorVersion; - int16_t m_minorVersion; -}; - -} // namespace deskflow::client diff --git a/src/unittests/legacytests/legacytests/client/HelloBackTests.cpp b/src/unittests/legacytests/legacytests/client/HelloBackTests.cpp deleted file mode 100644 index 4cd439da0..000000000 --- a/src/unittests/legacytests/legacytests/client/HelloBackTests.cpp +++ /dev/null @@ -1,227 +0,0 @@ -/* - * Deskflow -- mouse and keyboard sharing utility - * SPDX-FileCopyrightText: (C) 2024 Symless Ltd. - * SPDX-License-Identifier: GPL-2.0-only WITH LicenseRef-OpenSSL-Exception - */ - -#include "client/HelloBack.h" - -#include "unittests/legacytests/mock/io/MockStream.h" - -#include -#include -#include -#include -#include -#include - -using HelloBack = deskflow::client::HelloBack; -using namespace testing; - -namespace { - -class MockDeps : public HelloBack::Deps -{ -public: - ~MockDeps() override = default; - MOCK_METHOD(void, invalidHello, (), (override)); - MOCK_METHOD(void, incompatible, (int major, int minor), (override)); -}; - -void intTo2ByteBuf(int16_t value, std::array &buf) -{ - buf[0] = static_cast((value >> 8) & 0xFF); // MSB - buf[1] = static_cast(value & 0xFF); // LSB -} - -void intTo4ByteBuf(int32_t value, std::array &buf) -{ - buf[0] = static_cast((value >> 24) & 0xFF); // MSB - buf[1] = static_cast((value >> 16) & 0xFF); - buf[2] = static_cast((value >> 8) & 0xFF); - buf[3] = static_cast(value & 0xFF); // LSB -} - -std::string printAsHex(const char *buffer, size_t size) -{ - std::ostringstream hexStream; - for (size_t i = 0; i < size; ++i) { - hexStream << std::hex << std::setw(2) << std::setfill('0') - << static_cast(static_cast(buffer[i])) << " "; - } - return hexStream.str(); -} - -void setupMockHelloRead( - MockStream &stream, const std::string &protocolName, const int16_t majorVersion, const int16_t minorVersion -) -{ - - std::array majorBuf; - std::array minorBuf; - intTo2ByteBuf(majorVersion, majorBuf); - intTo2ByteBuf(minorVersion, minorBuf); - - EXPECT_CALL(stream, read(_, _)) - .WillOnce(DoAll( - WithArg<0>([protocolName](void *vbuffer) { - auto buffer = static_cast(vbuffer); - std::copy(protocolName.begin(), protocolName.end(), buffer); - }), - Return(7) - )) - .WillOnce(DoAll( - WithArg<0>([majorBuf](void *vbuffer) { - auto buffer = static_cast(vbuffer); - std::memcpy(buffer, majorBuf.data(), majorBuf.size()); - }), - Return(2) - )) - .WillOnce(DoAll( - WithArg<0>([minorBuf](void *vbuffer) { - auto buffer = static_cast(vbuffer); - std::memcpy(buffer, minorBuf.data(), minorBuf.size()); - }), - Return(2) - )); -} - -void setupMockHelloBackWrite( - MockStream &stream, const std::string &protocolName, const int16_t majorVersion, const int16_t minorVersion, - const std::string &name -) -{ - - std::array majorBuf; - std::array minorBuf; - std::array nameLenBuf; - intTo2ByteBuf(majorVersion, majorBuf); - intTo2ByteBuf(minorVersion, minorBuf); - intTo4ByteBuf(static_cast(name.size()), nameLenBuf); - - const auto versionIntSize = 4; - const auto clientNameIntSize = 4; - const uint32_t helloBackSize = - static_cast(protocolName.size() + versionIntSize + clientNameIntSize + name.size()); - - std::vector expect; - expect.reserve(helloBackSize); - expect.insert(expect.end(), protocolName.begin(), protocolName.end()); - expect.insert(expect.end(), majorBuf.begin(), majorBuf.end()); - expect.insert(expect.end(), minorBuf.begin(), minorBuf.end()); - expect.insert(expect.end(), nameLenBuf.begin(), nameLenBuf.end()); - expect.insert(expect.end(), name.begin(), name.end()); - - EXPECT_CALL(stream, write(_, helloBackSize)).WillOnce(WithArg<0>([expect, helloBackSize](const void *vbuffer) { - const auto buffer = static_cast(vbuffer); - - EXPECT_TRUE(std::memcmp(expect.data(), buffer, helloBackSize) == 0) - << "Buffer mismatch\n" - << "Expected: " << printAsHex(expect.data(), helloBackSize) << "\n" - << "Actual: " << printAsHex(buffer, helloBackSize) << "\n"; - })); -} - -} // namespace - -TEST(HelloBackTests, handleHello_nastyProtocol_invalidHello) -{ - auto deps = std::make_shared>(); - HelloBack helloBack(deps); - NiceMock stream; - const std::string clientName = "stub"; - - setupMockHelloRead(stream, "ShareMouse", 0, 0); - - EXPECT_CALL(*deps, invalidHello()).Times(1); - - helloBack.handleHello(&stream, clientName); -} - -TEST(HelloBackTests, handleHello_synergyProtocolCurrent_validMessage) -{ - auto deps = std::make_shared>(); - HelloBack helloBack(deps, 1, 2); - NiceMock stream; - const std::string clientName = "stub"; - - setupMockHelloRead(stream, "Synergy", 1, 2); - - EXPECT_CALL(*deps, incompatible(_, _)).Times(0); - EXPECT_CALL(*deps, invalidHello()).Times(0); - - helloBack.handleHello(&stream, clientName); -} - -TEST(HelloBackTests, handleHello_barrierProtocolCurrent_validMessage) -{ - auto deps = std::make_shared>(); - HelloBack helloBack(deps, 1, 2); - NiceMock stream; - const std::string clientName = "stub"; - - setupMockHelloRead(stream, "Barrier", 1, 2); - - EXPECT_CALL(*deps, incompatible(_, _)).Times(0); - EXPECT_CALL(*deps, invalidHello()).Times(0); - - helloBack.handleHello(&stream, clientName); -} - -TEST(HelloBackTests, handleHello_synergyProtocolOlder_validMessage) -{ - auto deps = std::make_shared>(); - HelloBack helloBack(deps, 1, 2); - NiceMock stream; - const std::string clientName = "stub"; - - setupMockHelloRead(stream, "Synergy", 1, 1); - - EXPECT_CALL(*deps, incompatible(1, 1)).Times(1); - - helloBack.handleHello(&stream, clientName); -} - -TEST(HelloBackTests, handleHello_synergyProtocolCurrent_wroteHelloBack) -{ - auto deps = std::make_shared>(); - HelloBack helloBack(deps, 1, 2); - NiceMock stream; - const std::string clientName = "test client"; - - setupMockHelloRead(stream, "Synergy", 1, 2); - - setupMockHelloBackWrite(stream, "Synergy", 1, 2, "test client"); - - helloBack.handleHello(&stream, clientName); -} - -TEST(HelloBackTests, handleHello_barrierProtocolCurrent_wroteHelloBack) -{ - auto deps = std::make_shared>(); - HelloBack helloBack(deps, 1, 2); - NiceMock stream; - const std::string clientName = "test client"; - - setupMockHelloRead(stream, "Barrier", 1, 2); - - setupMockHelloBackWrite(stream, "Barrier", 1, 2, "test client"); - - helloBack.handleHello(&stream, clientName); -} - -// If the client is protocol version 1.8 and the server is 1.6, the client -// should downgrade and respond with the server version. -TEST(HelloBackTests, handleHello_synergyProtocolCompat_wroteHelloBack) -{ - auto deps = std::make_shared>(); - HelloBack helloBack(deps, 1, 8); - NiceMock stream; - const std::string clientName = "test client"; - - setupMockHelloRead(stream, "Synergy", 1, 6); - - setupMockHelloBackWrite(stream, "Synergy", 1, 6, "test client"); - - helloBack.handleHello(&stream, clientName); -}