cabana: remove dependency on selfdrive/ui (#36434)
remove dependency on selfdrive/uipull/36438/head
parent
4f52f3f3c5
commit
378212e5ab
11 changed files with 410 additions and 11 deletions
@ -0,0 +1,161 @@ |
||||
#include "selfdrive/ui/qt/api.h" |
||||
|
||||
#include <openssl/pem.h> |
||||
#include <openssl/rsa.h> |
||||
|
||||
#include <QApplication> |
||||
#include <QCryptographicHash> |
||||
#include <QDateTime> |
||||
#include <QDebug> |
||||
#include <QJsonDocument> |
||||
#include <QNetworkRequest> |
||||
|
||||
#include <memory> |
||||
#include <string> |
||||
|
||||
#include "common/util.h" |
||||
#include "system/hardware/hw.h" |
||||
#include "selfdrive/ui/qt/util.h" |
||||
|
||||
QString getVersion() { |
||||
static QString version = QString::fromStdString(Params().get("Version")); |
||||
return version; |
||||
} |
||||
|
||||
QString getUserAgent() { |
||||
return "openpilot-" + getVersion(); |
||||
} |
||||
|
||||
std::optional<QString> getDongleId() { |
||||
std::string id = Params().get("DongleId"); |
||||
|
||||
if (!id.empty() && (id != "UnregisteredDevice")) { |
||||
return QString::fromStdString(id); |
||||
} else { |
||||
return {}; |
||||
} |
||||
} |
||||
|
||||
namespace CommaApi { |
||||
|
||||
RSA *get_rsa_private_key() { |
||||
static std::unique_ptr<RSA, decltype(&RSA_free)> rsa_private(nullptr, RSA_free); |
||||
if (!rsa_private) { |
||||
FILE *fp = fopen(Path::rsa_file().c_str(), "rb"); |
||||
if (!fp) { |
||||
qDebug() << "No RSA private key found, please run manager.py or registration.py"; |
||||
return nullptr; |
||||
} |
||||
rsa_private.reset(PEM_read_RSAPrivateKey(fp, NULL, NULL, NULL)); |
||||
fclose(fp); |
||||
} |
||||
return rsa_private.get(); |
||||
} |
||||
|
||||
QByteArray rsa_sign(const QByteArray &data) { |
||||
RSA *rsa_private = get_rsa_private_key(); |
||||
if (!rsa_private) return {}; |
||||
|
||||
QByteArray sig(RSA_size(rsa_private), Qt::Uninitialized); |
||||
unsigned int sig_len; |
||||
int ret = RSA_sign(NID_sha256, (unsigned char*)data.data(), data.size(), (unsigned char*)sig.data(), &sig_len, rsa_private); |
||||
assert(ret == 1); |
||||
assert(sig.size() == sig_len); |
||||
return sig; |
||||
} |
||||
|
||||
QString create_jwt(const QJsonObject &payloads, int expiry) { |
||||
QJsonObject header = {{"alg", "RS256"}}; |
||||
|
||||
auto t = QDateTime::currentSecsSinceEpoch(); |
||||
QJsonObject payload = {{"identity", getDongleId().value_or("")}, {"nbf", t}, {"iat", t}, {"exp", t + expiry}}; |
||||
for (auto it = payloads.begin(); it != payloads.end(); ++it) { |
||||
payload.insert(it.key(), it.value()); |
||||
} |
||||
|
||||
auto b64_opts = QByteArray::Base64UrlEncoding | QByteArray::OmitTrailingEquals; |
||||
QString jwt = QJsonDocument(header).toJson(QJsonDocument::Compact).toBase64(b64_opts) + '.' + |
||||
QJsonDocument(payload).toJson(QJsonDocument::Compact).toBase64(b64_opts); |
||||
|
||||
auto hash = QCryptographicHash::hash(jwt.toUtf8(), QCryptographicHash::Sha256); |
||||
return jwt + "." + rsa_sign(hash).toBase64(b64_opts); |
||||
} |
||||
|
||||
} // namespace CommaApi
|
||||
|
||||
HttpRequest::HttpRequest(QObject *parent, bool create_jwt, int timeout) : create_jwt(create_jwt), QObject(parent) { |
||||
networkTimer = new QTimer(this); |
||||
networkTimer->setSingleShot(true); |
||||
networkTimer->setInterval(timeout); |
||||
connect(networkTimer, &QTimer::timeout, this, &HttpRequest::requestTimeout); |
||||
} |
||||
|
||||
bool HttpRequest::active() const { |
||||
return reply != nullptr; |
||||
} |
||||
|
||||
bool HttpRequest::timeout() const { |
||||
return reply && reply->error() == QNetworkReply::OperationCanceledError; |
||||
} |
||||
|
||||
void HttpRequest::sendRequest(const QString &requestURL, const HttpRequest::Method method) { |
||||
if (active()) { |
||||
qDebug() << "HttpRequest is active"; |
||||
return; |
||||
} |
||||
QString token; |
||||
if (create_jwt) { |
||||
token = CommaApi::create_jwt(); |
||||
} else { |
||||
QString token_json = QString::fromStdString(util::read_file(util::getenv("HOME") + "/.comma/auth.json")); |
||||
QJsonDocument json_d = QJsonDocument::fromJson(token_json.toUtf8()); |
||||
token = json_d["access_token"].toString(); |
||||
} |
||||
|
||||
QNetworkRequest request; |
||||
request.setUrl(QUrl(requestURL)); |
||||
request.setRawHeader("User-Agent", getUserAgent().toUtf8()); |
||||
|
||||
if (!token.isEmpty()) { |
||||
request.setRawHeader(QByteArray("Authorization"), ("JWT " + token).toUtf8()); |
||||
} |
||||
|
||||
if (method == HttpRequest::Method::GET) { |
||||
reply = nam()->get(request); |
||||
} else if (method == HttpRequest::Method::DELETE) { |
||||
reply = nam()->deleteResource(request); |
||||
} |
||||
|
||||
networkTimer->start(); |
||||
connect(reply, &QNetworkReply::finished, this, &HttpRequest::requestFinished); |
||||
} |
||||
|
||||
void HttpRequest::requestTimeout() { |
||||
reply->abort(); |
||||
} |
||||
|
||||
void HttpRequest::requestFinished() { |
||||
networkTimer->stop(); |
||||
|
||||
if (reply->error() == QNetworkReply::NoError) { |
||||
emit requestDone(reply->readAll(), true, reply->error()); |
||||
} else { |
||||
QString error; |
||||
if (reply->error() == QNetworkReply::OperationCanceledError) { |
||||
nam()->clearAccessCache(); |
||||
nam()->clearConnectionCache(); |
||||
error = "Request timed out"; |
||||
} else { |
||||
error = reply->errorString(); |
||||
} |
||||
emit requestDone(error, false, reply->error()); |
||||
} |
||||
|
||||
reply->deleteLater(); |
||||
reply = nullptr; |
||||
} |
||||
|
||||
QNetworkAccessManager *HttpRequest::nam() { |
||||
static QNetworkAccessManager *networkAccessManager = new QNetworkAccessManager(qApp); |
||||
return networkAccessManager; |
||||
} |
||||
@ -0,0 +1,47 @@ |
||||
#pragma once |
||||
|
||||
#include <QJsonObject> |
||||
#include <QNetworkReply> |
||||
#include <QString> |
||||
#include <QTimer> |
||||
|
||||
#include "common/util.h" |
||||
|
||||
namespace CommaApi { |
||||
|
||||
const QString BASE_URL = util::getenv("API_HOST", "https://api.commadotai.com").c_str(); |
||||
QByteArray rsa_sign(const QByteArray &data); |
||||
QString create_jwt(const QJsonObject &payloads = {}, int expiry = 3600); |
||||
|
||||
} // namespace CommaApi
|
||||
|
||||
/**
|
||||
* Makes a request to the request endpoint. |
||||
*/ |
||||
|
||||
class HttpRequest : public QObject { |
||||
Q_OBJECT |
||||
|
||||
public: |
||||
enum class Method {GET, DELETE}; |
||||
|
||||
explicit HttpRequest(QObject* parent, bool create_jwt = true, int timeout = 20000); |
||||
void sendRequest(const QString &requestURL, const Method method = Method::GET); |
||||
bool active() const; |
||||
bool timeout() const; |
||||
|
||||
signals: |
||||
void requestDone(const QString &response, bool success, QNetworkReply::NetworkError error); |
||||
|
||||
protected: |
||||
QNetworkReply *reply = nullptr; |
||||
|
||||
private: |
||||
static QNetworkAccessManager *nam(); |
||||
QTimer *networkTimer = nullptr; |
||||
bool create_jwt; |
||||
|
||||
private slots: |
||||
void requestTimeout(); |
||||
void requestFinished(); |
||||
}; |
||||
@ -0,0 +1,29 @@ |
||||
#include "tools/cabana/utils/elidedlabel.h" |
||||
#include <QPainter> |
||||
#include <QStyleOption> |
||||
|
||||
ElidedLabel::ElidedLabel(QWidget *parent) : ElidedLabel({}, parent) {} |
||||
|
||||
ElidedLabel::ElidedLabel(const QString &text, QWidget *parent) : QLabel(text.trimmed(), parent) { |
||||
setSizePolicy(QSizePolicy::Preferred, QSizePolicy::Preferred); |
||||
setMinimumWidth(1); |
||||
} |
||||
|
||||
void ElidedLabel::resizeEvent(QResizeEvent* event) { |
||||
QLabel::resizeEvent(event); |
||||
lastText_ = elidedText_ = ""; |
||||
} |
||||
|
||||
void ElidedLabel::paintEvent(QPaintEvent *event) { |
||||
const QString curText = text(); |
||||
if (curText != lastText_) { |
||||
elidedText_ = fontMetrics().elidedText(curText, Qt::ElideRight, contentsRect().width()); |
||||
lastText_ = curText; |
||||
} |
||||
|
||||
QPainter painter(this); |
||||
drawFrame(&painter); |
||||
QStyleOption opt; |
||||
opt.initFrom(this); |
||||
style()->drawItemText(&painter, contentsRect(), alignment(), opt.palette, isEnabled(), elidedText_, foregroundRole()); |
||||
} |
||||
@ -0,0 +1,25 @@ |
||||
#pragma once |
||||
|
||||
#include <QLabel> |
||||
#include <QMouseEvent> |
||||
|
||||
class ElidedLabel : public QLabel { |
||||
Q_OBJECT |
||||
|
||||
public: |
||||
explicit ElidedLabel(QWidget *parent = 0); |
||||
explicit ElidedLabel(const QString &text, QWidget *parent = 0); |
||||
|
||||
signals: |
||||
void clicked(); |
||||
|
||||
protected: |
||||
void paintEvent(QPaintEvent *event) override; |
||||
void resizeEvent(QResizeEvent* event) override; |
||||
void mouseReleaseEvent(QMouseEvent *event) override { |
||||
if (rect().contains(event->pos())) { |
||||
emit clicked(); |
||||
} |
||||
} |
||||
QString lastText_, elidedText_; |
||||
}; |
||||
Loading…
Reference in new issue