Merge branch 'dev' into feature/app-update
|
|
@ -1 +1 @@
|
|||
Subproject commit eb43e90f389745af6d7ca3be92a96e400ba6dc6c
|
||||
Subproject commit ba580dc5bd7784f7b1e110ff0365f3286e549a61
|
||||
2
client/3rd/QJsonStruct/.gitignore
vendored
Normal file
|
|
@ -0,0 +1,2 @@
|
|||
*.user
|
||||
build/
|
||||
19
client/3rd/QJsonStruct/CMakeLists.txt
Normal file
|
|
@ -0,0 +1,19 @@
|
|||
cmake_minimum_required(VERSION 3.5)
|
||||
|
||||
project(QJsonStruct LANGUAGES CXX)
|
||||
|
||||
set(CMAKE_INCLUDE_CURRENT_DIR ON)
|
||||
set(CMAKE_AUTOMOC ON)
|
||||
set(CMAKE_AUTORCC ON)
|
||||
set(CMAKE_CXX_STANDARD 17)
|
||||
set(CMAKE_CXX_STANDARD_REQUIRED ON)
|
||||
|
||||
option(BUILD_TESTING ON)
|
||||
|
||||
include(QJsonStruct.cmake)
|
||||
find_package(Qt5 COMPONENTS Core REQUIRED)
|
||||
|
||||
if(BUILD_TESTING)
|
||||
include(CTest)
|
||||
add_subdirectory(test)
|
||||
endif()
|
||||
21
client/3rd/QJsonStruct/LICENSE
Normal file
|
|
@ -0,0 +1,21 @@
|
|||
MIT License
|
||||
|
||||
Copyright (c) 2020 Qv2ray Workgroup
|
||||
|
||||
Permission is hereby granted, free of charge, to any person obtaining a copy
|
||||
of this software and associated documentation files (the "Software"), to deal
|
||||
in the Software without restriction, including without limitation the rights
|
||||
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
|
||||
copies of the Software, and to permit persons to whom the Software is
|
||||
furnished to do so, subject to the following conditions:
|
||||
|
||||
The above copyright notice and this permission notice shall be included in all
|
||||
copies or substantial portions of the Software.
|
||||
|
||||
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
||||
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
||||
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
|
||||
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
||||
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
|
||||
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
|
||||
SOFTWARE.
|
||||
190
client/3rd/QJsonStruct/QJsonIO.hpp
Normal file
|
|
@ -0,0 +1,190 @@
|
|||
#pragma once
|
||||
#include <QJsonArray>
|
||||
#include <QJsonObject>
|
||||
#include <QJsonValue>
|
||||
#include <QList>
|
||||
#include <tuple>
|
||||
|
||||
enum class QJsonIOPathType
|
||||
{
|
||||
JSONIO_MODE_ARRAY,
|
||||
JSONIO_MODE_OBJECT
|
||||
};
|
||||
|
||||
typedef QPair<QString, QJsonIOPathType> QJsonIONodeType;
|
||||
|
||||
struct QJsonIOPath : QList<QJsonIONodeType>
|
||||
{
|
||||
template<typename type1, typename type2, typename... types>
|
||||
QJsonIOPath(const type1 t1, const type2 t2, const types... ts)
|
||||
{
|
||||
AppendPath(t1);
|
||||
AppendPath(t2);
|
||||
(AppendPath(ts), ...);
|
||||
}
|
||||
|
||||
void AppendPath(size_t index)
|
||||
{
|
||||
append({ QString::number(index), QJsonIOPathType::JSONIO_MODE_ARRAY });
|
||||
}
|
||||
|
||||
void AppendPath(const QString &key)
|
||||
{
|
||||
append({ key, QJsonIOPathType::JSONIO_MODE_OBJECT });
|
||||
}
|
||||
|
||||
template<typename t>
|
||||
QJsonIOPath &operator<<(const t &str)
|
||||
{
|
||||
AppendPath(str);
|
||||
return *this;
|
||||
}
|
||||
|
||||
template<typename t>
|
||||
QJsonIOPath &operator+=(const t &val)
|
||||
{
|
||||
AppendPath(val);
|
||||
return *this;
|
||||
}
|
||||
|
||||
QJsonIOPath &operator<<(const QJsonIOPath &other)
|
||||
{
|
||||
for (const auto &x : other)
|
||||
this->append(x);
|
||||
return *this;
|
||||
}
|
||||
|
||||
template<typename t>
|
||||
QJsonIOPath &operator<<(const t &val) const
|
||||
{
|
||||
auto _new = *this;
|
||||
return _new << val;
|
||||
}
|
||||
|
||||
template<typename t>
|
||||
QJsonIOPath operator+(const t &val) const
|
||||
{
|
||||
auto _new = *this;
|
||||
return _new << val;
|
||||
}
|
||||
|
||||
QJsonIOPath operator+(const QJsonIOPath &other) const
|
||||
{
|
||||
auto _new = *this;
|
||||
for (const auto &x : other)
|
||||
_new.append(x);
|
||||
return _new;
|
||||
}
|
||||
};
|
||||
|
||||
class QJsonIO
|
||||
{
|
||||
public:
|
||||
const static inline QJsonValue Null = QJsonValue::Null;
|
||||
const static inline QJsonValue Undefined = QJsonValue::Undefined;
|
||||
|
||||
template<typename current_key_type, typename... t_other_types>
|
||||
static QJsonValue GetValue(const QJsonValue &parent, const current_key_type ¤t, const t_other_types &...other)
|
||||
{
|
||||
if constexpr (sizeof...(t_other_types) == 0)
|
||||
if constexpr (std::is_integral_v<current_key_type>)
|
||||
return parent.toArray()[current];
|
||||
else
|
||||
return parent.toObject()[current];
|
||||
else if constexpr (std::is_integral_v<current_key_type>)
|
||||
return GetValue(parent.toArray()[current], other...);
|
||||
else
|
||||
return GetValue(parent.toObject()[current], other...);
|
||||
}
|
||||
|
||||
template<typename... key_types_t>
|
||||
static QJsonValue GetValue(QJsonValue value, const std::tuple<key_types_t...> &keys, const QJsonValue &defaultValue = Undefined)
|
||||
{
|
||||
std::apply([&](auto &&...args) { ((value = value[args]), ...); }, keys);
|
||||
return value.isUndefined() ? defaultValue : value;
|
||||
}
|
||||
|
||||
template<typename parent_type, typename t_value_type, typename current_key_type, typename... t_other_key_types>
|
||||
static void SetValue(parent_type &parent, const t_value_type &val, const current_key_type ¤t, const t_other_key_types &...other)
|
||||
{
|
||||
// If current parent is an array, increase its size to fit the "key"
|
||||
if constexpr (std::is_integral_v<current_key_type>)
|
||||
for (auto i = parent.size(); i <= current; i++)
|
||||
parent.insert(i, {});
|
||||
|
||||
// If the t_other_key_types has nothing....
|
||||
// Means we have reached the end of recursion.
|
||||
if constexpr (sizeof...(t_other_key_types) == 0)
|
||||
parent[current] = val;
|
||||
else if constexpr (std::is_integral_v<typename std::tuple_element_t<0, std::tuple<t_other_key_types...>>>)
|
||||
{
|
||||
// Means we still have many keys
|
||||
// So this element is an array.
|
||||
auto _array = parent[current].toArray();
|
||||
SetValue(_array, val, other...);
|
||||
parent[current] = _array;
|
||||
}
|
||||
else
|
||||
{
|
||||
auto _object = parent[current].toObject();
|
||||
SetValue(_object, val, other...);
|
||||
parent[current] = _object;
|
||||
}
|
||||
}
|
||||
|
||||
static QJsonValue GetValue(const QJsonValue &parent, const QJsonIOPath &path, const QJsonValue &defaultValue = QJsonIO::Undefined)
|
||||
{
|
||||
QJsonValue val = parent;
|
||||
for (const auto &[k, t] : path)
|
||||
{
|
||||
if (t == QJsonIOPathType::JSONIO_MODE_ARRAY)
|
||||
val = val.toArray()[k.toInt()];
|
||||
else
|
||||
val = val.toObject()[k];
|
||||
}
|
||||
return val.isUndefined() ? defaultValue : val;
|
||||
}
|
||||
|
||||
template<typename parent_type, typename value_type>
|
||||
static void SetValue(parent_type &parent, const value_type &t, const QJsonIOPath &path)
|
||||
{
|
||||
QList<std::tuple<QString, QJsonIOPathType, QJsonValue>> _stack;
|
||||
QJsonValue lastNode = parent;
|
||||
for (const auto &[key, type] : path)
|
||||
{
|
||||
_stack.prepend({ key, type, lastNode });
|
||||
if (type == QJsonIOPathType::JSONIO_MODE_ARRAY)
|
||||
lastNode = lastNode.toArray().at(key.toInt());
|
||||
else
|
||||
lastNode = lastNode.toObject()[key];
|
||||
}
|
||||
|
||||
lastNode = t;
|
||||
|
||||
for (const auto &[key, type, node] : _stack)
|
||||
{
|
||||
if (type == QJsonIOPathType::JSONIO_MODE_ARRAY)
|
||||
{
|
||||
const auto index = key.toInt();
|
||||
auto nodeArray = node.toArray();
|
||||
for (auto i = nodeArray.size(); i <= index; i++)
|
||||
nodeArray.insert(i, {});
|
||||
nodeArray[index] = lastNode;
|
||||
lastNode = nodeArray;
|
||||
}
|
||||
else
|
||||
{
|
||||
auto nodeObject = node.toObject();
|
||||
nodeObject[key] = lastNode;
|
||||
lastNode = nodeObject;
|
||||
}
|
||||
}
|
||||
|
||||
if constexpr (std::is_same_v<parent_type, QJsonObject>)
|
||||
parent = lastNode.toObject();
|
||||
else if constexpr (std::is_same_v<parent_type, QJsonArray>)
|
||||
parent = lastNode.toArray();
|
||||
else
|
||||
Q_UNREACHABLE();
|
||||
}
|
||||
};
|
||||
5
client/3rd/QJsonStruct/QJsonStruct.cmake
Normal file
|
|
@ -0,0 +1,5 @@
|
|||
include_directories(${CMAKE_CURRENT_LIST_DIR})
|
||||
|
||||
set(QJSONSTRUCT_SOURCES
|
||||
${CMAKE_CURRENT_LIST_DIR}/QJsonStruct.hpp
|
||||
${CMAKE_CURRENT_LIST_DIR}/QJsonIO.hpp)
|
||||
215
client/3rd/QJsonStruct/QJsonStruct.hpp
Normal file
|
|
@ -0,0 +1,215 @@
|
|||
#pragma once
|
||||
#include "macroexpansion.hpp"
|
||||
|
||||
#ifndef _X
|
||||
#include <QJsonArray>
|
||||
#include <QJsonObject>
|
||||
#include <QList>
|
||||
#include <QVariant>
|
||||
#endif
|
||||
|
||||
/// macro to define an operator==
|
||||
#define ___JSONSTRUCT_DEFAULT_COMPARE_IMPL(x) (this->x == ___another___instance__.x) &&
|
||||
#define JSONSTRUCT_COMPARE(CLASS, ...) \
|
||||
bool operator==(const CLASS &___another___instance__) const \
|
||||
{ \
|
||||
return FOR_EACH(___JSONSTRUCT_DEFAULT_COMPARE_IMPL, __VA_ARGS__) true; \
|
||||
}
|
||||
|
||||
// ============================================================================================
|
||||
// Load JSON IMPL
|
||||
#define ___DESERIALIZE_FROM_JSON_CONVERT_B_FUNC_IMPL(name) name::loadJson(___json_object_);
|
||||
#define ___DESERIALIZE_FROM_JSON_CONVERT_A_FUNC(name) ___DESERIALIZE_FROM_JSON_CONVERT_F_FUNC(name)
|
||||
#define ___DESERIALIZE_FROM_JSON_CONVERT_B_FUNC(...) FOREACH_CALL_FUNC_3(___DESERIALIZE_FROM_JSON_CONVERT_B_FUNC_IMPL, __VA_ARGS__)
|
||||
#define ___DESERIALIZE_FROM_JSON_CONVERT_F_FUNC(name) \
|
||||
if (___json_object_.toObject().contains(#name)) \
|
||||
{ \
|
||||
JsonStructHelper::Deserialize(this->name, ___json_object_.toObject()[#name]); \
|
||||
} \
|
||||
else \
|
||||
{ \
|
||||
this->name = ___qjsonstruct_default_check.name; \
|
||||
}
|
||||
|
||||
// ============================================================================================
|
||||
// To JSON IMPL
|
||||
#define ___SERIALIZE_TO_JSON_CONVERT_F_FUNC(name) \
|
||||
if (!(___qjsonstruct_default_check.name == this->name)) \
|
||||
{ \
|
||||
___json_object_.insert(#name, JsonStructHelper::Serialize(name)); \
|
||||
}
|
||||
#define ___SERIALIZE_TO_JSON_CONVERT_A_FUNC(name) ___json_object_.insert(#name, JsonStructHelper::Serialize(name));
|
||||
#define ___SERIALIZE_TO_JSON_CONVERT_B_FUNC_IMPL(name) JsonStructHelper::MergeJson(___json_object_, name::toJson());
|
||||
#define ___SERIALIZE_TO_JSON_CONVERT_B_FUNC(...) FOREACH_CALL_FUNC_3(___SERIALIZE_TO_JSON_CONVERT_B_FUNC_IMPL, __VA_ARGS__)
|
||||
|
||||
// ============================================================================================
|
||||
// Load JSON Wrapper
|
||||
#define ___DESERIALIZE_FROM_JSON_CONVERT_FUNC_DECL_A(...) FOREACH_CALL_FUNC_2(___DESERIALIZE_FROM_JSON_CONVERT_A_FUNC, __VA_ARGS__)
|
||||
#define ___DESERIALIZE_FROM_JSON_CONVERT_FUNC_DECL_F(...) FOREACH_CALL_FUNC_2(___DESERIALIZE_FROM_JSON_CONVERT_F_FUNC, __VA_ARGS__)
|
||||
#define ___DESERIALIZE_FROM_JSON_CONVERT_FUNC_DECL_B(...) FOREACH_CALL_FUNC_2(___DESERIALIZE_FROM_JSON_CONVERT_B_FUNC, __VA_ARGS__)
|
||||
#define ___DESERIALIZE_FROM_JSON_EXTRACT_B_F(name_option) ___DESERIALIZE_FROM_JSON_CONVERT_FUNC_DECL_##name_option
|
||||
|
||||
// ============================================================================================
|
||||
// To JSON Wrapper
|
||||
#define ___SERIALIZE_TO_JSON_CONVERT_FUNC_DECL_A(...) FOREACH_CALL_FUNC_2(___SERIALIZE_TO_JSON_CONVERT_A_FUNC, __VA_ARGS__)
|
||||
#define ___SERIALIZE_TO_JSON_CONVERT_FUNC_DECL_F(...) FOREACH_CALL_FUNC_2(___SERIALIZE_TO_JSON_CONVERT_F_FUNC, __VA_ARGS__)
|
||||
#define ___SERIALIZE_TO_JSON_CONVERT_FUNC_DECL_B(...) FOREACH_CALL_FUNC_2(___SERIALIZE_TO_JSON_CONVERT_B_FUNC, __VA_ARGS__)
|
||||
#define ___SERIALIZE_TO_JSON_EXTRACT_B_F(name_option) ___SERIALIZE_TO_JSON_CONVERT_FUNC_DECL_##name_option
|
||||
|
||||
// ============================================================================================
|
||||
#define JSONSTRUCT_REGISTER_NOCOPYMOVE(___class_type_, ...) \
|
||||
void loadJson(const QJsonValue &___json_object_) \
|
||||
{ \
|
||||
___class_type_ ___qjsonstruct_default_check; \
|
||||
FOREACH_CALL_FUNC(___DESERIALIZE_FROM_JSON_EXTRACT_B_F, __VA_ARGS__); \
|
||||
} \
|
||||
[[nodiscard]] const QJsonObject toJson() const \
|
||||
{ \
|
||||
___class_type_ ___qjsonstruct_default_check; \
|
||||
QJsonObject ___json_object_; \
|
||||
FOREACH_CALL_FUNC(___SERIALIZE_TO_JSON_EXTRACT_B_F, __VA_ARGS__); \
|
||||
return ___json_object_; \
|
||||
}
|
||||
|
||||
#define JSONSTRUCT_REGISTER(___class_type_, ...) \
|
||||
JSONSTRUCT_REGISTER_NOCOPYMOVE(___class_type_, __VA_ARGS__); \
|
||||
[[nodiscard]] static auto fromJson(const QJsonValue &___json_object_) \
|
||||
{ \
|
||||
___class_type_ _t; \
|
||||
_t.loadJson(___json_object_); \
|
||||
return _t; \
|
||||
}
|
||||
|
||||
#define ___DECL_JSON_STRUCT_LOAD_SIMPLE_TYPE_FUNC(type, convert_func) \
|
||||
static void Deserialize(type &t, const QJsonValue &d) \
|
||||
{ \
|
||||
t = d.convert_func; \
|
||||
}
|
||||
|
||||
class JsonStructHelper
|
||||
{
|
||||
public:
|
||||
static void MergeJson(QJsonObject &mergeTo, const QJsonObject &mergeIn)
|
||||
{
|
||||
for (const auto &key : mergeIn.keys())
|
||||
mergeTo[key] = mergeIn.value(key);
|
||||
}
|
||||
//
|
||||
template<typename T>
|
||||
static void Deserialize(T &t, const QJsonValue &d)
|
||||
{
|
||||
if constexpr (std::is_enum_v<T>)
|
||||
t = (T) d.toInt();
|
||||
else if constexpr (std::is_same_v<T, QJsonObject>)
|
||||
t = d.toObject();
|
||||
else if constexpr (std::is_same_v<T, QJsonArray>)
|
||||
t = d.toArray();
|
||||
else
|
||||
t.loadJson(d);
|
||||
}
|
||||
|
||||
___DECL_JSON_STRUCT_LOAD_SIMPLE_TYPE_FUNC(QString, toString());
|
||||
___DECL_JSON_STRUCT_LOAD_SIMPLE_TYPE_FUNC(QChar, toVariant().toChar());
|
||||
___DECL_JSON_STRUCT_LOAD_SIMPLE_TYPE_FUNC(std::string, toString().toStdString());
|
||||
___DECL_JSON_STRUCT_LOAD_SIMPLE_TYPE_FUNC(std::wstring, toString().toStdWString());
|
||||
___DECL_JSON_STRUCT_LOAD_SIMPLE_TYPE_FUNC(bool, toBool());
|
||||
___DECL_JSON_STRUCT_LOAD_SIMPLE_TYPE_FUNC(double, toDouble());
|
||||
___DECL_JSON_STRUCT_LOAD_SIMPLE_TYPE_FUNC(float, toVariant().toFloat());
|
||||
___DECL_JSON_STRUCT_LOAD_SIMPLE_TYPE_FUNC(int, toInt());
|
||||
___DECL_JSON_STRUCT_LOAD_SIMPLE_TYPE_FUNC(long, toVariant().toLongLong());
|
||||
___DECL_JSON_STRUCT_LOAD_SIMPLE_TYPE_FUNC(long long, toVariant().toLongLong());
|
||||
___DECL_JSON_STRUCT_LOAD_SIMPLE_TYPE_FUNC(unsigned int, toVariant().toUInt());
|
||||
___DECL_JSON_STRUCT_LOAD_SIMPLE_TYPE_FUNC(unsigned long, toVariant().toULongLong());
|
||||
___DECL_JSON_STRUCT_LOAD_SIMPLE_TYPE_FUNC(unsigned long long, toVariant().toULongLong());
|
||||
|
||||
template<typename T>
|
||||
static void Deserialize(QList<T> &t, const QJsonValue &d)
|
||||
{
|
||||
t.clear();
|
||||
for (const auto &val : d.toArray())
|
||||
{
|
||||
T data;
|
||||
Deserialize(data, val);
|
||||
t.push_back(data);
|
||||
}
|
||||
}
|
||||
|
||||
template<typename TKey, typename TValue>
|
||||
static void Deserialize(QMap<TKey, TValue> &t, const QJsonValue &d)
|
||||
{
|
||||
t.clear();
|
||||
const auto &jsonObject = d.toObject();
|
||||
TKey keyVal;
|
||||
TValue valueVal;
|
||||
for (const auto &key : jsonObject.keys())
|
||||
{
|
||||
Deserialize(keyVal, key);
|
||||
Deserialize(valueVal, jsonObject.value(key));
|
||||
t.insert(keyVal, valueVal);
|
||||
}
|
||||
}
|
||||
|
||||
// =========================== Store Json Data ===========================
|
||||
|
||||
template<typename T>
|
||||
static QJsonValue Serialize(const T &t)
|
||||
{
|
||||
if constexpr (std::is_enum_v<T>)
|
||||
return (int) t;
|
||||
else if constexpr (std::is_same_v<T, QJsonObject> || std::is_same_v<T, QJsonArray>)
|
||||
return t;
|
||||
else
|
||||
return t.toJson();
|
||||
}
|
||||
|
||||
#define pure_func(x) (x)
|
||||
#define ___DECL_JSON_STRUCT_STORE_SIMPLE_TYPE_FUNC(type) \
|
||||
static QJsonValue Serialize(const type &t) \
|
||||
{ \
|
||||
return QJsonValue(t); \
|
||||
}
|
||||
___DECL_JSON_STRUCT_STORE_SIMPLE_TYPE_FUNC(int);
|
||||
___DECL_JSON_STRUCT_STORE_SIMPLE_TYPE_FUNC(bool);
|
||||
___DECL_JSON_STRUCT_STORE_SIMPLE_TYPE_FUNC(QJsonArray);
|
||||
___DECL_JSON_STRUCT_STORE_SIMPLE_TYPE_FUNC(QJsonObject);
|
||||
___DECL_JSON_STRUCT_STORE_SIMPLE_TYPE_FUNC(QString);
|
||||
___DECL_JSON_STRUCT_STORE_SIMPLE_TYPE_FUNC(long long);
|
||||
___DECL_JSON_STRUCT_STORE_SIMPLE_TYPE_FUNC(float);
|
||||
___DECL_JSON_STRUCT_STORE_SIMPLE_TYPE_FUNC(double);
|
||||
|
||||
#define ___DECL_JSON_STRUCT_STORE_VARIANT_TYPE_FUNC(type, func) \
|
||||
static QJsonValue Serialize(const type &t) \
|
||||
{ \
|
||||
return QJsonValue::fromVariant(func); \
|
||||
}
|
||||
|
||||
___DECL_JSON_STRUCT_STORE_VARIANT_TYPE_FUNC(std::string, QString::fromStdString(t))
|
||||
___DECL_JSON_STRUCT_STORE_VARIANT_TYPE_FUNC(std::wstring, QString::fromStdWString(t))
|
||||
___DECL_JSON_STRUCT_STORE_VARIANT_TYPE_FUNC(long, QVariant::fromValue<long>(t))
|
||||
___DECL_JSON_STRUCT_STORE_VARIANT_TYPE_FUNC(unsigned int, QVariant::fromValue<unsigned int>(t))
|
||||
___DECL_JSON_STRUCT_STORE_VARIANT_TYPE_FUNC(unsigned long, QVariant::fromValue<unsigned long>(t))
|
||||
___DECL_JSON_STRUCT_STORE_VARIANT_TYPE_FUNC(unsigned long long, QVariant::fromValue<unsigned long long>(t))
|
||||
|
||||
template<typename TValue>
|
||||
static QJsonValue Serialize(const QMap<QString, TValue> &t)
|
||||
{
|
||||
QJsonObject mapObject;
|
||||
for (const auto &key : t.keys())
|
||||
{
|
||||
auto valueVal = Serialize(t.value(key));
|
||||
mapObject.insert(key, valueVal);
|
||||
}
|
||||
return mapObject;
|
||||
}
|
||||
|
||||
template<typename T>
|
||||
static QJsonValue Serialize(const QList<T> &t)
|
||||
{
|
||||
QJsonArray listObject;
|
||||
for (const auto &item : t)
|
||||
{
|
||||
listObject.push_back(Serialize(item));
|
||||
}
|
||||
return listObject;
|
||||
}
|
||||
};
|
||||
74
client/3rd/QJsonStruct/macroexpansion.hpp
Normal file
|
|
@ -0,0 +1,74 @@
|
|||
#pragma once
|
||||
|
||||
#define CONCATENATE1(arg1, arg2) CONCATENATE2(arg1, arg2)
|
||||
#define CONCATENATE2(arg1, arg2) arg1##arg2
|
||||
#define CONCATENATE(x, y) x##y
|
||||
|
||||
#define EXPAND(...) __VA_ARGS__
|
||||
#define FOR_EACH_1(what, x, ...) what(x)
|
||||
#define FOR_EACH_2(what, x, ...) what(x) EXPAND(FOR_EACH_1(what, __VA_ARGS__))
|
||||
#define FOR_EACH_3(what, x, ...) what(x) EXPAND(FOR_EACH_2(what, __VA_ARGS__))
|
||||
#define FOR_EACH_4(what, x, ...) what(x) EXPAND(FOR_EACH_3(what, __VA_ARGS__))
|
||||
#define FOR_EACH_5(what, x, ...) what(x) EXPAND(FOR_EACH_4(what, __VA_ARGS__))
|
||||
#define FOR_EACH_6(what, x, ...) what(x) EXPAND(FOR_EACH_5(what, __VA_ARGS__))
|
||||
#define FOR_EACH_7(what, x, ...) what(x) EXPAND(FOR_EACH_6(what, __VA_ARGS__))
|
||||
#define FOR_EACH_8(what, x, ...) what(x) EXPAND(FOR_EACH_7(what, __VA_ARGS__))
|
||||
#define FOR_EACH_9(what, x, ...) what(x) EXPAND(FOR_EACH_8(what, __VA_ARGS__))
|
||||
#define FOR_EACH_10(what, x, ...) what(x) EXPAND(FOR_EACH_9(what, __VA_ARGS__))
|
||||
#define FOR_EACH_11(what, x, ...) what(x) EXPAND(FOR_EACH_10(what, __VA_ARGS__))
|
||||
#define FOR_EACH_12(what, x, ...) what(x) EXPAND(FOR_EACH_11(what, __VA_ARGS__))
|
||||
#define FOR_EACH_13(what, x, ...) what(x) EXPAND(FOR_EACH_12(what, __VA_ARGS__))
|
||||
#define FOR_EACH_14(what, x, ...) what(x) EXPAND(FOR_EACH_13(what, __VA_ARGS__))
|
||||
#define FOR_EACH_15(what, x, ...) what(x) EXPAND(FOR_EACH_14(what, __VA_ARGS__))
|
||||
#define FOR_EACH_16(what, x, ...) what(x) EXPAND(FOR_EACH_15(what, __VA_ARGS__))
|
||||
|
||||
#define FOR_EACH_NARG(...) FOR_EACH_NARG_(__VA_ARGS__, FOR_EACH_RSEQ_N())
|
||||
#define FOR_EACH_NARG_(...) EXPAND(FOR_EACH_ARG_N(__VA_ARGS__))
|
||||
#define FOR_EACH_ARG_N(_1, _2, _3, _4, _5, _6, _7, _8, _9, _10, _11, _12, _13, _14, _15, _16, N, ...) N
|
||||
#define FOR_EACH_RSEQ_N() 16, 15, 14, 13, 12, 11, 10, 9, 8, 7, 6, 5, 4, 3, 2, 1, 0
|
||||
|
||||
#define FOR_EACH_(N, what, ...) EXPAND(CONCATENATE(FOR_EACH_, N)(what, __VA_ARGS__))
|
||||
#define FOR_EACH(what, ...) FOR_EACH_(FOR_EACH_NARG(__VA_ARGS__), what, __VA_ARGS__)
|
||||
#define FOREACH_CALL_FUNC(func, ...) FOR_EACH(func, __VA_ARGS__)
|
||||
|
||||
// Bad hack ==========================================================================================================================
|
||||
#define _2X_FOR_EACH_1(what, x, ...) what(x)
|
||||
#define _2X_FOR_EACH_2(what, x, ...) what(x) EXPAND(_2X_FOR_EACH_1(what, __VA_ARGS__))
|
||||
#define _2X_FOR_EACH_3(what, x, ...) what(x) EXPAND(_2X_FOR_EACH_2(what, __VA_ARGS__))
|
||||
#define _2X_FOR_EACH_4(what, x, ...) what(x) EXPAND(_2X_FOR_EACH_3(what, __VA_ARGS__))
|
||||
#define _2X_FOR_EACH_5(what, x, ...) what(x) EXPAND(_2X_FOR_EACH_4(what, __VA_ARGS__))
|
||||
#define _2X_FOR_EACH_6(what, x, ...) what(x) EXPAND(_2X_FOR_EACH_5(what, __VA_ARGS__))
|
||||
#define _2X_FOR_EACH_7(what, x, ...) what(x) EXPAND(_2X_FOR_EACH_6(what, __VA_ARGS__))
|
||||
#define _2X_FOR_EACH_8(what, x, ...) what(x) EXPAND(_2X_FOR_EACH_7(what, __VA_ARGS__))
|
||||
#define _2X_FOR_EACH_9(what, x, ...) what(x) EXPAND(_2X_FOR_EACH_8(what, __VA_ARGS__))
|
||||
#define _2X_FOR_EACH_10(what, x, ...) what(x) EXPAND(_2X_FOR_EACH_9(what, __VA_ARGS__))
|
||||
#define _2X_FOR_EACH_11(what, x, ...) what(x) EXPAND(_2X_FOR_EACH_10(what, __VA_ARGS__))
|
||||
#define _2X_FOR_EACH_12(what, x, ...) what(x) EXPAND(_2X_FOR_EACH_11(what, __VA_ARGS__))
|
||||
#define _2X_FOR_EACH_13(what, x, ...) what(x) EXPAND(_2X_FOR_EACH_12(what, __VA_ARGS__))
|
||||
#define _2X_FOR_EACH_14(what, x, ...) what(x) EXPAND(_2X_FOR_EACH_13(what, __VA_ARGS__))
|
||||
#define _2X_FOR_EACH_15(what, x, ...) what(x) EXPAND(_2X_FOR_EACH_14(what, __VA_ARGS__))
|
||||
#define _2X_FOR_EACH_16(what, x, ...) what(x) EXPAND(_2X_FOR_EACH_15(what, __VA_ARGS__))
|
||||
#define _2X_FOR_EACH_(N, what, ...) EXPAND(CONCATENATE(_2X_FOR_EACH_, N)(what, __VA_ARGS__))
|
||||
#define _2X_FOR_EACH(what, ...) _2X_FOR_EACH_(FOR_EACH_NARG(__VA_ARGS__), what, __VA_ARGS__)
|
||||
#define FOREACH_CALL_FUNC_2(func, ...) _2X_FOR_EACH(func, __VA_ARGS__)
|
||||
|
||||
// Bad hack ==========================================================================================================================
|
||||
#define _3X_FOR_EACH_1(what, x, ...) what(x)
|
||||
#define _3X_FOR_EACH_2(what, x, ...) what(x) EXPAND(_3X_FOR_EACH_1(what, __VA_ARGS__))
|
||||
#define _3X_FOR_EACH_3(what, x, ...) what(x) EXPAND(_3X_FOR_EACH_2(what, __VA_ARGS__))
|
||||
#define _3X_FOR_EACH_4(what, x, ...) what(x) EXPAND(_3X_FOR_EACH_3(what, __VA_ARGS__))
|
||||
#define _3X_FOR_EACH_5(what, x, ...) what(x) EXPAND(_3X_FOR_EACH_4(what, __VA_ARGS__))
|
||||
#define _3X_FOR_EACH_6(what, x, ...) what(x) EXPAND(_3X_FOR_EACH_5(what, __VA_ARGS__))
|
||||
#define _3X_FOR_EACH_7(what, x, ...) what(x) EXPAND(_3X_FOR_EACH_6(what, __VA_ARGS__))
|
||||
#define _3X_FOR_EACH_8(what, x, ...) what(x) EXPAND(_3X_FOR_EACH_7(what, __VA_ARGS__))
|
||||
#define _3X_FOR_EACH_9(what, x, ...) what(x) EXPAND(_3X_FOR_EACH_8(what, __VA_ARGS__))
|
||||
#define _3X_FOR_EACH_10(what, x, ...) what(x) EXPAND(_3X_FOR_EACH_9(what, __VA_ARGS__))
|
||||
#define _3X_FOR_EACH_11(what, x, ...) what(x) EXPAND(_3X_FOR_EACH_10(what, __VA_ARGS__))
|
||||
#define _3X_FOR_EACH_12(what, x, ...) what(x) EXPAND(_3X_FOR_EACH_11(what, __VA_ARGS__))
|
||||
#define _3X_FOR_EACH_13(what, x, ...) what(x) EXPAND(_3X_FOR_EACH_12(what, __VA_ARGS__))
|
||||
#define _3X_FOR_EACH_14(what, x, ...) what(x) EXPAND(_3X_FOR_EACH_13(what, __VA_ARGS__))
|
||||
#define _3X_FOR_EACH_15(what, x, ...) what(x) EXPAND(_3X_FOR_EACH_14(what, __VA_ARGS__))
|
||||
#define _3X_FOR_EACH_16(what, x, ...) what(x) EXPAND(_3X_FOR_EACH_15(what, __VA_ARGS__))
|
||||
#define _3X_FOR_EACH_(N, what, ...) EXPAND(CONCATENATE(_3X_FOR_EACH_, N)(what, __VA_ARGS__))
|
||||
#define _3X_FOR_EACH(what, ...) _3X_FOR_EACH_(FOR_EACH_NARG(__VA_ARGS__), what, __VA_ARGS__)
|
||||
#define FOREACH_CALL_FUNC_3(func, ...) _3X_FOR_EACH(func, __VA_ARGS__)
|
||||
16
client/3rd/QJsonStruct/test/CMakeLists.txt
Normal file
|
|
@ -0,0 +1,16 @@
|
|||
function(QJSONSTRUCT_ADD_TEST TEST_NAME TEST_SOURCE)
|
||||
add_executable(${TEST_NAME} ${TEST_SOURCE} catch.hpp ${QJSONSTRUCT_SOURCES})
|
||||
target_include_directories(${TEST_NAME}
|
||||
PRIVATE
|
||||
$<BUILD_INTERFACE:${CMAKE_CURRENT_SOURCE_DIR}>
|
||||
)
|
||||
target_link_libraries(
|
||||
${TEST_NAME}
|
||||
PRIVATE
|
||||
Qt::Core
|
||||
)
|
||||
add_test(NAME QJSONSTRUCT_TEST_${TEST_NAME} COMMAND $<TARGET_FILE:${TEST_NAME}> -s)
|
||||
endfunction()
|
||||
|
||||
QJSONSTRUCT_ADD_TEST(serialization serialize/main.cpp)
|
||||
#QJSONSTRUCT_ADD_TEST(serialize_strings serialize/strings.cpp)
|
||||
45
client/3rd/QJsonStruct/test/TestIO.hpp
Normal file
|
|
@ -0,0 +1,45 @@
|
|||
#pragma once
|
||||
#include "QJsonStruct.hpp"
|
||||
#ifndef _X
|
||||
#include <QList>
|
||||
#include <QString>
|
||||
#include <QStringList>
|
||||
#endif
|
||||
|
||||
struct BaseStruct
|
||||
{
|
||||
QString baseStr;
|
||||
int o;
|
||||
JSONSTRUCT_REGISTER(BaseStruct, F(baseStr, o))
|
||||
};
|
||||
|
||||
struct BaseStruct2
|
||||
{
|
||||
QString baseStr2;
|
||||
int o2;
|
||||
JSONSTRUCT_REGISTER(BaseStruct, F(baseStr2, o2))
|
||||
};
|
||||
struct TestInnerStruct
|
||||
: BaseStruct
|
||||
, BaseStruct2
|
||||
{
|
||||
QJsonObject jobj;
|
||||
QJsonArray jarray;
|
||||
QString str;
|
||||
JSONSTRUCT_REGISTER(TestInnerStruct, B(BaseStruct, BaseStruct2), F(str, jobj, jarray))
|
||||
};
|
||||
|
||||
struct JsonIOTest
|
||||
{
|
||||
QString str;
|
||||
QList<int> listOfNumber;
|
||||
QList<bool> listOfBool;
|
||||
QList<QString> listOfString;
|
||||
QList<QList<QString>> listOfListOfString;
|
||||
|
||||
QMap<QString, QString> map;
|
||||
TestInnerStruct inner;
|
||||
|
||||
JSONSTRUCT_REGISTER(JsonIOTest, F(str, listOfNumber, listOfBool, listOfString, listOfListOfString, map, inner));
|
||||
JsonIOTest(){};
|
||||
};
|
||||
19
client/3rd/QJsonStruct/test/TestOut.hpp
Normal file
|
|
@ -0,0 +1,19 @@
|
|||
#pragma once
|
||||
#include "QJsonStruct.hpp"
|
||||
|
||||
struct SubData
|
||||
{
|
||||
QString subString;
|
||||
JSONSTRUCT_REGISTER_TOJSON(subString)
|
||||
};
|
||||
|
||||
struct ToJsonOnlyData
|
||||
{
|
||||
QString x;
|
||||
int y;
|
||||
int z;
|
||||
QList<int> ints;
|
||||
SubData sub;
|
||||
QMap<QString, SubData> subs;
|
||||
JSONSTRUCT_REGISTER_TOJSON(x, y, z, sub, ints, subs)
|
||||
};
|
||||
17698
client/3rd/QJsonStruct/test/catch.hpp
Normal file
70
client/3rd/QJsonStruct/test/main.cpp
Normal file
|
|
@ -0,0 +1,70 @@
|
|||
#include "QJsonIO.hpp"
|
||||
#include "QJsonStruct.hpp"
|
||||
#include "TestIO.hpp"
|
||||
#include "TestOut.hpp"
|
||||
|
||||
#include <QCoreApplication>
|
||||
#include <QJsonDocument>
|
||||
#include <iostream>
|
||||
|
||||
int main(int argc, char *argv[])
|
||||
{
|
||||
Q_UNUSED(argc)
|
||||
Q_UNUSED(argv)
|
||||
|
||||
{
|
||||
ToJsonOnlyData data;
|
||||
data.x = "1string";
|
||||
data.y = 2;
|
||||
data.ints << 0;
|
||||
data.ints << 100;
|
||||
data.ints << 900;
|
||||
data.sub.subString = "subs";
|
||||
data.subs["subs-1"] = { "subs1-data" };
|
||||
data.subs["subs-2"] = { "subs2-data" };
|
||||
data.subs["subs-3"] = { "subs3-data" };
|
||||
data.z = 3;
|
||||
auto x = data.toJson();
|
||||
std::cout << QJsonDocument(x).toJson().toStdString() << std::endl;
|
||||
}
|
||||
//
|
||||
{
|
||||
auto f = JsonIOTest::fromJson( //
|
||||
QJsonObject{
|
||||
{ "inner", QJsonObject{ { "str", "innerString" }, //
|
||||
{ "jobj", QJsonObject{ { "key", "value" } } }, //
|
||||
{ "jarray", QJsonArray{ "array0", "array1", "array2" } }, //
|
||||
{ "baseStr", "baseInnerString" } } }, //
|
||||
{ "str", "data1" }, //
|
||||
{ "map", QJsonObject{ { "mapStr", "mapData" } } }, //
|
||||
{ "listOfString", QJsonArray{ "1", "2", "3", "4", "5" } }, //
|
||||
{ "listOfNumber", QJsonArray{ 1, 2, 3, 4, 5 } }, //
|
||||
{ "listOfBool", QJsonArray{ true, false, false, true, true } }, //
|
||||
{ "listOfListOfString", QJsonArray{ QJsonArray{ "1" }, //
|
||||
QJsonArray{ "1", "2" }, //
|
||||
QJsonArray{ "1", "2", "3" }, //
|
||||
QJsonArray{ "1", "2", "3", "4" }, //
|
||||
QJsonArray{ "1", "2", "3", "4", "5" } } }, //
|
||||
});
|
||||
auto x = f.toJson();
|
||||
std::cout << QJsonDocument(x).toJson().toStdString() << std::endl;
|
||||
}
|
||||
{
|
||||
QJsonObject obj{
|
||||
{ "inner", QJsonObject{ { "str", "innerString" }, { "baseStr", "baseInnerString" } } }, //
|
||||
{ "str", "data1" }, //
|
||||
{ "map", QJsonObject{ { "mapStr", "mapData" } } }, //
|
||||
{ "listOfString", QJsonArray{ "1", "2", "3", "4", "5" } }, //
|
||||
{ "listOfNumber", QJsonArray{ 1, 2, 3, 4, 5 } }, //
|
||||
{ "listOfBool", QJsonArray{ true, false, false, true, true } }, //
|
||||
{ "listOfListOfString", QJsonArray{ QJsonArray{ "1" }, //
|
||||
QJsonArray{ "1", "2" }, //
|
||||
QJsonArray{ "1", "2", "3" }, //
|
||||
QJsonArray{ "1", "2", "3", "4" }, //
|
||||
QJsonArray{ "1", "2", "3", "4", "5" } } }, //
|
||||
};
|
||||
auto y = QJsonIO::GetValue(obj, std::tuple{ "listOfListOfString", 2 });
|
||||
y.toObject();
|
||||
}
|
||||
return 0;
|
||||
}
|
||||
181
client/3rd/QJsonStruct/test/serialize/main.cpp
Normal file
|
|
@ -0,0 +1,181 @@
|
|||
#include "QJsonStruct.hpp"
|
||||
#define CATCH_CONFIG_MAIN
|
||||
#include "catch.hpp"
|
||||
|
||||
const static inline auto INT_TEST_MAX = std::numeric_limits<int>::max() - 1;
|
||||
const static inline auto INT_TEST_MIN = -(std::numeric_limits<int>::min() + 1);
|
||||
|
||||
#define SINGLE_ELEMENT_CLASS_DECL(CLASS, TYPE, field, defaultvalue, existance) \
|
||||
class CLASS \
|
||||
{ \
|
||||
public: \
|
||||
TYPE field = defaultvalue; \
|
||||
JSONSTRUCT_REGISTER(CLASS, existance(field)); \
|
||||
};
|
||||
|
||||
// SINGLE_ELEMENT_REQUIRE( CLASS_NAME , TYPE , FIELD , DEFAULT_VALUE , SET VALUE , CHECK VALUE )
|
||||
#define SINGLE_ELEMENT_REQUIRE(CLASS, TYPE, field, defaultvalue, value, checkvalue, existance) \
|
||||
SINGLE_ELEMENT_CLASS_DECL(CLASS, TYPE, field, defaultvalue, existance); \
|
||||
CLASS CLASS##_class; \
|
||||
CLASS##_class.field = value; \
|
||||
REQUIRE(CLASS##_class.toJson()[#field] == checkvalue);
|
||||
|
||||
using namespace std;
|
||||
SCENARIO("Test Serialization", "[Serialize]")
|
||||
{
|
||||
GIVEN("Single Element")
|
||||
{
|
||||
const static QList<QString> defaultList{ "entry 1", "entry 2" };
|
||||
const static QMap<QString, QString> defaultMap{ { "key1", "value1" }, { "key2", "value2" } };
|
||||
typedef QMap<QString, QString> QStringQStringMap;
|
||||
|
||||
WHEN("Serialize a single element")
|
||||
{
|
||||
const static QStringQStringMap setValueMap{ { "newkey1", "newvalue1" } };
|
||||
const static QJsonObject setValueJson{ { "newkey1", QJsonValue{ "newvalue1" } } };
|
||||
|
||||
SINGLE_ELEMENT_REQUIRE(QStringTest_Empty, QString, a, "empty", "", "", F);
|
||||
SINGLE_ELEMENT_REQUIRE(QStringTest, QString, a, "empty", "Some QString", "Some QString", F);
|
||||
SINGLE_ELEMENT_REQUIRE(QStringTest_WithQoutes, QString, a, "empty", "\"", "\"", F);
|
||||
SINGLE_ELEMENT_REQUIRE(QStringTest_zint, int, a, -10, 0, 0, F);
|
||||
SINGLE_ELEMENT_REQUIRE(QStringTest_nint, int, a, -10, 1, 1, F);
|
||||
SINGLE_ELEMENT_REQUIRE(QStringTest_pint, int, a, -10, -1, -1, F);
|
||||
SINGLE_ELEMENT_REQUIRE(QStringTest_pmint, int, a, -1, INT_TEST_MAX, INT_TEST_MAX, F);
|
||||
SINGLE_ELEMENT_REQUIRE(QStringTest_zmint, int, a, -1, INT_TEST_MIN, INT_TEST_MIN, F);
|
||||
SINGLE_ELEMENT_REQUIRE(QStringTest_zuint, uint, a, -10, 0, 0, F);
|
||||
SINGLE_ELEMENT_REQUIRE(QStringTest_puint, uint, a, -10, 1, 1, F);
|
||||
SINGLE_ELEMENT_REQUIRE(BoolTest_True, bool, a, false, true, true, F);
|
||||
SINGLE_ELEMENT_REQUIRE(BoolTest_False, bool, a, true, false, false, F);
|
||||
SINGLE_ELEMENT_REQUIRE(StdStringTest, string, a, "def", "std::string _test", "std::string _test", F);
|
||||
SINGLE_ELEMENT_REQUIRE(QListTest, QList<QString>, a, defaultList, { "newEntry" }, QJsonArray{ "newEntry" }, F);
|
||||
SINGLE_ELEMENT_REQUIRE(QMapTest, QStringQStringMap, a, defaultMap, {}, QJsonObject{}, F);
|
||||
SINGLE_ELEMENT_REQUIRE(QMapValueTest, QStringQStringMap, a, defaultMap, setValueMap, setValueJson, F);
|
||||
}
|
||||
|
||||
WHEN("Serialize a default value")
|
||||
{
|
||||
SINGLE_ELEMENT_REQUIRE(DefaultQString, QString, a, "defaultvalue", "defaultvalue", QJsonValue::Undefined, F);
|
||||
SINGLE_ELEMENT_REQUIRE(DefaultInteger, int, a, 12345, 12345, QJsonValue::Undefined, F);
|
||||
SINGLE_ELEMENT_REQUIRE(DefaultList, QList<QString>, a, defaultList, defaultList, QJsonValue::Undefined, F);
|
||||
SINGLE_ELEMENT_REQUIRE(DefaultMap, QStringQStringMap, a, defaultMap, defaultMap, QJsonValue::Undefined, F);
|
||||
}
|
||||
|
||||
WHEN("Serialize a force existance default value")
|
||||
{
|
||||
const static QJsonArray defaultListJson{ "entry 1", "entry 2" };
|
||||
const static QJsonObject defaultMapJson{ { "key1", "value1" }, { "key2", "value2" } };
|
||||
SINGLE_ELEMENT_REQUIRE(DefaultQString, QString, a, "defaultvalue", "defaultvalue", "defaultvalue", A);
|
||||
SINGLE_ELEMENT_REQUIRE(DefaultInteger, int, a, 12345, 12345, 12345, A);
|
||||
SINGLE_ELEMENT_REQUIRE(DefaultList, QList<QString>, a, defaultList, defaultList, defaultListJson, A);
|
||||
SINGLE_ELEMENT_REQUIRE(DefaultMap, QStringQStringMap, a, defaultMap, defaultMap, defaultMapJson, A);
|
||||
}
|
||||
}
|
||||
|
||||
GIVEN("Multiple Simple Elements")
|
||||
{
|
||||
WHEN("Can Omit Default Value")
|
||||
{
|
||||
class MultipleNonDefaultElementTestClass
|
||||
{
|
||||
public:
|
||||
QString astring;
|
||||
int integer = 0;
|
||||
double adouble = 0.0;
|
||||
QList<QString> myList;
|
||||
JSONSTRUCT_REGISTER(MultipleNonDefaultElementTestClass, F(astring, integer, adouble, myList))
|
||||
};
|
||||
MultipleNonDefaultElementTestClass instance;
|
||||
const auto json = instance.toJson();
|
||||
REQUIRE(json["astring"] == QJsonValue::Undefined);
|
||||
REQUIRE(json["integer"] == QJsonValue::Undefined);
|
||||
REQUIRE(json["adouble"] == QJsonValue::Undefined);
|
||||
REQUIRE(json["myList"] == QJsonValue::Undefined);
|
||||
}
|
||||
|
||||
WHEN("Forcing Existance")
|
||||
{
|
||||
class MultipleNonDefaultExistanceElementTestClass
|
||||
{
|
||||
public:
|
||||
QString astring;
|
||||
int integer = 0;
|
||||
double adouble = 0.0;
|
||||
QList<QString> myList;
|
||||
JSONSTRUCT_REGISTER(MultipleNonDefaultExistanceElementTestClass, A(astring, integer, adouble, myList))
|
||||
};
|
||||
MultipleNonDefaultExistanceElementTestClass instance;
|
||||
const auto json = instance.toJson();
|
||||
REQUIRE(json["astring"] == "");
|
||||
REQUIRE(json["integer"] == 0);
|
||||
REQUIRE(json["adouble"] == 0.0);
|
||||
REQUIRE(json["myList"] == QJsonArray{});
|
||||
}
|
||||
}
|
||||
|
||||
GIVEN("Nested Elements")
|
||||
{
|
||||
WHEN("Can Omit Default Value")
|
||||
{
|
||||
class Parent
|
||||
{
|
||||
class NestedChild
|
||||
{
|
||||
class NestedChild2
|
||||
{
|
||||
public:
|
||||
int childChildInt = 13579;
|
||||
JSONSTRUCT_COMPARE(NestedChild2, childChildInt)
|
||||
JSONSTRUCT_REGISTER(NestedChild2, F(childChildInt))
|
||||
};
|
||||
|
||||
public:
|
||||
int childInt = 54321;
|
||||
QString childQString = "A QString";
|
||||
NestedChild2 anotherChild;
|
||||
JSONSTRUCT_COMPARE(NestedChild, childInt, childQString, anotherChild)
|
||||
JSONSTRUCT_REGISTER(NestedChild, F(childInt, childQString, anotherChild))
|
||||
};
|
||||
|
||||
public:
|
||||
int parentInt = 12345;
|
||||
NestedChild child;
|
||||
JSONSTRUCT_REGISTER(Parent, F(parentInt, child))
|
||||
};
|
||||
|
||||
WHEN("Omitted whole child element")
|
||||
{
|
||||
Parent parent;
|
||||
const auto json = parent.toJson();
|
||||
REQUIRE(json["parentInt"] == QJsonValue::Undefined);
|
||||
REQUIRE(json["child"] == QJsonValue::Undefined);
|
||||
}
|
||||
|
||||
WHEN("Omitted one element in the child")
|
||||
{
|
||||
const auto childJson = QJsonObject{ { "childInt", 1314 } };
|
||||
Parent parent;
|
||||
parent.child.childInt = 1314;
|
||||
const auto json = parent.toJson();
|
||||
REQUIRE(json["parentInt"] == QJsonValue::Undefined);
|
||||
REQUIRE(json["child"] == childJson);
|
||||
REQUIRE(json["child"]["childInt"] == 1314);
|
||||
REQUIRE(json["child"]["childQString"] == QJsonValue::Undefined);
|
||||
REQUIRE(json["child"]["child"]["anotherChild"] == QJsonValue::Undefined);
|
||||
}
|
||||
|
||||
WHEN("Omitted one element in the child child")
|
||||
{
|
||||
Parent parent;
|
||||
parent.child.childInt = 1314;
|
||||
parent.child.anotherChild.childChildInt = 97531;
|
||||
const auto json = parent.toJson();
|
||||
REQUIRE(json["parentInt"] == QJsonValue::Undefined);
|
||||
REQUIRE(json["child"]["childInt"] == 1314);
|
||||
REQUIRE(json["child"]["childQString"] == QJsonValue::Undefined);
|
||||
const QJsonObject childChild{ { "childChildInt", 97531 } };
|
||||
REQUIRE(json["child"]["anotherChild"] == childChild);
|
||||
REQUIRE(json["child"]["anotherChild"]["childChildInt"] == 97531);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
1
client/3rd/QSimpleCrypto
vendored
Submodule
|
|
@ -0,0 +1 @@
|
|||
Subproject commit c99b33f0e08b7206116ddff85c22d3b97ce1e79d
|
||||
|
|
@ -1,20 +0,0 @@
|
|||
include_directories(${CMAKE_CURRENT_LIST_DIR})
|
||||
|
||||
set(HEADERS ${HEADERS}
|
||||
${CMAKE_CURRENT_LIST_DIR}/include/QAead.h
|
||||
${CMAKE_CURRENT_LIST_DIR}/include/QBlockCipher.h
|
||||
${CMAKE_CURRENT_LIST_DIR}/include/QCryptoError.h
|
||||
${CMAKE_CURRENT_LIST_DIR}/include/QRsa.h
|
||||
${CMAKE_CURRENT_LIST_DIR}/include/QSimpleCrypto_global.h
|
||||
${CMAKE_CURRENT_LIST_DIR}/include/QX509.h
|
||||
${CMAKE_CURRENT_LIST_DIR}/include/QX509Store.h
|
||||
)
|
||||
|
||||
set(SOURCES ${SOURCES}
|
||||
${CMAKE_CURRENT_LIST_DIR}/sources/QAead.cpp
|
||||
${CMAKE_CURRENT_LIST_DIR}/sources/QBlockCipher.cpp
|
||||
${CMAKE_CURRENT_LIST_DIR}/sources/QCryptoError.cpp
|
||||
${CMAKE_CURRENT_LIST_DIR}/sources/QRsa.cpp
|
||||
${CMAKE_CURRENT_LIST_DIR}/sources/QX509.cpp
|
||||
${CMAKE_CURRENT_LIST_DIR}/sources/QX509Store.cpp
|
||||
)
|
||||
|
|
@ -1,18 +0,0 @@
|
|||
INCLUDEPATH += $$PWD
|
||||
|
||||
HEADERS += \
|
||||
$$PWD/include/QAead.h \
|
||||
$$PWD/include/QBlockCipher.h \
|
||||
$$PWD/include/QCryptoError.h \
|
||||
$$PWD/include/QRsa.h \
|
||||
$$PWD/include/QSimpleCrypto_global.h \
|
||||
$$PWD/include/QX509.h \
|
||||
$$PWD/include/QX509Store.h
|
||||
|
||||
SOURCES += \
|
||||
$$PWD/sources/QAead.cpp \
|
||||
$$PWD/sources/QBlockCipher.cpp \
|
||||
$$PWD/sources/QCryptoError.cpp \
|
||||
$$PWD/sources/QRsa.cpp \
|
||||
$$PWD/sources/QX509.cpp \
|
||||
$$PWD/sources/QX509Store.cpp
|
||||
|
|
@ -1,87 +0,0 @@
|
|||
/**
|
||||
* Copyright 2021 BrutalWizard (https://github.com/bru74lw1z4rd). All Rights Reserved.
|
||||
*
|
||||
* Licensed under the Apache License 2.0 (the "License"). You may not use
|
||||
* this file except in compliance with the License. You can obtain a copy
|
||||
* in the file LICENSE in the source distribution
|
||||
**/
|
||||
|
||||
#ifndef QAEAD_H
|
||||
#define QAEAD_H
|
||||
|
||||
#include "QSimpleCrypto_global.h"
|
||||
|
||||
#include <QObject>
|
||||
|
||||
#include <memory>
|
||||
|
||||
#include <openssl/aes.h>
|
||||
#include <openssl/err.h>
|
||||
#include <openssl/evp.h>
|
||||
#include <openssl/rand.h>
|
||||
|
||||
#include "QCryptoError.h"
|
||||
|
||||
// clang-format off
|
||||
namespace QSimpleCrypto
|
||||
{
|
||||
class QSIMPLECRYPTO_EXPORT QAead {
|
||||
public:
|
||||
QAead();
|
||||
|
||||
///
|
||||
/// \brief encryptAesGcm - Function encrypts data with Gcm algorithm.
|
||||
/// \param data - Data that will be encrypted.
|
||||
/// \param key - AES key.
|
||||
/// \param iv - Initialization vector.
|
||||
/// \param tag - Authorization tag.
|
||||
/// \param aad - Additional authenticated data. Must be nullptr, if not used.
|
||||
/// \param cipher - Can be used with OpenSSL EVP_CIPHER (gcm) - 128, 192, 256. Example: EVP_aes_256_gcm().
|
||||
/// \return Returns encrypted data or "", if error happened.
|
||||
///
|
||||
QByteArray encryptAesGcm(QByteArray data, QByteArray key, QByteArray iv, QByteArray* tag, QByteArray aad = "", const EVP_CIPHER* cipher = EVP_aes_256_gcm());
|
||||
|
||||
///
|
||||
/// \brief decryptAesGcm - Function decrypts data with Gcm algorithm.
|
||||
/// \param data - Data that will be decrypted
|
||||
/// \param key - AES key
|
||||
/// \param iv - Initialization vector
|
||||
/// \param tag - Authorization tag
|
||||
/// \param aad - Additional authenticated data. Must be nullptr, if not used
|
||||
/// \param cipher - Can be used with OpenSSL EVP_CIPHER (gcm) - 128, 192, 256. Example: EVP_aes_256_gcm()
|
||||
/// \return Returns decrypted data or "", if error happened.
|
||||
///
|
||||
QByteArray decryptAesGcm(QByteArray data, QByteArray key, QByteArray iv, QByteArray* tag, QByteArray aad = "", const EVP_CIPHER* cipher = EVP_aes_256_gcm());
|
||||
|
||||
///
|
||||
/// \brief encryptAesCcm - Function encrypts data with Ccm algorithm.
|
||||
/// \param data - Data that will be encrypted.
|
||||
/// \param key - AES key.
|
||||
/// \param iv - Initialization vector.
|
||||
/// \param tag - Authorization tag.
|
||||
/// \param aad - Additional authenticated data. Must be nullptr, if not used.
|
||||
/// \param cipher - Can be used with OpenSSL EVP_CIPHER (ccm) - 128, 192, 256. Example: EVP_aes_256_ccm().
|
||||
/// \return Returns encrypted data or "", if error happened.
|
||||
///
|
||||
QByteArray encryptAesCcm(QByteArray data, QByteArray key, QByteArray iv, QByteArray* tag, QByteArray aad = "", const EVP_CIPHER* cipher = EVP_aes_256_ccm());
|
||||
|
||||
///
|
||||
/// \brief decryptAesCcm - Function decrypts data with Ccm algorithm.
|
||||
/// \param data - Data that will be decrypted.
|
||||
/// \param key - AES key.
|
||||
/// \param iv - Initialization vector.
|
||||
/// \param tag - Authorization tag.
|
||||
/// \param aad - Additional authenticated data. Must be nullptr, if not used.
|
||||
/// \param cipher - Can be used with OpenSSL EVP_CIPHER (ccm) - 128, 192, 256. Example: EVP_aes_256_ccm().
|
||||
/// \return Returns decrypted data or "", if error happened.
|
||||
///
|
||||
QByteArray decryptAesCcm(QByteArray data, QByteArray key, QByteArray iv, QByteArray* tag, QByteArray aad = "", const EVP_CIPHER* cipher = EVP_aes_256_ccm());
|
||||
|
||||
///
|
||||
/// \brief error - Error handler class.
|
||||
///
|
||||
QCryptoError error;
|
||||
};
|
||||
} // namespace QSimpleCrypto
|
||||
|
||||
#endif // QAEAD_H
|
||||
|
|
@ -1,84 +0,0 @@
|
|||
/**
|
||||
* Copyright 2021 BrutalWizard (https://github.com/bru74lw1z4rd). All Rights Reserved.
|
||||
*
|
||||
* Licensed under the Apache License 2.0 (the "License"). You may not use
|
||||
* this file except in compliance with the License. You can obtain a copy
|
||||
* in the file LICENSE in the source distribution
|
||||
**/
|
||||
|
||||
#ifndef QBLOCKCIPHER_H
|
||||
#define QBLOCKCIPHER_H
|
||||
|
||||
#include "QSimpleCrypto_global.h"
|
||||
|
||||
#include <QObject>
|
||||
|
||||
#include <memory>
|
||||
|
||||
#include <openssl/aes.h>
|
||||
#include <openssl/err.h>
|
||||
#include <openssl/evp.h>
|
||||
#include <openssl/rand.h>
|
||||
|
||||
#include "QCryptoError.h"
|
||||
|
||||
// clang-format off
|
||||
namespace QSimpleCrypto
|
||||
{
|
||||
class QSIMPLECRYPTO_EXPORT QBlockCipher {
|
||||
|
||||
#define Aes128Rounds 10
|
||||
#define Aes192Rounds 12
|
||||
#define Aes256Rounds 14
|
||||
|
||||
public:
|
||||
QBlockCipher();
|
||||
|
||||
///
|
||||
/// \brief generateRandomBytes - Function generates random bytes by size.
|
||||
/// \param size - Size of generated bytes.
|
||||
/// \return Returns random bytes.
|
||||
///
|
||||
QByteArray generateRandomBytes(const int& size);
|
||||
QByteArray generateSecureRandomBytes(const int& size);
|
||||
|
||||
///
|
||||
/// \brief encryptAesBlockCipher - Function encrypts data with Aes Block Cipher algorithm.
|
||||
/// \param data - Data that will be encrypted.
|
||||
/// \param key - AES key.
|
||||
/// \param iv - Initialization vector.
|
||||
/// \param password - Encryption password.
|
||||
/// \param salt - Random delta.
|
||||
/// \param rounds - Transformation rounds.
|
||||
/// \param chiper - Can be used with OpenSSL EVP_CIPHER (ecb, cbc, cfb, ofb, ctr) - 128, 192, 256. Example: EVP_aes_256_cbc().
|
||||
/// \param md - Hash algroitm (OpenSSL EVP_MD). Example: EVP_sha512().
|
||||
/// \return Returns decrypted data or "", if error happened.
|
||||
///
|
||||
QByteArray encryptAesBlockCipher(QByteArray data, QByteArray key,
|
||||
QByteArray iv = "", const int& rounds = Aes256Rounds,
|
||||
const EVP_CIPHER* cipher = EVP_aes_256_cbc(), const EVP_MD* md = EVP_sha512());
|
||||
|
||||
///
|
||||
/// \brief decryptAesBlockCipher - Function decrypts data with Aes Block Cipher algorithm.
|
||||
/// \param data - Data that will be decrypted.
|
||||
/// \param key - AES key.
|
||||
/// \param iv - Initialization vector.
|
||||
/// \param password - Decryption password.
|
||||
/// \param salt - Random delta.
|
||||
/// \param rounds - Transformation rounds.
|
||||
/// \param chiper - Can be used with OpenSSL EVP_CIPHER (ecb, cbc, cfb, ofb, ctr) - 128, 192, 256. Example: EVP_aes_256_cbc().
|
||||
/// \param md - Hash algroitm (OpenSSL EVP_MD). Example: EVP_sha512().
|
||||
/// \return Returns decrypted data or "", if error happened.
|
||||
///
|
||||
QByteArray decryptAesBlockCipher(QByteArray data, QByteArray key,
|
||||
QByteArray iv = "", const int& rounds = Aes256Rounds,
|
||||
const EVP_CIPHER* cipher = EVP_aes_256_cbc(), const EVP_MD* md = EVP_sha512());
|
||||
|
||||
///
|
||||
/// \brief error - Error handler class.
|
||||
///
|
||||
QCryptoError error;
|
||||
};
|
||||
} // namespace QSimpleCrypto
|
||||
|
||||
#endif // QBLOCKCIPHER_H
|
||||
|
|
@ -1,45 +0,0 @@
|
|||
#ifndef QCRYPTOERROR_H
|
||||
#define QCRYPTOERROR_H
|
||||
|
||||
#include <QObject>
|
||||
|
||||
#include "QSimpleCrypto_global.h"
|
||||
|
||||
/// TODO: Add Special error code for each error.
|
||||
|
||||
// clang-format off
|
||||
namespace QSimpleCrypto
|
||||
{
|
||||
class QSIMPLECRYPTO_EXPORT QCryptoError : public QObject {
|
||||
Q_OBJECT
|
||||
|
||||
public:
|
||||
explicit QCryptoError(QObject* parent = nullptr);
|
||||
|
||||
///
|
||||
/// \brief setError - Sets error information
|
||||
/// \param errorCode - Error code.
|
||||
/// \param errorSummary - Error summary.
|
||||
///
|
||||
inline void setError(const quint8 errorCode, const QString& errorSummary)
|
||||
{
|
||||
m_currentErrorCode = errorCode;
|
||||
m_errorSummary = errorSummary;
|
||||
}
|
||||
|
||||
///
|
||||
/// \brief lastError - Returns last error.
|
||||
/// \return Returns eror ID and error Text.
|
||||
///
|
||||
inline QPair<quint8, QString> lastError() const
|
||||
{
|
||||
return QPair<quint8, QString>(m_currentErrorCode, m_errorSummary);
|
||||
}
|
||||
|
||||
private:
|
||||
quint8 m_currentErrorCode;
|
||||
QString m_errorSummary;
|
||||
};
|
||||
}
|
||||
|
||||
#endif // QCRYPTOERROR_H
|
||||
|
|
@ -1,104 +0,0 @@
|
|||
/**
|
||||
* Copyright 2021 BrutalWizard (https://github.com/bru74lw1z4rd). All Rights Reserved.
|
||||
*
|
||||
* Licensed under the Apache License 2.0 (the "License"). You may not use
|
||||
* this file except in compliance with the License. You can obtain a copy
|
||||
* in the file LICENSE in the source distribution
|
||||
**/
|
||||
|
||||
#ifndef QRSA_H
|
||||
#define QRSA_H
|
||||
|
||||
#include "QSimpleCrypto_global.h"
|
||||
|
||||
#include <QFile>
|
||||
#include <QObject>
|
||||
|
||||
#include <memory>
|
||||
|
||||
#include <openssl/err.h>
|
||||
#include <openssl/pem.h>
|
||||
#include <openssl/rsa.h>
|
||||
|
||||
#include "QCryptoError.h"
|
||||
|
||||
// clang-format off
|
||||
namespace QSimpleCrypto
|
||||
{
|
||||
class QSIMPLECRYPTO_EXPORT QRsa {
|
||||
|
||||
#define PublicEncrypt 0
|
||||
#define PrivateEncrypt 1
|
||||
#define PublicDecrypt 2
|
||||
#define PrivateDecrypt 3
|
||||
|
||||
public:
|
||||
QRsa();
|
||||
|
||||
///
|
||||
/// \brief generateRsaKeys - Function generate Rsa Keys and returns them in OpenSSL structure.
|
||||
/// \param bits - RSA key size.
|
||||
/// \param rsaBigNumber - The exponent is an odd number, typically 3, 17 or 65537.
|
||||
/// \return Returns 'OpenSSL RSA structure' or 'nullptr', if error happened. Returned value must be cleaned up with 'RSA_free()' to avoid memory leak.
|
||||
///
|
||||
RSA* generateRsaKeys(const int& bits, const int& rsaBigNumber);
|
||||
|
||||
///
|
||||
/// \brief savePublicKey - Saves to file RSA public key.
|
||||
/// \param rsa - OpenSSL RSA structure.
|
||||
/// \param publicKeyFileName - Public key file name.
|
||||
///
|
||||
void savePublicKey(RSA *rsa, const QByteArray& publicKeyFileName);
|
||||
|
||||
///
|
||||
/// \brief savePrivateKey - Saves to file RSA private key.
|
||||
/// \param rsa - OpenSSL RSA structure.
|
||||
/// \param privateKeyFileName - Private key file name.
|
||||
/// \param password - Private key password.
|
||||
/// \param cipher - Can be used with 'OpenSSL EVP_CIPHER' (ecb, cbc, cfb, ofb, ctr) - 128, 192, 256. Example: EVP_aes_256_cbc().
|
||||
///
|
||||
void savePrivateKey(RSA* rsa, const QByteArray& privateKeyFileName, QByteArray password = "", const EVP_CIPHER* cipher = nullptr);
|
||||
|
||||
///
|
||||
/// \brief getPublicKeyFromFile - Gets RSA public key from a file.
|
||||
/// \param filePath - File path to public key file.
|
||||
/// \return Returns 'OpenSSL EVP_PKEY structure' or 'nullptr', if error happened. Returned value must be cleaned up with 'EVP_PKEY_free()' to avoid memory leak.
|
||||
///
|
||||
EVP_PKEY* getPublicKeyFromFile(const QByteArray& filePath);
|
||||
|
||||
///
|
||||
/// \brief getPrivateKeyFromFile - Gets RSA private key from a file.
|
||||
/// \param filePath - File path to private key file.
|
||||
/// \param password - Private key password.
|
||||
/// \return - Returns 'OpenSSL EVP_PKEY structure' or 'nullptr', if error happened. Returned value must be cleaned up with 'EVP_PKEY_free()' to avoid memory leak.
|
||||
///
|
||||
EVP_PKEY* getPrivateKeyFromFile(const QByteArray& filePath, const QByteArray& password = "");
|
||||
|
||||
///
|
||||
/// \brief encrypt - Encrypt data with RSA algorithm.
|
||||
/// \param plaintext - Text that must be encrypted.
|
||||
/// \param rsa - OpenSSL RSA structure.
|
||||
/// \param encryptType - Public or private encrypt type. (PUBLIC_ENCRYPT, PRIVATE_ENCRYPT).
|
||||
/// \param padding - OpenSSL RSA padding can be used with: 'RSA_PKCS1_PADDING', 'RSA_NO_PADDING' and etc.
|
||||
/// \return Returns encrypted data or "", if error happened.
|
||||
///
|
||||
QByteArray encrypt(QByteArray plainText, RSA* rsa, const int& encryptType = PublicEncrypt, const int& padding = RSA_PKCS1_PADDING);
|
||||
|
||||
///
|
||||
/// \brief decrypt - Decrypt data with RSA algorithm.
|
||||
/// \param cipherText - Text that must be decrypted.
|
||||
/// \param rsa - OpenSSL RSA structure.
|
||||
/// \param decryptType - Public or private type. (PUBLIC_DECRYPT, PRIVATE_DECRYPT).
|
||||
/// \param padding - RSA padding can be used with: 'RSA_PKCS1_PADDING', 'RSA_NO_PADDING' and etc.
|
||||
/// \return - Returns decrypted data or "", if error happened.
|
||||
///
|
||||
QByteArray decrypt(QByteArray cipherText, RSA* rsa, const int& decryptType = PrivateDecrypt, const int& padding = RSA_PKCS1_PADDING);
|
||||
|
||||
///
|
||||
/// \brief error - Error handler class.
|
||||
///
|
||||
QCryptoError error;
|
||||
};
|
||||
} // namespace QSimpleCrypto
|
||||
|
||||
#endif // QRSA_H
|
||||
|
|
@ -1,9 +0,0 @@
|
|||
#ifndef QSIMPLECRYPTO_GLOBAL_H
|
||||
#define QSIMPLECRYPTO_GLOBAL_H
|
||||
|
||||
#include <QtCore/qglobal.h>
|
||||
#include <stdexcept>
|
||||
|
||||
#define QSIMPLECRYPTO_EXPORT
|
||||
|
||||
#endif // QSIMPLECRYPTO_GLOBAL_H
|
||||
|
|
@ -1,87 +0,0 @@
|
|||
/**
|
||||
* Copyright 2021 BrutalWizard (https://github.com/bru74lw1z4rd). All Rights Reserved.
|
||||
*
|
||||
* Licensed under the Apache License 2.0 (the "License"). You may not use
|
||||
* this file except in compliance with the License. You can obtain a copy
|
||||
* in the file LICENSE in the source distribution
|
||||
**/
|
||||
|
||||
#ifndef QX509_H
|
||||
#define QX509_H
|
||||
|
||||
#include "QSimpleCrypto_global.h"
|
||||
|
||||
#include <QMap>
|
||||
#include <QObject>
|
||||
|
||||
#include <memory>
|
||||
|
||||
#include <openssl/bio.h>
|
||||
#include <openssl/err.h>
|
||||
#include <openssl/pem.h>
|
||||
#include <openssl/x509.h>
|
||||
#include <openssl/x509_vfy.h>
|
||||
|
||||
#include "QCryptoError.h"
|
||||
|
||||
// clang-format off
|
||||
namespace QSimpleCrypto
|
||||
{
|
||||
class QSIMPLECRYPTO_EXPORT QX509 {
|
||||
|
||||
#define oneYear 31536000L
|
||||
#define x509LastVersion 2
|
||||
|
||||
public:
|
||||
QX509();
|
||||
|
||||
///
|
||||
/// \brief loadCertificateFromFile - Function load X509 from file and returns OpenSSL structure.
|
||||
/// \param fileName - File path to certificate.
|
||||
/// \return Returns OpenSSL X509 structure or nullptr, if error happened. Returned value must be cleaned up with 'X509_free' to avoid memory leak.
|
||||
///
|
||||
X509* loadCertificateFromFile(const QByteArray& fileName);
|
||||
|
||||
///
|
||||
/// \brief signCertificate - Function signs X509 certificate and returns signed X509 OpenSSL structure.
|
||||
/// \param endCertificate - Certificate that will be signed
|
||||
/// \param caCertificate - CA certificate that will sign end certificate
|
||||
/// \param caPrivateKey - CA certificate private key
|
||||
/// \param fileName - With that name certificate will be saved. Leave "", if don't need to save it
|
||||
/// \return Returns OpenSSL X509 structure or nullptr, if error happened.
|
||||
///
|
||||
X509* signCertificate(X509* endCertificate, X509* caCertificate, EVP_PKEY* caPrivateKey, const QByteArray& fileName = "");
|
||||
|
||||
///
|
||||
/// \brief verifyCertificate - Function verifies X509 certificate and returns verified X509 OpenSSL structure.
|
||||
/// \param x509 - OpenSSL X509. That certificate will be verified.
|
||||
/// \param store - Trusted certificate must be added to X509_Store with 'addCertificateToStore(X509_STORE* ctx, X509* x509)'.
|
||||
/// \return Returns OpenSSL X509 structure or nullptr, if error happened
|
||||
///
|
||||
X509* verifyCertificate(X509* x509, X509_STORE* store);
|
||||
|
||||
///
|
||||
/// \brief generateSelfSignedCertificate - Function generatesand returns self signed X509.
|
||||
/// \param rsa - OpenSSL RSA.
|
||||
/// \param additionalData - Certificate information.
|
||||
/// \param certificateFileName - With that name certificate will be saved. Leave "", if don't need to save it.
|
||||
/// \param md - OpenSSL EVP_MD structure. Example: EVP_sha512().
|
||||
/// \param serialNumber - X509 certificate serial number.
|
||||
/// \param version - X509 certificate version.
|
||||
/// \param notBefore - X509 start date.
|
||||
/// \param notAfter - X509 end date.
|
||||
/// \return Returns OpenSSL X509 structure or nullptr, if error happened. Returned value must be cleaned up with 'X509_free' to avoid memory leak.
|
||||
///
|
||||
X509* generateSelfSignedCertificate(RSA* rsa, const QMap<QByteArray, QByteArray>& additionalData,
|
||||
const QByteArray& certificateFileName = "", const EVP_MD* md = EVP_sha512(),
|
||||
const long& serialNumber = 1, const long& version = x509LastVersion,
|
||||
const long& notBefore = 0, const long& notAfter = oneYear);
|
||||
|
||||
///
|
||||
/// \brief error - Error handler class.
|
||||
///
|
||||
QCryptoError error;
|
||||
};
|
||||
} // namespace QSimpleCrypto
|
||||
|
||||
#endif // QX509_H
|
||||
|
|
@ -1,120 +0,0 @@
|
|||
/**
|
||||
* Copyright 2021 BrutalWizard (https://github.com/bru74lw1z4rd). All Rights Reserved.
|
||||
*
|
||||
* Licensed under the Apache License 2.0 (the "License"). You may not use
|
||||
* this file except in compliance with the License. You can obtain a copy
|
||||
* in the file LICENSE in the source distribution
|
||||
**/
|
||||
|
||||
#ifndef QX509STORE_H
|
||||
#define QX509STORE_H
|
||||
|
||||
#include "QSimpleCrypto_global.h"
|
||||
|
||||
#include <QDir>
|
||||
#include <QFile>
|
||||
#include <QFileInfo>
|
||||
|
||||
#include <memory>
|
||||
|
||||
#include <openssl/err.h>
|
||||
#include <openssl/x509_vfy.h>
|
||||
#include <openssl/x509v3.h>
|
||||
|
||||
#include "QCryptoError.h"
|
||||
|
||||
// clang-format off
|
||||
namespace QSimpleCrypto
|
||||
{
|
||||
class QSIMPLECRYPTO_EXPORT QX509Store {
|
||||
public:
|
||||
QX509Store();
|
||||
|
||||
///
|
||||
/// \brief addCertificateToStore
|
||||
/// \param store - OpenSSL X509_STORE.
|
||||
/// \param x509 - OpenSSL X509.
|
||||
/// \return Returns 'true' on success and 'false', if error happened.
|
||||
///
|
||||
bool addCertificateToStore(X509_STORE* store, X509* x509);
|
||||
|
||||
///
|
||||
/// \brief addLookup
|
||||
/// \param store - OpenSSL X509_STORE.
|
||||
/// \param method - OpenSSL X509_LOOKUP_METHOD. Example: X509_LOOKUP_file.
|
||||
/// \return Returns 'true' on success and 'false', if error happened.
|
||||
///
|
||||
bool addLookup(X509_STORE* store, X509_LOOKUP_METHOD* method);
|
||||
|
||||
///
|
||||
/// \brief setCertificateDepth
|
||||
/// \param store - OpenSSL X509_STORE.
|
||||
/// \param depth - That is the maximum number of untrusted CA certificates that can appear in a chain. Example: 0.
|
||||
/// \return Returns 'true' on success and 'false', if error happened.
|
||||
///
|
||||
bool setDepth(X509_STORE* store, const int& depth);
|
||||
|
||||
///
|
||||
/// \brief setFlag
|
||||
/// \param store - OpenSSL X509_STORE.
|
||||
/// \param flag - The verification flags consists of zero or more of the following flags ored together. Example: X509_V_FLAG_CRL_CHECK.
|
||||
/// \return Returns 'true' on success and 'false', if error happened.
|
||||
///
|
||||
bool setFlag(X509_STORE* store, const unsigned long& flag);
|
||||
|
||||
///
|
||||
/// \brief setFlag
|
||||
/// \param store - OpenSSL X509_STORE.
|
||||
/// \param purpose - Verification purpose in param to purpose. Example: X509_PURPOSE_ANY.
|
||||
/// \return Returns 'true' on success and 'false', if error happened.
|
||||
///
|
||||
bool setPurpose(X509_STORE* store, const int& purpose);
|
||||
|
||||
///
|
||||
/// \brief setTrust
|
||||
/// \param store - OpenSSL X509_STORE.
|
||||
/// \param trust - Trust Level. Example: X509_TRUST_SSL_SERVER.
|
||||
/// \return Returns 'true' on success and 'false', if error happened.
|
||||
///
|
||||
bool setTrust(X509_STORE* store, const int& trust);
|
||||
|
||||
///
|
||||
/// \brief setDefaultPaths
|
||||
/// \param store - OpenSSL X509_STORE.
|
||||
/// \return Returns 'true' on success and 'false', if error happened.
|
||||
///
|
||||
bool setDefaultPaths(X509_STORE* store);
|
||||
|
||||
///
|
||||
/// \brief loadLocations
|
||||
/// \param store - OpenSSL X509_STORE.
|
||||
/// \param fileName - File name. Example: "caCertificate.pem".
|
||||
/// \param dirPath - Path to file. Example: "path/To/File".
|
||||
/// \return Returns 'true' on success and 'false', if error happened.
|
||||
///
|
||||
bool loadLocations(X509_STORE* store, const QByteArray& fileName, const QByteArray& dirPath);
|
||||
|
||||
///
|
||||
/// \brief loadLocations
|
||||
/// \param store - OpenSSL X509_STORE.
|
||||
/// \param file - Qt QFile that will be loaded.
|
||||
/// \return Returns 'true' on success and 'false', if error happened.
|
||||
///
|
||||
bool loadLocations(X509_STORE* store, const QFile& file);
|
||||
|
||||
///
|
||||
/// \brief loadLocations
|
||||
/// \param store - OpenSSL X509_STORE.
|
||||
/// \param fileInfo - Qt QFileInfo.
|
||||
/// \return Returns 'true' on success and 'false', if error happened.
|
||||
///
|
||||
bool loadLocations(X509_STORE* store, const QFileInfo& fileInfo);
|
||||
|
||||
///
|
||||
/// \brief error - Error handler class.
|
||||
///
|
||||
QCryptoError error;
|
||||
};
|
||||
}
|
||||
|
||||
#endif // QX509STORE_H
|
||||
|
|
@ -1,364 +0,0 @@
|
|||
/**
|
||||
* Copyright 2021 BrutalWizard (https://github.com/bru74lw1z4rd). All Rights Reserved.
|
||||
*
|
||||
* Licensed under the Apache License 2.0 (the "License"). You may not use
|
||||
* this file except in compliance with the License. You can obtain a copy
|
||||
* in the file LICENSE in the source distribution
|
||||
**/
|
||||
|
||||
#include "include/QAead.h"
|
||||
|
||||
QSimpleCrypto::QAead::QAead()
|
||||
{
|
||||
}
|
||||
|
||||
///
|
||||
/// \brief QSimpleCrypto::QAEAD::encryptAesGcm - Function encrypts data with Gcm algorithm.
|
||||
/// \param data - Data that will be encrypted.
|
||||
/// \param key - AES key.
|
||||
/// \param iv - Initialization vector.
|
||||
/// \param tag - Authorization tag.
|
||||
/// \param aad - Additional authenticated data. Must be nullptr, if not used.
|
||||
/// \param cipher - Can be used with OpenSSL EVP_CIPHER (gcm) - 128, 192, 256. Example: EVP_aes_256_gcm().
|
||||
/// \return Returns encrypted data or "", if error happened.
|
||||
///
|
||||
QByteArray QSimpleCrypto::QAead::encryptAesGcm(QByteArray data, QByteArray key, QByteArray iv, QByteArray* tag, QByteArray aad, const EVP_CIPHER* cipher)
|
||||
{
|
||||
try {
|
||||
/* Initialize EVP_CIPHER_CTX */
|
||||
std::unique_ptr<EVP_CIPHER_CTX, void (*)(EVP_CIPHER_CTX*)> encryptionCipher { EVP_CIPHER_CTX_new(), EVP_CIPHER_CTX_free };
|
||||
if (encryptionCipher == nullptr) {
|
||||
throw std::runtime_error("Couldn't initialize \'encryptionCipher\'. EVP_CIPHER_CTX_new(). Error: " + QByteArray(ERR_error_string(ERR_get_error(), nullptr)));
|
||||
}
|
||||
|
||||
/* Set data length */
|
||||
int plainTextLength = data.size();
|
||||
int cipherTextLength = 0;
|
||||
|
||||
/* Initialize cipherText. Here encrypted data will be stored */
|
||||
std::unique_ptr<unsigned char[]> cipherText { new unsigned char[plainTextLength]() };
|
||||
if (cipherText == nullptr) {
|
||||
throw std::runtime_error("Couldn't allocate memory for 'ciphertext'.");
|
||||
}
|
||||
|
||||
/* Initialize encryption operation. */
|
||||
if (!EVP_EncryptInit_ex(encryptionCipher.get(), cipher, nullptr, reinterpret_cast<unsigned char*>(key.data()), reinterpret_cast<unsigned char*>(iv.data()))) {
|
||||
throw std::runtime_error("Couldn't initialize encryption operation. EVP_EncryptInit_ex(). Error: " + QByteArray(ERR_error_string(ERR_get_error(), nullptr)));
|
||||
}
|
||||
|
||||
/* Set IV length if default 12 bytes (96 bits) is not appropriate */
|
||||
if (!EVP_CIPHER_CTX_ctrl(encryptionCipher.get(), EVP_CTRL_GCM_SET_IVLEN, iv.length(), nullptr)) {
|
||||
throw std::runtime_error("Couldn't set IV length. EVP_CIPHER_CTX_ctrl(). Error: " + QByteArray(ERR_error_string(ERR_get_error(), nullptr)));
|
||||
}
|
||||
|
||||
// /* Check if aad need to be used */
|
||||
// if (aad.length() > 0) {
|
||||
// /* Provide any AAD data. This can be called zero or more times as required */
|
||||
// if (!EVP_EncryptUpdate(encryptionCipher.get(), nullptr, &cipherTextLength, reinterpret_cast<unsigned char*>(aad.data()), aad.length())) {
|
||||
// throw std::runtime_error("Couldn't provide aad data. EVP_EncryptUpdate(). Error: " + QByteArray(ERR_error_string(ERR_get_error(), nullptr)));
|
||||
// }
|
||||
// }
|
||||
|
||||
/*
|
||||
* Provide the message to be encrypted, and obtain the encrypted output.
|
||||
* EVP_EncryptUpdate can be called multiple times if necessary
|
||||
*/
|
||||
if (!EVP_EncryptUpdate(encryptionCipher.get(), cipherText.get(), &cipherTextLength, reinterpret_cast<const unsigned char*>(data.data()), plainTextLength)) {
|
||||
throw std::runtime_error("Couldn't provide message to be encrypted. EVP_EncryptUpdate(). Error: " + QByteArray(ERR_error_string(ERR_get_error(), nullptr)));
|
||||
}
|
||||
|
||||
/*
|
||||
* Finalize the encryption. Normally cipher text bytes may be written at
|
||||
* this stage, but this does not occur in GCM mode
|
||||
*/
|
||||
if (!EVP_EncryptFinal_ex(encryptionCipher.get(), cipherText.get(), &plainTextLength)) {
|
||||
throw std::runtime_error("Couldn't finalize encryption. EVP_EncryptFinal_ex(). Error: " + QByteArray(ERR_error_string(ERR_get_error(), nullptr)));
|
||||
}
|
||||
|
||||
// /* Get tag */
|
||||
// if (!EVP_CIPHER_CTX_ctrl(encryptionCipher.get(), EVP_CTRL_GCM_GET_TAG, tag->length(), reinterpret_cast<unsigned char*>(tag->data()))) {
|
||||
// throw std::runtime_error("Couldn't get tag. EVP_CIPHER_CTX_ctrl(. Error: " + QByteArray(ERR_error_string(ERR_get_error(), nullptr)));
|
||||
// }
|
||||
|
||||
/* Finilize data to be readable with qt */
|
||||
QByteArray encryptedData = QByteArray(reinterpret_cast<char*>(cipherText.get()), cipherTextLength);
|
||||
|
||||
return encryptedData;
|
||||
|
||||
} catch (std::exception& exception) {
|
||||
QSimpleCrypto::QAead::error.setError(1, exception.what());
|
||||
return QByteArray();
|
||||
} catch (...) {
|
||||
QSimpleCrypto::QAead::error.setError(2, "Unknown error!");
|
||||
return QByteArray();
|
||||
}
|
||||
|
||||
return QByteArray();
|
||||
}
|
||||
|
||||
///
|
||||
/// \brief QSimpleCrypto::QAEAD::decryptAesGcm - Function decrypts data with Gcm algorithm.
|
||||
/// \param data - Data that will be decrypted
|
||||
/// \param key - AES key
|
||||
/// \param iv - Initialization vector
|
||||
/// \param tag - Authorization tag
|
||||
/// \param aad - Additional authenticated data. Must be nullptr, if not used
|
||||
/// \param cipher - Can be used with OpenSSL EVP_CIPHER (gcm) - 128, 192, 256. Example: EVP_aes_256_gcm()
|
||||
/// \return Returns decrypted data or "", if error happened.
|
||||
///
|
||||
QByteArray QSimpleCrypto::QAead::decryptAesGcm(QByteArray data, QByteArray key, QByteArray iv, QByteArray* tag, QByteArray aad, const EVP_CIPHER* cipher)
|
||||
{
|
||||
try {
|
||||
/* Initialize EVP_CIPHER_CTX */
|
||||
std::unique_ptr<EVP_CIPHER_CTX, void (*)(EVP_CIPHER_CTX*)> decryptionCipher { EVP_CIPHER_CTX_new(), EVP_CIPHER_CTX_free };
|
||||
if (decryptionCipher.get() == nullptr) {
|
||||
throw std::runtime_error("Couldn't initialize \'decryptionCipher\'. EVP_CIPHER_CTX_new(). Error: " + QByteArray(ERR_error_string(ERR_get_error(), nullptr)));
|
||||
}
|
||||
|
||||
/* Set data length */
|
||||
int cipherTextLength = data.size();
|
||||
int plainTextLength = 0;
|
||||
|
||||
/* Initialize plainText. Here decrypted data will be stored */
|
||||
std::unique_ptr<unsigned char[]> plainText { new unsigned char[cipherTextLength]() };
|
||||
if (plainText == nullptr) {
|
||||
throw std::runtime_error("Couldn't allocate memory for 'plaintext'.");
|
||||
}
|
||||
|
||||
/* Initialize decryption operation. */
|
||||
if (!EVP_DecryptInit_ex(decryptionCipher.get(), cipher, nullptr, reinterpret_cast<unsigned char*>(key.data()), reinterpret_cast<unsigned char*>(iv.data()))) {
|
||||
throw std::runtime_error("Couldn't initialize decryption operation. EVP_DecryptInit_ex(). Error: " + QByteArray(ERR_error_string(ERR_get_error(), nullptr)));
|
||||
}
|
||||
|
||||
/* Set IV length. Not necessary if this is 12 bytes (96 bits) */
|
||||
if (!EVP_CIPHER_CTX_ctrl(decryptionCipher.get(), EVP_CTRL_GCM_SET_IVLEN, iv.length(), nullptr)) {
|
||||
throw std::runtime_error("Couldn't set IV length. EVP_CIPHER_CTX_ctrl(). Error: " + QByteArray(ERR_error_string(ERR_get_error(), nullptr)));
|
||||
}
|
||||
|
||||
// /* Check if aad need to be used */
|
||||
// if (aad.length() > 0) {
|
||||
// /* Provide any AAD data. This can be called zero or more times as required */
|
||||
// if (!EVP_DecryptUpdate(decryptionCipher.get(), nullptr, &plainTextLength, reinterpret_cast<unsigned char*>(aad.data()), aad.length())) {
|
||||
// throw std::runtime_error("Couldn't provide aad data. EVP_DecryptUpdate(). Error: " + QByteArray(ERR_error_string(ERR_get_error(), nullptr)));
|
||||
// }
|
||||
// }
|
||||
|
||||
/*
|
||||
* Provide the message to be decrypted, and obtain the plain text output.
|
||||
* EVP_DecryptUpdate can be called multiple times if necessary
|
||||
*/
|
||||
if (!EVP_DecryptUpdate(decryptionCipher.get(), plainText.get(), &plainTextLength, reinterpret_cast<const unsigned char*>(data.data()), cipherTextLength)) {
|
||||
throw std::runtime_error("Couldn't provide message to be decrypted. EVP_DecryptUpdate(). Error: " + QByteArray(ERR_error_string(ERR_get_error(), nullptr)));
|
||||
}
|
||||
|
||||
// /* Set expected tag value. Works in OpenSSL 1.0.1d and later */
|
||||
// if (!EVP_CIPHER_CTX_ctrl(decryptionCipher.get(), EVP_CTRL_GCM_SET_TAG, tag->length(), reinterpret_cast<unsigned char*>(tag->data()))) {
|
||||
// throw std::runtime_error("Coldn't set tag. EVP_CIPHER_CTX_ctrl(). Error: " + QByteArray(ERR_error_string(ERR_get_error(), nullptr)));
|
||||
// }
|
||||
|
||||
/*
|
||||
* Finalize the decryption. A positive return value indicates success,
|
||||
* anything else is a failure - the plain text is not trustworthy.
|
||||
*/
|
||||
if (!EVP_DecryptFinal_ex(decryptionCipher.get(), plainText.get(), &cipherTextLength)) {
|
||||
throw std::runtime_error("Couldn't finalize decryption. EVP_DecryptFinal_ex(). Error: " + QByteArray(ERR_error_string(ERR_get_error(), nullptr)));
|
||||
}
|
||||
|
||||
/* Finilize data to be readable with qt */
|
||||
QByteArray decryptedData = QByteArray(reinterpret_cast<char*>(plainText.get()), plainTextLength);
|
||||
|
||||
return decryptedData;
|
||||
|
||||
} catch (std::exception& exception) {
|
||||
QSimpleCrypto::QAead::error.setError(1, exception.what());
|
||||
return QByteArray();
|
||||
} catch (...) {
|
||||
QSimpleCrypto::QAead::error.setError(2, "Unknown error!");
|
||||
return QByteArray();
|
||||
}
|
||||
|
||||
return QByteArray();
|
||||
}
|
||||
|
||||
///
|
||||
/// \brief QSimpleCrypto::QAEAD::encryptAesCcm - Function encrypts data with Ccm algorithm.
|
||||
/// \param data - Data that will be encrypted.
|
||||
/// \param key - AES key.
|
||||
/// \param iv - Initialization vector.
|
||||
/// \param tag - Authorization tag.
|
||||
/// \param aad - Additional authenticated data. Must be nullptr, if not used.
|
||||
/// \param cipher - Can be used with OpenSSL EVP_CIPHER (ccm) - 128, 192, 256. Example: EVP_aes_256_ccm().
|
||||
/// \return Returns encrypted data or "", if error happened.
|
||||
///
|
||||
QByteArray QSimpleCrypto::QAead::encryptAesCcm(QByteArray data, QByteArray key, QByteArray iv, QByteArray* tag, QByteArray aad, const EVP_CIPHER* cipher)
|
||||
{
|
||||
try {
|
||||
/* Initialize EVP_CIPHER_CTX */
|
||||
std::unique_ptr<EVP_CIPHER_CTX, void (*)(EVP_CIPHER_CTX*)> encryptionCipher { EVP_CIPHER_CTX_new(), EVP_CIPHER_CTX_free };
|
||||
if (encryptionCipher == nullptr) {
|
||||
throw std::runtime_error("Couldn't initialize \'encryptionCipher\'. EVP_CIPHER_CTX_new(). Error: " + QByteArray(ERR_error_string(ERR_get_error(), nullptr)));
|
||||
}
|
||||
|
||||
/* Set data length */
|
||||
int plainTextLength = data.size();
|
||||
int cipherTextLength = 0;
|
||||
|
||||
/* Initialize cipherText. Here encrypted data will be stored */
|
||||
std::unique_ptr<unsigned char[]> cipherText { new unsigned char[plainTextLength]() };
|
||||
if (cipherText.get() == nullptr) {
|
||||
throw std::runtime_error("Couldn't allocate memory for 'ciphertext'.");
|
||||
}
|
||||
|
||||
/* Initialize encryption operation. */
|
||||
if (!EVP_EncryptInit_ex(encryptionCipher.get(), cipher, nullptr, reinterpret_cast<unsigned char*>(key.data()), reinterpret_cast<unsigned char*>(iv.data()))) {
|
||||
throw std::runtime_error("Couldn't initialize encryption operation. EVP_EncryptInit_ex(). Error: " + QByteArray(ERR_error_string(ERR_get_error(), nullptr)));
|
||||
}
|
||||
|
||||
/* Set IV length if default 12 bytes (96 bits) is not appropriate */
|
||||
if (!EVP_CIPHER_CTX_ctrl(encryptionCipher.get(), EVP_CTRL_CCM_SET_IVLEN, iv.length(), nullptr)) {
|
||||
throw std::runtime_error("Couldn't set IV length. EVP_CIPHER_CTX_ctrl(). Error: " + QByteArray(ERR_error_string(ERR_get_error(), nullptr)));
|
||||
}
|
||||
|
||||
/* Set tag length */
|
||||
if (!EVP_CIPHER_CTX_ctrl(encryptionCipher.get(), EVP_CTRL_CCM_SET_TAG, tag->length(), nullptr)) {
|
||||
throw std::runtime_error("Coldn't set tag. EVP_CIPHER_CTX_ctrl(). Error: " + QByteArray(ERR_error_string(ERR_get_error(), nullptr)));
|
||||
}
|
||||
|
||||
/* Check if aad need to be used */
|
||||
if (aad.length() > 0) {
|
||||
/* Provide the total plain text length */
|
||||
if (!EVP_EncryptUpdate(encryptionCipher.get(), nullptr, &cipherTextLength, nullptr, plainTextLength)) {
|
||||
throw std::runtime_error("Couldn't provide total plaintext length. EVP_EncryptUpdate(). Error: " + QByteArray(ERR_error_string(ERR_get_error(), nullptr)));
|
||||
}
|
||||
|
||||
/* Provide any AAD data. This can be called zero or more times as required */
|
||||
if (!EVP_EncryptUpdate(encryptionCipher.get(), nullptr, &cipherTextLength, reinterpret_cast<unsigned char*>(aad.data()), aad.length())) {
|
||||
throw std::runtime_error("Couldn't provide aad data. EVP_EncryptUpdate(). Error: " + QByteArray(ERR_error_string(ERR_get_error(), nullptr)));
|
||||
}
|
||||
}
|
||||
|
||||
/*
|
||||
* Provide the message to be encrypted, and obtain the encrypted output.
|
||||
* EVP_EncryptUpdate can be called multiple times if necessary
|
||||
*/
|
||||
if (!EVP_EncryptUpdate(encryptionCipher.get(), cipherText.get(), &cipherTextLength, reinterpret_cast<const unsigned char*>(data.data()), plainTextLength)) {
|
||||
throw std::runtime_error("Couldn't provide message to be encrypted. EVP_EncryptUpdate(). Error: " + QByteArray(ERR_error_string(ERR_get_error(), nullptr)));
|
||||
}
|
||||
|
||||
/*
|
||||
* Finalize the encryption. Normally ciphertext bytes may be written at
|
||||
* this stage, but this does not occur in GCM mode
|
||||
*/
|
||||
if (!EVP_EncryptFinal_ex(encryptionCipher.get(), cipherText.get(), &plainTextLength)) {
|
||||
throw std::runtime_error("Couldn't finalize encryption. EVP_EncryptFinal_ex(). Error: " + QByteArray(ERR_error_string(ERR_get_error(), nullptr)));
|
||||
}
|
||||
|
||||
/* Get tag */
|
||||
if (!EVP_CIPHER_CTX_ctrl(encryptionCipher.get(), EVP_CTRL_CCM_GET_TAG, tag->length(), reinterpret_cast<unsigned char*>(tag->data()))) {
|
||||
throw std::runtime_error("Couldn't get tag. EVP_CIPHER_CTX_ctrl(). Error: " + QByteArray(ERR_error_string(ERR_get_error(), nullptr)));
|
||||
}
|
||||
|
||||
/* Finilize data to be readable with qt */
|
||||
QByteArray encryptedData = QByteArray(reinterpret_cast<char*>(cipherText.get()), cipherTextLength);
|
||||
|
||||
return encryptedData;
|
||||
|
||||
} catch (std::exception& exception) {
|
||||
QSimpleCrypto::QAead::error.setError(1, exception.what());
|
||||
return QByteArray();
|
||||
} catch (...) {
|
||||
QSimpleCrypto::QAead::error.setError(2, "Unknown error!");
|
||||
return QByteArray();
|
||||
}
|
||||
|
||||
return QByteArray();
|
||||
}
|
||||
|
||||
///
|
||||
/// \brief QSimpleCrypto::QAEAD::decryptAesCcm - Function decrypts data with Ccm algorithm.
|
||||
/// \param data - Data that will be decrypted.
|
||||
/// \param key - AES key.
|
||||
/// \param iv - Initialization vector.
|
||||
/// \param tag - Authorization tag.
|
||||
/// \param aad - Additional authenticated data. Must be nullptr, if not used.
|
||||
/// \param cipher - Can be used with OpenSSL EVP_CIPHER (ccm) - 128, 192, 256. Example: EVP_aes_256_ccm().
|
||||
/// \return Returns decrypted data or "", if error happened.
|
||||
///
|
||||
QByteArray QSimpleCrypto::QAead::decryptAesCcm(QByteArray data, QByteArray key, QByteArray iv, QByteArray* tag, QByteArray aad, const EVP_CIPHER* cipher)
|
||||
{
|
||||
try {
|
||||
/* Initialize EVP_CIPHER_CTX */
|
||||
std::unique_ptr<EVP_CIPHER_CTX, void (*)(EVP_CIPHER_CTX*)> decryptionCipher { EVP_CIPHER_CTX_new(), EVP_CIPHER_CTX_free };
|
||||
if (decryptionCipher.get() == nullptr) {
|
||||
throw std::runtime_error("Couldn't initialize \'decryptionCipher\'. EVP_CIPHER_CTX_new(). Error: " + QByteArray(ERR_error_string(ERR_get_error(), nullptr)));
|
||||
}
|
||||
|
||||
/* Set data length */
|
||||
int cipherTextLength = data.size();
|
||||
int plainTextLength = 0;
|
||||
|
||||
/* Initialize plainText. Here decrypted data will be stored */
|
||||
std::unique_ptr<unsigned char[]> plainText { new unsigned char[cipherTextLength]() };
|
||||
if (plainText == nullptr) {
|
||||
throw std::runtime_error("Couldn't allocate memory for 'plaintext'.");
|
||||
}
|
||||
|
||||
/* Initialize decryption operation. */
|
||||
if (!EVP_DecryptInit_ex(decryptionCipher.get(), cipher, nullptr, reinterpret_cast<unsigned char*>(key.data()), reinterpret_cast<unsigned char*>(iv.data()))) {
|
||||
throw std::runtime_error("Couldn't initialize decryption operation. EVP_DecryptInit_ex(). Error: " + QByteArray(ERR_error_string(ERR_get_error(), nullptr)));
|
||||
}
|
||||
|
||||
/* Set IV length. Not necessary if this is 12 bytes (96 bits) */
|
||||
if (!EVP_CIPHER_CTX_ctrl(decryptionCipher.get(), EVP_CTRL_CCM_SET_IVLEN, iv.length(), nullptr)) {
|
||||
throw std::runtime_error("Couldn't set IV length. EVP_CIPHER_CTX_ctrl(). Error: " + QByteArray(ERR_error_string(ERR_get_error(), nullptr)));
|
||||
}
|
||||
|
||||
/* Set expected tag value. Works in OpenSSL 1.0.1d and later */
|
||||
if (!EVP_CIPHER_CTX_ctrl(decryptionCipher.get(), EVP_CTRL_CCM_SET_TAG, tag->length(), reinterpret_cast<unsigned char*>(tag->data()))) {
|
||||
throw std::runtime_error("Coldn't set tag. EVP_CIPHER_CTX_ctrl(). Error: " + QByteArray(ERR_error_string(ERR_get_error(), nullptr)));
|
||||
}
|
||||
|
||||
/* Check if aad need to be used */
|
||||
if (aad.length() > 0) {
|
||||
/* Provide the total ciphertext length */
|
||||
if (!EVP_DecryptUpdate(decryptionCipher.get(), nullptr, &plainTextLength, nullptr, cipherTextLength)) {
|
||||
throw std::runtime_error("Couldn't provide total plaintext length. EVP_DecryptUpdate(). Error: " + QByteArray(ERR_error_string(ERR_get_error(), nullptr)));
|
||||
}
|
||||
|
||||
/* Provide any AAD data. This can be called zero or more times as required */
|
||||
if (!EVP_DecryptUpdate(decryptionCipher.get(), nullptr, &plainTextLength, reinterpret_cast<unsigned char*>(aad.data()), aad.length())) {
|
||||
throw std::runtime_error("Couldn't provide aad data. EVP_DecryptUpdate(). Error: " + QByteArray(ERR_error_string(ERR_get_error(), nullptr)));
|
||||
}
|
||||
}
|
||||
|
||||
/*
|
||||
* Provide the message to be decrypted, and obtain the plaintext output.
|
||||
* EVP_DecryptUpdate can be called multiple times if necessary
|
||||
*/
|
||||
if (!EVP_DecryptUpdate(decryptionCipher.get(), plainText.get(), &plainTextLength, reinterpret_cast<const unsigned char*>(data.data()), cipherTextLength)) {
|
||||
throw std::runtime_error("Couldn't provide message to be decrypted. EVP_DecryptUpdate(). Error: " + QByteArray(ERR_error_string(ERR_get_error(), nullptr)));
|
||||
}
|
||||
|
||||
/*
|
||||
* Finalize the decryption. A positive return value indicates success,
|
||||
* anything else is a failure - the plaintext is not trustworthy.
|
||||
*/
|
||||
if (!EVP_DecryptFinal_ex(decryptionCipher.get(), plainText.get(), &cipherTextLength)) {
|
||||
throw std::runtime_error("Couldn't finalize decryption. EVP_DecryptFinal_ex(). Error: " + QByteArray(ERR_error_string(ERR_get_error(), nullptr)));
|
||||
}
|
||||
|
||||
/* Finilize data to be readable with qt */
|
||||
QByteArray decryptedData = QByteArray(reinterpret_cast<char*>(plainText.get()), plainTextLength);
|
||||
|
||||
return decryptedData;
|
||||
|
||||
} catch (std::exception& exception) {
|
||||
QSimpleCrypto::QAead::error.setError(1, exception.what());
|
||||
return QByteArray();
|
||||
} catch (...) {
|
||||
QSimpleCrypto::QAead::error.setError(2, "Unknown error!");
|
||||
return QByteArray();
|
||||
}
|
||||
|
||||
return QByteArray();
|
||||
}
|
||||
|
|
@ -1,193 +0,0 @@
|
|||
/**
|
||||
* Copyright 2021 BrutalWizard (https://github.com/bru74lw1z4rd). All Rights Reserved.
|
||||
*
|
||||
* Licensed under the Apache License 2.0 (the "License"). You may not use
|
||||
* this file except in compliance with the License. You can obtain a copy
|
||||
* in the file LICENSE in the source distribution
|
||||
**/
|
||||
|
||||
#include "include/QBlockCipher.h"
|
||||
|
||||
QSimpleCrypto::QBlockCipher::QBlockCipher()
|
||||
{
|
||||
}
|
||||
|
||||
///
|
||||
/// \brief QSimpleCrypto::QBlockCipher::generateRandomBytes - Function generates random bytes by size.
|
||||
/// \param size - Size of generated bytes.
|
||||
/// \return Returns random bytes.
|
||||
///
|
||||
QByteArray QSimpleCrypto::QBlockCipher::generateRandomBytes(const int& size)
|
||||
{
|
||||
unsigned char arr[sizeof(size)];
|
||||
RAND_bytes(arr, sizeof(size));
|
||||
|
||||
QByteArray buffer = QByteArray(reinterpret_cast<char*>(arr), size);
|
||||
return buffer;
|
||||
}
|
||||
|
||||
QByteArray QSimpleCrypto::QBlockCipher::generateSecureRandomBytes(const int &size)
|
||||
{
|
||||
unsigned char arr[sizeof(size)];
|
||||
RAND_priv_bytes(arr, sizeof(size));
|
||||
|
||||
QByteArray buffer = QByteArray(reinterpret_cast<char*>(arr), size);
|
||||
return buffer;
|
||||
}
|
||||
|
||||
///
|
||||
/// \brief QSimpleCrypto::QBlockCipher::encryptAesBlockCipher - Function encrypts data with Aes Block Cipher algorithm.
|
||||
/// \param data - Data that will be encrypted.
|
||||
/// \param key - AES key.
|
||||
/// \param iv - Initialization vector.
|
||||
/// \param password - Encryption password.
|
||||
/// \param salt - Random delta.
|
||||
/// \param rounds - Transformation rounds.
|
||||
/// \param chiper - Can be used with OpenSSL EVP_CIPHER (ecb, cbc, cfb, ofb, ctr) - 128, 192, 256. Example: EVP_aes_256_cbc().
|
||||
/// \param md - Hash algroitm (OpenSSL EVP_MD). Example: EVP_sha512().
|
||||
/// \return Returns decrypted data or "", if error happened.
|
||||
///
|
||||
QByteArray QSimpleCrypto::QBlockCipher::encryptAesBlockCipher(QByteArray data, QByteArray key,
|
||||
QByteArray iv,
|
||||
const int& rounds, const EVP_CIPHER* cipher, const EVP_MD* md)
|
||||
{
|
||||
try {
|
||||
/* Initialize EVP_CIPHER_CTX */
|
||||
std::unique_ptr<EVP_CIPHER_CTX, void (*)(EVP_CIPHER_CTX*)> encryptionCipher { EVP_CIPHER_CTX_new(), EVP_CIPHER_CTX_free };
|
||||
if (encryptionCipher == nullptr) {
|
||||
throw std::runtime_error("Couldn't initialize \'encryptionCipher\'. EVP_CIPHER_CTX_new(). Error: " + QByteArray(ERR_error_string(ERR_get_error(), nullptr)));
|
||||
}
|
||||
|
||||
/* Reinterpret values for multi use */
|
||||
unsigned char* m_key = reinterpret_cast<unsigned char*>(key.data());
|
||||
unsigned char* m_iv = reinterpret_cast<unsigned char*>(iv.data());
|
||||
|
||||
/* Set data length */
|
||||
int cipherTextLength(data.size() + AES_BLOCK_SIZE);
|
||||
int finalLength = 0;
|
||||
|
||||
/* Initialize cipcherText. Here encrypted data will be stored */
|
||||
std::unique_ptr<unsigned char[]> cipherText { new unsigned char[cipherTextLength]() };
|
||||
if (cipherText == nullptr) {
|
||||
throw std::runtime_error("Couldn't allocate memory for 'cipherText'.");
|
||||
}
|
||||
|
||||
// Bug here
|
||||
// /* Start encryption with password based encryption routine */
|
||||
// if (!EVP_BytesToKey(cipher, md, reinterpret_cast<unsigned char*>(salt.data()), reinterpret_cast<unsigned char*>(password.data()), password.length(), rounds, m_key, m_iv)) {
|
||||
// throw std::runtime_error("Couldn't start encryption routine. EVP_BytesToKey(). Error: " + QByteArray(ERR_error_string(ERR_get_error(), nullptr)));
|
||||
// }
|
||||
|
||||
/* Initialize encryption operation. */
|
||||
if (!EVP_EncryptInit_ex(encryptionCipher.get(), cipher, nullptr, m_key, m_iv)) {
|
||||
throw std::runtime_error("Couldn't initialize encryption operation. EVP_EncryptInit_ex(). Error: " + QByteArray(ERR_error_string(ERR_get_error(), nullptr)));
|
||||
}
|
||||
|
||||
/*
|
||||
* Provide the message to be encrypted, and obtain the encrypted output.
|
||||
* EVP_EncryptUpdate can be called multiple times if necessary
|
||||
*/
|
||||
if (!EVP_EncryptUpdate(encryptionCipher.get(), cipherText.get(), &cipherTextLength, reinterpret_cast<const unsigned char*>(data.data()), data.size())) {
|
||||
throw std::runtime_error("Couldn't provide message to be encrypted. EVP_EncryptUpdate(). Error: " + QByteArray(ERR_error_string(ERR_get_error(), nullptr)));
|
||||
}
|
||||
|
||||
/* Finalize the encryption. Normally ciphertext bytes may be written at this stage */
|
||||
if (!EVP_EncryptFinal(encryptionCipher.get(), cipherText.get() + cipherTextLength, &finalLength)) {
|
||||
throw std::runtime_error("Couldn't finalize encryption. EVP_EncryptFinal(). Error: " + QByteArray(ERR_error_string(ERR_get_error(), nullptr)));
|
||||
}
|
||||
|
||||
/* Finilize data to be readable with qt */
|
||||
QByteArray encryptedData = QByteArray(reinterpret_cast<char*>(cipherText.get()), cipherTextLength + finalLength);
|
||||
|
||||
return encryptedData;
|
||||
|
||||
} catch (std::exception& exception) {
|
||||
QSimpleCrypto::QBlockCipher::error.setError(1, exception.what());
|
||||
return QByteArray();
|
||||
} catch (...) {
|
||||
QSimpleCrypto::QBlockCipher::error.setError(2, "Unknown error!");
|
||||
return QByteArray();
|
||||
}
|
||||
|
||||
return QByteArray();
|
||||
}
|
||||
|
||||
///
|
||||
/// \brief QSimpleCrypto::QBlockCipher::encryptAesBlockCipher - Function decrypts data with Aes Block Cipher algorithm.
|
||||
/// \param data - Data that will be decrypted.
|
||||
/// \param key - AES key.
|
||||
/// \param iv - Initialization vector.
|
||||
/// \param password - Decryption password.
|
||||
/// \param salt - Random delta.
|
||||
/// \param rounds - Transformation rounds.
|
||||
/// \param chiper - Can be used with OpenSSL EVP_CIPHER (ecb, cbc, cfb, ofb, ctr) - 128, 192, 256. Example: EVP_aes_256_cbc().
|
||||
/// \param md - Hash algroitm (OpenSSL EVP_MD). Example: EVP_sha512().
|
||||
/// \return Returns decrypted data or "", if error happened.
|
||||
///
|
||||
QByteArray QSimpleCrypto::QBlockCipher::decryptAesBlockCipher(QByteArray data, QByteArray key,
|
||||
QByteArray iv,
|
||||
const int& rounds, const EVP_CIPHER* cipher, const EVP_MD* md)
|
||||
{
|
||||
try {
|
||||
/* Initialize EVP_CIPHER_CTX */
|
||||
std::unique_ptr<EVP_CIPHER_CTX, void (*)(EVP_CIPHER_CTX*)> decryptionCipher { EVP_CIPHER_CTX_new(), EVP_CIPHER_CTX_free };
|
||||
if (decryptionCipher == nullptr) {
|
||||
throw std::runtime_error("Couldn't initialize \'decryptionCipher\'. EVP_CIPHER_CTX_new(). Error: " + QByteArray(ERR_error_string(ERR_get_error(), nullptr)));
|
||||
}
|
||||
|
||||
/* Reinterpret values for multi use */
|
||||
unsigned char* m_key = reinterpret_cast<unsigned char*>(key.data());
|
||||
unsigned char* m_iv = reinterpret_cast<unsigned char*>(iv.data());
|
||||
|
||||
/* Set data length */
|
||||
int plainTextLength(data.size());
|
||||
int finalLength = 0;
|
||||
|
||||
/* Initialize plainText. Here decrypted data will be stored */
|
||||
std::unique_ptr<unsigned char[]> plainText { new unsigned char[plainTextLength + AES_BLOCK_SIZE]() };
|
||||
if (plainText == nullptr) {
|
||||
throw std::runtime_error("Couldn't allocate memory for \'plainText\'. EVP_CIPHER_CTX_new(). Error: " + QByteArray(ERR_error_string(ERR_get_error(), nullptr)));
|
||||
}
|
||||
|
||||
// Bug here
|
||||
// /* Start encryption with password based encryption routine */
|
||||
// if (!EVP_BytesToKey(cipher, md, reinterpret_cast<const unsigned char*>(salt.data()), reinterpret_cast<const unsigned char*>(password.data()), password.length(), rounds, m_key, m_iv)) {
|
||||
// throw std::runtime_error("Couldn't start decryption routine. EVP_BytesToKey(). Error: " + QByteArray(ERR_error_string(ERR_get_error(), nullptr)));
|
||||
// }
|
||||
|
||||
/* Initialize decryption operation. */
|
||||
if (!EVP_DecryptInit_ex(decryptionCipher.get(), cipher, nullptr, m_key, m_iv)) {
|
||||
throw std::runtime_error("Couldn't initialize decryption operation. EVP_DecryptInit_ex(). Error: " + QByteArray(ERR_error_string(ERR_get_error(), nullptr)));
|
||||
}
|
||||
|
||||
/*
|
||||
* Provide the message to be decrypted, and obtain the plaintext output.
|
||||
* EVP_DecryptUpdate can be called multiple times if necessary
|
||||
*/
|
||||
if (!EVP_DecryptUpdate(decryptionCipher.get(), plainText.get(), &plainTextLength, reinterpret_cast<const unsigned char*>(data.data()), data.size())) {
|
||||
throw std::runtime_error("Couldn't provide message to be decrypted. EVP_DecryptUpdate(). Error: " + QByteArray(ERR_error_string(ERR_get_error(), nullptr)));
|
||||
}
|
||||
|
||||
/*
|
||||
* Finalize the decryption. A positive return value indicates success,
|
||||
* anything else is a failure - the plaintext is not trustworthy.
|
||||
*/
|
||||
if (!EVP_DecryptFinal(decryptionCipher.get(), plainText.get() + plainTextLength, &finalLength)) {
|
||||
throw std::runtime_error("Couldn't finalize decryption. EVP_DecryptFinal. Error: " + QByteArray(ERR_error_string(ERR_get_error(), nullptr)));
|
||||
}
|
||||
|
||||
/* Finilize data to be readable with qt */
|
||||
QByteArray decryptedData = QByteArray(reinterpret_cast<char*>(plainText.get()), plainTextLength + finalLength);
|
||||
|
||||
return decryptedData;
|
||||
|
||||
} catch (std::exception& exception) {
|
||||
QSimpleCrypto::QBlockCipher::error.setError(1, exception.what());
|
||||
return QByteArray(exception.what());
|
||||
} catch (...) {
|
||||
QSimpleCrypto::QBlockCipher::error.setError(2, "Unknown error!");
|
||||
return QByteArray();
|
||||
}
|
||||
|
||||
return QByteArray();
|
||||
}
|
||||
|
|
@ -1,6 +0,0 @@
|
|||
#include "include/QCryptoError.h"
|
||||
|
||||
QSimpleCrypto::QCryptoError::QCryptoError(QObject* parent)
|
||||
: QObject(parent)
|
||||
{
|
||||
}
|
||||
|
|
@ -1,274 +0,0 @@
|
|||
/**
|
||||
* Copyright 2021 BrutalWizard (https://github.com/bru74lw1z4rd). All Rights Reserved.
|
||||
*
|
||||
* Licensed under the Apache License 2.0 (the "License"). You may not use
|
||||
* this file except in compliance with the License. You can obtain a copy
|
||||
* in the file LICENSE in the source distribution
|
||||
**/
|
||||
|
||||
#include "include/QRsa.h"
|
||||
|
||||
QSimpleCrypto::QRsa::QRsa()
|
||||
{
|
||||
}
|
||||
|
||||
///
|
||||
/// \brief QSimpleCrypto::QRSA::generateRsaKeys - Function generate Rsa Keys and returns them in OpenSSL structure.
|
||||
/// \param bits - RSA key size.
|
||||
/// \param rsaBigNumber - The exponent is an odd number, typically 3, 17 or 65537.
|
||||
/// \return Returns 'OpenSSL RSA structure' or 'nullptr', if error happened. Returned value must be cleaned up with 'RSA_free()' to avoid memory leak.
|
||||
///
|
||||
RSA* QSimpleCrypto::QRsa::generateRsaKeys(const int& bits, const int& rsaBigNumber)
|
||||
{
|
||||
try {
|
||||
/* Initialize big number */
|
||||
std::unique_ptr<BIGNUM, void (*)(BIGNUM*)> bigNumber { BN_new(), BN_free };
|
||||
if (bigNumber == nullptr) {
|
||||
throw std::runtime_error("Couldn't initialize \'bigNumber\'. BN_new(). Error: " + QByteArray(ERR_error_string(ERR_get_error(), nullptr)));
|
||||
return nullptr;
|
||||
}
|
||||
|
||||
/* Set big number */
|
||||
if (!BN_set_word(bigNumber.get(), rsaBigNumber)) {
|
||||
throw std::runtime_error("Couldn't set bigNumber. BN_set_word(). Error: " + QByteArray(ERR_error_string(ERR_get_error(), nullptr)));
|
||||
}
|
||||
|
||||
/* Initialize RSA */
|
||||
RSA* rsa = nullptr;
|
||||
if (!(rsa = RSA_new())) {
|
||||
throw std::runtime_error("Couldn't initialize x509. X509_new(). Error: " + QByteArray(ERR_error_string(ERR_get_error(), nullptr)));
|
||||
}
|
||||
|
||||
/* Generate key pair and store it in RSA */
|
||||
if (!RSA_generate_key_ex(rsa, bits, bigNumber.get(), nullptr)) {
|
||||
throw std::runtime_error("Couldn't generate RSA. RSA_generate_key_ex(). Error: " + QByteArray(ERR_error_string(ERR_get_error(), nullptr)));
|
||||
}
|
||||
|
||||
return rsa;
|
||||
} catch (std::exception& exception) {
|
||||
QSimpleCrypto::QRsa::error.setError(1, exception.what());
|
||||
return nullptr;
|
||||
} catch (...) {
|
||||
QSimpleCrypto::QRsa::error.setError(2, "Unknown error!");
|
||||
return nullptr;
|
||||
}
|
||||
}
|
||||
|
||||
///
|
||||
/// \brief QSimpleCrypto::QRSA::savePublicKey - Saves to file RSA public key.
|
||||
/// \param rsa - OpenSSL RSA structure.
|
||||
/// \param publicKeyFileName - Public key file name.
|
||||
///
|
||||
void QSimpleCrypto::QRsa::savePublicKey(RSA* rsa, const QByteArray& publicKeyFileName)
|
||||
{
|
||||
try {
|
||||
/* Initialize BIO */
|
||||
std::unique_ptr<BIO, void (*)(BIO*)> bioPublicKey { BIO_new_file(publicKeyFileName.data(), "w+"), BIO_free_all };
|
||||
if (bioPublicKey == nullptr) {
|
||||
throw std::runtime_error("Couldn't initialize \'bioPublicKey\'. BIO_new_file(). Error: " + QByteArray(ERR_error_string(ERR_get_error(), nullptr)));
|
||||
}
|
||||
|
||||
/* Write public key on file */
|
||||
if (!PEM_write_bio_RSA_PUBKEY(bioPublicKey.get(), rsa)) {
|
||||
throw std::runtime_error("Couldn't save public key. PEM_write_bio_RSAPublicKey(). Error: " + QByteArray(ERR_error_string(ERR_get_error(), nullptr)));
|
||||
}
|
||||
} catch (std::exception& exception) {
|
||||
QSimpleCrypto::QRsa::error.setError(1, exception.what());
|
||||
return;
|
||||
} catch (...) {
|
||||
QSimpleCrypto::QRsa::error.setError(2, "Unknown error!");
|
||||
return;
|
||||
}
|
||||
}
|
||||
|
||||
///
|
||||
/// \brief QSimpleCrypto::QRSA::savePrivateKey - Saves to file RSA private key.
|
||||
/// \param rsa - OpenSSL RSA structure.
|
||||
/// \param privateKeyFileName - Private key file name.
|
||||
/// \param password - Private key password.
|
||||
/// \param cipher - Can be used with 'OpenSSL EVP_CIPHER' (ecb, cbc, cfb, ofb, ctr) - 128, 192, 256. Example: EVP_aes_256_cbc().
|
||||
///
|
||||
void QSimpleCrypto::QRsa::savePrivateKey(RSA* rsa, const QByteArray& privateKeyFileName, QByteArray password, const EVP_CIPHER* cipher)
|
||||
{
|
||||
try {
|
||||
/* Initialize BIO */
|
||||
std::unique_ptr<BIO, void (*)(BIO*)> bioPrivateKey { BIO_new_file(privateKeyFileName.data(), "w+"), BIO_free_all };
|
||||
if (bioPrivateKey == nullptr) {
|
||||
throw std::runtime_error("Couldn't initialize bioPrivateKey. BIO_new_file(). Error: " + QByteArray(ERR_error_string(ERR_get_error(), nullptr)));
|
||||
}
|
||||
|
||||
/* Write private key to file */
|
||||
if (!PEM_write_bio_RSAPrivateKey(bioPrivateKey.get(), rsa, cipher, reinterpret_cast<unsigned char*>(password.data()), password.size(), nullptr, nullptr)) {
|
||||
throw std::runtime_error("Couldn't save private key. PEM_write_bio_RSAPrivateKey(). Error: " + QByteArray(ERR_error_string(ERR_get_error(), nullptr)));
|
||||
}
|
||||
} catch (std::exception& exception) {
|
||||
QSimpleCrypto::QRsa::error.setError(1, exception.what());
|
||||
return;
|
||||
} catch (...) {
|
||||
QSimpleCrypto::QRsa::error.setError(2, "Unknown error!");
|
||||
return;
|
||||
}
|
||||
}
|
||||
|
||||
///
|
||||
/// \brief QSimpleCrypto::QRSA::getPublicKeyFromFile - Gets RSA public key from a file.
|
||||
/// \param filePath - File path to public key file.
|
||||
/// \return Returns 'OpenSSL EVP_PKEY structure' or 'nullptr', if error happened. Returned value must be cleaned up with 'EVP_PKEY_free()' to avoid memory leak.
|
||||
///
|
||||
EVP_PKEY* QSimpleCrypto::QRsa::getPublicKeyFromFile(const QByteArray& filePath)
|
||||
{
|
||||
try {
|
||||
/* Initialize BIO */
|
||||
std::unique_ptr<BIO, void (*)(BIO*)> bioPublicKey { BIO_new_file(filePath.data(), "r"), BIO_free_all };
|
||||
if (bioPublicKey == nullptr) {
|
||||
throw std::runtime_error("Couldn't initialize bioPublicKey. BIO_new_file(). Error: " + QByteArray(ERR_error_string(ERR_get_error(), nullptr)));
|
||||
}
|
||||
|
||||
/* Initialize EVP_PKEY */
|
||||
EVP_PKEY* keyStore = nullptr;
|
||||
if (!(keyStore = EVP_PKEY_new())) {
|
||||
throw std::runtime_error("Couldn't initialize keyStore. EVP_PKEY_new(). Error: " + QByteArray(ERR_error_string(ERR_get_error(), nullptr)));
|
||||
}
|
||||
|
||||
/* Write private key to file */
|
||||
if (!PEM_read_bio_PUBKEY(bioPublicKey.get(), &keyStore, nullptr, nullptr)) {
|
||||
throw std::runtime_error("Couldn't read private key. PEM_read_bio_PrivateKey(). Error: " + QByteArray(ERR_error_string(ERR_get_error(), nullptr)));
|
||||
}
|
||||
|
||||
return keyStore;
|
||||
|
||||
} catch (std::exception& exception) {
|
||||
QSimpleCrypto::QRsa::error.setError(1, exception.what());
|
||||
return nullptr;
|
||||
} catch (...) {
|
||||
QSimpleCrypto::QRsa::error.setError(2, "Unknown error!");
|
||||
return nullptr;
|
||||
}
|
||||
}
|
||||
|
||||
///
|
||||
/// \brief QSimpleCrypto::QRSA::getPrivateKeyFromFile - Gets RSA private key from a file.
|
||||
/// \param filePath - File path to private key file.
|
||||
/// \param password - Private key password.
|
||||
/// \return - Returns 'OpenSSL EVP_PKEY structure' or 'nullptr', if error happened. Returned value must be cleaned up with 'EVP_PKEY_free()' to avoid memory leak.
|
||||
///
|
||||
EVP_PKEY* QSimpleCrypto::QRsa::getPrivateKeyFromFile(const QByteArray& filePath, const QByteArray& password)
|
||||
{
|
||||
try {
|
||||
/* Initialize BIO */
|
||||
std::unique_ptr<BIO, void (*)(BIO*)> bioPrivateKey { BIO_new_file(filePath.data(), "r"), BIO_free_all };
|
||||
if (bioPrivateKey == nullptr) {
|
||||
throw std::runtime_error("Couldn't initialize bioPrivateKey. BIO_new_file(). Error: " + QByteArray(ERR_error_string(ERR_get_error(), nullptr)));
|
||||
}
|
||||
|
||||
/* Initialize EVP_PKEY */
|
||||
EVP_PKEY* keyStore = nullptr;
|
||||
if (!(keyStore = EVP_PKEY_new())) {
|
||||
throw std::runtime_error("Couldn't initialize keyStore. EVP_PKEY_new(). Error: " + QByteArray(ERR_error_string(ERR_get_error(), nullptr)));
|
||||
}
|
||||
|
||||
/* Write private key to file */
|
||||
if (!PEM_read_bio_PrivateKey(bioPrivateKey.get(), &keyStore, nullptr, (void*)password.data())) {
|
||||
throw std::runtime_error("Couldn't read private key. PEM_read_bio_PrivateKey(). Error: " + QByteArray(ERR_error_string(ERR_get_error(), nullptr)));
|
||||
}
|
||||
|
||||
return keyStore;
|
||||
|
||||
} catch (std::exception& exception) {
|
||||
QSimpleCrypto::QRsa::error.setError(1, exception.what());
|
||||
return nullptr;
|
||||
} catch (...) {
|
||||
QSimpleCrypto::QRsa::error.setError(2, "Unknown error!");
|
||||
return nullptr;
|
||||
}
|
||||
}
|
||||
|
||||
///
|
||||
/// \brief QSimpleCrypto::QRSA::encrypt - Encrypt data with RSA algorithm.
|
||||
/// \param plaintext - Text that must be encrypted.
|
||||
/// \param rsa - OpenSSL RSA structure.
|
||||
/// \param encryptType - Public or private encrypt type. (PUBLIC_ENCRYPT, PRIVATE_ENCRYPT).
|
||||
/// \param padding - OpenSSL RSA padding can be used with: 'RSA_PKCS1_PADDING', 'RSA_NO_PADDING' and etc.
|
||||
/// \return Returns encrypted data or "", if error happened.
|
||||
///
|
||||
QByteArray QSimpleCrypto::QRsa::encrypt(QByteArray plainText, RSA* rsa, const int& encryptType, const int& padding)
|
||||
{
|
||||
try {
|
||||
/* Initialize array. Here encrypted data will be saved */
|
||||
std::unique_ptr<unsigned char[]> cipherText { new unsigned char[RSA_size(rsa)]() };
|
||||
if (cipherText == nullptr) {
|
||||
throw std::runtime_error("Couldn't allocate memory for 'cipherText'.");
|
||||
}
|
||||
|
||||
/* Result of encryption operation */
|
||||
short int result = 0;
|
||||
|
||||
/* Execute encryption operation */
|
||||
if (encryptType == PublicDecrypt) {
|
||||
result = RSA_public_encrypt(plainText.size(), reinterpret_cast<unsigned char*>(plainText.data()), cipherText.get(), rsa, padding);
|
||||
} else if (encryptType == PrivateDecrypt) {
|
||||
result = RSA_private_encrypt(plainText.size(), reinterpret_cast<unsigned char*>(plainText.data()), cipherText.get(), rsa, padding);
|
||||
}
|
||||
|
||||
/* Check for result */
|
||||
if (result <= -1) {
|
||||
throw std::runtime_error("Couldn't encrypt data. Error: " + QByteArray(ERR_error_string(ERR_get_error(), nullptr)));
|
||||
}
|
||||
|
||||
/* Get encrypted data */
|
||||
const QByteArray& encryptedData = QByteArray(reinterpret_cast<char*>(cipherText.get()), RSA_size(rsa));
|
||||
|
||||
return encryptedData;
|
||||
} catch (std::exception& exception) {
|
||||
QSimpleCrypto::QRsa::error.setError(1, exception.what());
|
||||
return "";
|
||||
} catch (...) {
|
||||
QSimpleCrypto::QRsa::error.setError(2, "Unknown error!");
|
||||
return "";
|
||||
}
|
||||
}
|
||||
|
||||
///
|
||||
/// \brief QSimpleCrypto::QRSA::decrypt - Decrypt data with RSA algorithm.
|
||||
/// \param cipherText - Text that must be decrypted.
|
||||
/// \param rsa - OpenSSL RSA structure.
|
||||
/// \param decryptType - Public or private type. (PUBLIC_DECRYPT, PRIVATE_DECRYPT).
|
||||
/// \param padding - RSA padding can be used with: 'RSA_PKCS1_PADDING', 'RSA_NO_PADDING' and etc.
|
||||
/// \return - Returns decrypted data or "", if error happened.
|
||||
///
|
||||
QByteArray QSimpleCrypto::QRsa::decrypt(QByteArray cipherText, RSA* rsa, const int& decryptType, const int& padding)
|
||||
{
|
||||
try {
|
||||
/* Initialize array. Here decrypted data will be saved */
|
||||
std::unique_ptr<unsigned char[]> plainText { new unsigned char[cipherText.size()]() };
|
||||
if (plainText == nullptr) {
|
||||
throw std::runtime_error("Couldn't allocate memory for 'plainText'.");
|
||||
}
|
||||
|
||||
/* Result of decryption operation */
|
||||
short int result = 0;
|
||||
|
||||
/* Execute decryption operation */
|
||||
if (decryptType == PublicDecrypt) {
|
||||
result = RSA_public_decrypt(RSA_size(rsa), reinterpret_cast<unsigned char*>(cipherText.data()), plainText.get(), rsa, padding);
|
||||
} else if (decryptType == PrivateDecrypt) {
|
||||
result = RSA_private_decrypt(RSA_size(rsa), reinterpret_cast<unsigned char*>(cipherText.data()), plainText.get(), rsa, padding);
|
||||
}
|
||||
|
||||
/* Check for result */
|
||||
if (result <= -1) {
|
||||
throw std::runtime_error("Couldn't decrypt data. Error: " + QByteArray(ERR_error_string(ERR_get_error(), nullptr)));
|
||||
}
|
||||
|
||||
/* Get decrypted data */
|
||||
const QByteArray& decryptedData = QByteArray(reinterpret_cast<char*>(plainText.get()));
|
||||
|
||||
return decryptedData;
|
||||
} catch (std::exception& exception) {
|
||||
QSimpleCrypto::QRsa::error.setError(1, exception.what());
|
||||
return "";
|
||||
} catch (...) {
|
||||
QSimpleCrypto::QRsa::error.setError(2, "Unknown error!");
|
||||
return "";
|
||||
}
|
||||
}
|
||||
|
|
@ -1,234 +0,0 @@
|
|||
/**
|
||||
* Copyright 2021 BrutalWizard (https://github.com/bru74lw1z4rd). All Rights Reserved.
|
||||
*
|
||||
* Licensed under the Apache License 2.0 (the "License"). You may not use
|
||||
* this file except in compliance with the License. You can obtain a copy
|
||||
* in the file LICENSE in the source distribution
|
||||
**/
|
||||
|
||||
#include "include/QX509.h"
|
||||
|
||||
QSimpleCrypto::QX509::QX509()
|
||||
{
|
||||
}
|
||||
|
||||
///
|
||||
/// \brief QSimpleCrypto::QX509::loadCertificateFromFile - Function load X509 from file and returns OpenSSL structure.
|
||||
/// \param fileName - File path to certificate.
|
||||
/// \return Returns OpenSSL X509 structure or nullptr, if error happened. Returned value must be cleaned up with 'X509_free' to avoid memory leak.
|
||||
///
|
||||
X509* QSimpleCrypto::QX509::loadCertificateFromFile(const QByteArray& fileName)
|
||||
{
|
||||
try {
|
||||
/* Initialize X509 */
|
||||
X509* x509 = nullptr;
|
||||
if (!(x509 = X509_new())) {
|
||||
throw std::runtime_error("Couldn't initialize X509. X509_new(). Error: " + QByteArray(ERR_error_string(ERR_get_error(), nullptr)));
|
||||
}
|
||||
|
||||
/* Initialize BIO */
|
||||
std::unique_ptr<BIO, void (*)(BIO*)> certFile { BIO_new_file(fileName.data(), "r+"), BIO_free_all };
|
||||
if (certFile == nullptr) {
|
||||
throw std::runtime_error("Couldn't initialize certFile. BIO_new_file(). Error: " + QByteArray(ERR_error_string(ERR_get_error(), nullptr)));
|
||||
}
|
||||
|
||||
/* Read file */
|
||||
if (!PEM_read_bio_X509(certFile.get(), &x509, nullptr, nullptr)) {
|
||||
throw std::runtime_error("Couldn't read certificate file from disk. PEM_read_bio_X509(). Error: " + QByteArray(ERR_error_string(ERR_get_error(), nullptr)));
|
||||
}
|
||||
|
||||
return x509;
|
||||
} catch (std::exception& exception) {
|
||||
QSimpleCrypto::QX509::error.setError(1, exception.what());
|
||||
return nullptr;
|
||||
} catch (...) {
|
||||
QSimpleCrypto::QX509::error.setError(2, "Unknown error!");
|
||||
return nullptr;
|
||||
}
|
||||
}
|
||||
|
||||
///
|
||||
/// \brief QSimpleCrypto::QX509::signCertificate - Function signs X509 certificate and returns signed X509 OpenSSL structure.
|
||||
/// \param endCertificate - Certificate that will be signed
|
||||
/// \param caCertificate - CA certificate that will sign end certificate
|
||||
/// \param caPrivateKey - CA certificate private key
|
||||
/// \param fileName - With that name certificate will be saved. Leave "", if don't need to save it
|
||||
/// \return Returns OpenSSL X509 structure or nullptr, if error happened.
|
||||
///
|
||||
X509* QSimpleCrypto::QX509::signCertificate(X509* endCertificate, X509* caCertificate, EVP_PKEY* caPrivateKey, const QByteArray& fileName)
|
||||
{
|
||||
try {
|
||||
/* Set issuer to CA's subject. */
|
||||
if (!X509_set_issuer_name(endCertificate, X509_get_subject_name(caCertificate))) {
|
||||
throw std::runtime_error("Couldn't set issuer name for X509. X509_set_issuer_name(). Error: " + QByteArray(ERR_error_string(ERR_get_error(), nullptr)));
|
||||
}
|
||||
|
||||
/* Sign the certificate with key. */
|
||||
if (!X509_sign(endCertificate, caPrivateKey, EVP_sha256())) {
|
||||
throw std::runtime_error("Couldn't sign X509. X509_sign(). Error: " + QByteArray(ERR_error_string(ERR_get_error(), nullptr)));
|
||||
}
|
||||
|
||||
/* Write certificate file on disk. If needed */
|
||||
if (!fileName.isEmpty()) {
|
||||
/* Initialize BIO */
|
||||
std::unique_ptr<BIO, void (*)(BIO*)> certFile { BIO_new_file(fileName.data(), "w+"), BIO_free_all };
|
||||
if (certFile == nullptr) {
|
||||
throw std::runtime_error("Couldn't initialize certFile. BIO_new_file(). Error: " + QByteArray(ERR_error_string(ERR_get_error(), nullptr)));
|
||||
}
|
||||
|
||||
/* Write file on disk */
|
||||
if (!PEM_write_bio_X509(certFile.get(), endCertificate)) {
|
||||
throw std::runtime_error("Couldn't write certificate file on disk. PEM_write_bio_X509(). Error: " + QByteArray(ERR_error_string(ERR_get_error(), nullptr)));
|
||||
}
|
||||
}
|
||||
|
||||
return endCertificate;
|
||||
} catch (std::exception& exception) {
|
||||
QSimpleCrypto::QX509::error.setError(1, exception.what());
|
||||
return nullptr;
|
||||
} catch (...) {
|
||||
QSimpleCrypto::QX509::error.setError(2, "Unknown error!");
|
||||
return nullptr;
|
||||
}
|
||||
}
|
||||
|
||||
///
|
||||
/// \brief QSimpleCrypto::QX509::verifyCertificate - Function verifies X509 certificate and returns verified X509 OpenSSL structure.
|
||||
/// \param x509 - OpenSSL X509. That certificate will be verified.
|
||||
/// \param store - Trusted certificate must be added to X509_Store with 'addCertificateToStore(X509_STORE* ctx, X509* x509)'.
|
||||
/// \return Returns OpenSSL X509 structure or nullptr, if error happened
|
||||
///
|
||||
X509* QSimpleCrypto::QX509::verifyCertificate(X509* x509, X509_STORE* store)
|
||||
{
|
||||
try {
|
||||
/* Initialize X509_STORE_CTX */
|
||||
std::unique_ptr<X509_STORE_CTX, void (*)(X509_STORE_CTX*)> ctx { X509_STORE_CTX_new(), X509_STORE_CTX_free };
|
||||
if (ctx == nullptr) {
|
||||
throw std::runtime_error("Couldn't initialize keyStore. EVP_PKEY_new(). Error: " + QByteArray(ERR_error_string(ERR_get_error(), nullptr)));
|
||||
}
|
||||
|
||||
/* Set up CTX for a subsequent verification operation */
|
||||
if (!X509_STORE_CTX_init(ctx.get(), store, x509, nullptr)) {
|
||||
throw std::runtime_error("Couldn't initialize X509_STORE_CTX. X509_STORE_CTX_init(). Error: " + QByteArray(ERR_error_string(ERR_get_error(), nullptr)));
|
||||
}
|
||||
|
||||
/* Verify X509 */
|
||||
if (!X509_verify_cert(ctx.get())) {
|
||||
throw std::runtime_error("Couldn't verify cert. X509_verify_cert(). Error: " + QByteArray(ERR_error_string(ERR_get_error(), nullptr)));
|
||||
}
|
||||
|
||||
return x509;
|
||||
} catch (std::exception& exception) {
|
||||
QSimpleCrypto::QX509::error.setError(1, exception.what());
|
||||
return nullptr;
|
||||
} catch (...) {
|
||||
QSimpleCrypto::QX509::error.setError(2, "Unknown error!");
|
||||
return nullptr;
|
||||
}
|
||||
}
|
||||
|
||||
///
|
||||
/// \brief QSimpleCrypto::QX509::generateSelfSignedCertificate - Function generatesand returns self signed X509.
|
||||
/// \param rsa - OpenSSL RSA.
|
||||
/// \param additionalData - Certificate information.
|
||||
/// \param certificateFileName - With that name certificate will be saved. Leave "", if don't need to save it.
|
||||
/// \param md - OpenSSL EVP_MD structure. Example: EVP_sha512().
|
||||
/// \param serialNumber - X509 certificate serial number.
|
||||
/// \param version - X509 certificate version.
|
||||
/// \param notBefore - X509 start date.
|
||||
/// \param notAfter - X509 end date.
|
||||
/// \return Returns OpenSSL X509 structure or nullptr, if error happened. Returned value must be cleaned up with 'X509_free' to avoid memory leak.
|
||||
///
|
||||
X509* QSimpleCrypto::QX509::generateSelfSignedCertificate(RSA* rsa, const QMap<QByteArray, QByteArray>& additionalData,
|
||||
const QByteArray& certificateFileName, const EVP_MD* md,
|
||||
const long& serialNumber, const long& version,
|
||||
const long& notBefore, const long& notAfter)
|
||||
{
|
||||
try {
|
||||
/* Initialize X509 */
|
||||
X509* x509 = nullptr;
|
||||
if (!(x509 = X509_new())) {
|
||||
throw std::runtime_error("Couldn't initialize X509. X509_new(). Error: " + QByteArray(ERR_error_string(ERR_get_error(), nullptr)));
|
||||
}
|
||||
|
||||
/* Initialize EVP_PKEY */
|
||||
std::unique_ptr<EVP_PKEY, void (*)(EVP_PKEY*)> keyStore { EVP_PKEY_new(), EVP_PKEY_free };
|
||||
if (keyStore == nullptr) {
|
||||
throw std::runtime_error("Couldn't initialize keyStore. EVP_PKEY_new(). Error: " + QByteArray(ERR_error_string(ERR_get_error(), nullptr)));
|
||||
}
|
||||
|
||||
/* Sign rsa key */
|
||||
if (!EVP_PKEY_assign_RSA(keyStore.get(), rsa)) {
|
||||
throw std::runtime_error("Couldn't assign rsa. EVP_PKEY_assign_RSA(). Error: " + QByteArray(ERR_error_string(ERR_get_error(), nullptr)));
|
||||
}
|
||||
|
||||
/* Set certificate serial number. */
|
||||
if (!ASN1_INTEGER_set(X509_get_serialNumber(x509), serialNumber)) {
|
||||
throw std::runtime_error("Couldn't set serial number. ASN1_INTEGER_set(). Error: " + QByteArray(ERR_error_string(ERR_get_error(), nullptr)));
|
||||
}
|
||||
|
||||
/* Set certificate version */
|
||||
if (!X509_set_version(x509, version)) {
|
||||
throw std::runtime_error("Couldn't set version. X509_set_version(). Error: " + QByteArray(ERR_error_string(ERR_get_error(), nullptr)));
|
||||
}
|
||||
|
||||
/* Set certificate creation and expiration date */
|
||||
X509_gmtime_adj(X509_get_notBefore(x509), notBefore);
|
||||
X509_gmtime_adj(X509_get_notAfter(x509), notAfter);
|
||||
|
||||
/* Set certificate public key */
|
||||
if (!X509_set_pubkey(x509, keyStore.get())) {
|
||||
throw std::runtime_error("Couldn't set public key. X509_set_pubkey(). Error: " + QByteArray(ERR_error_string(ERR_get_error(), nullptr)));
|
||||
}
|
||||
|
||||
/* Initialize X509_NAME */
|
||||
X509_NAME* x509Name = X509_get_subject_name(x509);
|
||||
if (x509Name == nullptr) {
|
||||
throw std::runtime_error("Couldn't initialize X509_NAME. X509_NAME(). Error: " + QByteArray(ERR_error_string(ERR_get_error(), nullptr)));
|
||||
}
|
||||
|
||||
/* Add additional data to certificate */
|
||||
QMapIterator<QByteArray, QByteArray> certificateInformationList(additionalData);
|
||||
while (certificateInformationList.hasNext()) {
|
||||
/* Read next item in list */
|
||||
certificateInformationList.next();
|
||||
|
||||
/* Set additional data */
|
||||
if (!X509_NAME_add_entry_by_txt(x509Name, certificateInformationList.key().data(), MBSTRING_UTF8, reinterpret_cast<const unsigned char*>(certificateInformationList.value().data()), -1, -1, 0)) {
|
||||
throw std::runtime_error("Couldn't set additional information. X509_NAME_add_entry_by_txt(). Error: " + QByteArray(ERR_error_string(ERR_get_error(), nullptr)));
|
||||
}
|
||||
}
|
||||
|
||||
/* Set certificate info */
|
||||
if (!X509_set_issuer_name(x509, x509Name)) {
|
||||
throw std::runtime_error("Couldn't set issuer name. X509_set_issuer_name(). Error: " + QByteArray(ERR_error_string(ERR_get_error(), nullptr)));
|
||||
}
|
||||
|
||||
/* Sign certificate */
|
||||
if (!X509_sign(x509, keyStore.get(), md)) {
|
||||
throw std::runtime_error("Couldn't sign X509. X509_sign(). Error: " + QByteArray(ERR_error_string(ERR_get_error(), nullptr)));
|
||||
}
|
||||
|
||||
/* Write certificate file on disk. If needed */
|
||||
if (!certificateFileName.isEmpty()) {
|
||||
/* Initialize BIO */
|
||||
std::unique_ptr<BIO, void (*)(BIO*)> certFile { BIO_new_file(certificateFileName.data(), "w+"), BIO_free_all };
|
||||
if (certFile == nullptr) {
|
||||
throw std::runtime_error("Couldn't initialize certFile. BIO_new_file(). Error: " + QByteArray(ERR_error_string(ERR_get_error(), nullptr)));
|
||||
}
|
||||
|
||||
/* Write file on disk */
|
||||
if (!PEM_write_bio_X509(certFile.get(), x509)) {
|
||||
throw std::runtime_error("Couldn't write certificate file on disk. PEM_write_bio_X509(). Error: " + QByteArray(ERR_error_string(ERR_get_error(), nullptr)));
|
||||
}
|
||||
}
|
||||
|
||||
return x509;
|
||||
} catch (std::exception& exception) {
|
||||
QSimpleCrypto::QX509::error.setError(1, exception.what());
|
||||
return nullptr;
|
||||
} catch (...) {
|
||||
QSimpleCrypto::QX509::error.setError(2, "Unknown error!");
|
||||
return nullptr;
|
||||
}
|
||||
}
|
||||
|
|
@ -1,176 +0,0 @@
|
|||
/**
|
||||
* Copyright 2021 BrutalWizard (https://github.com/bru74lw1z4rd). All Rights Reserved.
|
||||
*
|
||||
* Licensed under the Apache License 2.0 (the "License"). You may not use
|
||||
* this file except in compliance with the License. You can obtain a copy
|
||||
* in the file LICENSE in the source distribution
|
||||
**/
|
||||
|
||||
#include "include/QX509Store.h"
|
||||
|
||||
QSimpleCrypto::QX509Store::QX509Store()
|
||||
{
|
||||
}
|
||||
|
||||
///
|
||||
/// \brief QSimpleCrypto::QX509::addCertificateToStore
|
||||
/// \param store - OpenSSL X509_STORE.
|
||||
/// \param x509 - OpenSSL X509.
|
||||
/// \return Returns 'true' on success and 'false', if error happened.
|
||||
///
|
||||
bool QSimpleCrypto::QX509Store::addCertificateToStore(X509_STORE* store, X509* x509)
|
||||
{
|
||||
if (!X509_STORE_add_cert(store, x509)) {
|
||||
QSimpleCrypto::QX509Store::error.setError(1, "Couldn't add certificate to X509_STORE. X509_STORE_add_cert(). Error: " + QByteArray(ERR_error_string(ERR_get_error(), nullptr)));
|
||||
return false;
|
||||
}
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
///
|
||||
/// \brief QSimpleCrypto::QX509Store::addLookup
|
||||
/// \param store - OpenSSL X509_STORE.
|
||||
/// \param method - OpenSSL X509_LOOKUP_METHOD. Example: X509_LOOKUP_file.
|
||||
/// \return Returns 'true' on success and 'false', if error happened.
|
||||
///
|
||||
bool QSimpleCrypto::QX509Store::addLookup(X509_STORE* store, X509_LOOKUP_METHOD* method)
|
||||
{
|
||||
if (!X509_STORE_add_lookup(store, method)) {
|
||||
QSimpleCrypto::QX509Store::error.setError(1, "Couldn't add lookup to X509_STORE. X509_STORE_add_lookup(). Error: " + QByteArray(ERR_error_string(ERR_get_error(), nullptr)));
|
||||
return false;
|
||||
}
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
///
|
||||
/// \brief QSimpleCrypto::QX509Store::setCertificateDepth
|
||||
/// \param store - OpenSSL X509_STORE.
|
||||
/// \param depth - That is the maximum number of untrusted CA certificates that can appear in a chain. Example: 0.
|
||||
/// \return Returns 'true' on success and 'false', if error happened.
|
||||
///
|
||||
bool QSimpleCrypto::QX509Store::setDepth(X509_STORE* store, const int& depth)
|
||||
{
|
||||
if (!X509_STORE_set_depth(store, depth)) {
|
||||
QSimpleCrypto::QX509Store::error.setError(1, "Couldn't set depth for X509_STORE. X509_STORE_set_depth(). Error: " + QByteArray(ERR_error_string(ERR_get_error(), nullptr)));
|
||||
return false;
|
||||
}
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
///
|
||||
/// \brief QSimpleCrypto::QX509Store::setFlag
|
||||
/// \param store - OpenSSL X509_STORE.
|
||||
/// \param flag - The verification flags consists of zero or more of the following flags ored together. Example: X509_V_FLAG_CRL_CHECK.
|
||||
/// \return Returns 'true' on success and 'false', if error happened.
|
||||
///
|
||||
bool QSimpleCrypto::QX509Store::setFlag(X509_STORE* store, const unsigned long& flag)
|
||||
{
|
||||
if (!X509_STORE_set_flags(store, flag)) {
|
||||
QSimpleCrypto::QX509Store::error.setError(1, "Couldn't set flag for X509_STORE. X509_STORE_set_flags(). Error: " + QByteArray(ERR_error_string(ERR_get_error(), nullptr)));
|
||||
return false;
|
||||
}
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
///
|
||||
/// \brief QSimpleCrypto::QX509Store::setFlag
|
||||
/// \param store - OpenSSL X509_STORE.
|
||||
/// \param purpose - Verification purpose in param to purpose. Example: X509_PURPOSE_ANY.
|
||||
/// \return Returns 'true' on success and 'false', if error happened.
|
||||
///
|
||||
bool QSimpleCrypto::QX509Store::setPurpose(X509_STORE* store, const int& purpose)
|
||||
{
|
||||
if (!X509_STORE_set_purpose(store, purpose)) {
|
||||
QSimpleCrypto::QX509Store::error.setError(1, "Couldn't set purpose for X509_STORE. X509_STORE_set_purpose(). Error: " + QByteArray(ERR_error_string(ERR_get_error(), nullptr)));
|
||||
return false;
|
||||
}
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
///
|
||||
/// \brief QSimpleCrypto::QX509Store::setTrust
|
||||
/// \param store - OpenSSL X509_STORE.
|
||||
/// \param trust - Trust Level. Example: X509_TRUST_SSL_SERVER.
|
||||
/// \return Returns 'true' on success and 'false', if error happened.
|
||||
///
|
||||
bool QSimpleCrypto::QX509Store::setTrust(X509_STORE* store, const int& trust)
|
||||
{
|
||||
if (!X509_STORE_set_trust(store, trust)) {
|
||||
QSimpleCrypto::QX509Store::error.setError(1, "Couldn't set trust for X509_STORE. X509_STORE_set_trust(). Error: " + QByteArray(ERR_error_string(ERR_get_error(), nullptr)));
|
||||
return false;
|
||||
}
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
///
|
||||
/// \brief QSimpleCrypto::QX509Store::setDefaultPaths
|
||||
/// \param store - OpenSSL X509_STORE.
|
||||
/// \return Returns 'true' on success and 'false', if error happened.
|
||||
///
|
||||
bool QSimpleCrypto::QX509Store::setDefaultPaths(X509_STORE* store)
|
||||
{
|
||||
if (!X509_STORE_set_default_paths(store)) {
|
||||
QSimpleCrypto::QX509Store::error.setError(1, "Couldn't set default paths for X509_STORE. X509_STORE_set_default_paths(). Error: " + QByteArray(ERR_error_string(ERR_get_error(), nullptr)));
|
||||
return false;
|
||||
}
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
///
|
||||
/// \brief QSimpleCrypto::QX509Store::loadLocations
|
||||
/// \param store - OpenSSL X509_STORE.
|
||||
/// \param fileName - File name. Example: "caCertificate.pem".
|
||||
/// \param dirPath - Path to file. Example: "path/To/File".
|
||||
/// \return Returns 'true' on success and 'false', if error happened.
|
||||
///
|
||||
bool QSimpleCrypto::QX509Store::loadLocations(X509_STORE* store, const QByteArray& fileName, const QByteArray& dirPath)
|
||||
{
|
||||
if (!X509_STORE_load_locations(store, fileName, dirPath)) {
|
||||
QSimpleCrypto::QX509Store::error.setError(1, "Couldn't load locations for X509_STORE. X509_STORE_load_locations(). Error: " + QByteArray(ERR_error_string(ERR_get_error(), nullptr)));
|
||||
return false;
|
||||
}
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
///
|
||||
/// \brief QSimpleCrypto::QX509Store::loadLocations
|
||||
/// \param store - OpenSSL X509_STORE.
|
||||
/// \param file - Qt QFile that will be loaded.
|
||||
/// \return Returns 'true' on success and 'false', if error happened.
|
||||
///
|
||||
bool QSimpleCrypto::QX509Store::loadLocations(X509_STORE* store, const QFile& file)
|
||||
{
|
||||
/* Initialize QFileInfo to read information about file */
|
||||
QFileInfo info(file);
|
||||
|
||||
if (!X509_STORE_load_locations(store, info.fileName().toLocal8Bit(), info.absoluteDir().path().toLocal8Bit())) {
|
||||
QSimpleCrypto::QX509Store::error.setError(1, "Couldn't load locations for X509_STORE. X509_STORE_load_locations(). Error: " + QByteArray(ERR_error_string(ERR_get_error(), nullptr)));
|
||||
return false;
|
||||
}
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
///
|
||||
/// \brief QSimpleCrypto::QX509Store::loadLocations
|
||||
/// \param store - OpenSSL X509_STORE.
|
||||
/// \param fileInfo - Qt QFileInfo.
|
||||
/// \return Returns 'true' on success and 'false', if error happened.
|
||||
///
|
||||
bool QSimpleCrypto::QX509Store::loadLocations(X509_STORE* store, const QFileInfo& fileInfo)
|
||||
{
|
||||
if (!X509_STORE_load_locations(store, fileInfo.fileName().toLocal8Bit(), fileInfo.absoluteDir().path().toLocal8Bit())) {
|
||||
QSimpleCrypto::QX509Store::error.setError(1, "Couldn't load locations for X509_STORE. X509_STORE_load_locations(). Error: " + QByteArray(ERR_error_string(ERR_get_error(), nullptr)));
|
||||
return false;
|
||||
}
|
||||
|
||||
return true;
|
||||
}
|
||||
|
|
@ -1,25 +0,0 @@
|
|||
include_directories(${CMAKE_CURRENT_LIST_DIR})
|
||||
|
||||
find_package(Qt6 REQUIRED COMPONENTS
|
||||
Core Network
|
||||
)
|
||||
set(LIBS ${LIBS} Qt6::Core Qt6::Network)
|
||||
|
||||
|
||||
set(HEADERS ${HEADERS}
|
||||
${CMAKE_CURRENT_LIST_DIR}/singleapplication.h
|
||||
${CMAKE_CURRENT_LIST_DIR}/singleapplication_p.h
|
||||
)
|
||||
|
||||
set(SOURCES ${SOURCES}
|
||||
${CMAKE_CURRENT_LIST_DIR}/singleapplication.cpp
|
||||
${CMAKE_CURRENT_LIST_DIR}/singleapplication_p.cpp
|
||||
)
|
||||
|
||||
if(WIN32)
|
||||
if(MSVC)
|
||||
set(LIBS ${LIBS} Advapi32.lib)
|
||||
elseif ("${CMAKE_CXX_COMPILER_ID}" STREQUAL "GNU")
|
||||
set(LIBS ${LIBS} advapi32)
|
||||
endif()
|
||||
endif()
|
||||
|
|
@ -1,274 +0,0 @@
|
|||
// The MIT License (MIT)
|
||||
//
|
||||
// Copyright (c) Itay Grudev 2015 - 2020
|
||||
//
|
||||
// Permission is hereby granted, free of charge, to any person obtaining a copy
|
||||
// of this software and associated documentation files (the "Software"), to deal
|
||||
// in the Software without restriction, including without limitation the rights
|
||||
// to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
|
||||
// copies of the Software, and to permit persons to whom the Software is
|
||||
// furnished to do so, subject to the following conditions:
|
||||
//
|
||||
// The above copyright notice and this permission notice shall be included in
|
||||
// all copies or substantial portions of the Software.
|
||||
//
|
||||
// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
||||
// IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
||||
// FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
|
||||
// AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
||||
// LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
|
||||
// OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
|
||||
// THE SOFTWARE.
|
||||
|
||||
#include <QtCore/QElapsedTimer>
|
||||
#include <QtCore/QByteArray>
|
||||
#include <QtCore/QSharedMemory>
|
||||
|
||||
#include "singleapplication.h"
|
||||
#include "singleapplication_p.h"
|
||||
|
||||
/**
|
||||
* @brief Constructor. Checks and fires up LocalServer or closes the program
|
||||
* if another instance already exists
|
||||
* @param argc
|
||||
* @param argv
|
||||
* @param allowSecondary Whether to enable secondary instance support
|
||||
* @param options Optional flags to toggle specific behaviour
|
||||
* @param timeout Maximum time blocking functions are allowed during app load
|
||||
*/
|
||||
SingleApplication::SingleApplication( int &argc, char *argv[], bool allowSecondary, Options options, int timeout, const QString &userData )
|
||||
: app_t( argc, argv ), d_ptr( new SingleApplicationPrivate( this ) )
|
||||
{
|
||||
Q_D( SingleApplication );
|
||||
|
||||
#if defined(Q_OS_ANDROID) || defined(Q_OS_IOS)
|
||||
// On Android and iOS since the library is not supported fallback to
|
||||
// standard QApplication behaviour by simply returning at this point.
|
||||
qWarning() << "SingleApplication is not supported on Android and iOS systems.";
|
||||
return;
|
||||
#endif
|
||||
|
||||
// Store the current mode of the program
|
||||
d->options = options;
|
||||
|
||||
// Add any unique user data
|
||||
if ( ! userData.isEmpty() )
|
||||
d->addAppData( userData );
|
||||
|
||||
// Generating an application ID used for identifying the shared memory
|
||||
// block and QLocalServer
|
||||
d->genBlockServerName();
|
||||
|
||||
// To mitigate QSharedMemory issues with large amount of processes
|
||||
// attempting to attach at the same time
|
||||
SingleApplicationPrivate::randomSleep();
|
||||
|
||||
#ifdef Q_OS_UNIX
|
||||
// By explicitly attaching it and then deleting it we make sure that the
|
||||
// memory is deleted even after the process has crashed on Unix.
|
||||
d->memory = new QSharedMemory( d->blockServerName );
|
||||
d->memory->attach();
|
||||
delete d->memory;
|
||||
#endif
|
||||
// Guarantee thread safe behaviour with a shared memory block.
|
||||
d->memory = new QSharedMemory( d->blockServerName );
|
||||
|
||||
// Create a shared memory block
|
||||
if( d->memory->create( sizeof( InstancesInfo ) )){
|
||||
// Initialize the shared memory block
|
||||
if( ! d->memory->lock() ){
|
||||
qCritical() << "SingleApplication: Unable to lock memory block after create.";
|
||||
abortSafely();
|
||||
}
|
||||
d->initializeMemoryBlock();
|
||||
} else {
|
||||
if( d->memory->error() == QSharedMemory::AlreadyExists ){
|
||||
// Attempt to attach to the memory segment
|
||||
if( ! d->memory->attach() ){
|
||||
qCritical() << "SingleApplication: Unable to attach to shared memory block.";
|
||||
abortSafely();
|
||||
}
|
||||
if( ! d->memory->lock() ){
|
||||
qCritical() << "SingleApplication: Unable to lock memory block after attach.";
|
||||
abortSafely();
|
||||
}
|
||||
} else {
|
||||
qCritical() << "SingleApplication: Unable to create block.";
|
||||
abortSafely();
|
||||
}
|
||||
}
|
||||
|
||||
auto *inst = static_cast<InstancesInfo*>( d->memory->data() );
|
||||
QElapsedTimer time;
|
||||
time.start();
|
||||
|
||||
// Make sure the shared memory block is initialised and in consistent state
|
||||
while( true ){
|
||||
// If the shared memory block's checksum is valid continue
|
||||
if( d->blockChecksum() == inst->checksum ) break;
|
||||
|
||||
// If more than 5s have elapsed, assume the primary instance crashed and
|
||||
// assume it's position
|
||||
if( time.elapsed() > 5000 ){
|
||||
qWarning() << "SingleApplication: Shared memory block has been in an inconsistent state from more than 5s. Assuming primary instance failure.";
|
||||
d->initializeMemoryBlock();
|
||||
}
|
||||
|
||||
// Otherwise wait for a random period and try again. The random sleep here
|
||||
// limits the probability of a collision between two racing apps and
|
||||
// allows the app to initialise faster
|
||||
if( ! d->memory->unlock() ){
|
||||
qDebug() << "SingleApplication: Unable to unlock memory for random wait.";
|
||||
qDebug() << d->memory->errorString();
|
||||
}
|
||||
SingleApplicationPrivate::randomSleep();
|
||||
if( ! d->memory->lock() ){
|
||||
qCritical() << "SingleApplication: Unable to lock memory after random wait.";
|
||||
abortSafely();
|
||||
}
|
||||
}
|
||||
|
||||
if( inst->primary == false ){
|
||||
d->startPrimary();
|
||||
if( ! d->memory->unlock() ){
|
||||
qDebug() << "SingleApplication: Unable to unlock memory after primary start.";
|
||||
qDebug() << d->memory->errorString();
|
||||
}
|
||||
return;
|
||||
}
|
||||
|
||||
// Check if another instance can be started
|
||||
if( allowSecondary ){
|
||||
d->startSecondary();
|
||||
if( d->options & Mode::SecondaryNotification ){
|
||||
d->connectToPrimary( timeout, SingleApplicationPrivate::SecondaryInstance );
|
||||
}
|
||||
if( ! d->memory->unlock() ){
|
||||
qDebug() << "SingleApplication: Unable to unlock memory after secondary start.";
|
||||
qDebug() << d->memory->errorString();
|
||||
}
|
||||
return;
|
||||
}
|
||||
|
||||
if( ! d->memory->unlock() ){
|
||||
qDebug() << "SingleApplication: Unable to unlock memory at end of execution.";
|
||||
qDebug() << d->memory->errorString();
|
||||
}
|
||||
|
||||
d->connectToPrimary( timeout, SingleApplicationPrivate::NewInstance );
|
||||
|
||||
delete d;
|
||||
|
||||
::exit( EXIT_SUCCESS );
|
||||
}
|
||||
|
||||
SingleApplication::~SingleApplication()
|
||||
{
|
||||
Q_D( SingleApplication );
|
||||
delete d;
|
||||
}
|
||||
|
||||
/**
|
||||
* Checks if the current application instance is primary.
|
||||
* @return Returns true if the instance is primary, false otherwise.
|
||||
*/
|
||||
bool SingleApplication::isPrimary() const
|
||||
{
|
||||
Q_D( const SingleApplication );
|
||||
return d->server != nullptr;
|
||||
}
|
||||
|
||||
/**
|
||||
* Checks if the current application instance is secondary.
|
||||
* @return Returns true if the instance is secondary, false otherwise.
|
||||
*/
|
||||
bool SingleApplication::isSecondary() const
|
||||
{
|
||||
Q_D( const SingleApplication );
|
||||
return d->server == nullptr;
|
||||
}
|
||||
|
||||
/**
|
||||
* Allows you to identify an instance by returning unique consecutive instance
|
||||
* ids. It is reset when the first (primary) instance of your app starts and
|
||||
* only incremented afterwards.
|
||||
* @return Returns a unique instance id.
|
||||
*/
|
||||
quint32 SingleApplication::instanceId() const
|
||||
{
|
||||
Q_D( const SingleApplication );
|
||||
return d->instanceNumber;
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns the OS PID (Process Identifier) of the process running the primary
|
||||
* instance. Especially useful when SingleApplication is coupled with OS.
|
||||
* specific APIs.
|
||||
* @return Returns the primary instance PID.
|
||||
*/
|
||||
qint64 SingleApplication::primaryPid() const
|
||||
{
|
||||
Q_D( const SingleApplication );
|
||||
return d->primaryPid();
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns the username the primary instance is running as.
|
||||
* @return Returns the username the primary instance is running as.
|
||||
*/
|
||||
QString SingleApplication::primaryUser() const
|
||||
{
|
||||
Q_D( const SingleApplication );
|
||||
return d->primaryUser();
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns the username the current instance is running as.
|
||||
* @return Returns the username the current instance is running as.
|
||||
*/
|
||||
QString SingleApplication::currentUser() const
|
||||
{
|
||||
return SingleApplicationPrivate::getUsername();
|
||||
}
|
||||
|
||||
/**
|
||||
* Sends message to the Primary Instance.
|
||||
* @param message The message to send.
|
||||
* @param timeout the maximum timeout in milliseconds for blocking functions.
|
||||
* @return true if the message was sent successfuly, false otherwise.
|
||||
*/
|
||||
bool SingleApplication::sendMessage( const QByteArray &message, int timeout )
|
||||
{
|
||||
Q_D( SingleApplication );
|
||||
|
||||
// Nobody to connect to
|
||||
if( isPrimary() ) return false;
|
||||
|
||||
// Make sure the socket is connected
|
||||
if( ! d->connectToPrimary( timeout, SingleApplicationPrivate::Reconnect ) )
|
||||
return false;
|
||||
|
||||
d->socket->write( message );
|
||||
bool dataWritten = d->socket->waitForBytesWritten( timeout );
|
||||
d->socket->flush();
|
||||
return dataWritten;
|
||||
}
|
||||
|
||||
/**
|
||||
* Cleans up the shared memory block and exits with a failure.
|
||||
* This function halts program execution.
|
||||
*/
|
||||
void SingleApplication::abortSafely()
|
||||
{
|
||||
Q_D( SingleApplication );
|
||||
|
||||
qCritical() << "SingleApplication: " << d->memory->error() << d->memory->errorString();
|
||||
delete d;
|
||||
::exit( EXIT_FAILURE );
|
||||
}
|
||||
|
||||
QStringList SingleApplication::userData() const
|
||||
{
|
||||
Q_D( const SingleApplication );
|
||||
return d->appData();
|
||||
}
|
||||
|
|
@ -1,154 +0,0 @@
|
|||
// The MIT License (MIT)
|
||||
//
|
||||
// Copyright (c) Itay Grudev 2015 - 2018
|
||||
//
|
||||
// Permission is hereby granted, free of charge, to any person obtaining a copy
|
||||
// of this software and associated documentation files (the "Software"), to deal
|
||||
// in the Software without restriction, including without limitation the rights
|
||||
// to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
|
||||
// copies of the Software, and to permit persons to whom the Software is
|
||||
// furnished to do so, subject to the following conditions:
|
||||
//
|
||||
// The above copyright notice and this permission notice shall be included in
|
||||
// all copies or substantial portions of the Software.
|
||||
//
|
||||
// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
||||
// IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
||||
// FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
|
||||
// AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
||||
// LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
|
||||
// OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
|
||||
// THE SOFTWARE.
|
||||
|
||||
#ifndef SINGLE_APPLICATION_H
|
||||
#define SINGLE_APPLICATION_H
|
||||
|
||||
#include <QtCore/QtGlobal>
|
||||
#include <QtNetwork/QLocalSocket>
|
||||
|
||||
#ifndef QAPPLICATION_CLASS
|
||||
#define QAPPLICATION_CLASS QApplication
|
||||
#endif
|
||||
|
||||
#include QT_STRINGIFY(QAPPLICATION_CLASS)
|
||||
|
||||
class SingleApplicationPrivate;
|
||||
|
||||
/**
|
||||
* @brief The SingleApplication class handles multiple instances of the same
|
||||
* Application
|
||||
* @see QCoreApplication
|
||||
*/
|
||||
class SingleApplication : public QAPPLICATION_CLASS
|
||||
{
|
||||
Q_OBJECT
|
||||
|
||||
using app_t = QAPPLICATION_CLASS;
|
||||
|
||||
public:
|
||||
/**
|
||||
* @brief Mode of operation of SingleApplication.
|
||||
* Whether the block should be user-wide or system-wide and whether the
|
||||
* primary instance should be notified when a secondary instance had been
|
||||
* started.
|
||||
* @note Operating system can restrict the shared memory blocks to the same
|
||||
* user, in which case the User/System modes will have no effect and the
|
||||
* block will be user wide.
|
||||
* @enum
|
||||
*/
|
||||
enum Mode {
|
||||
User = 1 << 0,
|
||||
System = 1 << 1,
|
||||
SecondaryNotification = 1 << 2,
|
||||
ExcludeAppVersion = 1 << 3,
|
||||
ExcludeAppPath = 1 << 4
|
||||
};
|
||||
Q_DECLARE_FLAGS(Options, Mode)
|
||||
|
||||
/**
|
||||
* @brief Intitializes a SingleApplication instance with argc command line
|
||||
* arguments in argv
|
||||
* @arg {int &} argc - Number of arguments in argv
|
||||
* @arg {const char *[]} argv - Supplied command line arguments
|
||||
* @arg {bool} allowSecondary - Whether to start the instance as secondary
|
||||
* if there is already a primary instance.
|
||||
* @arg {Mode} mode - Whether for the SingleApplication block to be applied
|
||||
* User wide or System wide.
|
||||
* @arg {int} timeout - Timeout to wait in milliseconds.
|
||||
* @note argc and argv may be changed as Qt removes arguments that it
|
||||
* recognizes
|
||||
* @note Mode::SecondaryNotification only works if set on both the primary
|
||||
* instance and the secondary instance.
|
||||
* @note The timeout is just a hint for the maximum time of blocking
|
||||
* operations. It does not guarantee that the SingleApplication
|
||||
* initialisation will be completed in given time, though is a good hint.
|
||||
* Usually 4*timeout would be the worst case (fail) scenario.
|
||||
* @see See the corresponding QAPPLICATION_CLASS constructor for reference
|
||||
*/
|
||||
explicit SingleApplication( int &argc, char *argv[], bool allowSecondary = false, Options options = Mode::User, int timeout = 1000, const QString &userData = {} );
|
||||
~SingleApplication() override;
|
||||
|
||||
/**
|
||||
* @brief Returns if the instance is the primary instance
|
||||
* @returns {bool}
|
||||
*/
|
||||
bool isPrimary() const;
|
||||
|
||||
/**
|
||||
* @brief Returns if the instance is a secondary instance
|
||||
* @returns {bool}
|
||||
*/
|
||||
bool isSecondary() const;
|
||||
|
||||
/**
|
||||
* @brief Returns a unique identifier for the current instance
|
||||
* @returns {qint32}
|
||||
*/
|
||||
quint32 instanceId() const;
|
||||
|
||||
/**
|
||||
* @brief Returns the process ID (PID) of the primary instance
|
||||
* @returns {qint64}
|
||||
*/
|
||||
qint64 primaryPid() const;
|
||||
|
||||
/**
|
||||
* @brief Returns the username of the user running the primary instance
|
||||
* @returns {QString}
|
||||
*/
|
||||
QString primaryUser() const;
|
||||
|
||||
/**
|
||||
* @brief Returns the username of the current user
|
||||
* @returns {QString}
|
||||
*/
|
||||
QString currentUser() const;
|
||||
|
||||
/**
|
||||
* @brief Sends a message to the primary instance. Returns true on success.
|
||||
* @param {int} timeout - Timeout for connecting
|
||||
* @returns {bool}
|
||||
* @note sendMessage() will return false if invoked from the primary
|
||||
* instance.
|
||||
*/
|
||||
bool sendMessage( const QByteArray &message, int timeout = 100 );
|
||||
|
||||
/**
|
||||
* @brief Get the set user data.
|
||||
* @returns {QStringList}
|
||||
*/
|
||||
QStringList userData() const;
|
||||
|
||||
Q_SIGNALS:
|
||||
void instanceStarted();
|
||||
void receivedMessage( quint32 instanceId, QByteArray message );
|
||||
|
||||
private:
|
||||
SingleApplicationPrivate *d_ptr;
|
||||
Q_DECLARE_PRIVATE(SingleApplication)
|
||||
void abortSafely();
|
||||
};
|
||||
|
||||
Q_DECLARE_OPERATORS_FOR_FLAGS(SingleApplication::Options)
|
||||
|
||||
#endif // SINGLE_APPLICATION_H
|
||||
|
|
@ -1,15 +0,0 @@
|
|||
QT += core network
|
||||
CONFIG += c++11
|
||||
|
||||
HEADERS += \
|
||||
$$PWD/singleapplication.h \
|
||||
$$PWD/singleapplication_p.h
|
||||
SOURCES += $$PWD/singleapplication.cpp \
|
||||
$$PWD/singleapplication_p.cpp
|
||||
|
||||
INCLUDEPATH += $$PWD
|
||||
|
||||
win32 {
|
||||
msvc:LIBS += Advapi32.lib
|
||||
gcc:LIBS += -ladvapi32
|
||||
}
|
||||
|
|
@ -1,486 +0,0 @@
|
|||
// The MIT License (MIT)
|
||||
//
|
||||
// Copyright (c) Itay Grudev 2015 - 2020
|
||||
//
|
||||
// Permission is hereby granted, free of charge, to any person obtaining a copy
|
||||
// of this software and associated documentation files (the "Software"), to deal
|
||||
// in the Software without restriction, including without limitation the rights
|
||||
// to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
|
||||
// copies of the Software, and to permit persons to whom the Software is
|
||||
// furnished to do so, subject to the following conditions:
|
||||
//
|
||||
// The above copyright notice and this permission notice shall be included in
|
||||
// all copies or substantial portions of the Software.
|
||||
//
|
||||
// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
||||
// IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
||||
// FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
|
||||
// AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
||||
// LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
|
||||
// OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
|
||||
// THE SOFTWARE.
|
||||
|
||||
//
|
||||
// W A R N I N G !!!
|
||||
// -----------------
|
||||
//
|
||||
// This file is not part of the SingleApplication API. It is used purely as an
|
||||
// implementation detail. This header file may change from version to
|
||||
// version without notice, or may even be removed.
|
||||
//
|
||||
|
||||
#include <cstdlib>
|
||||
#include <cstddef>
|
||||
|
||||
#include <QtCore/QDir>
|
||||
#include <QtCore/QThread>
|
||||
#include <QtCore/QByteArray>
|
||||
#include <QtCore/QDataStream>
|
||||
#include <QtCore/QElapsedTimer>
|
||||
#include <QtCore/QCryptographicHash>
|
||||
#include <QtNetwork/QLocalServer>
|
||||
#include <QtNetwork/QLocalSocket>
|
||||
|
||||
#if QT_VERSION >= QT_VERSION_CHECK(5, 10, 0)
|
||||
#include <QtCore/QRandomGenerator>
|
||||
#else
|
||||
#include <QtCore/QDateTime>
|
||||
#endif
|
||||
|
||||
#include "singleapplication.h"
|
||||
#include "singleapplication_p.h"
|
||||
|
||||
#ifdef Q_OS_UNIX
|
||||
#include <unistd.h>
|
||||
#include <sys/types.h>
|
||||
#include <pwd.h>
|
||||
#endif
|
||||
|
||||
#ifdef Q_OS_WIN
|
||||
#ifndef NOMINMAX
|
||||
#define NOMINMAX 1
|
||||
#endif
|
||||
#include <windows.h>
|
||||
#include <lmcons.h>
|
||||
#endif
|
||||
|
||||
SingleApplicationPrivate::SingleApplicationPrivate( SingleApplication *q_ptr )
|
||||
: q_ptr( q_ptr )
|
||||
{
|
||||
server = nullptr;
|
||||
socket = nullptr;
|
||||
memory = nullptr;
|
||||
instanceNumber = 0;
|
||||
}
|
||||
|
||||
SingleApplicationPrivate::~SingleApplicationPrivate()
|
||||
{
|
||||
if( socket != nullptr ){
|
||||
socket->close();
|
||||
delete socket;
|
||||
}
|
||||
|
||||
if( memory != nullptr ){
|
||||
memory->lock();
|
||||
auto *inst = static_cast<InstancesInfo*>(memory->data());
|
||||
if( server != nullptr ){
|
||||
server->close();
|
||||
delete server;
|
||||
inst->primary = false;
|
||||
inst->primaryPid = -1;
|
||||
inst->primaryUser[0] = '\0';
|
||||
inst->checksum = blockChecksum();
|
||||
}
|
||||
memory->unlock();
|
||||
|
||||
delete memory;
|
||||
}
|
||||
}
|
||||
|
||||
QString SingleApplicationPrivate::getUsername()
|
||||
{
|
||||
#ifdef Q_OS_WIN
|
||||
wchar_t username[UNLEN + 1];
|
||||
// Specifies size of the buffer on input
|
||||
DWORD usernameLength = UNLEN + 1;
|
||||
if( GetUserNameW( username, &usernameLength ) )
|
||||
return QString::fromWCharArray( username );
|
||||
#if QT_VERSION < QT_VERSION_CHECK(5, 10, 0)
|
||||
return QString::fromLocal8Bit( qgetenv( "USERNAME" ) );
|
||||
#else
|
||||
return qEnvironmentVariable( "USERNAME" );
|
||||
#endif
|
||||
#endif
|
||||
#ifdef Q_OS_UNIX
|
||||
QString username;
|
||||
uid_t uid = geteuid();
|
||||
struct passwd *pw = getpwuid( uid );
|
||||
if( pw )
|
||||
username = QString::fromLocal8Bit( pw->pw_name );
|
||||
if ( username.isEmpty() ){
|
||||
#if QT_VERSION < QT_VERSION_CHECK(5, 10, 0)
|
||||
username = QString::fromLocal8Bit( qgetenv( "USER" ) );
|
||||
#else
|
||||
username = qEnvironmentVariable( "USER" );
|
||||
#endif
|
||||
}
|
||||
return username;
|
||||
#endif
|
||||
}
|
||||
|
||||
void SingleApplicationPrivate::genBlockServerName()
|
||||
{
|
||||
QCryptographicHash appData( QCryptographicHash::Sha256 );
|
||||
appData.addData( "SingleApplication", 17 );
|
||||
appData.addData( SingleApplication::app_t::applicationName().toUtf8() );
|
||||
appData.addData( SingleApplication::app_t::organizationName().toUtf8() );
|
||||
appData.addData( SingleApplication::app_t::organizationDomain().toUtf8() );
|
||||
|
||||
if ( ! appDataList.isEmpty() )
|
||||
appData.addData( appDataList.join( "" ).toUtf8() );
|
||||
|
||||
if( ! (options & SingleApplication::Mode::ExcludeAppVersion) ){
|
||||
appData.addData( SingleApplication::app_t::applicationVersion().toUtf8() );
|
||||
}
|
||||
|
||||
if( ! (options & SingleApplication::Mode::ExcludeAppPath) ){
|
||||
#ifdef Q_OS_WIN
|
||||
appData.addData( SingleApplication::app_t::applicationFilePath().toLower().toUtf8() );
|
||||
#else
|
||||
appData.addData( SingleApplication::app_t::applicationFilePath().toUtf8() );
|
||||
#endif
|
||||
}
|
||||
|
||||
// User level block requires a user specific data in the hash
|
||||
if( options & SingleApplication::Mode::User ){
|
||||
appData.addData( getUsername().toUtf8() );
|
||||
}
|
||||
|
||||
// Replace the backslash in RFC 2045 Base64 [a-zA-Z0-9+/=] to comply with
|
||||
// server naming requirements.
|
||||
blockServerName = appData.result().toBase64().replace("/", "_");
|
||||
}
|
||||
|
||||
void SingleApplicationPrivate::initializeMemoryBlock() const
|
||||
{
|
||||
auto *inst = static_cast<InstancesInfo*>( memory->data() );
|
||||
inst->primary = false;
|
||||
inst->secondary = 0;
|
||||
inst->primaryPid = -1;
|
||||
inst->primaryUser[0] = '\0';
|
||||
inst->checksum = blockChecksum();
|
||||
}
|
||||
|
||||
void SingleApplicationPrivate::startPrimary()
|
||||
{
|
||||
// Reset the number of connections
|
||||
auto *inst = static_cast <InstancesInfo*>( memory->data() );
|
||||
|
||||
inst->primary = true;
|
||||
inst->primaryPid = QCoreApplication::applicationPid();
|
||||
qstrncpy( inst->primaryUser, getUsername().toUtf8().data(), sizeof(inst->primaryUser) );
|
||||
inst->checksum = blockChecksum();
|
||||
instanceNumber = 0;
|
||||
// Successful creation means that no main process exists
|
||||
// So we start a QLocalServer to listen for connections
|
||||
QLocalServer::removeServer( blockServerName );
|
||||
server = new QLocalServer();
|
||||
|
||||
// Restrict access to the socket according to the
|
||||
// SingleApplication::Mode::User flag on User level or no restrictions
|
||||
if( options & SingleApplication::Mode::User ){
|
||||
server->setSocketOptions( QLocalServer::UserAccessOption );
|
||||
} else {
|
||||
server->setSocketOptions( QLocalServer::WorldAccessOption );
|
||||
}
|
||||
|
||||
server->listen( blockServerName );
|
||||
QObject::connect(
|
||||
server,
|
||||
&QLocalServer::newConnection,
|
||||
this,
|
||||
&SingleApplicationPrivate::slotConnectionEstablished
|
||||
);
|
||||
}
|
||||
|
||||
void SingleApplicationPrivate::startSecondary()
|
||||
{
|
||||
auto *inst = static_cast <InstancesInfo*>( memory->data() );
|
||||
|
||||
inst->secondary += 1;
|
||||
inst->checksum = blockChecksum();
|
||||
instanceNumber = inst->secondary;
|
||||
}
|
||||
|
||||
bool SingleApplicationPrivate::connectToPrimary( int msecs, ConnectionType connectionType )
|
||||
{
|
||||
QElapsedTimer time;
|
||||
time.start();
|
||||
|
||||
// Connect to the Local Server of the Primary Instance if not already
|
||||
// connected.
|
||||
if( socket == nullptr ){
|
||||
socket = new QLocalSocket();
|
||||
}
|
||||
|
||||
if( socket->state() == QLocalSocket::ConnectedState ) return true;
|
||||
|
||||
if( socket->state() != QLocalSocket::ConnectedState ){
|
||||
|
||||
while( true ){
|
||||
randomSleep();
|
||||
|
||||
if( socket->state() != QLocalSocket::ConnectingState )
|
||||
socket->connectToServer( blockServerName );
|
||||
|
||||
if( socket->state() == QLocalSocket::ConnectingState ){
|
||||
socket->waitForConnected( static_cast<int>(msecs - time.elapsed()) );
|
||||
}
|
||||
|
||||
// If connected break out of the loop
|
||||
if( socket->state() == QLocalSocket::ConnectedState ) break;
|
||||
|
||||
// If elapsed time since start is longer than the method timeout return
|
||||
if( time.elapsed() >= msecs ) return false;
|
||||
}
|
||||
}
|
||||
|
||||
// Initialisation message according to the SingleApplication protocol
|
||||
QByteArray initMsg;
|
||||
QDataStream writeStream(&initMsg, QIODevice::WriteOnly);
|
||||
|
||||
#if (QT_VERSION >= QT_VERSION_CHECK(5, 6, 0))
|
||||
writeStream.setVersion(QDataStream::Qt_5_6);
|
||||
#endif
|
||||
|
||||
writeStream << blockServerName.toLatin1();
|
||||
writeStream << static_cast<quint8>(connectionType);
|
||||
writeStream << instanceNumber;
|
||||
#if QT_VERSION >= QT_VERSION_CHECK(6, 0, 0)
|
||||
quint16 checksum = qChecksum(QByteArray(initMsg, static_cast<quint32>(initMsg.length())));
|
||||
#else
|
||||
quint16 checksum = qChecksum(initMsg.constData(), static_cast<quint32>(initMsg.length()));
|
||||
#endif
|
||||
writeStream << checksum;
|
||||
|
||||
// The header indicates the message length that follows
|
||||
QByteArray header;
|
||||
QDataStream headerStream(&header, QIODevice::WriteOnly);
|
||||
|
||||
#if (QT_VERSION >= QT_VERSION_CHECK(5, 6, 0))
|
||||
headerStream.setVersion(QDataStream::Qt_5_6);
|
||||
#endif
|
||||
headerStream << static_cast <quint64>( initMsg.length() );
|
||||
|
||||
socket->write( header );
|
||||
socket->write( initMsg );
|
||||
bool result = socket->waitForBytesWritten( static_cast<int>(msecs - time.elapsed()) );
|
||||
socket->flush();
|
||||
return result;
|
||||
}
|
||||
|
||||
quint16 SingleApplicationPrivate::blockChecksum() const
|
||||
{
|
||||
#if QT_VERSION >= QT_VERSION_CHECK(6, 0, 0)
|
||||
quint16 checksum = qChecksum(QByteArray(static_cast<const char*>(memory->constData()), offsetof(InstancesInfo, checksum)));
|
||||
#else
|
||||
quint16 checksum = qChecksum(static_cast<const char*>(memory->constData()), offsetof(InstancesInfo, checksum));
|
||||
#endif
|
||||
return checksum;
|
||||
}
|
||||
|
||||
qint64 SingleApplicationPrivate::primaryPid() const
|
||||
{
|
||||
qint64 pid;
|
||||
|
||||
memory->lock();
|
||||
auto *inst = static_cast<InstancesInfo*>( memory->data() );
|
||||
pid = inst->primaryPid;
|
||||
memory->unlock();
|
||||
|
||||
return pid;
|
||||
}
|
||||
|
||||
QString SingleApplicationPrivate::primaryUser() const
|
||||
{
|
||||
QByteArray username;
|
||||
|
||||
memory->lock();
|
||||
auto *inst = static_cast<InstancesInfo*>( memory->data() );
|
||||
username = inst->primaryUser;
|
||||
memory->unlock();
|
||||
|
||||
return QString::fromUtf8( username );
|
||||
}
|
||||
|
||||
/**
|
||||
* @brief Executed when a connection has been made to the LocalServer
|
||||
*/
|
||||
void SingleApplicationPrivate::slotConnectionEstablished()
|
||||
{
|
||||
QLocalSocket *nextConnSocket = server->nextPendingConnection();
|
||||
connectionMap.insert(nextConnSocket, ConnectionInfo());
|
||||
|
||||
QObject::connect(nextConnSocket, &QLocalSocket::aboutToClose,
|
||||
[nextConnSocket, this](){
|
||||
auto &info = connectionMap[nextConnSocket];
|
||||
Q_EMIT this->slotClientConnectionClosed( nextConnSocket, info.instanceId );
|
||||
}
|
||||
);
|
||||
|
||||
QObject::connect(nextConnSocket, &QLocalSocket::disconnected, nextConnSocket, &QLocalSocket::deleteLater);
|
||||
|
||||
QObject::connect(nextConnSocket, &QLocalSocket::destroyed,
|
||||
[nextConnSocket, this](){
|
||||
connectionMap.remove(nextConnSocket);
|
||||
}
|
||||
);
|
||||
|
||||
QObject::connect(nextConnSocket, &QLocalSocket::readyRead,
|
||||
[nextConnSocket, this](){
|
||||
auto &info = connectionMap[nextConnSocket];
|
||||
switch(info.stage){
|
||||
case StageHeader:
|
||||
readInitMessageHeader(nextConnSocket);
|
||||
break;
|
||||
case StageBody:
|
||||
readInitMessageBody(nextConnSocket);
|
||||
break;
|
||||
case StageConnected:
|
||||
Q_EMIT this->slotDataAvailable( nextConnSocket, info.instanceId );
|
||||
break;
|
||||
default:
|
||||
break;
|
||||
};
|
||||
}
|
||||
);
|
||||
}
|
||||
|
||||
void SingleApplicationPrivate::readInitMessageHeader( QLocalSocket *sock )
|
||||
{
|
||||
if (!connectionMap.contains( sock )){
|
||||
return;
|
||||
}
|
||||
|
||||
if( sock->bytesAvailable() < ( qint64 )sizeof( quint64 ) ){
|
||||
return;
|
||||
}
|
||||
|
||||
QDataStream headerStream( sock );
|
||||
|
||||
#if (QT_VERSION >= QT_VERSION_CHECK(5, 6, 0))
|
||||
headerStream.setVersion( QDataStream::Qt_5_6 );
|
||||
#endif
|
||||
|
||||
// Read the header to know the message length
|
||||
quint64 msgLen = 0;
|
||||
headerStream >> msgLen;
|
||||
ConnectionInfo &info = connectionMap[sock];
|
||||
info.stage = StageBody;
|
||||
info.msgLen = msgLen;
|
||||
|
||||
if ( sock->bytesAvailable() >= (qint64) msgLen ){
|
||||
readInitMessageBody( sock );
|
||||
}
|
||||
}
|
||||
|
||||
void SingleApplicationPrivate::readInitMessageBody( QLocalSocket *sock )
|
||||
{
|
||||
Q_Q(SingleApplication);
|
||||
|
||||
if (!connectionMap.contains( sock )){
|
||||
return;
|
||||
}
|
||||
|
||||
ConnectionInfo &info = connectionMap[sock];
|
||||
if( sock->bytesAvailable() < ( qint64 )info.msgLen ){
|
||||
return;
|
||||
}
|
||||
|
||||
// Read the message body
|
||||
QByteArray msgBytes = sock->read(info.msgLen);
|
||||
QDataStream readStream(msgBytes);
|
||||
|
||||
#if (QT_VERSION >= QT_VERSION_CHECK(5, 6, 0))
|
||||
readStream.setVersion( QDataStream::Qt_5_6 );
|
||||
#endif
|
||||
|
||||
// server name
|
||||
QByteArray latin1Name;
|
||||
readStream >> latin1Name;
|
||||
|
||||
// connection type
|
||||
ConnectionType connectionType = InvalidConnection;
|
||||
quint8 connTypeVal = InvalidConnection;
|
||||
readStream >> connTypeVal;
|
||||
connectionType = static_cast <ConnectionType>( connTypeVal );
|
||||
|
||||
// instance id
|
||||
quint32 instanceId = 0;
|
||||
readStream >> instanceId;
|
||||
|
||||
// checksum
|
||||
quint16 msgChecksum = 0;
|
||||
readStream >> msgChecksum;
|
||||
|
||||
#if QT_VERSION >= QT_VERSION_CHECK(6, 0, 0)
|
||||
const quint16 actualChecksum = qChecksum(QByteArray(msgBytes, static_cast<quint32>(msgBytes.length() - sizeof(quint16))));
|
||||
#else
|
||||
const quint16 actualChecksum = qChecksum(msgBytes.constData(), static_cast<quint32>(msgBytes.length() - sizeof(quint16)));
|
||||
#endif
|
||||
|
||||
bool isValid = readStream.status() == QDataStream::Ok &&
|
||||
QLatin1String(latin1Name) == blockServerName &&
|
||||
msgChecksum == actualChecksum;
|
||||
|
||||
if( !isValid ){
|
||||
sock->close();
|
||||
return;
|
||||
}
|
||||
|
||||
info.instanceId = instanceId;
|
||||
info.stage = StageConnected;
|
||||
|
||||
if( connectionType == NewInstance ||
|
||||
( connectionType == SecondaryInstance &&
|
||||
options & SingleApplication::Mode::SecondaryNotification ) )
|
||||
{
|
||||
Q_EMIT q->instanceStarted();
|
||||
}
|
||||
|
||||
if (sock->bytesAvailable() > 0){
|
||||
Q_EMIT this->slotDataAvailable( sock, instanceId );
|
||||
}
|
||||
}
|
||||
|
||||
void SingleApplicationPrivate::slotDataAvailable( QLocalSocket *dataSocket, quint32 instanceId )
|
||||
{
|
||||
Q_Q(SingleApplication);
|
||||
Q_EMIT q->receivedMessage( instanceId, dataSocket->readAll() );
|
||||
}
|
||||
|
||||
void SingleApplicationPrivate::slotClientConnectionClosed( QLocalSocket *closedSocket, quint32 instanceId )
|
||||
{
|
||||
if( closedSocket->bytesAvailable() > 0 )
|
||||
Q_EMIT slotDataAvailable( closedSocket, instanceId );
|
||||
}
|
||||
|
||||
void SingleApplicationPrivate::randomSleep()
|
||||
{
|
||||
#if QT_VERSION >= QT_VERSION_CHECK( 5, 10, 0 )
|
||||
QThread::msleep( QRandomGenerator::global()->bounded( 8u, 18u ));
|
||||
#else
|
||||
qsrand( QDateTime::currentMSecsSinceEpoch() % std::numeric_limits<uint>::max() );
|
||||
QThread::msleep( 8 + static_cast <unsigned long>( static_cast <float>( qrand() ) / RAND_MAX * 10 ));
|
||||
#endif
|
||||
}
|
||||
|
||||
void SingleApplicationPrivate::addAppData(const QString &data)
|
||||
{
|
||||
appDataList.push_back(data);
|
||||
}
|
||||
|
||||
QStringList SingleApplicationPrivate::appData() const
|
||||
{
|
||||
return appDataList;
|
||||
}
|
||||
|
|
@ -1,104 +0,0 @@
|
|||
// The MIT License (MIT)
|
||||
//
|
||||
// Copyright (c) Itay Grudev 2015 - 2020
|
||||
//
|
||||
// Permission is hereby granted, free of charge, to any person obtaining a copy
|
||||
// of this software and associated documentation files (the "Software"), to deal
|
||||
// in the Software without restriction, including without limitation the rights
|
||||
// to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
|
||||
// copies of the Software, and to permit persons to whom the Software is
|
||||
// furnished to do so, subject to the following conditions:
|
||||
//
|
||||
// The above copyright notice and this permission notice shall be included in
|
||||
// all copies or substantial portions of the Software.
|
||||
//
|
||||
// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
||||
// IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
||||
// FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
|
||||
// AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
||||
// LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
|
||||
// OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
|
||||
// THE SOFTWARE.
|
||||
|
||||
//
|
||||
// W A R N I N G !!!
|
||||
// -----------------
|
||||
//
|
||||
// This file is not part of the SingleApplication API. It is used purely as an
|
||||
// implementation detail. This header file may change from version to
|
||||
// version without notice, or may even be removed.
|
||||
//
|
||||
|
||||
#ifndef SINGLEAPPLICATION_P_H
|
||||
#define SINGLEAPPLICATION_P_H
|
||||
|
||||
#include <QtCore/QSharedMemory>
|
||||
#include <QtNetwork/QLocalServer>
|
||||
#include <QtNetwork/QLocalSocket>
|
||||
#include "singleapplication.h"
|
||||
|
||||
struct InstancesInfo {
|
||||
bool primary;
|
||||
quint32 secondary;
|
||||
qint64 primaryPid;
|
||||
char primaryUser[128];
|
||||
quint16 checksum; // Must be the last field
|
||||
};
|
||||
|
||||
struct ConnectionInfo {
|
||||
qint64 msgLen = 0;
|
||||
quint32 instanceId = 0;
|
||||
quint8 stage = 0;
|
||||
};
|
||||
|
||||
class SingleApplicationPrivate : public QObject {
|
||||
Q_OBJECT
|
||||
public:
|
||||
enum ConnectionType : quint8 {
|
||||
InvalidConnection = 0,
|
||||
NewInstance = 1,
|
||||
SecondaryInstance = 2,
|
||||
Reconnect = 3
|
||||
};
|
||||
enum ConnectionStage : quint8 {
|
||||
StageHeader = 0,
|
||||
StageBody = 1,
|
||||
StageConnected = 2,
|
||||
};
|
||||
Q_DECLARE_PUBLIC(SingleApplication)
|
||||
|
||||
SingleApplicationPrivate( SingleApplication *q_ptr );
|
||||
~SingleApplicationPrivate() override;
|
||||
|
||||
static QString getUsername();
|
||||
void genBlockServerName();
|
||||
void initializeMemoryBlock() const;
|
||||
void startPrimary();
|
||||
void startSecondary();
|
||||
bool connectToPrimary( int msecs, ConnectionType connectionType );
|
||||
quint16 blockChecksum() const;
|
||||
qint64 primaryPid() const;
|
||||
QString primaryUser() const;
|
||||
void readInitMessageHeader(QLocalSocket *socket);
|
||||
void readInitMessageBody(QLocalSocket *socket);
|
||||
static void randomSleep();
|
||||
void addAppData(const QString &data);
|
||||
QStringList appData() const;
|
||||
|
||||
SingleApplication *q_ptr;
|
||||
QSharedMemory *memory;
|
||||
QLocalSocket *socket;
|
||||
QLocalServer *server;
|
||||
quint32 instanceNumber;
|
||||
QString blockServerName;
|
||||
SingleApplication::Options options;
|
||||
QMap<QLocalSocket*, ConnectionInfo> connectionMap;
|
||||
QStringList appDataList;
|
||||
|
||||
public Q_SLOTS:
|
||||
void slotConnectionEstablished();
|
||||
void slotDataAvailable( QLocalSocket*, quint32 );
|
||||
void slotClientConnectionClosed( QLocalSocket*, quint32 );
|
||||
};
|
||||
|
||||
#endif // SINGLEAPPLICATION_P_H
|
||||
2
client/3rd/amneziawg-apple
vendored
|
|
@ -1 +1 @@
|
|||
Subproject commit 0829e99ea9f4508fd1d4742546b62145d17587bb
|
||||
Subproject commit 76e7db556a6d7e2582f9481df91db188a46c009c
|
||||
2
client/3rd/qtkeychain
vendored
|
|
@ -1 +1 @@
|
|||
Subproject commit 74776e2a3e2d98d19943e0968901c5b5e04cc1bd
|
||||
Subproject commit 7460df6a978669290de5b56c2d98b199b61c3f88
|
||||
|
|
@ -24,6 +24,13 @@ execute_process(
|
|||
|
||||
add_definitions(-DGIT_COMMIT_HASH="${GIT_COMMIT_HASH}")
|
||||
|
||||
add_definitions(-DPROD_AGW_PUBLIC_KEY="$ENV{PROD_AGW_PUBLIC_KEY}")
|
||||
add_definitions(-DPROD_S3_ENDPOINT="$ENV{PROD_S3_ENDPOINT}")
|
||||
|
||||
add_definitions(-DDEV_AGW_PUBLIC_KEY="$ENV{DEV_AGW_PUBLIC_KEY}")
|
||||
add_definitions(-DDEV_AGW_ENDPOINT="$ENV{DEV_AGW_ENDPOINT}")
|
||||
add_definitions(-DDEV_S3_ENDPOINT="$ENV{DEV_S3_ENDPOINT}")
|
||||
|
||||
if(IOS)
|
||||
set(PACKAGES ${PACKAGES} Multimedia)
|
||||
endif()
|
||||
|
|
@ -34,7 +41,7 @@ endif()
|
|||
|
||||
find_package(Qt6 REQUIRED COMPONENTS ${PACKAGES})
|
||||
|
||||
set(LIBS ${LIBS}
|
||||
set(LIBS ${LIBS}
|
||||
Qt6::Core Qt6::Gui
|
||||
Qt6::Network Qt6::Xml Qt6::RemoteObjects
|
||||
Qt6::Quick Qt6::Svg Qt6::QuickControls2
|
||||
|
|
@ -55,6 +62,7 @@ qt_add_executable(${PROJECT} MANUAL_FINALIZATION)
|
|||
if(WIN32 OR (APPLE AND NOT IOS) OR (LINUX AND NOT ANDROID))
|
||||
qt_add_repc_replicas(${PROJECT} ${CMAKE_CURRENT_LIST_DIR}/../ipc/ipc_interface.rep)
|
||||
qt_add_repc_replicas(${PROJECT} ${CMAKE_CURRENT_LIST_DIR}/../ipc/ipc_process_interface.rep)
|
||||
qt_add_repc_replicas(${PROJECT} ${CMAKE_CURRENT_LIST_DIR}/../ipc/ipc_process_tun2socks.rep)
|
||||
endif()
|
||||
|
||||
qt6_add_resources(QRC ${QRC} ${CMAKE_CURRENT_LIST_DIR}/resources.qrc)
|
||||
|
|
@ -107,6 +115,7 @@ include(${CMAKE_CURRENT_LIST_DIR}/cmake/3rdparty.cmake)
|
|||
|
||||
include_directories(
|
||||
${CMAKE_CURRENT_LIST_DIR}/../ipc
|
||||
${CMAKE_CURRENT_LIST_DIR}/../common/logger
|
||||
${CMAKE_CURRENT_LIST_DIR}
|
||||
${CMAKE_CURRENT_BINARY_DIR}
|
||||
)
|
||||
|
|
@ -128,12 +137,15 @@ set(HEADERS ${HEADERS}
|
|||
${CMAKE_CURRENT_LIST_DIR}/protocols/protocols_defs.h
|
||||
${CMAKE_CURRENT_LIST_DIR}/protocols/qml_register_protocols.h
|
||||
${CMAKE_CURRENT_LIST_DIR}/ui/pages.h
|
||||
${CMAKE_CURRENT_LIST_DIR}/ui/property_helper.h
|
||||
${CMAKE_CURRENT_LIST_DIR}/ui/qautostart.h
|
||||
${CMAKE_CURRENT_LIST_DIR}/protocols/vpnprotocol.h
|
||||
${CMAKE_CURRENT_BINARY_DIR}/version.h
|
||||
${CMAKE_CURRENT_LIST_DIR}/core/sshclient.h
|
||||
${CMAKE_CURRENT_LIST_DIR}/core/networkUtilities.h
|
||||
${CMAKE_CURRENT_LIST_DIR}/core/serialization/serialization.h
|
||||
${CMAKE_CURRENT_LIST_DIR}/core/serialization/transfer.h
|
||||
${CMAKE_CURRENT_LIST_DIR}/core/enums/apiEnums.h
|
||||
${CMAKE_CURRENT_LIST_DIR}/../common/logger/logger.h
|
||||
)
|
||||
|
||||
# Mozilla headres
|
||||
|
|
@ -176,6 +188,15 @@ set(SOURCES ${SOURCES}
|
|||
${CMAKE_CURRENT_LIST_DIR}/protocols/vpnprotocol.cpp
|
||||
${CMAKE_CURRENT_LIST_DIR}/core/sshclient.cpp
|
||||
${CMAKE_CURRENT_LIST_DIR}/core/networkUtilities.cpp
|
||||
${CMAKE_CURRENT_LIST_DIR}/core/serialization/outbound.cpp
|
||||
${CMAKE_CURRENT_LIST_DIR}/core/serialization/inbound.cpp
|
||||
${CMAKE_CURRENT_LIST_DIR}/core/serialization/ss.cpp
|
||||
${CMAKE_CURRENT_LIST_DIR}/core/serialization/ssd.cpp
|
||||
${CMAKE_CURRENT_LIST_DIR}/core/serialization/vless.cpp
|
||||
${CMAKE_CURRENT_LIST_DIR}/core/serialization/trojan.cpp
|
||||
${CMAKE_CURRENT_LIST_DIR}/core/serialization/vmess.cpp
|
||||
${CMAKE_CURRENT_LIST_DIR}/core/serialization/vmess_new.cpp
|
||||
${CMAKE_CURRENT_LIST_DIR}/../common/logger/logger.cpp
|
||||
)
|
||||
|
||||
# Mozilla sources
|
||||
|
|
@ -242,7 +263,7 @@ set(SOURCES ${SOURCES}
|
|||
|
||||
if(WIN32)
|
||||
configure_file(
|
||||
${CMAKE_CURRENT_LIST_DIR}/platforms/windows/amneziavpn.rc.in
|
||||
${CMAKE_CURRENT_LIST_DIR}/platforms/windows/amneziavpn.rc.in
|
||||
${CMAKE_CURRENT_BINARY_DIR}/amneziavpn.rc
|
||||
)
|
||||
|
||||
|
|
|
|||
|
|
@ -3,13 +3,15 @@
|
|||
#include <QClipboard>
|
||||
#include <QFontDatabase>
|
||||
#include <QMimeData>
|
||||
#include <QQuickItem>
|
||||
#include <QQuickStyle>
|
||||
#include <QResource>
|
||||
#include <QStandardPaths>
|
||||
#include <QTextDocument>
|
||||
#include <QTimer>
|
||||
#include <QTranslator>
|
||||
#include <QQuickItem>
|
||||
#include <QLocalSocket>
|
||||
#include <QLocalServer>
|
||||
|
||||
#include "logger.h"
|
||||
#include "ui/models/installedAppsModel.h"
|
||||
|
|
@ -28,13 +30,7 @@
|
|||
#include <AmneziaVPN-Swift.h>
|
||||
#endif
|
||||
|
||||
#if defined(Q_OS_ANDROID) || defined(Q_OS_IOS)
|
||||
AmneziaApplication::AmneziaApplication(int &argc, char *argv[]) : AMNEZIA_BASE_CLASS(argc, argv)
|
||||
#else
|
||||
AmneziaApplication::AmneziaApplication(int &argc, char *argv[], bool allowSecondary, SingleApplication::Options options, int timeout,
|
||||
const QString &userData)
|
||||
: SingleApplication(argc, argv, allowSecondary, options, timeout, userData)
|
||||
#endif
|
||||
{
|
||||
setQuitOnLastWindowClosed(false);
|
||||
|
||||
|
|
@ -115,10 +111,11 @@ void AmneziaApplication::init()
|
|||
qFatal("Android controller initialization failed");
|
||||
}
|
||||
|
||||
connect(AndroidController::instance(), &AndroidController::importConfigFromOutside, [this](QString data) {
|
||||
m_pageController->replaceStartPage();
|
||||
connect(AndroidController::instance(), &AndroidController::importConfigFromOutside, this, [this](QString data) {
|
||||
emit m_pageController->goToPageHome();
|
||||
m_importController->extractConfigFromData(data);
|
||||
m_pageController->goToPageViewConfig();
|
||||
data.clear();
|
||||
emit m_pageController->goToPageViewConfig();
|
||||
});
|
||||
|
||||
m_engine->addImageProvider(QLatin1String("installedAppImage"), new InstalledAppsImageProvider);
|
||||
|
|
@ -126,16 +123,16 @@ void AmneziaApplication::init()
|
|||
|
||||
#ifdef Q_OS_IOS
|
||||
IosController::Instance()->initialize();
|
||||
connect(IosController::Instance(), &IosController::importConfigFromOutside, [this](QString data) {
|
||||
m_pageController->replaceStartPage();
|
||||
connect(IosController::Instance(), &IosController::importConfigFromOutside, this, [this](QString data) {
|
||||
emit m_pageController->goToPageHome();
|
||||
m_importController->extractConfigFromData(data);
|
||||
m_pageController->goToPageViewConfig();
|
||||
emit m_pageController->goToPageViewConfig();
|
||||
});
|
||||
|
||||
connect(IosController::Instance(), &IosController::importBackupFromOutside, [this](QString filePath) {
|
||||
m_pageController->replaceStartPage();
|
||||
connect(IosController::Instance(), &IosController::importBackupFromOutside, this, [this](QString filePath) {
|
||||
emit m_pageController->goToPageHome();
|
||||
m_pageController->goToPageSettingsBackup();
|
||||
m_settingsController->importBackupFromOutside(filePath);
|
||||
emit m_settingsController->importBackupFromOutside(filePath);
|
||||
});
|
||||
|
||||
QTimer::singleShot(0, this, [this]() { AmneziaVPN::toggleScreenshots(m_settings->isScreenshotsEnabled()); });
|
||||
|
|
@ -157,16 +154,19 @@ void AmneziaApplication::init()
|
|||
connect(this, &AmneziaApplication::translationsUpdated, m_notificationHandler.get(), &NotificationHandler::onTranslationsUpdated);
|
||||
#endif
|
||||
|
||||
m_engine->addImportPath("qrc:/ui/qml/Modules/");
|
||||
m_engine->load(url);
|
||||
m_systemController->setQmlRoot(m_engine->rootObjects().value(0));
|
||||
|
||||
bool enabled = m_settings->isSaveLogs();
|
||||
#ifndef Q_OS_ANDROID
|
||||
if (m_settings->isSaveLogs()) {
|
||||
if (!Logger::init()) {
|
||||
if (enabled) {
|
||||
if (!Logger::init(false)) {
|
||||
qWarning() << "Initialization of debug subsystem failed";
|
||||
}
|
||||
}
|
||||
#endif
|
||||
Logger::setServiceLogsEnabled(enabled);
|
||||
|
||||
#ifdef Q_OS_WIN
|
||||
if (m_parser.isSet("a"))
|
||||
|
|
@ -177,16 +177,6 @@ void AmneziaApplication::init()
|
|||
m_pageController->showOnStartup();
|
||||
#endif
|
||||
|
||||
// TODO - fix
|
||||
#if !defined(Q_OS_ANDROID) && !defined(Q_OS_IOS)
|
||||
if (isPrimary()) {
|
||||
QObject::connect(this, &SingleApplication::instanceStarted, m_pageController.get(), [this]() {
|
||||
qDebug() << "Secondary instance started, showing this window instead";
|
||||
emit m_pageController->raiseMainWindow();
|
||||
});
|
||||
}
|
||||
#endif
|
||||
|
||||
// Android TextArea clipboard workaround
|
||||
// Text from TextArea always has "text/html" mime-type:
|
||||
// /qt/6.6.1/Src/qtdeclarative/src/quick/items/qquicktextcontrol.cpp:1865
|
||||
|
|
@ -291,6 +281,24 @@ bool AmneziaApplication::parseCommands()
|
|||
return true;
|
||||
}
|
||||
|
||||
#if !defined(Q_OS_ANDROID) && !defined(Q_OS_IOS)
|
||||
void AmneziaApplication::startLocalServer() {
|
||||
const QString serverName("AmneziaVPNInstance");
|
||||
QLocalServer::removeServer(serverName);
|
||||
|
||||
QLocalServer* server = new QLocalServer(this);
|
||||
server->listen(serverName);
|
||||
|
||||
QObject::connect(server, &QLocalServer::newConnection, this, [server, this]() {
|
||||
if (server) {
|
||||
QLocalSocket* clientConnection = server->nextPendingConnection();
|
||||
clientConnection->deleteLater();
|
||||
}
|
||||
emit m_pageController->raiseMainWindow();
|
||||
});
|
||||
}
|
||||
#endif
|
||||
|
||||
QQmlApplicationEngine *AmneziaApplication::qmlEngine() const
|
||||
{
|
||||
return m_engine;
|
||||
|
|
@ -351,10 +359,25 @@ void AmneziaApplication::initModels()
|
|||
m_sftpConfigModel.reset(new SftpConfigModel(this));
|
||||
m_engine->rootContext()->setContextProperty("SftpConfigModel", m_sftpConfigModel.get());
|
||||
|
||||
m_socks5ConfigModel.reset(new Socks5ProxyConfigModel(this));
|
||||
m_engine->rootContext()->setContextProperty("Socks5ProxyConfigModel", m_socks5ConfigModel.get());
|
||||
|
||||
m_clientManagementModel.reset(new ClientManagementModel(m_settings, this));
|
||||
m_engine->rootContext()->setContextProperty("ClientManagementModel", m_clientManagementModel.get());
|
||||
connect(m_clientManagementModel.get(), &ClientManagementModel::adminConfigRevoked, m_serversModel.get(),
|
||||
&ServersModel::clearCachedProfile);
|
||||
|
||||
m_apiServicesModel.reset(new ApiServicesModel(this));
|
||||
m_engine->rootContext()->setContextProperty("ApiServicesModel", m_apiServicesModel.get());
|
||||
|
||||
m_apiCountryModel.reset(new ApiCountryModel(this));
|
||||
m_engine->rootContext()->setContextProperty("ApiCountryModel", m_apiCountryModel.get());
|
||||
connect(m_serversModel.get(), &ServersModel::updateApiLanguageModel, this, [this]() {
|
||||
m_apiCountryModel->updateModel(m_serversModel->getProcessedServerData("apiAvailableCountries").toJsonArray(),
|
||||
m_serversModel->getProcessedServerData("apiServerCountryCode").toString());
|
||||
});
|
||||
connect(m_serversModel.get(), &ServersModel::updateApiServicesModel, this,
|
||||
[this]() { m_apiServicesModel->updateModel(m_serversModel->getProcessedServerData("apiConfig").toJsonObject()); });
|
||||
}
|
||||
|
||||
void AmneziaApplication::initControllers()
|
||||
|
|
@ -363,19 +386,26 @@ void AmneziaApplication::initControllers()
|
|||
new ConnectionController(m_serversModel, m_containersModel, m_clientManagementModel, m_vpnConnection, m_settings));
|
||||
m_engine->rootContext()->setContextProperty("ConnectionController", m_connectionController.get());
|
||||
|
||||
connect(m_connectionController.get(), &ConnectionController::connectionErrorOccurred, this, [this](const QString &errorMessage) {
|
||||
emit m_pageController->showErrorMessage(errorMessage);
|
||||
emit m_vpnConnection->connectionStateChanged(Vpn::ConnectionState::Disconnected);
|
||||
});
|
||||
connect(m_connectionController.get(), qOverload<const QString &>(&ConnectionController::connectionErrorOccurred), this,
|
||||
[this](const QString &errorMessage) {
|
||||
emit m_pageController->showErrorMessage(errorMessage);
|
||||
emit m_vpnConnection->connectionStateChanged(Vpn::ConnectionState::Disconnected);
|
||||
});
|
||||
|
||||
connect(m_connectionController.get(), qOverload<ErrorCode>(&ConnectionController::connectionErrorOccurred), this,
|
||||
[this](ErrorCode errorCode) {
|
||||
emit m_pageController->showErrorMessage(errorCode);
|
||||
emit m_vpnConnection->connectionStateChanged(Vpn::ConnectionState::Disconnected);
|
||||
});
|
||||
|
||||
connect(m_connectionController.get(), &ConnectionController::connectButtonClicked, m_connectionController.get(),
|
||||
&ConnectionController::toggleConnection, Qt::QueuedConnection);
|
||||
|
||||
connect(this, &AmneziaApplication::translationsUpdated, m_connectionController.get(), &ConnectionController::onTranslationsUpdated);
|
||||
|
||||
m_pageController.reset(new PageController(m_serversModel, m_settings));
|
||||
m_engine->rootContext()->setContextProperty("PageController", m_pageController.get());
|
||||
|
||||
m_installController.reset(new InstallController(m_serversModel, m_containersModel, m_protocolsModel, m_clientManagementModel, m_settings));
|
||||
m_installController.reset(new InstallController(m_serversModel, m_containersModel, m_protocolsModel, m_clientManagementModel,
|
||||
m_apiServicesModel, m_settings));
|
||||
m_engine->rootContext()->setContextProperty("InstallController", m_installController.get());
|
||||
connect(m_installController.get(), &InstallController::passphraseRequestStarted, m_pageController.get(),
|
||||
&PageController::showPassphraseRequestDrawer);
|
||||
|
|
@ -384,6 +414,30 @@ void AmneziaApplication::initControllers()
|
|||
connect(m_installController.get(), &InstallController::currentContainerUpdated, m_connectionController.get(),
|
||||
&ConnectionController::onCurrentContainerUpdated);
|
||||
|
||||
connect(m_installController.get(), &InstallController::updateServerFromApiFinished, this, [this]() {
|
||||
disconnect(m_reloadConfigErrorOccurredConnection);
|
||||
emit m_connectionController->configFromApiUpdated();
|
||||
});
|
||||
|
||||
connect(m_connectionController.get(), &ConnectionController::updateApiConfigFromGateway, this, [this]() {
|
||||
m_reloadConfigErrorOccurredConnection = connect(
|
||||
m_installController.get(), qOverload<ErrorCode>(&InstallController::installationErrorOccurred), this,
|
||||
[this]() { emit m_vpnConnection->connectionStateChanged(Vpn::ConnectionState::Disconnected); },
|
||||
static_cast<Qt::ConnectionType>(Qt::AutoConnection || Qt::SingleShotConnection));
|
||||
m_installController->updateServiceFromApi(m_serversModel->getDefaultServerIndex(), "", "");
|
||||
});
|
||||
|
||||
connect(m_connectionController.get(), &ConnectionController::updateApiConfigFromTelegram, this, [this]() {
|
||||
m_reloadConfigErrorOccurredConnection = connect(
|
||||
m_installController.get(), qOverload<ErrorCode>(&InstallController::installationErrorOccurred), this,
|
||||
[this]() { emit m_vpnConnection->connectionStateChanged(Vpn::ConnectionState::Disconnected); },
|
||||
static_cast<Qt::ConnectionType>(Qt::AutoConnection || Qt::SingleShotConnection));
|
||||
m_serversModel->removeApiConfig(m_serversModel->getDefaultServerIndex());
|
||||
m_installController->updateServiceFromTelegram(m_serversModel->getDefaultServerIndex());
|
||||
});
|
||||
|
||||
connect(this, &AmneziaApplication::translationsUpdated, m_connectionController.get(), &ConnectionController::onTranslationsUpdated);
|
||||
|
||||
m_importController.reset(new ImportController(m_serversModel, m_containersModel, m_settings));
|
||||
m_engine->rootContext()->setContextProperty("ImportController", m_importController.get());
|
||||
|
||||
|
|
|
|||
|
|
@ -42,31 +42,26 @@
|
|||
#include "ui/models/protocols_model.h"
|
||||
#include "ui/models/servers_model.h"
|
||||
#include "ui/models/services/sftpConfigModel.h"
|
||||
#include "ui/models/services/socks5ProxyConfigModel.h"
|
||||
#include "ui/models/sites_model.h"
|
||||
#include "ui/models/clientManagementModel.h"
|
||||
#include "ui/models/appSplitTunnelingModel.h"
|
||||
#include "ui/models/apiServicesModel.h"
|
||||
#include "ui/models/apiCountryModel.h"
|
||||
|
||||
#define amnApp (static_cast<AmneziaApplication *>(QCoreApplication::instance()))
|
||||
|
||||
#if defined(Q_OS_ANDROID) || defined(Q_OS_IOS)
|
||||
#define AMNEZIA_BASE_CLASS QGuiApplication
|
||||
#else
|
||||
#define AMNEZIA_BASE_CLASS SingleApplication
|
||||
#define QAPPLICATION_CLASS QApplication
|
||||
#include "singleapplication.h"
|
||||
#define AMNEZIA_BASE_CLASS QApplication
|
||||
#endif
|
||||
|
||||
class AmneziaApplication : public AMNEZIA_BASE_CLASS
|
||||
{
|
||||
Q_OBJECT
|
||||
public:
|
||||
#if defined(Q_OS_ANDROID) || defined(Q_OS_IOS)
|
||||
AmneziaApplication(int &argc, char *argv[]);
|
||||
#else
|
||||
AmneziaApplication(int &argc, char *argv[], bool allowSecondary = false,
|
||||
SingleApplication::Options options = SingleApplication::User, int timeout = 1000,
|
||||
const QString &userData = {});
|
||||
#endif
|
||||
virtual ~AmneziaApplication();
|
||||
|
||||
void init();
|
||||
|
|
@ -76,6 +71,10 @@ public:
|
|||
void updateTranslator(const QLocale &locale);
|
||||
bool parseCommands();
|
||||
|
||||
#if !defined(Q_OS_ANDROID) && !defined(Q_OS_IOS)
|
||||
void startLocalServer();
|
||||
#endif
|
||||
|
||||
QQmlApplicationEngine *qmlEngine() const;
|
||||
QNetworkAccessManager *manager() { return m_nam; }
|
||||
|
||||
|
|
@ -103,6 +102,8 @@ private:
|
|||
QSharedPointer<SitesModel> m_sitesModel;
|
||||
QSharedPointer<AppSplitTunnelingModel> m_appSplitTunnelingModel;
|
||||
QSharedPointer<ClientManagementModel> m_clientManagementModel;
|
||||
QSharedPointer<ApiServicesModel> m_apiServicesModel;
|
||||
QSharedPointer<ApiCountryModel> m_apiCountryModel;
|
||||
|
||||
QScopedPointer<OpenVpnConfigModel> m_openVpnConfigModel;
|
||||
QScopedPointer<ShadowSocksConfigModel> m_shadowSocksConfigModel;
|
||||
|
|
@ -115,6 +116,7 @@ private:
|
|||
#endif
|
||||
|
||||
QScopedPointer<SftpConfigModel> m_sftpConfigModel;
|
||||
QScopedPointer<Socks5ProxyConfigModel> m_socks5ConfigModel;
|
||||
|
||||
QSharedPointer<VpnConnection> m_vpnConnection;
|
||||
QThread m_vpnConnectionThread;
|
||||
|
|
@ -134,6 +136,8 @@ private:
|
|||
QScopedPointer<UpdateController> m_updateController;
|
||||
|
||||
QNetworkAccessManager *m_nam;
|
||||
|
||||
QMetaObject::Connection m_reloadConfigErrorOccurredConnection;
|
||||
};
|
||||
|
||||
#endif // AMNEZIA_APPLICATION_H
|
||||
|
|
|
|||
|
|
@ -3,7 +3,6 @@
|
|||
<manifest
|
||||
xmlns:android="http://schemas.android.com/apk/res/android"
|
||||
xmlns:tools="http://schemas.android.com/tools"
|
||||
package="org.amnezia.vpn"
|
||||
android:versionName="-- %%INSERT_VERSION_NAME%% --"
|
||||
android:versionCode="-- %%INSERT_VERSION_CODE%% --"
|
||||
android:installLocation="auto">
|
||||
|
|
@ -11,6 +10,9 @@
|
|||
<uses-feature android:name="android.hardware.camera" android:required="false" />
|
||||
<uses-feature android:name="android.hardware.camera.any" android:required="false" />
|
||||
<uses-feature android:name="android.hardware.camera.autofocus" android:required="false" />
|
||||
<!-- for TV -->
|
||||
<uses-feature android:name="android.software.leanback" android:required="false" />
|
||||
<uses-feature android:name="android.hardware.touchscreen" android:required="false" />
|
||||
|
||||
<!-- The following comment will be replaced upon deployment with default features based on the dependencies
|
||||
of the application. Remove the comment if you do not require these default features. -->
|
||||
|
|
@ -31,9 +33,11 @@
|
|||
android:label="-- %%INSERT_APP_NAME%% --"
|
||||
android:icon="@mipmap/icon"
|
||||
android:roundIcon="@mipmap/icon_round"
|
||||
android:banner="@mipmap/ic_banner"
|
||||
android:theme="@style/NoActionBar"
|
||||
android:fullBackupContent="@xml/backup_content"
|
||||
android:dataExtractionRules="@xml/data_extraction_rules"
|
||||
android:hasFragileUserData="false"
|
||||
tools:targetApi="s">
|
||||
|
||||
<activity
|
||||
|
|
@ -41,12 +45,13 @@
|
|||
android:configChanges="uiMode|screenSize|smallestScreenSize|screenLayout|orientation|density
|
||||
|fontScale|layoutDirection|locale|keyboard|keyboardHidden|navigation|mcc|mnc"
|
||||
android:launchMode="singleInstance"
|
||||
android:windowSoftInputMode="adjustResize"
|
||||
android:windowSoftInputMode="stateUnchanged|adjustResize"
|
||||
android:exported="true">
|
||||
|
||||
<intent-filter>
|
||||
<action android:name="android.intent.action.MAIN" />
|
||||
<category android:name="android.intent.category.LAUNCHER" />
|
||||
<category android:name="android.intent.category.LEANBACK_LAUNCHER" />
|
||||
</intent-filter>
|
||||
|
||||
<intent-filter>
|
||||
|
|
@ -62,9 +67,6 @@
|
|||
android:name="android.app.lib_name"
|
||||
android:value="-- %%INSERT_APP_LIB_NAME%% --" />
|
||||
|
||||
<meta-data
|
||||
android:name="android.app.extract_android_style"
|
||||
android:value="minimal" />
|
||||
</activity>
|
||||
|
||||
<activity
|
||||
|
|
@ -82,6 +84,13 @@
|
|||
android:exported="false"
|
||||
android:theme="@style/Translucent" />
|
||||
|
||||
<activity android:name=".AuthActivity"
|
||||
android:excludeFromRecents="true"
|
||||
android:launchMode="singleTask"
|
||||
android:taskAffinity=""
|
||||
android:exported="false"
|
||||
android:theme="@style/Translucent" />
|
||||
|
||||
<activity
|
||||
android:name=".ImportConfigActivity"
|
||||
android:excludeFromRecents="true"
|
||||
|
|
@ -136,8 +145,34 @@
|
|||
</activity>
|
||||
|
||||
<service
|
||||
android:name=".AmneziaVpnService"
|
||||
android:process=":amneziaVpnService"
|
||||
android:name=".AwgService"
|
||||
android:process=":amneziaAwgService"
|
||||
android:permission="android.permission.BIND_VPN_SERVICE"
|
||||
android:foregroundServiceType="systemExempted"
|
||||
android:exported="false"
|
||||
tools:ignore="ForegroundServicePermission">
|
||||
|
||||
<intent-filter>
|
||||
<action android:name="android.net.VpnService" />
|
||||
</intent-filter>
|
||||
</service>
|
||||
|
||||
<service
|
||||
android:name=".OpenVpnService"
|
||||
android:process=":amneziaOpenVpnService"
|
||||
android:permission="android.permission.BIND_VPN_SERVICE"
|
||||
android:foregroundServiceType="systemExempted"
|
||||
android:exported="false"
|
||||
tools:ignore="ForegroundServicePermission">
|
||||
|
||||
<intent-filter>
|
||||
<action android:name="android.net.VpnService" />
|
||||
</intent-filter>
|
||||
</service>
|
||||
|
||||
<service
|
||||
android:name=".XrayService"
|
||||
android:process=":amneziaXrayService"
|
||||
android:permission="android.permission.BIND_VPN_SERVICE"
|
||||
android:foregroundServiceType="systemExempted"
|
||||
android:exported="false"
|
||||
|
|
|
|||
|
|
@ -1,81 +1,21 @@
|
|||
package org.amnezia.vpn.protocol.awg
|
||||
|
||||
import org.amnezia.vpn.protocol.wireguard.Wireguard
|
||||
import org.amnezia.vpn.protocol.wireguard.WireguardConfig
|
||||
import org.json.JSONObject
|
||||
|
||||
/**
|
||||
* Config example:
|
||||
* {
|
||||
* "protocol": "awg",
|
||||
* "description": "Server 1",
|
||||
* "dns1": "1.1.1.1",
|
||||
* "dns2": "1.0.0.1",
|
||||
* "hostName": "100.100.100.0",
|
||||
* "splitTunnelSites": [
|
||||
* ],
|
||||
* "splitTunnelType": 0,
|
||||
* "awg_config_data": {
|
||||
* "H1": "969537490",
|
||||
* "H2": "481688153",
|
||||
* "H3": "2049399200",
|
||||
* "H4": "52029755",
|
||||
* "Jc": "3",
|
||||
* "Jmax": "1000",
|
||||
* "Jmin": "50",
|
||||
* "S1": "49",
|
||||
* "S2": "60",
|
||||
* "client_ip": "10.8.1.1",
|
||||
* "hostName": "100.100.100.0",
|
||||
* "port": 12345,
|
||||
* "client_pub_key": "clientPublicKeyBase64",
|
||||
* "client_priv_key": "privateKeyBase64",
|
||||
* "psk_key": "presharedKeyBase64",
|
||||
* "server_pub_key": "publicKeyBase64",
|
||||
* "config": "[Interface]
|
||||
* Address = 10.8.1.1/32
|
||||
* DNS = 1.1.1.1, 1.0.0.1
|
||||
* PrivateKey = privateKeyBase64
|
||||
* Jc = 3
|
||||
* Jmin = 50
|
||||
* Jmax = 1000
|
||||
* S1 = 49
|
||||
* S2 = 60
|
||||
* H1 = 969537490
|
||||
* H2 = 481688153
|
||||
* H3 = 2049399200
|
||||
* H4 = 52029755
|
||||
*
|
||||
* [Peer]
|
||||
* PublicKey = publicKeyBase64
|
||||
* PresharedKey = presharedKeyBase64
|
||||
* AllowedIPs = 0.0.0.0/0, ::/0
|
||||
* Endpoint = 100.100.100.0:12345
|
||||
* PersistentKeepalive = 25
|
||||
* "
|
||||
* }
|
||||
* }
|
||||
*/
|
||||
|
||||
class Awg : Wireguard() {
|
||||
|
||||
override val ifName: String = "awg0"
|
||||
|
||||
override fun parseConfig(config: JSONObject): AwgConfig {
|
||||
val configDataJson = config.getJSONObject("awg_config_data")
|
||||
val configData = parseConfigData(configDataJson.getString("config"))
|
||||
return AwgConfig.build {
|
||||
configWireguard(configData, configDataJson)
|
||||
override fun parseConfig(config: JSONObject): WireguardConfig {
|
||||
val configData = config.getJSONObject("awg_config_data")
|
||||
return WireguardConfig.build {
|
||||
setUseProtocolExtension(true)
|
||||
configExtensionParameters(configData)
|
||||
configWireguard(config, configData)
|
||||
configSplitTunneling(config)
|
||||
configAppSplitTunneling(config)
|
||||
configData["Jc"]?.let { setJc(it.toInt()) }
|
||||
configData["Jmin"]?.let { setJmin(it.toInt()) }
|
||||
configData["Jmax"]?.let { setJmax(it.toInt()) }
|
||||
configData["S1"]?.let { setS1(it.toInt()) }
|
||||
configData["S2"]?.let { setS2(it.toInt()) }
|
||||
configData["H1"]?.let { setH1(it.toLong()) }
|
||||
configData["H2"]?.let { setH2(it.toLong()) }
|
||||
configData["H3"]?.let { setH3(it.toLong()) }
|
||||
configData["H4"]?.let { setH4(it.toLong()) }
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -1,108 +0,0 @@
|
|||
package org.amnezia.vpn.protocol.awg
|
||||
|
||||
import org.amnezia.vpn.protocol.BadConfigException
|
||||
import org.amnezia.vpn.protocol.wireguard.WireguardConfig
|
||||
|
||||
class AwgConfig private constructor(
|
||||
wireguardConfigBuilder: WireguardConfig.Builder,
|
||||
val jc: Int,
|
||||
val jmin: Int,
|
||||
val jmax: Int,
|
||||
val s1: Int,
|
||||
val s2: Int,
|
||||
val h1: Long,
|
||||
val h2: Long,
|
||||
val h3: Long,
|
||||
val h4: Long
|
||||
) : WireguardConfig(wireguardConfigBuilder) {
|
||||
|
||||
private constructor(builder: Builder) : this(
|
||||
builder,
|
||||
builder.jc,
|
||||
builder.jmin,
|
||||
builder.jmax,
|
||||
builder.s1,
|
||||
builder.s2,
|
||||
builder.h1,
|
||||
builder.h2,
|
||||
builder.h3,
|
||||
builder.h4
|
||||
)
|
||||
|
||||
override fun appendDeviceLine(sb: StringBuilder) = with(sb) {
|
||||
super.appendDeviceLine(this)
|
||||
appendLine("jc=$jc")
|
||||
appendLine("jmin=$jmin")
|
||||
appendLine("jmax=$jmax")
|
||||
appendLine("s1=$s1")
|
||||
appendLine("s2=$s2")
|
||||
appendLine("h1=$h1")
|
||||
appendLine("h2=$h2")
|
||||
appendLine("h3=$h3")
|
||||
appendLine("h4=$h4")
|
||||
}
|
||||
|
||||
class Builder : WireguardConfig.Builder() {
|
||||
|
||||
private var _jc: Int? = null
|
||||
internal var jc: Int
|
||||
get() = _jc ?: throw BadConfigException("AWG: parameter jc is undefined")
|
||||
private set(value) { _jc = value }
|
||||
|
||||
private var _jmin: Int? = null
|
||||
internal var jmin: Int
|
||||
get() = _jmin ?: throw BadConfigException("AWG: parameter jmin is undefined")
|
||||
private set(value) { _jmin = value }
|
||||
|
||||
private var _jmax: Int? = null
|
||||
internal var jmax: Int
|
||||
get() = _jmax ?: throw BadConfigException("AWG: parameter jmax is undefined")
|
||||
private set(value) { _jmax = value }
|
||||
|
||||
private var _s1: Int? = null
|
||||
internal var s1: Int
|
||||
get() = _s1 ?: throw BadConfigException("AWG: parameter s1 is undefined")
|
||||
private set(value) { _s1 = value }
|
||||
|
||||
private var _s2: Int? = null
|
||||
internal var s2: Int
|
||||
get() = _s2 ?: throw BadConfigException("AWG: parameter s2 is undefined")
|
||||
private set(value) { _s2 = value }
|
||||
|
||||
private var _h1: Long? = null
|
||||
internal var h1: Long
|
||||
get() = _h1 ?: throw BadConfigException("AWG: parameter h1 is undefined")
|
||||
private set(value) { _h1 = value }
|
||||
|
||||
private var _h2: Long? = null
|
||||
internal var h2: Long
|
||||
get() = _h2 ?: throw BadConfigException("AWG: parameter h2 is undefined")
|
||||
private set(value) { _h2 = value }
|
||||
|
||||
private var _h3: Long? = null
|
||||
internal var h3: Long
|
||||
get() = _h3 ?: throw BadConfigException("AWG: parameter h3 is undefined")
|
||||
private set(value) { _h3 = value }
|
||||
|
||||
private var _h4: Long? = null
|
||||
internal var h4: Long
|
||||
get() = _h4 ?: throw BadConfigException("AWG: parameter h4 is undefined")
|
||||
private set(value) { _h4 = value }
|
||||
|
||||
fun setJc(jc: Int) = apply { this.jc = jc }
|
||||
fun setJmin(jmin: Int) = apply { this.jmin = jmin }
|
||||
fun setJmax(jmax: Int) = apply { this.jmax = jmax }
|
||||
fun setS1(s1: Int) = apply { this.s1 = s1 }
|
||||
fun setS2(s2: Int) = apply { this.s2 = s2 }
|
||||
fun setH1(h1: Long) = apply { this.h1 = h1 }
|
||||
fun setH2(h2: Long) = apply { this.h2 = h2 }
|
||||
fun setH3(h3: Long) = apply { this.h3 = h3 }
|
||||
fun setH4(h4: Long) = apply { this.h4 = h4 }
|
||||
|
||||
override fun build(): AwgConfig = configBuild().run { AwgConfig(this@Builder) }
|
||||
}
|
||||
|
||||
companion object {
|
||||
inline fun build(block: Builder.() -> Unit): AwgConfig = Builder().apply(block).build()
|
||||
}
|
||||
}
|
||||
|
|
@ -3,3 +3,6 @@
|
|||
// android.bundle.enableUncompressedNativeLibs is deprecated
|
||||
// disable adding gradle property android.bundle.enableUncompressedNativeLibs by androiddeployqt
|
||||
useLegacyPackaging
|
||||
|
||||
// package name for androiddeployqt
|
||||
namespace = "org.amnezia.vpn"
|
||||
|
|
|
|||
|
|
@ -3,6 +3,7 @@ import com.android.build.gradle.internal.api.BaseVariantOutputImpl
|
|||
plugins {
|
||||
alias(libs.plugins.android.application)
|
||||
alias(libs.plugins.kotlin.android)
|
||||
alias(libs.plugins.kotlin.serialization)
|
||||
id("property-delegate")
|
||||
}
|
||||
|
||||
|
|
@ -68,6 +69,12 @@ android {
|
|||
}
|
||||
signingConfig = signingConfigs["release"]
|
||||
}
|
||||
|
||||
create("fdroid") {
|
||||
initWith(getByName("release"))
|
||||
signingConfig = null
|
||||
matchingFallbacks += "release"
|
||||
}
|
||||
}
|
||||
|
||||
splits {
|
||||
|
|
@ -98,7 +105,6 @@ android {
|
|||
}
|
||||
|
||||
dependencies {
|
||||
implementation(fileTree(mapOf("dir" to "libs", "include" to listOf("*.jar", "*.aar"))))
|
||||
implementation(project(":qt"))
|
||||
implementation(project(":utils"))
|
||||
implementation(project(":protocolApi"))
|
||||
|
|
@ -106,10 +112,14 @@ dependencies {
|
|||
implementation(project(":awg"))
|
||||
implementation(project(":openvpn"))
|
||||
implementation(project(":cloak"))
|
||||
implementation(project(":xray"))
|
||||
implementation(libs.androidx.core)
|
||||
implementation(libs.androidx.activity)
|
||||
implementation(libs.androidx.fragment)
|
||||
implementation(libs.kotlinx.coroutines)
|
||||
implementation(libs.kotlinx.serialization.protobuf)
|
||||
implementation(libs.bundles.androidx.camera)
|
||||
implementation(libs.google.mlkit)
|
||||
implementation(libs.androidx.datastore)
|
||||
implementation(libs.androidx.biometric)
|
||||
}
|
||||
|
|
|
|||
|
|
@ -3,40 +3,16 @@ package org.amnezia.vpn.protocol.cloak
|
|||
import android.util.Base64
|
||||
import net.openvpn.ovpn3.ClientAPI_Config
|
||||
import org.amnezia.vpn.protocol.openvpn.OpenVpn
|
||||
import org.amnezia.vpn.util.LibraryLoader.loadSharedLibrary
|
||||
import org.json.JSONObject
|
||||
|
||||
/**
|
||||
* Config Example:
|
||||
* {
|
||||
* "protocol": "cloak",
|
||||
* "description": "Server 1",
|
||||
* "dns1": "1.1.1.1",
|
||||
* "dns2": "1.0.0.1",
|
||||
* "hostName": "100.100.100.0",
|
||||
* "splitTunnelSites": [
|
||||
* ],
|
||||
* "splitTunnelType": 0,
|
||||
* "openvpn_config_data": {
|
||||
* "config": "openVpnConfig"
|
||||
* }
|
||||
* "cloak_config_data": {
|
||||
* "BrowserSig": "chrome",
|
||||
* "EncryptionMethod": "aes-gcm",
|
||||
* "NumConn": 1,
|
||||
* "ProxyMethod": "openvpn",
|
||||
* "PublicKey": "PublicKey=",
|
||||
* "RemoteHost": "100.100.100.0",
|
||||
* "RemotePort": "443",
|
||||
* "ServerName": "servername",
|
||||
* "StreamTimeout": 300,
|
||||
* "Transport": "direct",
|
||||
* "UID": "UID="
|
||||
* }
|
||||
* }
|
||||
*/
|
||||
|
||||
class Cloak : OpenVpn() {
|
||||
|
||||
override fun internalInit() {
|
||||
super.internalInit()
|
||||
if (!isInitialized) loadSharedLibrary(context, "ck-ovpn-plugin")
|
||||
}
|
||||
|
||||
override fun parseConfig(config: JSONObject): ClientAPI_Config {
|
||||
val openVpnConfig = ClientAPI_Config()
|
||||
|
||||
|
|
|
|||
|
|
@ -33,7 +33,7 @@ android.library.defaults.buildfeatures.androidresources=false
|
|||
# For development copy and set local values for these parameters in local.properties
|
||||
#androidCompileSdkVersion=android-34
|
||||
#androidBuildToolsVersion=34.0.0
|
||||
#qtMinSdkVersion=24
|
||||
#qtMinSdkVersion=26
|
||||
#qtTargetSdkVersion=34
|
||||
#androidNdkVersion=26.1.10909125
|
||||
#qtTargetAbiList=x86_64
|
||||
|
|
|
|||
|
|
@ -1,26 +1,32 @@
|
|||
[versions]
|
||||
agp = "8.2.0"
|
||||
kotlin = "1.9.20"
|
||||
androidx-core = "1.12.0"
|
||||
androidx-activity = "1.8.1"
|
||||
androidx-annotation = "1.7.0"
|
||||
androidx-camera = "1.3.0"
|
||||
agp = "8.5.2"
|
||||
kotlin = "1.9.24"
|
||||
androidx-core = "1.13.1"
|
||||
androidx-activity = "1.9.1"
|
||||
androidx-annotation = "1.8.2"
|
||||
androidx-biometric = "1.2.0-alpha05"
|
||||
androidx-camera = "1.3.4"
|
||||
androidx-fragment = "1.8.2"
|
||||
androidx-security-crypto = "1.1.0-alpha06"
|
||||
androidx-datastore = "1.1.0-beta01"
|
||||
kotlinx-coroutines = "1.7.3"
|
||||
google-mlkit = "17.2.0"
|
||||
androidx-datastore = "1.1.1"
|
||||
kotlinx-coroutines = "1.8.1"
|
||||
kotlinx-serialization = "1.6.3"
|
||||
google-mlkit = "17.3.0"
|
||||
|
||||
[libraries]
|
||||
androidx-core = { module = "androidx.core:core-ktx", version.ref = "androidx-core" }
|
||||
androidx-activity = { module = "androidx.activity:activity-ktx", version.ref = "androidx-activity" }
|
||||
androidx-annotation = { module = "androidx.annotation:annotation", version.ref = "androidx-annotation" }
|
||||
androidx-biometric = { module = "androidx.biometric:biometric-ktx", version.ref = "androidx-biometric" }
|
||||
androidx-camera-core = { module = "androidx.camera:camera-core", version.ref = "androidx-camera" }
|
||||
androidx-camera-camera2 = { module = "androidx.camera:camera-camera2", version.ref = "androidx-camera" }
|
||||
androidx-camera-lifecycle = { module = "androidx.camera:camera-lifecycle", version.ref = "androidx-camera" }
|
||||
androidx-camera-view = { module = "androidx.camera:camera-view", version.ref = "androidx-camera" }
|
||||
androidx-fragment = { module = "androidx.fragment:fragment-ktx", version.ref = "androidx-fragment" }
|
||||
androidx-security-crypto = { module = "androidx.security:security-crypto-ktx", version.ref = "androidx-security-crypto" }
|
||||
androidx-datastore = { module = "androidx.datastore:datastore-preferences", version.ref = "androidx-datastore" }
|
||||
kotlinx-coroutines = { module = "org.jetbrains.kotlinx:kotlinx-coroutines-android", version.ref = "kotlinx-coroutines" }
|
||||
kotlinx-serialization-protobuf = { module = "org.jetbrains.kotlinx:kotlinx-serialization-protobuf", version.ref = "kotlinx-serialization" }
|
||||
google-mlkit = { module = "com.google.mlkit:barcode-scanning", version.ref = "google-mlkit" }
|
||||
|
||||
[bundles]
|
||||
|
|
@ -35,3 +41,4 @@ androidx-camera = [
|
|||
android-application = { id = "com.android.application", version.ref = "agp" }
|
||||
android-library = { id = "com.android.library", version.ref = "agp" }
|
||||
kotlin-android = { id = "org.jetbrains.kotlin.android", version.ref = "kotlin" }
|
||||
kotlin-serialization = { id = "org.jetbrains.kotlin.plugin.serialization", version.ref = "kotlin"}
|
||||
|
|
|
|||
BIN
client/android/gradle/wrapper/gradle-wrapper.jar
vendored
|
|
@ -1,7 +1,5 @@
|
|||
distributionBase=GRADLE_USER_HOME
|
||||
distributionPath=wrapper/dists
|
||||
distributionUrl=https\://services.gradle.org/distributions/gradle-8.5-bin.zip
|
||||
networkTimeout=10000
|
||||
validateDistributionUrl=true
|
||||
distributionUrl=https\://services.gradle.org/distributions/gradle-8.10-bin.zip
|
||||
zipStoreBase=GRADLE_USER_HOME
|
||||
zipStorePath=wrapper/dists
|
||||
|
|
|
|||
7
client/android/gradlew
vendored
|
|
@ -15,6 +15,8 @@
|
|||
# See the License for the specific language governing permissions and
|
||||
# limitations under the License.
|
||||
#
|
||||
# SPDX-License-Identifier: Apache-2.0
|
||||
#
|
||||
|
||||
##############################################################################
|
||||
#
|
||||
|
|
@ -55,7 +57,7 @@
|
|||
# Darwin, MinGW, and NonStop.
|
||||
#
|
||||
# (3) This script is generated from the Groovy template
|
||||
# https://github.com/gradle/gradle/blob/HEAD/subprojects/plugins/src/main/resources/org/gradle/api/internal/plugins/unixStartScript.txt
|
||||
# https://github.com/gradle/gradle/blob/HEAD/platforms/jvm/plugins-application/src/main/resources/org/gradle/api/internal/plugins/unixStartScript.txt
|
||||
# within the Gradle project.
|
||||
#
|
||||
# You can find Gradle at https://github.com/gradle/gradle/.
|
||||
|
|
@ -84,7 +86,8 @@ done
|
|||
# shellcheck disable=SC2034
|
||||
APP_BASE_NAME=${0##*/}
|
||||
# Discard cd standard output in case $CDPATH is set (https://github.com/gradle/gradle/issues/25036)
|
||||
APP_HOME=$( cd "${APP_HOME:-./}" > /dev/null && pwd -P ) || exit
|
||||
APP_HOME=$( cd -P "${APP_HOME:-./}" > /dev/null && printf '%s
|
||||
' "$PWD" ) || exit
|
||||
|
||||
# Use the maximum available, or set MAX_FD != -1 to use that value.
|
||||
MAX_FD=maximum
|
||||
|
|
|
|||
22
client/android/gradlew.bat
vendored
|
|
@ -13,6 +13,8 @@
|
|||
@rem See the License for the specific language governing permissions and
|
||||
@rem limitations under the License.
|
||||
@rem
|
||||
@rem SPDX-License-Identifier: Apache-2.0
|
||||
@rem
|
||||
|
||||
@if "%DEBUG%"=="" @echo off
|
||||
@rem ##########################################################################
|
||||
|
|
@ -43,11 +45,11 @@ set JAVA_EXE=java.exe
|
|||
%JAVA_EXE% -version >NUL 2>&1
|
||||
if %ERRORLEVEL% equ 0 goto execute
|
||||
|
||||
echo.
|
||||
echo ERROR: JAVA_HOME is not set and no 'java' command could be found in your PATH.
|
||||
echo.
|
||||
echo Please set the JAVA_HOME variable in your environment to match the
|
||||
echo location of your Java installation.
|
||||
echo. 1>&2
|
||||
echo ERROR: JAVA_HOME is not set and no 'java' command could be found in your PATH. 1>&2
|
||||
echo. 1>&2
|
||||
echo Please set the JAVA_HOME variable in your environment to match the 1>&2
|
||||
echo location of your Java installation. 1>&2
|
||||
|
||||
goto fail
|
||||
|
||||
|
|
@ -57,11 +59,11 @@ set JAVA_EXE=%JAVA_HOME%/bin/java.exe
|
|||
|
||||
if exist "%JAVA_EXE%" goto execute
|
||||
|
||||
echo.
|
||||
echo ERROR: JAVA_HOME is set to an invalid directory: %JAVA_HOME%
|
||||
echo.
|
||||
echo Please set the JAVA_HOME variable in your environment to match the
|
||||
echo location of your Java installation.
|
||||
echo. 1>&2
|
||||
echo ERROR: JAVA_HOME is set to an invalid directory: %JAVA_HOME% 1>&2
|
||||
echo. 1>&2
|
||||
echo Please set the JAVA_HOME variable in your environment to match the 1>&2
|
||||
echo location of your Java installation. 1>&2
|
||||
|
||||
goto fail
|
||||
|
||||
|
|
|
|||
|
|
@ -1,43 +1,24 @@
|
|||
package org.amnezia.vpn.protocol.openvpn
|
||||
|
||||
import android.content.Context
|
||||
import android.net.VpnService.Builder
|
||||
import kotlinx.coroutines.CoroutineScope
|
||||
import kotlinx.coroutines.Dispatchers
|
||||
import kotlinx.coroutines.flow.MutableStateFlow
|
||||
import kotlinx.coroutines.cancel
|
||||
import kotlinx.coroutines.launch
|
||||
import net.openvpn.ovpn3.ClientAPI_Config
|
||||
import org.amnezia.vpn.protocol.BadConfigException
|
||||
import org.amnezia.vpn.protocol.Protocol
|
||||
import org.amnezia.vpn.protocol.ProtocolState
|
||||
import org.amnezia.vpn.protocol.ProtocolState.DISCONNECTED
|
||||
import org.amnezia.vpn.protocol.Statistics
|
||||
import org.amnezia.vpn.protocol.VpnStartException
|
||||
import org.amnezia.vpn.util.LibraryLoader.loadSharedLibrary
|
||||
import org.amnezia.vpn.util.net.InetNetwork
|
||||
import org.amnezia.vpn.util.net.getLocalNetworks
|
||||
import org.amnezia.vpn.util.net.parseInetAddress
|
||||
import org.json.JSONObject
|
||||
|
||||
/**
|
||||
* Config Example:
|
||||
* {
|
||||
* "protocol": "openvpn",
|
||||
* "description": "Server 1",
|
||||
* "dns1": "1.1.1.1",
|
||||
* "dns2": "1.0.0.1",
|
||||
* "hostName": "100.100.100.0",
|
||||
* "splitTunnelSites": [
|
||||
* ],
|
||||
* "splitTunnelType": 0,
|
||||
* "openvpn_config_data": {
|
||||
* "config": "openVpnConfig"
|
||||
* }
|
||||
* }
|
||||
*/
|
||||
|
||||
open class OpenVpn : Protocol() {
|
||||
|
||||
private lateinit var context: Context
|
||||
private var openVpnClient: OpenVpnClient? = null
|
||||
private lateinit var scope: CoroutineScope
|
||||
|
||||
|
|
@ -53,14 +34,18 @@ open class OpenVpn : Protocol() {
|
|||
return Statistics.EMPTY_STATISTICS
|
||||
}
|
||||
|
||||
override fun initialize(context: Context, state: MutableStateFlow<ProtocolState>, onError: (String) -> Unit) {
|
||||
super.initialize(context, state, onError)
|
||||
loadSharedLibrary(context, "ovpn3")
|
||||
this.context = context
|
||||
override fun internalInit() {
|
||||
if (!isInitialized) {
|
||||
loadSharedLibrary(context, "ovpn3")
|
||||
loadSharedLibrary(context, "ovpnutil")
|
||||
}
|
||||
if (this::scope.isInitialized) {
|
||||
scope.cancel()
|
||||
}
|
||||
scope = CoroutineScope(Dispatchers.IO)
|
||||
}
|
||||
|
||||
override fun startVpn(config: JSONObject, vpnBuilder: Builder, protect: (Int) -> Boolean) {
|
||||
override suspend fun startVpn(config: JSONObject, vpnBuilder: Builder, protect: (Int) -> Boolean) {
|
||||
val configBuilder = OpenVpnConfig.Builder()
|
||||
|
||||
openVpnClient = OpenVpnClient(
|
||||
|
|
|
|||
|
|
@ -2,7 +2,6 @@ package org.amnezia.vpn.protocol
|
|||
|
||||
sealed class ProtocolException(message: String? = null, cause: Throwable? = null) : Exception(message, cause)
|
||||
|
||||
class LoadLibraryException(message: String? = null, cause: Throwable? = null) : ProtocolException(message, cause)
|
||||
class BadConfigException(message: String? = null, cause: Throwable? = null) : ProtocolException(message, cause)
|
||||
|
||||
class VpnStartException(message: String? = null, cause: Throwable? = null) : ProtocolException(message, cause)
|
||||
|
|
|
|||
|
|
@ -1,6 +1,5 @@
|
|||
package org.amnezia.vpn.protocol
|
||||
|
||||
import android.annotation.SuppressLint
|
||||
import android.content.Context
|
||||
import android.net.IpPrefix
|
||||
import android.net.VpnService
|
||||
|
|
@ -8,9 +7,6 @@ import android.net.VpnService.Builder
|
|||
import android.os.Build
|
||||
import android.system.OsConstants
|
||||
import androidx.annotation.RequiresApi
|
||||
import java.io.File
|
||||
import java.io.FileOutputStream
|
||||
import java.util.zip.ZipFile
|
||||
import kotlinx.coroutines.flow.MutableStateFlow
|
||||
import org.amnezia.vpn.util.Log
|
||||
import org.amnezia.vpn.util.net.InetNetwork
|
||||
|
|
@ -27,15 +23,22 @@ private const val SPLIT_TUNNEL_EXCLUDE = 2
|
|||
abstract class Protocol {
|
||||
|
||||
abstract val statistics: Statistics
|
||||
protected lateinit var context: Context
|
||||
protected lateinit var state: MutableStateFlow<ProtocolState>
|
||||
protected lateinit var onError: (String) -> Unit
|
||||
protected var isInitialized: Boolean = false
|
||||
|
||||
open fun initialize(context: Context, state: MutableStateFlow<ProtocolState>, onError: (String) -> Unit) {
|
||||
fun initialize(context: Context, state: MutableStateFlow<ProtocolState>, onError: (String) -> Unit) {
|
||||
this.context = context
|
||||
this.state = state
|
||||
this.onError = onError
|
||||
internalInit()
|
||||
isInitialized = true
|
||||
}
|
||||
|
||||
abstract fun startVpn(config: JSONObject, vpnBuilder: Builder, protect: (Int) -> Boolean)
|
||||
protected abstract fun internalInit()
|
||||
|
||||
abstract suspend fun startVpn(config: JSONObject, vpnBuilder: Builder, protect: (Int) -> Boolean)
|
||||
|
||||
abstract fun stopVpn()
|
||||
|
||||
|
|
@ -151,60 +154,6 @@ abstract class Protocol {
|
|||
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.Q)
|
||||
vpnBuilder.setMetered(false)
|
||||
}
|
||||
|
||||
companion object {
|
||||
private fun extractLibrary(context: Context, libraryName: String, destination: File): Boolean {
|
||||
Log.d(TAG, "Extracting library: $libraryName")
|
||||
val apks = hashSetOf<String>()
|
||||
context.applicationInfo.run {
|
||||
sourceDir?.let { apks += it }
|
||||
splitSourceDirs?.let { apks += it }
|
||||
}
|
||||
for (abi in Build.SUPPORTED_ABIS) {
|
||||
for (apk in apks) {
|
||||
ZipFile(File(apk), ZipFile.OPEN_READ).use { zipFile ->
|
||||
val mappedName = System.mapLibraryName(libraryName)
|
||||
val libraryZipPath = listOf("lib", abi, mappedName).joinToString(File.separator)
|
||||
val zipEntry = zipFile.getEntry(libraryZipPath)
|
||||
zipEntry?.let {
|
||||
Log.d(TAG, "Extracting apk:/$libraryZipPath to ${destination.absolutePath}")
|
||||
FileOutputStream(destination).use { outStream ->
|
||||
zipFile.getInputStream(zipEntry).use { inStream ->
|
||||
inStream.copyTo(outStream, 32 * 1024)
|
||||
outStream.fd.sync()
|
||||
}
|
||||
}
|
||||
}
|
||||
return true
|
||||
}
|
||||
}
|
||||
}
|
||||
return false
|
||||
}
|
||||
|
||||
@SuppressLint("UnsafeDynamicallyLoadedCode")
|
||||
fun loadSharedLibrary(context: Context, libraryName: String) {
|
||||
Log.d(TAG, "Loading library: $libraryName")
|
||||
try {
|
||||
System.loadLibrary(libraryName)
|
||||
return
|
||||
} catch (_: UnsatisfiedLinkError) {
|
||||
Log.d(TAG, "Failed to load library, try to extract it from apk")
|
||||
}
|
||||
var tempFile: File? = null
|
||||
try {
|
||||
tempFile = File.createTempFile("lib", ".so", context.codeCacheDir)
|
||||
if (extractLibrary(context, libraryName, tempFile)) {
|
||||
System.load(tempFile.absolutePath)
|
||||
return
|
||||
}
|
||||
} catch (e: Exception) {
|
||||
throw LoadLibraryException("Failed to load library apk: $libraryName", e)
|
||||
} finally {
|
||||
tempFile?.delete()
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private fun VpnService.Builder.addAddress(addr: InetNetwork) = addAddress(addr.address, addr.mask)
|
||||
|
|
|
|||
|
|
@ -21,5 +21,5 @@ android {
|
|||
}
|
||||
|
||||
dependencies {
|
||||
implementation(fileTree(mapOf("dir" to "../libs", "include" to listOf("*.jar", "*.aar"))))
|
||||
api(fileTree(mapOf("dir" to "../libs", "include" to listOf("*.jar"))))
|
||||
}
|
||||
|
|
|
|||
5
client/android/res/mipmap-anydpi-v26/ic_banner.xml
Normal file
|
|
@ -0,0 +1,5 @@
|
|||
<?xml version="1.0" encoding="utf-8"?>
|
||||
<adaptive-icon xmlns:android="http://schemas.android.com/apk/res/android">
|
||||
<background android:drawable="@color/ic_banner_background"/>
|
||||
<foreground android:drawable="@mipmap/ic_banner_foreground"/>
|
||||
</adaptive-icon>
|
||||
|
Before Width: | Height: | Size: 3.3 KiB After Width: | Height: | Size: 6.5 KiB |
|
Before Width: | Height: | Size: 4.1 KiB After Width: | Height: | Size: 8.2 KiB |
|
Before Width: | Height: | Size: 1.9 KiB After Width: | Height: | Size: 2.6 KiB |
|
Before Width: | Height: | Size: 1.7 KiB After Width: | Height: | Size: 3.1 KiB |
|
Before Width: | Height: | Size: 3.4 KiB After Width: | Height: | Size: 3.8 KiB |
|
Before Width: | Height: | Size: 2.6 KiB After Width: | Height: | Size: 4.7 KiB |
BIN
client/android/res/mipmap-xhdpi/ic_banner.png
Normal file
|
After Width: | Height: | Size: 11 KiB |
BIN
client/android/res/mipmap-xhdpi/ic_banner_foreground.png
Normal file
|
After Width: | Height: | Size: 12 KiB |
|
Before Width: | Height: | Size: 9.7 KiB After Width: | Height: | Size: 9.3 KiB |
|
Before Width: | Height: | Size: 5.4 KiB After Width: | Height: | Size: 12 KiB |
|
Before Width: | Height: | Size: 19 KiB After Width: | Height: | Size: 15 KiB |
|
Before Width: | Height: | Size: 8.1 KiB After Width: | Height: | Size: 21 KiB |
|
Before Width: | Height: | Size: 30 KiB After Width: | Height: | Size: 22 KiB |
|
Before Width: | Height: | Size: 11 KiB After Width: | Height: | Size: 30 KiB |
4
client/android/res/values/ic_banner_background.xml
Normal file
|
|
@ -0,0 +1,4 @@
|
|||
<?xml version="1.0" encoding="utf-8"?>
|
||||
<resources>
|
||||
<color name="ic_banner_background">#1E1E1F</color>
|
||||
</resources>
|
||||
|
|
@ -3,7 +3,6 @@
|
|||
<!-- DO NOT EDIT THIS: This file is populated automatically by the deployment tool. -->
|
||||
|
||||
<array name="bundled_libs">
|
||||
<!-- %%INSERT_EXTRA_LIBS%% -->
|
||||
</array>
|
||||
|
||||
<array name="qt_libs">
|
||||
|
|
|
|||
|
|
@ -1,6 +1,9 @@
|
|||
<?xml version="1.0" encoding="utf-8"?>
|
||||
<resources>
|
||||
<color name="black">#FF0E0E11</color>
|
||||
<style name="NoActionBar">
|
||||
<item name="android:windowBackground">@color/black</item>
|
||||
<item name="android:colorBackground">@color/black</item>
|
||||
<item name="android:windowActionBar">false</item>
|
||||
<item name="android:windowNoTitle">true</item>
|
||||
</style>
|
||||
|
|
|
|||
|
|
@ -22,7 +22,7 @@ dependencyResolutionManagement {
|
|||
includeBuild("./gradle/plugins")
|
||||
|
||||
plugins {
|
||||
id("com.android.settings") version "8.2.0"
|
||||
id("com.android.settings") version "8.5.2"
|
||||
id("settings-property-delegate")
|
||||
}
|
||||
|
||||
|
|
@ -36,6 +36,8 @@ include(":wireguard")
|
|||
include(":awg")
|
||||
include(":openvpn")
|
||||
include(":cloak")
|
||||
include(":xray")
|
||||
include(":xray:libXray")
|
||||
|
||||
// get values from gradle or local properties
|
||||
val androidBuildToolsVersion: String by gradleProperties
|
||||
|
|
|
|||
|
|
@ -1,6 +1,7 @@
|
|||
package org.amnezia.vpn
|
||||
|
||||
import android.Manifest
|
||||
import android.annotation.SuppressLint
|
||||
import android.app.AlertDialog
|
||||
import android.app.NotificationManager
|
||||
import android.content.BroadcastReceiver
|
||||
|
|
@ -20,6 +21,7 @@ import android.os.Looper
|
|||
import android.os.Message
|
||||
import android.os.Messenger
|
||||
import android.provider.Settings
|
||||
import android.view.MotionEvent
|
||||
import android.view.WindowManager.LayoutParams
|
||||
import android.webkit.MimeTypeMap
|
||||
import android.widget.Toast
|
||||
|
|
@ -34,6 +36,7 @@ import kotlinx.coroutines.CompletableDeferred
|
|||
import kotlinx.coroutines.CoroutineScope
|
||||
import kotlinx.coroutines.Dispatchers
|
||||
import kotlinx.coroutines.SupervisorJob
|
||||
import kotlinx.coroutines.async
|
||||
import kotlinx.coroutines.cancel
|
||||
import kotlinx.coroutines.launch
|
||||
import kotlinx.coroutines.runBlocking
|
||||
|
|
@ -41,8 +44,11 @@ import kotlinx.coroutines.withContext
|
|||
import org.amnezia.vpn.protocol.getStatistics
|
||||
import org.amnezia.vpn.protocol.getStatus
|
||||
import org.amnezia.vpn.qt.QtAndroidController
|
||||
import org.amnezia.vpn.util.LibraryLoader.loadSharedLibrary
|
||||
import org.amnezia.vpn.util.Log
|
||||
import org.amnezia.vpn.util.Prefs
|
||||
import org.json.JSONException
|
||||
import org.json.JSONObject
|
||||
import org.qtproject.qt.android.bindings.QtActivity
|
||||
|
||||
private const val TAG = "AmneziaActivity"
|
||||
|
|
@ -59,6 +65,7 @@ class AmneziaActivity : QtActivity() {
|
|||
|
||||
private lateinit var mainScope: CoroutineScope
|
||||
private val qtInitialized = CompletableDeferred<Unit>()
|
||||
private var vpnProto: VpnProto? = null
|
||||
private var isWaitingStatus = true
|
||||
private var isServiceConnected = false
|
||||
private var isInBoundState = false
|
||||
|
|
@ -141,6 +148,7 @@ class AmneziaActivity : QtActivity() {
|
|||
override fun onBindingDied(name: ComponentName?) {
|
||||
Log.w(TAG, "Binding to the ${name?.flattenToString()} unexpectedly died")
|
||||
doUnbindService()
|
||||
QtAndroidController.onServiceDisconnected()
|
||||
doBindService()
|
||||
}
|
||||
}
|
||||
|
|
@ -151,17 +159,38 @@ class AmneziaActivity : QtActivity() {
|
|||
*/
|
||||
override fun onCreate(savedInstanceState: Bundle?) {
|
||||
super.onCreate(savedInstanceState)
|
||||
Log.d(TAG, "Create Amnezia activity: $intent")
|
||||
Log.d(TAG, "Create Amnezia activity")
|
||||
loadLibs()
|
||||
window.apply {
|
||||
addFlags(LayoutParams.FLAG_DRAWS_SYSTEM_BAR_BACKGROUNDS)
|
||||
statusBarColor = getColor(R.color.black)
|
||||
}
|
||||
mainScope = CoroutineScope(SupervisorJob() + Dispatchers.Main.immediate)
|
||||
val proto = mainScope.async(Dispatchers.IO) {
|
||||
VpnStateStore.getVpnState().vpnProto
|
||||
}
|
||||
vpnServiceMessenger = IpcMessenger(
|
||||
"VpnService",
|
||||
onDeadObjectException = {
|
||||
doUnbindService()
|
||||
QtAndroidController.onServiceDisconnected()
|
||||
doBindService()
|
||||
}
|
||||
)
|
||||
registerBroadcastReceivers()
|
||||
intent?.let(::processIntent)
|
||||
runBlocking { vpnProto = proto.await() }
|
||||
}
|
||||
|
||||
private fun loadLibs() {
|
||||
listOf(
|
||||
"rsapss",
|
||||
"crypto_3",
|
||||
"ssl_3",
|
||||
"ssh"
|
||||
).forEach {
|
||||
loadSharedLibrary(this.applicationContext, it)
|
||||
}
|
||||
}
|
||||
|
||||
private fun registerBroadcastReceivers() {
|
||||
|
|
@ -172,7 +201,7 @@ class AmneziaActivity : QtActivity() {
|
|||
NotificationManager.ACTION_APP_BLOCK_STATE_CHANGED
|
||||
)
|
||||
) {
|
||||
Log.d(
|
||||
Log.v(
|
||||
TAG, "Notification state changed: ${it?.action}, blocked = " +
|
||||
"${it?.getBooleanExtra(NotificationManager.EXTRA_BLOCKED_STATE, false)}"
|
||||
)
|
||||
|
|
@ -186,7 +215,7 @@ class AmneziaActivity : QtActivity() {
|
|||
|
||||
override fun onNewIntent(intent: Intent?) {
|
||||
super.onNewIntent(intent)
|
||||
Log.d(TAG, "onNewIntent: $intent")
|
||||
Log.v(TAG, "onNewIntent: $intent")
|
||||
intent?.let(::processIntent)
|
||||
}
|
||||
|
||||
|
|
@ -209,13 +238,21 @@ class AmneziaActivity : QtActivity() {
|
|||
Log.d(TAG, "Start Amnezia activity")
|
||||
mainScope.launch {
|
||||
qtInitialized.await()
|
||||
doBindService()
|
||||
vpnProto?.let { proto ->
|
||||
if (AmneziaVpnService.isRunning(applicationContext, proto.processName)) {
|
||||
doBindService()
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
override fun onStop() {
|
||||
Log.d(TAG, "Stop Amnezia activity")
|
||||
doUnbindService()
|
||||
mainScope.launch {
|
||||
qtInitialized.await()
|
||||
QtAndroidController.onServiceDisconnected()
|
||||
}
|
||||
super.onStop()
|
||||
}
|
||||
|
||||
|
|
@ -269,10 +306,12 @@ class AmneziaActivity : QtActivity() {
|
|||
@MainThread
|
||||
private fun doBindService() {
|
||||
Log.d(TAG, "Bind service")
|
||||
Intent(this, AmneziaVpnService::class.java).also {
|
||||
bindService(it, serviceConnection, BIND_ABOVE_CLIENT and BIND_AUTO_CREATE)
|
||||
vpnProto?.let { proto ->
|
||||
Intent(this, proto.serviceClass).also {
|
||||
bindService(it, serviceConnection, BIND_ABOVE_CLIENT and BIND_AUTO_CREATE)
|
||||
}
|
||||
isInBoundState = true
|
||||
}
|
||||
isInBoundState = true
|
||||
}
|
||||
|
||||
@MainThread
|
||||
|
|
@ -280,7 +319,6 @@ class AmneziaActivity : QtActivity() {
|
|||
if (isInBoundState) {
|
||||
Log.d(TAG, "Unbind service")
|
||||
isWaitingStatus = true
|
||||
QtAndroidController.onServiceDisconnected()
|
||||
isServiceConnected = false
|
||||
vpnServiceMessenger.send(Action.UNREGISTER_CLIENT, activityMessenger)
|
||||
vpnServiceMessenger.reset()
|
||||
|
|
@ -365,13 +403,32 @@ class AmneziaActivity : QtActivity() {
|
|||
|
||||
@MainThread
|
||||
private fun startVpn(vpnConfig: String) {
|
||||
if (isServiceConnected) {
|
||||
connectToVpn(vpnConfig)
|
||||
} else {
|
||||
getVpnProto(vpnConfig)?.let { proto ->
|
||||
Log.v(TAG, "Proto from config: $proto, current proto: $vpnProto")
|
||||
if (isServiceConnected) {
|
||||
if (proto.serviceClass == vpnProto?.serviceClass) {
|
||||
vpnProto = proto
|
||||
connectToVpn(vpnConfig)
|
||||
return
|
||||
}
|
||||
doUnbindService()
|
||||
}
|
||||
vpnProto = proto
|
||||
isWaitingStatus = false
|
||||
startVpnService(vpnConfig)
|
||||
startVpnService(vpnConfig, proto)
|
||||
doBindService()
|
||||
}
|
||||
} ?: QtAndroidController.onServiceError()
|
||||
}
|
||||
|
||||
private fun getVpnProto(vpnConfig: String): VpnProto? = try {
|
||||
require(vpnConfig.isNotBlank()) { "Blank VPN config" }
|
||||
VpnProto.get(JSONObject(vpnConfig).getString("protocol"))
|
||||
} catch (e: JSONException) {
|
||||
Log.e(TAG, "Invalid VPN config json format: ${e.message}")
|
||||
null
|
||||
} catch (e: IllegalArgumentException) {
|
||||
Log.e(TAG, "Protocol not found: ${e.message}")
|
||||
null
|
||||
}
|
||||
|
||||
private fun connectToVpn(vpnConfig: String) {
|
||||
|
|
@ -383,15 +440,15 @@ class AmneziaActivity : QtActivity() {
|
|||
}
|
||||
}
|
||||
|
||||
private fun startVpnService(vpnConfig: String) {
|
||||
Log.d(TAG, "Start VPN service")
|
||||
Intent(this, AmneziaVpnService::class.java).apply {
|
||||
private fun startVpnService(vpnConfig: String, proto: VpnProto) {
|
||||
Log.d(TAG, "Start VPN service: $proto")
|
||||
Intent(this, proto.serviceClass).apply {
|
||||
putExtra(MSG_VPN_CONFIG, vpnConfig)
|
||||
}.also {
|
||||
try {
|
||||
ContextCompat.startForegroundService(this, it)
|
||||
} catch (e: SecurityException) {
|
||||
Log.e(TAG, "Failed to start AmneziaVpnService: $e")
|
||||
Log.e(TAG, "Failed to start ${proto.serviceClass.simpleName}: $e")
|
||||
QtAndroidController.onServiceError()
|
||||
}
|
||||
}
|
||||
|
|
@ -460,7 +517,7 @@ class AmneziaActivity : QtActivity() {
|
|||
startActivityForResult(it, CREATE_FILE_ACTION_CODE, ActivityResultHandler(
|
||||
onSuccess = {
|
||||
it?.data?.let { uri ->
|
||||
Log.d(TAG, "Save file to $uri")
|
||||
Log.v(TAG, "Save file to $uri")
|
||||
try {
|
||||
contentResolver.openOutputStream(uri)?.use { os ->
|
||||
os.bufferedWriter().use { it.write(data) }
|
||||
|
|
@ -507,9 +564,9 @@ class AmneziaActivity : QtActivity() {
|
|||
}
|
||||
}.also {
|
||||
startActivityForResult(it, OPEN_FILE_ACTION_CODE, ActivityResultHandler(
|
||||
onSuccess = {
|
||||
onAny = {
|
||||
val uri = it?.data?.toString() ?: ""
|
||||
Log.d(TAG, "Open file: $uri")
|
||||
Log.v(TAG, "Open file: $uri")
|
||||
mainScope.launch {
|
||||
qtInitialized.await()
|
||||
QtAndroidController.onFileOpened(uri)
|
||||
|
|
@ -521,8 +578,12 @@ class AmneziaActivity : QtActivity() {
|
|||
}
|
||||
|
||||
@Suppress("unused")
|
||||
@SuppressLint("UnsupportedChromeOsCameraSystemFeature")
|
||||
fun isCameraPresent(): Boolean = applicationContext.packageManager.hasSystemFeature(PackageManager.FEATURE_CAMERA)
|
||||
|
||||
@Suppress("unused")
|
||||
fun isOnTv(): Boolean = applicationContext.packageManager.hasSystemFeature(PackageManager.FEATURE_LEANBACK)
|
||||
|
||||
@Suppress("unused")
|
||||
fun startQrCodeReader() {
|
||||
Log.v(TAG, "Start camera")
|
||||
|
|
@ -567,6 +628,14 @@ class AmneziaActivity : QtActivity() {
|
|||
}
|
||||
}
|
||||
|
||||
@Suppress("unused")
|
||||
fun setNavigationBarColor(color: Int) {
|
||||
Log.v(TAG, "Change navigation bar color: ${"#%08X".format(color)}")
|
||||
mainScope.launch {
|
||||
window.navigationBarColor = color
|
||||
}
|
||||
}
|
||||
|
||||
@Suppress("unused")
|
||||
fun minimizeApp() {
|
||||
Log.v(TAG, "Minimize application")
|
||||
|
|
@ -641,6 +710,77 @@ class AmneziaActivity : QtActivity() {
|
|||
.show()
|
||||
}
|
||||
|
||||
@Suppress("unused")
|
||||
fun requestAuthentication() {
|
||||
Log.v(TAG, "Request authentication")
|
||||
mainScope.launch {
|
||||
qtInitialized.await()
|
||||
Intent(this@AmneziaActivity, AuthActivity::class.java).also {
|
||||
startActivity(it)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// workaround for a bug in Qt that causes the mouse click event not to be handled
|
||||
// also disable right-click, as it causes the application to crash
|
||||
private var lastButtonState = 0
|
||||
private fun MotionEvent.fixCopy(): MotionEvent = MotionEvent.obtain(
|
||||
downTime,
|
||||
eventTime,
|
||||
action,
|
||||
pointerCount,
|
||||
(0 until pointerCount).map { i ->
|
||||
MotionEvent.PointerProperties().apply {
|
||||
getPointerProperties(i, this)
|
||||
}
|
||||
}.toTypedArray(),
|
||||
(0 until pointerCount).map { i ->
|
||||
MotionEvent.PointerCoords().apply {
|
||||
getPointerCoords(i, this)
|
||||
}
|
||||
}.toTypedArray(),
|
||||
metaState,
|
||||
MotionEvent.BUTTON_PRIMARY,
|
||||
xPrecision,
|
||||
yPrecision,
|
||||
deviceId,
|
||||
edgeFlags,
|
||||
source,
|
||||
flags
|
||||
)
|
||||
|
||||
private fun handleMouseEvent(ev: MotionEvent, superDispatch: (MotionEvent?) -> Boolean): Boolean {
|
||||
when (ev.action) {
|
||||
MotionEvent.ACTION_DOWN -> {
|
||||
lastButtonState = ev.buttonState
|
||||
if (ev.buttonState == MotionEvent.BUTTON_SECONDARY) return true
|
||||
}
|
||||
|
||||
MotionEvent.ACTION_UP -> {
|
||||
when (lastButtonState) {
|
||||
MotionEvent.BUTTON_SECONDARY -> return true
|
||||
MotionEvent.BUTTON_PRIMARY -> {
|
||||
val modEvent = ev.fixCopy()
|
||||
return superDispatch(modEvent).apply { modEvent.recycle() }
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
return superDispatch(ev)
|
||||
}
|
||||
|
||||
override fun dispatchTouchEvent(ev: MotionEvent?): Boolean {
|
||||
if (ev != null && ev.getToolType(0) == MotionEvent.TOOL_TYPE_MOUSE) {
|
||||
return handleMouseEvent(ev) { super.dispatchTouchEvent(it) }
|
||||
}
|
||||
return super.dispatchTouchEvent(ev)
|
||||
}
|
||||
|
||||
override fun dispatchTrackballEvent(ev: MotionEvent?): Boolean {
|
||||
ev?.let { return handleMouseEvent(ev) { super.dispatchTrackballEvent(it) }}
|
||||
return super.dispatchTrackballEvent(ev)
|
||||
}
|
||||
|
||||
/**
|
||||
* Utils methods
|
||||
*/
|
||||
|
|
|
|||
|
|
@ -39,6 +39,9 @@ class AmneziaTileService : TileService() {
|
|||
|
||||
@Volatile
|
||||
private var isServiceConnected = false
|
||||
|
||||
@Volatile
|
||||
private var vpnProto: VpnProto? = null
|
||||
private var isInBoundState = false
|
||||
@Volatile
|
||||
private var isVpnConfigExists = false
|
||||
|
|
@ -94,16 +97,21 @@ class AmneziaTileService : TileService() {
|
|||
|
||||
override fun onStartListening() {
|
||||
super.onStartListening()
|
||||
Log.d(TAG, "Start listening")
|
||||
if (AmneziaVpnService.isRunning(applicationContext)) {
|
||||
Log.d(TAG, "Vpn service is running")
|
||||
doBindService()
|
||||
} else {
|
||||
Log.d(TAG, "Vpn service is not running")
|
||||
isServiceConnected = false
|
||||
updateVpnState(DISCONNECTED)
|
||||
scope.launch {
|
||||
Log.d(TAG, "Start listening")
|
||||
vpnProto = VpnStateStore.getVpnState().vpnProto
|
||||
vpnProto.also { proto ->
|
||||
if (proto != null && AmneziaVpnService.isRunning(applicationContext, proto.processName)) {
|
||||
Log.d(TAG, "Vpn service is running")
|
||||
doBindService()
|
||||
} else {
|
||||
Log.d(TAG, "Vpn service is not running")
|
||||
isServiceConnected = false
|
||||
updateVpnState(DISCONNECTED)
|
||||
}
|
||||
}
|
||||
vpnStateListeningJob = launchVpnStateListening()
|
||||
}
|
||||
vpnStateListeningJob = launchVpnStateListening()
|
||||
}
|
||||
|
||||
override fun onStopListening() {
|
||||
|
|
@ -124,7 +132,7 @@ class AmneziaTileService : TileService() {
|
|||
}
|
||||
|
||||
private fun onClickInternal() {
|
||||
if (isVpnConfigExists) {
|
||||
if (isVpnConfigExists && vpnProto != null) {
|
||||
Log.d(TAG, "Change VPN state")
|
||||
if (qsTile.state == Tile.STATE_INACTIVE) {
|
||||
Log.d(TAG, "Start VPN")
|
||||
|
|
@ -147,10 +155,12 @@ class AmneziaTileService : TileService() {
|
|||
|
||||
private fun doBindService() {
|
||||
Log.d(TAG, "Bind service")
|
||||
Intent(this, AmneziaVpnService::class.java).also {
|
||||
bindService(it, serviceConnection, BIND_ABOVE_CLIENT)
|
||||
vpnProto?.let { proto ->
|
||||
Intent(this, proto.serviceClass).also {
|
||||
bindService(it, serviceConnection, BIND_ABOVE_CLIENT)
|
||||
}
|
||||
isInBoundState = true
|
||||
}
|
||||
isInBoundState = true
|
||||
}
|
||||
|
||||
private fun doUnbindService() {
|
||||
|
|
@ -180,6 +190,7 @@ class AmneziaTileService : TileService() {
|
|||
if (VpnService.prepare(applicationContext) != null) {
|
||||
Intent(this, VpnRequestActivity::class.java).apply {
|
||||
addFlags(Intent.FLAG_ACTIVITY_NEW_TASK)
|
||||
putExtra(EXTRA_PROTOCOL, vpnProto)
|
||||
}.also {
|
||||
startActivityAndCollapseCompat(it)
|
||||
}
|
||||
|
|
@ -189,14 +200,16 @@ class AmneziaTileService : TileService() {
|
|||
}
|
||||
|
||||
private fun startVpnService() {
|
||||
try {
|
||||
ContextCompat.startForegroundService(
|
||||
applicationContext,
|
||||
Intent(this, AmneziaVpnService::class.java)
|
||||
)
|
||||
} catch (e: SecurityException) {
|
||||
Log.e(TAG, "Failed to start AmneziaVpnService: $e")
|
||||
}
|
||||
vpnProto?.let { proto ->
|
||||
try {
|
||||
ContextCompat.startForegroundService(
|
||||
applicationContext,
|
||||
Intent(this, proto.serviceClass)
|
||||
)
|
||||
} catch (e: SecurityException) {
|
||||
Log.e(TAG, "Failed to start ${proto.serviceClass.simpleName}: $e")
|
||||
}
|
||||
} ?: Log.e(TAG, "Failed to start vpn service: vpnProto is null")
|
||||
}
|
||||
|
||||
private fun connectToVpn() = vpnServiceMessenger.send(Action.CONNECT)
|
||||
|
|
@ -220,11 +233,8 @@ class AmneziaTileService : TileService() {
|
|||
}
|
||||
}
|
||||
|
||||
private fun updateVpnState(state: ProtocolState) {
|
||||
scope.launch {
|
||||
VpnStateStore.store { it.copy(protocolState = state) }
|
||||
}
|
||||
}
|
||||
private fun updateVpnState(state: ProtocolState) =
|
||||
scope.launch { VpnStateStore.store { it.copy(protocolState = state) } }
|
||||
|
||||
private fun launchVpnStateListening() =
|
||||
scope.launch { VpnStateStore.dataFlow().collectLatest(::updateTile) }
|
||||
|
|
@ -232,9 +242,10 @@ class AmneziaTileService : TileService() {
|
|||
private fun updateTile(vpnState: VpnState) {
|
||||
Log.d(TAG, "Update tile: $vpnState")
|
||||
isVpnConfigExists = vpnState.serverName != null
|
||||
vpnProto = vpnState.vpnProto
|
||||
val tile = qsTile ?: return
|
||||
tile.apply {
|
||||
label = vpnState.serverName ?: DEFAULT_TILE_LABEL
|
||||
label = (vpnState.serverName ?: DEFAULT_TILE_LABEL) + (vpnProto?.let { " ${it.label}" } ?: "")
|
||||
when (val protocolState = vpnState.protocolState) {
|
||||
CONNECTED -> {
|
||||
state = Tile.STATE_ACTIVE
|
||||
|
|
|
|||
|
|
@ -1,5 +1,6 @@
|
|||
package org.amnezia.vpn
|
||||
|
||||
import android.annotation.SuppressLint
|
||||
import android.app.ActivityManager
|
||||
import android.app.ActivityManager.RunningAppProcessInfo.IMPORTANCE_FOREGROUND_SERVICE
|
||||
import android.app.NotificationManager
|
||||
|
|
@ -21,6 +22,7 @@ import androidx.annotation.MainThread
|
|||
import androidx.core.app.ServiceCompat
|
||||
import androidx.core.content.ContextCompat
|
||||
import androidx.core.content.getSystemService
|
||||
import java.net.UnknownHostException
|
||||
import java.util.concurrent.ConcurrentHashMap
|
||||
import kotlin.LazyThreadSafetyMode.NONE
|
||||
import kotlinx.coroutines.CoroutineExceptionHandler
|
||||
|
|
@ -30,6 +32,7 @@ import kotlinx.coroutines.Job
|
|||
import kotlinx.coroutines.SupervisorJob
|
||||
import kotlinx.coroutines.TimeoutCancellationException
|
||||
import kotlinx.coroutines.cancel
|
||||
import kotlinx.coroutines.cancelAndJoin
|
||||
import kotlinx.coroutines.delay
|
||||
import kotlinx.coroutines.flow.MutableStateFlow
|
||||
import kotlinx.coroutines.flow.drop
|
||||
|
|
@ -38,8 +41,6 @@ import kotlinx.coroutines.launch
|
|||
import kotlinx.coroutines.runBlocking
|
||||
import kotlinx.coroutines.withTimeout
|
||||
import org.amnezia.vpn.protocol.BadConfigException
|
||||
import org.amnezia.vpn.protocol.LoadLibraryException
|
||||
import org.amnezia.vpn.protocol.Protocol
|
||||
import org.amnezia.vpn.protocol.ProtocolState.CONNECTED
|
||||
import org.amnezia.vpn.protocol.ProtocolState.CONNECTING
|
||||
import org.amnezia.vpn.protocol.ProtocolState.DISCONNECTED
|
||||
|
|
@ -48,11 +49,8 @@ import org.amnezia.vpn.protocol.ProtocolState.RECONNECTING
|
|||
import org.amnezia.vpn.protocol.ProtocolState.UNKNOWN
|
||||
import org.amnezia.vpn.protocol.VpnException
|
||||
import org.amnezia.vpn.protocol.VpnStartException
|
||||
import org.amnezia.vpn.protocol.awg.Awg
|
||||
import org.amnezia.vpn.protocol.cloak.Cloak
|
||||
import org.amnezia.vpn.protocol.openvpn.OpenVpn
|
||||
import org.amnezia.vpn.protocol.putStatus
|
||||
import org.amnezia.vpn.protocol.wireguard.Wireguard
|
||||
import org.amnezia.vpn.util.LoadLibraryException
|
||||
import org.amnezia.vpn.util.Log
|
||||
import org.amnezia.vpn.util.Prefs
|
||||
import org.amnezia.vpn.util.net.NetworkState
|
||||
|
|
@ -63,6 +61,7 @@ import org.json.JSONObject
|
|||
private const val TAG = "AmneziaVpnService"
|
||||
|
||||
const val ACTION_DISCONNECT = "org.amnezia.vpn.action.disconnect"
|
||||
const val ACTION_CONNECT = "org.amnezia.vpn.action.connect"
|
||||
|
||||
const val MSG_VPN_CONFIG = "VPN_CONFIG"
|
||||
const val MSG_ERROR = "ERROR"
|
||||
|
|
@ -73,19 +72,18 @@ const val AFTER_PERMISSION_CHECK = "AFTER_PERMISSION_CHECK"
|
|||
private const val PREFS_CONFIG_KEY = "LAST_CONF"
|
||||
private const val PREFS_SERVER_NAME = "LAST_SERVER_NAME"
|
||||
private const val PREFS_SERVER_INDEX = "LAST_SERVER_INDEX"
|
||||
private const val PROCESS_NAME = "org.amnezia.vpn:amneziaVpnService"
|
||||
// private const val STATISTICS_SENDING_TIMEOUT = 1000L
|
||||
private const val TRAFFIC_STATS_UPDATE_TIMEOUT = 1000L
|
||||
private const val DISCONNECT_TIMEOUT = 5000L
|
||||
private const val STOP_SERVICE_TIMEOUT = 5000L
|
||||
|
||||
class AmneziaVpnService : VpnService() {
|
||||
@SuppressLint("Registered")
|
||||
open class AmneziaVpnService : VpnService() {
|
||||
|
||||
private lateinit var mainScope: CoroutineScope
|
||||
private lateinit var connectionScope: CoroutineScope
|
||||
private var isServiceBound = false
|
||||
private var protocol: Protocol? = null
|
||||
private val protocolCache = mutableMapOf<String, Protocol>()
|
||||
private var vpnProto: VpnProto? = null
|
||||
private var protocolState = MutableStateFlow(UNKNOWN)
|
||||
private var serverName: String? = null
|
||||
private var serverIndex: Int = -1
|
||||
|
|
@ -105,7 +103,7 @@ class AmneziaVpnService : VpnService() {
|
|||
// private var statisticsSendingJob: Job? = null
|
||||
private lateinit var networkState: NetworkState
|
||||
private lateinit var trafficStats: TrafficStats
|
||||
private var disconnectReceiver: BroadcastReceiver? = null
|
||||
private var controlReceiver: BroadcastReceiver? = null
|
||||
private var notificationStateReceiver: BroadcastReceiver? = null
|
||||
private var screenOnReceiver: BroadcastReceiver? = null
|
||||
private var screenOffReceiver: BroadcastReceiver? = null
|
||||
|
|
@ -115,8 +113,11 @@ class AmneziaVpnService : VpnService() {
|
|||
get() = clientMessengers.any { it.value.name == ACTIVITY_MESSENGER_NAME }
|
||||
|
||||
private val connectionExceptionHandler = CoroutineExceptionHandler { _, e ->
|
||||
connectionJob?.cancel()
|
||||
connectionJob = null
|
||||
disconnectionJob?.cancel()
|
||||
disconnectionJob = null
|
||||
protocolState.value = DISCONNECTED
|
||||
protocol = null
|
||||
when (e) {
|
||||
is IllegalArgumentException,
|
||||
is VpnStartException,
|
||||
|
|
@ -127,6 +128,8 @@ class AmneziaVpnService : VpnService() {
|
|||
|
||||
is LoadLibraryException -> onError("${e.message}. Caused: ${e.cause?.message}")
|
||||
|
||||
is UnknownHostException -> onError("Unknown host")
|
||||
|
||||
else -> throw e
|
||||
}
|
||||
}
|
||||
|
|
@ -227,7 +230,8 @@ class AmneziaVpnService : VpnService() {
|
|||
connect(intent?.getStringExtra(MSG_VPN_CONFIG))
|
||||
}
|
||||
ServiceCompat.startForeground(
|
||||
this, NOTIFICATION_ID, serviceNotification.buildNotification(serverName, protocolState.value),
|
||||
this, NOTIFICATION_ID,
|
||||
serviceNotification.buildNotification(serverName, vpnProto?.label, protocolState.value),
|
||||
foregroundServiceTypeCompat
|
||||
)
|
||||
return START_REDELIVER_INTENT
|
||||
|
|
@ -292,9 +296,17 @@ class AmneziaVpnService : VpnService() {
|
|||
|
||||
private fun registerBroadcastReceivers() {
|
||||
Log.d(TAG, "Register broadcast receivers")
|
||||
disconnectReceiver = registerBroadcastReceiver(ACTION_DISCONNECT, ContextCompat.RECEIVER_NOT_EXPORTED) {
|
||||
Log.d(TAG, "Broadcast request received: $ACTION_DISCONNECT")
|
||||
disconnect()
|
||||
controlReceiver = registerBroadcastReceiver(
|
||||
arrayOf(ACTION_CONNECT, ACTION_DISCONNECT), ContextCompat.RECEIVER_NOT_EXPORTED
|
||||
) {
|
||||
it?.action?.let { action ->
|
||||
Log.v(TAG, "Broadcast request received: $action")
|
||||
when (action) {
|
||||
ACTION_CONNECT -> connect()
|
||||
ACTION_DISCONNECT -> disconnect()
|
||||
else -> Log.w(TAG, "Unknown action received: $action")
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
notificationStateReceiver = if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.P) {
|
||||
|
|
@ -305,7 +317,7 @@ class AmneziaVpnService : VpnService() {
|
|||
)
|
||||
) {
|
||||
val state = it?.getBooleanExtra(NotificationManager.EXTRA_BLOCKED_STATE, false)
|
||||
Log.d(TAG, "Notification state changed: ${it?.action}, blocked = $state")
|
||||
Log.v(TAG, "Notification state changed: ${it?.action}, blocked = $state")
|
||||
if (state == false) {
|
||||
enableNotification()
|
||||
} else {
|
||||
|
|
@ -340,10 +352,10 @@ class AmneziaVpnService : VpnService() {
|
|||
|
||||
private fun unregisterBroadcastReceivers() {
|
||||
Log.d(TAG, "Unregister broadcast receivers")
|
||||
unregisterBroadcastReceiver(disconnectReceiver)
|
||||
unregisterBroadcastReceiver(controlReceiver)
|
||||
unregisterBroadcastReceiver(notificationStateReceiver)
|
||||
unregisterScreenStateBroadcastReceivers()
|
||||
disconnectReceiver = null
|
||||
controlReceiver = null
|
||||
notificationStateReceiver = null
|
||||
}
|
||||
|
||||
|
|
@ -356,7 +368,7 @@ class AmneziaVpnService : VpnService() {
|
|||
protocolState.drop(1).collect { protocolState ->
|
||||
Log.d(TAG, "Protocol state changed: $protocolState")
|
||||
|
||||
serviceNotification.updateNotification(serverName, protocolState)
|
||||
serviceNotification.updateNotification(serverName, vpnProto?.label, protocolState)
|
||||
|
||||
clientMessengers.send {
|
||||
ServiceEvent.STATUS_CHANGED.packToMessage {
|
||||
|
|
@ -364,7 +376,7 @@ class AmneziaVpnService : VpnService() {
|
|||
}
|
||||
}
|
||||
|
||||
VpnStateStore.store { VpnState(protocolState, serverName, serverIndex) }
|
||||
VpnStateStore.store { VpnState(protocolState, serverName, serverIndex, vpnProto) }
|
||||
|
||||
when (protocolState) {
|
||||
CONNECTED -> {
|
||||
|
|
@ -421,7 +433,7 @@ class AmneziaVpnService : VpnService() {
|
|||
@MainThread
|
||||
private fun enableNotification() {
|
||||
registerScreenStateBroadcastReceivers()
|
||||
serviceNotification.updateNotification(serverName, protocolState.value)
|
||||
serviceNotification.updateNotification(serverName, vpnProto?.label, protocolState.value)
|
||||
launchTrafficStatsUpdate()
|
||||
}
|
||||
|
||||
|
|
@ -438,7 +450,7 @@ class AmneziaVpnService : VpnService() {
|
|||
serviceNotification.isNotificationEnabled() &&
|
||||
getSystemService<PowerManager>()?.isInteractive != false
|
||||
) {
|
||||
Log.d(TAG, "Launch traffic stats update")
|
||||
Log.v(TAG, "Launch traffic stats update")
|
||||
trafficStats.reset()
|
||||
startTrafficStatsUpdateJob()
|
||||
}
|
||||
|
|
@ -484,8 +496,6 @@ class AmneziaVpnService : VpnService() {
|
|||
|
||||
Log.d(TAG, "Start VPN connection")
|
||||
|
||||
protocolState.value = CONNECTING
|
||||
|
||||
val config = parseConfigToJson(vpnConfig)
|
||||
saveServerData(config)
|
||||
if (config == null) {
|
||||
|
|
@ -494,6 +504,16 @@ class AmneziaVpnService : VpnService() {
|
|||
return
|
||||
}
|
||||
|
||||
try {
|
||||
vpnProto = VpnProto.get(config.getString("protocol"))
|
||||
} catch (e: Exception) {
|
||||
onError("Invalid VPN config: ${e.message}")
|
||||
protocolState.value = DISCONNECTED
|
||||
return
|
||||
}
|
||||
|
||||
protocolState.value = CONNECTING
|
||||
|
||||
if (!checkPermission()) {
|
||||
protocolState.value = DISCONNECTED
|
||||
return
|
||||
|
|
@ -503,8 +523,10 @@ class AmneziaVpnService : VpnService() {
|
|||
disconnectionJob?.join()
|
||||
disconnectionJob = null
|
||||
|
||||
protocol = getProtocol(config.getString("protocol"))
|
||||
protocol?.startVpn(config, Builder(), ::protect)
|
||||
vpnProto?.protocol?.let { protocol ->
|
||||
protocol.initialize(applicationContext, protocolState, ::onError)
|
||||
protocol.startVpn(config, Builder(), ::protect)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
|
|
@ -517,11 +539,11 @@ class AmneziaVpnService : VpnService() {
|
|||
protocolState.value = DISCONNECTING
|
||||
|
||||
disconnectionJob = connectionScope.launch {
|
||||
connectionJob?.join()
|
||||
connectionJob?.cancelAndJoin()
|
||||
connectionJob = null
|
||||
|
||||
protocol?.stopVpn()
|
||||
protocol = null
|
||||
vpnProto?.protocol?.stopVpn()
|
||||
|
||||
try {
|
||||
withTimeout(DISCONNECT_TIMEOUT) {
|
||||
// waiting for disconnect state
|
||||
|
|
@ -543,22 +565,10 @@ class AmneziaVpnService : VpnService() {
|
|||
protocolState.value = RECONNECTING
|
||||
|
||||
connectionJob = connectionScope.launch {
|
||||
protocol?.reconnectVpn(Builder())
|
||||
vpnProto?.protocol?.reconnectVpn(Builder())
|
||||
}
|
||||
}
|
||||
|
||||
@MainThread
|
||||
private fun getProtocol(protocolName: String): Protocol =
|
||||
protocolCache[protocolName]
|
||||
?: when (protocolName) {
|
||||
"wireguard" -> Wireguard()
|
||||
"awg" -> Awg()
|
||||
"openvpn" -> OpenVpn()
|
||||
"cloak" -> Cloak()
|
||||
else -> throw IllegalArgumentException("Protocol '$protocolName' not found")
|
||||
}.apply { initialize(applicationContext, protocolState, ::onError) }
|
||||
.also { protocolCache[protocolName] = it }
|
||||
|
||||
/**
|
||||
* Utils methods
|
||||
*/
|
||||
|
|
@ -603,6 +613,7 @@ class AmneziaVpnService : VpnService() {
|
|||
if (prepare(applicationContext) != null) {
|
||||
Intent(this, VpnRequestActivity::class.java).apply {
|
||||
addFlags(Intent.FLAG_ACTIVITY_NEW_TASK)
|
||||
putExtra(EXTRA_PROTOCOL, vpnProto)
|
||||
}.also {
|
||||
startActivity(it)
|
||||
}
|
||||
|
|
@ -612,9 +623,9 @@ class AmneziaVpnService : VpnService() {
|
|||
}
|
||||
|
||||
companion object {
|
||||
fun isRunning(context: Context): Boolean =
|
||||
fun isRunning(context: Context, processName: String): Boolean =
|
||||
context.getSystemService<ActivityManager>()!!.runningAppProcesses.any {
|
||||
it.processName == PROCESS_NAME && it.importance <= IMPORTANCE_FOREGROUND_SERVICE
|
||||
it.processName == processName && it.importance <= IMPORTANCE_FOREGROUND_SERVICE
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
|
|||
97
client/android/src/org/amnezia/vpn/AuthActivity.kt
Normal file
|
|
@ -0,0 +1,97 @@
|
|||
package org.amnezia.vpn
|
||||
|
||||
import android.os.Build
|
||||
import android.os.Bundle
|
||||
import androidx.biometric.BiometricManager
|
||||
import androidx.biometric.BiometricManager.Authenticators.BIOMETRIC_STRONG
|
||||
import androidx.biometric.BiometricManager.Authenticators.DEVICE_CREDENTIAL
|
||||
import androidx.biometric.BiometricPrompt
|
||||
import androidx.biometric.BiometricPrompt.AuthenticationResult
|
||||
import androidx.core.content.ContextCompat
|
||||
import androidx.fragment.app.FragmentActivity
|
||||
import org.amnezia.vpn.qt.QtAndroidController
|
||||
import org.amnezia.vpn.util.Log
|
||||
|
||||
private const val TAG = "AuthActivity"
|
||||
|
||||
private const val AUTHENTICATORS = BIOMETRIC_STRONG or DEVICE_CREDENTIAL
|
||||
|
||||
class AuthActivity : FragmentActivity() {
|
||||
|
||||
override fun onCreate(savedInstanceState: Bundle?) {
|
||||
super.onCreate(savedInstanceState)
|
||||
|
||||
val biometricManager = BiometricManager.from(applicationContext)
|
||||
when (biometricManager.canAuthenticate(AUTHENTICATORS)) {
|
||||
BiometricManager.BIOMETRIC_SUCCESS -> {
|
||||
showBiometricPrompt(biometricManager)
|
||||
return
|
||||
}
|
||||
|
||||
BiometricManager.BIOMETRIC_STATUS_UNKNOWN -> {
|
||||
Log.w(TAG, "Unknown biometric status")
|
||||
showBiometricPrompt(biometricManager)
|
||||
return
|
||||
}
|
||||
|
||||
BiometricManager.BIOMETRIC_ERROR_UNSUPPORTED -> {
|
||||
Log.e(TAG, "The specified options are incompatible with the current Android " +
|
||||
"version ${Build.VERSION.SDK_INT}")
|
||||
}
|
||||
|
||||
BiometricManager.BIOMETRIC_ERROR_HW_UNAVAILABLE -> {
|
||||
Log.w(TAG, "The hardware is unavailable")
|
||||
}
|
||||
|
||||
BiometricManager.BIOMETRIC_ERROR_NONE_ENROLLED -> {
|
||||
Log.w(TAG, "No biometric or device credential is enrolled")
|
||||
}
|
||||
|
||||
BiometricManager.BIOMETRIC_ERROR_NO_HARDWARE -> {
|
||||
Log.w(TAG, "There is no suitable hardware")
|
||||
}
|
||||
|
||||
BiometricManager.BIOMETRIC_ERROR_SECURITY_UPDATE_REQUIRED -> {
|
||||
Log.w(TAG, "A security vulnerability has been discovered with one or " +
|
||||
"more hardware sensors")
|
||||
}
|
||||
}
|
||||
QtAndroidController.onAuthResult(true)
|
||||
finish()
|
||||
}
|
||||
|
||||
private fun showBiometricPrompt(biometricManager: BiometricManager) {
|
||||
val executor = ContextCompat.getMainExecutor(applicationContext)
|
||||
val biometricPrompt = BiometricPrompt(this, executor,
|
||||
object : BiometricPrompt.AuthenticationCallback() {
|
||||
override fun onAuthenticationSucceeded(result: AuthenticationResult) {
|
||||
super.onAuthenticationSucceeded(result)
|
||||
Log.v(TAG, "Authentication succeeded")
|
||||
QtAndroidController.onAuthResult(true)
|
||||
finish()
|
||||
}
|
||||
|
||||
override fun onAuthenticationFailed() {
|
||||
super.onAuthenticationFailed()
|
||||
Log.w(TAG, "Authentication failed")
|
||||
}
|
||||
|
||||
override fun onAuthenticationError(errorCode: Int, errString: CharSequence) {
|
||||
super.onAuthenticationError(errorCode, errString)
|
||||
Log.e(TAG, "Authentication error $errorCode: $errString")
|
||||
QtAndroidController.onAuthResult(false)
|
||||
finish()
|
||||
}
|
||||
})
|
||||
|
||||
|
||||
|
||||
val promptInfo = BiometricPrompt.PromptInfo.Builder()
|
||||
.setAllowedAuthenticators(AUTHENTICATORS)
|
||||
.setTitle("AmneziaVPN")
|
||||
.setSubtitle(biometricManager.getStrings(AUTHENTICATORS)?.promptMessage)
|
||||
.build()
|
||||
|
||||
biometricPrompt.authenticate(promptInfo)
|
||||
}
|
||||
}
|
||||
|
|
@ -1,24 +0,0 @@
|
|||
package org.amnezia.vpn;
|
||||
|
||||
import android.content.Context;
|
||||
import android.app.KeyguardManager;
|
||||
import android.content.Intent;
|
||||
import org.qtproject.qt.android.bindings.QtActivity;
|
||||
|
||||
|
||||
import static android.content.Context.KEYGUARD_SERVICE;
|
||||
|
||||
public class AuthHelper extends QtActivity {
|
||||
|
||||
static final String TAG = "AuthHelper";
|
||||
|
||||
public static Intent getAuthIntent(Context context) {
|
||||
KeyguardManager mKeyguardManager = (KeyguardManager)context.getSystemService(KEYGUARD_SERVICE);
|
||||
if (mKeyguardManager.isDeviceSecure()) {
|
||||
return mKeyguardManager.createConfirmDeviceCredentialIntent(null, null);
|
||||
} else {
|
||||
return null;
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
3
client/android/src/org/amnezia/vpn/AwgService.kt
Normal file
|
|
@ -0,0 +1,3 @@
|
|||
package org.amnezia.vpn
|
||||
|
||||
class AwgService : AmneziaVpnService()
|
||||
|
|
@ -140,7 +140,7 @@ class CameraActivity : ComponentActivity() {
|
|||
}
|
||||
}
|
||||
}.addOnFailureListener {
|
||||
Log.e(TAG, "Processing QR-code image failed: ${it.message}")
|
||||
Log.e(TAG, "Processing QR code image failed: ${it.message}")
|
||||
}.addOnCompleteListener {
|
||||
imageProxy.close()
|
||||
}
|
||||
|
|
|
|||
|
|
@ -29,20 +29,20 @@ class ImportConfigActivity : ComponentActivity() {
|
|||
|
||||
override fun onCreate(savedInstanceState: Bundle?) {
|
||||
super.onCreate(savedInstanceState)
|
||||
Log.d(TAG, "Create Import Config Activity: $intent")
|
||||
Log.v(TAG, "Create Import Config Activity: $intent")
|
||||
intent?.let(::readConfig)
|
||||
}
|
||||
|
||||
override fun onNewIntent(intent: Intent?) {
|
||||
override fun onNewIntent(intent: Intent) {
|
||||
super.onNewIntent(intent)
|
||||
Log.d(TAG, "onNewIntent: $intent")
|
||||
intent?.let(::readConfig)
|
||||
Log.v(TAG, "onNewIntent: $intent")
|
||||
intent.let(::readConfig)
|
||||
}
|
||||
|
||||
private fun readConfig(intent: Intent) {
|
||||
when (intent.action) {
|
||||
ACTION_SEND -> {
|
||||
Log.d(TAG, "Process SEND action, type: ${intent.type}")
|
||||
Log.v(TAG, "Process SEND action, type: ${intent.type}")
|
||||
when (intent.type) {
|
||||
"application/octet-stream" -> {
|
||||
intent.getUriCompat()?.let { uri ->
|
||||
|
|
@ -60,7 +60,7 @@ class ImportConfigActivity : ComponentActivity() {
|
|||
}
|
||||
|
||||
ACTION_VIEW -> {
|
||||
Log.d(TAG, "Process VIEW action, scheme: ${intent.scheme}")
|
||||
Log.v(TAG, "Process VIEW action, scheme: ${intent.scheme}")
|
||||
when (intent.scheme) {
|
||||
"file", "content" -> {
|
||||
intent.data?.let { uri ->
|
||||
|
|
|
|||
3
client/android/src/org/amnezia/vpn/OpenVpnService.kt
Normal file
|
|
@ -0,0 +1,3 @@
|
|||
package org.amnezia.vpn
|
||||
|
||||
class OpenVpnService : AmneziaVpnService()
|
||||
|
|
@ -59,14 +59,14 @@ class ServiceNotification(private val context: Context) {
|
|||
formatSpeedString(rxString, txString)
|
||||
}
|
||||
|
||||
fun buildNotification(serverName: String?, state: ProtocolState): Notification {
|
||||
fun buildNotification(serverName: String?, protocol: String?, state: ProtocolState): Notification {
|
||||
val speedString = if (state == CONNECTED) zeroSpeed else null
|
||||
|
||||
Log.d(TAG, "Build notification: $serverName, $state")
|
||||
Log.v(TAG, "Build notification: $serverName, $state")
|
||||
|
||||
return notificationBuilder
|
||||
.setSmallIcon(R.drawable.ic_amnezia_round)
|
||||
.setContentTitle(serverName ?: "AmneziaVPN")
|
||||
.setContentTitle((serverName ?: "AmneziaVPN") + (protocol?.let { " $it" } ?: ""))
|
||||
.setContentText(context.getString(state))
|
||||
.setSubText(speedString)
|
||||
.setWhen(System.currentTimeMillis())
|
||||
|
|
@ -88,18 +88,16 @@ class ServiceNotification(private val context: Context) {
|
|||
fun isNotificationEnabled(): Boolean {
|
||||
if (!context.isNotificationPermissionGranted()) return false
|
||||
if (!notificationManager.areNotificationsEnabled()) return false
|
||||
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.O) {
|
||||
return notificationManager.getNotificationChannel(NOTIFICATION_CHANNEL_ID)
|
||||
?.let { it.importance != NotificationManager.IMPORTANCE_NONE } ?: true
|
||||
}
|
||||
return true
|
||||
return notificationManager.getNotificationChannel(NOTIFICATION_CHANNEL_ID)?.let {
|
||||
it.importance != NotificationManager.IMPORTANCE_NONE
|
||||
} ?: true
|
||||
}
|
||||
|
||||
@SuppressLint("MissingPermission")
|
||||
fun updateNotification(serverName: String?, state: ProtocolState) {
|
||||
fun updateNotification(serverName: String?, protocol: String?, state: ProtocolState) {
|
||||
if (context.isNotificationPermissionGranted()) {
|
||||
Log.d(TAG, "Update notification: $serverName, $state")
|
||||
notificationManager.notify(NOTIFICATION_ID, buildNotification(serverName, state))
|
||||
Log.v(TAG, "Update notification: $serverName, $state")
|
||||
notificationManager.notify(NOTIFICATION_ID, buildNotification(serverName, protocol, state))
|
||||
}
|
||||
}
|
||||
|
||||
|
|
@ -125,7 +123,7 @@ class ServiceNotification(private val context: Context) {
|
|||
context,
|
||||
DISCONNECT_REQUEST_CODE,
|
||||
Intent(ACTION_DISCONNECT).apply {
|
||||
setPackage("org.amnezia.vpn")
|
||||
setPackage(context.packageName)
|
||||
},
|
||||
PendingIntent.FLAG_IMMUTABLE or PendingIntent.FLAG_UPDATE_CURRENT
|
||||
)
|
||||
|
|
@ -135,10 +133,12 @@ class ServiceNotification(private val context: Context) {
|
|||
DISCONNECTED -> {
|
||||
Action(
|
||||
0, context.getString(R.string.connect),
|
||||
createServicePendingIntent(
|
||||
PendingIntent.getBroadcast(
|
||||
context,
|
||||
CONNECT_REQUEST_CODE,
|
||||
Intent(context, AmneziaVpnService::class.java),
|
||||
Intent(ACTION_CONNECT).apply {
|
||||
setPackage(context.packageName)
|
||||
},
|
||||
PendingIntent.FLAG_IMMUTABLE or PendingIntent.FLAG_UPDATE_CURRENT
|
||||
)
|
||||
)
|
||||
|
|
@ -148,13 +148,6 @@ class ServiceNotification(private val context: Context) {
|
|||
}
|
||||
}
|
||||
|
||||
private val createServicePendingIntent: (Context, Int, Intent, Int) -> PendingIntent =
|
||||
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.O) {
|
||||
PendingIntent::getForegroundService
|
||||
} else {
|
||||
PendingIntent::getService
|
||||
}
|
||||
|
||||
companion object {
|
||||
fun createNotificationChannel(context: Context) {
|
||||
with(NotificationManagerCompat.from(context)) {
|
||||
|
|
|
|||
75
client/android/src/org/amnezia/vpn/VpnProto.kt
Normal file
|
|
@ -0,0 +1,75 @@
|
|||
package org.amnezia.vpn
|
||||
|
||||
import org.amnezia.vpn.protocol.Protocol
|
||||
import org.amnezia.vpn.protocol.awg.Awg
|
||||
import org.amnezia.vpn.protocol.cloak.Cloak
|
||||
import org.amnezia.vpn.protocol.openvpn.OpenVpn
|
||||
import org.amnezia.vpn.protocol.wireguard.Wireguard
|
||||
import org.amnezia.vpn.protocol.xray.Xray
|
||||
|
||||
enum class VpnProto(
|
||||
val label: String,
|
||||
val processName: String,
|
||||
val serviceClass: Class<out AmneziaVpnService>
|
||||
) {
|
||||
WIREGUARD(
|
||||
"WireGuard",
|
||||
"org.amnezia.vpn:amneziaAwgService",
|
||||
AwgService::class.java
|
||||
) {
|
||||
override fun createProtocol(): Protocol = Wireguard()
|
||||
},
|
||||
|
||||
AWG(
|
||||
"AmneziaWG",
|
||||
"org.amnezia.vpn:amneziaAwgService",
|
||||
AwgService::class.java
|
||||
) {
|
||||
override fun createProtocol(): Protocol = Awg()
|
||||
},
|
||||
|
||||
OPENVPN(
|
||||
"OpenVPN",
|
||||
"org.amnezia.vpn:amneziaOpenVpnService",
|
||||
OpenVpnService::class.java
|
||||
) {
|
||||
override fun createProtocol(): Protocol = OpenVpn()
|
||||
},
|
||||
|
||||
CLOAK(
|
||||
"Cloak",
|
||||
"org.amnezia.vpn:amneziaOpenVpnService",
|
||||
OpenVpnService::class.java
|
||||
) {
|
||||
override fun createProtocol(): Protocol = Cloak()
|
||||
},
|
||||
|
||||
XRAY(
|
||||
"XRay",
|
||||
"org.amnezia.vpn:amneziaXrayService",
|
||||
XrayService::class.java
|
||||
) {
|
||||
override fun createProtocol(): Protocol = Xray.instance
|
||||
},
|
||||
|
||||
SSXRAY(
|
||||
"SSXRay",
|
||||
"org.amnezia.vpn:amneziaXrayService",
|
||||
XrayService::class.java
|
||||
) {
|
||||
override fun createProtocol(): Protocol = Xray.instance
|
||||
};
|
||||
|
||||
private var _protocol: Protocol? = null
|
||||
val protocol: Protocol
|
||||
get() {
|
||||
if (_protocol == null) _protocol = createProtocol()
|
||||
return _protocol ?: throw AssertionError("Set to null by another thread")
|
||||
}
|
||||
|
||||
protected abstract fun createProtocol(): Protocol
|
||||
|
||||
companion object {
|
||||
fun get(protocolName: String): VpnProto = VpnProto.valueOf(protocolName.uppercase())
|
||||
}
|
||||
}
|
||||
|
|
@ -7,6 +7,7 @@ import android.content.Intent
|
|||
import android.content.res.Configuration.UI_MODE_NIGHT_MASK
|
||||
import android.content.res.Configuration.UI_MODE_NIGHT_YES
|
||||
import android.net.VpnService
|
||||
import android.os.Build
|
||||
import android.os.Bundle
|
||||
import android.provider.Settings
|
||||
import android.widget.Toast
|
||||
|
|
@ -18,9 +19,11 @@ import androidx.core.content.getSystemService
|
|||
import org.amnezia.vpn.util.Log
|
||||
|
||||
private const val TAG = "VpnRequestActivity"
|
||||
const val EXTRA_PROTOCOL = "PROTOCOL"
|
||||
|
||||
class VpnRequestActivity : ComponentActivity() {
|
||||
|
||||
private var vpnProto: VpnProto? = null
|
||||
private var userPresentReceiver: BroadcastReceiver? = null
|
||||
private val requestLauncher =
|
||||
registerForActivityResult(StartActivityForResult(), ::checkRequestResult)
|
||||
|
|
@ -28,6 +31,12 @@ class VpnRequestActivity : ComponentActivity() {
|
|||
override fun onCreate(savedInstanceState: Bundle?) {
|
||||
super.onCreate(savedInstanceState)
|
||||
Log.d(TAG, "Start request activity")
|
||||
vpnProto = if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.TIRAMISU) {
|
||||
intent.extras?.getSerializable(EXTRA_PROTOCOL, VpnProto::class.java)
|
||||
} else {
|
||||
@Suppress("DEPRECATION")
|
||||
intent.extras?.getSerializable(EXTRA_PROTOCOL) as VpnProto
|
||||
}
|
||||
val requestIntent = VpnService.prepare(applicationContext)
|
||||
if (requestIntent != null) {
|
||||
if (getSystemService<KeyguardManager>()!!.isKeyguardLocked) {
|
||||
|
|
@ -66,10 +75,18 @@ class VpnRequestActivity : ComponentActivity() {
|
|||
|
||||
private fun onPermissionGranted() {
|
||||
Toast.makeText(this, resources.getString(R.string.vpnGranted), Toast.LENGTH_LONG).show()
|
||||
Intent(applicationContext, AmneziaVpnService::class.java).apply {
|
||||
putExtra(AFTER_PERMISSION_CHECK, true)
|
||||
}.also {
|
||||
ContextCompat.startForegroundService(this, it)
|
||||
vpnProto?.let { proto ->
|
||||
Intent(applicationContext, proto.serviceClass).apply {
|
||||
putExtra(AFTER_PERMISSION_CHECK, true)
|
||||
}.also {
|
||||
ContextCompat.startForegroundService(this, it)
|
||||
}
|
||||
} ?: run {
|
||||
Intent(this, AmneziaActivity::class.java).apply {
|
||||
addFlags(Intent.FLAG_ACTIVITY_NEW_TASK)
|
||||
}.also {
|
||||
startActivity(it)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
|
|
|
|||
|
|
@ -1,19 +1,22 @@
|
|||
package org.amnezia.vpn
|
||||
|
||||
import android.app.Application
|
||||
import androidx.datastore.core.CorruptionException
|
||||
import androidx.datastore.core.MultiProcessDataStoreFactory
|
||||
import androidx.datastore.core.Serializer
|
||||
import androidx.datastore.core.handlers.ReplaceFileCorruptionHandler
|
||||
import androidx.datastore.dataStoreFile
|
||||
import java.io.ByteArrayInputStream
|
||||
import java.io.ByteArrayOutputStream
|
||||
import java.io.InputStream
|
||||
import java.io.ObjectInputStream
|
||||
import java.io.ObjectOutputStream
|
||||
import java.io.OutputStream
|
||||
import java.io.Serializable
|
||||
import kotlinx.coroutines.Dispatchers
|
||||
import kotlinx.coroutines.flow.Flow
|
||||
import kotlinx.coroutines.withContext
|
||||
import kotlinx.coroutines.flow.catch
|
||||
import kotlinx.coroutines.flow.firstOrNull
|
||||
import kotlinx.serialization.ExperimentalSerializationApi
|
||||
import kotlinx.serialization.Serializable
|
||||
import kotlinx.serialization.SerializationException
|
||||
import kotlinx.serialization.decodeFromByteArray
|
||||
import kotlinx.serialization.encodeToByteArray
|
||||
import kotlinx.serialization.protobuf.ProtoBuf
|
||||
import org.amnezia.vpn.protocol.ProtocolState
|
||||
import org.amnezia.vpn.protocol.ProtocolState.DISCONNECTED
|
||||
import org.amnezia.vpn.util.Log
|
||||
|
|
@ -21,13 +24,14 @@ import org.amnezia.vpn.util.Log
|
|||
private const val TAG = "VpnState"
|
||||
private const val STORE_FILE_NAME = "vpnState"
|
||||
|
||||
@Serializable
|
||||
data class VpnState(
|
||||
val protocolState: ProtocolState,
|
||||
val serverName: String? = null,
|
||||
val serverIndex: Int = -1
|
||||
) : Serializable {
|
||||
val serverIndex: Int = -1,
|
||||
val vpnProto: VpnProto? = null
|
||||
) {
|
||||
companion object {
|
||||
private const val serialVersionUID: Long = -1760654961004181606
|
||||
val defaultState: VpnState = VpnState(DISCONNECTED)
|
||||
}
|
||||
}
|
||||
|
|
@ -37,7 +41,11 @@ object VpnStateStore {
|
|||
|
||||
private val dataStore = MultiProcessDataStoreFactory.create(
|
||||
serializer = VpnStateSerializer(),
|
||||
produceFile = { app.dataStoreFile(STORE_FILE_NAME) }
|
||||
produceFile = { app.dataStoreFile(STORE_FILE_NAME) },
|
||||
corruptionHandler = ReplaceFileCorruptionHandler { e ->
|
||||
Log.e(TAG, "VpnState DataStore corrupted: $e")
|
||||
VpnState.defaultState
|
||||
}
|
||||
)
|
||||
|
||||
fun init(app: Application) {
|
||||
|
|
@ -45,36 +53,36 @@ object VpnStateStore {
|
|||
this.app = app
|
||||
}
|
||||
|
||||
fun dataFlow(): Flow<VpnState> = dataStore.data
|
||||
fun dataFlow(): Flow<VpnState> = dataStore.data.catch { e ->
|
||||
Log.e(TAG, "Failed to read VpnState from store: ${e.message}")
|
||||
emit(VpnState.defaultState)
|
||||
}
|
||||
|
||||
suspend fun getVpnState(): VpnState = dataFlow().firstOrNull() ?: VpnState.defaultState
|
||||
|
||||
suspend fun store(f: (vpnState: VpnState) -> VpnState) {
|
||||
try {
|
||||
dataStore.updateData(f)
|
||||
} catch (e : Exception) {
|
||||
} catch (e: Exception) {
|
||||
Log.e(TAG, "Failed to store VpnState: $e")
|
||||
Log.w(TAG, "Remove DataStore file")
|
||||
app.dataStoreFile(STORE_FILE_NAME).delete()
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@OptIn(ExperimentalSerializationApi::class)
|
||||
private class VpnStateSerializer : Serializer<VpnState> {
|
||||
override val defaultValue: VpnState = VpnState.defaultState
|
||||
|
||||
override suspend fun readFrom(input: InputStream): VpnState {
|
||||
return withContext(Dispatchers.IO) {
|
||||
val bios = ByteArrayInputStream(input.readBytes())
|
||||
ObjectInputStream(bios).use {
|
||||
it.readObject() as VpnState
|
||||
}
|
||||
}
|
||||
override suspend fun readFrom(input: InputStream): VpnState = try {
|
||||
ProtoBuf.decodeFromByteArray<VpnState>(input.readBytes())
|
||||
} catch (e: SerializationException) {
|
||||
Log.e(TAG, "Failed to deserialize data: $e")
|
||||
throw CorruptionException("Failed to deserialize data", e)
|
||||
}
|
||||
|
||||
override suspend fun writeTo(t: VpnState, output: OutputStream) {
|
||||
withContext(Dispatchers.IO) {
|
||||
val baos = ByteArrayOutputStream()
|
||||
ObjectOutputStream(baos).use {
|
||||
it.writeObject(t)
|
||||
}
|
||||
output.write(baos.toByteArray())
|
||||
}
|
||||
}
|
||||
@Suppress("BlockingMethodInNonBlockingContext")
|
||||
override suspend fun writeTo(t: VpnState, output: OutputStream) =
|
||||
output.write(ProtoBuf.encodeToByteArray(t))
|
||||
}
|
||||
|
|
|
|||
3
client/android/src/org/amnezia/vpn/XrayService.kt
Normal file
|
|
@ -0,0 +1,3 @@
|
|||
package org.amnezia.vpn
|
||||
|
||||
class XrayService : AmneziaVpnService()
|
||||
|
|
@ -25,5 +25,7 @@ object QtAndroidController {
|
|||
|
||||
external fun onConfigImported(data: String)
|
||||
|
||||
external fun onAuthResult(result: Boolean)
|
||||
|
||||
external fun decodeQrCode(data: String): Boolean
|
||||
}
|
||||
9
client/android/utils/src/main/kotlin/JsonExt.kt
Normal file
|
|
@ -0,0 +1,9 @@
|
|||
package org.amnezia.vpn.util
|
||||
|
||||
import org.json.JSONArray
|
||||
import org.json.JSONObject
|
||||
|
||||
inline fun <reified T> JSONArray.asSequence(): Sequence<T> =
|
||||
(0..<length()).asSequence().map { get(it) as T }
|
||||
|
||||
fun JSONObject.optStringOrNull(name: String) = optString(name).ifEmpty { null }
|
||||
66
client/android/utils/src/main/kotlin/LibraryLoader.kt
Normal file
|
|
@ -0,0 +1,66 @@
|
|||
package org.amnezia.vpn.util
|
||||
|
||||
import android.annotation.SuppressLint
|
||||
import android.content.Context
|
||||
import android.os.Build
|
||||
import java.io.File
|
||||
import java.io.FileOutputStream
|
||||
import java.util.zip.ZipFile
|
||||
|
||||
private const val TAG = "LibraryLoader"
|
||||
|
||||
object LibraryLoader {
|
||||
private fun extractLibrary(context: Context, libraryName: String, destination: File): Boolean {
|
||||
Log.d(TAG, "Extracting library: $libraryName")
|
||||
val apks = hashSetOf<String>()
|
||||
context.applicationInfo.run {
|
||||
sourceDir?.let { apks += it }
|
||||
splitSourceDirs?.let { apks += it }
|
||||
}
|
||||
for (abi in Build.SUPPORTED_ABIS) {
|
||||
for (apk in apks) {
|
||||
ZipFile(File(apk), ZipFile.OPEN_READ).use { zipFile ->
|
||||
val mappedName = System.mapLibraryName(libraryName)
|
||||
val libraryZipPath = listOf("lib", abi, mappedName).joinToString(File.separator)
|
||||
val zipEntry = zipFile.getEntry(libraryZipPath)
|
||||
zipEntry?.let {
|
||||
Log.d(TAG, "Extracting apk:/$libraryZipPath to ${destination.absolutePath}")
|
||||
FileOutputStream(destination).use { outStream ->
|
||||
zipFile.getInputStream(zipEntry).use { inStream ->
|
||||
inStream.copyTo(outStream, 32 * 1024)
|
||||
outStream.fd.sync()
|
||||
}
|
||||
}
|
||||
}
|
||||
return true
|
||||
}
|
||||
}
|
||||
}
|
||||
return false
|
||||
}
|
||||
|
||||
@SuppressLint("UnsafeDynamicallyLoadedCode")
|
||||
fun loadSharedLibrary(context: Context, libraryName: String) {
|
||||
Log.d(TAG, "Loading library: $libraryName")
|
||||
try {
|
||||
System.loadLibrary(libraryName)
|
||||
return
|
||||
} catch (_: UnsatisfiedLinkError) {
|
||||
Log.w(TAG, "Failed to load library, try to extract it from apk")
|
||||
}
|
||||
var tempFile: File? = null
|
||||
try {
|
||||
tempFile = File.createTempFile("lib", ".so", context.codeCacheDir)
|
||||
if (extractLibrary(context, libraryName, tempFile)) {
|
||||
System.load(tempFile.absolutePath)
|
||||
return
|
||||
}
|
||||
} catch (e: Exception) {
|
||||
throw LoadLibraryException("Failed to load library apk: $libraryName", e)
|
||||
} finally {
|
||||
tempFile?.delete()
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
class LoadLibraryException(message: String? = null, cause: Throwable? = null) : Exception(message, cause)
|
||||
|
|
@ -1,8 +1,6 @@
|
|||
package org.amnezia.vpn.util
|
||||
|
||||
import android.content.Context
|
||||
import android.icu.text.DateFormat
|
||||
import android.icu.text.SimpleDateFormat
|
||||
import android.os.Build
|
||||
import android.os.Process
|
||||
import java.io.File
|
||||
|
|
@ -12,8 +10,6 @@ import java.nio.channels.FileChannel
|
|||
import java.nio.channels.FileLock
|
||||
import java.time.LocalDateTime
|
||||
import java.time.format.DateTimeFormatter
|
||||
import java.util.Date
|
||||
import java.util.Locale
|
||||
import java.util.concurrent.locks.ReentrantLock
|
||||
import org.amnezia.vpn.util.Log.Priority.D
|
||||
import org.amnezia.vpn.util.Log.Priority.E
|
||||
|
|
@ -41,11 +37,7 @@ private const val LOG_MAX_FILE_SIZE = 1024 * 1024
|
|||
* | | | create a report and/or terminate the process |
|
||||
*/
|
||||
object Log {
|
||||
private val dateTimeFormat: Any =
|
||||
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.O) DateTimeFormatter.ofPattern(DATE_TIME_PATTERN)
|
||||
else object : ThreadLocal<DateFormat>() {
|
||||
override fun initialValue(): DateFormat = SimpleDateFormat(DATE_TIME_PATTERN, Locale.US)
|
||||
}
|
||||
private val dateTimeFormat: DateTimeFormatter = DateTimeFormatter.ofPattern(DATE_TIME_PATTERN)
|
||||
|
||||
private lateinit var logDir: File
|
||||
private val logFile: File by lazy { File(logDir, LOG_FILE_NAME) }
|
||||
|
|
@ -143,12 +135,7 @@ object Log {
|
|||
}
|
||||
|
||||
private fun formatLogMsg(tag: String, msg: String, priority: Priority): String {
|
||||
val date = if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.O) {
|
||||
LocalDateTime.now().format(dateTimeFormat as DateTimeFormatter)
|
||||
} else {
|
||||
@Suppress("UNCHECKED_CAST")
|
||||
(dateTimeFormat as ThreadLocal<DateFormat>).get()?.format(Date())
|
||||
}
|
||||
val date = LocalDateTime.now().format(dateTimeFormat)
|
||||
return "$date ${Process.myPid()} ${Process.myTid()} $priority [${Thread.currentThread().name}] " +
|
||||
"$tag: $msg\n"
|
||||
}
|
||||
|
|
|
|||
|
|
@ -42,18 +42,12 @@ class NetworkState(
|
|||
private val networkCallback: NetworkCallback by lazy(NONE) {
|
||||
object : NetworkCallback() {
|
||||
override fun onAvailable(network: Network) {
|
||||
Log.d(TAG, "onAvailable: $network")
|
||||
Log.v(TAG, "onAvailable: $network")
|
||||
}
|
||||
|
||||
override fun onCapabilitiesChanged(network: Network, networkCapabilities: NetworkCapabilities) {
|
||||
Log.d(TAG, "onCapabilitiesChanged: $network, $networkCapabilities")
|
||||
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.O) {
|
||||
checkNetworkState(network, networkCapabilities)
|
||||
} else {
|
||||
handler.post {
|
||||
checkNetworkState(network, networkCapabilities)
|
||||
}
|
||||
}
|
||||
Log.v(TAG, "onCapabilitiesChanged: $network, $networkCapabilities")
|
||||
checkNetworkState(network, networkCapabilities)
|
||||
}
|
||||
|
||||
private fun checkNetworkState(network: Network, networkCapabilities: NetworkCapabilities) {
|
||||
|
|
@ -73,11 +67,11 @@ class NetworkState(
|
|||
}
|
||||
|
||||
override fun onBlockedStatusChanged(network: Network, blocked: Boolean) {
|
||||
Log.d(TAG, "onBlockedStatusChanged: $network, $blocked")
|
||||
Log.v(TAG, "onBlockedStatusChanged: $network, $blocked")
|
||||
}
|
||||
|
||||
override fun onLost(network: Network) {
|
||||
Log.d(TAG, "onLost: $network")
|
||||
Log.v(TAG, "onLost: $network")
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
@ -87,21 +81,27 @@ class NetworkState(
|
|||
Log.d(TAG, "Bind network listener")
|
||||
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.S) {
|
||||
connectivityManager.registerBestMatchingNetworkCallback(networkRequest, networkCallback, handler)
|
||||
} else if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.O) {
|
||||
try {
|
||||
connectivityManager.requestNetwork(networkRequest, networkCallback, handler)
|
||||
} catch (e: SecurityException) {
|
||||
Log.e(TAG, "Failed to bind network listener: $e")
|
||||
// Android 11 bug: https://issuetracker.google.com/issues/175055271
|
||||
if (e.message?.startsWith("Package android does not belong to") == true) {
|
||||
delay(1000)
|
||||
} else {
|
||||
val numberAttempts = 300
|
||||
var attemptCount = 0
|
||||
while(true) {
|
||||
try {
|
||||
connectivityManager.requestNetwork(networkRequest, networkCallback, handler)
|
||||
} else {
|
||||
throw e
|
||||
break
|
||||
} catch (e: SecurityException) {
|
||||
Log.e(TAG, "Failed to bind network listener: $e")
|
||||
// Android 11 bug: https://issuetracker.google.com/issues/175055271
|
||||
if (e.message?.startsWith("Package android does not belong to") == true) {
|
||||
if (++attemptCount > numberAttempts) {
|
||||
throw e
|
||||
}
|
||||
delay(1000)
|
||||
continue
|
||||
} else {
|
||||
throw e
|
||||
}
|
||||
}
|
||||
}
|
||||
} else {
|
||||
connectivityManager.requestNetwork(networkRequest, networkCallback)
|
||||
}
|
||||
isListenerBound = true
|
||||
}
|
||||
|
|
|
|||
|
|
@ -35,7 +35,7 @@ fun getLocalNetworks(context: Context, ipv6: Boolean): List<InetNetwork> {
|
|||
return emptyList()
|
||||
}
|
||||
|
||||
fun parseInetAddress(address: String): InetAddress = parseNumericAddressCompat(address)
|
||||
fun parseInetAddress(address: String): InetAddress = InetAddress.getByName(address)
|
||||
|
||||
private val parseNumericAddressCompat: (String) -> InetAddress =
|
||||
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.Q) {
|
||||
|
|
@ -60,7 +60,7 @@ private val parseNumericAddressCompat: (String) -> InetAddress =
|
|||
internal fun convertIpv6ToCanonicalForm(ipv6: String): String = ipv6
|
||||
.replace("((?:(?:^|:)0+\\b){2,}):?(?!\\S*\\b\\1:0+\\b)(\\S*)".toRegex(), "::$2")
|
||||
|
||||
internal val InetAddress.ip: String
|
||||
val InetAddress.ip: String
|
||||
get() = if (this is Inet4Address) {
|
||||
hostAddress!!
|
||||
} else {
|
||||
|
|
|
|||
|
|
@ -1,57 +1,26 @@
|
|||
package org.amnezia.vpn.protocol.wireguard
|
||||
|
||||
import android.content.Context
|
||||
import android.net.VpnService.Builder
|
||||
import java.util.TreeMap
|
||||
import kotlinx.coroutines.flow.MutableStateFlow
|
||||
import java.io.IOException
|
||||
import java.util.Locale
|
||||
import kotlinx.coroutines.Dispatchers
|
||||
import kotlinx.coroutines.delay
|
||||
import kotlinx.coroutines.withContext
|
||||
import org.amnezia.awg.GoBackend
|
||||
import org.amnezia.vpn.protocol.Protocol
|
||||
import org.amnezia.vpn.protocol.ProtocolState
|
||||
import org.amnezia.vpn.protocol.ProtocolState.CONNECTED
|
||||
import org.amnezia.vpn.protocol.ProtocolState.DISCONNECTED
|
||||
import org.amnezia.vpn.protocol.Statistics
|
||||
import org.amnezia.vpn.protocol.VpnStartException
|
||||
import org.amnezia.vpn.util.LibraryLoader.loadSharedLibrary
|
||||
import org.amnezia.vpn.util.Log
|
||||
import org.amnezia.vpn.util.asSequence
|
||||
import org.amnezia.vpn.util.net.InetEndpoint
|
||||
import org.amnezia.vpn.util.net.InetNetwork
|
||||
import org.amnezia.vpn.util.net.parseInetAddress
|
||||
import org.amnezia.vpn.util.optStringOrNull
|
||||
import org.json.JSONObject
|
||||
|
||||
/**
|
||||
* Config example:
|
||||
* {
|
||||
* "protocol": "wireguard",
|
||||
* "description": "Server 1",
|
||||
* "dns1": "1.1.1.1",
|
||||
* "dns2": "1.0.0.1",
|
||||
* "hostName": "100.100.100.0",
|
||||
* "splitTunnelSites": [
|
||||
* ],
|
||||
* "splitTunnelType": 0,
|
||||
* "wireguard_config_data": {
|
||||
* "client_ip": "10.8.1.1",
|
||||
* "hostName": "100.100.100.0",
|
||||
* "port": 12345,
|
||||
* "client_pub_key": "clientPublicKeyBase64",
|
||||
* "client_priv_key": "privateKeyBase64",
|
||||
* "psk_key": "presharedKeyBase64",
|
||||
* "server_pub_key": "publicKeyBase64",
|
||||
* "config": "[Interface]
|
||||
* Address = 10.8.1.1/32
|
||||
* DNS = 1.1.1.1, 1.0.0.1
|
||||
* PrivateKey = privateKeyBase64
|
||||
*
|
||||
* [Peer]
|
||||
* PublicKey = publicKeyBase64
|
||||
* PresharedKey = presharedKeyBase64
|
||||
* AllowedIPs = 0.0.0.0/0, ::/0
|
||||
* Endpoint = 100.100.100.0:12345
|
||||
* PersistentKeepalive = 25
|
||||
* "
|
||||
* }
|
||||
* }
|
||||
*/
|
||||
|
||||
private const val TAG = "Wireguard"
|
||||
|
||||
open class Wireguard : Protocol() {
|
||||
|
|
@ -78,72 +47,109 @@ open class Wireguard : Protocol() {
|
|||
}
|
||||
}
|
||||
|
||||
override fun initialize(context: Context, state: MutableStateFlow<ProtocolState>, onError: (String) -> Unit) {
|
||||
super.initialize(context, state, onError)
|
||||
loadSharedLibrary(context, "wg-go")
|
||||
override fun internalInit() {
|
||||
if (!isInitialized) loadSharedLibrary(context, "wg-go")
|
||||
}
|
||||
|
||||
override fun startVpn(config: JSONObject, vpnBuilder: Builder, protect: (Int) -> Boolean) {
|
||||
override suspend fun startVpn(config: JSONObject, vpnBuilder: Builder, protect: (Int) -> Boolean) {
|
||||
val wireguardConfig = parseConfig(config)
|
||||
val startTime = System.currentTimeMillis()
|
||||
start(wireguardConfig, vpnBuilder, protect)
|
||||
waitForConnection(startTime)
|
||||
state.value = CONNECTED
|
||||
}
|
||||
|
||||
private suspend fun waitForConnection(startTime: Long) {
|
||||
Log.d(TAG, "Waiting for connection")
|
||||
withContext(Dispatchers.IO) {
|
||||
val time = String.format(Locale.ROOT,"%.3f", startTime / 1000.0)
|
||||
try {
|
||||
delay(1000)
|
||||
var log = getLogcat(time)
|
||||
Log.v(TAG, "First waiting log: $log")
|
||||
// check that there is a connection log,
|
||||
// to avoid infinite connection
|
||||
if (!log.contains("Attaching to interface")) {
|
||||
Log.w(TAG, "Logs do not contain a connection log")
|
||||
return@withContext
|
||||
}
|
||||
while (!log.contains("Received handshake response")) {
|
||||
delay(1000)
|
||||
log = getLogcat(time)
|
||||
}
|
||||
} catch (e: IOException) {
|
||||
Log.e(TAG, "Failed to get logcat: $e")
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private fun getLogcat(time: String): String =
|
||||
ProcessBuilder("logcat", "--buffer=main", "--format=raw", "*:S AmneziaWG/awg0", "-t", time)
|
||||
.redirectErrorStream(true)
|
||||
.start()
|
||||
.inputStream.reader().readText()
|
||||
|
||||
protected open fun parseConfig(config: JSONObject): WireguardConfig {
|
||||
val configDataJson = config.getJSONObject("wireguard_config_data")
|
||||
val configData = parseConfigData(configDataJson.getString("config"))
|
||||
val configData = config.getJSONObject("wireguard_config_data")
|
||||
return WireguardConfig.build {
|
||||
configWireguard(configData, configDataJson)
|
||||
configWireguard(config, configData)
|
||||
configSplitTunneling(config)
|
||||
configAppSplitTunneling(config)
|
||||
}
|
||||
}
|
||||
|
||||
protected fun WireguardConfig.Builder.configWireguard(configData: Map<String, String>, configDataJson: JSONObject) {
|
||||
configData["Address"]?.split(",")?.map { address ->
|
||||
protected fun WireguardConfig.Builder.configWireguard(config: JSONObject, configData: JSONObject) {
|
||||
configData.getString("client_ip").split(",").map { address ->
|
||||
InetNetwork.parse(address.trim())
|
||||
}?.forEach(::addAddress)
|
||||
}.forEach(::addAddress)
|
||||
|
||||
configData["DNS"]?.split(",")?.map { dns ->
|
||||
parseInetAddress(dns.trim())
|
||||
}?.forEach(::addDnsServer)
|
||||
config.optStringOrNull("dns1")?.let { dns ->
|
||||
addDnsServer(parseInetAddress(dns.trim()))
|
||||
}
|
||||
|
||||
config.optStringOrNull("dns2")?.let { dns ->
|
||||
addDnsServer(parseInetAddress(dns.trim()))
|
||||
}
|
||||
|
||||
val defRoutes = hashSetOf(
|
||||
InetNetwork("0.0.0.0", 0),
|
||||
InetNetwork("::", 0)
|
||||
)
|
||||
val routes = hashSetOf<InetNetwork>()
|
||||
configData["AllowedIPs"]?.split(",")?.map { route ->
|
||||
configData.getJSONArray("allowed_ips").asSequence<String>().map { route ->
|
||||
InetNetwork.parse(route.trim())
|
||||
}?.forEach(routes::add)
|
||||
}.forEach(routes::add)
|
||||
// if the allowed IPs list contains at least one non-default route, disable global split tunneling
|
||||
if (routes.any { it !in defRoutes }) disableSplitTunneling()
|
||||
addRoutes(routes)
|
||||
|
||||
configDataJson.optString("mtu").let { mtu ->
|
||||
if (mtu.isNotEmpty()) {
|
||||
setMtu(mtu.toInt())
|
||||
} else {
|
||||
configData["MTU"]?.let { setMtu(it.toInt()) }
|
||||
}
|
||||
configData.optStringOrNull("mtu")?.let { setMtu(it.toInt()) }
|
||||
|
||||
val host = configData.getString("hostName").let { parseInetAddress(it.trim()) }
|
||||
val port = configData.getInt("port")
|
||||
setEndpoint(InetEndpoint(host, port))
|
||||
|
||||
if (configData.optBoolean("isObfuscationEnabled")) {
|
||||
setUseProtocolExtension(true)
|
||||
configExtensionParameters(configData)
|
||||
}
|
||||
|
||||
configData["Endpoint"]?.let { setEndpoint(InetEndpoint.parse(it)) }
|
||||
configData["PersistentKeepalive"]?.let { setPersistentKeepalive(it.toInt()) }
|
||||
configData["PrivateKey"]?.let { setPrivateKeyHex(it.base64ToHex()) }
|
||||
configData["PublicKey"]?.let { setPublicKeyHex(it.base64ToHex()) }
|
||||
configData["PresharedKey"]?.let { setPreSharedKeyHex(it.base64ToHex()) }
|
||||
configData.optStringOrNull("persistent_keep_alive")?.let { setPersistentKeepalive(it.toInt()) }
|
||||
configData.getString("client_priv_key").let { setPrivateKeyHex(it.base64ToHex()) }
|
||||
configData.getString("server_pub_key").let { setPublicKeyHex(it.base64ToHex()) }
|
||||
configData.optStringOrNull("psk_key")?.let { setPreSharedKeyHex(it.base64ToHex()) }
|
||||
}
|
||||
|
||||
protected fun parseConfigData(data: String): Map<String, String> {
|
||||
val parsedData = TreeMap<String, String>(String.CASE_INSENSITIVE_ORDER)
|
||||
data.lineSequence()
|
||||
.filter { it.isNotEmpty() && !it.startsWith('[') }
|
||||
.forEach { line ->
|
||||
val attr = line.split("=", limit = 2)
|
||||
parsedData[attr.first().trim()] = attr.last().trim()
|
||||
}
|
||||
return parsedData
|
||||
protected fun WireguardConfig.Builder.configExtensionParameters(configData: JSONObject) {
|
||||
configData.optStringOrNull("Jc")?.let { setJc(it.toInt()) }
|
||||
configData.optStringOrNull("Jmin")?.let { setJmin(it.toInt()) }
|
||||
configData.optStringOrNull("Jmax")?.let { setJmax(it.toInt()) }
|
||||
configData.optStringOrNull("S1")?.let { setS1(it.toInt()) }
|
||||
configData.optStringOrNull("S2")?.let { setS2(it.toInt()) }
|
||||
configData.optStringOrNull("H1")?.let { setH1(it.toLong()) }
|
||||
configData.optStringOrNull("H2")?.let { setH2(it.toLong()) }
|
||||
configData.optStringOrNull("H3")?.let { setH3(it.toLong()) }
|
||||
configData.optStringOrNull("H4")?.let { setH4(it.toLong()) }
|
||||
}
|
||||
|
||||
private fun start(config: WireguardConfig, vpnBuilder: Builder, protect: (Int) -> Boolean) {
|
||||
|
|
|
|||
|
|
@ -1,6 +1,7 @@
|
|||
package org.amnezia.vpn.protocol.wireguard
|
||||
|
||||
import android.util.Base64
|
||||
import org.amnezia.vpn.protocol.BadConfigException
|
||||
import org.amnezia.vpn.protocol.ProtocolConfig
|
||||
import org.amnezia.vpn.util.net.InetEndpoint
|
||||
|
||||
|
|
@ -12,7 +13,17 @@ open class WireguardConfig protected constructor(
|
|||
val persistentKeepalive: Int,
|
||||
val publicKeyHex: String,
|
||||
val preSharedKeyHex: String?,
|
||||
val privateKeyHex: String
|
||||
val privateKeyHex: String,
|
||||
val useProtocolExtension: Boolean,
|
||||
val jc: Int?,
|
||||
val jmin: Int?,
|
||||
val jmax: Int?,
|
||||
val s1: Int?,
|
||||
val s2: Int?,
|
||||
val h1: Long?,
|
||||
val h2: Long?,
|
||||
val h3: Long?,
|
||||
val h4: Long?
|
||||
) : ProtocolConfig(protocolConfigBuilder) {
|
||||
|
||||
protected constructor(builder: Builder) : this(
|
||||
|
|
@ -21,7 +32,17 @@ open class WireguardConfig protected constructor(
|
|||
builder.persistentKeepalive,
|
||||
builder.publicKeyHex,
|
||||
builder.preSharedKeyHex,
|
||||
builder.privateKeyHex
|
||||
builder.privateKeyHex,
|
||||
builder.useProtocolExtension,
|
||||
builder.jc,
|
||||
builder.jmin,
|
||||
builder.jmax,
|
||||
builder.s1,
|
||||
builder.s2,
|
||||
builder.h1,
|
||||
builder.h2,
|
||||
builder.h3,
|
||||
builder.h4
|
||||
)
|
||||
|
||||
fun toWgUserspaceString(): String = with(StringBuilder()) {
|
||||
|
|
@ -33,6 +54,30 @@ open class WireguardConfig protected constructor(
|
|||
|
||||
open fun appendDeviceLine(sb: StringBuilder) = with(sb) {
|
||||
appendLine("private_key=$privateKeyHex")
|
||||
if (useProtocolExtension) {
|
||||
validateProtocolExtensionParameters()
|
||||
appendLine("jc=$jc")
|
||||
appendLine("jmin=$jmin")
|
||||
appendLine("jmax=$jmax")
|
||||
appendLine("s1=$s1")
|
||||
appendLine("s2=$s2")
|
||||
appendLine("h1=$h1")
|
||||
appendLine("h2=$h2")
|
||||
appendLine("h3=$h3")
|
||||
appendLine("h4=$h4")
|
||||
}
|
||||
}
|
||||
|
||||
private fun validateProtocolExtensionParameters() {
|
||||
if (jc == null) throw BadConfigException("Parameter jc is undefined")
|
||||
if (jmin == null) throw BadConfigException("Parameter jmin is undefined")
|
||||
if (jmax == null) throw BadConfigException("Parameter jmax is undefined")
|
||||
if (s1 == null) throw BadConfigException("Parameter s1 is undefined")
|
||||
if (s2 == null) throw BadConfigException("Parameter s2 is undefined")
|
||||
if (h1 == null) throw BadConfigException("Parameter h1 is undefined")
|
||||
if (h2 == null) throw BadConfigException("Parameter h2 is undefined")
|
||||
if (h3 == null) throw BadConfigException("Parameter h3 is undefined")
|
||||
if (h4 == null) throw BadConfigException("Parameter h4 is undefined")
|
||||
}
|
||||
|
||||
open fun appendPeerLine(sb: StringBuilder) = with(sb) {
|
||||
|
|
@ -65,6 +110,18 @@ open class WireguardConfig protected constructor(
|
|||
|
||||
override var mtu: Int = WIREGUARD_DEFAULT_MTU
|
||||
|
||||
internal var useProtocolExtension: Boolean = false
|
||||
|
||||
internal var jc: Int? = null
|
||||
internal var jmin: Int? = null
|
||||
internal var jmax: Int? = null
|
||||
internal var s1: Int? = null
|
||||
internal var s2: Int? = null
|
||||
internal var h1: Long? = null
|
||||
internal var h2: Long? = null
|
||||
internal var h3: Long? = null
|
||||
internal var h4: Long? = null
|
||||
|
||||
fun setEndpoint(endpoint: InetEndpoint) = apply { this.endpoint = endpoint }
|
||||
|
||||
fun setPersistentKeepalive(persistentKeepalive: Int) = apply { this.persistentKeepalive = persistentKeepalive }
|
||||
|
|
@ -75,6 +132,18 @@ open class WireguardConfig protected constructor(
|
|||
|
||||
fun setPrivateKeyHex(privateKeyHex: String) = apply { this.privateKeyHex = privateKeyHex }
|
||||
|
||||
fun setUseProtocolExtension(useProtocolExtension: Boolean) = apply { this.useProtocolExtension = useProtocolExtension }
|
||||
|
||||
fun setJc(jc: Int) = apply { this.jc = jc }
|
||||
fun setJmin(jmin: Int) = apply { this.jmin = jmin }
|
||||
fun setJmax(jmax: Int) = apply { this.jmax = jmax }
|
||||
fun setS1(s1: Int) = apply { this.s1 = s1 }
|
||||
fun setS2(s2: Int) = apply { this.s2 = s2 }
|
||||
fun setH1(h1: Long) = apply { this.h1 = h1 }
|
||||
fun setH2(h2: Long) = apply { this.h2 = h2 }
|
||||
fun setH3(h3: Long) = apply { this.h3 = h3 }
|
||||
fun setH4(h4: Long) = apply { this.h4 = h4 }
|
||||
|
||||
override fun build(): WireguardConfig = configBuild().run { WireguardConfig(this@Builder) }
|
||||
}
|
||||
|
||||
|
|
|
|||
19
client/android/xray/build.gradle.kts
Normal file
|
|
@ -0,0 +1,19 @@
|
|||
plugins {
|
||||
id(libs.plugins.android.library.get().pluginId)
|
||||
id(libs.plugins.kotlin.android.get().pluginId)
|
||||
}
|
||||
|
||||
kotlin {
|
||||
jvmToolchain(17)
|
||||
}
|
||||
|
||||
android {
|
||||
namespace = "org.amnezia.vpn.protocol.xray"
|
||||
}
|
||||
|
||||
dependencies {
|
||||
compileOnly(project(":utils"))
|
||||
compileOnly(project(":protocolApi"))
|
||||
implementation(project(":xray:libXray"))
|
||||
implementation(libs.kotlinx.coroutines)
|
||||
}
|
||||
6
client/android/xray/libXray/build.gradle.kts
Normal file
|
|
@ -0,0 +1,6 @@
|
|||
@file:Suppress("UnstableApiUsage")
|
||||
|
||||
configurations {
|
||||
maybeCreate("default")
|
||||
}
|
||||
artifacts.add("default", file("libxray.aar"))
|
||||