fix: added user doc + tech doc. minor fixes
This commit is contained in:
145
README.md
Normal file
145
README.md
Normal file
@@ -0,0 +1,145 @@
|
|||||||
|
# LicenseManager — пользовательская документация
|
||||||
|
|
||||||
|
## Назначение
|
||||||
|
LicenseManager — настольное приложение на Qt5 для ведения базы клиентов и выпуска файлов лицензий на основе аппаратного идентификатора.
|
||||||
|
|
||||||
|
## Возможности
|
||||||
|
- хранение клиентов в локальной базе SQLite;
|
||||||
|
- добавление, редактирование и удаление записей;
|
||||||
|
- импорт данных клиента из JSON-файла (drag-and-drop или выбор файла);
|
||||||
|
- выдача файла лицензии по выбранной записи;
|
||||||
|
- отображение статуса базы и информации о сборке.
|
||||||
|
|
||||||
|
## Требования
|
||||||
|
- ОС: Linux/Windows/macOS (Qt5);
|
||||||
|
- зависимости при сборке: Qt5 (Core, Gui, Widgets, Sql), OpenSSL;
|
||||||
|
- для SQLite: системный драйвер Qt `QSQLITE` (например, пакет `libqt5sql5-sqlite` в Debian/Ubuntu).
|
||||||
|
|
||||||
|
## Сборка из исходников
|
||||||
|
```bash
|
||||||
|
cmake -S . -B build
|
||||||
|
cmake --build build -j
|
||||||
|
./build/LicenseManager
|
||||||
|
```
|
||||||
|
|
||||||
|
## Установка как .deb (опционально)
|
||||||
|
```bash
|
||||||
|
./deb/build.sh /path/to/LicenseManager
|
||||||
|
sudo dpkg -i licensemanager_*.deb
|
||||||
|
```
|
||||||
|
|
||||||
|
## Первое использование и база данных
|
||||||
|
- Приложение хранит данные в `./db.sqlite` относительно текущей рабочей директории.
|
||||||
|
- При первом запуске, если структура базы отсутствует, приложение предложит создать базу.
|
||||||
|
- Если нужен фиксированный путь, запускайте приложение из нужной папки или измените `DB_PATH` в коде.
|
||||||
|
|
||||||
|
## Интерфейс
|
||||||
|
### Таблица клиентов
|
||||||
|
Столбцы:
|
||||||
|
- Фамилия, Имя, Отчество
|
||||||
|
- Email, Телефон
|
||||||
|
- Компания, Город
|
||||||
|
- Срок лицензии (если пусто — отображается `Permanent`)
|
||||||
|
- Комментарий
|
||||||
|
|
||||||
|
### Панель инструментов
|
||||||
|
- Add client — добавить запись;
|
||||||
|
- Edit client — редактировать выбранную запись;
|
||||||
|
- Delete client — удалить выбранные записи;
|
||||||
|
- Get info — информация о сборке.
|
||||||
|
|
||||||
|
### Контекстное меню таблицы
|
||||||
|
- Reload table — обновить список;
|
||||||
|
- Add/Edit/Delete client — действия с записями;
|
||||||
|
- Get license file — сформировать файл лицензии.
|
||||||
|
|
||||||
|
### Иконки действий
|
||||||
|
| Действие | Иконка | Файл |
|
||||||
|
| --- | --- | --- |
|
||||||
|
| App icon | <img src="deps/icon.png" width="28" height="28" alt="App"> | `deps/icon.png` |
|
||||||
|
| Reload table | <img src="deps/reload.png" width="28" height="28" alt="Reload"> | `deps/reload.png` |
|
||||||
|
| Add client | <img src="deps/add.png" width="28" height="28" alt="Add"> | `deps/add.png` |
|
||||||
|
| Edit client | <img src="deps/edit.png" width="28" height="28" alt="Edit"> | `deps/edit.png` |
|
||||||
|
| Delete client | <img src="deps/delete.png" width="28" height="28" alt="Delete"> | `deps/delete.png` |
|
||||||
|
| Get info | <img src="deps/info.png" width="28" height="28" alt="Info"> | `deps/info.png` |
|
||||||
|
| Get license file | <img src="deps/getLicenseFile.png" width="28" height="28" alt="License file"> | `deps/getLicenseFile.png` |
|
||||||
|
| Drop files (автозаполнение) | <img src="deps/dropFiles.png" width="28" height="28" alt="Drop files"> | `deps/dropFiles.png` |
|
||||||
|
|
||||||
|
### Строка статуса
|
||||||
|
Показывает состояние базы (готова/ошибка/в работе).
|
||||||
|
|
||||||
|
## Работа с клиентами
|
||||||
|
### Добавление
|
||||||
|
1. Нажмите `Add client`.
|
||||||
|
2. Вкладка `File autofilled`:
|
||||||
|
- перетащите JSON-файл на область или нажмите и выберите файл;
|
||||||
|
- данные будут подставлены автоматически.
|
||||||
|
3. Вкладка `Main info`:
|
||||||
|
- заполните поля вручную.
|
||||||
|
4. Вкладка `License time`:
|
||||||
|
- включите `License is temporally`, чтобы выбрать дату окончания.
|
||||||
|
5. Нажмите `Save`.
|
||||||
|
|
||||||
|
Важно: поле `Hardware hash` можно редактировать только при добавлении записи.
|
||||||
|
|
||||||
|
### Редактирование
|
||||||
|
1. Выберите одну запись.
|
||||||
|
2. Нажмите `Edit client`.
|
||||||
|
3. Измените данные и нажмите `Save`.
|
||||||
|
|
||||||
|
Если нужно изменить `Hardware hash`, удалите запись и создайте новую.
|
||||||
|
|
||||||
|
### Удаление
|
||||||
|
Выберите записи и нажмите `Delete client` (или через контекстное меню). Подтвердите удаление.
|
||||||
|
|
||||||
|
### Обновление списка
|
||||||
|
Используйте `Reload table` для перечитывания данных из базы.
|
||||||
|
|
||||||
|
## Генерация файла лицензии
|
||||||
|
1. Выберите одну запись в таблице.
|
||||||
|
2. Нажмите `Get license file`.
|
||||||
|
3. Укажите место сохранения (по умолчанию — `Documents/` или `Home/`).
|
||||||
|
4. Файл сохраняется с расширением `.dat` (если расширение не указано, оно добавляется автоматически).
|
||||||
|
|
||||||
|
Внутри файла содержится зашифрованный (RSA) CBOR/JSON с полями:
|
||||||
|
- `machineId` — аппаратный идентификатор (`hardwareHash`);
|
||||||
|
- `validUntil` — дата окончания лицензии в ISO-формате (опционально).
|
||||||
|
|
||||||
|
## Формат JSON для авто‑заполнения
|
||||||
|
Пример:
|
||||||
|
```json
|
||||||
|
{
|
||||||
|
"lastName": "Иванов",
|
||||||
|
"firstName": "Иван",
|
||||||
|
"patronymic": "Иванович",
|
||||||
|
"email": "ivan@example.com",
|
||||||
|
"phone": "+7(777)777-77-77",
|
||||||
|
"city": "Москва",
|
||||||
|
"yourCompany": "ООО Ромашка",
|
||||||
|
"hardwareHash": "abcdef0123456789"
|
||||||
|
}
|
||||||
|
```
|
||||||
|
Любое поле можно опустить — оно останется пустым. Для выдачи лицензии важно заполнить `hardwareHash`.
|
||||||
|
|
||||||
|
## Хранилище данных
|
||||||
|
SQLite-файл: `db.sqlite` в рабочей директории.
|
||||||
|
|
||||||
|
Таблица `clients`:
|
||||||
|
- `id` (INTEGER, PK, AUTOINCREMENT)
|
||||||
|
- `city`, `email`, `firstName`, `hardwareHash`, `lastName`, `patronymic`, `phone`, `yourCompany` (TEXT, NOT NULL)
|
||||||
|
- `licenseTime` (TEXT, nullable)
|
||||||
|
- `comment` (TEXT, nullable)
|
||||||
|
|
||||||
|
## Настройки
|
||||||
|
Размеры окон сохраняются через `QSettings` (организация `LicenseManager`).
|
||||||
|
Расположение конфигурации зависит от ОС (на Linux обычно `~/.config/LicenseManager/`).
|
||||||
|
|
||||||
|
## Технический обзор (для сопровождения)
|
||||||
|
- Стек: C++20, Qt5 Widgets/Sql, OpenSSL, SQLite.
|
||||||
|
- Основные модули:
|
||||||
|
- `MainWidget` — главное окно, таблица, меню, выдача лицензии.
|
||||||
|
- `LicenseModel` — модель таблицы и работа с SQLite (в асинхронных задачах).
|
||||||
|
- `EditClientDialog` — диалог добавления/редактирования, импорт JSON.
|
||||||
|
- `LicenseGenerator`/`Crypt` — утилиты шифрования и генерации файла (часть функций не привязана к UI).
|
||||||
|
- Ресурсы Qt (`deps.qrc`) включают иконки, `tables.ddl` для инициализации БД и приватный ключ `Key`.
|
||||||
|
- Для смены ключа шифрования замените `deps/Key` и пересоберите проект.
|
||||||
@@ -1,22 +1,54 @@
|
|||||||
#ifndef LICENSEMANAGER_DROPFILEWIDGET_H
|
#ifndef LICENSEMANAGER_DROPFILEWIDGET_H
|
||||||
#define LICENSEMANAGER_DROPFILEWIDGET_H
|
#define LICENSEMANAGER_DROPFILEWIDGET_H
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @file DropFileWidget.h
|
||||||
|
* @brief Виджет для перетаскивания файлов.
|
||||||
|
*
|
||||||
|
* @details
|
||||||
|
* Предоставляет drag-and-drop и выбор файлов через диалог.
|
||||||
|
*/
|
||||||
|
|
||||||
// Qt
|
// Qt
|
||||||
#include <QWidget>
|
#include <QWidget>
|
||||||
class QLabel;
|
class QLabel;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @brief Виджет для приёма файлов через drag-and-drop.
|
||||||
|
*/
|
||||||
class DropFileWidget : public QWidget {
|
class DropFileWidget : public QWidget {
|
||||||
Q_OBJECT
|
Q_OBJECT
|
||||||
public:
|
public:
|
||||||
|
/**
|
||||||
|
* @brief Создаёт виджет.
|
||||||
|
*/
|
||||||
explicit DropFileWidget(QWidget *parent = nullptr);
|
explicit DropFileWidget(QWidget *parent = nullptr);
|
||||||
signals:
|
signals:
|
||||||
|
/**
|
||||||
|
* @brief Сигнал о том, что файлы выбраны или перетащены.
|
||||||
|
*/
|
||||||
void filesDropped(const QStringList &paths);
|
void filesDropped(const QStringList &paths);
|
||||||
protected:
|
protected:
|
||||||
|
/**
|
||||||
|
* @brief Обрабатывает начало перетаскивания.
|
||||||
|
*/
|
||||||
void dragEnterEvent(QDragEnterEvent *e) override;
|
void dragEnterEvent(QDragEnterEvent *e) override;
|
||||||
|
/**
|
||||||
|
* @brief Обрабатывает перемещение в области виджета.
|
||||||
|
*/
|
||||||
void dragMoveEvent(QDragMoveEvent *e) override;
|
void dragMoveEvent(QDragMoveEvent *e) override;
|
||||||
|
/**
|
||||||
|
* @brief Обрабатывает сброс файлов.
|
||||||
|
*/
|
||||||
void dropEvent(QDropEvent *e) override;
|
void dropEvent(QDropEvent *e) override;
|
||||||
|
/**
|
||||||
|
* @brief Обрабатывает клик для открытия диалога выбора.
|
||||||
|
*/
|
||||||
void mousePressEvent(QMouseEvent *e) override;
|
void mousePressEvent(QMouseEvent *e) override;
|
||||||
private:
|
private:
|
||||||
|
/**
|
||||||
|
* @brief Открывает диалог выбора файлов.
|
||||||
|
*/
|
||||||
void selectFiles();
|
void selectFiles();
|
||||||
};
|
};
|
||||||
|
|
||||||
|
|||||||
@@ -1,6 +1,15 @@
|
|||||||
#ifndef LICENSEMANAGER_EDITCLIENTDIALOG_H
|
#ifndef LICENSEMANAGER_EDITCLIENTDIALOG_H
|
||||||
#define LICENSEMANAGER_EDITCLIENTDIALOG_H
|
#define LICENSEMANAGER_EDITCLIENTDIALOG_H
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @file EditClientDialog.h
|
||||||
|
* @brief Диалог добавления/редактирования клиента.
|
||||||
|
*
|
||||||
|
* @details
|
||||||
|
* Позволяет заполнять данные вручную или автозаполнением из файла,
|
||||||
|
* а также задавать срок действия лицензии.
|
||||||
|
*/
|
||||||
|
|
||||||
// Qt
|
// Qt
|
||||||
#include <QDate>
|
#include <QDate>
|
||||||
#include <QDebug>
|
#include <QDebug>
|
||||||
@@ -18,52 +27,91 @@ class QTextEdit;
|
|||||||
// Self
|
// Self
|
||||||
class LicenseModel;
|
class LicenseModel;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @brief Диалог редактирования данных клиента.
|
||||||
|
*/
|
||||||
class EditClientDialog : public QDialog
|
class EditClientDialog : public QDialog
|
||||||
{
|
{
|
||||||
Q_OBJECT
|
Q_OBJECT
|
||||||
public:
|
public:
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @brief Режим работы диалога.
|
||||||
|
*/
|
||||||
enum class Type
|
enum class Type
|
||||||
{
|
{
|
||||||
None = 0,
|
None = 0, //!< Режим не выбран.
|
||||||
Add,
|
Add, //!< Добавление нового клиента.
|
||||||
Edit,
|
Edit, //!< Редактирование существующего клиента.
|
||||||
};
|
};
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @brief Создаёт диалог для заданной модели.
|
||||||
|
*/
|
||||||
EditClientDialog(LicenseModel* model, QWidget *parent = nullptr);
|
EditClientDialog(LicenseModel* model, QWidget *parent = nullptr);
|
||||||
|
/**
|
||||||
|
* @brief Деструктор.
|
||||||
|
*/
|
||||||
~EditClientDialog();
|
~EditClientDialog();
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @brief Устанавливает режим диалога.
|
||||||
|
*/
|
||||||
void setType(Type type);
|
void setType(Type type);
|
||||||
|
/**
|
||||||
|
* @brief Возвращает текущий режим диалога.
|
||||||
|
*/
|
||||||
Type getType() const;
|
Type getType() const;
|
||||||
|
/**
|
||||||
|
* @brief Формирует структуру клиента из полей формы.
|
||||||
|
*/
|
||||||
LicenseModel::LicenseItem getLicenseItem() const;
|
LicenseModel::LicenseItem getLicenseItem() const;
|
||||||
|
/**
|
||||||
|
* @brief Очищает поля ввода.
|
||||||
|
*/
|
||||||
void clear();
|
void clear();
|
||||||
|
/**
|
||||||
|
* @brief Заполняет поля данными клиента по индексу строки.
|
||||||
|
*/
|
||||||
void setClientInfo(int index);
|
void setClientInfo(int index);
|
||||||
private slots:
|
private slots:
|
||||||
|
/**
|
||||||
|
* @brief Обрабатывает выбор/перетаскивание файла с конфигурацией.
|
||||||
|
*/
|
||||||
void onFilesChanged(const QStringList &paths);
|
void onFilesChanged(const QStringList &paths);
|
||||||
private:
|
private:
|
||||||
|
/**
|
||||||
|
* @brief Загружает настройки окна (размер).
|
||||||
|
*/
|
||||||
void loadSettings();
|
void loadSettings();
|
||||||
|
/**
|
||||||
|
* @brief Сохраняет настройки окна (размер).
|
||||||
|
*/
|
||||||
void saveSettings();
|
void saveSettings();
|
||||||
|
/**
|
||||||
|
* @brief Создаёт стандартную подпись для поля ввода.
|
||||||
|
*/
|
||||||
QLabel* makeLabel(const QString &text);
|
QLabel* makeLabel(const QString &text);
|
||||||
private:
|
private:
|
||||||
LicenseModel* m_model{nullptr};
|
LicenseModel* m_model{nullptr}; //!< Модель данных клиентов.
|
||||||
QTabWidget* m_tabWidget{nullptr};
|
QTabWidget* m_tabWidget{nullptr}; //!< Набор вкладок диалога.
|
||||||
QLabel* m_configPathLabel{nullptr};
|
QLabel* m_configPathLabel{nullptr}; //!< Отображение пути к файлу.
|
||||||
QWidget* m_manualWidget{nullptr};
|
QWidget* m_manualWidget{nullptr}; //!< Вкладка ручного ввода.
|
||||||
QLineEdit* m_firstNameLineEdit{nullptr}; //!< Поле ввода имени пользователя.
|
QLineEdit* m_firstNameLineEdit{nullptr}; //!< Поле ввода имени пользователя.
|
||||||
QLineEdit* m_lastNameLineEdit{nullptr}; //!< Поле ввода фамилии пользователя.
|
QLineEdit* m_lastNameLineEdit{nullptr}; //!< Поле ввода фамилии пользователя.
|
||||||
QLineEdit* m_patronymicLineEdit{nullptr}; //!< Поле ввода отчества пользователя.
|
QLineEdit* m_patronymicLineEdit{nullptr}; //!< Поле ввода отчества пользователя.
|
||||||
QLineEdit* m_emailLineEdit{nullptr}; //!< Поле ввода электронной почты.
|
QLineEdit* m_emailLineEdit{nullptr}; //!< Поле ввода электронной почты.
|
||||||
QLineEdit* m_phoneLineEdit{nullptr}; //!< Поле ввода номера телефона.
|
QLineEdit* m_phoneLineEdit{nullptr}; //!< Поле ввода номера телефона.
|
||||||
QLineEdit* m_yourCompanyNameTextEdit{nullptr}; //!< Поле ввода названия компании пользователя.
|
QLineEdit* m_yourCompanyNameTextEdit{nullptr}; //!< Поле ввода названия компании пользователя.
|
||||||
QLineEdit* m_sellerNameTextEdit{nullptr}; //!< Поле ввода названия продавца (если используется).
|
QLineEdit* m_sellerNameTextEdit{nullptr}; //!< Поле ввода названия продавца (если используется).
|
||||||
QLineEdit* m_cityLineEdit{nullptr}; //!< Поле ввода города.
|
QLineEdit* m_cityLineEdit{nullptr}; //!< Поле ввода города.
|
||||||
QLineEdit* m_hardwareHashLineEdit{nullptr}; //!< Поле для отображения хеша оборудования.
|
QLineEdit* m_hardwareHashLineEdit{nullptr}; //!< Поле для отображения хеша оборудования.
|
||||||
QCheckBox* m_useTemporaryLicenseCheckBox{nullptr};//!<
|
QCheckBox* m_useTemporaryLicenseCheckBox{nullptr}; //!< Признак временной лицензии.
|
||||||
QCalendarWidget* m_calendarWidget{nullptr}; //!<
|
QCalendarWidget* m_calendarWidget{nullptr}; //!< Календарь для выбора даты.
|
||||||
QTextEdit* m_commentTextEdit{nullptr}; //!<
|
QTextEdit* m_commentTextEdit{nullptr}; //!< Поле ввода комментария.
|
||||||
QPushButton* m_saveButton{nullptr}; //!<
|
QPushButton* m_saveButton{nullptr}; //!< Кнопка сохранения.
|
||||||
QString m_filesPath;
|
QString m_filesPath; //!< Последний выбранный файл.
|
||||||
Type m_type = Type::None;
|
Type m_type = Type::None; //!< Текущий режим диалога.
|
||||||
};
|
};
|
||||||
|
|
||||||
#endif //LICENSEMANAGER_EDITCLIENTDIALOG_H
|
#endif //LICENSEMANAGER_EDITCLIENTDIALOG_H
|
||||||
|
|||||||
@@ -1,53 +0,0 @@
|
|||||||
#include "LicenseGenerator.h"
|
|
||||||
|
|
||||||
#include "crypt.h"
|
|
||||||
#include "Util.h"
|
|
||||||
|
|
||||||
LicenseGenerator::Result LicenseGenerator::createEncryptedFile(const QString& outputFile,
|
|
||||||
const QString& sshTarget,
|
|
||||||
const QString& password,
|
|
||||||
const RunSshFn& runSsh)
|
|
||||||
{
|
|
||||||
Result result;
|
|
||||||
|
|
||||||
QByteArray text;
|
|
||||||
if (!runSsh("ssh",
|
|
||||||
QStringList() << sshTarget << "sed -rn 's/Serial\\t\\t: (.+)/\\1/p' /proc/cpuinfo",
|
|
||||||
password,
|
|
||||||
"get cpu id",
|
|
||||||
&text))
|
|
||||||
{
|
|
||||||
result.error = Error::SshFailed;
|
|
||||||
return result;
|
|
||||||
}
|
|
||||||
|
|
||||||
result.cpuId = text;
|
|
||||||
|
|
||||||
if (text.isEmpty())
|
|
||||||
{
|
|
||||||
result.error = Error::CpuIdEmpty;
|
|
||||||
return result;
|
|
||||||
}
|
|
||||||
|
|
||||||
if ((text.length() != 16 && text.length() != 20) || findFirstNotOf(text, "0123456789abcdef") >= 0)
|
|
||||||
{
|
|
||||||
result.error = Error::CpuIdInvalid;
|
|
||||||
return result;
|
|
||||||
}
|
|
||||||
|
|
||||||
QByteArray encrypted = Crypt::rsaEncrypt(text, fileReadAll(":/deps/Key"));
|
|
||||||
if (encrypted.isEmpty())
|
|
||||||
{
|
|
||||||
result.error = Error::EncryptFailed;
|
|
||||||
return result;
|
|
||||||
}
|
|
||||||
|
|
||||||
if (!fileWriteAll(outputFile, encrypted))
|
|
||||||
{
|
|
||||||
result.error = Error::WriteFailed;
|
|
||||||
return result;
|
|
||||||
}
|
|
||||||
|
|
||||||
result.error = Error::Ok;
|
|
||||||
return result;
|
|
||||||
}
|
|
||||||
@@ -1,31 +0,0 @@
|
|||||||
#pragma once
|
|
||||||
|
|
||||||
#include <functional>
|
|
||||||
#include <QByteArray>
|
|
||||||
#include <QString>
|
|
||||||
#include <QStringList>
|
|
||||||
|
|
||||||
class LicenseGenerator
|
|
||||||
{
|
|
||||||
public:
|
|
||||||
enum class Error {
|
|
||||||
Ok,
|
|
||||||
SshFailed,
|
|
||||||
CpuIdEmpty,
|
|
||||||
CpuIdInvalid,
|
|
||||||
EncryptFailed,
|
|
||||||
WriteFailed
|
|
||||||
};
|
|
||||||
|
|
||||||
struct Result {
|
|
||||||
Error error = Error::Ok;
|
|
||||||
QByteArray cpuId;
|
|
||||||
};
|
|
||||||
|
|
||||||
using RunSshFn = std::function<bool(const QByteArray&, const QStringList&, const QString&, const QString&, QByteArray*)>;
|
|
||||||
|
|
||||||
static Result createEncryptedFile(const QString& outputFile,
|
|
||||||
const QString& sshTarget,
|
|
||||||
const QString& password,
|
|
||||||
const RunSshFn& runSsh);
|
|
||||||
};
|
|
||||||
@@ -1,13 +1,8 @@
|
|||||||
#include "Util.h"
|
#include "Util.h"
|
||||||
|
|
||||||
#include <random>
|
#include <QCborValue>
|
||||||
#include <optional>
|
#include <QFile>
|
||||||
#include <QRegularExpression>
|
#include <QJsonValue>
|
||||||
#include <QElapsedTimer>
|
|
||||||
#include <QCryptographicHash>
|
|
||||||
#include <QCoreApplication>
|
|
||||||
#include <QCborMap>
|
|
||||||
#include <QCborArray>
|
|
||||||
|
|
||||||
QByteArray fileReadAll(const QString& path)
|
QByteArray fileReadAll(const QString& path)
|
||||||
{
|
{
|
||||||
@@ -18,288 +13,6 @@ QByteArray fileReadAll(const QString& path)
|
|||||||
return buf;
|
return buf;
|
||||||
}
|
}
|
||||||
|
|
||||||
bool fileWriteAll(const QString& path, const QByteArray& data, int bufOffset)
|
|
||||||
{
|
|
||||||
QFile f(path);
|
|
||||||
if (!f.exists()) {
|
|
||||||
QFileInfo fi(f);
|
|
||||||
QDir d;
|
|
||||||
d.mkpath(fi.dir().path());
|
|
||||||
}
|
|
||||||
if (!f.open(QIODevice::WriteOnly | QIODevice::Truncate))
|
|
||||||
return false;
|
|
||||||
int size = data.size() - bufOffset;
|
|
||||||
int bytesWritten = f.write(data.constData() + bufOffset, size);
|
|
||||||
f.close();
|
|
||||||
return bytesWritten == size;
|
|
||||||
}
|
|
||||||
|
|
||||||
bool splitAddr(const QString& addrOrNetwork, bool isAddress, uint& addr, uint* mask)
|
|
||||||
{
|
|
||||||
QString s = "^(\\d{1,3})\\.(\\d{1,3})\\.(\\d{1,3})\\.(\\d{1,3})";
|
|
||||||
if (mask)
|
|
||||||
s += "/(\\d\\d?)";
|
|
||||||
s += '$';
|
|
||||||
QRegularExpression re(s, QRegularExpression::CaseInsensitiveOption);
|
|
||||||
QRegularExpressionMatch m = re.match(addrOrNetwork);
|
|
||||||
if (!m.hasMatch()) return false;
|
|
||||||
|
|
||||||
uint a = m.captured(1).toUInt();
|
|
||||||
if (a == 0 || a > 255) return false;
|
|
||||||
uint b = m.captured(2).toUInt();
|
|
||||||
if (b > 255) return false;
|
|
||||||
uint c = m.captured(3).toUInt();
|
|
||||||
if (c > 255) return false;
|
|
||||||
uint d = m.captured(4).toUInt();
|
|
||||||
if ((isAddress && d == 0) || d >= 255) return false;
|
|
||||||
if (mask)
|
|
||||||
{
|
|
||||||
*mask = m.captured(5).toInt();
|
|
||||||
if (*mask == 0 || *mask > 30) return false;
|
|
||||||
}
|
|
||||||
addr = (a << 24) | (b << 16) | (c << 8) | d;
|
|
||||||
return true;
|
|
||||||
}
|
|
||||||
|
|
||||||
bool ipOk(const QString& addr, bool netmask)
|
|
||||||
{
|
|
||||||
uint a;
|
|
||||||
if (netmask)
|
|
||||||
{
|
|
||||||
uint m;
|
|
||||||
return splitAddr(addr, true, a, &m);
|
|
||||||
}
|
|
||||||
return splitAddr(addr, true, a);
|
|
||||||
}
|
|
||||||
|
|
||||||
QString makeHash(const QString& password)
|
|
||||||
{
|
|
||||||
std::random_device rd;
|
|
||||||
QString result;
|
|
||||||
while(result.length() < 16) // salt
|
|
||||||
{
|
|
||||||
uint a = rd() % 52;
|
|
||||||
result += QChar(a < 26 ? ('a' + a) : ('A' + a - 26));
|
|
||||||
}
|
|
||||||
QCryptographicHash hash(QCryptographicHash::Sha1);
|
|
||||||
hash.addData(result.toLatin1());
|
|
||||||
hash.addData(password.toUtf8());
|
|
||||||
(result += '$') += hash.result().toHex();
|
|
||||||
return result;
|
|
||||||
}
|
|
||||||
|
|
||||||
bool testPassword(const QByteArray& password, const QByteArray& passwordHash)
|
|
||||||
{
|
|
||||||
int j = passwordHash.indexOf('$');
|
|
||||||
if (j < 0) return false;
|
|
||||||
QCryptographicHash hash(QCryptographicHash::Sha1);
|
|
||||||
hash.addData(passwordHash.mid(0, j));
|
|
||||||
hash.addData(password);
|
|
||||||
const QByteArray h = hash.result();
|
|
||||||
return std::equal(h.constData(), h.constData() + h.size(), passwordHash.constData() + j + 1);
|
|
||||||
}
|
|
||||||
|
|
||||||
int findFirstNotOf(const QString& s, const char* chars, int pos, int n)
|
|
||||||
{
|
|
||||||
if (n >= 0) n += pos;
|
|
||||||
else n = s.length();
|
|
||||||
|
|
||||||
for(; pos < n; ++pos)
|
|
||||||
for(const char* p = chars;; p++)
|
|
||||||
{
|
|
||||||
if (!*p) return pos;
|
|
||||||
if (s[pos] == char16_t(*p)) break;
|
|
||||||
}
|
|
||||||
return -1;
|
|
||||||
}
|
|
||||||
|
|
||||||
int findFirstNotOf(const QString& s, QChar c, int pos, int n)
|
|
||||||
{
|
|
||||||
if (n >= 0) n += pos;
|
|
||||||
else n = s.length();
|
|
||||||
|
|
||||||
for(; pos < n; ++pos)
|
|
||||||
{
|
|
||||||
if (s[pos] != c)
|
|
||||||
return pos;
|
|
||||||
}
|
|
||||||
return -1;
|
|
||||||
}
|
|
||||||
|
|
||||||
QByteArray& trimLeft(QByteArray& s, char c)
|
|
||||||
{
|
|
||||||
int i = 0, length = s.length();
|
|
||||||
for(; i < length; ++i)
|
|
||||||
if (s.at(i) != c)
|
|
||||||
break;
|
|
||||||
s.remove(0, i);
|
|
||||||
return s;
|
|
||||||
}
|
|
||||||
|
|
||||||
QByteArray& trimRight(QByteArray& s, char c)
|
|
||||||
{
|
|
||||||
if (!s.isEmpty())
|
|
||||||
{
|
|
||||||
int i = s.length() - 1;
|
|
||||||
for(; i >= 0; --i)
|
|
||||||
if (s.at(i) != c)
|
|
||||||
break;
|
|
||||||
i++;
|
|
||||||
s.resize(i);
|
|
||||||
}
|
|
||||||
return s;
|
|
||||||
}
|
|
||||||
|
|
||||||
QByteArray& trimLeft(QByteArray& s, const char* chars) // ascii only
|
|
||||||
{
|
|
||||||
int i = 0, length = s.length();
|
|
||||||
for(; i < length; ++i)
|
|
||||||
{
|
|
||||||
QChar c = s.at(i);
|
|
||||||
for(const char* p = chars;; p++)
|
|
||||||
{
|
|
||||||
if (!*p) goto breakfor;
|
|
||||||
if (char16_t(*p) == c) break;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
breakfor:
|
|
||||||
s.remove(0, i);
|
|
||||||
return s;
|
|
||||||
}
|
|
||||||
|
|
||||||
QByteArray& trimRight(QByteArray& s, const char* chars) // ascii only
|
|
||||||
{
|
|
||||||
if (!s.isEmpty())
|
|
||||||
{
|
|
||||||
int i = s.length() - 1;
|
|
||||||
for(; i >= 0; --i)
|
|
||||||
{
|
|
||||||
QChar c = s.at(i);
|
|
||||||
for(const char* p = chars;; p++)
|
|
||||||
{
|
|
||||||
if (!*p) goto breakfor;
|
|
||||||
if (char16_t(*p) == c) break;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
breakfor:
|
|
||||||
i++;
|
|
||||||
s.resize(i);
|
|
||||||
}
|
|
||||||
return s;
|
|
||||||
}
|
|
||||||
|
|
||||||
QString& trimLeft(QString& s, QChar c)
|
|
||||||
{
|
|
||||||
int i = 0, length = s.length();
|
|
||||||
for(; i < length; ++i)
|
|
||||||
if (s.at(i) != c)
|
|
||||||
break;
|
|
||||||
s.remove(0, i);
|
|
||||||
return s;
|
|
||||||
}
|
|
||||||
|
|
||||||
QString& trimRight(QString& s, QChar c)
|
|
||||||
{
|
|
||||||
if (!s.isEmpty())
|
|
||||||
{
|
|
||||||
int i = s.length() - 1;
|
|
||||||
for(; i >= 0; --i)
|
|
||||||
if (s.at(i) != c)
|
|
||||||
break;
|
|
||||||
i++;
|
|
||||||
s.resize(i);
|
|
||||||
}
|
|
||||||
return s;
|
|
||||||
}
|
|
||||||
|
|
||||||
QString& append(QString& s, const QVector<int>& v, QChar sep)
|
|
||||||
{
|
|
||||||
if (!v.isEmpty())
|
|
||||||
{
|
|
||||||
for(int i = 0; i < v.size(); ++i)
|
|
||||||
(s += QString::number(v[i])) += sep;
|
|
||||||
s.resize(s.size() - 1);
|
|
||||||
}
|
|
||||||
return s;
|
|
||||||
}
|
|
||||||
|
|
||||||
QVector<int> splitToInts(const QString& s, QChar sep)
|
|
||||||
{
|
|
||||||
QVector<int> r;
|
|
||||||
if (!s.isEmpty())
|
|
||||||
for(const QString& ss : s.split(sep))
|
|
||||||
r << ss.toInt();
|
|
||||||
return r;
|
|
||||||
}
|
|
||||||
|
|
||||||
QString getPath(const QString& path)
|
|
||||||
{
|
|
||||||
int i = path.length();
|
|
||||||
if (i == 0 || i == 1) return {};
|
|
||||||
--i;
|
|
||||||
if (path[i] == '/') --i;
|
|
||||||
i = path.lastIndexOf('/', i);
|
|
||||||
if (i == 0) return QString(1, '/');
|
|
||||||
if (i < 0) return QString();
|
|
||||||
return path.mid(0, i);
|
|
||||||
}
|
|
||||||
|
|
||||||
QString getFileName(const QString& path)
|
|
||||||
{
|
|
||||||
if (path.isEmpty()) return {};
|
|
||||||
int i = path.lastIndexOf('/');
|
|
||||||
return i >= 0 ? path.mid(i + 1) : path;
|
|
||||||
}
|
|
||||||
|
|
||||||
|
|
||||||
QJsonArray convertVectorToJsonArray(const QVector<bool> &vector)
|
|
||||||
{
|
|
||||||
QJsonArray resultArray;
|
|
||||||
for (const bool & value: vector){
|
|
||||||
resultArray.append(value);
|
|
||||||
}
|
|
||||||
return resultArray;
|
|
||||||
}
|
|
||||||
|
|
||||||
QVector<bool> convertJsonArrayToVector(const QJsonArray &array)
|
|
||||||
{
|
|
||||||
QVector<bool> vector;
|
|
||||||
for (int i = 0; i < array.count(); i++){
|
|
||||||
vector.append(array[i].toBool());
|
|
||||||
}
|
|
||||||
return vector;
|
|
||||||
}
|
|
||||||
|
|
||||||
|
|
||||||
QByteArray addSizeToByteArray(const QByteArray &arr)
|
|
||||||
{
|
|
||||||
QByteArray byteArray;
|
|
||||||
QDataStream stream(&byteArray, QIODevice::WriteOnly);
|
|
||||||
stream << arr.size();
|
|
||||||
return byteArray + arr;
|
|
||||||
}
|
|
||||||
|
|
||||||
int getSizeJsonInByteArr(const QByteArray &arr)
|
|
||||||
{
|
|
||||||
int size = -1;
|
|
||||||
if (arr.size() > 4) {
|
|
||||||
QDataStream ds(arr.mid(0, sizeof(int)));
|
|
||||||
ds >> size;
|
|
||||||
}
|
|
||||||
return size;
|
|
||||||
}
|
|
||||||
|
|
||||||
QString &append(QString &s, const QVector<QByteArray> &v, QChar sep)
|
|
||||||
{
|
|
||||||
if (!v.isEmpty())
|
|
||||||
{
|
|
||||||
for(int i = 0; i < v.size(); ++i)
|
|
||||||
(s += QString(v[i])) += sep;
|
|
||||||
s.resize(s.size() - 1);
|
|
||||||
}
|
|
||||||
return s;
|
|
||||||
}
|
|
||||||
|
|
||||||
QByteArray toCbor(const QJsonObject &jsonObject)
|
QByteArray toCbor(const QJsonObject &jsonObject)
|
||||||
{
|
{
|
||||||
QCborValue cborValue = QCborValue::fromJsonValue(QJsonValue(jsonObject));
|
QCborValue cborValue = QCborValue::fromJsonValue(QJsonValue(jsonObject));
|
||||||
@@ -321,90 +34,3 @@ QByteArray toCbor(const QJsonDocument &doc)
|
|||||||
}
|
}
|
||||||
return {}; // Return an empty QByteArray if the document is invalid
|
return {}; // Return an empty QByteArray if the document is invalid
|
||||||
}
|
}
|
||||||
|
|
||||||
QJsonDocument fromCbor(const QByteArray &cborData)
|
|
||||||
{
|
|
||||||
QCborValue cborValue = QCborValue::fromCbor(cborData);
|
|
||||||
|
|
||||||
if (cborValue.isMap()) {
|
|
||||||
return QJsonDocument(cborValue.toMap().toJsonObject());
|
|
||||||
} else if (cborValue.isArray()) {
|
|
||||||
return QJsonDocument(cborValue.toArray().toJsonArray());
|
|
||||||
}
|
|
||||||
|
|
||||||
// Return an empty QJsonDocument if the type is unsupported
|
|
||||||
return {};
|
|
||||||
}
|
|
||||||
|
|
||||||
|
|
||||||
QVector<int> arrayFromMask(uint8_t mask)
|
|
||||||
{
|
|
||||||
int idx=0;
|
|
||||||
QVector<int> ret;
|
|
||||||
while (mask) {
|
|
||||||
if(mask & 1) ret.append(idx);
|
|
||||||
mask>>=1;
|
|
||||||
idx++;
|
|
||||||
}
|
|
||||||
return ret;
|
|
||||||
}
|
|
||||||
|
|
||||||
|
|
||||||
QVector<int> intersectUnique(const QVector<int> &a, const QVector<int> &b) {
|
|
||||||
#if QT_VERSION >= QT_VERSION_CHECK(5, 14, 0)
|
|
||||||
QSet<int> sa(a.cbegin(), a.cend());
|
|
||||||
QSet<int> sb(b.cbegin(), b.cend());
|
|
||||||
#else
|
|
||||||
QSet<int> sa = QSet<int>::fromList(a.toList());
|
|
||||||
QSet<int> sb = QSet<int>::fromList(b.toList());
|
|
||||||
#endif
|
|
||||||
|
|
||||||
// In-place intersection; 'sa' becomes the result
|
|
||||||
sa.intersect(sb);
|
|
||||||
|
|
||||||
QVector<int> out;
|
|
||||||
out.reserve(sa.size());
|
|
||||||
for (int v : qAsConst(sa)) out.append(v);
|
|
||||||
|
|
||||||
// Optional: deterministic order
|
|
||||||
std::sort(out.begin(), out.end());
|
|
||||||
return out;
|
|
||||||
}
|
|
||||||
|
|
||||||
int popCount(const QVector<bool> &vec, bool value)
|
|
||||||
{
|
|
||||||
int count = 0;
|
|
||||||
for (bool v : vec) {
|
|
||||||
if (v == value)
|
|
||||||
++count;
|
|
||||||
}
|
|
||||||
return count;
|
|
||||||
}
|
|
||||||
|
|
||||||
QVector<bool> vectorOr(const QVector<bool> &v1, const QVector<bool> &v2)
|
|
||||||
{
|
|
||||||
Q_ASSERT(v1.size() == v2.size()); // sanity check
|
|
||||||
QVector<bool> v3(v1.size());
|
|
||||||
std::transform(v1.cbegin(), v1.cend(), v2.cbegin(), v3.begin(),
|
|
||||||
[](bool x, bool y){ return x || y; });
|
|
||||||
return v3;
|
|
||||||
}
|
|
||||||
|
|
||||||
QVector<bool> invert(const QVector<bool> &v)
|
|
||||||
{
|
|
||||||
QVector<bool> result(v.size());
|
|
||||||
std::transform(v.cbegin(), v.cend(), result.begin(),
|
|
||||||
[](bool b){ return !b; });
|
|
||||||
return result;
|
|
||||||
}
|
|
||||||
|
|
||||||
QString toString(const QVector<bool> &vec, bool value)
|
|
||||||
{
|
|
||||||
QStringList out;
|
|
||||||
for (int i = 0; i < vec.size(); ++i) {
|
|
||||||
if (vec[i] == value) {
|
|
||||||
out << QString::number(i + 1); // 1-based index
|
|
||||||
}
|
|
||||||
}
|
|
||||||
return out.join(", ");
|
|
||||||
}
|
|
||||||
|
|||||||
@@ -1,304 +1,29 @@
|
|||||||
#pragma once
|
#pragma once
|
||||||
#include <QFile>
|
/**
|
||||||
#include <QDateTime>
|
* @file Util.h
|
||||||
#include <QtNetwork/QTcpSocket>
|
* @brief Базовые вспомогательные функции проекта.
|
||||||
#include <vector>
|
*/
|
||||||
#include <QDir>
|
|
||||||
#include <QJsonDocument>
|
#include <QByteArray>
|
||||||
#include <QJsonArray>
|
#include <QJsonArray>
|
||||||
|
#include <QJsonDocument>
|
||||||
#include <QJsonObject>
|
#include <QJsonObject>
|
||||||
#include <QJsonValue>
|
#include <QString>
|
||||||
#include <QDataStream>
|
|
||||||
#include <deque>
|
|
||||||
#include <optional>
|
|
||||||
|
|
||||||
template <typename T>
|
|
||||||
class FifoContainer {
|
|
||||||
|
|
||||||
public:
|
|
||||||
explicit FifoContainer(std::size_t maxSize) : m_maxSize(maxSize) {}
|
|
||||||
void add(T value)
|
|
||||||
{
|
|
||||||
if (m_container.size() >= m_maxSize)
|
|
||||||
m_container.pop_front(); // Remove the oldest element (FIFO)
|
|
||||||
m_container.push_back(value); // Add the new element
|
|
||||||
}
|
|
||||||
std::optional<double> getAverage() const
|
|
||||||
{
|
|
||||||
if (m_container.size() < m_maxSize) {
|
|
||||||
return std::nullopt; // Indicate that the container is not yet full
|
|
||||||
}
|
|
||||||
// Use std::accumulate to calculate the sum of the elements
|
|
||||||
T sum = std::accumulate(m_container.begin(), m_container.end(), T(0));
|
|
||||||
return static_cast<double>(sum) / m_container.size();
|
|
||||||
}
|
|
||||||
void clear() { m_container.clear(); }
|
|
||||||
std::deque<T> &container() { return m_container; }
|
|
||||||
operator QString() const {
|
|
||||||
QStringList l;
|
|
||||||
for (int i = 0; i < m_container.size(); ++i) {
|
|
||||||
l << QString::number(m_container[i]);
|
|
||||||
}
|
|
||||||
return l.join(", ");
|
|
||||||
}
|
|
||||||
|
|
||||||
private:
|
|
||||||
std::size_t m_maxSize; // Maximum size of the m_container
|
|
||||||
std::deque<T> m_container; // FIFO container
|
|
||||||
};
|
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @brief Считывает файл целиком.
|
||||||
|
*/
|
||||||
QByteArray fileReadAll(const QString& path);
|
QByteArray fileReadAll(const QString& path);
|
||||||
inline QStringList fileReadAllLines(const QString& path)
|
|
||||||
{
|
|
||||||
#if QT_VERSION >= QT_VERSION_CHECK(5, 14, 0)
|
|
||||||
return QString(fileReadAll(path)).split('\n', Qt::SkipEmptyParts);
|
|
||||||
#else
|
|
||||||
return QString(fileReadAll(path)).split('\n', QString::SkipEmptyParts);
|
|
||||||
#endif
|
|
||||||
}
|
|
||||||
|
|
||||||
bool fileWriteAll(const QString& path, const QByteArray& data, int bufOffset = 0);
|
|
||||||
inline bool fileWriteAllLines(const QString& path, const QStringList& list) { return fileWriteAll(path, list.join('\n').toUtf8()); }
|
|
||||||
|
|
||||||
template <typename T, typename Cleanup = QScopedPointerDeleter<T>>
|
|
||||||
class Ptr : public QScopedPointer<T, Cleanup>
|
|
||||||
{
|
|
||||||
typedef QScopedPointer<T, Cleanup> base;
|
|
||||||
public:
|
|
||||||
using base::base;
|
|
||||||
operator T*() const { return base::data(); }
|
|
||||||
operator bool() const { return base::data() != 0; }
|
|
||||||
#if QT_VERSION < QT_VERSION_CHECK(5, 11, 0)
|
|
||||||
T* get() const { return base::data(); }
|
|
||||||
#endif
|
|
||||||
};
|
|
||||||
|
|
||||||
//inline bool isConnectingOrConnected(const QTcpSocket* s)
|
|
||||||
//{ return s->state() == QAbstractSocket::ConnectingState || s->state() == QAbstractSocket::ConnectedState; }
|
|
||||||
inline bool isConnected(const QTcpSocket* s) { return s->state() == QAbstractSocket::ConnectedState; }
|
|
||||||
|
|
||||||
QString getPath(const QString& path);
|
|
||||||
QString getFileName(const QString& path);
|
|
||||||
|
|
||||||
QString toStr(const QHostAddress& a);
|
|
||||||
bool splitAddr(const QString& addrOrNetwork, bool isAddress, uint& addr, uint* mask = 0);
|
|
||||||
bool ipOk(const QString& addr, bool netmask);
|
|
||||||
|
|
||||||
template<class T>
|
|
||||||
class Vector : public std::vector<T>
|
|
||||||
{
|
|
||||||
typedef std::vector<T> base;
|
|
||||||
public:
|
|
||||||
using base::base;
|
|
||||||
int size() const { return base::size(); }
|
|
||||||
void clear() { base::clear(); base::shrink_to_fit(); }
|
|
||||||
|
|
||||||
void remove(int pos) { /*checkIndex(pos);*/ base::erase(base::begin() + pos); }
|
|
||||||
};
|
|
||||||
|
|
||||||
template<class T>
|
|
||||||
class List : public std::list<T>
|
|
||||||
{
|
|
||||||
typedef std::list<T> base;
|
|
||||||
public:
|
|
||||||
using base::base;
|
|
||||||
int size() const { return base::size(); }
|
|
||||||
|
|
||||||
List& operator+=(const T& v) { base::push_back(v); return *this; }
|
|
||||||
List& operator+=(const List& v) { base::insert(base::end(), v.begin(), v.end()); return *this; }
|
|
||||||
List& operator<<(const T& v) { return *this += v; }
|
|
||||||
List& operator<<(const List& v) { return *this += v; }
|
|
||||||
};
|
|
||||||
|
|
||||||
namespace Alg
|
|
||||||
{
|
|
||||||
template<class C> inline void remove(C& c, const typename C::value_type& value)
|
|
||||||
{
|
|
||||||
typename C::iterator i = std::remove(c.begin(), c.end(), value);
|
|
||||||
c.erase(i, c.end());
|
|
||||||
}
|
|
||||||
template<class C, class P> inline void removeIf(C& c, P pred)
|
|
||||||
{
|
|
||||||
typename C::iterator i = std::remove_if(c.begin(), c.end(), pred);
|
|
||||||
c.erase(i, c.end());
|
|
||||||
}
|
|
||||||
|
|
||||||
template<class I, class T> inline bool contains(I first, I last, const T& value)
|
|
||||||
{
|
|
||||||
I i = std::find(first, last, value);
|
|
||||||
return i != last;
|
|
||||||
}
|
|
||||||
template<class I, class P> inline bool containsIf(I first, I last, P pred)
|
|
||||||
{
|
|
||||||
I i = std::find_if(first, last, pred);
|
|
||||||
return i != last;
|
|
||||||
}
|
|
||||||
|
|
||||||
template<class C> inline C& sort(C& c) { std::sort(c.begin(), c.end()); return c; }
|
|
||||||
template<class C, class P> inline C& sort(C& c, const P& pred) { std::sort(c.begin(), c.end(), pred); return c; }
|
|
||||||
|
|
||||||
template<class C> inline bool contains(const C& c, const typename C::value_type& value) { return contains(c.begin(), c.end(), value); }
|
|
||||||
template<class C, class P> inline bool containsIf(const C& c, P pred) { return containsIf(c.begin(), c.end(), pred); }
|
|
||||||
|
|
||||||
template<class C, class T> inline typename C::iterator find(C& c, const T& v) { return std::find(c.begin(), c.end(), v); }
|
|
||||||
template<class C, class T> inline typename C::const_iterator find(const C& c, const T& v) { return std::find(c.begin(), c.end(), v); }
|
|
||||||
|
|
||||||
template<class C, class P> inline typename C::iterator findIf(C& c, const P& pred) { return std::find_if(c.begin(), c.end(), pred); }
|
|
||||||
template<class C, class P> inline typename C::const_iterator findIf(const C& c, const P& pred) { return std::find_if(c.begin(), c.end(), pred); }
|
|
||||||
template<class C> inline int findIndex(const C& c, const typename C::value_type& v)
|
|
||||||
{
|
|
||||||
for(int i = 0; i < int(c.size()); ++i)
|
|
||||||
if (v == c[i])
|
|
||||||
return i;
|
|
||||||
return -1;
|
|
||||||
}
|
|
||||||
template<class C, class P> inline int findIndexIf(const C& c, const P& pred)
|
|
||||||
{
|
|
||||||
for(int i = 0; i < int(c.size()); ++i)
|
|
||||||
if (pred(c[i]))
|
|
||||||
return i;
|
|
||||||
return -1;
|
|
||||||
}
|
|
||||||
template<class C, class P> inline int rfindIndexIf(const C& c, const P& pred)
|
|
||||||
{
|
|
||||||
for(int i = int(c.size()) - 1; i >= 0; --i)
|
|
||||||
if (pred(c[i]))
|
|
||||||
return i;
|
|
||||||
return -1;
|
|
||||||
}
|
|
||||||
|
|
||||||
template<class C, class F> C& forEach(C& c, F f) { std::for_each(c.begin(), c.end(), f); return c; }
|
|
||||||
template<class C, class F> const C& forEach(const C& c, F f) { std::for_each(c.begin(), c.end(), f); return c; }
|
|
||||||
template<class C, typename T> inline void fill(C& c, const T& value) { std::fill(c.begin(), c.end(), value); }
|
|
||||||
|
|
||||||
template<class C, class It> It copy(C& c, It to) { return std::copy(c.begin(), c.end(), to); }
|
|
||||||
|
|
||||||
//template<class C, class P> C select(const C& v, P pred)
|
|
||||||
|
|
||||||
template<class T, class P> QVector<int> selectIndexes(const QVector<T>& v, P pred)
|
|
||||||
{
|
|
||||||
QVector<int> r;
|
|
||||||
for(int i = 0; i < int(v.size()); ++i)
|
|
||||||
if (pred(v[i]))
|
|
||||||
r << i;
|
|
||||||
return r;
|
|
||||||
}
|
|
||||||
|
|
||||||
template<class C, class P> inline int countIf(const C &c, const P &pred) { return std::count_if(c.begin(), c.end(), pred); }
|
|
||||||
}
|
|
||||||
|
|
||||||
// Returns pointer to value if the variant holds T, otherwise nullptr
|
|
||||||
template<typename T, typename Variant>
|
|
||||||
inline decltype(auto) get_if_type(Variant& v) {
|
|
||||||
return std::holds_alternative<T>(v) ? std::addressof(std::get<T>(v)) : nullptr;
|
|
||||||
}
|
|
||||||
|
|
||||||
template<typename T, typename Variant>
|
|
||||||
inline const T* get_if_polymorphic(const Variant& v) {
|
|
||||||
if (const auto* p = dynamic_cast<const T*>(std::addressof(v))) {
|
|
||||||
return p;
|
|
||||||
}
|
|
||||||
return nullptr;
|
|
||||||
}
|
|
||||||
|
|
||||||
const QString DateFormat = "dd.MM.yyyy", DtFormat = "dd.MM.yyyy HH:mm", DtFormatSec = "dd.MM.yyyy HH:mm:ss", DtFormatSecMsec = "ss:zzz";
|
|
||||||
|
|
||||||
bool isRu();
|
|
||||||
|
|
||||||
//inline QDate nowd() { return QDate::currentDate(); }
|
|
||||||
inline QTime nowt() { return QTime::currentTime(); }
|
|
||||||
inline QDateTime now() { return QDateTime::currentDateTime(); }
|
|
||||||
//inline QDateTime nowutc() { return QDateTime::currentDateTimeUtc(); }
|
|
||||||
//inline QDateTime today() { return QDateTime(nowd()); }
|
|
||||||
|
|
||||||
inline QString nowstr() { return now().toString(DtFormatSec); }
|
|
||||||
//inline QString nowdstr() { return nowd().toString(DateFormat); }
|
|
||||||
inline QString nowtstr() { return nowt().toString("hh:mm:ss"); }
|
|
||||||
|
|
||||||
QString makeHash(const QString& password);
|
|
||||||
bool testPassword(const QByteArray& password, const QByteArray& passwordHash);
|
|
||||||
|
|
||||||
//inline QDebug qs() { return qDebug().nospace(); }
|
|
||||||
//inline QDebug qq() { return qDebug().noquote(); }
|
|
||||||
|
|
||||||
//QByteArray calcHash(const QStringList& list);
|
|
||||||
|
|
||||||
inline int compareNoCase(const QString& a, const QString& b) { return a.compare(b, Qt::CaseInsensitive); }
|
|
||||||
|
|
||||||
int findFirstNotOf(const QString& s, const char* chars, int pos = 0, int n = -1);
|
|
||||||
int findFirstNotOf(const QString& s, QChar c, int pos = 0, int n = -1);
|
|
||||||
|
|
||||||
inline void noCaseSort(QStringList& list)
|
|
||||||
{ std::sort(list.begin(), list.end(), [](const QString& a, const QString& b) { int r = compareNoCase(a, b); return r == 0 ? (a < b) : r < 0; }); }
|
|
||||||
|
|
||||||
QByteArray& trimLeft(QByteArray& s, char c);
|
|
||||||
QByteArray& trimRight(QByteArray& s, char c);
|
|
||||||
inline QByteArray& trim(QByteArray& s, char c) { return trimLeft(trimRight(s, c), c); }
|
|
||||||
|
|
||||||
QByteArray& trimLeft(QByteArray& s, const char* chars); // ascii only
|
|
||||||
QByteArray& trimRight(QByteArray& s, const char* chars); // ascii only
|
|
||||||
inline QByteArray& trim(QByteArray& s, const char* chars) { return trimLeft(trimRight(s, chars), chars); }
|
|
||||||
|
|
||||||
QString& trimLeft(QString& s, QChar c);
|
|
||||||
QString& trimRight(QString& s, QChar c);
|
|
||||||
inline QString& trim(QString& s, QChar c) { return trimLeft(trimRight(s, c), c); }
|
|
||||||
|
|
||||||
//QString& trimLeft(QString& s, const char* chars); // ascii only
|
|
||||||
//QString& trimRight(QString& s, const char* chars); // ascii only
|
|
||||||
//inline QString& trim(QString& s, const char* chars) { return trimLeft(trimRight(s, chars), chars); }
|
|
||||||
|
|
||||||
//QString& trimLeft(QString& s, const char16_t* chars);
|
|
||||||
//QString& trimRight(QString& s, const char16_t* chars);
|
|
||||||
//inline QString& trim(QString& s, const char* chars) { return trimLeft(trimRight(s, chars), chars); }
|
|
||||||
|
|
||||||
QString& append(QString& s, const QVector<int>& v, QChar sep);
|
|
||||||
inline QString join(const QVector<int>& v, QChar sep)
|
|
||||||
{
|
|
||||||
QString s;
|
|
||||||
append(s, v, sep);
|
|
||||||
return s;
|
|
||||||
}
|
|
||||||
QString &append(QString &s, const QVector<QByteArray> &v, QChar sep);
|
|
||||||
QVector<int> splitToInts(const QString& s, QChar sep);
|
|
||||||
|
|
||||||
QVector<QHostAddress> getIpAddr();
|
|
||||||
|
|
||||||
QVector<bool> convertStringToVectorBool(const QString &string);
|
|
||||||
QByteArray addSizeToByteArray(const QByteArray &arr);
|
|
||||||
int getSizeJsonInByteArr(const QByteArray &arr);
|
|
||||||
QString toString(const QVector<bool> &vec, bool value);
|
|
||||||
//Counts values in bool vectors
|
|
||||||
int popCount(const QVector<bool> &vec, bool value);
|
|
||||||
QVector<bool> vectorOr(const QVector<bool> &a, const QVector<bool> &b);
|
|
||||||
QVector<bool> invert(const QVector<bool> &v);
|
|
||||||
|
|
||||||
QString execAndReadAll(const QString& prog, const QStringList& args, const QString& workDir, int waitTimeout = 0, bool ignoreErrorOut = false);
|
|
||||||
inline QString execAndReadAll1(const QString& prog, const QString& arg, int waitTimeout = 0, bool ignoreErrorOut = false) { return execAndReadAll(prog, QStringList(arg), {}, waitTimeout, ignoreErrorOut); }
|
|
||||||
|
|
||||||
QRegularExpressionMatch paCardMatchSinks();
|
|
||||||
QRegularExpressionMatch paPciMatchSinks();
|
|
||||||
QByteArray getPaUsbOutput();
|
|
||||||
QByteArray getPaUsbInput();
|
|
||||||
bool isUsbAudioCardPresent();
|
|
||||||
QByteArray getDefaultAudioInput();
|
|
||||||
QByteArray getDefaultAudioOutput();
|
|
||||||
QByteArray getDefaultAudioInputStr();
|
|
||||||
QByteArray getDefaultAudioOutputStr();
|
|
||||||
|
|
||||||
QByteArray getMachineId();
|
|
||||||
|
|
||||||
inline QString qStrFromBA(const QByteArray& chaddr) { return QString::fromUtf8(chaddr.left(6).toHex(':')); }
|
|
||||||
#ifndef Q_OS_WINDOWS
|
|
||||||
bool getVersion();
|
|
||||||
#endif
|
|
||||||
|
|
||||||
QJsonArray convertVectorToJsonArray(const QVector<bool> &vector);
|
|
||||||
QVector<bool> convertJsonArrayToVector(const QJsonArray &array);
|
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @brief Конвертирует QJsonObject в CBOR.
|
||||||
|
*/
|
||||||
QByteArray toCbor(const QJsonObject &jsonObject);
|
QByteArray toCbor(const QJsonObject &jsonObject);
|
||||||
|
/**
|
||||||
|
* @brief Конвертирует QJsonArray в CBOR.
|
||||||
|
*/
|
||||||
QByteArray toCbor(const QJsonArray &jsonArray);
|
QByteArray toCbor(const QJsonArray &jsonArray);
|
||||||
|
/**
|
||||||
|
* @brief Конвертирует QJsonDocument в CBOR.
|
||||||
|
*/
|
||||||
QByteArray toCbor(const QJsonDocument &doc);
|
QByteArray toCbor(const QJsonDocument &doc);
|
||||||
QJsonDocument fromCbor(const QByteArray &cborData);
|
|
||||||
|
|
||||||
QVector<int> arrayFromMask(uint8_t mask);
|
|
||||||
QVector<int> intersectUnique(const QVector<int>& a, const QVector<int>& b);
|
|
||||||
|
|||||||
@@ -1,365 +1,11 @@
|
|||||||
#include "crypt.h"
|
#include "crypt.h"
|
||||||
|
|
||||||
#include <cmath>
|
#include <cmath>
|
||||||
#include <openssl/pem.h>
|
#include <openssl/pem.h>
|
||||||
#include <openssl/bio.h>
|
#include <openssl/bio.h>
|
||||||
#include <openssl/rsa.h>
|
#include <openssl/rsa.h>
|
||||||
#include <openssl/err.h>
|
#include <openssl/err.h>
|
||||||
#include <openssl/rand.h>
|
|
||||||
#include <QDebug>
|
#include <QDebug>
|
||||||
#include <QDataStream>
|
|
||||||
|
|
||||||
QPair<QByteArray, QByteArray> Crypt::generateRSAKeyPair() {
|
|
||||||
QPair<QByteArray, QByteArray> keyPair;
|
|
||||||
EVP_PKEY *keypair = nullptr;
|
|
||||||
EVP_PKEY_CTX *ctx = EVP_PKEY_CTX_new_id(EVP_PKEY_RSA, nullptr);
|
|
||||||
|
|
||||||
if (!ctx || EVP_PKEY_keygen_init(ctx) <= 0 || EVP_PKEY_CTX_set_rsa_keygen_bits(ctx, 2048) <= 0) {
|
|
||||||
qDebug() << "Failed to initialize RSA key generation context";
|
|
||||||
EVP_PKEY_CTX_free(ctx);
|
|
||||||
return keyPair;
|
|
||||||
}
|
|
||||||
|
|
||||||
if (EVP_PKEY_keygen(ctx, &keypair) <= 0) {
|
|
||||||
qDebug() << "Failed to generate RSA key pair";
|
|
||||||
EVP_PKEY_CTX_free(ctx);
|
|
||||||
return keyPair;
|
|
||||||
}
|
|
||||||
|
|
||||||
// Convert private key to DER format
|
|
||||||
BIO *privateBio = BIO_new(BIO_s_mem());
|
|
||||||
if (i2d_PrivateKey_bio(privateBio, keypair) <= 0) {
|
|
||||||
qDebug() << "Failed to write private key in DER format";
|
|
||||||
EVP_PKEY_free(keypair);
|
|
||||||
BIO_free(privateBio);
|
|
||||||
EVP_PKEY_CTX_free(ctx);
|
|
||||||
return keyPair;
|
|
||||||
}
|
|
||||||
char *privateData = nullptr;
|
|
||||||
long privateLen = BIO_get_mem_data(privateBio, &privateData);
|
|
||||||
keyPair.first = QByteArray(privateData, privateLen);
|
|
||||||
BIO_free(privateBio);
|
|
||||||
|
|
||||||
// Convert public key to DER format
|
|
||||||
BIO *publicBio = BIO_new(BIO_s_mem());
|
|
||||||
if (i2d_PUBKEY_bio(publicBio, keypair) <= 0) {
|
|
||||||
qDebug() << "Failed to write public key in DER format";
|
|
||||||
EVP_PKEY_free(keypair);
|
|
||||||
BIO_free(publicBio);
|
|
||||||
EVP_PKEY_CTX_free(ctx);
|
|
||||||
return keyPair;
|
|
||||||
}
|
|
||||||
char *publicData = nullptr;
|
|
||||||
long publicLen = BIO_get_mem_data(publicBio, &publicData);
|
|
||||||
keyPair.second = QByteArray(publicData, publicLen);
|
|
||||||
BIO_free(publicBio);
|
|
||||||
|
|
||||||
EVP_PKEY_free(keypair);
|
|
||||||
EVP_PKEY_CTX_free(ctx);
|
|
||||||
return keyPair;
|
|
||||||
}
|
|
||||||
|
|
||||||
QByteArray Crypt::generateAESKey() {
|
|
||||||
QByteArray aesKey(32, 0); // 32 bytes for AES-256
|
|
||||||
if (!RAND_bytes(reinterpret_cast<unsigned char*>(aesKey.data()), aesKey.size())) {
|
|
||||||
qDebug() << "Failed to generate AES key";
|
|
||||||
}
|
|
||||||
return aesKey;
|
|
||||||
}
|
|
||||||
|
|
||||||
bool Crypt::encryptAESKeyWithRSA(EVP_PKEY *publicKey, const unsigned char *aesKey, size_t aesKeyLength, QByteArray &encryptedKey) {
|
|
||||||
encryptedKey.resize(256); // For RSA 2048-bit
|
|
||||||
size_t encryptedKeyLength = encryptedKey.size();
|
|
||||||
|
|
||||||
EVP_PKEY_CTX *ctx = EVP_PKEY_CTX_new(publicKey, nullptr);
|
|
||||||
if (!ctx || EVP_PKEY_encrypt_init(ctx) <= 0) {
|
|
||||||
qDebug() << "Failed to initialize RSA encryption";
|
|
||||||
EVP_PKEY_CTX_free(ctx);
|
|
||||||
return false;
|
|
||||||
}
|
|
||||||
|
|
||||||
EVP_PKEY_CTX_set_rsa_padding(ctx, RSA_PKCS1_OAEP_PADDING);
|
|
||||||
if (EVP_PKEY_encrypt(ctx, reinterpret_cast<unsigned char*>(encryptedKey.data()), &encryptedKeyLength, aesKey, aesKeyLength) <= 0) {
|
|
||||||
qDebug() << "RSA encryption failed";
|
|
||||||
EVP_PKEY_CTX_free(ctx);
|
|
||||||
ERR_print_errors_fp(stderr);
|
|
||||||
return false;
|
|
||||||
}
|
|
||||||
encryptedKey.resize(encryptedKeyLength);
|
|
||||||
EVP_PKEY_CTX_free(ctx);
|
|
||||||
return true;
|
|
||||||
}
|
|
||||||
|
|
||||||
Crypt::EncryptedData Crypt::encrypt(const QByteArray &plaintext, const QByteArray &publicKey, const QByteArray &aesKey) {
|
|
||||||
EncryptedData result;
|
|
||||||
|
|
||||||
EVP_PKEY *evpPublicKey = loadKeyFromDER(publicKey, true);
|
|
||||||
if (!evpPublicKey) {
|
|
||||||
return result;
|
|
||||||
}
|
|
||||||
|
|
||||||
// Generate random IV
|
|
||||||
result.iv.resize(12); // GCM standard recommends a 12-byte IV
|
|
||||||
if (!RAND_bytes(reinterpret_cast<unsigned char*>(result.iv.data()), result.iv.size())) {
|
|
||||||
qDebug() << "Failed to generate random IV";
|
|
||||||
EVP_PKEY_free(evpPublicKey);
|
|
||||||
return result;
|
|
||||||
}
|
|
||||||
|
|
||||||
// Encrypt the AES key with RSA public key
|
|
||||||
if (!encryptAESKeyWithRSA(evpPublicKey, reinterpret_cast<const unsigned char*>(aesKey.data()), aesKey.size(), result.encryptedKey)) {
|
|
||||||
qDebug() << "Failed to encrypt AES key with RSA";
|
|
||||||
EVP_PKEY_free(evpPublicKey);
|
|
||||||
return result;
|
|
||||||
}
|
|
||||||
EVP_PKEY_free(evpPublicKey); // Free the EVP_PKEY structure for the public key
|
|
||||||
|
|
||||||
// AES-GCM encryption of the plaintext
|
|
||||||
EVP_CIPHER_CTX *ctx = EVP_CIPHER_CTX_new();
|
|
||||||
if (!ctx) {
|
|
||||||
qDebug() << "Failed to create encryption context";
|
|
||||||
return result;
|
|
||||||
}
|
|
||||||
|
|
||||||
if (EVP_EncryptInit_ex(ctx, EVP_aes_256_gcm(), nullptr, nullptr, nullptr) <= 0 ||
|
|
||||||
EVP_CIPHER_CTX_ctrl(ctx, EVP_CTRL_GCM_SET_IVLEN, result.iv.size(), nullptr) <= 0 ||
|
|
||||||
EVP_EncryptInit_ex(ctx, nullptr, nullptr, reinterpret_cast<const unsigned char*>(aesKey.data()), reinterpret_cast<const unsigned char*>(result.iv.data())) <= 0) {
|
|
||||||
qDebug() << "Failed to initialize AES-GCM encryption";
|
|
||||||
EVP_CIPHER_CTX_free(ctx);
|
|
||||||
return result;
|
|
||||||
}
|
|
||||||
|
|
||||||
// Encrypt the plaintext
|
|
||||||
QByteArray ciphertext(plaintext.size() + EVP_CIPHER_block_size(EVP_aes_256_gcm()), 0);
|
|
||||||
int len = 0, ciphertext_len = 0;
|
|
||||||
if (EVP_EncryptUpdate(ctx, reinterpret_cast<unsigned char*>(ciphertext.data()), &len, reinterpret_cast<const unsigned char*>(plaintext.data()), plaintext.size()) <= 0) {
|
|
||||||
qDebug() << "AES-GCM encryption failed at Update step";
|
|
||||||
EVP_CIPHER_CTX_free(ctx);
|
|
||||||
return result;
|
|
||||||
}
|
|
||||||
ciphertext_len += len;
|
|
||||||
|
|
||||||
// Finalize encryption
|
|
||||||
if (EVP_EncryptFinal_ex(ctx, reinterpret_cast<unsigned char*>(ciphertext.data()) + len, &len) <= 0) {
|
|
||||||
qDebug() << "AES-GCM encryption failed at Final step";
|
|
||||||
EVP_CIPHER_CTX_free(ctx);
|
|
||||||
return result;
|
|
||||||
}
|
|
||||||
ciphertext_len += len;
|
|
||||||
ciphertext.resize(ciphertext_len);
|
|
||||||
result.encryptedData = ciphertext;
|
|
||||||
|
|
||||||
// Get the GCM authentication tag
|
|
||||||
result.tag.resize(16); // Standard GCM tag length is 16 bytes
|
|
||||||
if (EVP_CIPHER_CTX_ctrl(ctx, EVP_CTRL_GCM_GET_TAG, result.tag.size(), result.tag.data()) <= 0) {
|
|
||||||
qDebug() << "Failed to retrieve GCM tag";
|
|
||||||
EVP_CIPHER_CTX_free(ctx);
|
|
||||||
return result;
|
|
||||||
}
|
|
||||||
|
|
||||||
EVP_CIPHER_CTX_free(ctx);
|
|
||||||
return result;
|
|
||||||
}
|
|
||||||
|
|
||||||
bool Crypt::decryptAESKeyWithRSA(EVP_PKEY *privateKey, const QByteArray &encryptedKey, unsigned char *aesKey, size_t aesKeyLength) {
|
|
||||||
// Ensure the buffer size is set to the RSA key size (e.g., 256 bytes for a 2048-bit RSA key)
|
|
||||||
unsigned char decryptedBuffer[256]; // Adjust size according to RSA key length, e.g., 256 bytes for RSA 2048-bit
|
|
||||||
size_t decryptedBufferSize = sizeof(decryptedBuffer);
|
|
||||||
|
|
||||||
EVP_PKEY_CTX *ctx = EVP_PKEY_CTX_new(privateKey, nullptr);
|
|
||||||
if (!ctx || EVP_PKEY_decrypt_init(ctx) <= 0) {
|
|
||||||
qDebug() << "Failed to initialize RSA decryption";
|
|
||||||
EVP_PKEY_CTX_free(ctx);
|
|
||||||
return false;
|
|
||||||
}
|
|
||||||
|
|
||||||
// Set RSA padding to OAEP, which is recommended for encrypting small data such as AES keys
|
|
||||||
if (EVP_PKEY_CTX_set_rsa_padding(ctx, RSA_PKCS1_OAEP_PADDING) <= 0) {
|
|
||||||
qDebug() << "Failed to set RSA padding";
|
|
||||||
EVP_PKEY_CTX_free(ctx);
|
|
||||||
return false;
|
|
||||||
}
|
|
||||||
|
|
||||||
// Perform RSA decryption into the `decryptedBuffer`
|
|
||||||
if (EVP_PKEY_decrypt(ctx, decryptedBuffer, &decryptedBufferSize, reinterpret_cast<const unsigned char*>(encryptedKey.data()), encryptedKey.size()) <= 0) {
|
|
||||||
qDebug() << "RSA decryption failed";
|
|
||||||
EVP_PKEY_CTX_free(ctx);
|
|
||||||
ERR_print_errors_fp(stderr); // Print detailed OpenSSL error messages
|
|
||||||
return false;
|
|
||||||
}
|
|
||||||
|
|
||||||
// Check if the decrypted data fits the expected AES key size
|
|
||||||
if (decryptedBufferSize < aesKeyLength) {
|
|
||||||
qDebug() << "Decrypted data is too small for AES key";
|
|
||||||
EVP_PKEY_CTX_free(ctx);
|
|
||||||
return false;
|
|
||||||
}
|
|
||||||
|
|
||||||
// Copy only the first `aesKeyLength` bytes to `aesKey`
|
|
||||||
memcpy(aesKey, decryptedBuffer, aesKeyLength);
|
|
||||||
EVP_PKEY_CTX_free(ctx);
|
|
||||||
return true;
|
|
||||||
}
|
|
||||||
|
|
||||||
QByteArray Crypt::decrypt(const EncryptedData &encryptedData, const QByteArray &privateKey) {
|
|
||||||
QByteArray decryptedText;
|
|
||||||
|
|
||||||
EVP_PKEY *evpPrivateKey = loadKeyFromDER(privateKey, false);
|
|
||||||
if (!evpPrivateKey) {
|
|
||||||
return QByteArray();
|
|
||||||
}
|
|
||||||
|
|
||||||
// Decrypt the AES key using RSA private key
|
|
||||||
unsigned char aesKey[32]; // Buffer to hold the 32-byte AES key
|
|
||||||
if (!decryptAESKeyWithRSA(evpPrivateKey, encryptedData.encryptedKey, aesKey, sizeof(aesKey))) {
|
|
||||||
qDebug() << "Failed to decrypt AES key with RSA";
|
|
||||||
EVP_PKEY_free(evpPrivateKey);
|
|
||||||
return QByteArray();
|
|
||||||
}
|
|
||||||
EVP_PKEY_free(evpPrivateKey); // Free the EVP_PKEY structure for the private key
|
|
||||||
|
|
||||||
// AES-GCM decryption
|
|
||||||
EVP_CIPHER_CTX *ctx = EVP_CIPHER_CTX_new();
|
|
||||||
if (!ctx) {
|
|
||||||
qDebug() << "Failed to create decryption context";
|
|
||||||
return QByteArray();
|
|
||||||
}
|
|
||||||
|
|
||||||
// Initialize decryption with AES-256-GCM
|
|
||||||
if (EVP_DecryptInit_ex(ctx, EVP_aes_256_gcm(), nullptr, nullptr, nullptr) <= 0 ||
|
|
||||||
EVP_CIPHER_CTX_ctrl(ctx, EVP_CTRL_GCM_SET_IVLEN, encryptedData.iv.size(), nullptr) <= 0 ||
|
|
||||||
EVP_DecryptInit_ex(ctx, nullptr, nullptr, aesKey, reinterpret_cast<const unsigned char*>(encryptedData.iv.data())) <= 0) {
|
|
||||||
qDebug() << "Failed to initialize AES-GCM decryption";
|
|
||||||
EVP_CIPHER_CTX_free(ctx);
|
|
||||||
return QByteArray();
|
|
||||||
}
|
|
||||||
|
|
||||||
// Decrypt the encrypted data
|
|
||||||
decryptedText.resize(encryptedData.encryptedData.size());
|
|
||||||
int len = 0, plaintext_len = 0;
|
|
||||||
if (EVP_DecryptUpdate(ctx, reinterpret_cast<unsigned char*>(decryptedText.data()), &len,
|
|
||||||
reinterpret_cast<const unsigned char*>(encryptedData.encryptedData.data()), encryptedData.encryptedData.size()) <= 0) {
|
|
||||||
qDebug() << "AES-GCM decryption failed at Update step";
|
|
||||||
EVP_CIPHER_CTX_free(ctx);
|
|
||||||
return QByteArray();
|
|
||||||
}
|
|
||||||
plaintext_len += len;
|
|
||||||
|
|
||||||
// Set the expected tag for verification
|
|
||||||
if (EVP_CIPHER_CTX_ctrl(ctx, EVP_CTRL_GCM_SET_TAG, encryptedData.tag.size(),
|
|
||||||
const_cast<unsigned char*>(reinterpret_cast<const unsigned char*>(encryptedData.tag.data()))) <= 0) {
|
|
||||||
qDebug() << "Failed to set GCM tag for verification";
|
|
||||||
EVP_CIPHER_CTX_free(ctx);
|
|
||||||
return QByteArray();
|
|
||||||
}
|
|
||||||
|
|
||||||
// Finalize decryption and verify tag
|
|
||||||
if (EVP_DecryptFinal_ex(ctx, reinterpret_cast<unsigned char*>(decryptedText.data()) + plaintext_len, &len) <= 0) {
|
|
||||||
qDebug() << "Tag verification failed or decryption unsuccessful";
|
|
||||||
EVP_CIPHER_CTX_free(ctx);
|
|
||||||
return QByteArray();
|
|
||||||
}
|
|
||||||
plaintext_len += len;
|
|
||||||
|
|
||||||
EVP_CIPHER_CTX_free(ctx);
|
|
||||||
decryptedText.resize(plaintext_len); // Adjust the QByteArray to the actual plaintext length
|
|
||||||
|
|
||||||
return decryptedText;
|
|
||||||
}
|
|
||||||
|
|
||||||
QByteArray Crypt::decrypt(const QByteArray &encryptedData, const QByteArray &privateKey)
|
|
||||||
{
|
|
||||||
EncryptedData ed;
|
|
||||||
QDataStream ds(encryptedData);
|
|
||||||
ds >> ed;
|
|
||||||
return decrypt(ed, privateKey);
|
|
||||||
}
|
|
||||||
|
|
||||||
bool Crypt::isValidPublicKeyDER(const QByteArray &derKeyData)
|
|
||||||
{
|
|
||||||
// Create a memory BIO from the QByteArray
|
|
||||||
BIO *bio = BIO_new_mem_buf(derKeyData.constData(), derKeyData.size());
|
|
||||||
if (!bio) {
|
|
||||||
qWarning("Failed to create BIO");
|
|
||||||
return false;
|
|
||||||
}
|
|
||||||
|
|
||||||
// Attempt to decode the DER-encoded data as a public key
|
|
||||||
EVP_PKEY *pkey = d2i_PUBKEY_bio(bio, nullptr);
|
|
||||||
BIO_free(bio); // Free the BIO after use
|
|
||||||
|
|
||||||
bool isValid = (pkey != nullptr); // If pkey is non-null, it’s a valid key
|
|
||||||
if (pkey) {
|
|
||||||
EVP_PKEY_free(pkey); // Free the EVP_PKEY structure if it was valid
|
|
||||||
}
|
|
||||||
|
|
||||||
return isValid;
|
|
||||||
}
|
|
||||||
|
|
||||||
QByteArray Crypt::rsaDecrypt(const QByteArray &encryptedData, const QByteArray &publicKey)
|
|
||||||
{
|
|
||||||
EVP_PKEY *pkey = loadKeyFromDER(publicKey, true);
|
|
||||||
if (!pkey) {
|
|
||||||
return QByteArray();
|
|
||||||
}
|
|
||||||
EVP_PKEY_CTX* ctx = EVP_PKEY_CTX_new(pkey, nullptr);
|
|
||||||
if (!ctx) {
|
|
||||||
EVP_PKEY_free(pkey);
|
|
||||||
qDebug() << "Failed to create EVP_PKEY_CTX for decryption.";
|
|
||||||
ERR_print_errors_fp(stderr);
|
|
||||||
return QByteArray();
|
|
||||||
}
|
|
||||||
|
|
||||||
const int key_bits =
|
|
||||||
#if OPENSSL_VERSION_NUMBER < 0x30000000L
|
|
||||||
EVP_PKEY_bits(pkey);
|
|
||||||
#else
|
|
||||||
EVP_PKEY_get_bits(pkey);
|
|
||||||
#endif
|
|
||||||
qDebug() << "Key bits" << key_bits;
|
|
||||||
EVP_PKEY_free(pkey);
|
|
||||||
int chunkSize = key_bits / 8;
|
|
||||||
|
|
||||||
if (EVP_PKEY_verify_recover_init(ctx) <= 0) {
|
|
||||||
qDebug() << "Failed to initialize verification context.";
|
|
||||||
ERR_print_errors_fp(stderr);
|
|
||||||
EVP_PKEY_CTX_free(ctx);
|
|
||||||
return QByteArray();
|
|
||||||
}
|
|
||||||
|
|
||||||
if (EVP_PKEY_CTX_set_rsa_padding(ctx, RSA_PKCS1_PADDING) <= 0) {
|
|
||||||
qDebug() << "Failed to set RSA padding.";
|
|
||||||
ERR_print_errors_fp(stderr);
|
|
||||||
EVP_PKEY_CTX_free(ctx);
|
|
||||||
return QByteArray();
|
|
||||||
}
|
|
||||||
|
|
||||||
QByteArray ret;
|
|
||||||
for (int i = 0; i < encryptedData.size() / chunkSize; ++i) {
|
|
||||||
int offset = i * chunkSize;
|
|
||||||
QByteArray chunk = encryptedData.mid(offset, chunkSize);
|
|
||||||
size_t out_len = 0;
|
|
||||||
if (EVP_PKEY_verify_recover(ctx, nullptr, &out_len, reinterpret_cast<const unsigned char *>(chunk.data()), chunk.size()) <= 0) {
|
|
||||||
qDebug() << "Failed to determine decrypted data size.";
|
|
||||||
ERR_print_errors_fp(stderr);
|
|
||||||
EVP_PKEY_CTX_free(ctx);
|
|
||||||
return QByteArray();
|
|
||||||
}
|
|
||||||
|
|
||||||
QByteArray decrypted;
|
|
||||||
decrypted.resize(out_len);
|
|
||||||
if (EVP_PKEY_verify_recover(ctx, reinterpret_cast<unsigned char *>(decrypted.data()), &out_len, reinterpret_cast<const unsigned char *>(chunk.data()), chunk.size()) <= 0) {
|
|
||||||
qDebug() << "Verification (decryption) failed.";
|
|
||||||
ERR_print_errors_fp(stderr);
|
|
||||||
EVP_PKEY_CTX_free(ctx);
|
|
||||||
return QByteArray();
|
|
||||||
}
|
|
||||||
decrypted.resize(out_len);
|
|
||||||
ret.append(decrypted);
|
|
||||||
}
|
|
||||||
|
|
||||||
EVP_PKEY_CTX_free(ctx);
|
|
||||||
return ret;
|
|
||||||
}
|
|
||||||
|
|
||||||
QByteArray Crypt::rsaEncrypt(const QByteArray &data, const QByteArray &privateKey)
|
QByteArray Crypt::rsaEncrypt(const QByteArray &data, const QByteArray &privateKey)
|
||||||
{
|
{
|
||||||
@@ -428,17 +74,6 @@ QByteArray Crypt::rsaEncrypt(const QByteArray &data, const QByteArray &privateKe
|
|||||||
return ret;
|
return ret;
|
||||||
}
|
}
|
||||||
|
|
||||||
QByteArray Crypt::encryptToByteArray(const QByteArray &plaintext, const QByteArray &publicKey, const QByteArray &aesKey)
|
|
||||||
{
|
|
||||||
EncryptedData ed = encrypt(plaintext, publicKey, aesKey);
|
|
||||||
if (ed.isEmpty())
|
|
||||||
return QByteArray();
|
|
||||||
QByteArray ret;
|
|
||||||
QDataStream ds(&ret, QIODevice::WriteOnly);
|
|
||||||
ds << ed;
|
|
||||||
return ret;
|
|
||||||
}
|
|
||||||
|
|
||||||
EVP_PKEY *Crypt::loadKeyFromDER(const QByteArray &derKey, bool isPublicKey) {
|
EVP_PKEY *Crypt::loadKeyFromDER(const QByteArray &derKey, bool isPublicKey) {
|
||||||
// Create a memory BIO from the QByteArray
|
// Create a memory BIO from the QByteArray
|
||||||
BIO *bio = BIO_new_mem_buf(derKey.constData(), derKey.size());
|
BIO *bio = BIO_new_mem_buf(derKey.constData(), derKey.size());
|
||||||
@@ -458,21 +93,3 @@ EVP_PKEY *Crypt::loadKeyFromDER(const QByteArray &derKey, bool isPublicKey) {
|
|||||||
|
|
||||||
return key;
|
return key;
|
||||||
}
|
}
|
||||||
|
|
||||||
QDataStream &operator<< (QDataStream &ds, const Crypt::EncryptedData &data)
|
|
||||||
{
|
|
||||||
ds << data.encryptedData;
|
|
||||||
ds << data.iv;
|
|
||||||
ds << data.encryptedKey;
|
|
||||||
ds << data.tag;
|
|
||||||
return ds;
|
|
||||||
}
|
|
||||||
|
|
||||||
QDataStream &operator>> (QDataStream &ds, Crypt::EncryptedData &data)
|
|
||||||
{
|
|
||||||
ds >> data.encryptedData;
|
|
||||||
ds >> data.iv;
|
|
||||||
ds >> data.encryptedKey;
|
|
||||||
ds >> data.tag;
|
|
||||||
return ds;
|
|
||||||
}
|
|
||||||
|
|||||||
@@ -1,41 +1,27 @@
|
|||||||
#ifndef CRYPT_H
|
#ifndef CRYPT_H
|
||||||
#define CRYPT_H
|
#define CRYPT_H
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @file crypt.h
|
||||||
|
* @brief Криптографические операции (RSA) для генерации лицензий.
|
||||||
|
*
|
||||||
|
* @details
|
||||||
|
* Оставлены только операции, используемые в проекте.
|
||||||
|
*/
|
||||||
|
|
||||||
#include <openssl/evp.h>
|
#include <openssl/evp.h>
|
||||||
|
|
||||||
#include <QByteArray>
|
#include <QByteArray>
|
||||||
#include <QPair>
|
|
||||||
|
|
||||||
class Crypt
|
class Crypt
|
||||||
{
|
{
|
||||||
static bool encryptAESKeyWithRSA(EVP_PKEY *publicKey, const unsigned char *aesKey, size_t aesKeyLength, QByteArray &encryptedKey);
|
|
||||||
static bool decryptAESKeyWithRSA(EVP_PKEY *privateKey, const QByteArray &encryptedKey, unsigned char *aesKey, size_t aesKeyLength);
|
|
||||||
static EVP_PKEY *loadKeyFromDER(const QByteArray &derPrivateKey, bool isPublicKey);
|
static EVP_PKEY *loadKeyFromDER(const QByteArray &derPrivateKey, bool isPublicKey);
|
||||||
|
|
||||||
public:
|
public:
|
||||||
|
/**
|
||||||
struct EncryptedData {
|
* @brief RSA-шифрование приватным ключом (sign).
|
||||||
QByteArray encryptedData;
|
*/
|
||||||
QByteArray iv;
|
|
||||||
QByteArray encryptedKey;
|
|
||||||
QByteArray tag;
|
|
||||||
|
|
||||||
int size() { return encryptedData.size() + iv.size() + encryptedKey.size() + tag.size(); }
|
|
||||||
bool isEmpty() { return encryptedData.isEmpty(); }
|
|
||||||
};
|
|
||||||
|
|
||||||
static QPair<QByteArray, QByteArray> generateRSAKeyPair();
|
|
||||||
static QByteArray generateAESKey();
|
|
||||||
static EncryptedData encrypt(const QByteArray &plaintext, const QByteArray &publicKey, const QByteArray &aesKey);
|
|
||||||
static QByteArray encryptToByteArray(const QByteArray &plaintext, const QByteArray &publicKey, const QByteArray &aesKey);
|
|
||||||
static QByteArray decrypt(const EncryptedData &encryptedData, const QByteArray &privateKey);
|
|
||||||
static QByteArray decrypt(const QByteArray &encryptedData, const QByteArray &privateKey);
|
|
||||||
static bool isValidPublicKeyDER(const QByteArray& derKeyData);
|
|
||||||
static QByteArray rsaDecrypt(const QByteArray &encryptedData, const QByteArray &publicKey);
|
|
||||||
static QByteArray rsaEncrypt(const QByteArray &data, const QByteArray &privateKey);
|
static QByteArray rsaEncrypt(const QByteArray &data, const QByteArray &privateKey);
|
||||||
};
|
};
|
||||||
|
|
||||||
QDataStream &operator<< (QDataStream &ds, const Crypt::EncryptedData &data);
|
|
||||||
QDataStream &operator>> (QDataStream &ds, Crypt::EncryptedData &data);
|
|
||||||
|
|
||||||
#endif // CRYPT_H
|
#endif // CRYPT_H
|
||||||
|
|||||||
@@ -1,73 +1,149 @@
|
|||||||
#ifndef LICENSEMANAGER_LICENSEMODEL_H
|
#ifndef LICENSEMANAGER_LICENSEMODEL_H
|
||||||
#define LICENSEMANAGER_LICENSEMODEL_H
|
#define LICENSEMANAGER_LICENSEMODEL_H
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @file LicenseModel.h
|
||||||
|
* @brief Модель данных клиентов и операции с базой SQLite.
|
||||||
|
*
|
||||||
|
* @details
|
||||||
|
* Класс инкапсулирует структуру таблицы клиентов и обеспечивает
|
||||||
|
* асинхронные операции чтения/добавления/редактирования/удаления.
|
||||||
|
*/
|
||||||
|
|
||||||
// Qt
|
// Qt
|
||||||
#include <QAbstractItemModel>
|
#include <QAbstractItemModel>
|
||||||
#include <QFutureWatcher>
|
#include <QFutureWatcher>
|
||||||
#include <QtSql/QSqlDatabase>
|
#include <QtSql/QSqlDatabase>
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @brief Табличная модель клиентов и лицензий.
|
||||||
|
*
|
||||||
|
* @details
|
||||||
|
* Хранит данные в памяти, синхронизируясь с SQLite. Все операции,
|
||||||
|
* требующие доступа к БД, выполняются в отдельных потоках.
|
||||||
|
*/
|
||||||
class LicenseModel : public QAbstractItemModel
|
class LicenseModel : public QAbstractItemModel
|
||||||
{
|
{
|
||||||
Q_OBJECT
|
Q_OBJECT
|
||||||
public:
|
public:
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @brief Состояние модели и базы данных.
|
||||||
|
*/
|
||||||
enum class Status
|
enum class Status
|
||||||
{
|
{
|
||||||
None = 0,
|
None = 0, //!< Состояние не определено.
|
||||||
Ok,
|
Ok, //!< База данных готова.
|
||||||
DbStructError,
|
DbStructError, //!< Ошибка структуры или запроса к БД.
|
||||||
DbExistError,
|
DbExistError, //!< Ошибка открытия базы данных.
|
||||||
Working,
|
Working, //!< Выполняется операция с БД.
|
||||||
};
|
};
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @brief Запись клиента в базе.
|
||||||
|
*/
|
||||||
struct LicenseItem
|
struct LicenseItem
|
||||||
{
|
{
|
||||||
QString id;
|
QString id; //!< Идентификатор строки в БД.
|
||||||
QString city;
|
QString city; //!< Город.
|
||||||
QString email;
|
QString email; //!< Электронная почта.
|
||||||
QString firstName;
|
QString firstName; //!< Имя.
|
||||||
QString hardwareHash;
|
QString hardwareHash; //!< Аппаратный хеш (machine id).
|
||||||
QString lastName;
|
QString lastName; //!< Фамилия.
|
||||||
QString patronymic;
|
QString patronymic; //!< Отчество.
|
||||||
QString phone;
|
QString phone; //!< Телефон.
|
||||||
QString yourCompany;
|
QString yourCompany; //!< Название компании.
|
||||||
QString licenseTime;
|
QString licenseTime; //!< Срок лицензии (пусто = бессрочно).
|
||||||
QString comment;
|
QString comment; //!< Комментарий.
|
||||||
};
|
};
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @brief Результат асинхронной операции с БД.
|
||||||
|
*/
|
||||||
struct Result
|
struct Result
|
||||||
{
|
{
|
||||||
QList<LicenseItem> data;
|
QList<LicenseItem> data; //!< Данные, полученные из БД.
|
||||||
QStringList ids;
|
QStringList ids; //!< Список id, над которыми выполнялась операция.
|
||||||
Status status = Status::Ok;
|
Status status = Status::Ok; //!< Итоговый статус операции.
|
||||||
QString error;
|
QString error; //!< Текст ошибки (если есть).
|
||||||
};
|
};
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @brief Создаёт модель и выполняет первичную проверку БД.
|
||||||
|
*/
|
||||||
explicit LicenseModel(QObject* parent = nullptr);
|
explicit LicenseModel(QObject* parent = nullptr);
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @brief Возвращает количество строк модели.
|
||||||
|
*/
|
||||||
int rowCount(const QModelIndex &parent = QModelIndex()) const override;
|
int rowCount(const QModelIndex &parent = QModelIndex()) const override;
|
||||||
|
/**
|
||||||
|
* @brief Возвращает количество столбцов модели.
|
||||||
|
*/
|
||||||
int columnCount(const QModelIndex &parent = QModelIndex()) const override;
|
int columnCount(const QModelIndex &parent = QModelIndex()) const override;
|
||||||
|
/**
|
||||||
|
* @brief Возвращает отображаемые данные для ячейки.
|
||||||
|
*/
|
||||||
QVariant data(const QModelIndex &index, int role = Qt::DisplayRole) const override;
|
QVariant data(const QModelIndex &index, int role = Qt::DisplayRole) const override;
|
||||||
|
/**
|
||||||
|
* @brief Возвращает заголовки строк/столбцов.
|
||||||
|
*/
|
||||||
QVariant headerData(int section, Qt::Orientation orientation, int role = Qt::DisplayRole) const override;
|
QVariant headerData(int section, Qt::Orientation orientation, int role = Qt::DisplayRole) const override;
|
||||||
|
/**
|
||||||
|
* @brief Табличная модель не имеет иерархии.
|
||||||
|
*/
|
||||||
QModelIndex parent(const QModelIndex &index) const override;
|
QModelIndex parent(const QModelIndex &index) const override;
|
||||||
|
/**
|
||||||
|
* @brief Возвращает индекс ячейки по координатам.
|
||||||
|
*/
|
||||||
QModelIndex index(int row, int column, const QModelIndex &parent = QModelIndex()) const override;
|
QModelIndex index(int row, int column, const QModelIndex &parent = QModelIndex()) const override;
|
||||||
|
/**
|
||||||
|
* @brief Возвращает текущий статус модели.
|
||||||
|
*/
|
||||||
Status getStatus();
|
Status getStatus();
|
||||||
|
/**
|
||||||
|
* @brief Возвращает текст ошибок, накопленных моделью.
|
||||||
|
*/
|
||||||
QString getStatusText();
|
QString getStatusText();
|
||||||
|
/**
|
||||||
|
* @brief Создаёт таблицы БД из ресурса `tables.ddl`.
|
||||||
|
*/
|
||||||
bool prepareDatabase();
|
bool prepareDatabase();
|
||||||
|
/**
|
||||||
|
* @brief Добавляет клиента в БД и обновляет модель.
|
||||||
|
*/
|
||||||
void addClient(const LicenseItem &item);
|
void addClient(const LicenseItem &item);
|
||||||
|
/**
|
||||||
|
* @brief Удаляет клиентов по индексам строк.
|
||||||
|
*/
|
||||||
void deleteClient(const QList<int> &rows);
|
void deleteClient(const QList<int> &rows);
|
||||||
|
/**
|
||||||
|
* @brief Обновляет клиента по индексу строки.
|
||||||
|
*/
|
||||||
void editClient(const LicenseItem &item, int index);
|
void editClient(const LicenseItem &item, int index);
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @brief Перезагружает модель из базы данных.
|
||||||
|
*/
|
||||||
void updateModel();
|
void updateModel();
|
||||||
|
/**
|
||||||
|
* @brief Возвращает запись клиента по индексу строки.
|
||||||
|
*/
|
||||||
LicenseItem getItem(int index) const;
|
LicenseItem getItem(int index) const;
|
||||||
signals:
|
signals:
|
||||||
|
/**
|
||||||
|
* @brief Сигнал изменения статуса модели.
|
||||||
|
*/
|
||||||
void statusChanged();
|
void statusChanged();
|
||||||
private:
|
private:
|
||||||
|
/**
|
||||||
|
* @brief Проверяет наличие таблиц БД.
|
||||||
|
*/
|
||||||
bool checkTables();
|
bool checkTables();
|
||||||
private:
|
private:
|
||||||
QList<LicenseItem> m_data;
|
QList<LicenseItem> m_data; //!< Кэш данных для отображения.
|
||||||
Status m_status = Status::None;
|
Status m_status = Status::None; //!< Текущий статус модели.
|
||||||
QStringList m_errors;
|
QStringList m_errors; //!< Список ошибок работы с БД.
|
||||||
};
|
};
|
||||||
|
|
||||||
#endif // LICENSEMANAGER_LICENSEMODEL_H
|
#endif // LICENSEMANAGER_LICENSEMODEL_H
|
||||||
|
|||||||
@@ -57,11 +57,14 @@ MainWidget::MainWidget(QWidget *parent)
|
|||||||
{
|
{
|
||||||
if (!m_licenseModel->prepareDatabase())
|
if (!m_licenseModel->prepareDatabase())
|
||||||
{
|
{
|
||||||
QMessageBox::critical(
|
QMessageBox messageBox;
|
||||||
this
|
messageBox.setIcon(QMessageBox::Critical);
|
||||||
, tr("Error")
|
messageBox.setWindowTitle(tr("Error"));
|
||||||
, tr("Database create error")
|
messageBox.setText(tr("Database create error"));
|
||||||
);
|
messageBox.setDetailedText(m_licenseModel->getStatusText());
|
||||||
|
messageBox.exec();
|
||||||
|
|
||||||
|
QApplication::quit();
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
else if (result == QMessageBox::Button::No)
|
else if (result == QMessageBox::Button::No)
|
||||||
@@ -110,6 +113,8 @@ MainWidget::MainWidget(QWidget *parent)
|
|||||||
m_menu->addSeparator();
|
m_menu->addSeparator();
|
||||||
m_getLicenseFileAction = m_menu->addAction(QIcon(QStringLiteral(":/deps/getLicenseFile.png")), tr("Get license file"));
|
m_getLicenseFileAction = m_menu->addAction(QIcon(QStringLiteral(":/deps/getLicenseFile.png")), tr("Get license file"));
|
||||||
|
|
||||||
|
connect(m_deleteClientToolBarAction, &QAction::triggered, this, &MainWidget::onDeleteClientTriggered);
|
||||||
|
connect(m_editClientToolBarAction, &QAction::triggered, this, &MainWidget::onEditClientTriggered);
|
||||||
connect(m_reloadTableMenuAction, &QAction::triggered, this, &MainWidget::onReloadTableTriggered);
|
connect(m_reloadTableMenuAction, &QAction::triggered, this, &MainWidget::onReloadTableTriggered);
|
||||||
connect(m_addClientsMenuAction, &QAction::triggered, this, &MainWidget::onAddClientTriggered);
|
connect(m_addClientsMenuAction, &QAction::triggered, this, &MainWidget::onAddClientTriggered);
|
||||||
connect(m_editClientsMenuAction, &QAction::triggered, this, &MainWidget::onEditClientTriggered);
|
connect(m_editClientsMenuAction, &QAction::triggered, this, &MainWidget::onEditClientTriggered);
|
||||||
|
|||||||
@@ -1,6 +1,15 @@
|
|||||||
#ifndef LICENSEMANAGER_MAINWIDGET_H
|
#ifndef LICENSEMANAGER_MAINWIDGET_H
|
||||||
#define LICENSEMANAGER_MAINWIDGET_H
|
#define LICENSEMANAGER_MAINWIDGET_H
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @file MainWidget.h
|
||||||
|
* @brief Интерфейс главного окна приложения.
|
||||||
|
*
|
||||||
|
* @details
|
||||||
|
* Содержит декларацию главного окна, управляющего таблицей клиентов,
|
||||||
|
* контекстным меню, действиями панели инструментов и созданием лицензий.
|
||||||
|
*/
|
||||||
|
|
||||||
// Qt
|
// Qt
|
||||||
#include <QMainWindow>
|
#include <QMainWindow>
|
||||||
#include <QItemSelection>
|
#include <QItemSelection>
|
||||||
@@ -18,44 +27,89 @@ class MainWidget : public QMainWindow
|
|||||||
{
|
{
|
||||||
Q_OBJECT
|
Q_OBJECT
|
||||||
public:
|
public:
|
||||||
|
/**
|
||||||
|
* @brief Создаёт главное окно и инициализирует интерфейс.
|
||||||
|
*/
|
||||||
explicit MainWidget(QWidget *parent = nullptr);
|
explicit MainWidget(QWidget *parent = nullptr);
|
||||||
|
/**
|
||||||
|
* @brief Завершает работу окна и сохраняет настройки.
|
||||||
|
*/
|
||||||
~MainWidget() override;
|
~MainWidget() override;
|
||||||
|
|
||||||
protected:
|
protected:
|
||||||
|
/**
|
||||||
|
* @brief Перехватывает закрытие окна для подтверждения выхода.
|
||||||
|
*/
|
||||||
void closeEvent(QCloseEvent *event) override;
|
void closeEvent(QCloseEvent *event) override;
|
||||||
|
|
||||||
private slots:
|
private slots:
|
||||||
|
/**
|
||||||
|
* @brief Показывает контекстное меню таблицы.
|
||||||
|
*/
|
||||||
void onCustomContextMenuRequested(const QPoint &pos);
|
void onCustomContextMenuRequested(const QPoint &pos);
|
||||||
|
/**
|
||||||
|
* @brief Отображает диалог с информацией о версии/репозитории.
|
||||||
|
*/
|
||||||
void onGetInfoTriggered();
|
void onGetInfoTriggered();
|
||||||
|
/**
|
||||||
|
* @brief Перезагружает данные модели из базы.
|
||||||
|
*/
|
||||||
void onReloadTableTriggered();
|
void onReloadTableTriggered();
|
||||||
|
/**
|
||||||
|
* @brief Открывает диалог добавления клиента.
|
||||||
|
*/
|
||||||
void onAddClientTriggered();
|
void onAddClientTriggered();
|
||||||
|
/**
|
||||||
|
* @brief Открывает диалог редактирования выбранного клиента.
|
||||||
|
*/
|
||||||
void onEditClientTriggered();
|
void onEditClientTriggered();
|
||||||
|
/**
|
||||||
|
* @brief Удаляет выбранные строки клиентов.
|
||||||
|
*/
|
||||||
void onDeleteClientTriggered();
|
void onDeleteClientTriggered();
|
||||||
|
/**
|
||||||
|
* @brief Генерирует и сохраняет файл лицензии для выбранного клиента.
|
||||||
|
*/
|
||||||
void onGetLicenseFileTriggered();
|
void onGetLicenseFileTriggered();
|
||||||
|
/**
|
||||||
|
* @brief Обрабатывает закрытие диалога редактирования клиента.
|
||||||
|
*/
|
||||||
void onEditClientDialogClosed(int result);
|
void onEditClientDialogClosed(int result);
|
||||||
|
/**
|
||||||
|
* @brief Обновляет доступность действий при смене выбора.
|
||||||
|
*/
|
||||||
void selectionChanged();
|
void selectionChanged();
|
||||||
|
/**
|
||||||
|
* @brief Обновляет статусную строку при изменении состояния модели.
|
||||||
|
*/
|
||||||
void modelStatusChanged();
|
void modelStatusChanged();
|
||||||
private:
|
private:
|
||||||
|
/**
|
||||||
|
* @brief Сохраняет параметры окна (размер, положение).
|
||||||
|
*/
|
||||||
void saveSettings();
|
void saveSettings();
|
||||||
|
/**
|
||||||
|
* @brief Загружает параметры окна.
|
||||||
|
*/
|
||||||
void loadSettings();
|
void loadSettings();
|
||||||
|
|
||||||
private:
|
private:
|
||||||
QTableView* m_tableView{nullptr};
|
QTableView* m_tableView{nullptr}; //!< Таблица с клиентами.
|
||||||
LicenseModel* m_licenseModel{nullptr};
|
LicenseModel* m_licenseModel{nullptr}; //!< Модель данных лицензий.
|
||||||
QToolBar* m_toolBar{nullptr};
|
QToolBar* m_toolBar{nullptr}; //!< Панель инструментов.
|
||||||
QMenu* m_menu{nullptr};
|
QMenu* m_menu{nullptr}; //!< Контекстное меню таблицы.
|
||||||
QAction* m_addClientToolBarAction{nullptr};
|
QAction* m_addClientToolBarAction{nullptr}; //!< Действие добавления клиента.
|
||||||
QAction* m_editClientToolBarAction{nullptr};
|
QAction* m_editClientToolBarAction{nullptr}; //!< Действие редактирования клиента.
|
||||||
QAction* m_deleteClientToolBarAction{nullptr};
|
QAction* m_deleteClientToolBarAction{nullptr}; //!< Действие удаления клиента.
|
||||||
QAction* m_infoToolBarAction{nullptr};
|
QAction* m_infoToolBarAction{nullptr}; //!< Действие показа информации.
|
||||||
QAction* m_reloadTableMenuAction{nullptr};
|
QAction* m_reloadTableMenuAction{nullptr}; //!< Действие обновления таблицы.
|
||||||
QAction* m_addClientsMenuAction{nullptr};
|
QAction* m_addClientsMenuAction{nullptr}; //!< Действие добавления из меню.
|
||||||
QAction* m_deleteClientsMenuAction{nullptr};
|
QAction* m_deleteClientsMenuAction{nullptr}; //!< Действие удаления из меню.
|
||||||
QAction* m_editClientsMenuAction{nullptr};
|
QAction* m_editClientsMenuAction{nullptr}; //!< Действие редактирования из меню.
|
||||||
QAction* m_getLicenseFileAction{nullptr};
|
QAction* m_getLicenseFileAction{nullptr}; //!< Действие сохранения файла лицензии.
|
||||||
EditClientDialog* m_editClientDialog{nullptr};
|
EditClientDialog* m_editClientDialog{nullptr}; //!< Диалог редактирования клиента.
|
||||||
QStatusBar* m_statusBar{nullptr};
|
QStatusBar* m_statusBar{nullptr}; //!< Строка состояния.
|
||||||
QLabel* m_statusLabel{nullptr};
|
QLabel* m_statusLabel{nullptr}; //!< Текст статуса модели.
|
||||||
};
|
};
|
||||||
|
|
||||||
#endif // LICENSEMANAGER_MAINWIDGET_H
|
#endif // LICENSEMANAGER_MAINWIDGET_H
|
||||||
|
|||||||
17
src/def.h
17
src/def.h
@@ -1,10 +1,25 @@
|
|||||||
#ifndef LICENSEMANAGER_DEF_H
|
#ifndef LICENSEMANAGER_DEF_H
|
||||||
#define LICENSEMANAGER_DEF_H
|
#define LICENSEMANAGER_DEF_H
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @file def.h
|
||||||
|
* @brief Общие константы и базовые настройки приложения.
|
||||||
|
*
|
||||||
|
* @details
|
||||||
|
* Содержит глобальные значения, используемые в разных частях проекта:
|
||||||
|
* путь к базе данных и имя организации для QSettings.
|
||||||
|
*/
|
||||||
|
|
||||||
#include <QDir>
|
#include <QDir>
|
||||||
#include <QString>
|
#include <QString>
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @brief Путь к SQLite базе данных приложения.
|
||||||
|
*/
|
||||||
const static QString DB_PATH = "./db.sqlite";
|
const static QString DB_PATH = "./db.sqlite";
|
||||||
|
/**
|
||||||
|
* @brief Имя организации для QSettings.
|
||||||
|
*/
|
||||||
const static QString ORGANIZATION_NAME = "LicenseManager";
|
const static QString ORGANIZATION_NAME = "LicenseManager";
|
||||||
|
|
||||||
#endif //LICENSEMANAGER_DEF_H
|
#endif //LICENSEMANAGER_DEF_H
|
||||||
|
|||||||
Reference in New Issue
Block a user