diff options
author | Kari Oikarinen <kari.oikarinen@qt.io> | 2016-06-22 13:18:04 +0300 |
---|---|---|
committer | Kari Oikarinen <kari.oikarinen@qt.io> | 2016-07-14 11:57:38 +0000 |
commit | 3a6284c97ee1fb22f0bafe14f0f4d4ac2d8d2acf (patch) | |
tree | 259845e8b1c150fb5bb42e7dbcbe2d77a76d6650 | |
parent | 174aad875aa79027dae885450ddd97d59cd27426 (diff) |
Import QDB prototype
The prototype is capable of:
- Running a process on the device
- Pushing a file to the device
- Pulling a file from the device
The device is connected to through USB. There is not yet management of
multiple connected devices.
Change-Id: Icba20e1d68dafbab9d71f44b86c20efb1df45310
Reviewed-by: Rainer Keller <Rainer.Keller@qt.io>
75 files changed, 6298 insertions, 0 deletions
diff --git a/.gitignore b/.gitignore new file mode 100644 index 0000000..3c2d35b --- /dev/null +++ b/.gitignore @@ -0,0 +1,2 @@ +*.pro.user +build-*/ diff --git a/client/client.pro b/client/client.pro new file mode 100644 index 0000000..6c53490 --- /dev/null +++ b/client/client.pro @@ -0,0 +1,39 @@ +QT -= gui + +CONFIG += c++11 + +TARGET = qdb +win32: CONFIG += console +CONFIG -= app_bundle + +TEMPLATE = app + +HEADERS += \ + connection.h \ + echoservice.h \ + filepullservice.h \ + filepushservice.h \ + processservice.h \ + service.h + + +SOURCES += \ + connection.cpp \ + echoservice.cpp \ + filepullservice.cpp \ + filepushservice.cpp \ + main.cpp \ + processservice.cpp \ + service.cpp + + +INCLUDEPATH += $$PWD/../libqdb + +LIBS = -L$$OUT_PWD/../libqdb -lqdb +QMAKE_RPATHDIR += ../libqdb + +unix { + target.path = /usr/bin + INSTALLS += target +} + diff --git a/client/connection.cpp b/client/connection.cpp new file mode 100644 index 0000000..88fe82f --- /dev/null +++ b/client/connection.cpp @@ -0,0 +1,280 @@ +/****************************************************************************** +** +** Copyright (C) 2016 The Qt Company Ltd. +** Contact: http://www.qt.io/licensing/ +** +** This file is part of the Qt Debug Bridge. +** +** $QT_BEGIN_LICENSE:COMM$ +** +** Commercial License Usage +** Licensees holding valid commercial Qt licenses may use this file in +** accordance with the commercial license agreement provided with the +** Software or, alternatively, in accordance with the terms contained in +** a written agreement between you and The Qt Company. For licensing terms +** and conditions see http://www.qt.io/terms-conditions. For further +** information use the contact form at http://www.qt.io/contact-us. +** +** $QT_END_LICENSE$ +** +******************************************************************************/ +#include "connection.h" + +#include "../utils/make_unique.h" +#include "protocol/qdbtransport.h" +#include "service.h" + +#include <QtCore/qdebug.h> +#include <QtCore/qloggingcategory.h> + +Q_LOGGING_CATEGORY(connectionC, "connection"); + +Connection::Connection(QdbTransport *transport, QObject *parent) + : AbstractConnection{transport, parent}, + m_state{ConnectionState::Disconnected}, + m_streamRequests{}, + m_closing{false} +{ + +} + +void Connection::connect() +{ + Q_ASSERT(m_state == ConnectionState::Disconnected); + enqueueMessage(QdbMessage{QdbMessage::Connect, 0, 0}); +} + +void Connection::close() +{ + if (m_state == ConnectionState::Disconnected) + return; + + if (m_state == ConnectionState::WaitingForConnection) { + m_state = ConnectionState::Disconnected; + return; + } + + // No need to wait for 'Ok's just to send the final Closes + if (m_state == ConnectionState::Waiting) + m_state = ConnectionState::Connected; + + m_outgoingMessages.clear(); + m_streamRequests.clear(); + + while (!m_streams.empty()) { + auto &stream = m_streams.begin()->second; + enqueueMessage(QdbMessage{QdbMessage::Close, stream->hostId(), stream->deviceId()}); + // Processing of the Close message erased the stream from m_streams + } +} + +ConnectionState Connection::state() const +{ + return m_state; +} + +void Connection::createStream(const QByteArray &openTag, StreamCreatedCallback streamCreatedCallback) +{ + StreamId id = m_nextStreamId++; + m_streamRequests[id] = streamCreatedCallback; + enqueueMessage(QdbMessage{QdbMessage::Open, id, 0, openTag}); +} + +Connection::~Connection() = default; + +void Connection::enqueueMessage(const QdbMessage &message) +{ + Q_ASSERT(message.command() != QdbMessage::Invalid); + qCDebug(connectionC) << "Connection enqueue: " << message; + m_outgoingMessages.enqueue(message); + processQueue(); +} + +void Connection::handleMessage() +{ + QdbMessage message = m_transport->receive(); + + if (message.command() == QdbMessage::Open) + qFatal("Connection received QdbMessage::Open, which is not supported!"); + + if (message.command() == QdbMessage::Invalid) + qFatal("Connection received invalid message!"); + + switch (m_state) { + case ConnectionState::Disconnected: + qWarning() << "Connection got a message in Disconnected state"; + resetConnection(); + break; + case ConnectionState::WaitingForConnection: + if (message.command() != QdbMessage::Connect) { + qWarning() << "Connection got a non-Connect message in WaitingForConnection state"; + resetConnection(); + break; + } + m_state = ConnectionState::Connected; + break; + case ConnectionState::Connected: + switch (message.command()) { + case QdbMessage::Connect: + qWarning() << "Connection received QdbMessage::Connect while already connected. Reconnecting."; + resetConnection(); + break; + case QdbMessage::Write: + handleWrite(message); + break; + case QdbMessage::Close: + closeStream(message.hostStream()); + break; + case QdbMessage::Ok: + qWarning() << "Connection received QdbMessage::Ok in connected state"; + break; + case QdbMessage::Open: + [[fallthrough]] + case QdbMessage::Invalid: + Q_UNREACHABLE(); + break; + } + break; + case ConnectionState::Waiting: + switch (message.command()) { + case QdbMessage::Connect: + qWarning() << "Connection received QdbMessage::Connect while already connected and waiting. Reconnecting."; + resetConnection(); + break; + case QdbMessage::Write: + handleWrite(message); + break; + case QdbMessage::Close: + closeStream(message.hostStream()); + break; + case QdbMessage::Ok: + m_state = ConnectionState::Connected; + if (m_streamRequests.contains(message.hostStream())) { + // This message is a response to Open + finishCreateStream(message.hostStream(), message.deviceStream()); + } + break; + case QdbMessage::Open: + [[fallthrough]] + case QdbMessage::Invalid: + Q_UNREACHABLE(); + break; + } + break; + } + processQueue(); +} + +void Connection::acknowledge(StreamId hostId, StreamId deviceId) +{ + // QdbMessage::Ok is sent also in Waiting state to avoid both sides ending up waiting for Ok + Q_ASSERT(m_state == ConnectionState::Connected || m_state == ConnectionState::Waiting); + + QdbMessage message{QdbMessage::Ok, hostId, deviceId}; + if (!m_transport->send(message)) { + qCritical() << "Connection could not send" << message; + m_state = ConnectionState::Disconnected; + return; + } +} + +void Connection::processQueue() +{ + if (m_outgoingMessages.isEmpty()) { + return; + } + + if (m_state == ConnectionState::Waiting) { + qCDebug(connectionC) << "Connection::processQueue() skipping to wait for QdbMessage::Ok"; + return; + } + + if (m_state == ConnectionState::WaitingForConnection) { + qCDebug(connectionC) << "Connection::processQueue() skipping to wait for QdbMessage::Connect"; + return; + } + + auto message = m_outgoingMessages.dequeue(); + + Q_ASSERT_X(message.command() != QdbMessage::Invalid, "Connection::processQueue()", + "Tried to send invalid message"); + + if (!m_transport->send(message)) { + qCritical() << "Connection could not send" << message; + m_state = ConnectionState::Disconnected; + return; + } + + switch (message.command()) { + case QdbMessage::Connect: + Q_ASSERT(m_state == ConnectionState::Disconnected); + m_state = ConnectionState::WaitingForConnection; + break; + // Need to wait for Ok after Open and Write + case QdbMessage::Open: + Q_ASSERT(m_state == ConnectionState::Connected); + m_state = ConnectionState::Waiting; + break; + case QdbMessage::Write: + Q_ASSERT(m_state == ConnectionState::Connected); + m_state = ConnectionState::Waiting; + break; + // Close is not acknowledged so no need to transition to ConnectionState::Waiting + case QdbMessage::Close: + Q_ASSERT(m_state == ConnectionState::Connected); + if (m_streams.find(message.hostStream()) != m_streams.end()) { + closeStream(message.hostStream()); + } + break; + case QdbMessage::Ok: + // 'Ok's are sent via acknowledge() + [[fallthrough]] + case QdbMessage::Invalid: + Q_UNREACHABLE(); + break; + } +} + +void Connection::resetConnection() +{ + m_outgoingMessages.clear(); + m_state = ConnectionState::Disconnected; + m_streamRequests.clear(); + m_streams.clear(); + enqueueMessage(QdbMessage{QdbMessage::Connect, 0, 0}); +} + +void Connection::closeStream(StreamId id) +{ + if (m_streams.find(id) == m_streams.end()) + return; + + m_streams[id]->close(); + m_streams.erase(id); + + auto messageInStream = [&id](const QdbMessage &message) { + return message.hostStream() == id; + }; + m_outgoingMessages.erase( + std::remove_if(m_outgoingMessages.begin(), m_outgoingMessages.end(), messageInStream), + m_outgoingMessages.end()); + // Closes are not acknowledged +} + +void Connection::finishCreateStream(StreamId hostId, StreamId deviceId) +{ + m_streams[hostId] = make_unique<Stream>(this, hostId, deviceId); + StreamCreatedCallback callback = m_streamRequests.take(hostId); + callback(m_streams[hostId].get()); +} + +void Connection::handleWrite(QdbMessage message) +{ + if (m_streams.find(message.hostStream()) == m_streams.end()) { + qWarning() << "Connection received message to non-existing stream" << message.hostStream(); + enqueueMessage(QdbMessage{QdbMessage::Close, message.hostStream(), message.deviceStream()}); + return; + } + acknowledge(message.hostStream(), message.deviceStream()); + m_streams[message.hostStream()]->receiveMessage(message); +} diff --git a/client/connection.h b/client/connection.h new file mode 100644 index 0000000..dd6fe4f --- /dev/null +++ b/client/connection.h @@ -0,0 +1,76 @@ +/****************************************************************************** +** +** Copyright (C) 2016 The Qt Company Ltd. +** Contact: http://www.qt.io/licensing/ +** +** This file is part of the Qt Debug Bridge. +** +** $QT_BEGIN_LICENSE:COMM$ +** +** Commercial License Usage +** Licensees holding valid commercial Qt licenses may use this file in +** accordance with the commercial license agreement provided with the +** Software or, alternatively, in accordance with the terms contained in +** a written agreement between you and The Qt Company. For licensing terms +** and conditions see http://www.qt.io/terms-conditions. For further +** information use the contact form at http://www.qt.io/contact-us. +** +** $QT_END_LICENSE$ +** +******************************************************************************/ +#ifndef CONNECTION_H +#define CONNECTION_H + +#include "abstractconnection.h" +class Service; +class QdbMessage; +class QdbTransport; + +#include <QtCore/qhash.h> + +#include <functional> +#include <memory> +#include <unordered_map> + +using StreamCreatedCallback = std::function<void(Stream *)>; + +enum class ConnectionState +{ + Disconnected, + WaitingForConnection, + Connected, + Waiting +}; + +class Connection : public AbstractConnection +{ + Q_OBJECT +public: + /*! Server takes ownership of the passed QdbTransport. */ + explicit Connection(QdbTransport *transport, QObject *parent = 0); + ~Connection(); + + void connect(); + ConnectionState state() const; + void createStream(const QByteArray &openTag, StreamCreatedCallback streamCreatedCallback); + + void enqueueMessage(const QdbMessage &message) override; + +public slots: + void close(); + void handleMessage() override; + +private: + void acknowledge(StreamId hostId, StreamId deviceId); + void processQueue(); + void resetConnection(); + void closeStream(StreamId id); + void finishCreateStream(StreamId hostId, StreamId deviceId); + void handleWrite(QdbMessage message); + + ConnectionState m_state; + QHash<StreamId, StreamCreatedCallback> m_streamRequests; + bool m_closing; +}; + +#endif // CONNECTION_H diff --git a/client/echoservice.cpp b/client/echoservice.cpp new file mode 100644 index 0000000..4767951 --- /dev/null +++ b/client/echoservice.cpp @@ -0,0 +1,71 @@ +/****************************************************************************** +** +** Copyright (C) 2016 The Qt Company Ltd. +** Contact: http://www.qt.io/licensing/ +** +** This file is part of the Qt Debug Bridge. +** +** $QT_BEGIN_LICENSE:COMM$ +** +** Commercial License Usage +** Licensees holding valid commercial Qt licenses may use this file in +** accordance with the commercial license agreement provided with the +** Software or, alternatively, in accordance with the terms contained in +** a written agreement between you and The Qt Company. For licensing terms +** and conditions see http://www.qt.io/terms-conditions. For further +** information use the contact form at http://www.qt.io/contact-us. +** +** $QT_END_LICENSE$ +** +******************************************************************************/ +#include "echoservice.h" + +#include "connection.h" +#include "protocol/services.h" +#include "stream.h" + +#include <QtCore/qdebug.h> + +EchoService::EchoService(Connection *connection) + : m_connection{connection} +{ + +} + +EchoService::~EchoService() +{ + if (m_stream) + m_stream->requestClose(); +} + +void EchoService::initialize() +{ + m_connection->createStream(tagBuffer(EchoTag), [=](Stream *stream) { + this->streamCreated(stream); + }); +} + +bool EchoService::hasStream() const +{ + return m_stream != nullptr; +} + +void EchoService::send(const QString &string) +{ + if (!m_stream) { + qCritical() << "No valid stream in EchoService when trying to send"; + return; + } + StreamPacket packet{string.toUtf8()}; + m_stream->write(packet); +} + +void EchoService::close() +{ + m_stream->requestClose(); +} + +void EchoService::receive(StreamPacket packet) +{ + emit echo(QString{packet.buffer()}); +} diff --git a/client/echoservice.h b/client/echoservice.h new file mode 100644 index 0000000..31b1370 --- /dev/null +++ b/client/echoservice.h @@ -0,0 +1,52 @@ +/****************************************************************************** +** +** Copyright (C) 2016 The Qt Company Ltd. +** Contact: http://www.qt.io/licensing/ +** +** This file is part of the Qt Debug Bridge. +** +** $QT_BEGIN_LICENSE:COMM$ +** +** Commercial License Usage +** Licensees holding valid commercial Qt licenses may use this file in +** accordance with the commercial license agreement provided with the +** Software or, alternatively, in accordance with the terms contained in +** a written agreement between you and The Qt Company. For licensing terms +** and conditions see http://www.qt.io/terms-conditions. For further +** information use the contact form at http://www.qt.io/contact-us. +** +** $QT_END_LICENSE$ +** +******************************************************************************/ +#ifndef ECHOSERVICE_H +#define ECHOSERVICE_H + +#include "service.h" + +class Connection; +class Stream; +class StreamPacket; + +class EchoService : public Service +{ + Q_OBJECT +public: + explicit EchoService(Connection *connection); + ~EchoService(); + + void initialize() override; + + bool hasStream() const; + void send(const QString &string); + void close(); +signals: + void echo(QString echo); + +public slots: + void receive(StreamPacket packet) override; + +private: + Connection *m_connection; +}; + +#endif // ECHOSERVICE_H diff --git a/client/filepullservice.cpp b/client/filepullservice.cpp new file mode 100644 index 0000000..1123aac --- /dev/null +++ b/client/filepullservice.cpp @@ -0,0 +1,145 @@ +/****************************************************************************** +** +** Copyright (C) 2016 The Qt Company Ltd. +** Contact: http://www.qt.io/licensing/ +** +** This file is part of the Qt Debug Bridge. +** +** $QT_BEGIN_LICENSE:COMM$ +** +** Commercial License Usage +** Licensees holding valid commercial Qt licenses may use this file in +** accordance with the commercial license agreement provided with the +** Software or, alternatively, in accordance with the terms contained in +** a written agreement between you and The Qt Company. For licensing terms +** and conditions see http://www.qt.io/terms-conditions. For further +** information use the contact form at http://www.qt.io/contact-us. +** +** $QT_END_LICENSE$ +** +******************************************************************************/ +#include "filepullservice.h" + +#include "../utils/make_unique.h" +#include "connection.h" +#include "filepullcommon.h" +#include "protocol/services.h" + +#include <QtCore/qbytearray.h> +#include <QtCore/qdebug.h> +#include <QtCore/qfile.h> + +FilePullService::FilePullService(Connection *connection) + : m_connection{connection}, + m_hostPath{}, + m_devicePath{}, + m_sink{nullptr} +{ + +} + +FilePullService::~FilePullService() = default; + +void FilePullService::initialize() +{ + m_connection->createStream(tagBuffer(FilePullTag), [=](Stream *stream) { + this->streamCreated(stream); + }); +} + +bool FilePullService::pull(const QString &devicePath, const QString &hostPath) +{ + if (!m_stream) { + qCritical() << "No valid stream in FilePullService when trying to send"; + return false; + } + m_devicePath = devicePath; + m_hostPath = hostPath; + + StreamPacket packet; + packet << FilePullOpen << m_devicePath; + + return m_stream->write(packet); +} + +void FilePullService::receive(StreamPacket packet) +{ + Q_ASSERT(m_stream); + uint32_t typeValue; + packet >> typeValue; + auto type = toFilePullPacketType(typeValue); + switch (type) { + case FilePullOpened: + qDebug() << "Opened device file for pull"; + openSink(); + break; + case FilePullRead: { + QByteArray fileData; + packet >> fileData; + writeToSink(fileData); + break; + } + case FilePullEnd: + qDebug() << "Pull complete"; + closeSink(); + emit pulled(); + break; + case FilePullError: { + QString errorMessage; + packet >> errorMessage; + qWarning() << "Error on device while pulling:" << errorMessage; + emit error(errorMessage); + m_stream->requestClose(); + break; + } + default: + qFatal("Unsupported FilePullPacketType %d", type); + } +} + +void FilePullService::openSink() +{ + Q_ASSERT(m_stream); + qDebug() << "Opening sink file" << m_hostPath; + m_sink = make_unique<QFile>(m_hostPath); + if (!m_sink->open(QIODevice::WriteOnly)) { + StreamPacket packet; + packet << FilePullError; + m_stream->write(packet); + auto errorMessage = QString{"Could not open sink file \"%1\" on host"}.arg(m_sink->fileName()); + qDebug() << "Error on host:" << errorMessage; + emit error(errorMessage); + m_stream->requestClose(); + } +} + +void FilePullService::writeToSink(const QByteArray &data) +{ + Q_ASSERT(m_stream); + + if (!m_sink->isOpen()) { + qDebug() << "Skipping write to closed sink"; + return; + } + + auto written = m_sink->write(data); + + StreamPacket packet; + + if (written != data.size()) { + qDebug() << "Error in writing to sink"; + packet << FilePullError; + m_stream->write(packet); + m_stream->requestClose(); + } else { + packet << FilePullWasRead; + m_stream->write(packet); + } +} + +void FilePullService::closeSink() +{ + Q_ASSERT(m_stream); + qDebug() << "Closing sink file" << m_sink->fileName(); + m_sink->close(); +} diff --git a/client/filepullservice.h b/client/filepullservice.h new file mode 100644 index 0000000..eee84ae --- /dev/null +++ b/client/filepullservice.h @@ -0,0 +1,62 @@ +/****************************************************************************** +** +** Copyright (C) 2016 The Qt Company Ltd. +** Contact: http://www.qt.io/licensing/ +** +** This file is part of the Qt Debug Bridge. +** +** $QT_BEGIN_LICENSE:COMM$ +** +** Commercial License Usage +** Licensees holding valid commercial Qt licenses may use this file in +** accordance with the commercial license agreement provided with the +** Software or, alternatively, in accordance with the terms contained in +** a written agreement between you and The Qt Company. For licensing terms +** and conditions see http://www.qt.io/terms-conditions. For further +** information use the contact form at http://www.qt.io/contact-us. +** +** $QT_END_LICENSE$ +** +******************************************************************************/ +#ifndef FILEPULLSERVICE_H +#define FILEPULLSERVICE_H + +class Connection; +#include "service.h" + +class QByteArray; +class QFile; +#include <QtCore/qstring.h> + +#include <memory> + +class FilePullService : public Service +{ + Q_OBJECT +public: + explicit FilePullService(Connection *connection); + ~FilePullService(); + + void initialize() override; + + bool pull(const QString &devicePath, const QString &hostPath); + +signals: + void pulled(); + void error(QString error); + +public slots: + void receive(StreamPacket packet) override; + +private: + void openSink(); + void writeToSink(const QByteArray &data); + void closeSink(); + + Connection *m_connection; + QString m_hostPath; + QString m_devicePath; + std::unique_ptr<QFile> m_sink; +}; + +#endif // FILEPULLSERVICE_H diff --git a/client/filepushservice.cpp b/client/filepushservice.cpp new file mode 100644 index 0000000..4efa323 --- /dev/null +++ b/client/filepushservice.cpp @@ -0,0 +1,137 @@ +/****************************************************************************** +** +** Copyright (C) 2016 The Qt Company Ltd. +** Contact: http://www.qt.io/licensing/ +** +** This file is part of the Qt Debug Bridge. +** +** $QT_BEGIN_LICENSE:COMM$ +** +** Commercial License Usage +** Licensees holding valid commercial Qt licenses may use this file in +** accordance with the commercial license agreement provided with the +** Software or, alternatively, in accordance with the terms contained in +** a written agreement between you and The Qt Company. For licensing terms +** and conditions see http://www.qt.io/terms-conditions. For further +** information use the contact form at http://www.qt.io/contact-us. +** +** $QT_END_LICENSE$ +** +******************************************************************************/ +#include "filepushservice.h" + +#include "../utils/make_unique.h" +#include "connection.h" +#include "filepushcommon.h" +#include "protocol/services.h" + +#include <QtCore/qbytearray.h> +#include <QtCore/qdebug.h> +#include <QtCore/qfile.h> + +FilePushService::FilePushService(Connection *connection) + : m_connection{connection}, + m_hostPath{}, + m_devicePath{}, + m_source{nullptr}, + m_transferring{false} +{ + +} + +FilePushService::~FilePushService() +{ + if (m_source && m_source->isOpen()) + m_source->close(); +} + +void FilePushService::initialize() +{ + m_connection->createStream(tagBuffer(FilePushTag), [=](Stream *stream) { + this->streamCreated(stream); + }); +} + +bool FilePushService::push(const QString &hostPath, const QString& devicePath) +{ + if (!m_stream) { + qCritical() << "No valid stream in FilePushService when trying to send"; + return false; + } + m_hostPath = hostPath; + m_devicePath = devicePath; + + StreamPacket packet; + packet << FilePushOpen << m_devicePath; + + return m_stream->write(packet); +} + +void FilePushService::receive(StreamPacket packet) +{ + uint32_t typeValue; + packet >> typeValue; + auto type = toFilePushPacketType(typeValue); + switch (type) { + case FilePushOpened: + qDebug() << "FilePushOpened"; + transferBlock(); + break; + case FilePushWritten: + qDebug() << "FilePushWritten"; + if (m_transferring) + transferBlock(); + else + endTransfer(); + break; + case FilePushError: + m_transferring = false; + qDebug() << "FilePushError"; + emit error("Error on device while pushing"); + break; + default: + qFatal("Unsupported FilePushPacketType %d", type); + } +} + +bool FilePushService::openSource() +{ + m_source = make_unique<QFile>(m_hostPath); + if (!m_source->open(QIODevice::ReadOnly)) { + StreamPacket packet; + packet << FilePushError; + + m_stream->write(packet); + + emit error("Could not open " + m_source->fileName() + " on host"); + return false; + } + return true; +} + +void FilePushService::transferBlock() +{ + if (!m_transferring) { + if (!openSource()) + return; + m_transferring = true; + } + + QByteArray block = m_source->read(fileTransferBlockSize); + m_transferring = !m_source->atEnd(); + + StreamPacket packet; + packet << FilePushWrite << block; + + m_stream->write(packet); +} + +void FilePushService::endTransfer() +{ + StreamPacket packet; + packet << FilePushEnd; + + m_stream->write(packet); + + emit pushed(); +} diff --git a/client/filepushservice.h b/client/filepushservice.h new file mode 100644 index 0000000..1350079 --- /dev/null +++ b/client/filepushservice.h @@ -0,0 +1,63 @@ +/****************************************************************************** +** +** Copyright (C) 2016 The Qt Company Ltd. +** Contact: http://www.qt.io/licensing/ +** +** This file is part of the Qt Debug Bridge. +** +** $QT_BEGIN_LICENSE:COMM$ +** +** Commercial License Usage +** Licensees holding valid commercial Qt licenses may use this file in +** accordance with the commercial license agreement provided with the +** Software or, alternatively, in accordance with the terms contained in +** a written agreement between you and The Qt Company. For licensing terms +** and conditions see http://www.qt.io/terms-conditions. For further +** information use the contact form at http://www.qt.io/contact-us. +** +** $QT_END_LICENSE$ +** +******************************************************************************/ +#ifndef FILEPUSHSERVICE_H +#define FILEPUSHSERVICE_H + +class Connection; +#include "service.h" + +class QByteArray; +class QFile; +#include <QtCore/qstring.h> + +#include <memory> + +class FilePushService : public Service +{ + Q_OBJECT +public: + explicit FilePushService(Connection *connection); + ~FilePushService(); + + void initialize() override; + + bool push(const QString &hostPath, const QString &devicePath); + +signals: + void pushed(); + void error(QString error); + +public slots: + void receive(StreamPacket packet) override; + +private: + bool openSource(); + void transferBlock(); + void endTransfer(); + + Connection *m_connection; + QString m_hostPath; + QString m_devicePath; + std::unique_ptr<QFile> m_source; + bool m_transferring; +}; + +#endif // FILEPUSHSERVICE_H diff --git a/client/main.cpp b/client/main.cpp new file mode 100644 index 0000000..c3a5449 --- /dev/null +++ b/client/main.cpp @@ -0,0 +1,162 @@ +/****************************************************************************** +** +** Copyright (C) 2016 The Qt Company Ltd. +** Contact: http://www.qt.io/licensing/ +** +** This file is part of the Qt Debug Bridge. +** +** $QT_BEGIN_LICENSE:COMM$ +** +** Commercial License Usage +** Licensees holding valid commercial Qt licenses may use this file in +** accordance with the commercial license agreement provided with the +** Software or, alternatively, in accordance with the terms contained in +** a written agreement between you and The Qt Company. For licensing terms +** and conditions see http://www.qt.io/terms-conditions. For further +** information use the contact form at http://www.qt.io/contact-us. +** +** $QT_END_LICENSE$ +** +******************************************************************************/ +#include "../libqdb/usb/usbconnection.h" +#include "../libqdb/protocol/qdbtransport.h" +#include "connection.h" +#include "filepullservice.h" +#include "filepushservice.h" +#include "interruptsignalhandler.h" +#include "processservice.h" + +#include <QtCore/qcommandlineparser.h> +#include <QtCore/qcoreapplication.h> +#include <QtCore/qdebug.h> +#include <QtCore/qloggingcategory.h> +#include <QtCore/qtimer.h> + +void setupFilePullService(Connection *connection, const QString &sourcePath, const QString &sinkPath) +{ + auto *service = new FilePullService{connection}; + + QObject::connect(QCoreApplication::instance(), &QCoreApplication::aboutToQuit, + service, &QObject::deleteLater); + QObject::connect(service, &FilePullService::pulled, + []() { + qDebug() << "File pull finished."; + QCoreApplication::quit(); + }); + QObject::connect(service, &FilePullService::error, []() { + qDebug() << "Error while pulling file."; + QCoreApplication::exit(1); + }); + QObject::connect(service, &Service::initialized, [=]() { + service->pull(sourcePath, sinkPath); + }); + + service->initialize(); +} + +void setupFilePushService(Connection *connection, const QString &sourcePath, const QString &sinkPath) +{ + auto *service = new FilePushService{connection}; + + QObject::connect(QCoreApplication::instance(), &QCoreApplication::aboutToQuit, + service, &QObject::deleteLater); + QObject::connect(service, &FilePushService::pushed, + []() { + qDebug() << "File transfer finished."; + QCoreApplication::quit(); + }); + QObject::connect(service, &FilePushService::error, []() { + qDebug() << "Error while pushing file."; + QCoreApplication::exit(1); + }); + QObject::connect(service, &Service::initialized, [=]() { + service->push(sourcePath, sinkPath); + }); + + service->initialize(); +} + +void setupProcessService(Connection *connection, const QString &processName, const QStringList &arguments) +{ + auto *service = new ProcessService{connection}; + + QObject::connect(QCoreApplication::instance(), &QCoreApplication::aboutToQuit, + service, &QObject::deleteLater); + QObject::connect(service, &ProcessService::executed, + [](int exitCode, QProcess::ExitStatus exitStatus, QString output) { + qDebug() << "Process run, exit code:" << exitCode << exitStatus << output; + QTimer::singleShot(1, []() { QCoreApplication::quit(); }); + }); + QObject::connect(service, &ProcessService::executionError, [](QProcess::ProcessError error) { + qDebug() << "Process not run, error:" << error; + QTimer::singleShot(1, []() {QCoreApplication::exit(1); }); + }); + QObject::connect(service, &ProcessService::started, []() { + qDebug() << "Process started"; + }); + QObject::connect(service, &ProcessService::readyRead, [=]() { + qDebug() << "Process output:" << service->read(); + }); + QObject::connect(service, &Service::initialized, [=]() { + service->execute(processName, arguments); + }); + + service->initialize(); +} + +int main(int argc, char *argv[]) +{ + QCoreApplication app{argc, argv}; + InterruptSignalHandler signalHandler; + + QCommandLineParser parser; + parser.addHelpOption(); + parser.addOption({{"d","debug-transport"}, "Print each message that is sent."}); + parser.addOption({"debug-connection", "Show enqueued messages"}); + parser.addPositionalArgument("command", + "Subcommand of qdb to run. Possible commands are: " + "run, push, pull"); + parser.process(app); + + QString filterRules; + if (!parser.isSet("debug-transport")) + filterRules.append("transport=false\n"); + if (!parser.isSet("debug-connection")) + filterRules.append("connection=false\n"); + QLoggingCategory::setFilterRules(filterRules); + + Connection connection{new QdbTransport{new UsbConnection{}}}; + if (!connection.initialize()) { + qDebug() << "could not initialize Connection"; + return 1; + } + QObject::connect(&signalHandler, &InterruptSignalHandler::interrupted, [&]() { + connection.close(); + QCoreApplication::exit(130); + }); + + qDebug() << "initialized connection"; + + connection.connect(); + + QStringList arguments = parser.positionalArguments(); + if (arguments.size() < 2) + return 0; + + QString command = arguments[0]; + if (command == "run") { + Q_ASSERT(arguments.size() >= 2); + setupProcessService(&connection, arguments[1], arguments.mid(2)); + } else if (command == "push") { + Q_ASSERT(arguments.size() == 3); + setupFilePushService(&connection, arguments[1], arguments[2]); + } else if (command == "pull") { + Q_ASSERT(arguments.size() == 3); + setupFilePullService(&connection, arguments[1], arguments[2]); + } else { + qDebug() << "Unrecognized command:" << command; + return 1; + } + + return app.exec(); +} diff --git a/client/processservice.cpp b/client/processservice.cpp new file mode 100644 index 0000000..8a249c8 --- /dev/null +++ b/client/processservice.cpp @@ -0,0 +1,119 @@ +/****************************************************************************** +** +** Copyright (C) 2016 The Qt Company Ltd. +** Contact: http://www.qt.io/licensing/ +** +** This file is part of the Qt Debug Bridge. +** +** $QT_BEGIN_LICENSE:COMM$ +** +** Commercial License Usage +** Licensees holding valid commercial Qt licenses may use this file in +** accordance with the commercial license agreement provided with the +** Software or, alternatively, in accordance with the terms contained in +** a written agreement between you and The Qt Company. For licensing terms +** and conditions see http://www.qt.io/terms-conditions. For further +** information use the contact form at http://www.qt.io/contact-us. +** +** $QT_END_LICENSE$ +** +******************************************************************************/ +#include "processservice.h" + +#include "connection.h" +#include "processcommon.h" +#include "protocol/services.h" +#include "stream.h" + +#include <QtCore/qbytearray.h> +#include <QtCore/qdatastream.h> +#include <QtCore/qdebug.h> + +ProcessService::ProcessService(Connection *connection) + : m_connection{connection} +{ + +} + +void ProcessService::initialize() +{ + m_connection->createStream(tagBuffer(ProcessTag), [=](Stream *stream) { + this->streamCreated(stream); + }); +} + +bool ProcessService::execute(const QString &process, const QStringList &arguments) +{ + if (!m_stream) { + qCritical() << "No valid stream in ProcessService when trying to send"; + return false; + } + + StreamPacket packet; + packet << ProcessStart << process << arguments; + + return m_stream->write(packet); +} + +QByteArray ProcessService::read() +{ + if (m_reads.isEmpty()) { + qWarning() << "ProcessService::read(): read from empty queue"; + return QByteArray{}; + } + return m_reads.dequeue(); +} + +qint64 ProcessService::write(const QByteArray &data) +{ + if (!m_stream) { + qCritical() << "No valid stream in ProcessService when trying to write"; + return -1; + } + + StreamPacket packet; + packet << ProcessWrite << data; + + return m_stream->write(packet) ? data.size() : -1; +} + +void ProcessService::receive(StreamPacket packet) +{ + uint32_t typeValue; + packet >> typeValue; + auto type = toProcessPacketType(typeValue); + switch (type) { + case ProcessStarted: { + emit started(); + break; + } + case ProcessRead: { + QByteArray buffer; + packet >> buffer; + m_reads.enqueue(buffer); + emit readyRead(); + break; + } + case ProcessError: { + uint32_t errorValue; + packet >> errorValue; + emit executionError(static_cast<QProcess::ProcessError>(errorValue)); + break; + } + case ProcessFinished: { + int32_t exitCode; + bool normalExit; + QByteArray output; + packet >> exitCode >> normalExit >> output; + + QProcess::ExitStatus exitStatus = normalExit ? QProcess::NormalExit : QProcess::CrashExit; + + emit executed(exitCode, exitStatus, output); + break; + } + default: + Q_ASSERT_X(false, "ProcessService::receive", "Unsupported ProcessPacketType"); + break; + } + +} diff --git a/client/processservice.h b/client/processservice.h new file mode 100644 index 0000000..78a19e5 --- /dev/null +++ b/client/processservice.h @@ -0,0 +1,57 @@ +/****************************************************************************** +** +** Copyright (C) 2016 The Qt Company Ltd. +** Contact: http://www.qt.io/licensing/ +** +** This file is part of the Qt Debug Bridge. +** +** $QT_BEGIN_LICENSE:COMM$ +** +** Commercial License Usage +** Licensees holding valid commercial Qt licenses may use this file in +** accordance with the commercial license agreement provided with the +** Software or, alternatively, in accordance with the terms contained in +** a written agreement between you and The Qt Company. For licensing terms +** and conditions see http://www.qt.io/terms-conditions. For further +** information use the contact form at http://www.qt.io/contact-us. +** +** $QT_END_LICENSE$ +** +******************************************************************************/ +#ifndef PROCESSSERVICE_H +#define PROCESSSERVICE_H + +class Connection; +class Stream; +#include "service.h" + +#include <QtCore/qbytearray.h> +#include <QtCore/qprocess.h> +#include <QtCore/qqueue.h> + +class ProcessService : public Service +{ + Q_OBJECT +public: + explicit ProcessService(Connection *connection); + + void initialize() override; + + bool execute(const QString &process, const QStringList &arguments); + QByteArray read(); + qint64 write(const QByteArray &data); +signals: + void executed(int exitCode, QProcess::ExitStatus exitStatus, QByteArray output); + void executionError(QProcess::ProcessError error); + void readyRead(); + void started(); + +public slots: + void receive(StreamPacket packet) override; + +private: + Connection *m_connection; + QQueue<QByteArray> m_reads; +}; + +#endif // PROCESSSERVICE_H diff --git a/client/service.cpp b/client/service.cpp new file mode 100644 index 0000000..55e9f4c --- /dev/null +++ b/client/service.cpp @@ -0,0 +1,46 @@ +/****************************************************************************** +** +** Copyright (C) 2016 The Qt Company Ltd. +** Contact: http://www.qt.io/licensing/ +** +** This file is part of the Qt Debug Bridge. +** +** $QT_BEGIN_LICENSE:COMM$ +** +** Commercial License Usage +** Licensees holding valid commercial Qt licenses may use this file in +** accordance with the commercial license agreement provided with the +** Software or, alternatively, in accordance with the terms contained in +** a written agreement between you and The Qt Company. For licensing terms +** and conditions see http://www.qt.io/terms-conditions. For further +** information use the contact form at http://www.qt.io/contact-us. +** +** $QT_END_LICENSE$ +** +******************************************************************************/ +#include "service.h" + +#include "stream.h" + +#include <QtCore/qdebug.h> + +Service::Service() + : m_stream{nullptr} +{ + +} + +void Service::streamCreated(Stream *stream) +{ + if (stream) { + m_stream = stream; + connect(m_stream, &Stream::packetAvailable, this, &Service::receive); + connect(m_stream, &Stream::closed, this, &Service::onStreamClosed); + emit initialized(); + } +} + +void Service::onStreamClosed() +{ + m_stream = nullptr; +} diff --git a/client/service.h b/client/service.h new file mode 100644 index 0000000..7f8f743 --- /dev/null +++ b/client/service.h @@ -0,0 +1,52 @@ +/****************************************************************************** +** +** Copyright (C) 2016 The Qt Company Ltd. +** Contact: http://www.qt.io/licensing/ +** +** This file is part of the Qt Debug Bridge. +** +** $QT_BEGIN_LICENSE:COMM$ +** +** Commercial License Usage +** Licensees holding valid commercial Qt licenses may use this file in +** accordance with the commercial license agreement provided with the +** Software or, alternatively, in accordance with the terms contained in +** a written agreement between you and The Qt Company. For licensing terms +** and conditions see http://www.qt.io/terms-conditions. For further +** information use the contact form at http://www.qt.io/contact-us. +** +** $QT_END_LICENSE$ +** +******************************************************************************/ +#ifndef SERVICE_H +#define SERVICE_H + +class Stream; +#include "streampacket.h" + +#include <QtCore/qobject.h> + +class Service : public QObject +{ + Q_OBJECT +public: + Service(); + virtual ~Service() { } + + virtual void initialize() = 0; + virtual void streamCreated(Stream *stream); + +signals: + void initialized(); + +public slots: + virtual void receive(StreamPacket) = 0; + +private slots: + virtual void onStreamClosed(); + +protected: + Stream *m_stream; +}; + +#endif // SERVICE_H diff --git a/doc/streams-device.uml b/doc/streams-device.uml new file mode 100644 index 0000000..94e4a39 --- /dev/null +++ b/doc/streams-device.uml @@ -0,0 +1,33 @@ +@startuml + +title Streams from device perspective + +Transport -> Server : receive CONNECT 0 0 +Transport <- Server : send CONNECT 0 0 +Transport -> Server : receive OPEN 1 0 tag +Server -> createExecutor : createExecutor(stream1, tag) +note right: Server provides an already opened Stream +create ProcessExecutor +createExecutor -> ProcessExecutor : new ProcessExecutor(Stream *stream1) +Transport <- Server : send OK 1 1 +Transport -> Server : receive WRITE 1 1 +create control Stream1 +Server -> Stream1 : receiveMessage() +Stream1 -> ProcessExecutor : packetAvailable() +note right: command to run +Transport <- Server : send OK 1 1 +create QProcess +ProcessExecutor -> QProcess : start() +ProcessExecutor <- QProcess : finished() +Stream1 <- ProcessExecutor : write() +note right: return code and command output +Server <- Stream1 : enqueueMessage() +Transport <- Server : send WRITE 1 1 +Transport -> Server : receive OK 1 1 +Transport -> Server : receive CLOSE 1 1 +Server -> ProcessExecutor : delete +destroy ProcessExecutor +Server -> Stream1 : close() +destroy Stream1 + +@enduml diff --git a/doc/streams-host.uml b/doc/streams-host.uml new file mode 100644 index 0000000..73570d2 --- /dev/null +++ b/doc/streams-host.uml @@ -0,0 +1,43 @@ +@startuml + +actor User + +title Streams from host perspective + +Connection -> Transport : send CONNECT 0 0 +Connection <- Transport : receive CONNECT 0 0 +User -> ProcessService : start() +note right: process to run +ProcessService -> Connection : createStream(streamCreatedCallback) +Connection -> Transport : send OPEN 1 0 +Connection <- Transport : receive OK 1 1 +note right: tells the device-side stream ID +ProcessService <- Connection : streamCreatedCallback() +create control Stream1 +ProcessService -> Stream1 : write() +note right: process to run +Stream1 -> Connection : enqueueMessage() +Connection -> Transport : send WRITE 1 1 +Connection <- Transport : receive OK 1 1 +Connection <- Transport : receive WRITE 1 1 +note right: block of output +Stream1 <- Connection : receiveMessage() +ProcessService <- Stream1 : packetAvailable() +User <- ProcessService : readyRead() +User -> ProcessService : read() +Connection -> Transport : send OK 1 1 +Connection <- Transport : receive WRITE 1 1 +note right: exit code and final block of output +Connection -> Transport : send OK 1 1 +Stream1 <- Connection : receiveMessage() +ProcessService <- Stream1 : packetAvailable() +User <- ProcessService : readyRead() +User <- ProcessService : finished() +User -> ProcessService : read() +User -> ProcessService : exitCode() +ProcessService -> Stream1 : close() +Stream1 -> Connection : closeStream() +destroy Stream1 +Connection <- Transport : send CLOSE 1 1 + +@enduml diff --git a/libqdb/abstractconnection.cpp b/libqdb/abstractconnection.cpp new file mode 100644 index 0000000..7f54646 --- /dev/null +++ b/libqdb/abstractconnection.cpp @@ -0,0 +1,44 @@ +/****************************************************************************** +** +** Copyright (C) 2016 The Qt Company Ltd. +** Contact: http://www.qt.io/licensing/ +** +** This file is part of the Qt Debug Bridge. +** +** $QT_BEGIN_LICENSE:COMM$ +** +** Commercial License Usage +** Licensees holding valid commercial Qt licenses may use this file in +** accordance with the commercial license agreement provided with the +** Software or, alternatively, in accordance with the terms contained in +** a written agreement between you and The Qt Company. For licensing terms +** and conditions see http://www.qt.io/terms-conditions. For further +** information use the contact form at http://www.qt.io/contact-us. +** +** $QT_END_LICENSE$ +** +******************************************************************************/ +#include "abstractconnection.h" +#include "protocol/qdbtransport.h" + + +AbstractConnection::AbstractConnection(QdbTransport *transport, QObject *parent) + : QObject{parent}, + m_transport{transport}, + m_outgoingMessages{}, + m_streams{}, + m_nextStreamId{1} // Start from 1 since stream IDs of 0 have special meaning +{ + +} + +AbstractConnection::~AbstractConnection() +{ + +} + +bool AbstractConnection::initialize() +{ + connect(m_transport.get(), &QdbTransport::messageAvailable, this, &AbstractConnection::handleMessage); + return m_transport->open(); +} diff --git a/libqdb/abstractconnection.h b/libqdb/abstractconnection.h new file mode 100644 index 0000000..eb25c57 --- /dev/null +++ b/libqdb/abstractconnection.h @@ -0,0 +1,54 @@ +/****************************************************************************** +** +** Copyright (C) 2016 The Qt Company Ltd. +** Contact: http://www.qt.io/licensing/ +** +** This file is part of the Qt Debug Bridge. +** +** $QT_BEGIN_LICENSE:COMM$ +** +** Commercial License Usage +** Licensees holding valid commercial Qt licenses may use this file in +** accordance with the commercial license agreement provided with the +** Software or, alternatively, in accordance with the terms contained in +** a written agreement between you and The Qt Company. For licensing terms +** and conditions see http://www.qt.io/terms-conditions. For further +** information use the contact form at http://www.qt.io/contact-us. +** +** $QT_END_LICENSE$ +** +******************************************************************************/ +#ifndef ABSTRACTCONNECTION_H +#define ABSTRACTCONNECTION_H + +#include "protocol/qdbmessage.h" +#include "stream.h" +class QdbTransport; + +#include <QtCore/qobject.h> +#include <QtCore/qqueue.h> + +#include <memory> +#include <unordered_map> + +class AbstractConnection : public QObject +{ + Q_OBJECT +public: + AbstractConnection(QdbTransport *transport, QObject *parent = 0); + virtual ~AbstractConnection(); + + virtual bool initialize(); + virtual void enqueueMessage(const QdbMessage &message) = 0; + +public slots: + virtual void handleMessage() = 0; + +protected: + std::unique_ptr<QdbTransport> m_transport; + QQueue<QdbMessage> m_outgoingMessages; + std::unordered_map<StreamId, std::unique_ptr<Stream>> m_streams; + StreamId m_nextStreamId; +}; + +#endif // ABSTRACTCONNECTION_H diff --git a/libqdb/filepullcommon.h b/libqdb/filepullcommon.h new file mode 100644 index 0000000..b4cdf43 --- /dev/null +++ b/libqdb/filepullcommon.h @@ -0,0 +1,61 @@ +/****************************************************************************** +** +** Copyright (C) 2016 The Qt Company Ltd. +** Contact: http://www.qt.io/licensing/ +** +** This file is part of the Qt Debug Bridge. +** +** $QT_BEGIN_LICENSE:COMM$ +** +** Commercial License Usage +** Licensees holding valid commercial Qt licenses may use this file in +** accordance with the commercial license agreement provided with the +** Software or, alternatively, in accordance with the terms contained in +** a written agreement between you and The Qt Company. For licensing terms +** and conditions see http://www.qt.io/terms-conditions. For further +** information use the contact form at http://www.qt.io/contact-us. +** +** $QT_END_LICENSE$ +** +******************************************************************************/ +#ifndef FILEPULLCOMMON_H +#define FILEPULLCOMMON_H + +#include <QtGlobal> + +#include <cstdint> + +enum FilePullPacketType : uint32_t +{ + FilePullOpen = 1, + FilePullOpened, + FilePullRead, + FilePullWasRead, + FilePullEnd, + FilePullError, +}; + +inline +FilePullPacketType toFilePullPacketType(uint32_t x) +{ + switch (static_cast<FilePullPacketType>(x)) + { + case FilePullOpen: + return FilePullOpen; + case FilePullOpened: + return FilePullOpened; + case FilePullRead: + return FilePullRead; + case FilePullWasRead: + return FilePullWasRead; + case FilePullEnd: + return FilePullEnd; + case FilePullError: + return FilePullError; + default: + Q_UNREACHABLE(); + return FilePullError; + } +} + +#endif // FILEPULLCOMMON_H diff --git a/libqdb/filepushcommon.h b/libqdb/filepushcommon.h new file mode 100644 index 0000000..35537a7 --- /dev/null +++ b/libqdb/filepushcommon.h @@ -0,0 +1,61 @@ +/****************************************************************************** +** +** Copyright (C) 2016 The Qt Company Ltd. +** Contact: http://www.qt.io/licensing/ +** +** This file is part of the Qt Debug Bridge. +** +** $QT_BEGIN_LICENSE:COMM$ +** +** Commercial License Usage +** Licensees holding valid commercial Qt licenses may use this file in +** accordance with the commercial license agreement provided with the +** Software or, alternatively, in accordance with the terms contained in +** a written agreement between you and The Qt Company. For licensing terms +** and conditions see http://www.qt.io/terms-conditions. For further +** information use the contact form at http://www.qt.io/contact-us. +** +** $QT_END_LICENSE$ +** +******************************************************************************/ +#ifndef FILEPUSHCOMMON_H +#define FILEPUSHCOMMON_H + +#include <QtGlobal> + +#include <cstdint> + +enum FilePushPacketType : uint32_t +{ + FilePushOpen = 1, + FilePushOpened, + FilePushWrite, + FilePushWritten, + FilePushEnd, + FilePushError, +}; + +inline +FilePushPacketType toFilePushPacketType(uint32_t x) +{ + switch (static_cast<FilePushPacketType>(x)) + { + case FilePushOpen: + return FilePushOpen; + case FilePushOpened: + return FilePushOpened; + case FilePushWrite: + return FilePushWrite; + case FilePushWritten: + return FilePushWritten; + case FilePushEnd: + return FilePushEnd; + case FilePushError: + return FilePushError; + default: + Q_UNREACHABLE(); + return FilePushError; + } +} + +#endif // FILEPUSHCOMMON_H diff --git a/libqdb/interruptsignalhandler.cpp b/libqdb/interruptsignalhandler.cpp new file mode 100644 index 0000000..067be36 --- /dev/null +++ b/libqdb/interruptsignalhandler.cpp @@ -0,0 +1,82 @@ +/****************************************************************************** +** +** Copyright (C) 2016 The Qt Company Ltd. +** Contact: http://www.qt.io/licensing/ +** +** This file is part of the Qt Debug Bridge. +** +** $QT_BEGIN_LICENSE:COMM$ +** +** Commercial License Usage +** Licensees holding valid commercial Qt licenses may use this file in +** accordance with the commercial license agreement provided with the +** Software or, alternatively, in accordance with the terms contained in +** a written agreement between you and The Qt Company. For licensing terms +** and conditions see http://www.qt.io/terms-conditions. For further +** information use the contact form at http://www.qt.io/contact-us. +** +** $QT_END_LICENSE$ +** +******************************************************************************/ +#include "interruptsignalhandler.h" + +#include "../utils/make_unique.h" + +#include <QtCore/qdebug.h> +#include <QtCore/qsocketnotifier.h> + +#include <signal.h> +#include <sys/socket.h> +#include <unistd.h> + +InterruptSignalHandler::InterruptSignalHandler(QObject *parent) + : QObject(parent), + m_socketNotifier{nullptr} +{ + if (socketpair(AF_UNIX, SOCK_STREAM, 0, s_socketPair)) + qFatal("Could not create socketpair for signal handling in InterruptSignalHandler"); + + m_socketNotifier = make_unique<QSocketNotifier>(s_socketPair[1], QSocketNotifier::Read); + connect(m_socketNotifier.get(), SIGNAL(activated(int)), this, SLOT(handleSigInt())); + + if (!installSigIntHandler()) + qFatal("Could not install signal handler in InterruptSignalHandler"); +} + +int InterruptSignalHandler::s_socketPair[2] = {0, 0}; + +InterruptSignalHandler::~InterruptSignalHandler() = default; + +void InterruptSignalHandler::sigIntHandler(int signalId) +{ + Q_UNUSED(signalId); + + char a = 1; + write(s_socketPair[0], &a, sizeof(a)); +} + +void InterruptSignalHandler::handleSigInt() +{ + char tmp; + read(s_socketPair[1], &tmp, sizeof(tmp)); + + // Reset signal action to allow a second Control-C to interrupt hanging qdb + sigaction(SIGINT, &m_oldAction, nullptr); + + emit interrupted(); +} + +bool InterruptSignalHandler::installSigIntHandler() +{ + struct sigaction action; + action.sa_handler = &InterruptSignalHandler::sigIntHandler; + sigemptyset(&action.sa_mask); + action.sa_flags = 0; + action.sa_flags |= SA_RESTART; + + if (sigaction(SIGINT, &action, &m_oldAction)) + return false; + + return true; +} + diff --git a/libqdb/interruptsignalhandler.h b/libqdb/interruptsignalhandler.h new file mode 100644 index 0000000..4e59efe --- /dev/null +++ b/libqdb/interruptsignalhandler.h @@ -0,0 +1,58 @@ +/****************************************************************************** +** +** Copyright (C) 2016 The Qt Company Ltd. +** Contact: http://www.qt.io/licensing/ +** +** This file is part of the Qt Debug Bridge. +** +** $QT_BEGIN_LICENSE:COMM$ +** +** Commercial License Usage +** Licensees holding valid commercial Qt licenses may use this file in +** accordance with the commercial license agreement provided with the +** Software or, alternatively, in accordance with the terms contained in +** a written agreement between you and The Qt Company. For licensing terms +** and conditions see http://www.qt.io/terms-conditions. For further +** information use the contact form at http://www.qt.io/contact-us. +** +** $QT_END_LICENSE$ +** +******************************************************************************/ +#ifndef INTERRUPTSIGNALHANDLER_H +#define INTERRUPTSIGNALHANDLER_H + +#include <QObject> +class QSocketNotifier; + +#include <memory> + +#ifndef Q_OS_UNIX +# error "InterruptSignalHandler only supports UNIX signals" +#endif + +#include <signal.h> + +class InterruptSignalHandler : public QObject +{ + Q_OBJECT +public: + explicit InterruptSignalHandler(QObject *parent = 0); + ~InterruptSignalHandler(); + + static void sigIntHandler(int signalId); + +signals: + void interrupted(); + +public slots: + void handleSigInt(); + +private: + bool installSigIntHandler(); + + static int s_socketPair[2]; + struct sigaction m_oldAction; + std::unique_ptr<QSocketNotifier> m_socketNotifier; +}; + +#endif // INTERRUPTSIGNALHANDLER_H diff --git a/libqdb/libqdb.pro b/libqdb/libqdb.pro new file mode 100644 index 0000000..5e1a5c9 --- /dev/null +++ b/libqdb/libqdb.pro @@ -0,0 +1,42 @@ +QT -= gui + +TARGET = qdb +TEMPLATE = lib + +DEFINES += QDB_LIBRARY + +SOURCES += \ + abstractconnection.cpp \ + interruptsignalhandler.cpp \ + protocol/qdbmessage.cpp \ + protocol/qdbtransport.cpp \ + stream.cpp \ + streampacket.cpp \ + usb/usbconnection.cpp \ + usb/usbconnectionreader.cpp \ + +HEADERS += \ + abstractconnection.h \ + filepullcommon.h \ + filepushcommon.h \ + interruptsignalhandler.h \ + libqdb_global.h \ + processcommon.h \ + protocol/protocol.h \ + protocol/qdbmessage.h \ + protocol/qdbtransport.h \ + protocol/services.h \ + stream.h \ + streampacket.h \ + usb/usbconnection.h \ + usb/usbconnectionreader.h \ + +INCLUDEPATH += $$PWD + +INCLUDEPATH += $$[QT_SYSROOT]/usr/include/libusb-1.0/ +LIBS += -lusb-1.0 + +unix { + target.path = /usr/lib + INSTALLS += target +} diff --git a/libqdb/libqdb_global.h b/libqdb/libqdb_global.h new file mode 100644 index 0000000..86e9393 --- /dev/null +++ b/libqdb/libqdb_global.h @@ -0,0 +1,32 @@ +/****************************************************************************** +** +** Copyright (C) 2016 The Qt Company Ltd. +** Contact: http://www.qt.io/licensing/ +** +** This file is part of the Qt Debug Bridge. +** +** $QT_BEGIN_LICENSE:COMM$ +** +** Commercial License Usage +** Licensees holding valid commercial Qt licenses may use this file in +** accordance with the commercial license agreement provided with the +** Software or, alternatively, in accordance with the terms contained in +** a written agreement between you and The Qt Company. For licensing terms +** and conditions see http://www.qt.io/terms-conditions. For further +** information use the contact form at http://www.qt.io/contact-us. +** +** $QT_END_LICENSE$ +** +******************************************************************************/ +#ifndef LIBQDB_GLOBAL_H +#define LIBQDB_GLOBAL_H + +#include <QtCore/qglobal.h> + +#if defined(LIBQDB_LIBRARY) +# define LIBQDBSHARED_EXPORT Q_DECL_EXPORT +#else +# define LIBQDBSHARED_EXPORT Q_DECL_IMPORT +#endif + +#endif // LIBQDB_GLOBAL_H diff --git a/libqdb/processcommon.h b/libqdb/processcommon.h new file mode 100644 index 0000000..d7dbc31 --- /dev/null +++ b/libqdb/processcommon.h @@ -0,0 +1,61 @@ +/****************************************************************************** +** +** Copyright (C) 2016 The Qt Company Ltd. +** Contact: http://www.qt.io/licensing/ +** +** This file is part of the Qt Debug Bridge. +** +** $QT_BEGIN_LICENSE:COMM$ +** +** Commercial License Usage +** Licensees holding valid commercial Qt licenses may use this file in +** accordance with the commercial license agreement provided with the +** Software or, alternatively, in accordance with the terms contained in +** a written agreement between you and The Qt Company. For licensing terms +** and conditions see http://www.qt.io/terms-conditions. For further +** information use the contact form at http://www.qt.io/contact-us. +** +** $QT_END_LICENSE$ +** +******************************************************************************/ +#ifndef PROCESSCOMMON_H +#define PROCESSCOMMON_H + +#include <QtGlobal> + +#include <cstdint> + +enum ProcessPacketType : uint32_t +{ + ProcessStart = 1, + ProcessStarted, + ProcessRead, + ProcessWrite, + ProcessError, + ProcessFinished, +}; + +inline +ProcessPacketType toProcessPacketType(uint32_t x) +{ + switch (static_cast<ProcessPacketType>(x)) + { + case ProcessStart: + return ProcessStart; + case ProcessStarted: + return ProcessStarted; + case ProcessRead: + return ProcessRead; + case ProcessWrite: + return ProcessWrite; + case ProcessError: + return ProcessError; + case ProcessFinished: + return ProcessFinished; + default: + Q_UNREACHABLE(); // all possible values are covered + return ProcessError; + } +} + +#endif // PROCESSCOMMON_H diff --git a/libqdb/protocol/protocol.h b/libqdb/protocol/protocol.h new file mode 100644 index 0000000..5878e52 --- /dev/null +++ b/libqdb/protocol/protocol.h @@ -0,0 +1,31 @@ +/****************************************************************************** +** +** Copyright (C) 2016 The Qt Company Ltd. +** Contact: http://www.qt.io/licensing/ +** +** This file is part of the Qt Debug Bridge. +** +** $QT_BEGIN_LICENSE:COMM$ +** +** Commercial License Usage +** Licensees holding valid commercial Qt licenses may use this file in +** accordance with the commercial license agreement provided with the +** Software or, alternatively, in accordance with the terms contained in +** a written agreement between you and The Qt Company. For licensing terms +** and conditions see http://www.qt.io/terms-conditions. For further +** information use the contact form at http://www.qt.io/contact-us. +** +** $QT_END_LICENSE$ +** +******************************************************************************/ +#ifndef PROTOCOL_H +#define PROTOCOL_H + +const uint8_t qdbUsbClassId = 0xff; +const uint8_t qdbUsbSubclassId = 0x52; +const uint8_t qdbUsbProtocolId = 0x1; +const int qdbHeaderSize = 4*sizeof(uint32_t); +const int qdbMessageSize = 16*1024; +const int qdbMaxPayloadSize = qdbMessageSize - qdbHeaderSize; + +#endif diff --git a/libqdb/protocol/qdbmessage.cpp b/libqdb/protocol/qdbmessage.cpp new file mode 100644 index 0000000..9a9f26f --- /dev/null +++ b/libqdb/protocol/qdbmessage.cpp @@ -0,0 +1,190 @@ +/****************************************************************************** +** +** Copyright (C) 2016 The Qt Company Ltd. +** Contact: http://www.qt.io/licensing/ +** +** This file is part of the Qt Debug Bridge. +** +** $QT_BEGIN_LICENSE:COMM$ +** +** Commercial License Usage +** Licensees holding valid commercial Qt licenses may use this file in +** accordance with the commercial license agreement provided with the +** Software or, alternatively, in accordance with the terms contained in +** a written agreement between you and The Qt Company. For licensing terms +** and conditions see http://www.qt.io/terms-conditions. For further +** information use the contact form at http://www.qt.io/contact-us. +** +** $QT_END_LICENSE$ +** +******************************************************************************/ +#include "qdbmessage.h" + +#include <QtCore/qdatastream.h> +#include <QtCore/qdebug.h> +#include <QtEndian> + +int QdbMessage::GetDataSize(const QByteArray &header) +{ + const uint32_t sizeFieldBE = *(reinterpret_cast<const uint32_t *>(header.data()) + 3); + const uint32_t sizeField = qFromBigEndian(sizeFieldBE); + + if (sizeField == 0xFFFFFFFF) { + // empty QByteArray + return 0; + } + Q_ASSERT(sizeField <= std::numeric_limits<int>::max()); + return sizeField; +} + +QdbMessage::QdbMessage() + : QdbMessage{Invalid, 0, 0, QByteArray{}} +{ + +} + +QdbMessage::QdbMessage(QdbMessage::CommandType command, StreamId hostStream, StreamId deviceStream) + : QdbMessage{command, hostStream, deviceStream, QByteArray{}} +{ + +} + +QdbMessage::QdbMessage(QdbMessage::CommandType command, StreamId hostStream, StreamId deviceStream, QByteArray data) + : m_command{command}, + m_hostStream{hostStream}, + m_deviceStream{deviceStream}, + m_data{data} +{ + +} + +QdbMessage::QdbMessage(QdbMessage::CommandType command, StreamId hostStream, StreamId deviceStream, const char *data, int length) + : QdbMessage{command, hostStream, deviceStream, QByteArray{data, length}} +{ + +} + +QdbMessage::CommandType QdbMessage::command() const +{ + return m_command; +} + +void QdbMessage::setCommand(QdbMessage::CommandType command) +{ + m_command = command; +} + +StreamId QdbMessage::hostStream() const +{ + return m_hostStream; +} + +void QdbMessage::setHostStream(StreamId hostStream) +{ + m_hostStream = hostStream; +} + +StreamId QdbMessage::deviceStream() const +{ + return m_deviceStream; +} + +void QdbMessage::setDeviceStream(StreamId deviceStream) +{ + m_deviceStream = deviceStream; +} + +const QByteArray &QdbMessage::data() const +{ + return m_data; +} + +void QdbMessage::setData(const QByteArray &data) +{ + m_data = data; +} + +void QdbMessage::setData(const char *data, int length) +{ + m_data = QByteArray{data, length}; +} + +QDataStream &operator<<(QDataStream &stream, const QdbMessage &message) +{ + stream << message.command(); + stream << message.hostStream() << message.deviceStream(); + stream << message.data(); + + return stream; +} + +QdbMessage::CommandType toCommandType(uint32_t command) +{ + switch (command) { + case static_cast<uint32_t>(QdbMessage::Connect): + return QdbMessage::Connect; + case static_cast<uint32_t>(QdbMessage::Open): + return QdbMessage::Open; + case static_cast<uint32_t>(QdbMessage::Write): + return QdbMessage::Write; + case static_cast<uint32_t>(QdbMessage::Close): + return QdbMessage::Close; + case static_cast<uint32_t>(QdbMessage::Ok): + return QdbMessage::Ok; + } + return QdbMessage::Invalid; +} + +QDataStream &operator>>(QDataStream &stream, QdbMessage &message) +{ + uint32_t command; + stream >> command; + message.setCommand(toCommandType(command)); + + StreamId hostStream; + stream >> hostStream; + message.setHostStream(hostStream); + + StreamId deviceStream; + stream >> deviceStream; + message.setDeviceStream(deviceStream); + + QByteArray data; + stream >> data; + message.setData(data); + + return stream; +} + +QDebug &operator<<(QDebug &stream, QdbMessage::CommandType command) +{ + switch (command) { + case QdbMessage::Invalid: + stream << "Invalid"; + break; + case QdbMessage::Connect: + stream << "Connect"; + break; + case QdbMessage::Open: + stream << "Open"; + break; + case QdbMessage::Write: + stream << "Write"; + break; + case QdbMessage::Close: + stream << "Close"; + break; + case QdbMessage::Ok: + stream << "Ok"; + break; + } + return stream; +} + +QDebug &operator<<(QDebug &stream, const QdbMessage &message) +{ + stream << message.command() << message.hostStream() << message.deviceStream() << + message.data(); + + return stream; +} diff --git a/libqdb/protocol/qdbmessage.h b/libqdb/protocol/qdbmessage.h new file mode 100644 index 0000000..6c87635 --- /dev/null +++ b/libqdb/protocol/qdbmessage.h @@ -0,0 +1,78 @@ +/****************************************************************************** +** +** Copyright (C) 2016 The Qt Company Ltd. +** Contact: http://www.qt.io/licensing/ +** +** This file is part of the Qt Debug Bridge. +** +** $QT_BEGIN_LICENSE:COMM$ +** +** Commercial License Usage +** Licensees holding valid commercial Qt licenses may use this file in +** accordance with the commercial license agreement provided with the +** Software or, alternatively, in accordance with the terms contained in +** a written agreement between you and The Qt Company. For licensing terms +** and conditions see http://www.qt.io/terms-conditions. For further +** information use the contact form at http://www.qt.io/contact-us. +** +** $QT_END_LICENSE$ +** +******************************************************************************/ +#ifndef QDBMESSAGE_H +#define QDBMESSAGE_H + +#include <QtCore/qbytearray.h> +#include <QtCore/qmetatype.h> + +class QDataStream; + +using StreamId = uint32_t; + +class QdbMessage +{ +public: + /*! Get the amount of bytes of data in the message payload from the message header. */ + static int GetDataSize(const QByteArray &header); + + enum CommandType : uint32_t + { + Invalid = 0, // never sent + Connect = 0x434e584e, // CNXN + Open = 0x4f50454e, // OPEN + Write = 0x57525445, // WRTE + Close = 0x434c5345, // CLSE + Ok = 0x4f4b4159, // OKAY + }; + + QdbMessage(); + QdbMessage(CommandType command, StreamId hostStream, StreamId deviceStream); + QdbMessage(CommandType command, StreamId hostStream, StreamId deviceStream, QByteArray data); + QdbMessage(CommandType command, StreamId hostStream, StreamId deviceStream, const char *data, int length); + + CommandType command() const; + void setCommand(CommandType command); + + StreamId hostStream() const; + void setHostStream(StreamId hostStream); + + StreamId deviceStream() const; + void setDeviceStream(StreamId deviceStream); + + const QByteArray &data() const; + void setData(const QByteArray &data); + void setData(const char *data, int length); + +private: + CommandType m_command; + StreamId m_hostStream; + StreamId m_deviceStream; + QByteArray m_data; +}; +Q_DECLARE_METATYPE(QdbMessage::CommandType) + +QDebug &operator<<(QDebug &stream, const QdbMessage &message); + +QDataStream &operator<<(QDataStream &stream, const QdbMessage &message); +QDataStream &operator>>(QDataStream &stream, QdbMessage &message); + +#endif // QDBMESSAGE_H diff --git a/libqdb/protocol/qdbtransport.cpp b/libqdb/protocol/qdbtransport.cpp new file mode 100644 index 0000000..d95432b --- /dev/null +++ b/libqdb/protocol/qdbtransport.cpp @@ -0,0 +1,79 @@ +/****************************************************************************** +** +** Copyright (C) 2016 The Qt Company Ltd. +** Contact: http://www.qt.io/licensing/ +** +** This file is part of the Qt Debug Bridge. +** +** $QT_BEGIN_LICENSE:COMM$ +** +** Commercial License Usage +** Licensees holding valid commercial Qt licenses may use this file in +** accordance with the commercial license agreement provided with the +** Software or, alternatively, in accordance with the terms contained in +** a written agreement between you and The Qt Company. For licensing terms +** and conditions see http://www.qt.io/terms-conditions. For further +** information use the contact form at http://www.qt.io/contact-us. +** +** $QT_END_LICENSE$ +** +******************************************************************************/ +#include "qdbtransport.h" + +#include "protocol/protocol.h" + +#include <QtCore/qdatastream.h> +#include <QtCore/qdebug.h> +#include <QtCore/qloggingcategory.h> + +Q_LOGGING_CATEGORY(transportC, "transport"); + +QdbTransport::QdbTransport(QIODevice *io) + : m_io{io} +{ + +} + +QdbTransport::~QdbTransport() +{ + +} + +bool QdbTransport::open() +{ + connect(m_io.get(), &QIODevice::readyRead, this, &QdbTransport::messageAvailable, Qt::QueuedConnection); + return m_io->open(QIODevice::ReadWrite | QIODevice::Unbuffered); +} + +bool QdbTransport::send(const QdbMessage &message) +{ + int messageSize = qdbHeaderSize + message.data().size(); + QByteArray buf{messageSize, '\0'}; + QDataStream stream{&buf, QIODevice::WriteOnly}; + stream << message; + + int count = m_io->write(buf.constData(), messageSize); + if (count != messageSize) { + qCritical() << "QdbTransport::send() could not write entire message of" << messageSize << ", only wrote" << count; + return false; + } + + qCDebug(transportC) << "TX:" << message; + return true; +} + +QdbMessage QdbTransport::receive() +{ + QByteArray buf{qdbMessageSize, '\0'}; + int count = m_io->read(buf.data(), buf.size()); + if (count < qdbHeaderSize) { + qDebug() << "QdbTransport::receive() could only read" << count << "out of package header's" << qdbHeaderSize; + return QdbMessage{QdbMessage::Invalid, 0, 0}; + } + QDataStream stream{buf}; + QdbMessage message; + stream >> message; + qCDebug(transportC) << "RX:" << message; + + return message; +} diff --git a/libqdb/protocol/qdbtransport.h b/libqdb/protocol/qdbtransport.h new file mode 100644 index 0000000..1fe91e4 --- /dev/null +++ b/libqdb/protocol/qdbtransport.h @@ -0,0 +1,52 @@ +/****************************************************************************** +** +** Copyright (C) 2016 The Qt Company Ltd. +** Contact: http://www.qt.io/licensing/ +** +** This file is part of the Qt Debug Bridge. +** +** $QT_BEGIN_LICENSE:COMM$ +** +** Commercial License Usage +** Licensees holding valid commercial Qt licenses may use this file in +** accordance with the commercial license agreement provided with the +** Software or, alternatively, in accordance with the terms contained in +** a written agreement between you and The Qt Company. For licensing terms +** and conditions see http://www.qt.io/terms-conditions. For further +** information use the contact form at http://www.qt.io/contact-us. +** +** $QT_END_LICENSE$ +** +******************************************************************************/ +#ifndef QDBTRANSPORT_H +#define QDBTRANSPORT_H + +#include "protocol/qdbmessage.h" + +#include <QtCore/qobject.h> + +#include <memory> + +class QIODevice; + +class QdbTransport : public QObject +{ + Q_OBJECT +public: + /*! QdbTransport takes ownership of the passed QIODevice. */ + QdbTransport(QIODevice *io); + ~QdbTransport(); + + bool open(); + + bool send(const QdbMessage &message); + QdbMessage receive(); + +signals: + void messageAvailable(); + +private: + std::unique_ptr<QIODevice> m_io; +}; + +#endif // QDBTRANSPORT_H diff --git a/libqdb/protocol/services.h b/libqdb/protocol/services.h new file mode 100644 index 0000000..9b07d62 --- /dev/null +++ b/libqdb/protocol/services.h @@ -0,0 +1,48 @@ +/****************************************************************************** +** +** Copyright (C) 2016 The Qt Company Ltd. +** Contact: http://www.qt.io/licensing/ +** +** This file is part of the Qt Debug Bridge. +** +** $QT_BEGIN_LICENSE:COMM$ +** +** Commercial License Usage +** Licensees holding valid commercial Qt licenses may use this file in +** accordance with the commercial license agreement provided with the +** Software or, alternatively, in accordance with the terms contained in +** a written agreement between you and The Qt Company. For licensing terms +** and conditions see http://www.qt.io/terms-conditions. For further +** information use the contact form at http://www.qt.io/contact-us. +** +** $QT_END_LICENSE$ +** +******************************************************************************/ +#ifndef SERVICES_H +#define SERVICES_H + +#include <QtCore/qbytearray.h> +#include <QtCore/qdatastream.h> + +#include <cstdint> + +enum ServiceTag : uint32_t +{ + EchoTag = 1, + ProcessTag, + FilePushTag, + FilePullTag, +}; + +const int fileTransferBlockSize = 4096; // in bytes + +inline +QByteArray tagBuffer(ServiceTag tag, int padding = 0) +{ + QByteArray buffer{static_cast<int>(sizeof(ServiceTag)) + padding, '\0'}; + QDataStream stream{&buffer, QIODevice::WriteOnly}; + stream << tag; + return buffer; +} + +#endif // SERVICES_H diff --git a/libqdb/stream.cpp b/libqdb/stream.cpp new file mode 100644 index 0000000..940d16b --- /dev/null +++ b/libqdb/stream.cpp @@ -0,0 +1,123 @@ +/****************************************************************************** +** +** Copyright (C) 2016 The Qt Company Ltd. +** Contact: http://www.qt.io/licensing/ +** +** This file is part of the Qt Debug Bridge. +** +** $QT_BEGIN_LICENSE:COMM$ +** +** Commercial License Usage +** Licensees holding valid commercial Qt licenses may use this file in +** accordance with the commercial license agreement provided with the +** Software or, alternatively, in accordance with the terms contained in +** a written agreement between you and The Qt Company. For licensing terms +** and conditions see http://www.qt.io/terms-conditions. For further +** information use the contact form at http://www.qt.io/contact-us. +** +** $QT_END_LICENSE$ +** +******************************************************************************/ +#include "stream.h" + +#include "abstractconnection.h" +#include "protocol/protocol.h" + +#include <QtCore/qdatastream.h> +#include <QtCore/qdebug.h> + +QByteArray wrapPacket(const StreamPacket &packet) +{ + QByteArray buffer{packet.size() + static_cast<int>(sizeof(quint32)), '\0'}; + QDataStream dataStream{&buffer, QIODevice::WriteOnly}; + dataStream << packet.buffer(); + return buffer; +} + +Stream::Stream(AbstractConnection *connection, StreamId hostId, StreamId deviceId) + : m_connection{connection}, + m_hostId{hostId}, + m_deviceId{deviceId}, + m_partlyReceived{false}, + m_incomingSize{0}, + m_incomingData{} +{ + +} + +bool Stream::write(const StreamPacket &packet) +{ + Q_ASSERT(packet.size() > 0); // writing nothing to the stream does not make sense + QByteArray data = wrapPacket(packet); + while (data.size() > 0) { + const int splitSize = std::min(data.size(), qdbMaxPayloadSize); + m_connection->enqueueMessage(QdbMessage{QdbMessage::Write, m_hostId, m_deviceId, + data.left(splitSize)}); + data = data.mid(splitSize); + } + return true; +} + +StreamId Stream::hostId() const +{ + return m_hostId; +} + +StreamId Stream::deviceId() const +{ + return m_deviceId; +} + +void Stream::requestClose() +{ + m_connection->enqueueMessage(QdbMessage{QdbMessage::Close, m_hostId, m_deviceId}); +} + +void Stream::close() +{ + emit closed(); +} + +void Stream::receiveMessage(const QdbMessage &message) +{ + Q_ASSERT(message.command() == QdbMessage::Write); + Q_ASSERT(message.hostStream() == m_hostId); + Q_ASSERT(message.deviceStream() == m_deviceId); + + if (m_partlyReceived) { + const int missing = m_incomingSize - m_incomingData.size() - message.data().size(); + Q_ASSERT_X(missing >= 0, "Stream::receiveMessage", "One QdbMessage must only contain data from a single Stream packet"); + + m_incomingData.append(message.data()); + if (missing > 0) + return; + } else { + int packetSize = 0; + { + QDataStream dataStream{message.data()}; + Q_ASSERT(message.data().size() >= static_cast<int>(sizeof(uint32_t))); + uint32_t size; + dataStream >> size; + packetSize = int(size); + } + + const int dataSize = message.data().size() - static_cast<int>(sizeof(uint32_t)); + Q_ASSERT_X(dataSize <= packetSize, "Stream::receiveMessage", "One QdbMessage must only contain data from a single Stream packet"); + + m_incomingData = message.data().mid(sizeof(uint32_t)); + if (dataSize < packetSize) { + m_partlyReceived = true; + m_incomingSize = packetSize; + return; + } + } + + StreamPacket packet{m_incomingData}; + + m_partlyReceived = false; + m_incomingSize = 0; + m_incomingData.clear(); + + // Emitted last because handling of the signal may lead to closing of stream + emit packetAvailable(packet); +} diff --git a/libqdb/stream.h b/libqdb/stream.h new file mode 100644 index 0000000..e5eb391 --- /dev/null +++ b/libqdb/stream.h @@ -0,0 +1,60 @@ +/****************************************************************************** +** +** Copyright (C) 2016 The Qt Company Ltd. +** Contact: http://www.qt.io/licensing/ +** +** This file is part of the Qt Debug Bridge. +** +** $QT_BEGIN_LICENSE:COMM$ +** +** Commercial License Usage +** Licensees holding valid commercial Qt licenses may use this file in +** accordance with the commercial license agreement provided with the +** Software or, alternatively, in accordance with the terms contained in +** a written agreement between you and The Qt Company. For licensing terms +** and conditions see http://www.qt.io/terms-conditions. For further +** information use the contact form at http://www.qt.io/contact-us. +** +** $QT_END_LICENSE$ +** +******************************************************************************/ +#ifndef STREAM_H +#define STREAM_H + +class AbstractConnection; +#include "protocol/qdbmessage.h" +#include "streampacket.h" + +#include <QtCore/qobject.h> + +class Stream : public QObject +{ + Q_OBJECT +public: + Stream(AbstractConnection *connection, StreamId hostId, StreamId deviceId); + + bool write(const StreamPacket &packet); + + StreamId hostId() const; + StreamId deviceId() const; + + void requestClose(); + // Should only be called by AbstractConnection, use requestClose() instead elsewhere + void close(); +signals: + void packetAvailable(StreamPacket data); + void closed(); + +public slots: + void receiveMessage(const QdbMessage &message); + +private: + AbstractConnection *m_connection; + StreamId m_hostId; + StreamId m_deviceId; + bool m_partlyReceived; + int m_incomingSize; + QByteArray m_incomingData; +}; + +#endif // STREAM_H diff --git a/libqdb/streampacket.cpp b/libqdb/streampacket.cpp new file mode 100644 index 0000000..55a9b01 --- /dev/null +++ b/libqdb/streampacket.cpp @@ -0,0 +1,52 @@ +/****************************************************************************** +** +** Copyright (C) 2016 The Qt Company Ltd. +** Contact: http://www.qt.io/licensing/ +** +** This file is part of the Qt Debug Bridge. +** +** $QT_BEGIN_LICENSE:COMM$ +** +** Commercial License Usage +** Licensees holding valid commercial Qt licenses may use this file in +** accordance with the commercial license agreement provided with the +** Software or, alternatively, in accordance with the terms contained in +** a written agreement between you and The Qt Company. For licensing terms +** and conditions see http://www.qt.io/terms-conditions. For further +** information use the contact form at http://www.qt.io/contact-us. +** +** $QT_END_LICENSE$ +** +******************************************************************************/ +#include "streampacket.h" + +StreamPacket::StreamPacket() + : m_buffer{}, + m_dataStream{&m_buffer, QIODevice::WriteOnly} +{ + +} + +StreamPacket::StreamPacket(const QByteArray &data) + : m_buffer{data}, + m_dataStream{m_buffer} +{ + +} + +StreamPacket::StreamPacket(const StreamPacket &other) + : m_buffer{other.buffer()}, + m_dataStream{m_buffer} +{ + +} + +const QByteArray &StreamPacket::buffer() const +{ + return m_buffer; +} + +int StreamPacket::size() const +{ + return m_buffer.size(); +} diff --git a/libqdb/streampacket.h b/libqdb/streampacket.h new file mode 100644 index 0000000..42934c8 --- /dev/null +++ b/libqdb/streampacket.h @@ -0,0 +1,61 @@ +/****************************************************************************** +** +** Copyright (C) 2016 The Qt Company Ltd. +** Contact: http://www.qt.io/licensing/ +** +** This file is part of the Qt Debug Bridge. +** +** $QT_BEGIN_LICENSE:COMM$ +** +** Commercial License Usage +** Licensees holding valid commercial Qt licenses may use this file in +** accordance with the commercial license agreement provided with the +** Software or, alternatively, in accordance with the terms contained in +** a written agreement between you and The Qt Company. For licensing terms +** and conditions see http://www.qt.io/terms-conditions. For further +** information use the contact form at http://www.qt.io/contact-us. +** +** $QT_END_LICENSE$ +** +******************************************************************************/ +#ifndef STREAMPACKET_H +#define STREAMPACKET_H + +#include <QtCore/qbytearray.h> +#include <QtCore/qdatastream.h> + +class StreamPacket +{ +public: + //! Create a writable StreamPacket + StreamPacket(); + StreamPacket(const StreamPacket &other); + StreamPacket& operator=(const StreamPacket &rhs) = delete; + //! Create a readable StreamPacket + explicit StreamPacket(const QByteArray &data); + + + const QByteArray &buffer() const; + int size() const; + + template<typename T> + StreamPacket &operator<<(const T &value) + { + m_dataStream << value; + return *this; + } + + template<typename T> + StreamPacket &operator>>(T &target) + { + m_dataStream >> target; + return *this; + } + +private: + QByteArray m_buffer; + QDataStream m_dataStream; +}; +Q_DECLARE_METATYPE(StreamPacket); + +#endif // STREAMPACKET_H diff --git a/libqdb/usb/usbconnection.cpp b/libqdb/usb/usbconnection.cpp new file mode 100644 index 0000000..7564f3e --- /dev/null +++ b/libqdb/usb/usbconnection.cpp @@ -0,0 +1,215 @@ +/****************************************************************************** +** +** Copyright (C) 2016 The Qt Company Ltd. +** Contact: http://www.qt.io/licensing/ +** +** This file is part of the Qt Debug Bridge. +** +** $QT_BEGIN_LICENSE:COMM$ +** +** Commercial License Usage +** Licensees holding valid commercial Qt licenses may use this file in +** accordance with the commercial license agreement provided with the +** Software or, alternatively, in accordance with the terms contained in +** a written agreement between you and The Qt Company. For licensing terms +** and conditions see http://www.qt.io/terms-conditions. For further +** information use the contact form at http://www.qt.io/contact-us. +** +** $QT_END_LICENSE$ +** +******************************************************************************/ +#include "usbconnection.h" + +#include "../utils/make_unique.h" +#include "../utils/scopeguard.h" +#include "protocol/protocol.h" +#include "usbconnectionreader.h" + +#include <QtCore/qdebug.h> +#include <QtCore/qthread.h> + +#include <libusb.h> + +static const uint16_t vendorID = 0x18d1; // Google Inc. +static const uint16_t productID = 0x0d02; // Celkon A88 +static const int inEndpointIndex = 1; +static const int outEndpointIndex = 0; + +bool isQdbInterface(const libusb_interface &interface) +{ + const libusb_interface_descriptor *descriptor = &interface.altsetting[0]; + return descriptor->bInterfaceClass == qdbUsbClassId && descriptor->bInterfaceSubClass == qdbUsbSubclassId; +} + +UsbConnection::UsbConnection() + : m_context(nullptr), + m_handle(nullptr), + m_interfaceNumber(0), + m_inAddress(0), + m_outAddress(0), + m_readThread{nullptr}, + m_reader{nullptr}, + m_reads{} +{ +} + +UsbConnection::~UsbConnection() +{ + if (m_readThread) { + m_readThread->quit(); + m_readThread->wait(); + } + if (m_handle) { + libusb_release_interface(m_handle, m_interfaceNumber); + libusb_close(m_handle); + } + if (m_context) + libusb_exit(m_context); +} + +bool UsbConnection::open(OpenMode mode) +{ + Q_ASSERT(mode == (QIODevice::ReadWrite | QIODevice::Unbuffered)); + QIODevice::open(mode); + + libusb_device *found = NULL; + + int ret = libusb_init(&m_context); + if (ret) { + qDebug("cannot init libusb: %s\n", libusb_error_name(ret)); + return false; + } + + libusb_device **devices; + ssize_t deviceCount = libusb_get_device_list(m_context, &devices); + ScopeGuard deviceListGuard = [&]() { + libusb_free_device_list(devices, 1); + }; + + if (deviceCount <= 0) { + qDebug("no devices found\n"); + return false; + } + + for (int i = 0; i < deviceCount; ++i) { + libusb_device *device = devices[i]; + struct libusb_device_descriptor desc; + ret = libusb_get_device_descriptor(device, &desc); + if (ret) { + qDebug("unable to get device descriptor: %s\n", + libusb_error_name(ret)); + return false; + } + if (desc.idVendor == vendorID && desc.idProduct == productID) { + qDebug("found QDB device"); + found = device; + break; + } + } + + if (!found) { + qDebug("no QDB devices found\n"); + return false; + } + + libusb_config_descriptor *config; + ret = libusb_get_active_config_descriptor(found, &config); + if (ret) { + qDebug("could not get config descriptor: %s\n", + libusb_error_name(ret)); + return false; + } + ScopeGuard configGuard = [&]() { + libusb_free_config_descriptor(config); + }; + + auto last = config->interface + config->bNumInterfaces; + auto qdbInterface = std::find_if(config->interface, last, isQdbInterface); + if (qdbInterface == last) { + qDebug("no QDB interface found in device"); + return false; + } + + const libusb_interface_descriptor *interface = &qdbInterface->altsetting[0]; + m_interfaceNumber = interface->bInterfaceNumber; + m_inAddress = interface->endpoint[inEndpointIndex].bEndpointAddress; + m_outAddress = interface->endpoint[outEndpointIndex].bEndpointAddress; + + qDebug("interface %d is a qdb interface", m_interfaceNumber); + + ret = libusb_open(found, &m_handle); + if (ret) { + qDebug("cannot open device: %s\n", libusb_error_name(ret)); + return false; + } + + libusb_set_auto_detach_kernel_driver(m_handle, 1); + + ret = libusb_claim_interface(m_handle, m_interfaceNumber); + if (ret) { + qDebug("cannot claim interface: %s", libusb_error_name(ret)); + return false; + } + qDebug("claimed interface %d", m_interfaceNumber); + + startReader(m_handle, m_inAddress); + + return true; +} + +qint64 UsbConnection::readData(char *data, qint64 maxSize) +{ + if (m_reads.isEmpty()) { + qDebug() << "UsbConnection read queue empty in readData"; + return -1; + } + QByteArray read = m_reads.dequeue(); + Q_ASSERT(read.size() <= maxSize); // TODO: handle too big reads + std::copy(read.begin(), read.end(), data); + + return read.size(); +} + +qint64 UsbConnection::writeData(const char *data, qint64 maxSize) +{ + // Send header as a separate transfer to allow separate read on device side + int size = maxSize > qdbHeaderSize ? qdbHeaderSize : maxSize; + + int transferred = 0; + int ret = libusb_bulk_transfer(m_handle, m_outAddress, (unsigned char*)data, size, &transferred, 0); + if (ret) { + qDebug() << "writeData error:" << libusb_error_name(ret); + return -1; + } + Q_ASSERT(transferred == size); // TODO: handle partial transfers of header + transferred = 0; + + if (size < maxSize) { + int rest = maxSize - size; + int ret = libusb_bulk_transfer(m_handle, m_outAddress, (unsigned char*)data + size, rest, &transferred, 0); + if (ret) { + qDebug() << "writeData error:" << libusb_error_name(ret); + return -1; + } + } + return size + transferred; +} + +void UsbConnection::dataRead(QByteArray data) +{ + m_reads.enqueue(data); + emit readyRead(); +} + +void UsbConnection::startReader(libusb_device_handle *handle, uint8_t inAddress) +{ + m_readThread = make_unique<QThread>(); + m_reader = make_unique<UsbConnectionReader>(handle, inAddress); + + connect(m_reader.get(), &UsbConnectionReader::newRead, this, &UsbConnection::dataRead); + connect(m_readThread.get(), &QThread::started, m_reader.get(), &UsbConnectionReader::executeRead); + m_reader->moveToThread(m_readThread.get()); + + m_readThread->setObjectName("UsbConnectionReader"); + m_readThread->start(); +} diff --git a/libqdb/usb/usbconnection.h b/libqdb/usb/usbconnection.h new file mode 100644 index 0000000..bdf2670 --- /dev/null +++ b/libqdb/usb/usbconnection.h @@ -0,0 +1,67 @@ +/****************************************************************************** +** +** Copyright (C) 2016 The Qt Company Ltd. +** Contact: http://www.qt.io/licensing/ +** +** This file is part of the Qt Debug Bridge. +** +** $QT_BEGIN_LICENSE:COMM$ +** +** Commercial License Usage +** Licensees holding valid commercial Qt licenses may use this file in +** accordance with the commercial license agreement provided with the +** Software or, alternatively, in accordance with the terms contained in +** a written agreement between you and The Qt Company. For licensing terms +** and conditions see http://www.qt.io/terms-conditions. For further +** information use the contact form at http://www.qt.io/contact-us. +** +** $QT_END_LICENSE$ +** +******************************************************************************/ +#ifndef USBMANAGER_H +#define USBMANAGER_H + +#include "libqdb_global.h" + +class UsbConnectionReader; + +#include <QtCore/qbytearray.h> +#include <QtCore/qiodevice.h> +class QThread; +#include <QtCore/qqueue.h> + +#include <memory> + +struct libusb_context; +struct libusb_device_handle; + +class LIBQDBSHARED_EXPORT UsbConnection : public QIODevice +{ + Q_OBJECT +public: + UsbConnection(); + ~UsbConnection(); + + bool open(QIODevice::OpenMode mode) override; + +protected: + qint64 readData(char *data, qint64 maxSize) override; + qint64 writeData(const char *data, qint64 maxSize) override; + +private slots: + void dataRead(QByteArray data); + +private: + void startReader(libusb_device_handle *handle, uint8_t inAddress); + + libusb_context *m_context; + libusb_device_handle *m_handle; + uint8_t m_interfaceNumber; + uint8_t m_inAddress; + uint8_t m_outAddress; + std::unique_ptr<QThread> m_readThread; + std::unique_ptr<UsbConnectionReader> m_reader; + QQueue<QByteArray> m_reads; +}; + +#endif // USBMANAGER_H diff --git a/libqdb/usb/usbconnectionreader.cpp b/libqdb/usb/usbconnectionreader.cpp new file mode 100644 index 0000000..9313514 --- /dev/null +++ b/libqdb/usb/usbconnectionreader.cpp @@ -0,0 +1,56 @@ +/****************************************************************************** +** +** Copyright (C) 2016 The Qt Company Ltd. +** Contact: http://www.qt.io/licensing/ +** +** This file is part of the Qt Debug Bridge. +** +** $QT_BEGIN_LICENSE:COMM$ +** +** Commercial License Usage +** Licensees holding valid commercial Qt licenses may use this file in +** accordance with the commercial license agreement provided with the +** Software or, alternatively, in accordance with the terms contained in +** a written agreement between you and The Qt Company. For licensing terms +** and conditions see http://www.qt.io/terms-conditions. For further +** information use the contact form at http://www.qt.io/contact-us. +** +** $QT_END_LICENSE$ +** +******************************************************************************/ +#include "usbconnectionreader.h" + +#include "protocol/protocol.h" + +#include <QtCore/qdebug.h> +#include <QtCore/qthread.h> +#include <QtCore/qtimer.h> + +#include <libusb.h> + +// Amount of milliseconds between yielding control to the event loop of the reading thread +static const int quitCheckingTimeout = 500; + +UsbConnectionReader::UsbConnectionReader(libusb_device_handle *handle, uint8_t inAddress) + : m_handle{handle}, + m_inAddress{inAddress} +{ + +} + +void UsbConnectionReader::executeRead() +{ + QByteArray buffer{qdbMessageSize, '\0'}; + int transferred = 0; + int ret = libusb_bulk_transfer(m_handle, m_inAddress, reinterpret_cast<unsigned char *>(buffer.data()), + buffer.size(), &transferred, quitCheckingTimeout); + if (ret) { + // TODO: report errors? + if (ret != LIBUSB_ERROR_TIMEOUT) + qDebug() << "UsbConnectionReader error:" << libusb_error_name(ret); + } else { + buffer.resize(transferred); + emit newRead(buffer); + } + QTimer::singleShot(0, this, &UsbConnectionReader::executeRead); +} diff --git a/libqdb/usb/usbconnectionreader.h b/libqdb/usb/usbconnectionreader.h new file mode 100644 index 0000000..041deca --- /dev/null +++ b/libqdb/usb/usbconnectionreader.h @@ -0,0 +1,45 @@ +/****************************************************************************** +** +** Copyright (C) 2016 The Qt Company Ltd. +** Contact: http://www.qt.io/licensing/ +** +** This file is part of the Qt Debug Bridge. +** +** $QT_BEGIN_LICENSE:COMM$ +** +** Commercial License Usage +** Licensees holding valid commercial Qt licenses may use this file in +** accordance with the commercial license agreement provided with the +** Software or, alternatively, in accordance with the terms contained in +** a written agreement between you and The Qt Company. For licensing terms +** and conditions see http://www.qt.io/terms-conditions. For further +** information use the contact form at http://www.qt.io/contact-us. +** +** $QT_END_LICENSE$ +** +******************************************************************************/ +#ifndef USBCONNECTIONREADER_H +#define USBCONNECTIONREADER_H + +#include <QtCore/qobject.h> + +class libusb_device_handle; + +class UsbConnectionReader : public QObject +{ + Q_OBJECT +public: + UsbConnectionReader(libusb_device_handle *handle, uint8_t inAddress); + +signals: + void newRead(QByteArray data); + +public slots: + void executeRead(); + +private: + libusb_device_handle *m_handle; + uint8_t m_inAddress; +}; + +#endif // USBCONNECTIONREADER_H @@ -0,0 +1,11 @@ +TEMPLATE = subdirs + +SUBDIRS += \ + libqdb \ + client \ + qdbd \ + test + +client.depends += libqdb +qdbd.depends += libqdb +test.depends += libqdb diff --git a/qdbd/createexecutor.cpp b/qdbd/createexecutor.cpp new file mode 100644 index 0000000..1cc1dbf --- /dev/null +++ b/qdbd/createexecutor.cpp @@ -0,0 +1,52 @@ +/****************************************************************************** +** +** Copyright (C) 2016 The Qt Company Ltd. +** Contact: http://www.qt.io/licensing/ +** +** This file is part of the Qt Debug Bridge. +** +** $QT_BEGIN_LICENSE:COMM$ +** +** Commercial License Usage +** Licensees holding valid commercial Qt licenses may use this file in +** accordance with the commercial license agreement provided with the +** Software or, alternatively, in accordance with the terms contained in +** a written agreement between you and The Qt Company. For licensing terms +** and conditions see http://www.qt.io/terms-conditions. For further +** information use the contact form at http://www.qt.io/contact-us. +** +** $QT_END_LICENSE$ +** +******************************************************************************/ +#include "createexecutor.h" + +#include "../utils/make_unique.h" +#include "echoexecutor.h" +#include "filepullexecutor.h" +#include "filepushexecutor.h" +#include "processexecutor.h" +#include "protocol/services.h" + +#include <QtCore/qdatastream.h> +#include <QtCore/qfile.h> + +std::unique_ptr<Executor> createExecutor(Stream *stream, const QByteArray &tagBuffer) +{ + QDataStream tagStream{tagBuffer}; + uint32_t tag; + tagStream >> tag; + + switch (static_cast<ServiceTag>(tag)) { + case EchoTag: + return make_unique<EchoExecutor>(stream); + case ProcessTag: + return make_unique<ProcessExecutor>(stream); + case FilePushTag: + return make_unique<FilePushExecutor>(stream); + case FilePullTag: + return make_unique<FilePullExecutor>(stream); + default: + qCritical("Unknown ServiceTag %d in createExecutor", tag); + return std::unique_ptr<Executor>{}; + } +} diff --git a/qdbd/createexecutor.h b/qdbd/createexecutor.h new file mode 100644 index 0000000..f00b412 --- /dev/null +++ b/qdbd/createexecutor.h @@ -0,0 +1,33 @@ +/****************************************************************************** +** +** Copyright (C) 2016 The Qt Company Ltd. +** Contact: http://www.qt.io/licensing/ +** +** This file is part of the Qt Debug Bridge. +** +** $QT_BEGIN_LICENSE:COMM$ +** +** Commercial License Usage +** Licensees holding valid commercial Qt licenses may use this file in +** accordance with the commercial license agreement provided with the +** Software or, alternatively, in accordance with the terms contained in +** a written agreement between you and The Qt Company. For licensing terms +** and conditions see http://www.qt.io/terms-conditions. For further +** information use the contact form at http://www.qt.io/contact-us. +** +** $QT_END_LICENSE$ +** +******************************************************************************/ +#ifndef CREATEEXECUTOR_H +#define CREATEEXECUTOR_H + +#include <memory> + +class Executor; +class Stream; + +class QByteArray; + +std::unique_ptr<Executor> createExecutor(Stream *stream, const QByteArray &tagBuffer); + +#endif // CREATEEXECUTOR_H diff --git a/qdbd/echoexecutor.cpp b/qdbd/echoexecutor.cpp new file mode 100644 index 0000000..8c6788f --- /dev/null +++ b/qdbd/echoexecutor.cpp @@ -0,0 +1,38 @@ +/****************************************************************************** +** +** Copyright (C) 2016 The Qt Company Ltd. +** Contact: http://www.qt.io/licensing/ +** +** This file is part of the Qt Debug Bridge. +** +** $QT_BEGIN_LICENSE:COMM$ +** +** Commercial License Usage +** Licensees holding valid commercial Qt licenses may use this file in +** accordance with the commercial license agreement provided with the +** Software or, alternatively, in accordance with the terms contained in +** a written agreement between you and The Qt Company. For licensing terms +** and conditions see http://www.qt.io/terms-conditions. For further +** information use the contact form at http://www.qt.io/contact-us. +** +** $QT_END_LICENSE$ +** +******************************************************************************/ +#include "echoexecutor.h" + +#include "stream.h" + +#include <QtCore/qdebug.h> + +EchoExecutor::EchoExecutor(Stream *stream) + : m_stream{stream} +{ + if (m_stream) + connect(m_stream, &Stream::packetAvailable, this, &Executor::receive); +} + +void EchoExecutor::receive(StreamPacket packet) +{ + qDebug() << "EchoExecutor received:" << packet.buffer(); + m_stream->write(packet); +} diff --git a/qdbd/echoexecutor.h b/qdbd/echoexecutor.h new file mode 100644 index 0000000..04aa759 --- /dev/null +++ b/qdbd/echoexecutor.h @@ -0,0 +1,41 @@ +/****************************************************************************** +** +** Copyright (C) 2016 The Qt Company Ltd. +** Contact: http://www.qt.io/licensing/ +** +** This file is part of the Qt Debug Bridge. +** +** $QT_BEGIN_LICENSE:COMM$ +** +** Commercial License Usage +** Licensees holding valid commercial Qt licenses may use this file in +** accordance with the commercial license agreement provided with the +** Software or, alternatively, in accordance with the terms contained in +** a written agreement between you and The Qt Company. For licensing terms +** and conditions see http://www.qt.io/terms-conditions. For further +** information use the contact form at http://www.qt.io/contact-us. +** +** $QT_END_LICENSE$ +** +******************************************************************************/ +#ifndef ECHOEXECUTOR_H +#define ECHOEXECUTOR_H + +#include "executor.h" + +class Stream; + +class EchoExecutor : public Executor +{ + Q_OBJECT +public: + EchoExecutor(Stream *stream); + +public slots: + void receive(StreamPacket packet) override; + +private: + Stream *m_stream; +}; + +#endif // ECHOEXECUTOR_H diff --git a/qdbd/executor.cpp b/qdbd/executor.cpp new file mode 100644 index 0000000..058f80f --- /dev/null +++ b/qdbd/executor.cpp @@ -0,0 +1,34 @@ +/****************************************************************************** +** +** Copyright (C) 2016 The Qt Company Ltd. +** Contact: http://www.qt.io/licensing/ +** +** This file is part of the Qt Debug Bridge. +** +** $QT_BEGIN_LICENSE:COMM$ +** +** Commercial License Usage +** Licensees holding valid commercial Qt licenses may use this file in +** accordance with the commercial license agreement provided with the +** Software or, alternatively, in accordance with the terms contained in +** a written agreement between you and The Qt Company. For licensing terms +** and conditions see http://www.qt.io/terms-conditions. For further +** information use the contact form at http://www.qt.io/contact-us. +** +** $QT_END_LICENSE$ +** +******************************************************************************/ +#include "executor.h" + +Executor::Executor() + : m_stream{nullptr} +{ + +} + +Executor::~Executor() = default; + +void Executor::onStreamClosed() +{ + m_stream = nullptr; +} diff --git a/qdbd/executor.h b/qdbd/executor.h new file mode 100644 index 0000000..4dde463 --- /dev/null +++ b/qdbd/executor.h @@ -0,0 +1,44 @@ +/****************************************************************************** +** +** Copyright (C) 2016 The Qt Company Ltd. +** Contact: http://www.qt.io/licensing/ +** +** This file is part of the Qt Debug Bridge. +** +** $QT_BEGIN_LICENSE:COMM$ +** +** Commercial License Usage +** Licensees holding valid commercial Qt licenses may use this file in +** accordance with the commercial license agreement provided with the +** Software or, alternatively, in accordance with the terms contained in +** a written agreement between you and The Qt Company. For licensing terms +** and conditions see http://www.qt.io/terms-conditions. For further +** information use the contact form at http://www.qt.io/contact-us. +** +** $QT_END_LICENSE$ +** +******************************************************************************/ +#ifndef EXECUTOR_H +#define EXECUTOR_H + +class Stream; +#include "streampacket.h" + +#include <QtCore/qobject.h> + +class Executor : public QObject +{ + Q_OBJECT +public: + Executor(); + virtual ~Executor(); + +public slots: + virtual void receive(StreamPacket packet) = 0; + virtual void onStreamClosed(); + +private: + Stream *m_stream; +}; + +#endif // EXECUTOR_H diff --git a/qdbd/filepullexecutor.cpp b/qdbd/filepullexecutor.cpp new file mode 100644 index 0000000..ad581a8 --- /dev/null +++ b/qdbd/filepullexecutor.cpp @@ -0,0 +1,108 @@ +/****************************************************************************** +** +** Copyright (C) 2016 The Qt Company Ltd. +** Contact: http://www.qt.io/licensing/ +** +** This file is part of the Qt Debug Bridge. +** +** $QT_BEGIN_LICENSE:COMM$ +** +** Commercial License Usage +** Licensees holding valid commercial Qt licenses may use this file in +** accordance with the commercial license agreement provided with the +** Software or, alternatively, in accordance with the terms contained in +** a written agreement between you and The Qt Company. For licensing terms +** and conditions see http://www.qt.io/terms-conditions. For further +** information use the contact form at http://www.qt.io/contact-us. +** +** $QT_END_LICENSE$ +** +******************************************************************************/ +#include "filepullexecutor.h" + +#include "../utils/make_unique.h" +#include "filepullcommon.h" +#include "protocol/services.h" +#include "stream.h" + +#include <QtCore/qdatastream.h> +#include <QtCore/qdebug.h> +#include <QtCore/qfile.h> + +FilePullExecutor::FilePullExecutor(Stream *stream) + : m_stream{stream}, + m_source{nullptr}, + m_transferring{false} +{ + if (m_stream) + connect(m_stream, &Stream::packetAvailable, this, &Executor::receive); +} + +void FilePullExecutor::receive(StreamPacket packet) +{ + uint32_t typeValue; + packet >> typeValue; + auto type = toFilePullPacketType(typeValue); + switch (type) { + case FilePullOpen: { + QString sourcePath; + packet >> sourcePath; + if (openSource(sourcePath)) + transferBlock(); + break; + } + case FilePullWasRead: + qDebug() << "File pull read acknowledged."; + if (m_transferring) + transferBlock(); + else + closeSource(); + break; + case FilePullError: + qDebug() << "FilePullError from host"; + closeSource(); + break; + default: + qFatal("Unsupported FilePushPacketType %d in ProcessExecutor::receive", type); + } +} + +bool FilePullExecutor::openSource(const QString &path) +{ + qDebug() << "Opening source file" << path; + m_source = make_unique<QFile>(path); + bool opened = m_source->open(QIODevice::ReadOnly); + + StreamPacket packet; + + if (!opened) { + packet << FilePullError + << QString{"Could not open \"%1\" on device"}.arg(m_source->fileName()); + } else { + packet << FilePullOpened; + } + + return m_stream->write(packet) && opened; +} + +void FilePullExecutor::transferBlock() +{ + QByteArray block = m_source->read(fileTransferBlockSize); + m_transferring = !m_source->atEnd(); + + StreamPacket packet; + packet << FilePullRead << block; + + m_stream->write(packet); +} + +void FilePullExecutor::closeSource() +{ + qDebug() << "Closing source file" << m_source->fileName(); + m_source->close(); + + StreamPacket packet; + packet << FilePullEnd; + + m_stream->write(packet); +} diff --git a/qdbd/filepullexecutor.h b/qdbd/filepullexecutor.h new file mode 100644 index 0000000..5706d2f --- /dev/null +++ b/qdbd/filepullexecutor.h @@ -0,0 +1,50 @@ +/****************************************************************************** +** +** Copyright (C) 2016 The Qt Company Ltd. +** Contact: http://www.qt.io/licensing/ +** +** This file is part of the Qt Debug Bridge. +** +** $QT_BEGIN_LICENSE:COMM$ +** +** Commercial License Usage +** Licensees holding valid commercial Qt licenses may use this file in +** accordance with the commercial license agreement provided with the +** Software or, alternatively, in accordance with the terms contained in +** a written agreement between you and The Qt Company. For licensing terms +** and conditions see http://www.qt.io/terms-conditions. For further +** information use the contact form at http://www.qt.io/contact-us. +** +** $QT_END_LICENSE$ +** +******************************************************************************/ +#ifndef FILEPULLEXECUTOR_H +#define FILEPULLEXECUTOR_H + +#include "executor.h" +class Stream; + +class QByteArray; +class QFile; + +#include <memory> + +class FilePullExecutor : public Executor +{ +public: + explicit FilePullExecutor(Stream *stream); + +public slots: + void receive(StreamPacket packet) override; + +private: + bool openSource(const QString &path); + void transferBlock(); + void closeSource(); + + Stream* m_stream; + std::unique_ptr<QFile> m_source; + bool m_transferring; +}; + +#endif // FILEPULLEXECUTOR_H diff --git a/qdbd/filepushexecutor.cpp b/qdbd/filepushexecutor.cpp new file mode 100644 index 0000000..a208a11 --- /dev/null +++ b/qdbd/filepushexecutor.cpp @@ -0,0 +1,102 @@ +/****************************************************************************** +** +** Copyright (C) 2016 The Qt Company Ltd. +** Contact: http://www.qt.io/licensing/ +** +** This file is part of the Qt Debug Bridge. +** +** $QT_BEGIN_LICENSE:COMM$ +** +** Commercial License Usage +** Licensees holding valid commercial Qt licenses may use this file in +** accordance with the commercial license agreement provided with the +** Software or, alternatively, in accordance with the terms contained in +** a written agreement between you and The Qt Company. For licensing terms +** and conditions see http://www.qt.io/terms-conditions. For further +** information use the contact form at http://www.qt.io/contact-us. +** +** $QT_END_LICENSE$ +** +******************************************************************************/ +#include "filepushexecutor.h" + +#include "../utils/make_unique.h" +#include "filepushcommon.h" +#include "stream.h" + +#include <QtCore/qdatastream.h> +#include <QtCore/qdebug.h> +#include <QtCore/qfile.h> + +FilePushExecutor::FilePushExecutor(Stream *stream) + : m_stream{stream}, + m_sink{nullptr} +{ + if (m_stream) + connect(m_stream, &Stream::packetAvailable, this, &Executor::receive); +} + +void FilePushExecutor::receive(StreamPacket packet) +{ + uint32_t typeValue; + packet >> typeValue; + auto type = toFilePushPacketType(typeValue); + switch (type) { + case FilePushOpen: { + QString sinkPath; + packet >> sinkPath; + openSink(sinkPath); + break; + } + case FilePushWrite: { + QByteArray fileData; + packet >> fileData; + writeToSink(fileData); + break; + } + case FilePushEnd: + closeSink(); + break; + case FilePushError: + qDebug() << "FilePushError from host"; + if (m_sink) + m_sink->remove(); + break; + default: + qFatal("Unsupported FilePushPacketType %d in ProcessExecutor::receive", type); + } +} + +void FilePushExecutor::openSink(const QString &path) +{ + qDebug() << "Opening sink file" << path; + m_sink = make_unique<QFile>(path); + StreamPacket packet; + + if (!m_sink->open(QIODevice::WriteOnly)) + packet << FilePushError; + else + packet << FilePushOpened; + + m_stream->write(packet); +} + +void FilePushExecutor::writeToSink(const QByteArray &data) +{ + auto written = m_sink->write(data); + + StreamPacket packet; + + if (written != data.size()) + packet << FilePushError; + else + packet << FilePushWritten; + + m_stream->write(packet); +} + +void FilePushExecutor::closeSink() +{ + qDebug() << "Closing sink file" << m_sink->fileName(); + m_sink->close(); +} diff --git a/qdbd/filepushexecutor.h b/qdbd/filepushexecutor.h new file mode 100644 index 0000000..7902ca6 --- /dev/null +++ b/qdbd/filepushexecutor.h @@ -0,0 +1,49 @@ +/****************************************************************************** +** +** Copyright (C) 2016 The Qt Company Ltd. +** Contact: http://www.qt.io/licensing/ +** +** This file is part of the Qt Debug Bridge. +** +** $QT_BEGIN_LICENSE:COMM$ +** +** Commercial License Usage +** Licensees holding valid commercial Qt licenses may use this file in +** accordance with the commercial license agreement provided with the +** Software or, alternatively, in accordance with the terms contained in +** a written agreement between you and The Qt Company. For licensing terms +** and conditions see http://www.qt.io/terms-conditions. For further +** information use the contact form at http://www.qt.io/contact-us. +** +** $QT_END_LICENSE$ +** +******************************************************************************/ +#ifndef FILEPUSHEXECUTOR_H +#define FILEPUSHEXECUTOR_H + +#include "executor.h" +class Stream; + +class QByteArray; +class QFile; + +#include <memory> + +class FilePushExecutor : public Executor +{ +public: + explicit FilePushExecutor(Stream *stream); + +public slots: + void receive(StreamPacket packet) override; + +private: + void openSink(const QString &path); + void writeToSink(const QByteArray &data); + void closeSink(); + + Stream* m_stream; + std::unique_ptr<QFile> m_sink; +}; + +#endif // FILEPUSHEXECUTOR_H diff --git a/qdbd/main.cpp b/qdbd/main.cpp new file mode 100644 index 0000000..208e8c9 --- /dev/null +++ b/qdbd/main.cpp @@ -0,0 +1,58 @@ +/****************************************************************************** +** +** Copyright (C) 2016 The Qt Company Ltd. +** Contact: http://www.qt.io/licensing/ +** +** This file is part of the Qt Debug Bridge. +** +** $QT_BEGIN_LICENSE:COMM$ +** +** Commercial License Usage +** Licensees holding valid commercial Qt licenses may use this file in +** accordance with the commercial license agreement provided with the +** Software or, alternatively, in accordance with the terms contained in +** a written agreement between you and The Qt Company. For licensing terms +** and conditions see http://www.qt.io/terms-conditions. For further +** information use the contact form at http://www.qt.io/contact-us. +** +** $QT_END_LICENSE$ +** +******************************************************************************/ +#include "usb-gadget/usbgadget.h" +#include "server.h" +#include "protocol/qdbtransport.h" + +#include <QtCore/qcommandlineparser.h> +#include <QtCore/qcoreapplication.h> +#include <QtCore/qdebug.h> +#include <QtCore/qloggingcategory.h> + +int main(int argc, char *argv[]) +{ + QCoreApplication app(argc, argv); + + QCommandLineParser parser; + parser.addHelpOption(); + parser.addOption({"debug-transport", "Show each transmitted message"}); + parser.addOption({"debug-connection", "Show enqueued messages"}); + parser.process(app); + + QString filterRules; + if (!parser.isSet("debug-transport")) { + filterRules.append("transport=false\n"); + } + if (!parser.isSet("debug-connection")) { + filterRules.append("connection=false\n"); + } + QLoggingCategory::setFilterRules(filterRules); + + Server server{new QdbTransport{new UsbGadget{}}}; + if (server.initialize()) { + qDebug() << "initialized server"; + } else { + qDebug() << "could not initialize server"; + return 1; + } + + return app.exec(); +} diff --git a/qdbd/processexecutor.cpp b/qdbd/processexecutor.cpp new file mode 100644 index 0000000..7ea2e6c --- /dev/null +++ b/qdbd/processexecutor.cpp @@ -0,0 +1,150 @@ +/****************************************************************************** +** +** Copyright (C) 2016 The Qt Company Ltd. +** Contact: http://www.qt.io/licensing/ +** +** This file is part of the Qt Debug Bridge. +** +** $QT_BEGIN_LICENSE:COMM$ +** +** Commercial License Usage +** Licensees holding valid commercial Qt licenses may use this file in +** accordance with the commercial license agreement provided with the +** Software or, alternatively, in accordance with the terms contained in +** a written agreement between you and The Qt Company. For licensing terms +** and conditions see http://www.qt.io/terms-conditions. For further +** information use the contact form at http://www.qt.io/contact-us. +** +** $QT_END_LICENSE$ +** +******************************************************************************/ +#include "processexecutor.h" + +#include "../utils/make_unique.h" +#include "processcommon.h" +#include "stream.h" + +#include <QtCore/qdatastream.h> +#include <QtCore/qdebug.h> +#include <QtCore/qprocess.h> + +ProcessExecutor::ProcessExecutor(Stream *stream) + : m_stream{stream}, + m_process{nullptr} +{ + if (m_stream) { + connect(m_stream, &Stream::packetAvailable, this, &Executor::receive); + connect(m_stream, &Stream::closed, this, &Executor::onStreamClosed); + } +} + +void ProcessExecutor::receive(StreamPacket packet) +{ + uint32_t typeValue; + packet >> typeValue; + auto type = toProcessPacketType(typeValue); + switch (type) { + case ProcessStart: { + QString command; + QStringList arguments; + packet >> command >> arguments; + startProcess(command, arguments); + break; + } + case ProcessWrite: { + QByteArray data; + packet >> data; + writeToProcess(data); + break; + } + default: + Q_ASSERT_X(false, "ProcessExecutor::receive", "Unsupported ProcessPacketType"); + break; + } +} + +void ProcessExecutor::onStarted() +{ + qDebug() << "Process started"; + + StreamPacket packet; + packet << ProcessStarted; + + if (m_stream) + m_stream->write(packet); +} + +void ProcessExecutor::onReadyRead() +{ + qDebug() << "Process readyRead"; + auto size = m_process->bytesAvailable(); + QByteArray read = m_process->read(size); + + StreamPacket packet; + packet << ProcessRead; + packet << read; + + if (m_stream) + m_stream->write(packet); +} + +void ProcessExecutor::onFinished(int exitCode, QProcess::ExitStatus exitStatus) +{ + qDebug() << "Process finished:" << exitCode; + + QByteArray output = m_process->readAll(); + + StreamPacket packet; + packet << ProcessFinished; + packet << exitCode; + packet << (exitStatus == QProcess::NormalExit); + packet << output; + + if (m_stream) + m_stream->write(packet); +} + +void ProcessExecutor::onErrorOccurred(QProcess::ProcessError error) +{ + qDebug() << "Process error:" << error; + + StreamPacket packet; + packet << ProcessError; + uint32_t errorValue = static_cast<uint32_t>(error); + packet << errorValue; + + if (m_stream) + m_stream->write(packet); +} + +void ProcessExecutor::onStreamClosed() +{ + m_stream = nullptr; + if (m_process) { + m_process->kill(); + } +} + +void ProcessExecutor::startProcess(const QString &command, const QStringList &arguments) +{ + m_process = make_unique<QProcess>(); + // merge stdout and stderr + m_process->setProcessChannelMode(QProcess::MergedChannels); + + connect(m_process.get(), &QProcess::started, this, &ProcessExecutor::onStarted); + connect(m_process.get(), &QProcess::readyRead, this, &ProcessExecutor::onReadyRead); + connect(m_process.get(), static_cast<void (QProcess::*)(int, QProcess::ExitStatus)>(&QProcess::finished), + this, &ProcessExecutor::onFinished); + connect(m_process.get(), &QProcess::errorOccurred, this, &ProcessExecutor::onErrorOccurred); + + qDebug() << "Running" << command << arguments; + m_process->start(command, arguments); +} + +void ProcessExecutor::writeToProcess(const QByteArray &data) +{ + Q_ASSERT(m_process); + + qDebug() << "Writing to process:" << data; + m_process->write(data); +} diff --git a/qdbd/processexecutor.h b/qdbd/processexecutor.h new file mode 100644 index 0000000..5219dd4 --- /dev/null +++ b/qdbd/processexecutor.h @@ -0,0 +1,57 @@ +/****************************************************************************** +** +** Copyright (C) 2016 The Qt Company Ltd. +** Contact: http://www.qt.io/licensing/ +** +** This file is part of the Qt Debug Bridge. +** +** $QT_BEGIN_LICENSE:COMM$ +** +** Commercial License Usage +** Licensees holding valid commercial Qt licenses may use this file in +** accordance with the commercial license agreement provided with the +** Software or, alternatively, in accordance with the terms contained in +** a written agreement between you and The Qt Company. For licensing terms +** and conditions see http://www.qt.io/terms-conditions. For further +** information use the contact form at http://www.qt.io/contact-us. +** +** $QT_END_LICENSE$ +** +******************************************************************************/ +#ifndef PROCESSEXECUTOR_H +#define PROCESSEXECUTOR_H + +#include "executor.h" +class Stream; + +class QByteArray; +class QDataStream; +#include "QtCore/qprocess.h" + +#include <memory> + +class ProcessExecutor : public Executor +{ + Q_OBJECT +public: + explicit ProcessExecutor(Stream *stream); + +public slots: + void receive(StreamPacket packet) override; + +private slots: + void onStarted(); + void onReadyRead(); + void onFinished(int exitCode, QProcess::ExitStatus exitStatus); + void onErrorOccurred(QProcess::ProcessError error); + void onStreamClosed(); + +private: + void startProcess(const QString &command, const QStringList &arguments); + void writeToProcess(const QByteArray &data); + + Stream *m_stream; + std::unique_ptr<QProcess> m_process; +}; + +#endif // PROCESSEXECUTOR_H diff --git a/qdbd/qdbd.pro b/qdbd/qdbd.pro new file mode 100644 index 0000000..73d57c1 --- /dev/null +++ b/qdbd/qdbd.pro @@ -0,0 +1,46 @@ +QT += core +QT -= gui + +CONFIG += c++11 + +TARGET = qdbd +CONFIG += console +CONFIG -= app_bundle + +TEMPLATE = app + +SOURCES += \ + createexecutor.cpp \ + echoexecutor.cpp \ + executor.cpp \ + filepullexecutor.cpp \ + filepushexecutor.cpp \ + main.cpp \ + processexecutor.cpp \ + server.cpp \ + usb-gadget/usbgadget.cpp \ + usb-gadget/usbgadgetreader.cpp \ + usb-gadget/usbgadgetwriter.cpp \ + + +HEADERS += \ + createexecutor.h \ + echoexecutor.h \ + executor.h \ + filepullexecutor.h \ + filepushexecutor.h \ + processexecutor.h \ + server.h \ + usb-gadget/usbgadget.h \ + usb-gadget/usbgadgetreader.h \ + usb-gadget/usbgadgetwriter.h \ + +INCLUDEPATH += $$PWD/../libqdb + +LIBS = -L$$OUT_PWD/../libqdb -lqdb +QMAKE_RPATHDIR += ../libqdb + +unix { + target.path = /usr/bin + INSTALLS += target +} diff --git a/qdbd/server.cpp b/qdbd/server.cpp new file mode 100644 index 0000000..b352333 --- /dev/null +++ b/qdbd/server.cpp @@ -0,0 +1,218 @@ +/****************************************************************************** +** +** Copyright (C) 2016 The Qt Company Ltd. +** Contact: http://www.qt.io/licensing/ +** +** This file is part of the Qt Debug Bridge. +** +** $QT_BEGIN_LICENSE:COMM$ +** +** Commercial License Usage +** Licensees holding valid commercial Qt licenses may use this file in +** accordance with the commercial license agreement provided with the +** Software or, alternatively, in accordance with the terms contained in +** a written agreement between you and The Qt Company. For licensing terms +** and conditions see http://www.qt.io/terms-conditions. For further +** information use the contact form at http://www.qt.io/contact-us. +** +** $QT_END_LICENSE$ +** +******************************************************************************/ +#include "server.h" + +#include "../utils/make_unique.h" +#include "createexecutor.h" +#include "echoexecutor.h" +#include "protocol/qdbmessage.h" +#include "protocol/qdbtransport.h" +#include "stream.h" + +#include <QtCore/qdebug.h> +#include <QtCore/qloggingcategory.h> + +#include <algorithm> + +Q_LOGGING_CATEGORY(connectionC, "connection"); + +Server::Server(QdbTransport *transport, QObject *parent) + : AbstractConnection{transport, parent}, + m_state{ServerState::Disconnected}, + m_executors{} +{ + +} + +Server::~Server() = default; + +void Server::handleMessage() +{ + QdbMessage message = m_transport->receive(); + + Q_ASSERT_X(message.command() != QdbMessage::Invalid, "Server::handleMessage()", "Received invalid message"); + + switch (m_state) { + case ServerState::Disconnected: + if (message.command() != QdbMessage::Connect) { + qWarning() << "Server got non-Connect message in Disconnected state. Resetting."; + resetServer(false); + break; + } + resetServer(true); + break; + case ServerState::Connected: + switch (message.command()) { + case QdbMessage::Connect: + qWarning() << "Server received QdbMessage::Connect while already connected. Resetting."; + resetServer(true); + break; + case QdbMessage::Open: + handleOpen(message.hostStream(), message.data()); + break; + case QdbMessage::Write: + handleWrite(message); + break; + case QdbMessage::Close: + closeStream(message.deviceStream()); + break; + case QdbMessage::Ok: + qWarning() << "Server received QdbMessage::Ok in connected state"; + break; + case QdbMessage::Invalid: + Q_UNREACHABLE(); + break; + } + break; + case ServerState::Waiting: + switch (message.command()) { + case QdbMessage::Connect: + qWarning() << "Server received QdbMessage::Connect while already connected and waiting. Resetting."; + resetServer(true); + break; + case QdbMessage::Open: + handleOpen(message.hostStream(), message.data()); + break; + case QdbMessage::Write: + handleWrite(message); + break; + case QdbMessage::Close: + m_state = ServerState::Connected; + closeStream(message.deviceStream()); + break; + case QdbMessage::Ok: + m_state = ServerState::Connected; + break; + case QdbMessage::Invalid: + Q_UNREACHABLE(); + break; + } + break; + } + processQueue(); +} + +void Server::enqueueMessage(const QdbMessage &message) +{ + Q_ASSERT(message.command() != QdbMessage::Invalid); + qCDebug(connectionC) << "Server enqueue: " << message; + m_outgoingMessages.enqueue(message); + processQueue(); +} + +void Server::processQueue() +{ + if (m_outgoingMessages.isEmpty()) { + return; + } + + if (m_state == ServerState::Waiting) { + qCDebug(connectionC) << "Server::processQueue() skipping to wait for QdbMessage::Ok"; + return; + } + + auto message = m_outgoingMessages.dequeue(); + + Q_ASSERT_X(message.command() != QdbMessage::Invalid, "Server::processQueue()", + "Tried to send invalid message"); + + if (!m_transport->send(message)) { + qCritical() << "Server could not send" << message; + m_state = ServerState::Disconnected; + return; + } + + switch (message.command()) { + case QdbMessage::Open: + qFatal("Server sending QdbMessage::Open is not supported"); + break; + case QdbMessage::Write: + Q_ASSERT(m_state == ServerState::Connected); + m_state = ServerState::Waiting; + break; + // Connect, Close and Ok are not acknowledged when sent by server, + // so no need to transition to ServerState::Waiting. + // Since there is no acknowledgment incoming, we also need to move + // onto the next message in the queue, otherwise it would only be sent + // after host sends us something. + case QdbMessage::Connect: + [[fallthrough]] + case QdbMessage::Close: + [[fallthrough]] + case QdbMessage::Ok: + if (!m_outgoingMessages.isEmpty()) { + processQueue(); + } + break; + case QdbMessage::Invalid: + Q_UNREACHABLE(); + break; + } +} + +void Server::handleOpen(StreamId hostId, const QByteArray &tag) +{ + StreamId deviceId = m_nextStreamId++; + enqueueMessage(QdbMessage{QdbMessage::Ok, hostId, deviceId}); + m_streams[deviceId] = make_unique<Stream>(this, hostId, deviceId); + m_executors[deviceId] = createExecutor(m_streams[deviceId].get(), tag); +} + +void Server::resetServer(bool hostConnected) +{ + m_outgoingMessages.clear(); + m_executors.clear(); + m_streams.clear(); + m_state = hostConnected ? ServerState::Connected : ServerState::Disconnected; + enqueueMessage(QdbMessage{QdbMessage::Connect, 0, 0}); +} + +void Server::handleWrite(QdbMessage message) +{ + if (m_streams.find(message.deviceStream()) == m_streams.end()) { + qWarning() << "Server received message to non-existing stream" << message.deviceStream(); + enqueueMessage(QdbMessage{QdbMessage::Close, message.hostStream(), message.deviceStream()}); + return; + } + enqueueMessage(QdbMessage{QdbMessage::Ok, message.hostStream(), message.deviceStream()}); + m_streams[message.deviceStream()]->receiveMessage(message); +} + +void Server::closeStream(StreamId id) +{ + if (m_streams.find(id) == m_streams.end()) { + qWarning() << "Server received Close to a non-existing stream"; + return; + } + + m_streams[id]->close(); + m_executors.erase(id); + m_streams.erase(id); + + auto messageInStream = [&id](const QdbMessage &message) { + return message.deviceStream() == id; + }; + m_outgoingMessages.erase( + std::remove_if(m_outgoingMessages.begin(), m_outgoingMessages.end(), messageInStream), + m_outgoingMessages.end()); + // Closes are not acknowledged +} + diff --git a/qdbd/server.h b/qdbd/server.h new file mode 100644 index 0000000..9c963f9 --- /dev/null +++ b/qdbd/server.h @@ -0,0 +1,64 @@ +/****************************************************************************** +** +** Copyright (C) 2016 The Qt Company Ltd. +** Contact: http://www.qt.io/licensing/ +** +** This file is part of the Qt Debug Bridge. +** +** $QT_BEGIN_LICENSE:COMM$ +** +** Commercial License Usage +** Licensees holding valid commercial Qt licenses may use this file in +** accordance with the commercial license agreement provided with the +** Software or, alternatively, in accordance with the terms contained in +** a written agreement between you and The Qt Company. For licensing terms +** and conditions see http://www.qt.io/terms-conditions. For further +** information use the contact form at http://www.qt.io/contact-us. +** +** $QT_END_LICENSE$ +** +******************************************************************************/ +#ifndef SERVER_H +#define SERVER_H + +#include "abstractconnection.h" +class Executor; +class QdbMessage; +class QdbTransport; + +#include <memory> +#include <unordered_map> + +enum class ServerState +{ + Disconnected, + Connected, + Waiting +}; + +class Server : public AbstractConnection +{ + Q_OBJECT +public: + /*! Server takes ownership of the passed QdbTransport. */ + explicit Server(QdbTransport *transport, QObject *parent = 0); + ~Server(); + + void enqueueMessage(const QdbMessage &message) override; +signals: + +public slots: + void handleMessage() override; + +private: + void processQueue(); + void handleOpen(StreamId hostId, const QByteArray &tag); + void resetServer(bool hostConnected); + void handleWrite(QdbMessage message); + void closeStream(StreamId id); + + ServerState m_state; + std::unordered_map<StreamId, std::unique_ptr<Executor>> m_executors; +}; + +#endif // SERVER_H diff --git a/qdbd/usb-gadget/usbgadget.cpp b/qdbd/usb-gadget/usbgadget.cpp new file mode 100644 index 0000000..6d65312 --- /dev/null +++ b/qdbd/usb-gadget/usbgadget.cpp @@ -0,0 +1,243 @@ +/****************************************************************************** +** +** Copyright (C) 2016 The Qt Company Ltd. +** Contact: http://www.qt.io/licensing/ +** +** This file is part of the Qt Debug Bridge. +** +** $QT_BEGIN_LICENSE:COMM$ +** +** Commercial License Usage +** Licensees holding valid commercial Qt licenses may use this file in +** accordance with the commercial license agreement provided with the +** Software or, alternatively, in accordance with the terms contained in +** a written agreement between you and The Qt Company. For licensing terms +** and conditions see http://www.qt.io/terms-conditions. For further +** information use the contact form at http://www.qt.io/contact-us. +** +** $QT_END_LICENSE$ +** +******************************************************************************/ +#include "usbgadget.h" + +#include "../utils/make_unique.h" +#include "protocol/protocol.h" +#include "protocol/qdbmessage.h" +#include "usb-gadget/usbgadgetreader.h" +#include "usb-gadget/usbgadgetwriter.h" + +#include <QtCore/qdatastream.h> +#include <QtCore/qdebug.h> +#include <QtCore/qthread.h> + +#include <linux/usb/functionfs.h> + +usb_interface_descriptor makeInterfaceDescriptor() +{ + usb_interface_descriptor interface; + interface.bLength = sizeof(interface); + interface.bDescriptorType = USB_DT_INTERFACE; + interface.bInterfaceNumber = 0; + interface.bAlternateSetting = 0; + interface.bNumEndpoints = 2; + interface.bInterfaceClass = qdbUsbClassId; + interface.bInterfaceSubClass = qdbUsbSubclassId; + interface.bInterfaceProtocol = qdbUsbProtocolId; + interface.iInterface = 1; + return interface; +} + +usb_endpoint_descriptor_no_audio makeEndpointDescriptor(uint8_t endpointAddress, uint16_t maxPacketSize) +{ + usb_endpoint_descriptor_no_audio endpoint; + endpoint.bLength = sizeof(endpoint); + endpoint.bDescriptorType = USB_DT_ENDPOINT; + endpoint.bEndpointAddress = endpointAddress; + endpoint.bmAttributes = USB_ENDPOINT_XFER_BULK; + endpoint.wMaxPacketSize = htole16(maxPacketSize); + endpoint.bInterval = 0; + return endpoint; +} + +const struct { + struct usb_functionfs_descs_head header; + struct { + struct usb_interface_descriptor intf; + struct usb_endpoint_descriptor_no_audio bulk_source; + struct usb_endpoint_descriptor_no_audio bulk_sink; + } __attribute__ ((__packed__)) fs_descs, hs_descs; +} __attribute__ ((__packed__)) descriptors = { + { + htole32(FUNCTIONFS_DESCRIPTORS_MAGIC), + htole32(sizeof(descriptors)), /* length */ + htole32(3), /* full speed descriptor count */ + htole32(3), /* high speed descriptor count */ + }, + { + makeInterfaceDescriptor(), /* full speed interface descriptor */ + makeEndpointDescriptor(1 | USB_DIR_OUT, 64), + makeEndpointDescriptor(2 | USB_DIR_IN, 64), + }, + { + makeInterfaceDescriptor(), /* high speed interface descriptor */ + makeEndpointDescriptor(1 | USB_DIR_OUT, 512), + makeEndpointDescriptor(2 | USB_DIR_IN, 512), + }, +}; + +#define STR_INTERFACE "QDB Interface" + +const struct { + struct usb_functionfs_strings_head header; + struct { + __le16 code; + const char str1[sizeof(STR_INTERFACE)]; + } __attribute__ ((__packed__)) lang0; +} __attribute__ ((__packed__)) strings = { + { + htole32(FUNCTIONFS_STRINGS_MAGIC), + htole32(sizeof(strings)), + htole32(1), + htole32(1), + }, + { + htole16(0x0409), /* en-us */ + STR_INTERFACE, + }, +}; + +const QString usbFunctionFsPath = "/dev/usb-ffs/qdb/"; + +UsbGadget::UsbGadget() + : m_controlEndpoint(usbFunctionFsPath + "ep0"), + m_outEndpoint(usbFunctionFsPath + "ep1"), + m_inEndpoint(usbFunctionFsPath + "ep2"), + m_readThread{nullptr}, + m_writeThread{nullptr}, + m_reader{nullptr}, + m_writer{nullptr}, + m_reads{} +{ + +} + +UsbGadget::~UsbGadget() +{ + if (m_readThread) { + m_readThread->terminate(); + m_readThread->wait(); + } + if (m_writeThread) { + m_writeThread->terminate(); + m_readThread->wait(); + } + m_inEndpoint.close(); + m_outEndpoint.close(); + m_controlEndpoint.close(); +} + +bool UsbGadget::open(QIODevice::OpenMode mode) +{ + Q_ASSERT(mode == (QIODevice::ReadWrite | QIODevice::Unbuffered)); + QIODevice::open(mode); + + if (!openControlEndpoint()) + return false; + + qint64 bytes = m_controlEndpoint.write(reinterpret_cast<const char*>(&descriptors), sizeof(descriptors)); + if (bytes == -1) { + qDebug() << "Failed to write USB descriptors:" << m_controlEndpoint.errorString(); + return false; + } + + bytes = m_controlEndpoint.write(reinterpret_cast<const char*>(&strings), sizeof(strings)); + if (bytes == -1) { + qDebug() << "Failed to write USB strings:" << m_controlEndpoint.errorString(); + return false; + } + + if (!m_outEndpoint.open(QIODevice::ReadOnly | QIODevice::Unbuffered)) { + qDebug() << "Failed to open endpoint from host to gadget"; + return false; + } + + if (!m_inEndpoint.open(QIODevice::WriteOnly | QIODevice::Unbuffered)) { + qDebug() << "Failed to open endpoint from gadget to host"; + return false; + } + qDebug() << "Initialized function fs"; + + startReadThread(); + startWriteThread(); + + return true; +} + +qint64 UsbGadget::readData(char *data, qint64 maxSize) +{ + if (m_reads.isEmpty()) { + qDebug() << "UsbGadget read queue empty in readData"; + return -1; + } + QByteArray read = m_reads.dequeue(); + Q_ASSERT(read.size() <= maxSize); // TODO: handle too big reads + std::copy(read.begin(), read.end(), data); + + return read.size(); +} + +qint64 UsbGadget::writeData(const char *data, qint64 size) +{ + if (m_inEndpoint.isOpen()) { + emit writeAvailable(QByteArray{data, static_cast<int>(size)}); + return size; + } + + qDebug() << "Tried to send to host through closed endpoint"; + return -1; +} + +void UsbGadget::dataRead(QByteArray data) +{ + m_reads.enqueue(data); + emit readyRead(); +} + +void UsbGadget::startReadThread() +{ + m_reader = make_unique<UsbGadgetReader>(&m_outEndpoint); + m_readThread = make_unique<QThread>(); + + connect(m_reader.get(), &UsbGadgetReader::newRead, this, &UsbGadget::dataRead); + connect(m_readThread.get(), &QThread::started, m_reader.get(), &UsbGadgetReader::executeRead); + + m_reader->moveToThread(m_readThread.get()); + m_readThread->setObjectName("UsbGadgetReader"); + m_readThread->start(); +} + +void UsbGadget::startWriteThread() +{ + m_writer = make_unique<UsbGadgetWriter>(&m_inEndpoint); + m_writeThread = make_unique<QThread>(); + + connect(this, &UsbGadget::writeAvailable, m_writer.get(), &UsbGadgetWriter::write); + + m_writer->moveToThread(m_writeThread.get()); + m_writeThread->setObjectName("UsbGadgetWriter"); + m_writeThread->start(); +} + +bool UsbGadget::openControlEndpoint() +{ + if (!QFile::exists(m_controlEndpoint.fileName())) { + qCritical() << "USB ffs control endpoint" << m_controlEndpoint.fileName() << "does not exist"; + return false; + } + if (!m_controlEndpoint.open(QIODevice::ReadWrite | QIODevice::Unbuffered)) { + qCritical() << "Failed to open control endpoint" << m_controlEndpoint.fileName(); + return false; + } + return true; +} + diff --git a/qdbd/usb-gadget/usbgadget.h b/qdbd/usb-gadget/usbgadget.h new file mode 100644 index 0000000..5c32f46 --- /dev/null +++ b/qdbd/usb-gadget/usbgadget.h @@ -0,0 +1,72 @@ +/****************************************************************************** +** +** Copyright (C) 2016 The Qt Company Ltd. +** Contact: http://www.qt.io/licensing/ +** +** This file is part of the Qt Debug Bridge. +** +** $QT_BEGIN_LICENSE:COMM$ +** +** Commercial License Usage +** Licensees holding valid commercial Qt licenses may use this file in +** accordance with the commercial license agreement provided with the +** Software or, alternatively, in accordance with the terms contained in +** a written agreement between you and The Qt Company. For licensing terms +** and conditions see http://www.qt.io/terms-conditions. For further +** information use the contact form at http://www.qt.io/contact-us. +** +** $QT_END_LICENSE$ +** +******************************************************************************/ +#ifndef USBGADGET_H +#define USBGADGET_H + +class UsbGadgetReader; +class UsbGadgetWriter; + +#include <QtCore/qbytearray.h> +#include <QtCore/qfile.h> +#include <QtCore/qiodevice.h> +class QThread; +#include <QtCore/qqueue.h> + +#include <memory> + +class UsbGadget : public QIODevice +{ + Q_OBJECT + +public: + UsbGadget(); + virtual ~UsbGadget(); + + bool open(OpenMode mode) override; + +signals: + void writeAvailable(QByteArray data); + +protected: + qint64 readData(char *data, qint64 maxSize) override; + qint64 writeData(const char *data, qint64 maxSize) override; + +private slots: + void dataRead(QByteArray data); + +private: + bool openControlEndpoint(); + void startReadThread(); + void startWriteThread(); + + QFile m_controlEndpoint; + // Endpoints are named in line with USB terminology. Out means from host to + // gadget and in means from gadget to host. + QFile m_outEndpoint; + QFile m_inEndpoint; + std::unique_ptr<QThread> m_readThread; + std::unique_ptr<QThread> m_writeThread; + std::unique_ptr<UsbGadgetReader> m_reader; + std::unique_ptr<UsbGadgetWriter> m_writer; + QQueue<QByteArray> m_reads; +}; + +#endif // USBGADGET_H diff --git a/qdbd/usb-gadget/usbgadgetreader.cpp b/qdbd/usb-gadget/usbgadgetreader.cpp new file mode 100644 index 0000000..2aa6bf0 --- /dev/null +++ b/qdbd/usb-gadget/usbgadgetreader.cpp @@ -0,0 +1,73 @@ +/****************************************************************************** +** +** Copyright (C) 2016 The Qt Company Ltd. +** Contact: http://www.qt.io/licensing/ +** +** This file is part of the Qt Debug Bridge. +** +** $QT_BEGIN_LICENSE:COMM$ +** +** Commercial License Usage +** Licensees holding valid commercial Qt licenses may use this file in +** accordance with the commercial license agreement provided with the +** Software or, alternatively, in accordance with the terms contained in +** a written agreement between you and The Qt Company. For licensing terms +** and conditions see http://www.qt.io/terms-conditions. For further +** information use the contact form at http://www.qt.io/contact-us. +** +** $QT_END_LICENSE$ +** +******************************************************************************/ +#include "usbgadgetreader.h" + +#include "protocol/protocol.h" +#include "protocol/qdbmessage.h" + +#include <QtCore/qdebug.h> +#include <QtCore/qfile.h> +#include <QtCore/qtimer.h> + +UsbGadgetReader::UsbGadgetReader(QFile *readEndpoint) + : m_readEndpoint{readEndpoint} +{ + +} + +void UsbGadgetReader::executeRead() +{ + if (!m_readEndpoint->isOpen()) { + qWarning() << "tried to receive from host through closed endpoint"; + return; + } + + QTimer::singleShot(0, this, &UsbGadgetReader::executeRead); + + QByteArray headerBuffer{qdbHeaderSize, '\0'}; + int count = m_readEndpoint->read(headerBuffer.data(), headerBuffer.size()); + if (count == -1) { + qWarning() << "error in reading message header from endpoint"; + return; + } else if (count < headerBuffer.size()) { + qWarning() << "error: read" << count << "out of" << headerBuffer.size() << "byte header from endpoint"; + return; + } + + int dataSize = QdbMessage::GetDataSize(headerBuffer); + Q_ASSERT(dataSize >= 0); + if (dataSize == 0) { + emit newRead(headerBuffer); + return; + } + + QByteArray dataBuffer{dataSize, '\0'}; + count = m_readEndpoint->read(dataBuffer.data(), dataBuffer.size()); + if (count == -1) { + qWarning() << "error in reading data from endpoint"; + return; + } else if (count < dataBuffer.size()) { + qWarning() << "error: read" << count << "out of" << headerBuffer.size() << "byte data from endpoint"; + return; + } + + emit newRead(headerBuffer + dataBuffer); +} diff --git a/qdbd/usb-gadget/usbgadgetreader.h b/qdbd/usb-gadget/usbgadgetreader.h new file mode 100644 index 0000000..a07b859 --- /dev/null +++ b/qdbd/usb-gadget/usbgadgetreader.h @@ -0,0 +1,43 @@ +/****************************************************************************** +** +** Copyright (C) 2016 The Qt Company Ltd. +** Contact: http://www.qt.io/licensing/ +** +** This file is part of the Qt Debug Bridge. +** +** $QT_BEGIN_LICENSE:COMM$ +** +** Commercial License Usage +** Licensees holding valid commercial Qt licenses may use this file in +** accordance with the commercial license agreement provided with the +** Software or, alternatively, in accordance with the terms contained in +** a written agreement between you and The Qt Company. For licensing terms +** and conditions see http://www.qt.io/terms-conditions. For further +** information use the contact form at http://www.qt.io/contact-us. +** +** $QT_END_LICENSE$ +** +******************************************************************************/ +#ifndef USBGADGETREADER_H +#define USBGADGETREADER_H + +class QFile; +#include <QtCore/qobject.h> + +class UsbGadgetReader : public QObject +{ + Q_OBJECT +public: + UsbGadgetReader(QFile *readEndpoint); + +signals: + void newRead(QByteArray data); + +public slots: + void executeRead(); + +private: + QFile *m_readEndpoint; +}; + +#endif // USBGADGETREADER_H diff --git a/qdbd/usb-gadget/usbgadgetwriter.cpp b/qdbd/usb-gadget/usbgadgetwriter.cpp new file mode 100644 index 0000000..315514a --- /dev/null +++ b/qdbd/usb-gadget/usbgadgetwriter.cpp @@ -0,0 +1,50 @@ +/****************************************************************************** +** +** Copyright (C) 2016 The Qt Company Ltd. +** Contact: http://www.qt.io/licensing/ +** +** This file is part of the Qt Debug Bridge. +** +** $QT_BEGIN_LICENSE:COMM$ +** +** Commercial License Usage +** Licensees holding valid commercial Qt licenses may use this file in +** accordance with the commercial license agreement provided with the +** Software or, alternatively, in accordance with the terms contained in +** a written agreement between you and The Qt Company. For licensing terms +** and conditions see http://www.qt.io/terms-conditions. For further +** information use the contact form at http://www.qt.io/contact-us. +** +** $QT_END_LICENSE$ +** +******************************************************************************/ +#include "usbgadgetwriter.h" + +#include "protocol/protocol.h" +#include "protocol/qdbmessage.h" + +#include <QtCore/qdebug.h> +#include <QtCore/qfile.h> +#include <QtCore/qtimer.h> + +UsbGadgetWriter::UsbGadgetWriter(QFile *writeEndpoint) + : m_writeEndpoint{writeEndpoint} +{ + +} + +void UsbGadgetWriter::write(QByteArray data) +{ + if (!m_writeEndpoint->isOpen()) { + qWarning() << "UsbGadgetWriter: Tried to write to a closed endpoint"; + emit writeDone(false); + return; + } + + auto written = m_writeEndpoint->write(data); + if (written != data.size()) { + qWarning() << "UsbGadgetWriter: Write to endpoint failed"; + emit writeDone(false); + } + emit writeDone(true); +} diff --git a/qdbd/usb-gadget/usbgadgetwriter.h b/qdbd/usb-gadget/usbgadgetwriter.h new file mode 100644 index 0000000..72c15ba --- /dev/null +++ b/qdbd/usb-gadget/usbgadgetwriter.h @@ -0,0 +1,43 @@ +/****************************************************************************** +** +** Copyright (C) 2016 The Qt Company Ltd. +** Contact: http://www.qt.io/licensing/ +** +** This file is part of the Qt Debug Bridge. +** +** $QT_BEGIN_LICENSE:COMM$ +** +** Commercial License Usage +** Licensees holding valid commercial Qt licenses may use this file in +** accordance with the commercial license agreement provided with the +** Software or, alternatively, in accordance with the terms contained in +** a written agreement between you and The Qt Company. For licensing terms +** and conditions see http://www.qt.io/terms-conditions. For further +** information use the contact form at http://www.qt.io/contact-us. +** +** $QT_END_LICENSE$ +** +******************************************************************************/ +#ifndef USBGADGETWRITER_H +#define USBGADGETWRITER_H + +class QFile; +#include <QtCore/qobject.h> + +class UsbGadgetWriter : public QObject +{ + Q_OBJECT +public: + UsbGadgetWriter(QFile *writeEndpoint); + +signals: + void writeDone(bool success); + +public slots: + void write(QByteArray data); + +private: + QFile *m_writeEndpoint; +}; + +#endif // USBGADGETWRITER_H diff --git a/test/servicetest.cpp b/test/servicetest.cpp new file mode 100644 index 0000000..c87d429 --- /dev/null +++ b/test/servicetest.cpp @@ -0,0 +1,508 @@ +/****************************************************************************** +** +** Copyright (C) 2016 The Qt Company Ltd. +** Contact: http://www.qt.io/licensing/ +** +** This file is part of the Qt Debug Bridge. +** +** $QT_BEGIN_LICENSE:COMM$ +** +** Commercial License Usage +** Licensees holding valid commercial Qt licenses may use this file in +** accordance with the commercial license agreement provided with the +** Software or, alternatively, in accordance with the terms contained in +** a written agreement between you and The Qt Company. For licensing terms +** and conditions see http://www.qt.io/terms-conditions. For further +** information use the contact form at http://www.qt.io/contact-us. +** +** $QT_END_LICENSE$ +** +******************************************************************************/ +#include "../client/connection.h" +#include "../client/filepullservice.h" +#include "../client/filepushservice.h" +#include "../client/processservice.h" +#include "../client/echoservice.h" +#include "../utils/make_unique.h" +#include "usb/usbconnection.h" +#include "protocol/qdbtransport.h" +#include "protocol/services.h" + +#include <QtCore/qdebug.h> +#include <QtCore/qregularexpression.h> +#include <QtCore/qtimer.h> +#include <QtTest/QtTest> + +const int testTimeout = 500; // in milliseconds + +// Helper to initialize Connection in testcases +struct ConnectionContext +{ + ConnectionContext() + : connection{new QdbTransport{new UsbConnection{}}} + { + QVERIFY(connection.initialize()); + + connection.connect(); + } + Connection connection; +}; + +class ServiceTest : public QObject +{ + Q_OBJECT +private slots: + void initTestCase(); + void echo(); + void processOutput(); + void processMultipleOutput(); + void processErrorCode(); + void processNonExistent(); + void processCrash(); + void processInput(); + void processMultipleInput(); + void filePush(); + void filePushNonexistent(); + void filePull(); + void filePullNonexistent(); + void filePullToUnopenable(); +}; + +const QString pushPullFileName = "qdbtestfile1"; +static QByteArray pushPullFileContents = "abcd\nefgh\n"; +const QString nonexistentFileName{"qdbtestfile2"}; + +void ServiceTest::initTestCase() +{ + qRegisterMetaType<QProcess::ProcessError>("QProcess::ProcessError"); + qRegisterMetaType<QProcess::ExitStatus>("QProcess::ExitStatus"); +} + +void ServiceTest::echo() +{ + ConnectionContext ctx; + + EchoService echo{&ctx.connection}; + connect(&echo, &EchoService::initialized, [&]() { + echo.send("ABCD"); + }); + QSignalSpy spy{&echo, &EchoService::echo}; + + echo.initialize(); + + spy.wait(testTimeout); + QCOMPARE(spy.count(), 1); + QCOMPARE(spy[0][0].toByteArray(), QByteArray{"ABCD"}); +} + +void ServiceTest::processOutput() +{ + ConnectionContext ctx; + + QByteArray output; + + ProcessService processService{&ctx.connection}; + connect(&processService, &ProcessService::executionError, [](QProcess::ProcessError error) { + qDebug() << "Command not run, error:" << error; + QFAIL("Command was not run successfully"); + }); + connect(&processService, &Service::initialized, [&]() { + processService.execute("echo", {"ABCD"}); + }); + connect(&processService, &ProcessService::readyRead, [&]() { + output.append(processService.read()); + }); + QSignalSpy spy{&processService, &ProcessService::executed}; + + processService.initialize(); + + spy.wait(testTimeout); + QCOMPARE(spy.count(), 1); + auto exitCode = spy[0][0].toInt(); + QProcess::ExitStatus exitStatus = spy[0][1].value<QProcess::ExitStatus>(); + auto finalOutput = spy[0][2].toByteArray(); + output.append(finalOutput); + QCOMPARE(exitCode, 0); + QCOMPARE(exitStatus, QProcess::NormalExit); + QCOMPARE(output, QByteArray{"ABCD\n"}); +} + +void ServiceTest::processMultipleOutput() +{ + ConnectionContext ctx; + + QByteArray output; + + ProcessService processService{&ctx.connection}; + connect(&processService, &ProcessService::executionError, [](QProcess::ProcessError error) { + qDebug() << "Command not run, error:" << error; + QFAIL("Command was not run successfully"); + }); + + connect(&processService, &Service::initialized, [&]() { + processService.execute("sh", {"-c", "echo abcd && sleep 1 && echo defg"}); + }); + connect(&processService, &ProcessService::readyRead, [&]() { + output.append(processService.read()); + }); + QSignalSpy readyReadSpy{&processService, &ProcessService::readyRead}; + QSignalSpy executedSpy{&processService, &ProcessService::executed}; + + processService.initialize(); + + executedSpy.wait(1000 + testTimeout); + QCOMPARE(executedSpy.count(), 1); + // In principle there could be only one (or more than two) readyRead, but in + // practice the above command seems split into two outputs and otherwise + // this test is not fulfilling its purpose. + QCOMPARE(readyReadSpy.count(), 2); + auto exitCode = executedSpy[0][0].toInt(); + QProcess::ExitStatus exitStatus = executedSpy[0][1].value<QProcess::ExitStatus>(); + auto finalOutput = executedSpy[0][2].toByteArray(); + output.append(finalOutput); + QCOMPARE(exitCode, 0); + QCOMPARE(exitStatus, QProcess::NormalExit); + QCOMPARE(output, QByteArray{"abcd\ndefg\n"}); +} + +void ServiceTest::processErrorCode() +{ + ConnectionContext ctx; + + ProcessService processService{&ctx.connection}; + connect(&processService, &ProcessService::executionError, [](QProcess::ProcessError error) { + qDebug() << "Command not run, error:" << error; + QFAIL("Command was not run successfully"); + }); + connect(&processService, &Service::initialized, [&]() { + processService.execute("test", {"-z", "ABCD"}); + }); + QSignalSpy spy{&processService, &ProcessService::executed}; + + processService.initialize(); + + spy.wait(testTimeout); + QCOMPARE(spy.count(), 1); + auto exitCode = spy[0][0].toInt(); + QProcess::ExitStatus exitStatus = spy[0][1].value<QProcess::ExitStatus>(); + auto output = spy[0][2].toString(); + QCOMPARE(exitCode, 1); + QCOMPARE(exitStatus, QProcess::NormalExit); + QCOMPARE(output, QString{""}); +} + +void ServiceTest::processNonExistent() +{ + ConnectionContext ctx; + + ProcessService processService{&ctx.connection}; + connect(&processService, &ProcessService::executed, [](int, QProcess::ExitStatus, QString) { + QFAIL("Command was unexpectedly run successfully"); + }); + connect(&processService, &Service::initialized, [&]() { + processService.execute("lsfdajlvaie", {}); + }); + QSignalSpy spy{&processService, &ProcessService::executionError}; + + processService.initialize(); + + spy.wait(testTimeout); + QCOMPARE(spy.count(), 1); + auto error = spy[0][0].value<QProcess::ProcessError>(); + QCOMPARE(error, QProcess::FailedToStart); +} + +void ServiceTest::processCrash() +{ + ConnectionContext ctx; + + ProcessService processService{&ctx.connection}; + connect(&processService, &Service::initialized, [&]() { + // Crash the process by having it send SIGSEGV to itself + processService.execute("sh", {"-c", "kill -SEGV $$"}); + }); + QSignalSpy errorSpy{&processService, &ProcessService::executionError}; + QSignalSpy executedSpy{&processService, &ProcessService::executed}; + + processService.initialize(); + + errorSpy.wait(testTimeout); + QCOMPARE(errorSpy.count(), 1); + auto error = errorSpy[0][0].value<QProcess::ProcessError>(); + QCOMPARE(error, QProcess::Crashed); + + executedSpy.wait(testTimeout); + QCOMPARE(executedSpy.count(), 1); + auto exitCode = executedSpy[0][0].toInt(); + auto exitStatus = executedSpy[0][1].value<QProcess::ExitStatus>(); + auto output = executedSpy[0][2].toString(); + QCOMPARE(exitCode, 11); // 11 for segfault + QCOMPARE(exitStatus, QProcess::CrashExit); + QCOMPARE(output, QString{""}); +} + +void ServiceTest::processInput() +{ + ConnectionContext ctx; + + QByteArray output; + + ProcessService processService{&ctx.connection}; + connect(&processService, &ProcessService::executionError, [](QProcess::ProcessError error) { + qDebug() << "Command not run, error:" << error; + QFAIL("Command was not run successfully"); + }); + connect(&processService, &Service::initialized, [&]() { + processService.execute("sh", {"-c", "read input; echo $input"}); + }); + connect(&processService, &ProcessService::readyRead, [&]() { + output.append(processService.read()); + }); + connect(&processService, &ProcessService::started, [&]() { + processService.write("abcd\n"); + }); + QSignalSpy spy{&processService, &ProcessService::executed}; + + processService.initialize(); + + spy.wait(testTimeout); + QCOMPARE(spy.count(), 1); + auto exitCode = spy[0][0].toInt(); + QProcess::ExitStatus exitStatus = spy[0][1].value<QProcess::ExitStatus>(); + auto finalOutput = spy[0][2].toByteArray(); + output.append(finalOutput); + QCOMPARE(exitCode, 0); + QCOMPARE(exitStatus, QProcess::NormalExit); + QCOMPARE(output, QByteArray{"abcd\n"}); +} + +void ServiceTest::processMultipleInput() +{ + ConnectionContext ctx; + + QByteArray output; + + ProcessService processService{&ctx.connection}; + connect(&processService, &ProcessService::executionError, [](QProcess::ProcessError error) { + qDebug() << "Command not run, error:" << error; + QFAIL("Command was not run successfully"); + }); + connect(&processService, &Service::initialized, [&]() { + processService.execute("sh", {"-c", "for i in {1..2}; do read input; echo $input; done"}); + }); + connect(&processService, &ProcessService::readyRead, [&]() { + output.append(processService.read()); + }); + connect(&processService, &ProcessService::started, [&]() { + processService.write("abcd\n"); + processService.write("efgh\n"); + }); + QSignalSpy spy{&processService, &ProcessService::executed}; + + processService.initialize(); + + spy.wait(testTimeout); + QCOMPARE(spy.count(), 1); + auto exitCode = spy[0][0].toInt(); + QProcess::ExitStatus exitStatus = spy[0][1].value<QProcess::ExitStatus>(); + auto finalOutput = spy[0][2].toByteArray(); + output.append(finalOutput); + QCOMPARE(exitCode, 0); + QCOMPARE(exitStatus, QProcess::NormalExit); + QCOMPARE(output, QByteArray{"abcd\nefgh\n"}); +} + +void ServiceTest::filePush() +{ + ConnectionContext ctx; + + // Write source file + QFile source{pushPullFileName}; + QVERIFY(source.open(QIODevice::WriteOnly)); + source.write(pushPullFileContents); + source.close(); + + // Push source file to device (it's cleaned up in filePullToUnopenable()) + FilePushService filePushService{&ctx.connection}; + connect(&filePushService, &FilePushService::error, [](QString error) { + qCritical() << error; + QFAIL("Error while pushing file."); + }); + connect(&filePushService, &Service::initialized, [&]() { + filePushService.push(pushPullFileName, pushPullFileName); + }); + QSignalSpy pushSpy{&filePushService, &FilePushService::pushed}; + + filePushService.initialize(); + + pushSpy.wait(testTimeout); + QCOMPARE(pushSpy.count(), 1); + + // Remove source file + source.remove(); + + // Check contents on device + QByteArray output; + + ProcessService processService{&ctx.connection}; + connect(&processService, &ProcessService::executionError, [](QProcess::ProcessError error) { + qDebug() << "Command not run, error:" << error; + QFAIL("Command was not run successfully"); + }); + connect(&processService, &Service::initialized, [&]() { + processService.execute("cat", {pushPullFileName}); + }); + connect(&processService, &ProcessService::readyRead, [&]() { + output.append(processService.read()); + }); + QSignalSpy processSpy{&processService, &ProcessService::executed}; + + processService.initialize(); + + processSpy.wait(testTimeout); + QCOMPARE(processSpy.count(), 1); + auto exitCode = processSpy[0][0].toInt(); + QProcess::ExitStatus exitStatus = processSpy[0][1].value<QProcess::ExitStatus>(); + auto finalOutput = processSpy[0][2].toByteArray(); + output.append(finalOutput); + QCOMPARE(exitCode, 0); + QCOMPARE(exitStatus, QProcess::NormalExit); + QCOMPARE(output, pushPullFileContents); +} + +void ServiceTest::filePushNonexistent() +{ + QVERIFY(!QFile::exists(nonexistentFileName)); + + ConnectionContext ctx; + + FilePushService filePushService{&ctx.connection}; + connect(&filePushService, &FilePushService::pushed, []() { + QFAIL("Unexpectedly succeeded pushing nonexistent file."); + }); + connect(&filePushService, &Service::initialized, [&]() { + filePushService.push(nonexistentFileName, nonexistentFileName); + }); + QSignalSpy spy{&filePushService, &FilePushService::error}; + + filePushService.initialize(); + + spy.wait(testTimeout); + QCOMPARE(spy.count(), 1); + QRegularExpression regexp{"^Could not open.+host$"}; + auto errorMessage = spy[0][0].toString(); + QVERIFY(regexp.match(errorMessage).hasMatch()); +} + +// This test relies on the file pushed in filePush() +void ServiceTest::filePull() +{ + ConnectionContext ctx; + + // Pull source file from device + FilePullService filePullService{&ctx.connection}; + connect(&filePullService, &FilePullService::error, [](QString error) { + qCritical() << error; + QFAIL("Error while pulling file"); + }); + connect(&filePullService, &Service::initialized, [&]() { + filePullService.pull(pushPullFileName, pushPullFileName); + }); + QSignalSpy pullSpy{&filePullService, &FilePullService::pulled}; + + filePullService.initialize(); + + pullSpy.wait(testTimeout); + QCOMPARE(pullSpy.count(), 1); + + // Check contents + QFile sink{pushPullFileName}; + QVERIFY(sink.open(QIODevice::ReadOnly)); + auto contents = sink.readAll(); + QCOMPARE(contents, pushPullFileContents); + + sink.close(); + sink.remove(); +} + +void ServiceTest::filePullNonexistent() +{ + const QString nonexistentFileName{"qdbtestfile2"}; + + ConnectionContext ctx; + + // Pull source file from device + FilePullService filePullService{&ctx.connection}; + connect(&filePullService, &FilePullService::pulled, []() { + QFAIL("Unexpectedly succeeded pulling nonexistent file"); + }); + connect(&filePullService, &Service::initialized, [&]() { + filePullService.pull(nonexistentFileName, nonexistentFileName); + }); + QSignalSpy spy{&filePullService, &FilePullService::error}; + + filePullService.initialize(); + + spy.wait(testTimeout); + QCOMPARE(spy.count(), 1); + QRegularExpression regexp{"^Could not open.+device$"}; + auto errorMessage = spy[0][0].toString(); + QVERIFY(regexp.match(errorMessage).hasMatch()); +} + +// This test relies on the file pushed in filePush() and removes it in the end +void ServiceTest::filePullToUnopenable() +{ + const QString fileName{"qdbtestfile2"}; + + QFile blocker{fileName}; + blocker.open(QIODevice::WriteOnly); + blocker.setPermissions(QFileDevice::ReadUser); + blocker.close(); + + ConnectionContext ctx; + + // Pull source file from device + FilePullService filePullService{&ctx.connection}; + connect(&filePullService, &FilePullService::pulled, []() { + QFAIL("Unexpectedly succeeded pulling into file that can't be written to"); + }); + connect(&filePullService, &Service::initialized, [&]() { + filePullService.pull(pushPullFileName, fileName); + }); + QSignalSpy spy{&filePullService, &FilePullService::error}; + + filePullService.initialize(); + + spy.wait(testTimeout); + + blocker.remove(); + + QCOMPARE(spy.count(), 1); + QRegularExpression regexp{"^Could not open.+host$"}; + auto errorMessage = spy[0][0].toString(); + QVERIFY(regexp.match(errorMessage).hasMatch()); + + // Remove file from device + ProcessService processService{&ctx.connection}; + connect(&processService, &ProcessService::executionError, [](QProcess::ProcessError error) { + qDebug() << "Command not run, error:" << error; + QFAIL("Command was not run successfully"); + }); + connect(&processService, &Service::initialized, [&]() { + processService.execute("rm", {pushPullFileName}); + }); + QSignalSpy processSpy{&processService, &ProcessService::executed}; + + processService.initialize(); + + processSpy.wait(testTimeout); + QCOMPARE(processSpy.count(), 1); + auto exitCode = processSpy[0][0].toInt(); + QProcess::ExitStatus exitStatus = processSpy[0][1].value<QProcess::ExitStatus>(); + QCOMPARE(exitCode, 0); + QCOMPARE(exitStatus, QProcess::NormalExit); +} + +QTEST_GUILESS_MAIN(ServiceTest) +#include "servicetest.moc" diff --git a/test/servicetest.pro b/test/servicetest.pro new file mode 100644 index 0000000..255595a --- /dev/null +++ b/test/servicetest.pro @@ -0,0 +1,30 @@ +QT -= gui +QT += testlib + +win32: CONFIG += console +CONFIG -= app_bundle + +TEMPLATE = app + +HEADERS += \ + ../client/connection.h \ + ../client/filepullservice.h \ + ../client/filepushservice.h \ + ../client/processservice.h \ + ../client/echoservice.h \ + ../client/service.h + +SOURCES += \ + servicetest.cpp \ + ../client/connection.cpp \ + ../client/filepullservice.cpp \ + ../client/filepushservice.cpp \ + ../client/processservice.cpp \ + ../client/echoservice.cpp \ + ../client/service.cpp + +INCLUDEPATH += $$PWD/../libqdb + +LIBS = -L$$OUT_PWD/../libqdb -lqdb +QMAKE_RPATHDIR += ../libqdb + diff --git a/test/streamtest.cpp b/test/streamtest.cpp new file mode 100644 index 0000000..e47b586 --- /dev/null +++ b/test/streamtest.cpp @@ -0,0 +1,469 @@ +/****************************************************************************** +** +** Copyright (C) 2016 The Qt Company Ltd. +** Contact: http://www.qt.io/licensing/ +** +** This file is part of the Qt Debug Bridge. +** +** $QT_BEGIN_LICENSE:COMM$ +** +** Commercial License Usage +** Licensees holding valid commercial Qt licenses may use this file in +** accordance with the commercial license agreement provided with the +** Software or, alternatively, in accordance with the terms contained in +** a written agreement between you and The Qt Company. For licensing terms +** and conditions see http://www.qt.io/terms-conditions. For further +** information use the contact form at http://www.qt.io/contact-us. +** +** $QT_END_LICENSE$ +** +******************************************************************************/ +#include "../utils/make_unique.h" +#include "usb/usbconnection.h" +#include "protocol/qdbmessage.h" +#include "protocol/qdbtransport.h" +#include "protocol/services.h" + +#include <QtCore/qdebug.h> +#include <QtCore/qtimer.h> +#include <QtTest/QtTest> + +const int testTimeout = 500; // in milliseconds + +class TestCase : public QObject +{ + Q_OBJECT +public slots: + void run() + { + m_transport = make_unique<QdbTransport>(new UsbConnection{}); + if (m_transport->open()) { + qDebug() << "opened transport"; + connect(m_transport.get(), &QdbTransport::messageAvailable, this, &TestCase::testPhases); + testPhases(); + } else { + qDebug() << "failed to open transport"; + } + } + + virtual void testPhases() = 0; +signals: + void passed(); +protected: + std::unique_ptr<QdbTransport> m_transport; + int m_phase = 0; +}; + +class OpenWriteCloseEchoTest : public TestCase +{ + Q_OBJECT +public slots: + void testPhases() override + { + switch (m_phase) { + case 0: { + QdbMessage cnxn{QdbMessage::Connect, 0, 0}; + QVERIFY(m_transport->send(cnxn)); + break; + } + case 1: { + QdbMessage response = m_transport->receive(); + QCOMPARE(response.command(), QdbMessage::Connect); + + QdbMessage open{QdbMessage::Open, m_hostId, 0, tagBuffer(EchoTag)}; + QVERIFY(m_transport->send(open)); + break; + } + case 2: { + QdbMessage response = m_transport->receive(); + QCOMPARE(response.command(), QdbMessage::Ok); + QCOMPARE(response.hostStream(), m_hostId); + QVERIFY(response.deviceStream() != 0); + QCOMPARE(response.data(), QByteArray{}); + + m_deviceId = response.deviceStream(); + + QdbMessage write{QdbMessage::Write, m_hostId, m_deviceId, QByteArray{"\x00\x00\x00\x04""ABCD", 8}}; + QVERIFY(m_transport->send(write)); + break; + } + case 3: { + QdbMessage response = m_transport->receive(); + QCOMPARE(response.command(), QdbMessage::Ok); + QCOMPARE(response.hostStream(), m_hostId); + QCOMPARE(response.deviceStream(), m_deviceId); + QCOMPARE(response.data(), QByteArray{}); + break; + } + case 4: { + QdbMessage response = m_transport->receive(); + QCOMPARE(response.command(), QdbMessage::Write); + QCOMPARE(response.hostStream(), m_hostId); + QCOMPARE(response.deviceStream(), m_deviceId); + QByteArray data{"\x00\x00\x00\x04""ABCD", 8}; + QCOMPARE(response.data(), data); + + QdbMessage ok{QdbMessage::Ok, m_hostId, m_deviceId}; + QVERIFY(m_transport->send(ok)); + + QdbMessage close{QdbMessage::Close, m_hostId, m_deviceId}; + QVERIFY(m_transport->send(close)); + + emit passed(); + break; + } + } + ++m_phase; + } + +private: + const StreamId m_hostId = 1; + StreamId m_deviceId = 0; +}; + +class DoubleConnectTest : public TestCase +{ + Q_OBJECT +public slots: + void testPhases() override + { + switch (m_phase) { + case 0: { + QdbMessage connect{QdbMessage::Connect, 0, 0}; + QVERIFY(m_transport->send(connect)); + break; + } + case 1: { + QdbMessage response = m_transport->receive(); + QCOMPARE(response.command(), QdbMessage::Connect); + QCOMPARE(response.hostStream(), 0u); + QCOMPARE(response.deviceStream(), 0u); + QCOMPARE(response.data(), QByteArray{}); + + QdbMessage connect{QdbMessage::Connect, 0, 0}; + QVERIFY(m_transport->send(connect)); + break; + } + case 2: { + QdbMessage response = m_transport->receive(); + QCOMPARE(response.command(), QdbMessage::Connect); + QCOMPARE(response.hostStream(), 0u); + QCOMPARE(response.deviceStream(), 0u); + QCOMPARE(response.data(), QByteArray{}); + + emit passed(); + break; + } + } + ++m_phase; + } +}; + +// Closing a stream twice caused a segmentation fault at one point. This is +// guarding against that regression. +class DoubleCloseTest : public TestCase +{ + Q_OBJECT +public slots: + void testPhases() override + { + switch (m_phase) { + case 0: { + QdbMessage cnxn{QdbMessage::Connect, 0, 0}; + QVERIFY(m_transport->send(cnxn)); + break; + } + case 1: { + QdbMessage response = m_transport->receive(); + QCOMPARE(response.command(), QdbMessage::Connect); + + QdbMessage open{QdbMessage::Open, m_hostId, 0, tagBuffer(EchoTag)}; + QVERIFY(m_transport->send(open)); + break; + } + case 2: { + QdbMessage response = m_transport->receive(); + QCOMPARE(response.command(), QdbMessage::Ok); + QCOMPARE(response.hostStream(), m_hostId); + QVERIFY(response.deviceStream() != 0); + QCOMPARE(response.data(), QByteArray{}); + + m_deviceId = response.deviceStream(); + + QdbMessage close{QdbMessage::Close, m_hostId, m_deviceId}; + QVERIFY(m_transport->send(close)); + QVERIFY(m_transport->send(close)); + + // do a write to check whether qdbd is still alive + + QdbMessage write{QdbMessage::Write, m_hostId, m_deviceId, QByteArray{"\x00\x00\x00\x04""ABCD", 8}}; + QVERIFY(m_transport->send(write)); + break; + } + case 3: { + QdbMessage response = m_transport->receive(); + QCOMPARE(response.command(), QdbMessage::Close); + QCOMPARE(response.hostStream(), m_hostId); + QCOMPARE(response.deviceStream(), m_deviceId); + QCOMPARE(response.data(), QByteArray{}); + + emit passed(); + break; + } + } + ++m_phase; + } + +private: + const StreamId m_hostId = 1; + StreamId m_deviceId = 0; +}; + +class WriteToNonExistentStreamTest : public TestCase +{ + Q_OBJECT +public slots: + void testPhases() override + { + switch (m_phase) { + case 0: { + QdbMessage connect{QdbMessage::Connect, 0, 0}; + QVERIFY(m_transport->send(connect)); + break; + } + case 1: { + QdbMessage response = m_transport->receive(); + QCOMPARE(response.command(), QdbMessage::Connect); + QCOMPARE(response.hostStream(), 0u); + QCOMPARE(response.deviceStream(), 0u); + QCOMPARE(response.data(), QByteArray{}); + + // Try to directly write to an unopened stream + QdbMessage write{QdbMessage::Write, m_hostId, m_deviceId}; + QVERIFY(m_transport->send(write)); + break; + } + case 2: { + QdbMessage response = m_transport->receive(); + QCOMPARE(response.command(), QdbMessage::Close); + QCOMPARE(response.hostStream(), m_hostId); + QCOMPARE(response.deviceStream(), m_deviceId); + QCOMPARE(response.data(), QByteArray{}); + + // Open and close a stream and then try to write to it + QdbMessage open{QdbMessage::Open, m_hostId, 0, tagBuffer(EchoTag)}; + QVERIFY(m_transport->send(open)); + break; + } + case 3: { + QdbMessage response = m_transport->receive(); + QCOMPARE(response.command(), QdbMessage::Ok); + QCOMPARE(response.hostStream(), m_hostId); + QVERIFY(response.deviceStream() != 0); + QCOMPARE(response.data(), QByteArray{}); + + m_deviceId2 = response.deviceStream(); + + QdbMessage close{QdbMessage::Close, m_hostId, m_deviceId2}; + QVERIFY(m_transport->send(close)); + + QdbMessage write{QdbMessage::Write, m_hostId, m_deviceId2, QByteArray{"\x00\x00\x00\x04""ABCD", 8}}; + QVERIFY(m_transport->send(write)); + break; + } + case 4: { + QdbMessage response = m_transport->receive(); + QCOMPARE(response.command(), QdbMessage::Close); + QCOMPARE(response.hostStream(), m_hostId); + // Device stream ID does not matter, since device already closed it + QCOMPARE(response.data(), QByteArray{}); + emit passed(); + break; + } + } + ++m_phase; + } +private: + const StreamId m_hostId = 435; + const StreamId m_deviceId = 7542; + StreamId m_deviceId2 = 0; +}; + +class TwoEchoStreamsTest : public TestCase +{ + Q_OBJECT +public slots: + void testPhases() override + { + switch (m_phase) { + case 0: { + QdbMessage cnxn{QdbMessage::Connect, 0, 0}; + QVERIFY(m_transport->send(cnxn)); + break; + } + case 1: { + QdbMessage response = m_transport->receive(); + QCOMPARE(response.command(), QdbMessage::Connect); + + QdbMessage open{QdbMessage::Open, m_hostId1, 0, tagBuffer(EchoTag)}; + QVERIFY(m_transport->send(open)); + break; + } + case 2: { + QdbMessage response = m_transport->receive(); + QCOMPARE(response.command(), QdbMessage::Ok); + QCOMPARE(response.hostStream(), m_hostId1); + QVERIFY(response.deviceStream() != 0); + QCOMPARE(response.data(), QByteArray{}); + + m_deviceId1 = response.deviceStream(); + + qDebug() << "writing ABCD"; + QdbMessage write{QdbMessage::Write, m_hostId1, m_deviceId1, QByteArray{"\x00\x00\x00\x04""ABCD", 8}}; + QVERIFY(m_transport->send(write)); + qDebug() << "wrote"; + break; + } + case 3: { + QdbMessage response = m_transport->receive(); + QCOMPARE(response.command(), QdbMessage::Ok); + QCOMPARE(response.hostStream(), m_hostId1); + QCOMPARE(response.deviceStream(), m_deviceId1); + QCOMPARE(response.data(), QByteArray{}); + break; + } + case 4: { + QdbMessage response = m_transport->receive(); + QCOMPARE(response.command(), QdbMessage::Write); + QCOMPARE(response.hostStream(), m_hostId1); + QCOMPARE(response.deviceStream(), m_deviceId1); + QByteArray data{"\x00\x00\x00\x04""ABCD", 8}; + QCOMPARE(response.data(), data); + + QdbMessage ok{QdbMessage::Ok, m_hostId1, m_deviceId1}; + QVERIFY(m_transport->send(ok)); + + QdbMessage open{QdbMessage::Open, m_hostId2, 0, tagBuffer(EchoTag)}; + QVERIFY(m_transport->send(open)); + break; + } + case 5: { + QdbMessage response = m_transport->receive(); + QCOMPARE(response.command(), QdbMessage::Ok); + QCOMPARE(response.hostStream(), m_hostId2); + QVERIFY(response.deviceStream() != 0); + QCOMPARE(response.data(), QByteArray{}); + + m_deviceId2 = response.deviceStream(); + + QdbMessage write{QdbMessage::Write, m_hostId2, m_deviceId2, + QByteArray{"\x00\x00\x00\x05\x00\x01\x02\x03\x04", 9}}; + QVERIFY(m_transport->send(write)); + break; + } + case 6: { + QdbMessage response = m_transport->receive(); + QCOMPARE(response.command(), QdbMessage::Ok); + QCOMPARE(response.hostStream(), m_hostId2); + QCOMPARE(response.deviceStream(), m_deviceId2); + QCOMPARE(response.data(), QByteArray{}); + break; + } + case 7: { + QdbMessage response = m_transport->receive(); + QCOMPARE(response.command(), QdbMessage::Write); + QCOMPARE(response.hostStream(), m_hostId2); + QCOMPARE(response.deviceStream(), m_deviceId2); + auto data = QByteArray{"\x00\x00\x00\x05\x00\x01\x02\x03\x04", 9}; + QCOMPARE(response.data(), data); + + QdbMessage ok{QdbMessage::Ok, m_hostId2, m_deviceId2}; + QVERIFY(m_transport->send(ok)); + + QdbMessage close{QdbMessage::Close, m_hostId2, m_deviceId2}; + QVERIFY(m_transport->send(close)); + + QdbMessage write{QdbMessage::Write, m_hostId1, m_deviceId1, QByteArray{"\x00\x00\x00\x03QDB", 7}}; + QVERIFY(m_transport->send(write)); + break; + } + case 8: { + QdbMessage response = m_transport->receive(); + QCOMPARE(response.command(), QdbMessage::Ok); + QCOMPARE(response.hostStream(), m_hostId1); + QCOMPARE(response.deviceStream(), m_deviceId1); + QCOMPARE(response.data(), QByteArray{}); + break; + } + case 9: { + QdbMessage response = m_transport->receive(); + QCOMPARE(response.command(), QdbMessage::Write); + QCOMPARE(response.hostStream(), m_hostId1); + QCOMPARE(response.deviceStream(), m_deviceId1); + QByteArray data{"\x00\x00\x00\x03QDB", 7}; + QCOMPARE(response.data(), data); + + QdbMessage ok{QdbMessage::Ok, m_hostId1, m_deviceId1}; + QVERIFY(m_transport->send(ok)); + + QdbMessage close{QdbMessage::Close, m_hostId1, m_deviceId1}; + QVERIFY(m_transport->send(close)); + + emit passed(); + break; + } + } + ++m_phase; + } +private: + const StreamId m_hostId1 = 1; + StreamId m_deviceId1 = 0; + const StreamId m_hostId2 = 2; + StreamId m_deviceId2 = 0; +}; + +void testCase(TestCase *test) +{ + QSignalSpy spy{test, &TestCase::passed}; + QTimer::singleShot(0, test, &TestCase::run); + spy.wait(testTimeout); + QCOMPARE(spy.count(), 1); +} + +class StreamTest : public QObject +{ + Q_OBJECT +private slots: + void openWriteCloseEcho() + { + OpenWriteCloseEchoTest test; + testCase(&test); + } + + void doubleConnect() + { + DoubleConnectTest test; + testCase(&test); + } + + void doubleClose() + { + DoubleCloseTest test; + testCase(&test); + } + + void writeToNonexistentStream() + { + WriteToNonExistentStreamTest test; + testCase(&test); + } + + void twoEchoStreamsTest() + { + TwoEchoStreamsTest test; + testCase(&test); + } +}; + +QTEST_GUILESS_MAIN(StreamTest) +#include "streamtest.moc" diff --git a/test/streamtest.pro b/test/streamtest.pro new file mode 100644 index 0000000..a281ca0 --- /dev/null +++ b/test/streamtest.pro @@ -0,0 +1,15 @@ +QT -= gui +QT += testlib + +win32: CONFIG += console +CONFIG -= app_bundle + +TEMPLATE = app + +SOURCES += streamtest.cpp + +INCLUDEPATH += $$PWD/../libqdb + +LIBS = -L$$OUT_PWD/../libqdb -lqdb +QMAKE_RPATHDIR += ../libqdb + diff --git a/test/test.pro b/test/test.pro new file mode 100644 index 0000000..28b6fad --- /dev/null +++ b/test/test.pro @@ -0,0 +1,7 @@ +TEMPLATE = subdirs + +SUBDIRS = \ + tst_qdbmessage.pro \ + tst_stream.pro \ + servicetest.pro \ + streamtest.pro diff --git a/test/tst_qdbmessage.cpp b/test/tst_qdbmessage.cpp new file mode 100644 index 0000000..121d65d --- /dev/null +++ b/test/tst_qdbmessage.cpp @@ -0,0 +1,148 @@ +/****************************************************************************** +** +** Copyright (C) 2016 The Qt Company Ltd. +** Contact: http://www.qt.io/licensing/ +** +** This file is part of the Qt Debug Bridge. +** +** $QT_BEGIN_LICENSE:COMM$ +** +** Commercial License Usage +** Licensees holding valid commercial Qt licenses may use this file in +** accordance with the commercial license agreement provided with the +** Software or, alternatively, in accordance with the terms contained in +** a written agreement between you and The Qt Company. For licensing terms +** and conditions see http://www.qt.io/terms-conditions. For further +** information use the contact form at http://www.qt.io/contact-us. +** +** $QT_END_LICENSE$ +** +******************************************************************************/ +#include <QtTest/QtTest> + +#include "../libqdb/protocol/protocol.h" +#include "../libqdb/protocol/qdbmessage.h" + +class tst_QdbMessage : public QObject +{ + Q_OBJECT +private slots: + void construction_data(); + void construction(); + void setters(); + void roundtrip_data(); + void roundtrip(); + void gettingSize_data(); + void gettingSize(); +}; + +void testData() +{ + QTest::addColumn<QdbMessage::CommandType>("command"); + QTest::addColumn<StreamId>("hostStream"); + QTest::addColumn<StreamId>("deviceStream"); + QTest::addColumn<QByteArray>("data"); + QTest::addColumn<int>("dataSize"); + + QTest::newRow("connect") << QdbMessage::Connect << 0u << 0u << QByteArray() << 0; + QTest::newRow("open") << QdbMessage::Open << 1u << 0u << QByteArray("\x05\x04\x03\x02\x01") << 5; + QTest::newRow("write") << QdbMessage::Write << 255u << 254u << QByteArray("\x01\x02\x03") << 3; + QTest::newRow("close") << QdbMessage::Write << 0u << 1u << QByteArray("1234") << 4; + QTest::newRow("ok") << QdbMessage::Ok << 3u << 5u << QByteArray("\x0A\x0B\x0C\x0D") << 4; +} + +void tst_QdbMessage::construction_data() +{ + testData(); +} + +void tst_QdbMessage::construction() +{ + QFETCH(QdbMessage::CommandType, command); + QFETCH(StreamId, hostStream); + QFETCH(StreamId, deviceStream); + + QdbMessage message{command, hostStream, deviceStream}; + QCOMPARE(message.command(), command); + QCOMPARE(message.hostStream(), hostStream); + QCOMPARE(message.deviceStream(), deviceStream); + QCOMPARE(message.data().isEmpty(), true); + + QFETCH(QByteArray, data); + + message = QdbMessage{command, hostStream, deviceStream, data}; + QCOMPARE(message.command(), command); + QCOMPARE(message.hostStream(), hostStream); + QCOMPARE(message.deviceStream(), deviceStream); + QCOMPARE(message.data(), data); +} + +void tst_QdbMessage::setters() +{ + QdbMessage message{QdbMessage::Open, 0, 0}; + + message.setHostStream(255u); + QCOMPARE(message.hostStream(), 255u); + + message.setDeviceStream((12u)); + QCOMPARE(message.deviceStream(), 12u); + + message.setData("ABCD", 4); + QCOMPARE(message.data(), QByteArray("ABCD", 4)); +} + +void tst_QdbMessage::roundtrip_data() +{ + testData(); +} + +void tst_QdbMessage::roundtrip() +{ + QFETCH(QdbMessage::CommandType, command); + QFETCH(StreamId, hostStream); + QFETCH(StreamId, deviceStream); + QFETCH(QByteArray, data); + + QByteArray buf{qdbMessageSize, '\0'}; + QDataStream writeStream{&buf, QIODevice::WriteOnly}; + + QdbMessage message{command, hostStream, deviceStream, data}; + writeStream << message; + + QDataStream readStream{buf}; + QdbMessage readMessage; + readStream >> readMessage; + + QCOMPARE(readMessage.command(), message.command()); + QCOMPARE(readMessage.hostStream(), message.hostStream()); + QCOMPARE(readMessage.deviceStream(), message.deviceStream()); + QCOMPARE(readMessage.data(), message.data()); +} + +void tst_QdbMessage::gettingSize_data() +{ + testData(); +} + +void tst_QdbMessage::gettingSize() +{ + QFETCH(QdbMessage::CommandType, command); + QFETCH(StreamId, hostStream); + QFETCH(StreamId, deviceStream); + QFETCH(QByteArray, data); + + QByteArray buf{qdbMessageSize, '\0'}; + QDataStream writeStream{&buf, QIODevice::WriteOnly}; + + QdbMessage message{command, hostStream, deviceStream, data}; + writeStream << message; + + QFETCH(int, dataSize); + + int size = QdbMessage::GetDataSize(buf); + + QCOMPARE(size, dataSize); +} + +QTEST_APPLESS_MAIN(tst_QdbMessage) +#include "tst_qdbmessage.moc" diff --git a/test/tst_qdbmessage.pro b/test/tst_qdbmessage.pro new file mode 100644 index 0000000..03d2e51 --- /dev/null +++ b/test/tst_qdbmessage.pro @@ -0,0 +1,15 @@ +QT -= gui +QT += testlib + +CONFIG += c++11 +CONFIG += testcase + +TARGET = tst_qdbmessage +CONFIG += console +CONFIG -= app_bundle + +TEMPLATE = app + +SOURCES += \ + tst_qdbmessage.cpp \ + ../libqdb/protocol/qdbmessage.cpp diff --git a/test/tst_stream.cpp b/test/tst_stream.cpp new file mode 100644 index 0000000..8bdace1 --- /dev/null +++ b/test/tst_stream.cpp @@ -0,0 +1,143 @@ +/****************************************************************************** +** +** Copyright (C) 2016 The Qt Company Ltd. +** Contact: http://www.qt.io/licensing/ +** +** This file is part of the Qt Debug Bridge. +** +** $QT_BEGIN_LICENSE:COMM$ +** +** Commercial License Usage +** Licensees holding valid commercial Qt licenses may use this file in +** accordance with the commercial license agreement provided with the +** Software or, alternatively, in accordance with the terms contained in +** a written agreement between you and The Qt Company. For licensing terms +** and conditions see http://www.qt.io/terms-conditions. For further +** information use the contact form at http://www.qt.io/contact-us. +** +** $QT_END_LICENSE$ +** +******************************************************************************/ +#include <QtTest/QtTest> + +#include "abstractconnection.h" +#include "stream.h" + +class ConnectionStub : public AbstractConnection +{ +public: + ConnectionStub() : AbstractConnection{nullptr} {} + + bool initialize() override + { + return true; + } + + void enqueueMessage(const QdbMessage &message) override + { + enqueued.append(message); + } + + void handleMessage() override + { + + } + + void reset() + { + enqueued.clear(); + } + + QList<QdbMessage> enqueued; +}; + +class tst_Stream : public QObject +{ + Q_OBJECT +public: + tst_Stream() + : m_connection{}, + m_stream{&m_connection, 13, 26}, + m_hostId{13}, + m_deviceId{26} + { } + +private slots: + void singleWrite(); + void doubleWrite(); + void singleMessagePacket(); + void splitPacket(); + void closedIsEmitted(); + +private: + ConnectionStub m_connection; + Stream m_stream; + StreamId m_hostId; + StreamId m_deviceId; +}; + +void tst_Stream::singleWrite() +{ + m_connection.reset(); + + StreamPacket packet{QByteArray{"ABCD"}}; + m_stream.write(packet); + + QCOMPARE(m_connection.enqueued.size(), 1); + QByteArray expected{"\x00\x00\x00\x04""ABCD", 8}; + QCOMPARE(m_connection.enqueued[0].data(), expected); +} + +void tst_Stream::doubleWrite() +{ + m_connection.reset(); + + StreamPacket packet{QByteArray{"ABCD"}}; + m_stream.write(packet); + StreamPacket packet2{QByteArray{"QDB"}}; + m_stream.write(packet2); + + QCOMPARE(m_connection.enqueued.size(), 2); + QByteArray expected{"\x00\x00\x00\x04""ABCD", 8}; + QCOMPARE(m_connection.enqueued[0].data(), expected); + expected = QByteArray{"\x00\x00\x00\x03QDB", 7}; + QCOMPARE(m_connection.enqueued[1].data(), expected); +} + +void tst_Stream::singleMessagePacket() +{ + QSignalSpy spy{&m_stream, &Stream::packetAvailable}; + m_stream.receiveMessage(QdbMessage{QdbMessage::Write, m_hostId, m_deviceId, + QByteArray{"\x00\x00\x00\x02OK", 6}}); + + QCOMPARE(spy.count(), 1); + QCOMPARE(static_cast<int>(spy[0][0].type()), QMetaType::type("StreamPacket")); + auto packet = spy[0][0].value<StreamPacket>(); + QCOMPARE(packet.buffer(), QByteArray{"OK"}); +} + +void tst_Stream::splitPacket() +{ + QSignalSpy spy{&m_stream, &Stream::packetAvailable}; + m_stream.receiveMessage(QdbMessage{QdbMessage::Write, m_hostId, m_deviceId, + QByteArray{"\x00\x00\x00\x05""AB", 6}}); + m_stream.receiveMessage(QdbMessage{QdbMessage::Write, m_hostId, m_deviceId, + QByteArray{"C"}}); + m_stream.receiveMessage(QdbMessage{QdbMessage::Write, m_hostId, m_deviceId, + QByteArray{"DE"}}); + + QCOMPARE(spy.count(), 1); + QCOMPARE(static_cast<int>(spy[0][0].type()), QMetaType::type("StreamPacket")); + auto packet = spy[0][0].value<StreamPacket>(); + QCOMPARE(packet.buffer(), QByteArray{"ABCDE"}); +} + +void tst_Stream::closedIsEmitted() +{ + QSignalSpy spy{&m_stream, &Stream::closed}; + m_stream.close(); + QCOMPARE(spy.count(), 1); +} + +QTEST_APPLESS_MAIN(tst_Stream) +#include "tst_stream.moc" diff --git a/test/tst_stream.pro b/test/tst_stream.pro new file mode 100644 index 0000000..2b8a48f --- /dev/null +++ b/test/tst_stream.pro @@ -0,0 +1,18 @@ +QT -= gui +QT += testlib + +CONFIG += c++11 +CONFIG += testcase + +CONFIG += console +CONFIG -= app_bundle + +TEMPLATE = app + +SOURCES += \ + tst_stream.cpp \ + +INCLUDEPATH += $$PWD/../libqdb + +LIBS = -L$$OUT_PWD/../libqdb -lqdb +QMAKE_RPATHDIR += ../libqdb diff --git a/tools/qdbd-dev.sh b/tools/qdbd-dev.sh new file mode 100755 index 0000000..cd89c4f --- /dev/null +++ b/tools/qdbd-dev.sh @@ -0,0 +1,88 @@ +#! /bin/sh +# Copyright (C) 2016 The Qt Company Ltd. +# Contact: http://www.qt.io/licensing/ +# +# $QT_BEGIN_LICENSE:BSD$ +# You may use this file under the terms of the BSD license as follows: +# +# "Redistribution and use in source and binary forms, with or without +# modification, are permitted provided that the following conditions are +# met: +# * Redistributions of source code must retain the above copyright +# notice, this list of conditions and the following disclaimer. +# * Redistributions in binary form must reproduce the above copyright +# notice, this list of conditions and the following disclaimer in +# the documentation and/or other materials provided with the +# distribution. +# * Neither the name of The Qt Company Ltd nor the names of its +# contributors may be used to endorse or promote products derived +# from this software without specific prior written permission. +# +# +# THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS +# "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT +# LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR +# A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT +# OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, +# SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT +# LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, +# DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY +# THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT +# (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE +# OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE." +# +# $QT_END_LICENSE$ + +# This is a development script that can run adbd and qdbd simultaneusly on a +# boot2qt device. The first argument is one of {start,stop,restart} and the rest +# of the arguments are passed to qdbd. + +ADBD=/usr/bin/adbd +DAEMON=/usr/bin/qdbd + +. /etc/default/adbd + +if [ -e /var/run/dbus/session_bus_address ]; then + . /var/run/dbus/session_bus_address +fi + +case "$1" in +start) + if [ "$USE_ETHERNET" = "no" ]; then + modprobe g_ffs idVendor=${VENDOR} idProduct=${PRODUCT} iSerialNumber=${SERIAL:0:32} functions=adb,qdb + mkdir -p /dev/usb-ffs + chmod 770 /dev/usb-ffs + mkdir -p /dev/usb-ffs/qdb + chmod 770 /dev/usb-ffs/qdb + mkdir -p /dev/usb-ffs/adb + chmod 770 /dev/usb-ffs/adb + mount -t functionfs qdb /dev/usb-ffs/qdb -o uid=0,gid=0 + mount -t functionfs adb /dev/usb-ffs/adb -o uid=0,gid=0 + fi + shift + start-stop-daemon --start --quiet --exec $DAEMON -- $@ & + start-stop-daemon --start --quiet --exec $ADBD & + ;; +stop) + start-stop-daemon --stop --quiet --exec $DAEMON + start-stop-daemon --stop --quiet --exec $ADBD + if [ "$USE_ETHERNET" = "no" ]; then + sleep 1 + umount /dev/usb-ffs/qdb + umount /dev/usb-ffs/adb + rmmod g_ffs + fi + ;; +restart) + start-stop-daemon --stop --quiet --exec $DAEMON + start-stop-daemon --stop --quiet --exec $ADBD + sleep 1 + shift + start-stop-daemon --start --quiet --exec $DAEMON -- $@ & + start-stop-daemon --start --quiet --exec $ADBD & + ;; +*) + echo "Usage: $0 {start|stop|restart}" + exit 1 +esac +exit 0 diff --git a/utils/make_unique.h b/utils/make_unique.h new file mode 100644 index 0000000..3e2d89e --- /dev/null +++ b/utils/make_unique.h @@ -0,0 +1,34 @@ +/****************************************************************************** +** +** Copyright (C) 2016 The Qt Company Ltd. +** Contact: http://www.qt.io/licensing/ +** +** This file is part of the Qt Debug Bridge. +** +** $QT_BEGIN_LICENSE:COMM$ +** +** Commercial License Usage +** Licensees holding valid commercial Qt licenses may use this file in +** accordance with the commercial license agreement provided with the +** Software or, alternatively, in accordance with the terms contained in +** a written agreement between you and The Qt Company. For licensing terms +** and conditions see http://www.qt.io/terms-conditions. For further +** information use the contact form at http://www.qt.io/contact-us. +** +** $QT_END_LICENSE$ +** +******************************************************************************/ +#ifndef MAKE_UNIQUE_H +#define MAKE_UNIQUE_H + +#include <memory> + +// Substitute for std::make_unique from C++14. +// From https://herbsutter.com/gotw/_102/ +template<typename T, typename ...Args> +std::unique_ptr<T> make_unique( Args&& ...args ) +{ + return std::unique_ptr<T>( new T( std::forward<Args>(args)... ) ); +} + +#endif // MAKE_UNIQUE_H diff --git a/utils/scopeguard.h b/utils/scopeguard.h new file mode 100644 index 0000000..89ff695 --- /dev/null +++ b/utils/scopeguard.h @@ -0,0 +1,53 @@ +/****************************************************************************** +** +** Copyright (C) 2016 The Qt Company Ltd. +** Contact: http://www.qt.io/licensing/ +** +** This file is part of the Qt Debug Bridge. +** +** $QT_BEGIN_LICENSE:COMM$ +** +** Commercial License Usage +** Licensees holding valid commercial Qt licenses may use this file in +** accordance with the commercial license agreement provided with the +** Software or, alternatively, in accordance with the terms contained in +** a written agreement between you and The Qt Company. For licensing terms +** and conditions see http://www.qt.io/terms-conditions. For further +** information use the contact form at http://www.qt.io/contact-us. +** +** $QT_END_LICENSE$ +** +******************************************************************************/ +#ifndef SCOPEGUARD_H +#define SCOPEGUARD_H + +#include <functional> + +class ScopeGuard { +public: + template<class Callable> + ScopeGuard(Callable &&callable) + : m_function(std::forward<Callable>(callable)) {} + + ScopeGuard(ScopeGuard &&other) + : m_function(std::move(other.m_function)) { + other.m_function = nullptr; + } + + ~ScopeGuard() { + if (m_function) + m_function(); + } + + void dismiss() throw() { + m_function = nullptr; + } + + ScopeGuard(const ScopeGuard&) = delete; + void operator = (const ScopeGuard&) = delete; + +private: + std::function<void()> m_function; +}; + +#endif // SCOPEGUARD_H |