1// Copyright (C) 2016 Klaralvdalens Datakonsult AB (KDAB).
2// SPDX-License-Identifier: LicenseRef-Qt-Commercial OR LGPL-3.0-only OR GPL-2.0-only OR GPL-3.0-only
3
4#include "aspectcommanddebugger_p.h"
5
6#include <QtNetwork/QTcpSocket>
7#include <QtCore/QJsonDocument>
8#include <QtCore/QJsonObject>
9
10#include <Qt3DCore/private/qabstractaspect_p.h>
11#include <Qt3DCore/private/qsysteminformationservice_p.h>
12
13QT_BEGIN_NAMESPACE
14
15namespace Qt3DCore {
16
17namespace Debug {
18
19namespace {
20
21const qint32 MagicNumber = 0x454;
22
23struct CommandHeader
24{
25 qint32 magic;
26 qint32 size;
27};
28
29} // anonymous
30
31void AspectCommandDebugger::ReadBuffer::insert(const QByteArray &array)
32{
33 buffer.insert(i: endIdx, data: array);
34 endIdx += array.size();
35}
36
37void AspectCommandDebugger::ReadBuffer::trim()
38{
39 if (startIdx != endIdx && startIdx != 0) {
40 memcpy(dest: buffer.data(),
41 src: buffer.constData() + startIdx,
42 n: size());
43 endIdx -= startIdx;
44 startIdx = 0;
45 }
46}
47
48AspectCommandDebugger::AspectCommandDebugger(QSystemInformationService *parent)
49 : QTcpServer(parent)
50 , m_service(parent)
51{
52}
53
54void AspectCommandDebugger::initialize()
55{
56 QObject::connect(sender: this, signal: &QTcpServer::newConnection, slot: [this] {
57 QTcpSocket *socket = nextPendingConnection();
58 m_connections.push_back(t: socket);
59
60 QObject::connect(sender: socket, signal: &QTcpSocket::disconnected, slot: [this, socket] {
61 m_connections.removeOne(t: socket);
62 // Destroy object to make sure all QObject connection are removed
63 socket->deleteLater();
64 });
65
66 QObject::connect(sender: socket, signal: &QTcpSocket::readyRead, slot: [this, socket] {
67 onCommandReceived(socket);
68 });
69 });
70 const bool listening = listen(address: QHostAddress::Any, port: 8883);
71 if (!listening)
72 qWarning() << Q_FUNC_INFO << "failed to listen on port 8883";
73}
74
75void AspectCommandDebugger::asynchronousReplyFinished(AsynchronousCommandReply *reply)
76{
77 Q_ASSERT(reply->isFinished());
78 QTcpSocket *socket = m_asyncCommandToSocketEntries.take(key: reply);
79 if (m_connections.contains(t: socket)) {
80 QJsonObject replyObj;
81 replyObj.insert(key: QLatin1String("command"), value: QJsonValue(reply->commandName()));
82 replyObj.insert(key: QLatin1String("data"), value: QJsonDocument::fromJson(json: reply->data()).object());
83 sendReply(socket, data: QJsonDocument(replyObj).toJson());
84 }
85 reply->deleteLater();
86}
87
88
89// Expects to receive commands in the form
90// CommandHeader { MagicNumber; size }
91// JSON {
92// command: "commandName"
93// data: JSON Obj
94// }
95void AspectCommandDebugger::onCommandReceived(QTcpSocket *socket)
96{
97 const QByteArray newData = socket->readAll();
98 m_readBuffer.insert(array: newData);
99
100 const int commandPacketSize = sizeof(CommandHeader);
101 while (m_readBuffer.size() >= commandPacketSize) {
102 CommandHeader *header = reinterpret_cast<CommandHeader *>(m_readBuffer.buffer.data() + m_readBuffer.startIdx);
103 if (header->magic == MagicNumber) {
104 // Early return, header is valid but we haven't yet received all the data
105 if ((m_readBuffer.size() - commandPacketSize) < header->size)
106 return;
107 // We have a valid command
108 // We expect command to be a CommandHeader + some json text
109 const QJsonDocument doc = QJsonDocument::fromJson(
110 json: QByteArray(m_readBuffer.buffer.data() + m_readBuffer.startIdx + commandPacketSize,
111 header->size));
112
113 if (!doc.isNull()) {
114 // Send command to the aspectEngine
115 QJsonObject commandObj = doc.object();
116 const QJsonValue commandNameValue = commandObj.value(key: QLatin1String("command"));
117 executeCommand(command: commandNameValue.toString(), socket);
118 }
119
120 m_readBuffer.startIdx += commandPacketSize + header->size;
121 }
122 }
123 // Copy remaining length of buffer at begininning if we have read some commands
124 // and some partial one remain
125 m_readBuffer.trim();
126}
127
128void AspectCommandDebugger::sendReply(QTcpSocket *socket, const QByteArray &payload)
129{
130 CommandHeader replyHeader;
131
132 replyHeader.magic = MagicNumber;
133 replyHeader.size = payload.size();
134 // Write header
135 socket->write(data: reinterpret_cast<const char *>(&replyHeader), len: sizeof(CommandHeader));
136 // Write payload
137 socket->write(data: payload.constData(), len: payload.size());
138}
139
140void AspectCommandDebugger::executeCommand(const QString &command,
141 QTcpSocket *socket)
142{
143 // Only a single aspect is going to reply
144 const QVariant response = m_service->executeCommand(command);
145 if (response.userType() == qMetaTypeId<AsynchronousCommandReply *>()) { // AsynchronousCommand
146 // Store the command | socket in a table
147 AsynchronousCommandReply *reply = response.value<AsynchronousCommandReply *>();
148 // Command has already been completed
149 if (reply->isFinished()) {
150 asynchronousReplyFinished(reply);
151 } else { // Command is not completed yet
152 QObject::connect(sender: reply, signal: &AsynchronousCommandReply::finished,
153 context: this, slot: &AspectCommandDebugger::asynchronousReplyFinished);
154 m_asyncCommandToSocketEntries.insert(key: reply, value: socket);
155 }
156 } else { // Synchronous command
157 // and send response to client
158 QJsonObject reply;
159 reply.insert(key: QLatin1String("command"), value: QJsonValue(command));
160 // TO DO: convert QVariant to QJsonDocument/QByteArray
161 sendReply(socket, payload: QJsonDocument(reply).toJson());
162
163 }
164}
165
166} // Debug
167
168} // Qt3DCore
169
170QT_END_NAMESPACE
171
172#include "moc_aspectcommanddebugger_p.cpp"
173

source code of qt3d/src/core/aspects/aspectcommanddebugger.cpp