Revision control
++ // Copyright (C) 2018 Andre Hartmann <aha_1980@gmx.de>
++ // SPDX-License-Identifier: LicenseRef-Qt-Commercial OR LGPL-3.0-only OR GPL-2.0-only OR GPL-3.0-only
++ // Qt-Security score:critical reason:data-parser
++
++ #include "virtualcanbackend.h"
++
++ #include <QtCore/qdatetime.h>
++ #include <QtCore/qloggingcategory.h>
++ #include <QtCore/qmutex.h>
++ #include <QtCore/qregularexpression.h>
++ #include <QtCore/qthread.h>
++
++ #include <QtNetwork/qtcpserver.h>
++ #include <QtNetwork/qtcpsocket.h>
++
++ QT_BEGIN_NAMESPACE
++
++ using namespace Qt::Literals::StringLiterals;
++
++ Q_DECLARE_LOGGING_CATEGORY(QT_CANBUS_PLUGINS_VIRTUALCAN)
++
++ enum {
++ ServerDefaultTcpPort = 35468,
++ VirtualChannels = 10
++ };
++
++ static const char RemoteRequestFlag = 'R';
++ static const char ExtendedFormatFlag = 'X';
++ static const char FlexibleDataRateFlag = 'F';
++ static const char BitRateSwitchFlag = 'B';
++ static const char ErrorStateFlag = 'E';
++ static const char LocalEchoFlag = 'L';
++
++ VirtualCanServer::VirtualCanServer(QObject *parent)
++ : QObject(parent)
++ {
++ qCDebug(QT_CANBUS_PLUGINS_VIRTUALCAN, "Server [%p] constructed.", this);
++ }
++
++ VirtualCanServer::~VirtualCanServer()
++ {
++ qCDebug(QT_CANBUS_PLUGINS_VIRTUALCAN, "Server [%p] destructed.", this);
++ }
++
++ void VirtualCanServer::start(quint16 port)
++ {
++ // If there is already a server object, return immediately
++ if (m_server) {
++ qCInfo(QT_CANBUS_PLUGINS_VIRTUALCAN, "Server [%p] is already running.", this);
++ return;
++ }
++
++ if (QThread::currentThread() != this->thread()) {
++ // This can happen if this methode is invoked a second time by a different thread
++ // than when it was invoked the first time, and the first time the QTcpServer
++ // couldn't listen because the TCP port was taken by a different process on the system
++ return;
++ }
++
++ // Otherwise try to start a new server. If there is already
++ // another server listen on the specified port, give up.
++ m_server = new QTcpServer(this);
++ if (!m_server->listen(QHostAddress::LocalHost, port)) {
++ qCInfo(QT_CANBUS_PLUGINS_VIRTUALCAN,
++ "Server [%p] could not be started, port %d is already in use.", this, port);
++ m_server->deleteLater();
++ m_server = nullptr;
++ return;
++ }
++
++ // Server successfully started
++ connect(m_server, &QTcpServer::newConnection, this, &VirtualCanServer::connected);
++ qCInfo(QT_CANBUS_PLUGINS_VIRTUALCAN,
++ "Server [%p] started and listening on port %d.", this, port);
++ return;
++ }
++
++ void VirtualCanServer::connected()
++ {
++ while (m_server->hasPendingConnections()) {
++ qCInfo(QT_CANBUS_PLUGINS_VIRTUALCAN, "Server [%p] client connected.", this);
++ QTcpSocket *next = m_server->nextPendingConnection();
++ m_serverSockets.append(next);
++ connect(next, &QIODevice::readyRead, this, &VirtualCanServer::readyRead);
++ connect(next, &QTcpSocket::disconnected, this, &VirtualCanServer::disconnected);
++ }
++ }
++
++ void VirtualCanServer::disconnected()
++ {
++ qCInfo(QT_CANBUS_PLUGINS_VIRTUALCAN, "Server [%p] client disconnected.", this);
++
++ auto socket = qobject_cast<QTcpSocket *>(sender());
++ Q_ASSERT(socket);
++
++ m_serverSockets.removeOne(socket);
++ socket->deleteLater();
++ }
++
++ void VirtualCanServer::readyRead()
++ {
++ auto readSocket = qobject_cast<QTcpSocket *>(sender());
++ Q_ASSERT(readSocket);
++
++ while (readSocket->canReadLine()) {
++ const QByteArray command = readSocket->readLine().trimmed();
++ qCDebug(QT_CANBUS_PLUGINS_VIRTUALCAN,
++ "Server [%p] received: '%s'.", this, command.constData());
++
++ if (command.startsWith("connect:")) {
++ const QVariant interfaces = readSocket->property("interfaces");
++ QStringList list = interfaces.toStringList();
++ list.append(command.mid(int(strlen("connect:"))));
++ readSocket->setProperty("interfaces", list);
++
++ } else if (command.startsWith("disconnect:")) {
++ const QVariant interfaces = readSocket->property("interfaces");
++ QStringList list = interfaces.toStringList();
++ list.removeAll(command.mid(int(strlen("disconnect:"))));
++ readSocket->setProperty("interfaces", list);
++ readSocket->disconnectFromHost();
++
++ } else {
++ const QByteArrayList commandList = command.split(':');
++ Q_ASSERT(commandList.size() == 2);
++
++ for (QTcpSocket *writeSocket : std::as_const(m_serverSockets)) {
++ // Don't send the frame back to its origin
++ if (writeSocket == readSocket)
++ continue;
++
++ // Send frame to all clients registered to the same interface as sender
++ const QVariant property = writeSocket->property("interfaces");
++ if (!property.isValid())
++ continue;
++
++ const QStringList propertyList = property.toStringList();
++ if (propertyList.contains(commandList.first()))
++ writeSocket->write(commandList.last() + '\n');
++ }
++ }
++ }
++ }
++
++ Q_GLOBAL_STATIC(VirtualCanServer, g_server)
++ static QBasicMutex g_serverMutex;
++
++ VirtualCanBackend::VirtualCanBackend(const QString &interface, QObject *parent)
++ : QCanBusDevice(parent)
++ {
++ m_url = QUrl(interface);
++ const QString canDevice = m_url.fileName();
++
++ const QRegularExpression re(QStringLiteral("can(\\d)"));
++ const QRegularExpressionMatch match = re.match(canDevice);
++
++ if (Q_UNLIKELY(!match.hasMatch())) {
++ qCWarning(QT_CANBUS_PLUGINS_VIRTUALCAN,
++ "Invalid interface '%ls'.", qUtf16Printable(interface));
++ setError(tr("Invalid interface '%1'.").arg(interface), QCanBusDevice::ConnectionError);
++ return;
++ }
++
++ const uint channel = match.captured(1).toUInt();
++ if (Q_UNLIKELY(channel >= VirtualChannels)) {
++ qCWarning(QT_CANBUS_PLUGINS_VIRTUALCAN,
++ "Invalid interface '%ls'.", qUtf16Printable(interface));
++ setError(tr("Invalid interface '%1'.").arg(interface), QCanBusDevice::ConnectionError);
++ return;
++ }
++
++ m_channel = channel;
++ }
++
++ VirtualCanBackend::~VirtualCanBackend()
++ {
++ qCDebug(QT_CANBUS_PLUGINS_VIRTUALCAN, "Client [%p] socket destructed.", this);
++ }
++
++ bool VirtualCanBackend::open()
++ {
++ setState(QCanBusDevice::ConnectingState);
++
++ const QString host = m_url.host();
++ const QHostAddress address = host.isEmpty() ? QHostAddress::LocalHost : QHostAddress(host);
++ const quint16 port = static_cast<quint16>(m_url.port(ServerDefaultTcpPort));
++
++ if (address.isLoopback()) {
++ const QMutexLocker locker(&g_serverMutex);
++ g_server->start(port);
++ }
++
++ m_clientSocket = new QTcpSocket(this);
++ m_clientSocket->connectToHost(address, port, QIODevice::ReadWrite);
++ connect(m_clientSocket, &QAbstractSocket::connected, this, &VirtualCanBackend::clientConnected);
++ connect(m_clientSocket, &QAbstractSocket::disconnected, this, &VirtualCanBackend::clientDisconnected);
++ connect(m_clientSocket, &QIODevice::readyRead, this, &VirtualCanBackend::clientReadyRead);
++ qCDebug(QT_CANBUS_PLUGINS_VIRTUALCAN, "Client [%p] socket created.", this);
++ return true;
++ }
++
++ void VirtualCanBackend::close()
++ {
++ qCDebug(QT_CANBUS_PLUGINS_VIRTUALCAN, "Client [%p] sends disconnect to server.", this);
++
++ m_clientSocket->write(QByteArray("disconnect:can"_ba + QByteArray::number(m_channel) + '\n'));
++ }
++
++ void VirtualCanBackend::setConfigurationParameter(ConfigurationKey key, const QVariant &value)
++ {
++ if (key == QCanBusDevice::ReceiveOwnKey || key == QCanBusDevice::CanFdKey)
++ QCanBusDevice::setConfigurationParameter(key, value);
++ }
++
++ /*
++ Protocol format: All data is in ASCII, one CAN message per line,
++ each line ends with line feed '\n'.
++
++ Format: "<CAN-Channel>:<Flags>#<CAN-ID>#<Data-Bytes>\n"
++ Example: "can0:XF#123#123456\n"
++
++ The first part is the destination CAN channel, "can0", "can1",
++ up to "can9", followed by the flags list:
++
++ * R - Remote Request
++ * X - Extended Frame Format
++ * F - Flexible Data Rate Format
++ * B - Bitrate Switch
++ * E - Error State Indicator
++ * L - Local Echo
++
++ Afterwards the CAN-ID and the data follows, both separated by '#'.
++ */
++
++ bool VirtualCanBackend::writeFrame(const QCanBusFrame &frame)
++ {
++ if (Q_UNLIKELY(state() != ConnectedState)) {
++ qCWarning(QT_CANBUS_PLUGINS_VIRTUALCAN, "Error: Cannot write frame as client is not connected!");
++ return false;
++ }
++
++ bool canFdEnabled = configurationParameter(QCanBusDevice::CanFdKey).toBool();
++ if (Q_UNLIKELY(frame.hasFlexibleDataRateFormat() && !canFdEnabled)) {
++ qCWarning(QT_CANBUS_PLUGINS_VIRTUALCAN,
++ "Error: Cannot write CAN FD frame as CAN FD is not enabled!");
++ return false;
++ }
++
++ QByteArray flags;
++ if (frame.frameType() == QCanBusFrame::RemoteRequestFrame)
++ flags.append(RemoteRequestFlag);
++ if (frame.hasExtendedFrameFormat())
++ flags.append(ExtendedFormatFlag);
++ if (frame.hasFlexibleDataRateFormat())
++ flags.append(FlexibleDataRateFlag);
++ if (frame.hasBitrateSwitch())
++ flags.append(BitRateSwitchFlag);
++ if (frame.hasErrorStateIndicator())
++ flags.append(ErrorStateFlag);
++ if (frame.hasLocalEcho())
++ flags.append(LocalEchoFlag);
++ const QByteArray frameId = QByteArray::number(frame.frameId());
++ const QByteArray command = "can" + QByteArray::number(m_channel)
++ + ':' + frameId + '#' + flags + '#' + frame.payload().toHex() + '\n';
++ m_clientSocket->write(command);
++
++ if (configurationParameter(QCanBusDevice::ReceiveOwnKey).toBool()) {
++ const qint64 timeStamp = QDateTime::currentDateTime().toMSecsSinceEpoch();
++ QCanBusFrame echoFrame = frame;
++ echoFrame.setLocalEcho(true);
++ echoFrame.setTimeStamp(QCanBusFrame::TimeStamp::fromMicroSeconds(timeStamp * 1000));
++ enqueueReceivedFrames({echoFrame});
++ }
++
++ emit framesWritten(qint64(1));
++ return true;
++ }
++
++ QString VirtualCanBackend::interpretErrorFrame(const QCanBusFrame &errorFrame)
++ {
++ Q_UNUSED(errorFrame);
++ return QString();
++ }
++
++ QCanBusDeviceInfo VirtualCanBackend::virtualCanDeviceInfo(uint channel)
++ {
++ return createDeviceInfo(
++ QStringLiteral("virtualcan"),
++ QStringLiteral("can%1").arg(channel), QString(),
++ QStringLiteral("Qt Virtual CAN bus"), QString(),
++ channel, true, true);
++ }
++
++ QList<QCanBusDeviceInfo> VirtualCanBackend::interfaces()
++ {
++ QList<QCanBusDeviceInfo> result;
++
++ for (uint channel = 0; channel < VirtualChannels; ++channel)
++ result.append(virtualCanDeviceInfo(channel));
++
++ return result;
++ }
++
++ QCanBusDeviceInfo VirtualCanBackend::deviceInfo() const
++ {
++ return virtualCanDeviceInfo(m_channel);
++ }
++
++ void VirtualCanBackend::clientConnected()
++ {
++ qCInfo(QT_CANBUS_PLUGINS_VIRTUALCAN, "Client [%p] socket connected.", this);
++ m_clientSocket->write(QByteArray("connect:can"_ba + QByteArray::number(m_channel) + '\n'));
++
++ setState(QCanBusDevice::ConnectedState);
++ }
++
++ void VirtualCanBackend::clientDisconnected()
++ {
++ qCInfo(QT_CANBUS_PLUGINS_VIRTUALCAN, "Client [%p] socket disconnected.", this);
++
++ setState(UnconnectedState);
++ }
++
++ void VirtualCanBackend::clientReadyRead()
++ {
++ while (m_clientSocket->canReadLine()) {
++ const QByteArray answer = m_clientSocket->readLine().trimmed();
++ qCDebug(QT_CANBUS_PLUGINS_VIRTUALCAN, "Client [%p] received: '%s'.",
++ this, answer.constData());
++
++ if (answer.startsWith(QByteArray("disconnect:can"_ba + QByteArray::number(m_channel)))) {
++ m_clientSocket->disconnectFromHost();
++ continue;
++ }
++
++ const QByteArrayList list = answer.split('#');
++ Q_ASSERT(list.size() == 3);
++
++ const QCanBusFrame::FrameId id = list.at(0).toUInt();
++ const QByteArray flags = list.at(1);
++ const QByteArray data = QByteArray::fromHex(list.at(2));
++ const qint64 timeStamp = QDateTime::currentDateTime().toMSecsSinceEpoch();
++ QCanBusFrame frame(id, data);
++ frame.setTimeStamp(QCanBusFrame::TimeStamp::fromMicroSeconds(timeStamp * 1000));
++ if (flags.contains(RemoteRequestFlag))
++ frame.setFrameType(QCanBusFrame::RemoteRequestFrame);
++ frame.setExtendedFrameFormat(flags.contains(ExtendedFormatFlag));
++ frame.setFlexibleDataRateFormat(flags.contains(FlexibleDataRateFlag));
++ frame.setBitrateSwitch(flags.contains(BitRateSwitchFlag));
++ frame.setErrorStateIndicator(flags.contains(ErrorStateFlag));
++ frame.setLocalEcho(flags.contains(LocalEchoFlag));
++ enqueueReceivedFrames({frame});
++ }
++ }
++
++ QT_END_NAMESPACE