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