ckb-next  v0.2.8 at branch master
ckb-next driver for corsair devices
 All Data Structures Namespaces Files Functions Variables Typedefs Enumerations Enumerator Friends Macros Pages
kbfirmware.cpp
Go to the documentation of this file.
1 #include "kbfirmware.h"
2 #include "kbmanager.h"
3 #include "quazip/quazip.h"
4 #include "quazip/quazipfile.h"
5 #include <QDateTime>
6 #include <QDebug>
7 
8 // Auto check: 1 hr
9 static const quint64 AUTO_CHECK_TIME = 60 * 60 * 1000;
10 
12 KbFirmware::FW::FW() : fwVersion(0.f), ckbVersion(0.f) {}
13 
16 {
17  // Disable bearer polling. This corrects an issue with latency spikes when
18  // using WiFi. The problem and workaround are described here:
19  // https://lostdomain.org/2017/06/17/qt-qnetworkaccessmanager-causing-latency-spikes-on-wifi/
20  qputenv("QT_BEARER_POLL_TIMEOUT", QByteArray::number(-1));
21  networkManager = new QNetworkAccessManager();
22 }
23 
25  delete networkManager;
26 }
27 
29  quint64 now = QDateTime::currentMSecsSinceEpoch();
30  if(now < lastCheck + AUTO_CHECK_TIME)
31  return false;
32  // First location is for debugging only.
33  // tableDownload = networkManager->get(QNetworkRequest(QUrl("https://raw.githubusercontent.com/frickler24/ckb-next/issues-26-Firmware-Incident/FIRMWARE")));
34  // This one is the production one.
35  tableDownload = networkManager->get(QNetworkRequest(QUrl("https://raw.githubusercontent.com/mattanger/ckb-next/master/FIRMWARE")));
36  connect(tableDownload, SIGNAL(finished()), this, SLOT(downloadFinished()));
37  lastCheck = now;
38  return true;
39 }
40 
41 void KbFirmware::processDownload(QNetworkReply* reply){
42  if(reply->error() != QNetworkReply::NoError)
43  return;
44  // Update last check
45  lastCheck = lastFinished = QDateTime::currentMSecsSinceEpoch();
46  QByteArray data = reply->readAll();
47  // Don't do anything if this is the same as the last version downloaded
48  QByteArray hash = QCryptographicHash::hash(data, QCryptographicHash::Sha256);
49  if(hash == fwTableHash)
50  return;
51  fwTableHash = hash;
52  if(hasGPG == UNKNOWN){
53  // Check for a GPG installation
54  QProcess gpg;
55  gpg.start("gpg", QStringList("--version"));
56  gpg.waitForFinished();
57  if(gpg.error() == QProcess::FailedToStart)
58  // No GPG install
59  hasGPG = NO;
60  else {
61  QString output = QString::fromUtf8(gpg.readAll());
62  // Must support RSA keys and SHA256
63  if(output.contains("RSA", Qt::CaseInsensitive) && output.contains("SHA256", Qt::CaseInsensitive))
64  hasGPG = YES;
65  else
66  hasGPG = NO;
67  }
68  if(!hasGPG)
69  qDebug() << "No GPG detected, signature verification disabled";
70  }
71  if(hasGPG){
72  // If GPG is available, check the signature on the file before proceeding.
73  QDir tmp = QDir::temp();
74  // Save file to a temporary path. Include PID to avoid conflicts
75  qint64 pid = QCoreApplication::applicationPid();
76  QString fwPath = tmp.absoluteFilePath(QString("ckb-%1-firmware").arg(pid));
77  QFile firmware(fwPath);
78  if(!firmware.open(QIODevice::WriteOnly)
79  || firmware.write(data) != data.length()){
80  qDebug() << "Failed to write firmware file to temporary location, aborting firmware check";
81  return;
82  }
83  firmware.close();
84  // Write GPG key
85  QString keyPath = tmp.absoluteFilePath(QString("ckb-%1-key.gpg").arg(pid));
86  if(!QFile::copy(":/bin/ckb-next-key.gpg", keyPath)){
87  firmware.remove();
88  qDebug() << "Failed to write GPG key to temporary location, aborting firmware check";
89  return;
90  }
91  // Check signature
92  QProcess gpg;
93  gpg.start("gpg", QStringList("--no-default-keyring") << "--keyring" << keyPath << "--verify" << fwPath);
94  gpg.waitForFinished();
95  // Clean up temp files
96  tmp.remove(fwPath);
97  tmp.remove(keyPath);
98  if(gpg.error() != QProcess::UnknownError || gpg.exitCode() != 0){
99  qDebug() << "GPG couldn't verify firmware signature:";
100  qDebug() << gpg.readAllStandardOutput();
101  qDebug() << gpg.readAllStandardError();
102  return;
103  }
104  // Signature good, proceed to update database
105  }
106  fwTable.clear();
107  QStringList lines = QString::fromUtf8(data).split("\n");
108  bool scan = false;
109  foreach(QString line, lines){
110  // Collapse whitespace
111  line.replace(QRegExp("\\s+"), " ").remove(QRegExp("^\\s")).remove(QRegExp("\\s$"));
112  // Skip empty or commented-out lines
113  if(line.length() == 0 || line.at(0) == '#')
114  continue;
115  // Don't read anything until the entries begin and don't read anything after they end
116  if(!scan){
117  if(line == "!BEGIN FW ENTRIES")
118  scan = true;
119  else
120  continue;
121  }
122  if(line == "!END FW ENTRIES")
123  break;
124  QStringList components = line.split(" ");
125  if(components.length() != 7)
126  continue;
127  // "VENDOR-PRODUCT"
128  QString device = components[0].toUpper() + "-" + components[1].toUpper();
129  FW fw;
130  fw.fwVersion = components[2].toFloat(); // Firmware blob version
131  fw.url = QUrl::fromPercentEncoding(components[3].toLatin1()); // URL to zip file
132  fw.ckbVersion = KbManager::parseVersionString(components[4]); // Minimum ckb version
133  fw.fileName = QUrl::fromPercentEncoding(components[5].toLatin1()); // Name of file inside zip
134  fw.hash = QByteArray::fromHex(components[6].toLatin1()); // SHA256 of file inside zip
135  // Update entry
136  fwTable[device] = fw;
137  }
138  qDebug() << "Downloaded new firmware list." << fwTable.count() << "entries found.";
139 }
140 
142  if(!tableDownload)
143  return;
145  tableDownload->deleteLater();
146  tableDownload = 0;
147  emit downloaded();
148 }
149 
150 static QString tableName(const QString& features){
151  QStringList components = features.split(" ");
152  if(components.length() < 2)
153  return "";
154  // First two components are vendor and model
155  QString vendorModel = components[0].toUpper() + "-" + components[1].toUpper();
156  // Add "RGB" on RGB boards
157  if(features.contains("rgb") && !features.contains("monochrome"))
158  vendorModel += "RGB";
159  return vendorModel;
160 }
161 
162 float KbFirmware::_latestForBoard(const QString& features, bool waitForComplete){
163  if((tableDownload || checkUpdates()) && waitForComplete){
164  // If waiting is desired, enter an event loop and stay here until the download is finished
165  QEventLoop loop(this);
166  connect(this, SIGNAL(downloaded()), &loop, SLOT(quit()));
167  loop.exec();
168  }
169  // Find this board
170  QString name = tableName(features);
171  FW info = fwTable.value(name);
172  if(info.hash.isEmpty())
173  return 0.f;
174  // Don't return the new version if the current ckb doesn't support it
176  return -1.f;
177  return info.fwVersion;
178 }
179 
180 QByteArray KbFirmware::_fileForBoard(const QString& features){
181  QString name = tableName(features);
182  FW info = fwTable.value(name);
183  if(info.hash.isEmpty())
184  return "";
185  // Download zip from URL. Wait for it to finish.
186  QNetworkReply* reply = networkManager->get(QNetworkRequest(QUrl(info.url)));
187  QEventLoop loop(this);
188  connect(reply, SIGNAL(finished()), &loop, SLOT(quit()));
189  loop.exec();
190  // Download finished, process data
191  if(reply->error() != QNetworkReply::NoError)
192  return "";
193  QByteArray zipData = reply->readAll();
194  QBuffer buffer(&zipData);
195  // Open zip archive
196  QuaZip zip(&buffer);
197  if(!zip.open(QuaZip::mdUnzip))
198  return "";
199  // Find the desired file
201  return "";
202  QuaZipFile binFile(&zip);
203  if(!binFile.open(QIODevice::ReadOnly))
204  return "";
205  QByteArray binary = binFile.readAll();
206  // Check the hash
207  if(QCryptographicHash::hash(binary, QCryptographicHash::Sha256) != info.hash)
208  return "";
209  return binary;
210 }
static KbFirmware instance
Definition: kbfirmware.h:58
QNetworkAccessManager * networkManager
Definition: kbfirmware.h:28
void processDownload(QNetworkReply *reply)
Definition: kbfirmware.cpp:41
bool _checkUpdates()
Definition: kbfirmware.cpp:28
QString fileName
Definition: kbfirmware.h:38
static void quit()
quit Stop working the daemon. function is called if the daemon received a sigterm In this case...
Definition: main.c:30
float fwVersion
Definition: kbfirmware.h:40
static float parseVersionString(QString version)
Definition: kbmanager.cpp:48
QByteArray fwTableHash
Definition: kbfirmware.h:46
static float ckbDaemonVersionF()
Definition: kbmanager.h:31
static const quint64 AUTO_CHECK_TIME
Definition: kbfirmware.cpp:9
QString url
Definition: kbfirmware.h:38
quint64 lastFinished
Definition: kbfirmware.h:35
float _latestForBoard(const QString &features, bool waitForComplete)
Definition: kbfirmware.cpp:162
quint64 lastCheck
Definition: kbfirmware.h:35
static float ckbGuiVersionF()
Definition: kbmanager.h:30
enum KbFirmware::@1 hasGPG
bool setCurrentFile(const QString &fileName, CaseSensitivity cs=csDefault)
Sets current file by its name.
Definition: quazip.cpp:408
ZIP file is open for reading files inside it.
Definition: quazip.h:96
QMap< QString, FW > fwTable
Definition: kbfirmware.h:44
static bool checkUpdates()
Definition: kbfirmware.h:15
static QString tableName(const QString &features)
Definition: kbfirmware.cpp:150
ZIP archive.
Definition: quazip.h:84
A file inside ZIP archive.
Definition: quazipfile.h:74
virtual bool open(OpenMode mode)
Opens a file for reading.
Definition: quazipfile.cpp:221
Case insensitive.
Definition: quazip.h:117
QNetworkReply * tableDownload
Definition: kbfirmware.h:49
float ckbVersion
Definition: kbfirmware.h:40
bool open(Mode mode, zlib_filefunc_def *ioApi=NULL)
Opens ZIP file.
Definition: quazip.cpp:215
void downloadFinished()
Definition: kbfirmware.cpp:141
QByteArray hash
Definition: kbfirmware.h:39
QByteArray _fileForBoard(const QString &features)
Definition: kbfirmware.cpp:180