/**************************************************************************** * * (c) 2019 QGROUNDCONTROL PROJECT * * 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 #include #include #include 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(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(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; iGetStringUTFChars(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(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 //-----------------------------------------------------------------------------