1// Copyright (C) 2018 Andre Hartmann <aha_1980@gmx.de>
2// SPDX-License-Identifier: LicenseRef-Qt-Commercial OR LGPL-3.0-only OR GPL-2.0-only OR GPL-3.0-only
3// Qt-Security score:critical reason:data-parser
4
5#include "virtualcanbackend.h"
6
7#include <QtCore/qdatetime.h>
8#include <QtCore/qloggingcategory.h>
9#include <QtCore/qmutex.h>
10#include <QtCore/qregularexpression.h>
11#include <QtCore/qthread.h>
12
13#include <QtNetwork/qtcpserver.h>
14#include <QtNetwork/qtcpsocket.h>
15
16QT_BEGIN_NAMESPACE
17
18using namespace Qt::Literals::StringLiterals;
19
20Q_DECLARE_LOGGING_CATEGORY(QT_CANBUS_PLUGINS_VIRTUALCAN)
21
22enum {
23 ServerDefaultTcpPort = 35468,
24 VirtualChannels = 10
25};
26
27static const char RemoteRequestFlag = 'R';
28static const char ExtendedFormatFlag = 'X';
29static const char FlexibleDataRateFlag = 'F';
30static const char BitRateSwitchFlag = 'B';
31static const char ErrorStateFlag = 'E';
32static const char LocalEchoFlag = 'L';
33
34VirtualCanServer::VirtualCanServer(QObject *parent)
35 : QObject(parent)
36{
37 qCDebug(QT_CANBUS_PLUGINS_VIRTUALCAN, "Server [%p] constructed.", this);
38}
39
40VirtualCanServer::~VirtualCanServer()
41{
42 qCDebug(QT_CANBUS_PLUGINS_VIRTUALCAN, "Server [%p] destructed.", this);
43}
44
45void VirtualCanServer::start(quint16 port)
46{
47 // If there is already a server object, return immediately
48 if (m_server) {
49 qCInfo(QT_CANBUS_PLUGINS_VIRTUALCAN, "Server [%p] is already running.", this);
50 return;
51 }
52
53 if (QThread::currentThread() != this->thread()) {
54 // This can happen if this methode is invoked a second time by a different thread
55 // than when it was invoked the first time, and the first time the QTcpServer
56 // couldn't listen because the TCP port was taken by a different process on the system
57 return;
58 }
59
60 // Otherwise try to start a new server. If there is already
61 // another server listen on the specified port, give up.
62 m_server = new QTcpServer(this);
63 if (!m_server->listen(address: QHostAddress::LocalHost, port)) {
64 qCInfo(QT_CANBUS_PLUGINS_VIRTUALCAN,
65 "Server [%p] could not be started, port %d is already in use.", this, port);
66 m_server->deleteLater();
67 m_server = nullptr;
68 return;
69 }
70
71 // Server successfully started
72 connect(sender: m_server, signal: &QTcpServer::newConnection, context: this, slot: &VirtualCanServer::connected);
73 qCInfo(QT_CANBUS_PLUGINS_VIRTUALCAN,
74 "Server [%p] started and listening on port %d.", this, port);
75 return;
76}
77
78void VirtualCanServer::connected()
79{
80 while (m_server->hasPendingConnections()) {
81 qCInfo(QT_CANBUS_PLUGINS_VIRTUALCAN, "Server [%p] client connected.", this);
82 QTcpSocket *next = m_server->nextPendingConnection();
83 m_serverSockets.append(t: next);
84 connect(sender: next, signal: &QIODevice::readyRead, context: this, slot: &VirtualCanServer::readyRead);
85 connect(sender: next, signal: &QTcpSocket::disconnected, context: this, slot: &VirtualCanServer::disconnected);
86 }
87}
88
89void VirtualCanServer::disconnected()
90{
91 qCInfo(QT_CANBUS_PLUGINS_VIRTUALCAN, "Server [%p] client disconnected.", this);
92
93 auto socket = qobject_cast<QTcpSocket *>(object: sender());
94 Q_ASSERT(socket);
95
96 m_serverSockets.removeOne(t: socket);
97 socket->deleteLater();
98}
99
100void VirtualCanServer::readyRead()
101{
102 auto readSocket = qobject_cast<QTcpSocket *>(object: sender());
103 Q_ASSERT(readSocket);
104
105 while (readSocket->canReadLine()) {
106 const QByteArray command = readSocket->readLine().trimmed();
107 qCDebug(QT_CANBUS_PLUGINS_VIRTUALCAN,
108 "Server [%p] received: '%s'.", this, command.constData());
109
110 if (command.startsWith(bv: "connect:")) {
111 const QVariant interfaces = readSocket->property(name: "interfaces");
112 QStringList list = interfaces.toStringList();
113 list.append(t: command.mid(index: int(strlen(s: "connect:"))));
114 readSocket->setProperty(name: "interfaces", value: list);
115
116 } else if (command.startsWith(bv: "disconnect:")) {
117 const QVariant interfaces = readSocket->property(name: "interfaces");
118 QStringList list = interfaces.toStringList();
119 list.removeAll(t: command.mid(index: int(strlen(s: "disconnect:"))));
120 readSocket->setProperty(name: "interfaces", value: list);
121 readSocket->disconnectFromHost();
122
123 } else {
124 const QByteArrayList commandList = command.split(sep: ':');
125 Q_ASSERT(commandList.size() == 2);
126
127 for (QTcpSocket *writeSocket : std::as_const(t&: m_serverSockets)) {
128 // Don't send the frame back to its origin
129 if (writeSocket == readSocket)
130 continue;
131
132 // Send frame to all clients registered to the same interface as sender
133 const QVariant property = writeSocket->property(name: "interfaces");
134 if (!property.isValid())
135 continue;
136
137 const QStringList propertyList = property.toStringList();
138 if (propertyList.contains(t: commandList.first()))
139 writeSocket->write(data: commandList.last() + '\n');
140 }
141 }
142 }
143}
144
145Q_GLOBAL_STATIC(VirtualCanServer, g_server)
146static QBasicMutex g_serverMutex;
147
148VirtualCanBackend::VirtualCanBackend(const QString &interface, QObject *parent)
149 : QCanBusDevice(parent)
150{
151 m_url = QUrl(interface);
152 const QString canDevice = m_url.fileName();
153
154 const QRegularExpression re(QStringLiteral("can(\\d)"));
155 const QRegularExpressionMatch match = re.match(subject: canDevice);
156
157 if (Q_UNLIKELY(!match.hasMatch())) {
158 qCWarning(QT_CANBUS_PLUGINS_VIRTUALCAN,
159 "Invalid interface '%ls'.", qUtf16Printable(interface));
160 setError(errorText: tr(s: "Invalid interface '%1'.").arg(a: interface), QCanBusDevice::ConnectionError);
161 return;
162 }
163
164 const uint channel = match.captured(nth: 1).toUInt();
165 if (Q_UNLIKELY(channel >= VirtualChannels)) {
166 qCWarning(QT_CANBUS_PLUGINS_VIRTUALCAN,
167 "Invalid interface '%ls'.", qUtf16Printable(interface));
168 setError(errorText: tr(s: "Invalid interface '%1'.").arg(a: interface), QCanBusDevice::ConnectionError);
169 return;
170 }
171
172 m_channel = channel;
173}
174
175VirtualCanBackend::~VirtualCanBackend()
176{
177 qCDebug(QT_CANBUS_PLUGINS_VIRTUALCAN, "Client [%p] socket destructed.", this);
178}
179
180bool VirtualCanBackend::open()
181{
182 setState(QCanBusDevice::ConnectingState);
183
184 const QString host = m_url.host();
185 const QHostAddress address = host.isEmpty() ? QHostAddress::LocalHost : QHostAddress(host);
186 const quint16 port = static_cast<quint16>(m_url.port(defaultPort: ServerDefaultTcpPort));
187
188 if (address.isLoopback()) {
189 const QMutexLocker locker(&g_serverMutex);
190 g_server->start(port);
191 }
192
193 m_clientSocket = new QTcpSocket(this);
194 m_clientSocket->connectToHost(address, port, mode: QIODevice::ReadWrite);
195 connect(sender: m_clientSocket, signal: &QAbstractSocket::connected, context: this, slot: &VirtualCanBackend::clientConnected);
196 connect(sender: m_clientSocket, signal: &QAbstractSocket::disconnected, context: this, slot: &VirtualCanBackend::clientDisconnected);
197 connect(sender: m_clientSocket, signal: &QIODevice::readyRead, context: this, slot: &VirtualCanBackend::clientReadyRead);
198 qCDebug(QT_CANBUS_PLUGINS_VIRTUALCAN, "Client [%p] socket created.", this);
199 return true;
200}
201
202void VirtualCanBackend::close()
203{
204 qCDebug(QT_CANBUS_PLUGINS_VIRTUALCAN, "Client [%p] sends disconnect to server.", this);
205
206 m_clientSocket->write(data: QByteArray("disconnect:can"_ba + QByteArray::number(m_channel) + '\n'));
207}
208
209void VirtualCanBackend::setConfigurationParameter(ConfigurationKey key, const QVariant &value)
210{
211 if (key == QCanBusDevice::ReceiveOwnKey || key == QCanBusDevice::CanFdKey)
212 QCanBusDevice::setConfigurationParameter(key, value);
213}
214
215/*
216 Protocol format: All data is in ASCII, one CAN message per line,
217 each line ends with line feed '\n'.
218
219 Format: "<CAN-Channel>:<Flags>#<CAN-ID>#<Data-Bytes>\n"
220 Example: "can0:XF#123#123456\n"
221
222 The first part is the destination CAN channel, "can0", "can1",
223 up to "can9", followed by the flags list:
224
225 * R - Remote Request
226 * X - Extended Frame Format
227 * F - Flexible Data Rate Format
228 * B - Bitrate Switch
229 * E - Error State Indicator
230 * L - Local Echo
231
232 Afterwards the CAN-ID and the data follows, both separated by '#'.
233*/
234
235bool VirtualCanBackend::writeFrame(const QCanBusFrame &frame)
236{
237 if (Q_UNLIKELY(state() != ConnectedState)) {
238 qCWarning(QT_CANBUS_PLUGINS_VIRTUALCAN, "Error: Cannot write frame as client is not connected!");
239 return false;
240 }
241
242 bool canFdEnabled = configurationParameter(key: QCanBusDevice::CanFdKey).toBool();
243 if (Q_UNLIKELY(frame.hasFlexibleDataRateFormat() && !canFdEnabled)) {
244 qCWarning(QT_CANBUS_PLUGINS_VIRTUALCAN,
245 "Error: Cannot write CAN FD frame as CAN FD is not enabled!");
246 return false;
247 }
248
249 QByteArray flags;
250 if (frame.frameType() == QCanBusFrame::RemoteRequestFrame)
251 flags.append(c: RemoteRequestFlag);
252 if (frame.hasExtendedFrameFormat())
253 flags.append(c: ExtendedFormatFlag);
254 if (frame.hasFlexibleDataRateFormat())
255 flags.append(c: FlexibleDataRateFlag);
256 if (frame.hasBitrateSwitch())
257 flags.append(c: BitRateSwitchFlag);
258 if (frame.hasErrorStateIndicator())
259 flags.append(c: ErrorStateFlag);
260 if (frame.hasLocalEcho())
261 flags.append(c: LocalEchoFlag);
262 const QByteArray frameId = QByteArray::number(frame.frameId());
263 const QByteArray command = "can" + QByteArray::number(m_channel)
264 + ':' + frameId + '#' + flags + '#' + frame.payload().toHex() + '\n';
265 m_clientSocket->write(data: command);
266
267 if (configurationParameter(key: QCanBusDevice::ReceiveOwnKey).toBool()) {
268 const qint64 timeStamp = QDateTime::currentDateTime().toMSecsSinceEpoch();
269 QCanBusFrame echoFrame = frame;
270 echoFrame.setLocalEcho(true);
271 echoFrame.setTimeStamp(QCanBusFrame::TimeStamp::fromMicroSeconds(usec: timeStamp * 1000));
272 enqueueReceivedFrames(newFrames: {echoFrame});
273 }
274
275 emit framesWritten(framesCount: qint64(1));
276 return true;
277}
278
279QString VirtualCanBackend::interpretErrorFrame(const QCanBusFrame &errorFrame)
280{
281 Q_UNUSED(errorFrame);
282 return QString();
283}
284
285QCanBusDeviceInfo VirtualCanBackend::virtualCanDeviceInfo(uint channel)
286{
287 return createDeviceInfo(
288 QStringLiteral("virtualcan"),
289 QStringLiteral("can%1").arg(a: channel), serialNumber: QString(),
290 QStringLiteral("Qt Virtual CAN bus"), alias: QString(),
291 channel, isVirtual: true, isFlexibleDataRateCapable: true);
292}
293
294QList<QCanBusDeviceInfo> VirtualCanBackend::interfaces()
295{
296 QList<QCanBusDeviceInfo> result;
297
298 for (uint channel = 0; channel < VirtualChannels; ++channel)
299 result.append(t: virtualCanDeviceInfo(channel));
300
301 return result;
302}
303
304QCanBusDeviceInfo VirtualCanBackend::deviceInfo() const
305{
306 return virtualCanDeviceInfo(channel: m_channel);
307}
308
309void VirtualCanBackend::clientConnected()
310{
311 qCInfo(QT_CANBUS_PLUGINS_VIRTUALCAN, "Client [%p] socket connected.", this);
312 m_clientSocket->write(data: QByteArray("connect:can"_ba + QByteArray::number(m_channel) + '\n'));
313
314 setState(QCanBusDevice::ConnectedState);
315}
316
317void VirtualCanBackend::clientDisconnected()
318{
319 qCInfo(QT_CANBUS_PLUGINS_VIRTUALCAN, "Client [%p] socket disconnected.", this);
320
321 setState(UnconnectedState);
322}
323
324void VirtualCanBackend::clientReadyRead()
325{
326 while (m_clientSocket->canReadLine()) {
327 const QByteArray answer = m_clientSocket->readLine().trimmed();
328 qCDebug(QT_CANBUS_PLUGINS_VIRTUALCAN, "Client [%p] received: '%s'.",
329 this, answer.constData());
330
331 if (answer.startsWith(bv: QByteArray("disconnect:can"_ba + QByteArray::number(m_channel)))) {
332 m_clientSocket->disconnectFromHost();
333 continue;
334 }
335
336 const QByteArrayList list = answer.split(sep: '#');
337 Q_ASSERT(list.size() == 3);
338
339 const QCanBusFrame::FrameId id = list.at(i: 0).toUInt();
340 const QByteArray flags = list.at(i: 1);
341 const QByteArray data = QByteArray::fromHex(hexEncoded: list.at(i: 2));
342 const qint64 timeStamp = QDateTime::currentDateTime().toMSecsSinceEpoch();
343 QCanBusFrame frame(id, data);
344 frame.setTimeStamp(QCanBusFrame::TimeStamp::fromMicroSeconds(usec: timeStamp * 1000));
345 if (flags.contains(c: RemoteRequestFlag))
346 frame.setFrameType(QCanBusFrame::RemoteRequestFrame);
347 frame.setExtendedFrameFormat(flags.contains(c: ExtendedFormatFlag));
348 frame.setFlexibleDataRateFormat(flags.contains(c: FlexibleDataRateFlag));
349 frame.setBitrateSwitch(flags.contains(c: BitRateSwitchFlag));
350 frame.setErrorStateIndicator(flags.contains(c: ErrorStateFlag));
351 frame.setLocalEcho(flags.contains(c: LocalEchoFlag));
352 enqueueReceivedFrames(newFrames: {frame});
353 }
354}
355
356QT_END_NAMESPACE
357

source code of qtserialbus/src/plugins/canbus/virtualcan/virtualcanbackend.cpp