diff options
author | Kari Oikarinen <kari.oikarinen@qt.io> | 2016-12-08 13:11:36 +0200 |
---|---|---|
committer | Kari Oikarinen <kari.oikarinen@qt.io> | 2016-12-22 08:43:03 +0000 |
commit | 7e1e1889736a5c0f6f6324d0ad8173ff0a8679a8 (patch) | |
tree | cf36d5a059dd76444daa801bcf0df93cf3809054 | |
parent | db61153547cb8aafaf374f43932047ea400bd482 (diff) |
Handle host-device version mismatches forward-compatibly
There is no support for multiple versions, but the behavior changes in
this commit should allow adding that in the future. Device responds with
a new Refuse message, if it does not support the requested version.
Change-Id: I8a747654edb1c6efab485808b2692cd9689bd100
Reviewed-by: Samuli Piippo <samuli.piippo@qt.io>
-rw-r--r-- | libqdb/protocol/protocol.h | 12 | ||||
-rw-r--r-- | libqdb/protocol/qdbmessage.cpp | 5 | ||||
-rw-r--r-- | libqdb/protocol/qdbmessage.h | 2 | ||||
-rw-r--r-- | qdb/server/connection.cpp | 74 | ||||
-rw-r--r-- | qdb/server/connection.h | 4 | ||||
-rw-r--r-- | qdb/server/handshakeservice.cpp | 15 | ||||
-rw-r--r-- | qdb/server/handshakeservice.h | 3 | ||||
-rw-r--r-- | qdbd/server.cpp | 89 | ||||
-rw-r--r-- | qdbd/server.h | 7 | ||||
-rw-r--r-- | tests/servicetest.cpp | 38 | ||||
-rw-r--r-- | tests/streamtest.cpp | 69 |
11 files changed, 265 insertions, 53 deletions
diff --git a/libqdb/protocol/protocol.h b/libqdb/protocol/protocol.h index 4f50ab2..c42c17f 100644 --- a/libqdb/protocol/protocol.h +++ b/libqdb/protocol/protocol.h @@ -21,11 +21,21 @@ #ifndef PROTOCOL_H #define PROTOCOL_H +#include <QtCore/qbytearray.h> +#include <QtCore/qdatastream.h> + #include <cstdint> const int qdbHeaderSize = 4*sizeof(uint32_t); const int qdbMessageSize = 16*1024; const int qdbMaxPayloadSize = qdbMessageSize - qdbHeaderSize; -const uint32_t qdbProtocolVersion = 0; +const uint32_t qdbProtocolVersion = 1; + +enum class RefuseReason : uint32_t +{ + Invalid = 0, // Never used except for deserialization failure + NotConnected, + UnknownVersion, +}; #endif diff --git a/libqdb/protocol/qdbmessage.cpp b/libqdb/protocol/qdbmessage.cpp index 7f30808..42e48da 100644 --- a/libqdb/protocol/qdbmessage.cpp +++ b/libqdb/protocol/qdbmessage.cpp @@ -114,6 +114,8 @@ QdbMessage::CommandType toCommandType(uint32_t command) switch (command) { case static_cast<uint32_t>(QdbMessage::Connect): return QdbMessage::Connect; + case static_cast<uint32_t>(QdbMessage::Refuse): + return QdbMessage::Refuse; case static_cast<uint32_t>(QdbMessage::Open): return QdbMessage::Open; case static_cast<uint32_t>(QdbMessage::Write): @@ -167,6 +169,9 @@ QDebug &operator<<(QDebug &stream, ::QdbMessage::CommandType command) case QdbMessage::Connect: stream << "Connect"; break; + case QdbMessage::Refuse: + stream << "Refuse"; + break; case QdbMessage::Open: stream << "Open"; break; diff --git a/libqdb/protocol/qdbmessage.h b/libqdb/protocol/qdbmessage.h index 99c57bb..56e6dd9 100644 --- a/libqdb/protocol/qdbmessage.h +++ b/libqdb/protocol/qdbmessage.h @@ -42,6 +42,7 @@ public: { Invalid = 0, // never sent Connect = 0x434e584e, // CNXN + Refuse = 0x52465345, // RFSE Open = 0x4f50454e, // OPEN Write = 0x57525445, // WRTE Close = 0x434c5345, // CLSE @@ -75,6 +76,7 @@ private: Q_DECLARE_METATYPE(QdbMessage::CommandType) QT_BEGIN_NAMESPACE +QDebug &operator<<(QDebug &stream, ::QdbMessage::CommandType command); QDebug &operator<<(QDebug &stream, const ::QdbMessage &message); QDataStream &operator<<(QDataStream &stream, const ::QdbMessage &message); diff --git a/qdb/server/connection.cpp b/qdb/server/connection.cpp index c2d7edd..03f2693 100644 --- a/qdb/server/connection.cpp +++ b/qdb/server/connection.cpp @@ -30,6 +30,17 @@ Q_LOGGING_CATEGORY(connectionC, "qdb.connection"); +RefuseReason toRefuseReason(uint32_t data) +{ + switch (data) { + case static_cast<uint32_t>(RefuseReason::NotConnected): + return RefuseReason::NotConnected; + case static_cast<uint32_t>(RefuseReason::UnknownVersion): + return RefuseReason::UnknownVersion; + } + return RefuseReason::Invalid; +} + Connection::Connection(QdbTransport *transport, QObject *parent) : AbstractConnection{transport, parent}, m_state{ConnectionState::Disconnected}, @@ -115,15 +126,20 @@ void Connection::handleMessage() resetConnection(true); break; case ConnectionState::WaitingForConnection: - if (message.command() != QdbMessage::Connect) { - qCWarning(connectionC) << "Connection got a non-Connect message in WaitingForConnection state"; + if (message.command() != QdbMessage::Connect && message.command() != QdbMessage::Refuse) { + qCWarning(connectionC) << "Connection got an unexpected message in WaitingForConnection state" << message; resetConnection(true); break; } - if (checkVersion(message)) - m_state = ConnectionState::Connected; - else - m_state = ConnectionState::Disconnected; + + if (message.command() == QdbMessage::Connect) { + if (checkVersion(message)) + m_state = ConnectionState::Connected; + else + m_state = ConnectionState::Disconnected; + } else if (message.command() == QdbMessage::Refuse) { + handleRefuse(message.data()); + } break; case ConnectionState::Connected: switch (message.command()) { @@ -131,6 +147,9 @@ void Connection::handleMessage() qCWarning(connectionC) << "Connection received QdbMessage::Connect while already connected. Reconnecting."; resetConnection(true); break; + case QdbMessage::Refuse: + handleRefuse(message.data()); + break; case QdbMessage::Write: handleWrite(message); break; @@ -153,6 +172,9 @@ void Connection::handleMessage() qCWarning(connectionC) << "Connection received QdbMessage::Connect while already connected and waiting. Reconnecting."; resetConnection(true); break; + case QdbMessage::Refuse: + handleRefuse(message.data()); + break; case QdbMessage::Write: handleWrite(message); break; @@ -197,12 +219,12 @@ void Connection::processQueue() } if (m_state == ConnectionState::Waiting) { - qCDebug(connectionC) << "Connection::processQueue() skipping to wait for QdbMessage::Ok"; + qCDebug(connectionC) << "Delaying sending outgoing message to wait for Ok from device"; return; } if (m_state == ConnectionState::WaitingForConnection) { - qCDebug(connectionC) << "Connection::processQueue() skipping to wait for QdbMessage::Connect"; + qCDebug(connectionC) << "Delaying sending outgoing message due to waiting for Connect or Refuse from device"; return; } @@ -210,6 +232,8 @@ void Connection::processQueue() Q_ASSERT_X(message.command() != QdbMessage::Invalid, "Connection::processQueue()", "Tried to send invalid message"); + Q_ASSERT_X(message.command() != QdbMessage::Refuse, "Connection::processQueue()", + "Tried to send Refuse message from host"); if (!m_transport->send(message)) { qCCritical(connectionC) << "Connection could not send" << message; @@ -239,6 +263,8 @@ void Connection::processQueue() case QdbMessage::Ok: // 'Ok's are sent via acknowledge() //[[fallthrough]] + case QdbMessage::Refuse: + //[[fallthrough]] case QdbMessage::Invalid: Q_UNREACHABLE(); break; @@ -254,6 +280,8 @@ void Connection::resetConnection(bool reconnect) if (reconnect) connect(); + else + emit disconnected(); } void Connection::closeStream(StreamId id) @@ -280,6 +308,32 @@ void Connection::finishCreateStream(StreamId hostId, StreamId deviceId) callback(m_streams[hostId].get()); } +void Connection::handleRefuse(const QByteArray &payload) +{ + QDataStream dataStream{payload}; + uint32_t rawReason; + dataStream >> rawReason; + + const auto reason = toRefuseReason(rawReason); + switch (reason) { + case RefuseReason::NotConnected: + qCWarning(connectionC) << "Received Refuse due to not being connected, reconnecting"; + resetConnection(true); + break; + case RefuseReason::UnknownVersion: + uint32_t version; + dataStream >> version; + qCCritical(connectionC) << "Device does not recognize version" << qdbProtocolVersion + << "and requested for unknown version" << version << ". Can not connect."; + resetConnection(false); + break; + case RefuseReason::Invalid: + qCCritical(connectionC) << "Received Refuse with an invalid reason. Disconnected."; + resetConnection(false); + break; + } +} + void Connection::handleWrite(const QdbMessage &message) { if (m_streams.find(message.hostStream()) == m_streams.end()) { @@ -301,8 +355,8 @@ bool Connection::checkVersion(const QdbMessage &message) dataStream >> protocolVersion; if (protocolVersion != qdbProtocolVersion) { - qCCritical(connectionC) << "Device offered protocol version" << protocolVersion - << ", but only version" << qdbProtocolVersion << "is supported"; + qCCritical(connectionC) << "Device responded with protocol version" << protocolVersion + << ", but version" << qdbProtocolVersion << "was requested"; return false; } return true; diff --git a/qdb/server/connection.h b/qdb/server/connection.h index 451a4da..c71b590 100644 --- a/qdb/server/connection.h +++ b/qdb/server/connection.h @@ -56,6 +56,9 @@ public: void enqueueMessage(const QdbMessage &message) override; +signals: + void disconnected(); + public slots: void close(); void handleMessage() override; @@ -66,6 +69,7 @@ private: void resetConnection(bool reconnect); void closeStream(StreamId id); void finishCreateStream(StreamId hostId, StreamId deviceId); + void handleRefuse(const QByteArray &payload); void handleWrite(const QdbMessage &message); bool checkVersion(const QdbMessage &message); diff --git a/qdb/server/handshakeservice.cpp b/qdb/server/handshakeservice.cpp index c9f3df1..be766f9 100644 --- a/qdb/server/handshakeservice.cpp +++ b/qdb/server/handshakeservice.cpp @@ -44,6 +44,7 @@ HandshakeService::~HandshakeService() void HandshakeService::initialize() { + connect(m_connection, &Connection::disconnected, this, &HandshakeService::handleDisconnected); m_connection->createStream(tagBuffer(HandshakeTag), [=](Stream *stream) { this->streamCreated(stream); }); @@ -85,6 +86,18 @@ void HandshakeService::receive(StreamPacket packet) void HandshakeService::onStreamClosed() { Service::onStreamClosed(); - if (!m_responded) + failedResponse(); +} + +void HandshakeService::handleDisconnected() +{ + failedResponse(); +} + +void HandshakeService::failedResponse() +{ + if (!m_responded) { emit response("", "", ""); + m_responded = true; + } } diff --git a/qdb/server/handshakeservice.h b/qdb/server/handshakeservice.h index c99e6ce..09fe03f 100644 --- a/qdb/server/handshakeservice.h +++ b/qdb/server/handshakeservice.h @@ -50,6 +50,9 @@ protected slots: void onStreamClosed() override; private: + void handleDisconnected(); + void failedResponse(); + Connection *m_connection; bool m_responded; }; diff --git a/qdbd/server.cpp b/qdbd/server.cpp index 9e47528..ea28699 100644 --- a/qdbd/server.cpp +++ b/qdbd/server.cpp @@ -23,7 +23,6 @@ #include "createexecutor.h" #include "echoexecutor.h" #include "libqdb/make_unique.h" -#include "libqdb/protocol/protocol.h" #include "libqdb/protocol/qdbmessage.h" #include "libqdb/protocol/qdbtransport.h" #include "libqdb/stream.h" @@ -49,24 +48,26 @@ void Server::handleMessage() { QdbMessage message = m_transport->receive(); - Q_ASSERT_X(message.command() != QdbMessage::Invalid, "Server::handleMessage()", "Received invalid message"); + if (message.command() == QdbMessage::Invalid) + qFatal("Server received Invalid message, which is not supported"); + + if (message.command() == QdbMessage::Refuse) + qFatal("Server received Refuse message, which is not supported"); switch (m_state) { case ServerState::Disconnected: if (message.command() != QdbMessage::Connect) { - qCWarning(connectionC) << "Server got non-Connect message in Disconnected state. Resetting."; - resetServer(false); + qCWarning(connectionC) << "Server got non-Connect message in Disconnected state. Refusing."; + refuse(RefuseReason::NotConnected); break; } - checkVersion(message); - resetServer(true); + handleConnect(message.data()); break; case ServerState::Connected: switch (message.command()) { case QdbMessage::Connect: - qCWarning(connectionC()) << "Server received QdbMessage::Connect while already connected. Resetting."; - checkVersion(message); - resetServer(true); + qCWarning(connectionC) << "Server received QdbMessage::Connect while already connected. Resetting."; + handleConnect(message.data()); break; case QdbMessage::Open: handleOpen(message.hostStream(), message.data()); @@ -80,6 +81,8 @@ void Server::handleMessage() case QdbMessage::Ok: qCWarning(connectionC) << "Server received QdbMessage::Ok in connected state"; break; + case QdbMessage::Refuse: + //[[fallthrough]] case QdbMessage::Invalid: Q_UNREACHABLE(); break; @@ -88,8 +91,8 @@ void Server::handleMessage() case ServerState::Waiting: switch (message.command()) { case QdbMessage::Connect: - qCWarning(connectionC()) << "Server received QdbMessage::Connect while already connected and waiting. Resetting."; - resetServer(true); + qCWarning(connectionC) << "Server received QdbMessage::Connect while already connected and waiting. Resetting."; + handleConnect(message.data()); break; case QdbMessage::Open: handleOpen(message.hostStream(), message.data()); @@ -104,6 +107,8 @@ void Server::handleMessage() case QdbMessage::Ok: m_state = ServerState::Connected; break; + case QdbMessage::Refuse: + //[[fallthrough]] case QdbMessage::Invalid: Q_UNREACHABLE(); break; @@ -123,9 +128,8 @@ void Server::enqueueMessage(const QdbMessage &message) void Server::processQueue() { - if (m_outgoingMessages.isEmpty()) { + if (m_outgoingMessages.isEmpty()) return; - } if (m_state == ServerState::Waiting) { qCDebug(connectionC) << "Server::processQueue() skipping to wait for QdbMessage::Ok"; @@ -144,6 +148,9 @@ void Server::processQueue() } switch (message.command()) { + case QdbMessage::Refuse: + m_state = ServerState::Disconnected; + break; case QdbMessage::Open: qFatal("Server sending QdbMessage::Open is not supported"); break; @@ -171,6 +178,23 @@ void Server::processQueue() } } +void Server::handleConnect(const QByteArray &payload) +{ + if (!checkVersion(payload)) { + refuse(RefuseReason::UnknownVersion); + return; + } + + resetServer(); + m_state = ServerState::Connected; + + QByteArray buffer{}; + QDataStream dataStream{&buffer, QIODevice::WriteOnly}; + dataStream << qdbProtocolVersion; + + enqueueMessage(QdbMessage{QdbMessage::Connect, 0, 0, buffer}); +} + void Server::handleOpen(StreamId hostId, const QByteArray &tag) { StreamId deviceId = m_nextStreamId++; @@ -179,18 +203,25 @@ void Server::handleOpen(StreamId hostId, const QByteArray &tag) m_executors[deviceId] = createExecutor(m_streams[deviceId].get(), tag); } -void Server::resetServer(bool hostConnected) +void Server::refuse(RefuseReason reason) +{ + resetServer(); + QByteArray buffer{}; + QDataStream stream{&buffer, QIODevice::WriteOnly}; + stream << static_cast<uint32_t>(reason); + + if (reason == RefuseReason::UnknownVersion) + stream << qdbProtocolVersion; + + m_state = ServerState::Disconnected; + enqueueMessage(QdbMessage{QdbMessage::Refuse, 0, 0, buffer}); +} + +void Server::resetServer() { m_outgoingMessages.clear(); m_executors.clear(); m_streams.clear(); - m_state = hostConnected ? ServerState::Connected : ServerState::Disconnected; - - QByteArray buffer{}; - QDataStream dataStream{&buffer, QIODevice::WriteOnly}; - dataStream << qdbProtocolVersion; - - enqueueMessage(QdbMessage{QdbMessage::Connect, 0, 0, buffer}); } void Server::handleWrite(const QdbMessage &message) @@ -224,18 +255,22 @@ void Server::closeStream(StreamId id) // Closes are not acknowledged } -void Server::checkVersion(const QdbMessage &message) +bool Server::checkVersion(const QByteArray &payload) { - Q_ASSERT(message.command() == QdbMessage::Connect); - Q_ASSERT(message.data().size() == sizeof(qdbProtocolVersion)); + if (static_cast<size_t>(payload.size()) < sizeof(qdbProtocolVersion)) { + qCCritical(connectionC) << "Connection request did not contain a protocol version"; + return false; + }; - QDataStream dataStream{message.data()}; + QDataStream dataStream{payload}; uint32_t protocolVersion; dataStream >> protocolVersion; if (protocolVersion != qdbProtocolVersion) { - qCCritical(connectionC()) << "Protocol version" << protocolVersion << "requested, but only version" - << qdbProtocolVersion << "is known"; + qCWarning(connectionC) << "Protocol version" << protocolVersion << "requested, but only version" + << qdbProtocolVersion << "is known"; + return false; } + return true; } diff --git a/qdbd/server.h b/qdbd/server.h index 8f8bf09..097d286 100644 --- a/qdbd/server.h +++ b/qdbd/server.h @@ -22,6 +22,7 @@ #define SERVER_H #include "libqdb/abstractconnection.h" +#include "libqdb/protocol/protocol.h" class Executor; class QdbMessage; class QdbTransport; @@ -52,11 +53,13 @@ public slots: private: void processQueue(); + void handleConnect(const QByteArray &payload); void handleOpen(StreamId hostId, const QByteArray &tag); - void resetServer(bool hostConnected); + void refuse(RefuseReason reason); + void resetServer(); void handleWrite(const QdbMessage &message); void closeStream(StreamId id); - void checkVersion(const QdbMessage &message); + bool checkVersion(const QByteArray &payload); ServerState m_state; std::unordered_map<StreamId, std::unique_ptr<Executor>> m_executors; diff --git a/tests/servicetest.cpp b/tests/servicetest.cpp index a895257..76af6cf 100644 --- a/tests/servicetest.cpp +++ b/tests/servicetest.cpp @@ -34,18 +34,39 @@ const int testTimeout = 500; // in milliseconds // Helper to initialize Connection in testcases -struct ConnectionContext +class ConnectionContext { +public: ConnectionContext() - : deviceEnumerator{}, - connection{new QdbTransport{new UsbConnection{deviceEnumerator.listUsbDevices()[0]}}} + : m_connection{nullptr}, + m_valid{false} { - QVERIFY(connection.initialize()); + UsbDeviceEnumerator deviceEnumerator; + const auto devices = deviceEnumerator.listUsbDevices(); + if (devices.empty()) + QFAIL("Could not find QDB USB device to run the test against"); - connection.connect(); + m_connection = make_unique<Connection>(new QdbTransport{new UsbConnection{devices[0]}}); + + QVERIFY(m_connection->initialize()); + m_valid = true; + + m_connection->connect(); + } + + Connection *connection() + { + return m_connection.get(); } - UsbDeviceEnumerator deviceEnumerator; - Connection connection; + + bool isValid() + { + return m_valid; + } + +private: + std::unique_ptr<Connection> m_connection; + bool m_valid; }; class ServiceTest : public QObject @@ -58,8 +79,9 @@ private slots: void ServiceTest::echo() { ConnectionContext ctx; + QVERIFY(ctx.isValid()); - EchoService echo{&ctx.connection}; + EchoService echo{ctx.connection()}; connect(&echo, &EchoService::initialized, [&]() { echo.send("ABCD"); }); diff --git a/tests/streamtest.cpp b/tests/streamtest.cpp index 0e3068b..67ff33c 100644 --- a/tests/streamtest.cpp +++ b/tests/streamtest.cpp @@ -46,7 +46,11 @@ public slots: void run() { UsbDeviceEnumerator deviceManager; - m_transport = make_unique<QdbTransport>(new UsbConnection{deviceManager.listUsbDevices()[0]}); + const auto devices = deviceManager.listUsbDevices(); + if (devices.empty()) + QFAIL("Could not find QDB USB device to run the test against"); + + m_transport = make_unique<QdbTransport>(new UsbConnection{devices[0]}); if (m_transport->open()) { qDebug() << "opened transport"; connect(m_transport.get(), &QdbTransport::messageAvailable, this, &TestCase::testPhases); @@ -179,7 +183,7 @@ public slots: { switch (m_phase) { case 0: { - QByteArray unsupported{}; + QByteArray unsupported; QDataStream dataStream{&unsupported, QIODevice::WriteOnly}; dataStream << (qdbProtocolVersion + 1); QdbMessage connect{QdbMessage::Connect, 0, 0, unsupported}; @@ -188,10 +192,16 @@ public slots: } case 1: { QdbMessage response = m_transport->receive(); - QCOMPARE(response.command(), QdbMessage::Connect); + QCOMPARE(response.command(), QdbMessage::Refuse); QCOMPARE(response.hostStream(), 0u); QCOMPARE(response.deviceStream(), 0u); - QCOMPARE(response.data(), m_versionBuffer); + QDataStream dataStream{response.data()}; + uint32_t reason; + dataStream >> reason; + QCOMPARE(reason, static_cast<uint32_t>(RefuseReason::UnknownVersion)); + uint32_t version; + dataStream >> version; + QCOMPARE(version, qdbProtocolVersion); emit passed(); break; @@ -464,6 +474,51 @@ private: StreamId m_deviceId2 = 0; }; +class WriteWithoutConnectingTest : public TestCase +{ + Q_OBJECT +public slots: + void testPhases() override + { + switch (m_phase) { + case 0: { + // Send Connect with unknown version to ensure not connected state. + // Otherwise the device will still be connected due to previous + // tests establishing the connection. + QByteArray unsupported; + QDataStream dataStream{&unsupported, QIODevice::WriteOnly}; + dataStream << (qdbProtocolVersion + 1); + QdbMessage connect{QdbMessage::Connect, 0, 0, unsupported}; + QVERIFY(m_transport->send(connect)); + break; + } + case 1: { + QdbMessage response = m_transport->receive(); + QCOMPARE(response.command(), QdbMessage::Refuse); + + QByteArray data{"ABCD"}; + QdbMessage write{QdbMessage::Write, 1, 1, data}; + QVERIFY(m_transport->send(write)); + break; + } + case 2: { + QdbMessage response = m_transport->receive(); + QCOMPARE(response.command(), QdbMessage::Refuse); + QCOMPARE(response.hostStream(), 0u); + QCOMPARE(response.deviceStream(), 0u); + QByteArray expectedReason; + QDataStream dataStream{&expectedReason, QIODevice::WriteOnly}; + dataStream << static_cast<uint32_t>(RefuseReason::NotConnected); + QCOMPARE(response.data(), expectedReason); + + emit passed(); + break; + } + } + ++m_phase; + } +}; + void testCase(TestCase *test) { QSignalSpy spy{test, &TestCase::passed}; @@ -511,6 +566,12 @@ private slots: TwoEchoStreamsTest test; testCase(&test); } + + void writeWithoutConnecting() + { + WriteWithoutConnectingTest test; + testCase(&test); + } }; QTEST_GUILESS_MAIN(StreamTest) |