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