You can not select more than 25 topics Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.
wwcs-1314/src/PairingManager/PairingManager.cc

576 lines
19 KiB

/****************************************************************************
*
* (c) 2019 QGROUNDCONTROL PROJECT <http://www.qgroundcontrol.org>
*
* QGroundControl is licensed according to the terms in the file
* COPYING.md in the root of the source code directory.
*
****************************************************************************/
#include "PairingManager.h"
#include "SettingsManager.h"
#include "MicrohardManager.h"
#include "QGCApplication.h"
#include "QGCCorePlugin.h"
#include <QSettings>
#include <QJsonObject>
#include <QStandardPaths>
#include <QMutexLocker>
QGC_LOGGING_CATEGORY(PairingManagerLog, "PairingManagerLog")
static const char* jsonFileName = "pairing.json";
//-----------------------------------------------------------------------------
static QString
random_string(uint length)
{
auto randchar = []() -> char
{
const char charset[] =
"0123456789"
"ABCDEFGHIJKLMNOPQRSTUVWXYZ"
"abcdefghijklmnopqrstuvwxyz";
const uint max_index = (sizeof(charset) - 1);
return charset[static_cast<uint>(rand()) % max_index];
};
std::string str(length, 0);
std::generate_n(str.begin(), length, randchar);
return QString::fromStdString(str);
}
//-----------------------------------------------------------------------------
PairingManager::PairingManager(QGCApplication* app, QGCToolbox* toolbox)
: QGCTool(app, toolbox)
, _aes("J6+KuWh9K2!hG(F'", 0x368de30e8ec063ce)
{
_jsonFileName = QDir::temp().filePath(jsonFileName);
connect(this, &PairingManager::parsePairingJson, this, &PairingManager::_parsePairingJson);
connect(this, &PairingManager::setPairingStatus, this, &PairingManager::_setPairingStatus);
connect(this, &PairingManager::startUpload, this, &PairingManager::_startUpload);
}
//-----------------------------------------------------------------------------
PairingManager::~PairingManager()
{
}
//-----------------------------------------------------------------------------
void
PairingManager::setToolbox(QGCToolbox *toolbox)
{
QGCTool::setToolbox(toolbox);
_updatePairedDeviceNameList();
emit pairedListChanged();
}
//-----------------------------------------------------------------------------
void
PairingManager::_pairingCompleted(QString name)
{
_writeJson(_jsonDoc, _pairingCacheFile(name));
_remotePairingMap["NM"] = name;
_lastPaired = name;
_updatePairedDeviceNameList();
emit pairedListChanged();
emit pairedVehicleChanged();
//_app->informationMessageBoxOnMainThread("", tr("Paired with %1").arg(name));
setPairingStatus(PairingSuccess, tr("Pairing Successfull"));
}
//-----------------------------------------------------------------------------
void
PairingManager::_connectionCompleted(QString /*name*/)
{
//QString pwd = _remotePairingMap["PWD"].toString();
//_toolbox->microhardManager()->switchToConnectionEncryptionKey(pwd);
//_app->informationMessageBoxOnMainThread("", tr("Connected to %1").arg(name));
setPairingStatus(PairingConnected, tr("Connection Successfull"));
}
//-----------------------------------------------------------------------------
void
PairingManager::_startUpload(QString pairURL, QJsonDocument jsonDoc)
{
QMutexLocker lock(&_uploadMutex);
if (_uploadManager != nullptr) {
return;
}
_uploadManager = new QNetworkAccessManager(this);
QString str = jsonDoc.toJson(QJsonDocument::JsonFormat::Compact);
qCDebug(PairingManagerLog) << "Starting upload to: " << pairURL << " " << str;
_uploadData = QString::fromStdString(_aes.encrypt(str.toStdString()));
_uploadURL = pairURL;
_startUploadRequest();
}
//-----------------------------------------------------------------------------
void
PairingManager::_startUploadRequest()
{
QNetworkRequest req;
req.setUrl(QUrl(_uploadURL));
req.setHeader(QNetworkRequest::ContentTypeHeader, "application/x-www-form-urlencoded");
QNetworkReply *reply = _uploadManager->post(req, _uploadData.toUtf8());
connect(reply, &QNetworkReply::finished, this, &PairingManager::_uploadFinished);
}
//-----------------------------------------------------------------------------
void
PairingManager::_stopUpload()
{
QMutexLocker lock(&_uploadMutex);
if (_uploadManager != nullptr) {
delete _uploadManager;
_uploadManager = nullptr;
}
}
//-----------------------------------------------------------------------------
void
PairingManager::_uploadFinished()
{
QMutexLocker lock(&_uploadMutex);
QNetworkReply* reply = qobject_cast<QNetworkReply*>(QObject::sender());
if (reply) {
if (_uploadManager != nullptr) {
if (reply->error() == QNetworkReply::NoError) {
qCDebug(PairingManagerLog) << "Upload finished.";
QByteArray bytes = reply->readAll();
QString str = QString::fromUtf8(bytes.data(), bytes.size());
qCDebug(PairingManagerLog) << "Reply: " << str;
auto a = str.split(QRegExp("\\s+"));
if (a[0] == "Accepted" && a.length() > 1) {
_pairingCompleted(a[1]);
} else if (a[0] == "Connected" && a.length() > 1) {
_connectionCompleted(a[1]);
} else if (a[0] == "Connection" && a.length() > 1) {
setPairingStatus(PairingConnectionRejected, tr("Connection Rejected"));
qCDebug(PairingManagerLog) << "Connection error: " << str;
} else {
setPairingStatus(PairingRejected, tr("Pairing Rejected"));
qCDebug(PairingManagerLog) << "Pairing error: " << str;
}
_uploadManager->deleteLater();
_uploadManager = nullptr;
} else {
if(++_pairRetryCount > 3) {
qCDebug(PairingManagerLog) << "Giving up";
setPairingStatus(PairingError, tr("No Response From Vehicle"));
_uploadManager->deleteLater();
_uploadManager = nullptr;
} else {
qCDebug(PairingManagerLog) << "Upload error: " + reply->errorString();
_startUploadRequest();
}
}
}
}
}
//-----------------------------------------------------------------------------
void
PairingManager::_parsePairingJsonFile()
{
QFile file(_jsonFileName);
file.open(QIODevice::ReadOnly | QIODevice::Text);
QString json = file.readAll();
file.remove();
file.close();
jsonReceived(json);
}
//-----------------------------------------------------------------------------
void
PairingManager::connectToPairedDevice(QString name)
{
setPairingStatus(PairingConnecting, tr("Connecting to %1").arg(name));
QFile file(_pairingCacheFile(name));
file.open(QIODevice::ReadOnly | QIODevice::Text);
QString json = file.readAll();
jsonReceived(json);
}
//-----------------------------------------------------------------------------
void
PairingManager::removePairedDevice(QString name)
{
QFile file(_pairingCacheFile(name));
file.remove();
_updatePairedDeviceNameList();
emit pairedListChanged();
}
//-----------------------------------------------------------------------------
void
PairingManager::_updatePairedDeviceNameList()
{
_deviceList.clear();
QDirIterator it(_pairingCacheDir().absolutePath(), QDir::Files);
while (it.hasNext()) {
QFileInfo fileInfo(it.next());
_deviceList.append(fileInfo.fileName());
qCDebug(PairingManagerLog) << "Listing: " << fileInfo.fileName();
}
}
//-----------------------------------------------------------------------------
QString
PairingManager::_assumeMicrohardPairingJson()
{
QJsonDocument json;
QJsonObject jsonObject;
jsonObject.insert("LT", "MH");
jsonObject.insert("IP", "192.168.168.10");
jsonObject.insert("AIP", _toolbox->microhardManager()->remoteIPAddr());
jsonObject.insert("CU", _toolbox->microhardManager()->configUserName());
jsonObject.insert("CP", _toolbox->microhardManager()->configPassword());
jsonObject.insert("EK", _toolbox->microhardManager()->encryptionKey());
json.setObject(jsonObject);
return QString(json.toJson(QJsonDocument::Compact));
}
//-----------------------------------------------------------------------------
void
PairingManager::_parsePairingJson(QString jsonEnc)
{
QString json = QString::fromStdString(_aes.decrypt(jsonEnc.toStdString()));
if (json == "") {
json = jsonEnc;
}
qCDebug(PairingManagerLog) << "Parsing JSON: " << json;
_jsonDoc = QJsonDocument::fromJson(json.toUtf8());
if (_jsonDoc.isNull()) {
setPairingStatus(PairingError, tr("Invalid Pairing File"));
qCDebug(PairingManagerLog) << "Failed to create Pairing JSON doc.";
return;
}
if (!_jsonDoc.isObject()) {
setPairingStatus(PairingError, tr("Error Parsing Pairing File"));
qCDebug(PairingManagerLog) << "Pairing JSON is not an object.";
return;
}
QJsonObject jsonObj = _jsonDoc.object();
if (jsonObj.isEmpty()) {
setPairingStatus(PairingError, tr("Error Parsing Pairing File"));
qCDebug(PairingManagerLog) << "Pairing JSON object is empty.";
return;
}
_remotePairingMap = jsonObj.toVariantMap();
QString linkType = _remotePairingMap["LT"].toString();
QString pport = _remotePairingMap["PP"].toString();
if (pport.length()==0) {
pport = "29351";
}
if (linkType.length()==0) {
setPairingStatus(PairingError, tr("Error Parsing Pairing File"));
qCDebug(PairingManagerLog) << "Pairing JSON is malformed.";
return;
}
_toolbox->microhardManager()->switchToPairingEncryptionKey();
QString pairURL = "http://" + _remotePairingMap["IP"].toString() + ":" + pport;
bool connecting = jsonObj.contains("PWD");
QJsonDocument jsonDoc;
if (!connecting) {
pairURL += + "/pair";
QString pwd = random_string(8);
// TODO generate certificates
QString cert1 = "";
QString cert2 = "";
jsonObj.insert("PWD", pwd);
jsonObj.insert("CERT1", cert1);
jsonObj.insert("CERT2", cert2);
_jsonDoc.setObject(jsonObj);
if (linkType == "ZT") {
jsonDoc = _createZeroTierPairingJson(cert1);
} else if (linkType == "MH") {
jsonDoc = _createMicrohardPairingJson(pwd, cert1);
}
} else {
pairURL += + "/connect";
QString cert2 = _remotePairingMap["CERT2"].toString();
if (linkType == "ZT") {
jsonDoc = _createZeroTierConnectJson(cert2);
} else if (linkType == "MH") {
jsonDoc = _createMicrohardConnectJson(cert2);
}
}
if (linkType == "ZT") {
_toolbox->settingsManager()->appSettings()->enableMicrohard()->setRawValue(false);
_toolbox->settingsManager()->appSettings()->enableTaisync()->setRawValue(false);
emit startUpload(pairURL, jsonDoc);
} else if (linkType == "MH") {
_toolbox->settingsManager()->appSettings()->enableMicrohard()->setRawValue(true);
_toolbox->settingsManager()->appSettings()->enableTaisync()->setRawValue(false);
if (_remotePairingMap.contains("AIP")) {
_toolbox->microhardManager()->setRemoteIPAddr(_remotePairingMap["AIP"].toString());
}
if (_remotePairingMap.contains("CU")) {
_toolbox->microhardManager()->setConfigUserName(_remotePairingMap["CU"].toString());
}
if (_remotePairingMap.contains("CP")) {
_toolbox->microhardManager()->setConfigPassword(_remotePairingMap["CP"].toString());
}
if (_remotePairingMap.contains("EK") && !connecting) {
_toolbox->microhardManager()->setEncryptionKey(_remotePairingMap["EK"].toString());
}
_toolbox->microhardManager()->updateSettings();
emit startUpload(pairURL, jsonDoc);
}
}
//-----------------------------------------------------------------------------
QString
PairingManager::_getLocalIPInNetwork(QString remoteIP, int num)
{
QStringList pieces = remoteIP.split(".");
QString ipPrefix = "";
for (int i = 0; i<num && i<pieces.length(); i++) {
ipPrefix += pieces[i] + ".";
}
const QHostAddress &localhost = QHostAddress(QHostAddress::LocalHost);
for (const QHostAddress &address: QNetworkInterface::allAddresses()) {
if (address.protocol() == QAbstractSocket::IPv4Protocol && address != localhost) {
if (address.toString().startsWith(ipPrefix)) {
return address.toString();
}
}
}
return "";
}
//-----------------------------------------------------------------------------
QDir
PairingManager::_pairingCacheDir()
{
const QString spath(QFileInfo(QSettings().fileName()).dir().absolutePath());
QDir dir = spath + QDir::separator() + "PairingCache";
if (!dir.exists()) {
dir.mkpath(".");
}
return dir;
}
//-----------------------------------------------------------------------------
QString
PairingManager::_pairingCacheFile(QString uavName)
{
return _pairingCacheDir().filePath(uavName);
}
//-----------------------------------------------------------------------------
void
PairingManager::_writeJson(QJsonDocument &jsonDoc, QString fileName)
{
QString val = jsonDoc.toJson(QJsonDocument::JsonFormat::Compact);
qCDebug(PairingManagerLog) << "Write json " << val;
QString enc = QString::fromStdString(_aes.encrypt(val.toStdString()));
QFile file(fileName);
file.open(QIODevice::WriteOnly | QIODevice::Text | QIODevice::Truncate);
file.write(enc.toUtf8());
file.close();
}
//-----------------------------------------------------------------------------
QJsonDocument
PairingManager::_createZeroTierPairingJson(QString cert1)
{
QString localIP = _getLocalIPInNetwork(_remotePairingMap["IP"].toString(), 2);
QJsonObject jsonObj;
jsonObj.insert("LT", "ZT");
jsonObj.insert("IP", localIP);
jsonObj.insert("P", 14550);
jsonObj.insert("CERT1", cert1);
return QJsonDocument(jsonObj);
}
//-----------------------------------------------------------------------------
QJsonDocument
PairingManager::_createMicrohardPairingJson(QString pwd, QString cert1)
{
QString localIP = _getLocalIPInNetwork(_remotePairingMap["IP"].toString(), 3);
QJsonObject jsonObj;
jsonObj.insert("LT", "MH");
jsonObj.insert("IP", localIP);
jsonObj.insert("P", 14550);
jsonObj.insert("PWD", pwd);
jsonObj.insert("CERT1", cert1);
return QJsonDocument(jsonObj);
}
//-----------------------------------------------------------------------------
QJsonDocument
PairingManager::_createZeroTierConnectJson(QString cert2)
{
QString localIP = _getLocalIPInNetwork(_remotePairingMap["IP"].toString(), 2);
QJsonObject jsonObj;
jsonObj.insert("LT", "ZT");
jsonObj.insert("IP", localIP);
jsonObj.insert("P", 14550);
jsonObj.insert("CERT2", cert2);
return QJsonDocument(jsonObj);
}
//-----------------------------------------------------------------------------
QJsonDocument
PairingManager::_createMicrohardConnectJson(QString cert2)
{
QString localIP = _getLocalIPInNetwork(_remotePairingMap["IP"].toString(), 3);
QJsonObject jsonObj;
jsonObj.insert("LT", "MH");
jsonObj.insert("IP", localIP);
jsonObj.insert("P", 14550);
jsonObj.insert("CERT2", cert2);
return QJsonDocument(jsonObj);
}
//-----------------------------------------------------------------------------
QStringList
PairingManager::pairingLinkTypeStrings()
{
//-- Must follow same order as enum LinkType in LinkConfiguration.h
static QStringList list;
int i = 0;
if (!list.size()) {
#if defined QGC_ENABLE_QTNFC
list += tr("NFC");
_nfcIndex = i++;
#endif
#if defined QGC_GST_MICROHARD_ENABLED
list += tr("Microhard");
_microhardIndex = i++;
#endif
}
return list;
}
//-----------------------------------------------------------------------------
void
PairingManager::_setPairingStatus(PairingStatus status, QString statusStr)
{
_status = status;
_statusString = statusStr;
emit pairingStatusChanged();
}
//-----------------------------------------------------------------------------
QString
PairingManager::pairingStatusStr() const
{
return _statusString;
}
#if QGC_GST_MICROHARD_ENABLED
//-----------------------------------------------------------------------------
void
PairingManager::startMicrohardPairing()
{
stopPairing();
_pairRetryCount = 0;
setPairingStatus(PairingActive, tr("Pairing..."));
_parsePairingJson(_assumeMicrohardPairingJson());
}
#endif
//-----------------------------------------------------------------------------
void
PairingManager::stopPairing()
{
#if defined QGC_ENABLE_QTNFC
pairingNFC.stop();
#endif
_stopUpload();
setPairingStatus(PairingIdle, "");
}
#if defined QGC_ENABLE_QTNFC
//-----------------------------------------------------------------------------
void
PairingManager::startNFCScan()
{
stopPairing();
setPairingStatus(PairingActive, tr("Pairing..."));
pairingNFC.start();
}
#endif
//-----------------------------------------------------------------------------
#ifdef __android__
static const char kJniClassName[] {"org/mavlink/qgroundcontrol/QGCActivity"};
//-----------------------------------------------------------------------------
static void jniNFCTagReceived(JNIEnv *envA, jobject thizA, jstring messageA)
{
Q_UNUSED(thizA);
const char *stringL = envA->GetStringUTFChars(messageA, nullptr);
QString ndef = QString::fromUtf8(stringL);
envA->ReleaseStringUTFChars(messageA, stringL);
if (envA->ExceptionCheck())
envA->ExceptionClear();
qCDebug(PairingManagerLog) << "NDEF Tag Received: " << ndef;
qgcApp()->toolbox()->pairingManager()->jsonReceived(ndef);
}
//-----------------------------------------------------------------------------
void PairingManager::setNativeMethods(void)
{
// REGISTER THE C++ FUNCTION WITH JNI
JNINativeMethod javaMethods[] {
{"nativeNFCTagReceived", "(Ljava/lang/String;)V", reinterpret_cast<void *>(jniNFCTagReceived)}
};
QAndroidJniEnvironment jniEnv;
if (jniEnv->ExceptionCheck()) {
jniEnv->ExceptionDescribe();
jniEnv->ExceptionClear();
}
jclass objectClass = jniEnv->FindClass(kJniClassName);
if(!objectClass) {
qWarning() << "Couldn't find class:" << kJniClassName;
return;
}
jint val = jniEnv->RegisterNatives(objectClass, javaMethods, sizeof(javaMethods) / sizeof(javaMethods[0]));
if (val < 0) {
qWarning() << "Error registering methods: " << val;
} else {
qCDebug(PairingManagerLog) << "Native Functions Registered";
}
if (jniEnv->ExceptionCheck()) {
jniEnv->ExceptionDescribe();
jniEnv->ExceptionClear();
}
}
#endif
//-----------------------------------------------------------------------------