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

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