summaryrefslogtreecommitdiffstats
path: root/vehicle-signals/vehiclesignals.cpp
diff options
context:
space:
mode:
Diffstat (limited to 'vehicle-signals/vehiclesignals.cpp')
-rw-r--r--vehicle-signals/vehiclesignals.cpp481
1 files changed, 115 insertions, 366 deletions
diff --git a/vehicle-signals/vehiclesignals.cpp b/vehicle-signals/vehiclesignals.cpp
index d6cf8eb..adf371a 100644
--- a/vehicle-signals/vehiclesignals.cpp
+++ b/vehicle-signals/vehiclesignals.cpp
@@ -1,427 +1,176 @@
/*
- * Copyright (C) 2022 Konsulko Group
+ * Copyright (C) 2022,2023 Konsulko Group
*
- * Licensed under the Apache License, Version 2.0 (the "License");
- * you may not use this file except in compliance with the License.
- * You may obtain a copy of the License at
- *
- * http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS,
- * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- * See the License for the specific language governing permissions and
- * limitations under the License.
+ * SPDX-License-Identifier: Apache-2.0
*/
#include <QDebug>
-#include <QSettings>
-#include <QUrl>
-#include <QFile>
-#include <QSslKey>
-#include <QTimer>
-#include <QVariantMap>
-#include <QJsonDocument>
-#include <QJsonObject>
+#include <QtConcurrent>
#include "vehiclesignals.h"
+#include "QtKuksaClient.h"
-#define DEFAULT_CLIENT_KEY_FILE "/etc/kuksa-val/Client.key"
-#define DEFAULT_CLIENT_CERT_FILE "/etc/kuksa-val/Client.pem"
-#define DEFAULT_CA_CERT_FILE "/etc/kuksa-val/CA.pem"
-
-VehicleSignalsConfig::VehicleSignalsConfig(const QString &hostname,
- const unsigned port,
- const QByteArray &clientKey,
- const QByteArray &clientCert,
- const QByteArray &caCert,
- const QString &authToken,
- bool verifyPeer) :
- m_hostname(hostname),
- m_port(port),
- m_clientKey(clientKey),
- m_clientCert(clientCert),
- m_caCert(caCert),
- m_authToken(authToken),
- m_verifyPeer(verifyPeer),
- m_verbose(0),
- m_valid(true)
-{
- // Potentially could do some certificate validation here...
-}
-
-VehicleSignalsConfig::VehicleSignalsConfig(const QString &appname)
+VehicleSignals::VehicleSignals(const VehicleSignalsConfig &config, QObject *parent) :
+ QObject(parent),
+ m_config(config)
{
- m_valid = false;
-
- QSettings *pSettings = new QSettings("AGL", appname);
- if (!pSettings)
- return;
-
- m_hostname = pSettings->value("vis-client/server", "localhost").toString();
- if (m_hostname.isEmpty()) {
- qCritical() << "Invalid server hostname";
- return;
- }
-
- m_port = pSettings->value("vis-client/port", 8090).toInt();
- if (m_port == 0) {
- qCritical() << "Invalid server port";
- return;
- }
-
- // Default to disabling peer verification for now to be able
- // to use the default upstream KUKSA.val certificates for
- // testing. Wrangling server and CA certificate generation
- // and management to be able to verify will require further
- // investigation.
- m_verifyPeer = pSettings->value("vis-client/verify-server", false).toBool();
-
- QString keyFileName = pSettings->value("vis-client/key", DEFAULT_CLIENT_KEY_FILE).toString();
- if (keyFileName.isEmpty()) {
- qCritical() << "Invalid client key filename";
- return;
- }
- QFile keyFile(keyFileName);
- if (!keyFile.open(QIODevice::ReadOnly)) {
- qCritical() << "Could not open client key file";
- return;
- }
- QByteArray keyData = keyFile.readAll();
- if (keyData.isEmpty()) {
- qCritical() << "Invalid client key file";
- return;
- }
- m_clientKey = keyData;
-
- QString certFileName = pSettings->value("vis-client/certificate", DEFAULT_CLIENT_CERT_FILE).toString();
- if (certFileName.isEmpty()) {
- qCritical() << "Invalid client certificate filename";
- return;
- }
- QFile certFile(certFileName);
- if (!certFile.open(QIODevice::ReadOnly)) {
- qCritical() << "Could not open client certificate file";
- return;
- }
- QByteArray certData = certFile.readAll();
- if (certData.isEmpty()) {
- qCritical() << "Invalid client certificate file";
- return;
- }
- m_clientCert = certData;
-
- QString caCertFileName = pSettings->value("vis-client/ca-certificate", DEFAULT_CA_CERT_FILE).toString();
- if (caCertFileName.isEmpty()) {
- qCritical() << "Invalid CA certificate filename";
- return;
- }
- QFile caCertFile(caCertFileName);
- if (!caCertFile.open(QIODevice::ReadOnly)) {
- qCritical() << "Could not open CA certificate file";
- return;
- }
- QByteArray caCertData = caCertFile.readAll();
- if (caCertData.isEmpty()) {
- qCritical() << "Invalid CA certificate file";
- return;
- }
- // Pre-check CA certificate
- QList<QSslCertificate> newSslCaCerts = QSslCertificate::fromData(caCertData);
- if (newSslCaCerts.isEmpty()) {
- qCritical() << "Invalid CA certificate";
- return;
- }
- m_caCert = caCertData;
-
- QString authTokenFileName = pSettings->value("vis-client/authorization").toString();
- if (authTokenFileName.isEmpty()) {
- qCritical() << "Invalid authorization token filename";
- return;
- }
- QFile authTokenFile(authTokenFileName);
- if (!authTokenFile.open(QIODevice::ReadOnly | QIODevice::Text)) {
- qCritical() << "Could not open authorization token file";
- return;
- }
- QTextStream in(&authTokenFile);
- QString authToken = in.readLine();
- if (authToken.isEmpty()) {
- qCritical() << "Invalid authorization token file";
- return;
+ // Create gRPC channel
+ // NOTE: channel creation and waiting for connected state could be put into
+ // a thread that is spawned here.
+
+ QString host = m_config.hostname();
+ host += ":";
+ host += QString::number(m_config.port());
+
+ std::shared_ptr<grpc::Channel> channel;
+ if (!m_config.caCert().isEmpty()) {
+ qInfo() << "Using TLS";
+ grpc::SslCredentialsOptions options;
+ options.pem_root_certs = m_config.caCert().toStdString();
+ if (!m_config.tlsServerName().isEmpty()) {
+ grpc::ChannelArguments args;
+ auto target = m_config.tlsServerName();
+ qInfo() << "Overriding TLS target name with " << target;
+ args.SetString(GRPC_SSL_TARGET_NAME_OVERRIDE_ARG, target.toStdString());
+ channel = grpc::CreateCustomChannel(host.toStdString(), grpc::SslCredentials(options), args);
+ } else {
+ channel = grpc::CreateChannel(host.toStdString(), grpc::SslCredentials(options));
+ }
+ } else {
+ channel = grpc::CreateChannel(host.toStdString(), grpc::InsecureChannelCredentials());
}
- m_authToken = authToken;
- m_verbose = 0;
- QString verbose = pSettings->value("vis-client/verbose").toString();
- if (!verbose.isEmpty()) {
- if (verbose == "true" || verbose == "1")
- m_verbose = 1;
- if (verbose == "2")
- m_verbose = 2;
- }
+ m_broker = new QtKuksaClient(channel, config);
+ if (!m_broker)
+ qCritical() << "gRPC client initialization failed";
- m_valid = true;
-}
-
-VehicleSignals::VehicleSignals(const VehicleSignalsConfig &config, QObject *parent) :
- QObject(parent),
- m_config(config),
- m_request_id(0)
-{
- QObject::connect(&m_websocket, &QWebSocket::connected, this, &VehicleSignals::onConnected);
- QObject::connect(&m_websocket, QOverload<QAbstractSocket::SocketError>::of(&QWebSocket::error),
- this, &VehicleSignals::onError);
- QObject::connect(&m_websocket, &QWebSocket::disconnected, this, &VehicleSignals::onDisconnected);
+ QObject::connect(m_broker, &QtKuksaClient::connected, this, &VehicleSignals::onConnected);
}
VehicleSignals::~VehicleSignals()
{
- m_websocket.close();
+ delete m_broker;
}
void VehicleSignals::connect()
{
- if (!m_config.valid()) {
- qCritical() << "Invalid VIS server configuration";
- return;
- }
-
- QUrl visUrl;
- visUrl.setScheme(QStringLiteral("wss"));
- visUrl.setHost(m_config.hostname());
- visUrl.setPort(m_config.port());
-
- QSslConfiguration sslConfig = QSslConfiguration::defaultConfiguration();
-
- // Add client private key
- // i.e. kuksa_certificates/Client.key in source tree
- QSslKey sslKey(m_config.clientKey(), QSsl::Rsa);
- sslConfig.setPrivateKey(sslKey);
-
- // Add local client certificate
- // i.e. kuksa_certificates/Client.pem in source tree
- QList<QSslCertificate> sslCerts = QSslCertificate::fromData(m_config.clientCert());
- if (sslCerts.empty()) {
- qCritical() << "Invalid client certificate";
- return;
- }
- sslConfig.setLocalCertificate(sslCerts.first());
-
- // Add CA certificate
- // i.e. kuksa_certificates/CA.pem in source tree
- // Note the following can be simplified with QSslConfiguration::addCaCertificate with Qt 5.15
- QList<QSslCertificate> sslCaCerts = sslConfig.caCertificates();
- QList<QSslCertificate> newSslCaCerts = QSslCertificate::fromData(m_config.caCert());
- if (newSslCaCerts.empty()) {
- qCritical() << "Invalid CA certificate";
- return;
- }
- sslCaCerts.append(newSslCaCerts.first());
- sslConfig.setCaCertificates(sslCaCerts);
-
- sslConfig.setPeerVerifyMode(m_config.verifyPeer() ? QSslSocket::VerifyPeer : QSslSocket::VerifyNone);
+ // QtKuksaClient will call our onConnected slot when the channel
+ // is connected, and then we pass that along via our connected
+ // signal.
+ if (m_broker)
+ m_broker->connect();
+}
- m_websocket.setSslConfiguration(sslConfig);
+void VehicleSignals::authorize()
+{
+ // Databroker has no separate authorize call, so this is a no-op
+ emit authorized();
+}
- if (m_config.verbose())
- qInfo() << "Opening VIS websocket";
- m_websocket.open(visUrl);
+void VehicleSignals::get(const QString &path, const bool actuator)
+{
+ if (m_broker)
+ m_broker->get(path, actuator);
}
-void VehicleSignals::onConnected()
+void VehicleSignals::set(const QString &path, const QString &value, const bool actuator)
{
- if (m_config.verbose() > 1)
- qDebug() << "VehicleSignals::onConnected: enter";
- QObject::connect(&m_websocket, &QWebSocket::textMessageReceived, this, &VehicleSignals::onTextMessageReceived);
- emit connected();
+ if (m_broker)
+ m_broker->set(path, value, actuator);
}
-void VehicleSignals::onError(QAbstractSocket::SocketError error)
+void VehicleSignals::set(const QString &path, const qint32 value, const bool actuator)
{
- if (m_config.verbose() > 1)
- qDebug() << "VehicleSignals::onError: enter";
- QTimer::singleShot(1000, this, &VehicleSignals::reconnect);
+ if (m_broker)
+ m_broker->set(path, value, actuator);
}
-void VehicleSignals::reconnect()
+void VehicleSignals::set(const QString &path, const qint64 value, const bool actuator)
{
- if (m_config.verbose() > 1)
- qDebug() << "VehicleSignals::reconnect: enter";
- connect();
+ if (m_broker)
+ m_broker->set(path, value, actuator);
}
-void VehicleSignals::onDisconnected()
+void VehicleSignals::set(const QString &path, const quint32 value, const bool actuator)
{
- if (m_config.verbose() > 1)
- qDebug() << "VehicleSignals::onDisconnected: enter";
- QObject::disconnect(&m_websocket, &QWebSocket::textMessageReceived, this, &VehicleSignals::onTextMessageReceived);
- emit disconnected();
+ if (m_broker)
+ m_broker->set(path, value, actuator);
+}
- // Try to reconnect
- QTimer::singleShot(1000, this, &VehicleSignals::reconnect);
+void VehicleSignals::set(const QString &path, const quint64 value, const bool actuator)
+{
+ if (m_broker)
+ m_broker->set(path, value, actuator);
}
-void VehicleSignals::authorize()
+void VehicleSignals::set(const QString &path, const float value, const bool actuator)
{
- QVariantMap map;
- map["action"] = QString("authorize");
- map["tokens"] = m_config.authToken();
- map["requestId"] = QString::number(m_request_id++);
- QJsonDocument doc = QJsonDocument::fromVariant(map);
- m_websocket.sendTextMessage(doc.toJson(QJsonDocument::Compact).data());
+ if (m_broker)
+ m_broker->set(path, value, actuator);
}
-void VehicleSignals::get(const QString &path)
+void VehicleSignals::set(const QString &path, const double value, const bool actuator)
{
- QVariantMap map;
- map["action"] = QString("get");
- map["tokens"] = m_config.authToken();
- map["path"] = path;
- map["requestId"] = QString::number(m_request_id++);
- QJsonDocument doc = QJsonDocument::fromVariant(map);
- m_websocket.sendTextMessage(doc.toJson(QJsonDocument::Compact).data());
+ if (m_broker)
+ m_broker->set(path, value, actuator);
}
-void VehicleSignals::set(const QString &path, const QString &value)
+void VehicleSignals::subscribe(const QString &path, bool actuator)
{
- QVariantMap map;
- map["action"] = QString("set");
- map["tokens"] = m_config.authToken();
- map["path"] = path;
- map["value"] = value;
- map["requestId"] = QString::number(m_request_id++);
- QJsonDocument doc = QJsonDocument::fromVariant(map);
- m_websocket.sendTextMessage(doc.toJson(QJsonDocument::Compact).data());
+ if (m_broker)
+ m_broker->subscribe(path, actuator);
}
-void VehicleSignals::subscribe(const QString &path)
+void VehicleSignals::subscribe(const QMap<QString, bool> &signals_)
{
- QVariantMap map;
- map["action"] = QString("subscribe");
- map["tokens"] = m_config.authToken();
- map["path"] = path;
- map["requestId"] = QString::number(m_request_id++);
- QJsonDocument doc = QJsonDocument::fromVariant(map);
- m_websocket.sendTextMessage(doc.toJson(QJsonDocument::Compact).data());
+ if (m_broker)
+ m_broker->subscribe(signals_);
}
-bool VehicleSignals::parseData(const QJsonObject &response, QString &path, QString &value, QString &timestamp)
+// Slots
+
+void VehicleSignals::onConnected()
{
- if (response.contains("error")) {
- QString error = response.value("error").toString();
- return false;
- }
+ QObject::connect(m_broker, &QtKuksaClient::getResponse, this, &VehicleSignals::onGetResponse);
+ QObject::connect(m_broker, &QtKuksaClient::setResponse, this, &VehicleSignals::onSetResponse);
+ QObject::connect(m_broker, &QtKuksaClient::subscribeResponse, this, &VehicleSignals::onSubscribeResponse);
+ //QObject::connect(m_broker, &QtKuksaClient::subscribeDone, this, &VehicleSignals::onSubscribeDone);
- if (!(response.contains("data") && response["data"].isObject())) {
- qWarning() << "Malformed response (data missing)";
- return false;
- }
- QJsonObject data = response["data"].toObject();
- if (!(data.contains("path") && data["path"].isString())) {
- qWarning() << "Malformed response (path missing)";
- return false;
- }
- path = data["path"].toString();
- // Convert '/' to '.' in paths to ensure consistency for clients
- path.replace(QString("/"), QString("."));
+ emit connected();
+}
- if (!(data.contains("dp") && data["dp"].isObject())) {
- qWarning() << "Malformed response (datapoint missing)";
- return false;
- }
- QJsonObject dp = data["dp"].toObject();
- if (!dp.contains("value")) {
- qWarning() << "Malformed response (value missing)";
- return false;
- } else if (dp["value"].isString()) {
- value = dp["value"].toString();
- } else if (dp["value"].isDouble()) {
- value = QString::number(dp["value"].toDouble(), 'f', 9);
- } else if (dp["value"].isBool()) {
- value = dp["value"].toBool() ? "true" : "false";
- } else {
- qWarning() << "Malformed response (unsupported value type)";
- return false;
- }
+void VehicleSignals::onGetResponse(QString path, QString value, QString timestamp)
+{
+ emit getSuccessResponse(path, value, timestamp);
+}
- if (!(dp.contains("ts") && dp["ts"].isString())) {
- qWarning() << "Malformed response (timestamp missing)";
- return false;
- }
- timestamp = dp["ts"].toString();
+void VehicleSignals::onSetResponse(QString path, QString error)
+{
+ emit setErrorResponse(path, error);
+}
- return true;
+void VehicleSignals::onSubscribeResponse(QString path, QString value, QString timestamp)
+{
+ if (m_config.verbose() > 1)
+ qDebug() << "VehicleSignals::onSubscribeResponse: got " << path << " = " << value;
+ emit signalNotification(path, value, timestamp);
}
-//
-// NOTE:
-//
-// Ideally request ids would be used to provide some form of mapping
-// to callers of get/set for responses/errors. At present the demo
-// usecases are simple enough that it does not seem worth implementing
-// just yet.
-//
-void VehicleSignals::onTextMessageReceived(QString msg)
+void VehicleSignals::onSubscribeDone(const QMap<QString, bool> &signals_, bool canceled)
{
- msg = msg.simplified();
- QJsonDocument doc(QJsonDocument::fromJson(msg.toUtf8()));
- if (doc.isEmpty()) {
- qWarning() << "Received invalid JSON: empty VIS message";
- return;
+ if (!canceled) {
+ // queue up a resubscribe attempt
+ QFuture<void> future = QtConcurrent::run(this, &VehicleSignals::resubscribe, signals_);
}
+}
- if (!doc.isObject()) {
- qWarning() << "Received invalid JSON: malformed VIS message";
- return;
- }
- QJsonObject obj = doc.object();
+// Private
- if (!obj.contains("action")) {
- qWarning() << "Received unknown message (no action), discarding";
- return;
- }
-
- QString action = obj.value("action").toString();
- if (action == "authorize") {
- if (obj.contains("error")) {
- QString error = obj.value("error").toString();
- qWarning() << "VIS authorization failed: " << error;
- } else {
- if (m_config.verbose() > 1)
- qDebug() << "authorized";
- emit authorized();
- }
- } else if (action == "subscribe") {
- if (obj.contains("error")) {
- QString error = obj.value("error").toString();
- qWarning() << "VIS subscription failed: " << error;
- }
- } else if (action == "get") {
- if (obj.contains("error")) {
- QString error = obj.value("error").toString();
- qWarning() << "VIS get failed: " << error;
- } else {
- QString path, value, ts;
- if (parseData(obj, path, value, ts)) {
- if (m_config.verbose() > 1)
- qDebug() << "VehicleSignals::onTextMessageReceived: emitting response" << path << " = " << value;
- emit getSuccessResponse(path, value, ts);
- }
- }
- } else if (action == "set") {
- if (obj.contains("error")) {
- QString error = obj.value("error").toString();
- qWarning() << "VIS set failed: " << error;
- }
- } else if (action == "subscription") {
- QString path, value, ts;
- if (parseData(obj, path, value, ts)) {
- if (m_config.verbose() > 1)
- qDebug() << "VehicleSignals::onTextMessageReceived: emitting notification" << path << " = " << value;
- emit signalNotification(path, value, ts);
- }
- } else {
- qWarning() << "unhandled VIS response of type: " << action;
- }
+void VehicleSignals::resubscribe(const QMap<QString, bool> &signals_)
+{
+ // Delay 100 milliseconds between subscribe attempts
+ QThread::msleep(100);
+
+ if (m_broker)
+ m_broker->subscribe(signals_);
}