summaryrefslogtreecommitdiffstats
diff options
context:
space:
mode:
authorKari Oikarinen <kari.oikarinen@qt.io>2016-06-22 13:18:04 +0300
committerKari Oikarinen <kari.oikarinen@qt.io>2016-07-14 11:57:38 +0000
commit3a6284c97ee1fb22f0bafe14f0f4d4ac2d8d2acf (patch)
tree259845e8b1c150fb5bb42e7dbcbe2d77a76d6650
parent174aad875aa79027dae885450ddd97d59cd27426 (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>
-rw-r--r--.gitignore2
-rw-r--r--client/client.pro39
-rw-r--r--client/connection.cpp280
-rw-r--r--client/connection.h76
-rw-r--r--client/echoservice.cpp71
-rw-r--r--client/echoservice.h52
-rw-r--r--client/filepullservice.cpp145
-rw-r--r--client/filepullservice.h62
-rw-r--r--client/filepushservice.cpp137
-rw-r--r--client/filepushservice.h63
-rw-r--r--client/main.cpp162
-rw-r--r--client/processservice.cpp119
-rw-r--r--client/processservice.h57
-rw-r--r--client/service.cpp46
-rw-r--r--client/service.h52
-rw-r--r--doc/streams-device.uml33
-rw-r--r--doc/streams-host.uml43
-rw-r--r--libqdb/abstractconnection.cpp44
-rw-r--r--libqdb/abstractconnection.h54
-rw-r--r--libqdb/filepullcommon.h61
-rw-r--r--libqdb/filepushcommon.h61
-rw-r--r--libqdb/interruptsignalhandler.cpp82
-rw-r--r--libqdb/interruptsignalhandler.h58
-rw-r--r--libqdb/libqdb.pro42
-rw-r--r--libqdb/libqdb_global.h32
-rw-r--r--libqdb/processcommon.h61
-rw-r--r--libqdb/protocol/protocol.h31
-rw-r--r--libqdb/protocol/qdbmessage.cpp190
-rw-r--r--libqdb/protocol/qdbmessage.h78
-rw-r--r--libqdb/protocol/qdbtransport.cpp79
-rw-r--r--libqdb/protocol/qdbtransport.h52
-rw-r--r--libqdb/protocol/services.h48
-rw-r--r--libqdb/stream.cpp123
-rw-r--r--libqdb/stream.h60
-rw-r--r--libqdb/streampacket.cpp52
-rw-r--r--libqdb/streampacket.h61
-rw-r--r--libqdb/usb/usbconnection.cpp215
-rw-r--r--libqdb/usb/usbconnection.h67
-rw-r--r--libqdb/usb/usbconnectionreader.cpp56
-rw-r--r--libqdb/usb/usbconnectionreader.h45
-rw-r--r--qdb.pro11
-rw-r--r--qdbd/createexecutor.cpp52
-rw-r--r--qdbd/createexecutor.h33
-rw-r--r--qdbd/echoexecutor.cpp38
-rw-r--r--qdbd/echoexecutor.h41
-rw-r--r--qdbd/executor.cpp34
-rw-r--r--qdbd/executor.h44
-rw-r--r--qdbd/filepullexecutor.cpp108
-rw-r--r--qdbd/filepullexecutor.h50
-rw-r--r--qdbd/filepushexecutor.cpp102
-rw-r--r--qdbd/filepushexecutor.h49
-rw-r--r--qdbd/main.cpp58
-rw-r--r--qdbd/processexecutor.cpp150
-rw-r--r--qdbd/processexecutor.h57
-rw-r--r--qdbd/qdbd.pro46
-rw-r--r--qdbd/server.cpp218
-rw-r--r--qdbd/server.h64
-rw-r--r--qdbd/usb-gadget/usbgadget.cpp243
-rw-r--r--qdbd/usb-gadget/usbgadget.h72
-rw-r--r--qdbd/usb-gadget/usbgadgetreader.cpp73
-rw-r--r--qdbd/usb-gadget/usbgadgetreader.h43
-rw-r--r--qdbd/usb-gadget/usbgadgetwriter.cpp50
-rw-r--r--qdbd/usb-gadget/usbgadgetwriter.h43
-rw-r--r--test/servicetest.cpp508
-rw-r--r--test/servicetest.pro30
-rw-r--r--test/streamtest.cpp469
-rw-r--r--test/streamtest.pro15
-rw-r--r--test/test.pro7
-rw-r--r--test/tst_qdbmessage.cpp148
-rw-r--r--test/tst_qdbmessage.pro15
-rw-r--r--test/tst_stream.cpp143
-rw-r--r--test/tst_stream.pro18
-rwxr-xr-xtools/qdbd-dev.sh88
-rw-r--r--utils/make_unique.h34
-rw-r--r--utils/scopeguard.h53
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
diff --git a/qdb.pro b/qdb.pro
new file mode 100644
index 0000000..9a07f50
--- /dev/null
+++ b/qdb.pro
@@ -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