/* * Deskflow -- mouse and keyboard sharing utility * SPDX-FileCopyrightText: (C) 2026 Deskflow Developers * SPDX-License-Identifier: GPL-2.0-only WITH LicenseRef-OpenSSL-Exception */ #include "ServerProxyTests.h" #include "base/Event.h" #include "base/IEventQueue.h" #include "client/Client.h" #include "client/ServerProxy.h" #include "deskflow/AppUtil.h" #include "deskflow/ProtocolTypes.h" #include "io/IStream.h" #include #include #include #include #include #include #include #include #include namespace { class TestAppUtil : public AppUtil { public: int run() override { return 0; } std::vector getKeyboardLayoutList() override { return {"en"}; } std::string getCurrentLanguageCode() override { return "en"; } }; class FakeStream : public deskflow::IStream { public: void push(const std::string &bytes) { m_chunks.push_back(bytes); } void close() override { m_chunks.clear(); m_inputShutdown = true; } uint32_t read(void *buffer, uint32_t size) override { if (m_inputShutdown || m_chunks.empty() || size == 0) { return 0; } auto &front = m_chunks.front(); const size_t bytesToRead = std::min(static_cast(size), front.size()); if (buffer != nullptr) { std::memcpy(buffer, front.data(), bytesToRead); } front.erase(0, bytesToRead); if (front.empty()) { m_chunks.pop_front(); } return static_cast(bytesToRead); } void write(const void *, uint32_t) override { } void flush() override { } void shutdownInput() override { close(); } void shutdownOutput() override { } void *getEventTarget() const override { return const_cast(this); } bool isReady() const override { return !m_inputShutdown && !m_chunks.empty(); } uint32_t getSize() const override { size_t total = 0; for (const auto &chunk : m_chunks) { total += chunk.size(); } return static_cast(std::min(total, UINT32_MAX)); } private: std::deque m_chunks; bool m_inputShutdown = false; }; class RecordingEventQueue : public IEventQueue { public: ~RecordingEventQueue() override { for (const auto &event : m_addedEvents) { Event::deleteData(event); } } int loop() override { return 0; } void adoptBuffer(IEventQueueBuffer *) override { } bool getEvent(Event &, double = -1.0) override { return false; } bool dispatchEvent(const Event &event) override { const auto handler = m_handlers.find(HandlerKey{event.getType(), event.getTarget()}); if (handler == m_handlers.end()) { return false; } const auto callback = handler->second; callback(event); return true; } void addEvent(Event &&event) override { m_addedEvents.emplace_back(std::move(event)); } EventQueueTimer *newTimer(double, void *) override { return timer(); } EventQueueTimer *newOneShotTimer(double, void *) override { return timer(); } void deleteTimer(EventQueueTimer *) override { } void addHandler(EventTypes type, void *target, const EventHandler &handler) override { m_handlers[HandlerKey{type, target}] = handler; } void removeHandler(EventTypes type, void *target) override { m_handlers.erase(HandlerKey{type, target}); } void removeHandlers(void *target) override { for (auto it = m_handlers.begin(); it != m_handlers.end();) { if (it->first.target == target) { it = m_handlers.erase(it); } else { ++it; } } } void waitForReady() const override { } void *getSystemTarget() override { return this; } EventQueueTimer *timer() { return reinterpret_cast(&m_timerStorage); } const std::vector &addedEvents() const { return m_addedEvents; } private: struct HandlerKey { EventTypes type; void *target; bool operator<(const HandlerKey &other) const { if (type != other.type) { return static_cast(type) < static_cast(other.type); } return std::less{}(target, other.target); } }; int m_timerStorage = 0; std::map m_handlers; std::vector m_addedEvents; }; class TestServerProxy : public ServerProxy { public: using ServerProxy::ServerProxy; bool parseHandshakeMessageReturnsDisconnect(const uint8_t *code) { return parseHandshakeMessage(code) == ConnectionResult::Disconnect; } }; Client *undereferenceableClient() { // These paths must queue cleanup without calling through to Client. return reinterpret_cast(0x1); } TestAppUtil &testAppUtil() { static TestAppUtil util; return util; } const Client::DisconnectRequest *disconnectRequest(const RecordingEventQueue &events) { if (events.addedEvents().size() != 1) { return nullptr; } return static_cast(events.addedEvents().front().getDataObject()); } } // namespace void ServerProxyTests::initTestCase() { (void)testAppUtil(); m_log.setFilter(LogLevel::Level::Debug); } void ServerProxyTests::handleKeepAliveAlarm_timeout_queuesDisconnectRequest() { RecordingEventQueue events; FakeStream stream; ServerProxy proxy(undereferenceableClient(), &stream, &events); QVERIFY(events.dispatchEvent(Event(EventTypes::Timer, events.timer()))); QCOMPARE(events.addedEvents().size(), static_cast(1)); const auto &event = events.addedEvents().front(); QVERIFY(event.getType() == EventTypes::ClientDisconnectRequested); QCOMPARE(event.getTarget(), stream.getEventTarget()); const auto *request = disconnectRequest(events); QVERIFY(request != nullptr); QVERIFY(request->kind() == Client::DisconnectRequest::Kind::Disconnect); QCOMPARE(QString::fromUtf8(request->message()), QStringLiteral("server is not responding")); } void ServerProxyTests::handleData_incompleteMessage_queuesDisconnectRequest() { RecordingEventQueue events; FakeStream stream; stream.push("DF"); ServerProxy proxy(undereferenceableClient(), &stream, &events); QVERIFY(events.dispatchEvent(Event(EventTypes::StreamInputReady, stream.getEventTarget()))); QCOMPARE(events.addedEvents().size(), static_cast(1)); const auto &event = events.addedEvents().front(); QVERIFY(event.getType() == EventTypes::ClientDisconnectRequested); QCOMPARE(event.getTarget(), stream.getEventTarget()); const auto *request = disconnectRequest(events); QVERIFY(request != nullptr); QVERIFY(request->kind() == Client::DisconnectRequest::Kind::Disconnect); QCOMPARE(QString::fromUtf8(request->message()), QStringLiteral("incomplete message from server")); } void ServerProxyTests::parseHandshakeMessage_protocolError_queuesRefusalRequest() { RecordingEventQueue events; FakeStream stream; TestServerProxy proxy(undereferenceableClient(), &stream, &events); QVERIFY(proxy.parseHandshakeMessageReturnsDisconnect(reinterpret_cast(kMsgEBad))); QCOMPARE(events.addedEvents().size(), static_cast(1)); const auto *request = disconnectRequest(events); QVERIFY(request != nullptr); QVERIFY(request->kind() == Client::DisconnectRequest::Kind::Refuse); QVERIFY(request->refusalReason() == deskflow::core::ConnectionRefusal::ProtocolError); QCOMPARE(QString::fromUtf8(request->message()), QStringLiteral("server reported a protocol error")); } QTEST_MAIN(ServerProxyTests)