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

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