#include "rdp_session_backend.h" #include #include #include #include class TestRdpSessionBackend : public QObject { Q_OBJECT private slots: void normalizedRdpSecurityModeRecognizesKnownValues(); void normalizedRdpSecurityModeFallsBackToNegotiate(); void normalizedRdpPerformanceProfileRecognizesKnownValues(); void normalizedRdpPerformanceProfileFallsBackToBalanced(); void nearestFreeRdpScaleValueMapsToLegalValues(); void sanitizeDesktopWidthClampsToLegalRange(); void sanitizeDesktopHeightClampsToLegalRange(); void scancodeFromNativeScanCodeHandlesZero(); #if defined(Q_OS_LINUX) void scancodeFromNativeScanCodeDelegatesToX11TableOnLinux(); void scancodeFromNativeScanCodeDoesNotTreatX11KeycodeAsPcAtScancode(); #endif void scancodeForQtKeyMapsDirectKeys(); void scancodeForQtKeyRespectsKeypadModifier(); void scancodeForQtKeyDisambiguatesLeftRightModifiers(); void scancodeForQtKeyReturnsUnknownForUnhandledKey(); void mapRdpErrorRecognizesAuthFailureCodes(); void mapRdpErrorRecognizesAccountStateCodes(); void mapRdpErrorRecognizesNetworkCodes(); void mapRdpErrorFallsBackForUnknownCode(); void isExpectedDisconnectCodeRecognizesBenignCodes(); void isExpectedDisconnectCodeRejectsAuthFailure(); void isExpectedConnectAbortCodeRecognizesCancellation(); void isExpectedConnectAbortCodeRejectsAuthFailure(); void disconnectMessageForCodeRecognizesKnownCodes(); void disconnectMessageForCodeFallsBackForUnknownCode(); void rdpErrorRawIncludesHexCode(); }; void TestRdpSessionBackend::normalizedRdpSecurityModeRecognizesKnownValues() { QCOMPARE(RdpSessionBackend::normalizedRdpSecurityMode(QStringLiteral("nla")), QStringLiteral("NLA")); QCOMPARE(RdpSessionBackend::normalizedRdpSecurityMode(QStringLiteral(" TLS ")), QStringLiteral("TLS")); QCOMPARE(RdpSessionBackend::normalizedRdpSecurityMode(QStringLiteral("Rdp")), QStringLiteral("RDP")); } void TestRdpSessionBackend::normalizedRdpSecurityModeFallsBackToNegotiate() { QCOMPARE(RdpSessionBackend::normalizedRdpSecurityMode(QStringLiteral("bogus")), QStringLiteral("Negotiate")); QCOMPARE(RdpSessionBackend::normalizedRdpSecurityMode(QString()), QStringLiteral("Negotiate")); } void TestRdpSessionBackend::normalizedRdpPerformanceProfileRecognizesKnownValues() { QCOMPARE(RdpSessionBackend::normalizedRdpPerformanceProfile(QStringLiteral("best quality")), QStringLiteral("Best Quality")); QCOMPARE(RdpSessionBackend::normalizedRdpPerformanceProfile(QStringLiteral(" Best Performance ")), QStringLiteral("Best Performance")); QCOMPARE(RdpSessionBackend::normalizedRdpPerformanceProfile(QStringLiteral("auto detect")), QStringLiteral("Auto Detect")); } void TestRdpSessionBackend::normalizedRdpPerformanceProfileFallsBackToBalanced() { QCOMPARE(RdpSessionBackend::normalizedRdpPerformanceProfile(QStringLiteral("bogus")), QStringLiteral("Balanced")); } void TestRdpSessionBackend::nearestFreeRdpScaleValueMapsToLegalValues() { // MS-RDPEDISP legally permits only {100, 140, 180} -- anything else is // silently ignored by the server. QCOMPARE(RdpSessionBackend::nearestFreeRdpScaleValue(1.0), quint32(100)); QCOMPARE(RdpSessionBackend::nearestFreeRdpScaleValue(1.2), quint32(100)); QCOMPARE(RdpSessionBackend::nearestFreeRdpScaleValue(1.25), quint32(140)); QCOMPARE(RdpSessionBackend::nearestFreeRdpScaleValue(1.6), quint32(140)); QCOMPARE(RdpSessionBackend::nearestFreeRdpScaleValue(2.0), quint32(180)); QCOMPARE(RdpSessionBackend::nearestFreeRdpScaleValue(3.0), quint32(180)); } void TestRdpSessionBackend::sanitizeDesktopWidthClampsToLegalRange() { QCOMPARE(RdpSessionBackend::sanitizeDesktopWidth(0), 1280); QCOMPARE(RdpSessionBackend::sanitizeDesktopWidth(-100), 1280); QCOMPARE(RdpSessionBackend::sanitizeDesktopWidth(100), 640); QCOMPARE(RdpSessionBackend::sanitizeDesktopWidth(1920), 1920); QCOMPARE(RdpSessionBackend::sanitizeDesktopWidth(99999), 8192); } void TestRdpSessionBackend::sanitizeDesktopHeightClampsToLegalRange() { QCOMPARE(RdpSessionBackend::sanitizeDesktopHeight(0), 720); QCOMPARE(RdpSessionBackend::sanitizeDesktopHeight(-100), 720); QCOMPARE(RdpSessionBackend::sanitizeDesktopHeight(100), 360); QCOMPARE(RdpSessionBackend::sanitizeDesktopHeight(1080), 1080); QCOMPARE(RdpSessionBackend::sanitizeDesktopHeight(99999), 4320); } void TestRdpSessionBackend::scancodeFromNativeScanCodeHandlesZero() { QCOMPARE(RdpSessionBackend::scancodeFromNativeScanCode(0), quint32(RDP_SCANCODE_UNKNOWN)); } #if defined(Q_OS_LINUX) void TestRdpSessionBackend::scancodeFromNativeScanCodeDelegatesToX11TableOnLinux() { // Our wrapper must be a faithful passthrough to FreeRDP's own // authoritative X11-keycode table, not a reimplementation of it. const quint32 apostropheKeycode = 0x30; #if defined(__GNUC__) || defined(__clang__) #pragma GCC diagnostic push #pragma GCC diagnostic ignored "-Wdeprecated-declarations" #endif const quint32 expected = static_cast(freerdp_keyboard_get_rdp_scancode_from_x11_keycode(apostropheKeycode)); #if defined(__GNUC__) || defined(__clang__) #pragma GCC diagnostic pop #endif QCOMPARE(RdpSessionBackend::scancodeFromNativeScanCode(apostropheKeycode), expected); } void TestRdpSessionBackend::scancodeFromNativeScanCodeDoesNotTreatX11KeycodeAsPcAtScancode() { // Regression guard for the historical bug this table replaced: X11 // keycode 0x30 (apostrophe/quote) must NOT resolve to whatever a naive // "treat the X11 keycode as a PC/AT set-1 scancode" interpretation // would give (PC/AT 0x30 is the B key). const quint32 apostropheKeycode = 0x30; const quint32 naivePcAtInterpretation = MAKE_RDP_SCANCODE(apostropheKeycode, FALSE); QVERIFY(RdpSessionBackend::scancodeFromNativeScanCode(apostropheKeycode) != naivePcAtInterpretation); } #endif void TestRdpSessionBackend::scancodeForQtKeyMapsDirectKeys() { QCOMPARE(RdpSessionBackend::scancodeForQtKey(Qt::Key_Escape, Qt::NoModifier, 0), quint32(RDP_SCANCODE_ESCAPE)); QCOMPARE(RdpSessionBackend::scancodeForQtKey(Qt::Key_A, Qt::NoModifier, 0), quint32(RDP_SCANCODE_KEY_A)); QCOMPARE(RdpSessionBackend::scancodeForQtKey(Qt::Key_F1, Qt::NoModifier, 0), quint32(RDP_SCANCODE_F1)); QCOMPARE(RdpSessionBackend::scancodeForQtKey(Qt::Key_Space, Qt::NoModifier, 0), quint32(RDP_SCANCODE_SPACE)); } void TestRdpSessionBackend::scancodeForQtKeyRespectsKeypadModifier() { QCOMPARE(RdpSessionBackend::scancodeForQtKey(Qt::Key_Insert, Qt::NoModifier, 0), quint32(RDP_SCANCODE_INSERT)); QCOMPARE(RdpSessionBackend::scancodeForQtKey(Qt::Key_Insert, Qt::KeypadModifier, 0), quint32(RDP_SCANCODE_NUMPAD0)); QCOMPARE(RdpSessionBackend::scancodeForQtKey(Qt::Key_Delete, Qt::NoModifier, 0), quint32(RDP_SCANCODE_DELETE)); QCOMPARE(RdpSessionBackend::scancodeForQtKey(Qt::Key_Delete, Qt::KeypadModifier, 0), quint32(RDP_SCANCODE_DECIMAL)); } void TestRdpSessionBackend::scancodeForQtKeyDisambiguatesLeftRightModifiers() { // With no reliable native scancode (0 -> RDP_SCANCODE_UNKNOWN, which // matches neither side), both Shift and Control must default to their // left variant rather than picking arbitrarily. QCOMPARE(RdpSessionBackend::scancodeForQtKey(Qt::Key_Shift, Qt::NoModifier, 0), quint32(RDP_SCANCODE_LSHIFT)); QCOMPARE(RdpSessionBackend::scancodeForQtKey(Qt::Key_Control, Qt::NoModifier, 0), quint32(RDP_SCANCODE_LCONTROL)); } void TestRdpSessionBackend::scancodeForQtKeyReturnsUnknownForUnhandledKey() { QCOMPARE(RdpSessionBackend::scancodeForQtKey(Qt::Key_MediaPlay, Qt::NoModifier, 0), quint32(RDP_SCANCODE_UNKNOWN)); } void TestRdpSessionBackend::mapRdpErrorRecognizesAuthFailureCodes() { const QString expected = QStringLiteral("Authentication failed. Check username and password."); QCOMPARE(RdpSessionBackend::mapRdpError(FREERDP_ERROR_CONNECT_LOGON_FAILURE), expected); QCOMPARE(RdpSessionBackend::mapRdpError(FREERDP_ERROR_CONNECT_WRONG_PASSWORD), expected); QCOMPARE(RdpSessionBackend::mapRdpError(FREERDP_ERROR_CONNECT_ACCESS_DENIED), expected); } void TestRdpSessionBackend::mapRdpErrorRecognizesAccountStateCodes() { QCOMPARE(RdpSessionBackend::mapRdpError(FREERDP_ERROR_CONNECT_ACCOUNT_DISABLED), QStringLiteral("Account is disabled.")); QCOMPARE(RdpSessionBackend::mapRdpError(FREERDP_ERROR_CONNECT_ACCOUNT_LOCKED_OUT), QStringLiteral("Account is locked out.")); QCOMPARE(RdpSessionBackend::mapRdpError(FREERDP_ERROR_CONNECT_ACCOUNT_EXPIRED), QStringLiteral("Account has expired.")); QCOMPARE(RdpSessionBackend::mapRdpError(FREERDP_ERROR_CONNECT_PASSWORD_EXPIRED), QStringLiteral("Password has expired.")); } void TestRdpSessionBackend::mapRdpErrorRecognizesNetworkCodes() { QCOMPARE(RdpSessionBackend::mapRdpError(FREERDP_ERROR_DNS_NAME_NOT_FOUND), QStringLiteral("Host could not be resolved.")); QCOMPARE(RdpSessionBackend::mapRdpError(FREERDP_ERROR_CONNECT_TRANSPORT_FAILED), QStringLiteral("Network transport failed while connecting.")); QCOMPARE(RdpSessionBackend::mapRdpError(FREERDP_ERROR_SECURITY_NEGO_CONNECT_FAILED), QStringLiteral("RDP security negotiation failed. Try a different RDP security mode.")); } void TestRdpSessionBackend::mapRdpErrorFallsBackForUnknownCode() { // Not a code mapRdpError special-cases; must still return something // non-empty rather than crashing or returning an empty string. const QString result = RdpSessionBackend::mapRdpError(0x7FFFFFFF); QVERIFY(!result.isEmpty()); } void TestRdpSessionBackend::isExpectedDisconnectCodeRecognizesBenignCodes() { QVERIFY(RdpSessionBackend::isExpectedDisconnectCode(FREERDP_ERROR_SUCCESS)); QVERIFY(RdpSessionBackend::isExpectedDisconnectCode(FREERDP_ERROR_NONE)); QVERIFY(RdpSessionBackend::isExpectedDisconnectCode(FREERDP_ERROR_LOGOFF_BY_USER)); QVERIFY(RdpSessionBackend::isExpectedDisconnectCode(FREERDP_ERROR_IDLE_TIMEOUT)); } void TestRdpSessionBackend::isExpectedDisconnectCodeRejectsAuthFailure() { // An authentication failure must be treated as a real error, never as // an expected/benign disconnect -- otherwise the user would see no // error message at all for a failed login. QVERIFY(!RdpSessionBackend::isExpectedDisconnectCode(FREERDP_ERROR_CONNECT_LOGON_FAILURE)); } void TestRdpSessionBackend::isExpectedConnectAbortCodeRecognizesCancellation() { QVERIFY(RdpSessionBackend::isExpectedConnectAbortCode(FREERDP_ERROR_CONNECT_CANCELLED)); QVERIFY(RdpSessionBackend::isExpectedConnectAbortCode(FREERDP_ERROR_SUCCESS)); } void TestRdpSessionBackend::isExpectedConnectAbortCodeRejectsAuthFailure() { QVERIFY(!RdpSessionBackend::isExpectedConnectAbortCode(FREERDP_ERROR_CONNECT_LOGON_FAILURE)); } void TestRdpSessionBackend::disconnectMessageForCodeRecognizesKnownCodes() { QCOMPARE(RdpSessionBackend::disconnectMessageForCode(FREERDP_ERROR_IDLE_TIMEOUT), QStringLiteral("RDP session disconnected due to idle timeout.")); QCOMPARE(RdpSessionBackend::disconnectMessageForCode(FREERDP_ERROR_LOGOFF_BY_USER), QStringLiteral("RDP session signed out.")); QCOMPARE(RdpSessionBackend::disconnectMessageForCode(FREERDP_ERROR_CONNECT_CANCELLED), QStringLiteral("Connection cancelled.")); } void TestRdpSessionBackend::disconnectMessageForCodeFallsBackForUnknownCode() { QCOMPARE(RdpSessionBackend::disconnectMessageForCode(0x7FFFFFFF), QStringLiteral("RDP session ended.")); } void TestRdpSessionBackend::rdpErrorRawIncludesHexCode() { const QString result = RdpSessionBackend::rdpErrorRaw(FREERDP_ERROR_SUCCESS); QVERIFY(result.contains(QStringLiteral("(0x00000000)"))); } QTEST_GUILESS_MAIN(TestRdpSessionBackend) #include "test_rdp_session_backend.moc"