mirror of
https://github.com/alicealys/t5-gsc-utils.git
synced 2026-08-01 04:30:37 +00:00
Compare commits
11
Commits
05be91f6bc
..
main
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
8a87903b88 | ||
|
|
6beabba9e5 | ||
|
|
eb6341882d | ||
|
|
fe1fe6fe1c | ||
|
|
db0357ac19 | ||
|
|
b98796d28d | ||
|
|
b69b906175 | ||
|
|
087fbee1e3 | ||
|
|
2e0c364ac8 | ||
|
|
d91dc80333 | ||
|
|
4969d6f28f |
@@ -37,3 +37,9 @@
|
||||
[submodule "deps/date"]
|
||||
path = deps/date
|
||||
url = https://github.com/HowardHinnant/date
|
||||
[submodule "deps/libtommath"]
|
||||
path = deps/libtommath
|
||||
url = https://github.com/libtom/libtommath.git
|
||||
[submodule "deps/libtomcrypt"]
|
||||
path = deps/libtomcrypt
|
||||
url = https://github.com/libtom/libtomcrypt.git
|
||||
|
||||
@@ -226,7 +226,7 @@ You can access a mysql database using the following functions:
|
||||
{
|
||||
// call mysql::set_config
|
||||
|
||||
query = mysql::execute("select * from `players` where guid=1");
|
||||
query = mysql::query("select * from `players` where guid=1");
|
||||
query waittill("done", result);
|
||||
if (result.size > 0)
|
||||
{
|
||||
|
||||
+1
Submodule deps/libtomcrypt added at a8ed78c2ca
+1
Submodule deps/libtommath added at ae40a87a92
Vendored
+61
@@ -0,0 +1,61 @@
|
||||
libtomcrypt = {
|
||||
source = path.join(dependencies.basePath, "libtomcrypt"),
|
||||
}
|
||||
|
||||
function libtomcrypt.import()
|
||||
links {
|
||||
"libtomcrypt"
|
||||
}
|
||||
|
||||
libtomcrypt.includes()
|
||||
end
|
||||
|
||||
function libtomcrypt.includes()
|
||||
includedirs {
|
||||
path.join(libtomcrypt.source, "src/headers")
|
||||
}
|
||||
|
||||
defines {
|
||||
"LTC_NO_FAST",
|
||||
"LTC_NO_PROTOTYPES",
|
||||
"LTC_NO_RSA_BLINDING",
|
||||
}
|
||||
end
|
||||
|
||||
function libtomcrypt.project()
|
||||
project "libtomcrypt"
|
||||
language "C"
|
||||
|
||||
libtomcrypt.includes()
|
||||
libtommath.import()
|
||||
|
||||
files {
|
||||
path.join(libtomcrypt.source, "src/**.c"),
|
||||
}
|
||||
|
||||
removefiles {
|
||||
path.join(libtomcrypt.source, "src/**/*tab.c"),
|
||||
path.join(libtomcrypt.source, "src/encauth/ocb3/**.c"),
|
||||
}
|
||||
|
||||
defines {
|
||||
"_CRT_SECURE_NO_WARNINGS",
|
||||
"LTC_SOURCE",
|
||||
"_LIB",
|
||||
"USE_LTM"
|
||||
}
|
||||
|
||||
removedefines {
|
||||
"_DLL",
|
||||
"_USRDLL"
|
||||
}
|
||||
|
||||
linkoptions {
|
||||
"-IGNORE:4221"
|
||||
}
|
||||
|
||||
warnings "Off"
|
||||
kind "StaticLib"
|
||||
end
|
||||
|
||||
table.insert(dependencies, libtomcrypt)
|
||||
Vendored
+52
@@ -0,0 +1,52 @@
|
||||
libtommath = {
|
||||
source = path.join(dependencies.basePath, "libtommath"),
|
||||
}
|
||||
|
||||
function libtommath.import()
|
||||
links {
|
||||
"libtommath"
|
||||
}
|
||||
|
||||
libtommath.includes()
|
||||
end
|
||||
|
||||
function libtommath.includes()
|
||||
includedirs {
|
||||
libtommath.source
|
||||
}
|
||||
|
||||
defines {
|
||||
"LTM_DESC",
|
||||
"__STDC_IEC_559__",
|
||||
"MP_NO_DEV_URANDOM",
|
||||
}
|
||||
end
|
||||
|
||||
function libtommath.project()
|
||||
project "libtommath"
|
||||
language "C"
|
||||
|
||||
libtommath.includes()
|
||||
|
||||
files {
|
||||
path.join(libtommath.source, "*.c"),
|
||||
}
|
||||
|
||||
defines {
|
||||
"_LIB"
|
||||
}
|
||||
|
||||
removedefines {
|
||||
"_DLL",
|
||||
"_USRDLL"
|
||||
}
|
||||
|
||||
linkoptions {
|
||||
"-IGNORE:4221"
|
||||
}
|
||||
|
||||
warnings "Off"
|
||||
kind "StaticLib"
|
||||
end
|
||||
|
||||
table.insert(dependencies, libtommath)
|
||||
+193
-137
@@ -9,167 +9,223 @@
|
||||
#include "scripting.hpp"
|
||||
|
||||
#include <utils/http.hpp>
|
||||
#include <utils/concurrency.hpp>
|
||||
#include <curl/curl.h>
|
||||
|
||||
namespace http
|
||||
{
|
||||
std::unordered_map<uint64_t, bool> active_requests{};
|
||||
uint64_t request_id{};
|
||||
namespace
|
||||
{
|
||||
constexpr const auto max_result_size = 0x5000u;
|
||||
|
||||
struct http_request_params_t
|
||||
{
|
||||
std::string method;
|
||||
std::string url;
|
||||
std::string fields;
|
||||
utils::http::headers headers;
|
||||
};
|
||||
|
||||
struct http_request_t
|
||||
{
|
||||
http_request_params_t params;
|
||||
scripting::object handle;
|
||||
std::optional<utils::http::result> result;
|
||||
std::atomic_bool completed;
|
||||
};
|
||||
|
||||
std::vector<std::shared_ptr<http_request_t>> requests;
|
||||
|
||||
void notify_request_result(std::shared_ptr<http_request_t>& request)
|
||||
{
|
||||
const auto handle_id = request->handle.get_entity_id();
|
||||
|
||||
if (!request->result.has_value())
|
||||
{
|
||||
scripting::notify(handle_id, "done", {{}, false, "unknown error"});
|
||||
return;
|
||||
}
|
||||
|
||||
auto& result = request->result.value();
|
||||
const auto error = curl_easy_strerror(result.code);
|
||||
|
||||
if (result.code != CURLE_OK)
|
||||
{
|
||||
scripting::notify(handle_id, "done", {{}, false, error});
|
||||
return;
|
||||
}
|
||||
|
||||
if (result.buffer.size() >= max_result_size)
|
||||
{
|
||||
printf("^3WARNING: http result size bigger than %i bytes (%i), truncating!", max_result_size,
|
||||
static_cast<int>(result.buffer.size()));
|
||||
result.buffer.resize(max_result_size);
|
||||
}
|
||||
|
||||
scripting::notify(handle_id, "done", {result.buffer, true});
|
||||
}
|
||||
|
||||
void check_requests()
|
||||
{
|
||||
for (auto i = requests.begin(); i != requests.end(); )
|
||||
{
|
||||
auto& request = *i;
|
||||
if (!request->completed)
|
||||
{
|
||||
++i;
|
||||
continue;
|
||||
}
|
||||
else
|
||||
{
|
||||
notify_request_result(request);
|
||||
i = requests.erase(i);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
scripting::object create_request(const http_request_params_t& params)
|
||||
{
|
||||
const auto request = std::make_shared<http_request_t>();
|
||||
requests.emplace_back(request);
|
||||
|
||||
request->params = params;
|
||||
scheduler::thread_pool.push([request]
|
||||
{
|
||||
request->result = utils::http::get_data(
|
||||
request->params.url,
|
||||
request->params.fields,
|
||||
request->params.headers,
|
||||
request->params.method);
|
||||
request->completed = true;
|
||||
});
|
||||
|
||||
return request->handle;
|
||||
}
|
||||
|
||||
template <typename T>
|
||||
void push_request(T&& r)
|
||||
{
|
||||
const auto request = std::make_shared<http_request_t>(std::forward<T>(r));
|
||||
requests.emplace_back(request);
|
||||
}
|
||||
|
||||
void wait_and_clear_requests()
|
||||
{
|
||||
for (auto& task : requests)
|
||||
{
|
||||
while (!task->completed)
|
||||
{
|
||||
std::this_thread::sleep_for(10ms);
|
||||
}
|
||||
}
|
||||
|
||||
requests.clear();
|
||||
}
|
||||
|
||||
void parse_request_options(http_request_params_t& params, const scripting::array& options)
|
||||
{
|
||||
const auto fields = options["parameters"];
|
||||
const auto body = options["body"];
|
||||
const auto headers = options["headers"];
|
||||
const auto method = options["method"];
|
||||
|
||||
if (method.is<std::string>())
|
||||
{
|
||||
params.method = method.as<std::string>();
|
||||
}
|
||||
|
||||
if (fields.is<scripting::array>())
|
||||
{
|
||||
const auto fields_ = fields.as<scripting::array>();
|
||||
const auto keys = fields_.get_keys();
|
||||
|
||||
for (const auto& key : keys)
|
||||
{
|
||||
if (!key.is<std::string>())
|
||||
{
|
||||
continue;
|
||||
}
|
||||
|
||||
const auto key_ = key.as<std::string>();
|
||||
const auto value = fields_[key].to_string();
|
||||
params.fields += key_ + "=" + value + "&";
|
||||
}
|
||||
|
||||
}
|
||||
else if (body.is<std::string>())
|
||||
{
|
||||
params.fields = body.as<std::string>();
|
||||
}
|
||||
|
||||
if (headers.is<scripting::array>())
|
||||
{
|
||||
const auto headers_arr = headers.as<scripting::array>();
|
||||
const auto keys = headers_arr.get_keys();
|
||||
|
||||
for (const auto& key : keys)
|
||||
{
|
||||
if (!key.is<std::string>())
|
||||
{
|
||||
continue;
|
||||
}
|
||||
|
||||
const auto key_str = key.as<std::string>();
|
||||
const auto value = headers_arr[key].to_string();
|
||||
|
||||
params.headers[key_str] = value;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
class component final : public component_interface
|
||||
{
|
||||
public:
|
||||
void on_shutdown([[maybe_unused]] plugin::plugin* plugin) override
|
||||
{
|
||||
scripting::on_shutdown(wait_and_clear_requests);
|
||||
}
|
||||
|
||||
void on_startup([[maybe_unused]] plugin::plugin* plugin) override
|
||||
{
|
||||
scripting::on_shutdown([]()
|
||||
{
|
||||
active_requests.clear();
|
||||
});
|
||||
scheduler::loop(check_requests, scheduler::server);
|
||||
scripting::on_shutdown(wait_and_clear_requests);
|
||||
|
||||
gsc::function::add_multiple([](const std::string& url)
|
||||
{
|
||||
const auto id = request_id++;
|
||||
active_requests[id] = true;
|
||||
|
||||
const auto object = scripting::object{};
|
||||
const auto object_id = object.get_entity_id();
|
||||
|
||||
scheduler::once([id, object_id, url]()
|
||||
{
|
||||
const auto data = utils::http::get_data(url);
|
||||
scheduler::once([id, object_id, data]()
|
||||
{
|
||||
if (active_requests.find(id) == active_requests.end())
|
||||
{
|
||||
return;
|
||||
}
|
||||
|
||||
if (!data.has_value())
|
||||
{
|
||||
scripting::notify(object_id, "done", {{}, false, "Unknown error"});
|
||||
return;
|
||||
}
|
||||
|
||||
const auto& result = data.value();
|
||||
const auto error = curl_easy_strerror(result.code);
|
||||
|
||||
if (result.code != CURLE_OK)
|
||||
{
|
||||
scripting::notify(object_id, "done", {{}, false, error});
|
||||
return;
|
||||
}
|
||||
|
||||
if (result.buffer.size() >= 0x5000)
|
||||
{
|
||||
printf("^3WARNING: http result size bigger than 20480 bytes (%i), truncating!", static_cast<int>(result.buffer.size()));
|
||||
}
|
||||
|
||||
scripting::notify(object_id, "done", {result.buffer.substr(0, 0x5000), true});
|
||||
}, scheduler::pipeline::server);
|
||||
}, scheduler::pipeline::async);
|
||||
|
||||
return object;
|
||||
http_request_params_t params{};
|
||||
params.url = url;
|
||||
return create_request(params);
|
||||
}, "http::get", "httpget", "curl");
|
||||
|
||||
gsc::function::add("http::request", [](const std::string& url, const scripting::variadic_args& va)
|
||||
{
|
||||
const auto id = request_id++;
|
||||
active_requests[id] = true;
|
||||
|
||||
const auto object = scripting::object{};
|
||||
const auto object_id = object.get_entity_id();
|
||||
|
||||
std::string fields_string{};
|
||||
std::unordered_map<std::string, std::string> headers_map{};
|
||||
http_request_params_t params{};
|
||||
params.url = url;
|
||||
|
||||
if (va.size() > 0)
|
||||
{
|
||||
const auto options = va[0].as<scripting::array>();
|
||||
|
||||
const auto fields = options["parameters"];
|
||||
const auto body = options["body"];
|
||||
const auto headers = options["headers"];
|
||||
|
||||
if (fields.is<scripting::array>())
|
||||
{
|
||||
const auto fields_ = fields.as<scripting::array>();
|
||||
const auto keys = fields_.get_keys();
|
||||
|
||||
for (const auto& key : keys)
|
||||
{
|
||||
if (!key.is<std::string>())
|
||||
{
|
||||
continue;
|
||||
}
|
||||
|
||||
const auto key_ = key.as<std::string>();
|
||||
const auto value = fields_[key].to_string();
|
||||
fields_string += key_ + "=" + value + "&";
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
if (body.is<std::string>())
|
||||
{
|
||||
fields_string = body.as<std::string>();
|
||||
}
|
||||
|
||||
if (headers.is<scripting::array>())
|
||||
{
|
||||
const auto headers_ = headers.as<scripting::array>();
|
||||
const auto keys = headers_.get_keys();
|
||||
|
||||
for (const auto& key : keys)
|
||||
{
|
||||
if (!key.is<std::string>())
|
||||
{
|
||||
continue;
|
||||
}
|
||||
|
||||
const auto key_ = key.as<std::string>();
|
||||
const auto value = headers_[key].to_string();
|
||||
|
||||
headers_map[key_] = value;
|
||||
}
|
||||
}
|
||||
parse_request_options(params, options);
|
||||
}
|
||||
|
||||
scheduler::once([id, object_id, url, fields_string, headers_map]()
|
||||
{
|
||||
const auto data = utils::http::get_data(url, fields_string, headers_map);
|
||||
scheduler::once([data, object_id, id]
|
||||
{
|
||||
if (active_requests.find(id) == active_requests.end())
|
||||
{
|
||||
return;
|
||||
}
|
||||
|
||||
if (!data.has_value())
|
||||
{
|
||||
scripting::notify(object_id, "done", {{}, false, "Unknown error"});
|
||||
return;
|
||||
}
|
||||
|
||||
const auto& result = data.value();
|
||||
const auto error = curl_easy_strerror(result.code);
|
||||
|
||||
if (result.code != CURLE_OK)
|
||||
{
|
||||
scripting::notify(object_id, "done", {{}, false, error});
|
||||
return;
|
||||
}
|
||||
|
||||
if (result.buffer.size() >= 0x5000)
|
||||
{
|
||||
printf("^3WARNING: http result size bigger than 20480 bytes (%i), truncating!", static_cast<int>(result.buffer.size()));
|
||||
}
|
||||
|
||||
scripting::notify(object_id, "done", {result.buffer.substr(0, 0x5000), true});
|
||||
}, scheduler::pipeline::server);
|
||||
}, scheduler::pipeline::async);
|
||||
|
||||
return object;
|
||||
return create_request(params);
|
||||
});
|
||||
|
||||
gsc::function::add_multiple([](const std::string& url, const scripting::variadic_args& va)
|
||||
{
|
||||
http_request_params_t params{};
|
||||
params.url = url;
|
||||
params.method = "POST";
|
||||
|
||||
if (va.size() > 0)
|
||||
{
|
||||
const auto options = va[0].as<scripting::array>();
|
||||
parse_request_options(params, options);
|
||||
}
|
||||
|
||||
return create_request(params);
|
||||
}, "httppost", "http::post");
|
||||
}
|
||||
};
|
||||
}
|
||||
|
||||
+12
-4
@@ -13,12 +13,20 @@ namespace io
|
||||
class component final : public component_interface
|
||||
{
|
||||
public:
|
||||
void on_after_dvar_init([[maybe_unused]] plugin::plugin* plugin) override
|
||||
{
|
||||
const auto fs_homepath = game::Dvar_FindVar("fs_homepath");
|
||||
if (fs_homepath == nullptr)
|
||||
{
|
||||
return;
|
||||
}
|
||||
|
||||
std::filesystem::current_path(fs_homepath->current.string);
|
||||
printf("working directory: %s", fs_homepath->current.string);
|
||||
}
|
||||
|
||||
void on_startup([[maybe_unused]] plugin::plugin* plugin) override
|
||||
{
|
||||
// TODO: fix this, or is it because pluto does it already?
|
||||
//const auto fs_basegame = game::Dvar_FindVar("fs_basegame");
|
||||
//std::filesystem::current_path(fs_basegame->current.string);
|
||||
|
||||
gsc::function::add_multiple([](const std::string& file, const std::string& data,
|
||||
const scripting::variadic_args& va)
|
||||
{
|
||||
|
||||
+229
-183
@@ -22,15 +22,13 @@ namespace mysql
|
||||
|
||||
struct task_t
|
||||
{
|
||||
std::thread thread;
|
||||
bool done;
|
||||
bool canceled;
|
||||
std::unique_ptr<scripting::object> handle;
|
||||
mysql_result_t result;
|
||||
scripting::object handle;
|
||||
mysql_result_t result{};
|
||||
std::atomic_bool completed;
|
||||
};
|
||||
|
||||
uint64_t task_index{};
|
||||
std::unordered_map<uint64_t, task_t> tasks;
|
||||
std::vector<std::shared_ptr<task_t>> tasks;
|
||||
std::array<connection, max_connections> connection_pool;
|
||||
|
||||
scripting::script_value field_to_value(const MYSQL_FIELD* field, const std::string& row)
|
||||
{
|
||||
@@ -210,35 +208,153 @@ namespace mysql
|
||||
template <typename F>
|
||||
scripting::object create_mysql_query(F&& cb)
|
||||
{
|
||||
auto task = &tasks[task_index++];
|
||||
auto task = std::make_shared<task_t>();
|
||||
tasks.emplace_back(task);
|
||||
|
||||
task->done = false;
|
||||
task->canceled = false;
|
||||
task->handle = std::make_unique<scripting::object>();
|
||||
|
||||
task->thread = std::thread([=]()
|
||||
scheduler::thread_pool.push([=]
|
||||
{
|
||||
try
|
||||
{
|
||||
mysql::access([&](database_t& db)
|
||||
{
|
||||
task->result = cb(db);
|
||||
task->done = true;
|
||||
task->completed = true;
|
||||
});
|
||||
}
|
||||
catch (const std::exception& e)
|
||||
{
|
||||
printf("%s\n", e.what());
|
||||
task->done = true;
|
||||
printf("^1MySQL ERROR: %s\n", e.what());
|
||||
task->completed = true;
|
||||
}
|
||||
});
|
||||
|
||||
return *task->handle.get();
|
||||
return task->handle;
|
||||
}
|
||||
|
||||
void wait_and_clear_tasks()
|
||||
{
|
||||
for (auto& task : tasks)
|
||||
{
|
||||
while (!task->completed)
|
||||
{
|
||||
std::this_thread::sleep_for(10ms);
|
||||
}
|
||||
}
|
||||
|
||||
tasks.clear();
|
||||
}
|
||||
|
||||
void notify_task_result(std::shared_ptr<task_t>& task)
|
||||
{
|
||||
const auto result = task->result.result
|
||||
? generate_result(task->result.result)
|
||||
: generate_result(task->result.stmt);
|
||||
|
||||
const auto rows = static_cast<std::size_t>(task->result.affected_rows);
|
||||
scripting::notify(task->handle.get_entity_id(), "done", {result, rows, task->result.error});
|
||||
|
||||
if (task->result.result != nullptr)
|
||||
{
|
||||
mysql_free_result(task->result.result);
|
||||
task->result.result = nullptr;
|
||||
}
|
||||
|
||||
if (task->result.stmt != nullptr)
|
||||
{
|
||||
mysql_stmt_close(task->result.stmt);
|
||||
task->result.stmt = nullptr;
|
||||
}
|
||||
}
|
||||
|
||||
void check_tasks()
|
||||
{
|
||||
for (auto i = tasks.begin(); i != tasks.end(); )
|
||||
{
|
||||
auto& task = *i;
|
||||
if (!task->completed)
|
||||
{
|
||||
++i;
|
||||
continue;
|
||||
}
|
||||
else
|
||||
{
|
||||
notify_task_result(task);
|
||||
i = tasks.erase(i);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
template <typename T>
|
||||
MYSQL_BIND* bind_statement_args(const T& args, std::size_t& bind_count)
|
||||
{
|
||||
bind_count = args.size();
|
||||
const auto binds = utils::memory::allocate_array<MYSQL_BIND>(bind_count);
|
||||
|
||||
for (auto i = 0u; i < args.size(); i++)
|
||||
{
|
||||
const auto& arg = args[i];
|
||||
const auto& raw_value = arg.get_raw();
|
||||
|
||||
switch (raw_value.type)
|
||||
{
|
||||
case game::SCRIPT_FLOAT:
|
||||
{
|
||||
binds[i].buffer = utils::memory::allocate<float>();
|
||||
binds[i].buffer_type = MYSQL_TYPE_FLOAT;
|
||||
*reinterpret_cast<float*>(binds[i].buffer) = raw_value.u.floatValue;
|
||||
break;
|
||||
}
|
||||
case game::SCRIPT_INTEGER:
|
||||
{
|
||||
binds[i].buffer = utils::memory::allocate<int>();
|
||||
binds[i].buffer_type = MYSQL_TYPE_LONG;
|
||||
*reinterpret_cast<int*>(binds[i].buffer) = raw_value.u.intValue;
|
||||
break;
|
||||
}
|
||||
case game::SCRIPT_STRING:
|
||||
{
|
||||
const auto str = arg.as<std::string>();
|
||||
const auto str_copy = utils::memory::duplicate_string(str);
|
||||
binds[i].buffer = str_copy;
|
||||
binds[i].buffer_length = str.size();
|
||||
binds[i].buffer_type = MYSQL_TYPE_STRING;
|
||||
break;
|
||||
}
|
||||
default:
|
||||
{
|
||||
binds[i].buffer_type = MYSQL_TYPE_NULL;
|
||||
break;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
return binds;
|
||||
}
|
||||
|
||||
void cleanup_connections()
|
||||
{
|
||||
for (auto& connection : connection_pool)
|
||||
{
|
||||
connection.cleanup();
|
||||
}
|
||||
}
|
||||
|
||||
void free_statement_binds(MYSQL_BIND* binds, const std::size_t bind_count)
|
||||
{
|
||||
if (binds == nullptr)
|
||||
{
|
||||
return;
|
||||
}
|
||||
|
||||
for (auto i = 0u; i < bind_count; i++)
|
||||
{
|
||||
utils::memory::free(binds[i].buffer);
|
||||
}
|
||||
|
||||
utils::memory::free(binds);
|
||||
}
|
||||
}
|
||||
|
||||
std::array<connection_t, max_connections> connection_pool;
|
||||
|
||||
utils::concurrency::container<sql::connection_config>& get_config()
|
||||
{
|
||||
static utils::concurrency::container<sql::connection_config> config;
|
||||
@@ -260,23 +376,70 @@ namespace mysql
|
||||
return config;
|
||||
}
|
||||
|
||||
void cleanup_connections()
|
||||
void connection::check()
|
||||
{
|
||||
for (auto& connection : connection_pool)
|
||||
const auto now = std::chrono::high_resolution_clock::now();
|
||||
const auto diff = now - this->start_;
|
||||
|
||||
if (this->db.get() == nullptr || !this->db->ping_server() || diff >= connection_timeout)
|
||||
{
|
||||
std::unique_lock<database_mutex_t> lock(connection.mutex, std::try_to_lock);
|
||||
get_config().access([&](sql::connection_config& cfg)
|
||||
{
|
||||
this->db = std::make_unique<sql::connection>(cfg);
|
||||
this->start_ = now;
|
||||
});
|
||||
}
|
||||
|
||||
this->last_access_ = now;
|
||||
}
|
||||
|
||||
void connection::cleanup()
|
||||
{
|
||||
std::unique_lock<database_mutex_t> lock(this->mutex, std::try_to_lock);
|
||||
if (!lock.owns_lock())
|
||||
{
|
||||
return;
|
||||
}
|
||||
|
||||
const auto now = std::chrono::high_resolution_clock::now();
|
||||
const auto diff = now - this->last_access_;
|
||||
if (diff >= connection_timeout)
|
||||
{
|
||||
this->db.reset();
|
||||
}
|
||||
}
|
||||
|
||||
connection* get_connection(std::unique_lock<database_mutex_t>& lock)
|
||||
{
|
||||
static thread_local connection* last_connection{};
|
||||
if (last_connection != nullptr)
|
||||
{
|
||||
lock = std::unique_lock(last_connection->mutex, std::try_to_lock);
|
||||
if (lock.owns_lock())
|
||||
{
|
||||
return last_connection;
|
||||
}
|
||||
}
|
||||
|
||||
for (auto i = 0u; i < connection_pool.size(); i++)
|
||||
{
|
||||
auto connection = &connection_pool[i];
|
||||
if (connection == last_connection)
|
||||
{
|
||||
continue;
|
||||
}
|
||||
|
||||
lock = std::unique_lock(connection->mutex, std::try_to_lock);
|
||||
if (!lock.owns_lock())
|
||||
{
|
||||
continue;
|
||||
}
|
||||
|
||||
const auto now = std::chrono::high_resolution_clock::now();
|
||||
const auto diff = now - connection.last_access;
|
||||
if (diff >= connection_timeout)
|
||||
{
|
||||
connection.db.reset();
|
||||
}
|
||||
last_connection = connection;
|
||||
return connection;
|
||||
}
|
||||
|
||||
return nullptr;
|
||||
}
|
||||
|
||||
class component final : public component_interface
|
||||
@@ -284,74 +447,14 @@ namespace mysql
|
||||
public:
|
||||
void on_shutdown([[maybe_unused]] plugin::plugin* plugin) override
|
||||
{
|
||||
for (auto i = tasks.begin(); i != tasks.end(); ++i)
|
||||
{
|
||||
i->second.canceled = true;
|
||||
|
||||
if (i->second.thread.joinable())
|
||||
{
|
||||
i->second.thread.join();
|
||||
}
|
||||
}
|
||||
wait_and_clear_tasks();
|
||||
}
|
||||
|
||||
void on_startup([[maybe_unused]] plugin::plugin* plugin) override
|
||||
{
|
||||
scripting::on_shutdown([]()
|
||||
{
|
||||
for (auto i = tasks.begin(); i != tasks.end(); ++i)
|
||||
{
|
||||
i->second.canceled = true;
|
||||
i->second.handle.reset();
|
||||
}
|
||||
});
|
||||
|
||||
scheduler::loop([]
|
||||
{
|
||||
cleanup_connections();
|
||||
}, scheduler::async, 1s);
|
||||
|
||||
scheduler::loop([]
|
||||
{
|
||||
for (auto i = tasks.begin(); i != tasks.end(); )
|
||||
{
|
||||
if (!i->second.done)
|
||||
{
|
||||
++i;
|
||||
continue;
|
||||
}
|
||||
|
||||
if (i->second.thread.joinable())
|
||||
{
|
||||
i->second.thread.join();
|
||||
}
|
||||
|
||||
if (!i->second.canceled)
|
||||
{
|
||||
const auto result = i->second.result.result
|
||||
? generate_result(i->second.result.result)
|
||||
: generate_result(i->second.result.stmt);
|
||||
|
||||
const auto rows = static_cast<size_t>(i->second.result.affected_rows);
|
||||
|
||||
scripting::notify(i->second.handle->get_entity_id(), "done", {result, rows, i->second.result.error});
|
||||
|
||||
if (i->second.result.result)
|
||||
{
|
||||
mysql_free_result(i->second.result.result);
|
||||
i->second.result.result = nullptr;
|
||||
}
|
||||
|
||||
if (i->second.result.stmt)
|
||||
{
|
||||
mysql_stmt_close(i->second.result.stmt);
|
||||
i->second.result.stmt = nullptr;
|
||||
}
|
||||
}
|
||||
|
||||
i = tasks.erase(i);
|
||||
}
|
||||
}, scheduler::server);
|
||||
scripting::on_shutdown(wait_and_clear_tasks);
|
||||
scheduler::loop(cleanup_connections, scheduler::async, 60s);
|
||||
scheduler::loop(check_tasks, scheduler::server);
|
||||
|
||||
gsc::function::add("mysql::set_config", [](const scripting::object& config)
|
||||
{
|
||||
@@ -365,7 +468,7 @@ namespace mysql
|
||||
});
|
||||
});
|
||||
|
||||
gsc::function::add("mysql::query", [](const std::string& query)
|
||||
gsc::function::add_multiple([](const std::string& query)
|
||||
{
|
||||
return create_mysql_query([=](database_t& db)
|
||||
{
|
||||
@@ -383,114 +486,57 @@ namespace mysql
|
||||
|
||||
return result;
|
||||
});
|
||||
});
|
||||
}, "mysql::query", "mysql::execute");
|
||||
|
||||
gsc::function::add("mysql::prepared_statement", [](const std::string& query, const scripting::variadic_args& values)
|
||||
{
|
||||
MYSQL_BIND* binds = nullptr;
|
||||
size_t bind_count = 0;
|
||||
|
||||
const auto free_binds = [=]
|
||||
{
|
||||
if (binds == nullptr)
|
||||
{
|
||||
return;
|
||||
}
|
||||
|
||||
for (auto i = 0u; i < bind_count; i++)
|
||||
{
|
||||
utils::memory::free(binds[i].buffer);
|
||||
}
|
||||
|
||||
utils::memory::free(binds);
|
||||
};
|
||||
auto bind_count = 0u;
|
||||
|
||||
try
|
||||
{
|
||||
const auto bind_args = [&]<typename T>(const T& args)
|
||||
{
|
||||
bind_count = args.size();
|
||||
binds = utils::memory::allocate_array<MYSQL_BIND>(bind_count);
|
||||
|
||||
for (auto i = 0u; i < args.size(); i++)
|
||||
{
|
||||
const auto& arg = args[i];
|
||||
const auto& raw_value = arg.get_raw();
|
||||
|
||||
switch (raw_value.type)
|
||||
{
|
||||
case game::SCRIPT_FLOAT:
|
||||
{
|
||||
binds[i].buffer = utils::memory::allocate<float>();
|
||||
binds[i].buffer_type = MYSQL_TYPE_FLOAT;
|
||||
*reinterpret_cast<float*>(binds[i].buffer) = raw_value.u.floatValue;
|
||||
break;
|
||||
}
|
||||
case game::SCRIPT_INTEGER:
|
||||
{
|
||||
binds[i].buffer = utils::memory::allocate<int>();
|
||||
binds[i].buffer_type = MYSQL_TYPE_LONG;
|
||||
*reinterpret_cast<int*>(binds[i].buffer) = raw_value.u.intValue;
|
||||
break;
|
||||
}
|
||||
case game::SCRIPT_STRING:
|
||||
{
|
||||
const auto str = arg.as<std::string>();
|
||||
const auto str_copy = utils::memory::duplicate_string(str);
|
||||
binds[i].buffer = str_copy;
|
||||
binds[i].buffer_length = str.size();
|
||||
binds[i].buffer_type = MYSQL_TYPE_STRING;
|
||||
break;
|
||||
}
|
||||
default:
|
||||
{
|
||||
binds[i].buffer_type = MYSQL_TYPE_NULL;
|
||||
break;
|
||||
}
|
||||
}
|
||||
}
|
||||
};
|
||||
|
||||
if (values.size() > 0 && values[0].is<scripting::array>())
|
||||
{
|
||||
bind_args(values[0].as<scripting::array>());
|
||||
binds = bind_statement_args(values[0].as<scripting::array>(), bind_count);
|
||||
}
|
||||
else
|
||||
{
|
||||
bind_args(values);
|
||||
binds = bind_statement_args(values, bind_count);
|
||||
}
|
||||
|
||||
const auto handle = create_mysql_query([binds, bind_count, query](database_t& db)
|
||||
{
|
||||
const auto _0 = gsl::finally([=]
|
||||
{
|
||||
free_statement_binds(binds, bind_count);
|
||||
});
|
||||
|
||||
mysql_result_t result{};
|
||||
|
||||
const auto handle = db->get_handle();
|
||||
const auto stmt = mysql_stmt_init(handle);
|
||||
|
||||
if (mysql_stmt_prepare(stmt, query.data(), query.size()) != 0 ||
|
||||
(binds != nullptr && mysql_stmt_bind_param(stmt, binds) != 0) ||
|
||||
mysql_stmt_execute(stmt) != 0)
|
||||
{
|
||||
result.error = mysql_stmt_error(stmt);
|
||||
return result;
|
||||
}
|
||||
|
||||
result.stmt = stmt;
|
||||
result.affected_rows = mysql_stmt_affected_rows(stmt);
|
||||
|
||||
return result;
|
||||
});
|
||||
|
||||
return handle;
|
||||
}
|
||||
catch (const std::exception& e)
|
||||
{
|
||||
free_binds();
|
||||
free_statement_binds(binds, bind_count);
|
||||
throw e;
|
||||
}
|
||||
|
||||
return create_mysql_query([=](database_t& db)
|
||||
{
|
||||
const auto _0 = gsl::finally([&]
|
||||
{
|
||||
free_binds();
|
||||
});
|
||||
|
||||
mysql_result_t result{};
|
||||
|
||||
const auto handle = db->get_handle();
|
||||
const auto stmt = mysql_stmt_init(handle);
|
||||
|
||||
if (mysql_stmt_prepare(stmt, query.data(), query.size()) != 0 ||
|
||||
mysql_stmt_bind_param(stmt, binds) != 0 ||
|
||||
mysql_stmt_execute(stmt) != 0)
|
||||
{
|
||||
result.error = mysql_stmt_error(stmt);
|
||||
return result;
|
||||
}
|
||||
|
||||
result.stmt = stmt;
|
||||
result.affected_rows = mysql_stmt_affected_rows(stmt);
|
||||
|
||||
return result;
|
||||
});
|
||||
});
|
||||
}
|
||||
};
|
||||
|
||||
+21
-33
@@ -16,55 +16,43 @@ namespace sql = sqlpp::mysql;
|
||||
|
||||
namespace mysql
|
||||
{
|
||||
constexpr auto max_connections = 256;
|
||||
constexpr auto max_connections = 100;
|
||||
constexpr auto connection_timeout = 200s;
|
||||
|
||||
using database_mutex_t = std::recursive_mutex;
|
||||
using database_t = std::unique_ptr<sql::connection>;
|
||||
|
||||
struct connection_t
|
||||
utils::concurrency::container<sql::connection_config>& get_config();
|
||||
|
||||
class connection
|
||||
{
|
||||
public:
|
||||
connection() = default;
|
||||
void check();
|
||||
void cleanup();
|
||||
|
||||
database_t db;
|
||||
database_mutex_t mutex;
|
||||
std::chrono::high_resolution_clock::time_point start;
|
||||
std::chrono::high_resolution_clock::time_point last_access;
|
||||
|
||||
private:
|
||||
std::chrono::high_resolution_clock::time_point start_;
|
||||
std::chrono::high_resolution_clock::time_point last_access_;
|
||||
|
||||
};
|
||||
|
||||
extern std::array<connection_t, max_connections> connection_pool;
|
||||
|
||||
utils::concurrency::container<sql::connection_config>& get_config();
|
||||
connection* get_connection(std::unique_lock<database_mutex_t>& lock);
|
||||
|
||||
template <typename T = void, typename F>
|
||||
T access(F&& accessor)
|
||||
{
|
||||
for (auto& connection : connection_pool)
|
||||
std::unique_lock<database_mutex_t> lock;
|
||||
auto conn = get_connection(lock);
|
||||
if (conn == nullptr)
|
||||
{
|
||||
std::unique_lock<database_mutex_t> lock(connection.mutex, std::try_to_lock);
|
||||
if (!lock.owns_lock())
|
||||
{
|
||||
continue;
|
||||
}
|
||||
|
||||
const auto now = std::chrono::high_resolution_clock::now();
|
||||
const auto diff = now - connection.start;
|
||||
|
||||
if (!connection.db.get() || !connection.db->ping_server() || diff >= 1h)
|
||||
{
|
||||
get_config().access([&](sql::connection_config& cfg)
|
||||
{
|
||||
connection.db = std::make_unique<sql::connection>(cfg);
|
||||
connection.start = now;
|
||||
});
|
||||
}
|
||||
|
||||
connection.last_access = now;
|
||||
return accessor(connection.db);
|
||||
throw std::runtime_error("out of connections");
|
||||
}
|
||||
|
||||
throw std::runtime_error("out of connections");
|
||||
conn->check();
|
||||
return accessor(conn->db);
|
||||
}
|
||||
|
||||
void cleanup_connections();
|
||||
|
||||
void run_tasks();
|
||||
}
|
||||
|
||||
@@ -9,6 +9,8 @@
|
||||
|
||||
namespace scheduler
|
||||
{
|
||||
utils::thread_pool thread_pool;
|
||||
|
||||
namespace
|
||||
{
|
||||
utils::hook::detour server_frame_hook;
|
||||
@@ -142,6 +144,9 @@ namespace scheduler
|
||||
public:
|
||||
void on_startup([[maybe_unused]] plugin::plugin* plugin) override
|
||||
{
|
||||
thread_pool.initialize(8);
|
||||
thread_pool.start();
|
||||
|
||||
thread = std::thread([]()
|
||||
{
|
||||
while (!kill_thread)
|
||||
@@ -156,6 +161,7 @@ namespace scheduler
|
||||
|
||||
void on_shutdown([[maybe_unused]] plugin::plugin* plugin) override
|
||||
{
|
||||
thread_pool.stop();
|
||||
kill_thread = true;
|
||||
|
||||
if (thread.joinable())
|
||||
|
||||
@@ -1,5 +1,7 @@
|
||||
#pragma once
|
||||
|
||||
#include <utils/thread_pool.hpp>
|
||||
|
||||
namespace scheduler
|
||||
{
|
||||
enum pipeline
|
||||
@@ -18,4 +20,6 @@ namespace scheduler
|
||||
std::chrono::milliseconds delay = 0ms);
|
||||
void once(const std::function<void()>& callback, pipeline type = pipeline::server,
|
||||
std::chrono::milliseconds delay = 0ms);
|
||||
|
||||
extern utils::thread_pool thread_pool;
|
||||
}
|
||||
|
||||
@@ -13,24 +13,21 @@ namespace user_info
|
||||
namespace
|
||||
{
|
||||
using user_info_map = std::unordered_map<std::string, std::string>;
|
||||
std::unordered_map<int, user_info_map> user_info_overrides;
|
||||
std::array<user_info_map, 18> user_info_overrides;
|
||||
|
||||
utils::hook::detour scr_shutdown_system_hook;
|
||||
|
||||
void clear_client_overrides(const int client_num)
|
||||
void clear_client_overrides(unsigned int client_num)
|
||||
{
|
||||
user_info_overrides[client_num].clear();
|
||||
}
|
||||
|
||||
void clear_all_overrides()
|
||||
{
|
||||
user_info_overrides.clear();
|
||||
}
|
||||
|
||||
void client_disconnect_stub(const int client_num)
|
||||
{
|
||||
clear_client_overrides(client_num);
|
||||
game::ClientDisconnect(client_num);
|
||||
for (auto& entry : user_info_overrides)
|
||||
{
|
||||
entry.clear();
|
||||
}
|
||||
}
|
||||
|
||||
void scr_shutdown_system_stub(const game::scriptInstance_t inst, const unsigned char sys, const int b_complete)
|
||||
@@ -45,11 +42,6 @@ namespace user_info
|
||||
|
||||
utils::info_string map(buffer);
|
||||
|
||||
if (!user_info_overrides.contains(index))
|
||||
{
|
||||
user_info_overrides[index] = {};
|
||||
}
|
||||
|
||||
for (const auto& [key, val] : user_info_overrides[index])
|
||||
{
|
||||
if (val.empty())
|
||||
@@ -75,7 +67,8 @@ namespace user_info
|
||||
utils::hook::call(SELECT_VALUE(0x5D38EB, 0x4A75E2), sv_get_user_info_stub);
|
||||
utils::hook::call(SELECT_VALUE(0x67FFE9, 0x548DB0), sv_get_user_info_stub);
|
||||
|
||||
utils::hook::call(SELECT_VALUE(0x4F3931, 0x5DC953), client_disconnect_stub);
|
||||
plugin->get_interface()->callbacks()->on_player_connect(clear_client_overrides);
|
||||
plugin->get_interface()->callbacks()->on_player_disconnect(clear_client_overrides);
|
||||
|
||||
scr_shutdown_system_hook.create(SELECT_VALUE(0x596D40, 0x540780), scr_shutdown_system_stub);
|
||||
|
||||
|
||||
+28
-2
@@ -8,22 +8,48 @@
|
||||
#include <utils/hook.hpp>
|
||||
#include <utils/binary_resource.hpp>
|
||||
#include <utils/nt.hpp>
|
||||
#include <utils/cryptography.hpp>
|
||||
#include <utils/io.hpp>
|
||||
|
||||
namespace
|
||||
{
|
||||
utils::hook::detour load_library_hook;
|
||||
|
||||
std::string extract_resource(const std::string& name, const int resource)
|
||||
{
|
||||
const auto data = utils::nt::load_resource(resource);
|
||||
const auto path = std::filesystem::current_path() / "tmp" / name;
|
||||
const auto path_str = path.generic_string();
|
||||
|
||||
if (!utils::io::write_file(path_str, data))
|
||||
{
|
||||
const auto current = utils::io::read_file(path_str);
|
||||
const auto hash_current = utils::cryptography::md5::compute(current);
|
||||
const auto hash_target = utils::cryptography::md5::compute(data);
|
||||
if (hash_target != hash_current)
|
||||
{
|
||||
throw std::runtime_error("failed to extract libmysql.dll!");
|
||||
}
|
||||
}
|
||||
|
||||
return path_str;
|
||||
}
|
||||
|
||||
HMODULE __stdcall load_library_stub(LPCSTR lib_name, HANDLE file, DWORD flags)
|
||||
{
|
||||
if (lib_name == "libmysql.dll"s)
|
||||
{
|
||||
static auto dll = utils::binary_resource{LIBMYSQL_DLL, lib_name};
|
||||
const auto path = dll.get_extracted_file();
|
||||
const auto path = extract_resource(lib_name, LIBMYSQL_DLL);
|
||||
const auto handle = load_library_hook.invoke_pascal<HMODULE>(path.data(), file, flags);
|
||||
|
||||
if (handle != nullptr)
|
||||
{
|
||||
return handle;
|
||||
}
|
||||
else
|
||||
{
|
||||
throw std::runtime_error(std::format("failed to load libmysql.dll: {}", GetLastError()));
|
||||
}
|
||||
}
|
||||
|
||||
return load_library_hook.invoke_pascal<HMODULE>(lib_name, file, flags);
|
||||
|
||||
+24
-1
@@ -1 +1,24 @@
|
||||
#include <stdinc.hpp>
|
||||
#include <stdinc.hpp>
|
||||
|
||||
extern "C"
|
||||
{
|
||||
int s_read_arc4random(void*, size_t)
|
||||
{
|
||||
return -1;
|
||||
}
|
||||
|
||||
int s_read_getrandom(void*, size_t)
|
||||
{
|
||||
return -1;
|
||||
}
|
||||
|
||||
int s_read_urandom(void*, size_t)
|
||||
{
|
||||
return -1;
|
||||
}
|
||||
|
||||
int s_read_ltm_rng(void*, size_t)
|
||||
{
|
||||
return -1;
|
||||
}
|
||||
}
|
||||
|
||||
@@ -0,0 +1,691 @@
|
||||
#include <stdinc.hpp>
|
||||
|
||||
#include "cryptography.hpp"
|
||||
#include "nt.hpp"
|
||||
#include <gsl/gsl>
|
||||
|
||||
#undef max
|
||||
using namespace std::string_literals;
|
||||
|
||||
/// http://www.opensource.apple.com/source/CommonCrypto/CommonCrypto-55010/Source/libtomcrypt/doc/libTomCryptDoc.pdf
|
||||
|
||||
#pragma warning(push)
|
||||
#pragma warning(disable: 4996)
|
||||
|
||||
namespace utils::cryptography
|
||||
{
|
||||
namespace
|
||||
{
|
||||
struct __
|
||||
{
|
||||
__()
|
||||
{
|
||||
ltc_mp = ltm_desc;
|
||||
|
||||
register_cipher(&aes_desc);
|
||||
register_cipher(&des3_desc);
|
||||
|
||||
register_prng(&sprng_desc);
|
||||
register_prng(&fortuna_desc);
|
||||
register_prng(&yarrow_desc);
|
||||
|
||||
register_hash(&sha1_desc);
|
||||
register_hash(&sha256_desc);
|
||||
register_hash(&sha512_desc);
|
||||
}
|
||||
} ___;
|
||||
|
||||
[[maybe_unused]] const char* cs(const uint8_t* data)
|
||||
{
|
||||
return reinterpret_cast<const char*>(data);
|
||||
}
|
||||
|
||||
[[maybe_unused]] char* cs(uint8_t* data)
|
||||
{
|
||||
return reinterpret_cast<char*>(data);
|
||||
}
|
||||
|
||||
[[maybe_unused]] const uint8_t* cs(const char* data)
|
||||
{
|
||||
return reinterpret_cast<const uint8_t*>(data);
|
||||
}
|
||||
|
||||
[[maybe_unused]] uint8_t* cs(char* data)
|
||||
{
|
||||
return reinterpret_cast<uint8_t*>(data);
|
||||
}
|
||||
|
||||
[[maybe_unused]] unsigned long ul(const size_t value)
|
||||
{
|
||||
return static_cast<unsigned long>(value);
|
||||
}
|
||||
|
||||
class prng
|
||||
{
|
||||
public:
|
||||
prng(const ltc_prng_descriptor& descriptor, const bool autoseed = true)
|
||||
: state_(std::make_unique<prng_state>())
|
||||
, descriptor_(descriptor)
|
||||
{
|
||||
this->id_ = register_prng(&descriptor);
|
||||
if (this->id_ == -1)
|
||||
{
|
||||
throw std::runtime_error("PRNG "s + this->descriptor_.name + " could not be registered!");
|
||||
}
|
||||
|
||||
if (autoseed)
|
||||
{
|
||||
this->auto_seed();
|
||||
}
|
||||
else
|
||||
{
|
||||
this->descriptor_.start(this->state_.get());
|
||||
}
|
||||
}
|
||||
|
||||
~prng()
|
||||
{
|
||||
this->descriptor_.done(this->state_.get());
|
||||
}
|
||||
|
||||
prng_state* get_state() const
|
||||
{
|
||||
this->descriptor_.ready(this->state_.get());
|
||||
return this->state_.get();
|
||||
}
|
||||
|
||||
int get_id() const
|
||||
{
|
||||
return this->id_;
|
||||
}
|
||||
|
||||
void add_entropy(const void* data, const size_t length) const
|
||||
{
|
||||
this->descriptor_.add_entropy(static_cast<const uint8_t*>(data), ul(length), this->state_.get());
|
||||
}
|
||||
|
||||
void read(void* data, const size_t length) const
|
||||
{
|
||||
this->descriptor_.read(static_cast<unsigned char*>(data), ul(length), this->get_state());
|
||||
}
|
||||
|
||||
private:
|
||||
int id_;
|
||||
std::unique_ptr<prng_state> state_;
|
||||
const ltc_prng_descriptor& descriptor_;
|
||||
|
||||
void auto_seed() const
|
||||
{
|
||||
rng_make_prng(128, this->id_, this->state_.get(), nullptr);
|
||||
|
||||
int i[4]; // uninitialized data
|
||||
auto* i_ptr = &i;
|
||||
this->add_entropy(reinterpret_cast<uint8_t*>(&i), sizeof(i));
|
||||
this->add_entropy(reinterpret_cast<uint8_t*>(&i_ptr), sizeof(i_ptr));
|
||||
|
||||
auto t = time(nullptr);
|
||||
this->add_entropy(reinterpret_cast<uint8_t*>(&t), sizeof(t));
|
||||
}
|
||||
};
|
||||
|
||||
const prng prng_(fortuna_desc);
|
||||
}
|
||||
|
||||
ecc::key::key()
|
||||
{
|
||||
ZeroMemory(&this->key_storage_, sizeof(this->key_storage_));
|
||||
}
|
||||
|
||||
ecc::key::~key()
|
||||
{
|
||||
this->free();
|
||||
}
|
||||
|
||||
ecc::key::key(key&& obj) noexcept
|
||||
: key()
|
||||
{
|
||||
this->operator=(std::move(obj));
|
||||
}
|
||||
|
||||
ecc::key::key(const key& obj)
|
||||
: key()
|
||||
{
|
||||
this->operator=(obj);
|
||||
}
|
||||
|
||||
ecc::key& ecc::key::operator=(key&& obj) noexcept
|
||||
{
|
||||
if (this != &obj)
|
||||
{
|
||||
std::memmove(&this->key_storage_, &obj.key_storage_, sizeof(this->key_storage_));
|
||||
ZeroMemory(&obj.key_storage_, sizeof(obj.key_storage_));
|
||||
}
|
||||
|
||||
return *this;
|
||||
}
|
||||
|
||||
ecc::key& ecc::key::operator=(const key& obj)
|
||||
{
|
||||
if (this != &obj && obj.is_valid())
|
||||
{
|
||||
this->deserialize(obj.serialize(obj.key_storage_.type));
|
||||
}
|
||||
|
||||
return *this;
|
||||
}
|
||||
|
||||
bool ecc::key::is_valid() const
|
||||
{
|
||||
return (!memory::is_set(&this->key_storage_, 0, sizeof(this->key_storage_)));
|
||||
}
|
||||
|
||||
ecc_key& ecc::key::get()
|
||||
{
|
||||
return this->key_storage_;
|
||||
}
|
||||
|
||||
const ecc_key& ecc::key::get() const
|
||||
{
|
||||
return this->key_storage_;
|
||||
}
|
||||
|
||||
std::string ecc::key::get_public_key() const
|
||||
{
|
||||
uint8_t buffer[512] = {0};
|
||||
unsigned long length = sizeof(buffer);
|
||||
|
||||
if (ecc_ansi_x963_export(&this->key_storage_, buffer, &length) == CRYPT_OK)
|
||||
{
|
||||
return std::string(cs(buffer), length);
|
||||
}
|
||||
|
||||
return {};
|
||||
}
|
||||
|
||||
void ecc::key::set(const std::string& pub_key_buffer)
|
||||
{
|
||||
this->free();
|
||||
|
||||
if (ecc_ansi_x963_import(cs(pub_key_buffer.data()),
|
||||
ul(pub_key_buffer.size()),
|
||||
&this->key_storage_) != CRYPT_OK)
|
||||
{
|
||||
ZeroMemory(&this->key_storage_, sizeof(this->key_storage_));
|
||||
}
|
||||
}
|
||||
|
||||
void ecc::key::deserialize(const std::string& key)
|
||||
{
|
||||
this->free();
|
||||
|
||||
if (ecc_import(cs(key.data()), ul(key.size()),
|
||||
&this->key_storage_) != CRYPT_OK
|
||||
)
|
||||
{
|
||||
ZeroMemory(&this->key_storage_, sizeof(this->key_storage_));
|
||||
}
|
||||
}
|
||||
|
||||
std::string ecc::key::serialize(const int type) const
|
||||
{
|
||||
uint8_t buffer[4096] = {0};
|
||||
unsigned long length = sizeof(buffer);
|
||||
|
||||
if (ecc_export(buffer, &length, type, &this->key_storage_) == CRYPT_OK)
|
||||
{
|
||||
return std::string(cs(buffer), length);
|
||||
}
|
||||
|
||||
return "";
|
||||
}
|
||||
|
||||
void ecc::key::free()
|
||||
{
|
||||
if (this->is_valid())
|
||||
{
|
||||
ecc_free(&this->key_storage_);
|
||||
}
|
||||
|
||||
ZeroMemory(&this->key_storage_, sizeof(this->key_storage_));
|
||||
}
|
||||
|
||||
bool ecc::key::operator==(key& key) const
|
||||
{
|
||||
return (this->is_valid() && key.is_valid() && this->serialize(PK_PUBLIC) == key.serialize(PK_PUBLIC));
|
||||
}
|
||||
|
||||
uint64_t ecc::key::get_hash() const
|
||||
{
|
||||
const auto hash = sha1::compute(this->get_public_key());
|
||||
if (hash.size() >= 8)
|
||||
{
|
||||
return *reinterpret_cast<const uint64_t*>(hash.data());
|
||||
}
|
||||
|
||||
return 0;
|
||||
}
|
||||
|
||||
ecc::key ecc::generate_key(const int bits)
|
||||
{
|
||||
key key;
|
||||
ecc_make_key(prng_.get_state(), prng_.get_id(), bits / 8, &key.get());
|
||||
|
||||
return key;
|
||||
}
|
||||
|
||||
ecc::key ecc::generate_key(const int bits, const std::string& entropy)
|
||||
{
|
||||
key key{};
|
||||
const prng yarrow(yarrow_desc, false);
|
||||
yarrow.add_entropy(entropy.data(), entropy.size());
|
||||
|
||||
ecc_make_key(yarrow.get_state(), yarrow.get_id(), bits / 8, &key.get());
|
||||
|
||||
return key;
|
||||
}
|
||||
|
||||
std::string ecc::sign_message(const key& key, const std::string& message)
|
||||
{
|
||||
if (!key.is_valid()) return "";
|
||||
|
||||
uint8_t buffer[512];
|
||||
unsigned long length = sizeof(buffer);
|
||||
|
||||
ecc_sign_hash(cs(message.data()), ul(message.size()), buffer, &length, prng_.get_state(), prng_.get_id(),
|
||||
&key.get());
|
||||
|
||||
return std::string(cs(buffer), length);
|
||||
}
|
||||
|
||||
bool ecc::verify_message(const key& key, const std::string& message, const std::string& signature)
|
||||
{
|
||||
if (!key.is_valid()) return false;
|
||||
|
||||
auto result = 0;
|
||||
return (ecc_verify_hash(cs(signature.data()),
|
||||
ul(signature.size()),
|
||||
cs(message.data()),
|
||||
ul(message.size()), &result,
|
||||
&key.get()) == CRYPT_OK && result != 0);
|
||||
}
|
||||
|
||||
bool ecc::encrypt(const key& key, std::string& data)
|
||||
{
|
||||
std::string out_data{};
|
||||
out_data.resize(std::max(ul(data.size() * 3), ul(0x100)));
|
||||
|
||||
auto out_len = ul(out_data.size());
|
||||
auto crypt = [&]()
|
||||
{
|
||||
return ecc_encrypt_key(cs(data.data()), ul(data.size()), cs(out_data.data()), &out_len,
|
||||
prng_.get_state(), prng_.get_id(), find_hash("sha512"), &key.get());
|
||||
};
|
||||
|
||||
auto res = crypt();
|
||||
|
||||
if (res == CRYPT_BUFFER_OVERFLOW)
|
||||
{
|
||||
out_data.resize(out_len);
|
||||
res = crypt();
|
||||
}
|
||||
|
||||
if (res != CRYPT_OK)
|
||||
{
|
||||
return false;
|
||||
}
|
||||
|
||||
out_data.resize(out_len);
|
||||
data = std::move(out_data);
|
||||
return true;
|
||||
}
|
||||
|
||||
bool ecc::decrypt(const key& key, std::string& data)
|
||||
{
|
||||
std::string out_data{};
|
||||
out_data.resize(std::max(ul(data.size() * 3), ul(0x100)));
|
||||
|
||||
auto out_len = ul(out_data.size());
|
||||
auto crypt = [&]()
|
||||
{
|
||||
return ecc_decrypt_key(cs(data.data()), ul(data.size()), cs(out_data.data()), &out_len, &key.get());
|
||||
};
|
||||
|
||||
auto res = crypt();
|
||||
|
||||
if (res == CRYPT_BUFFER_OVERFLOW)
|
||||
{
|
||||
out_data.resize(out_len);
|
||||
res = crypt();
|
||||
}
|
||||
|
||||
if (res != CRYPT_OK)
|
||||
{
|
||||
return false;
|
||||
}
|
||||
|
||||
out_data.resize(out_len);
|
||||
data = std::move(out_data);
|
||||
return true;
|
||||
}
|
||||
|
||||
std::string rsa::encrypt(const std::string& data, const std::string& hash, const std::string& key)
|
||||
{
|
||||
rsa_key new_key;
|
||||
rsa_import(cs(key.data()), ul(key.size()), &new_key);
|
||||
const auto _ = gsl::finally([&]()
|
||||
{
|
||||
rsa_free(&new_key);
|
||||
});
|
||||
|
||||
|
||||
std::string out_data{};
|
||||
out_data.resize(std::max(ul(data.size() * 3), ul(0x100)));
|
||||
|
||||
auto out_len = ul(out_data.size());
|
||||
auto crypt = [&]()
|
||||
{
|
||||
return rsa_encrypt_key_ex(cs(data.data()), ul(data.size()), cs(out_data.data()), &out_len, cs(hash.data()),
|
||||
ul(hash.size()), prng_.get_state(), prng_.get_id(), find_hash("sha512"), LTC_PKCS_1_V1_5, &new_key);
|
||||
};
|
||||
|
||||
auto res = crypt();
|
||||
|
||||
if (res == CRYPT_BUFFER_OVERFLOW)
|
||||
{
|
||||
out_data.resize(out_len);
|
||||
res = crypt();
|
||||
}
|
||||
|
||||
if (res == CRYPT_OK)
|
||||
{
|
||||
out_data.resize(out_len);
|
||||
return out_data;
|
||||
}
|
||||
|
||||
return {};
|
||||
}
|
||||
|
||||
std::string des3::encrypt(const std::string& data, const std::string& iv, const std::string& key)
|
||||
{
|
||||
std::string enc_data;
|
||||
enc_data.resize(data.size());
|
||||
|
||||
symmetric_CBC cbc;
|
||||
const auto des3 = find_cipher("3des");
|
||||
|
||||
cbc_start(des3, cs(iv.data()), cs(key.data()), static_cast<int>(key.size()), 0, &cbc);
|
||||
cbc_encrypt(cs(data.data()), cs(enc_data.data()), ul(data.size()), &cbc);
|
||||
cbc_done(&cbc);
|
||||
|
||||
return enc_data;
|
||||
}
|
||||
|
||||
std::string des3::decrypt(const std::string& data, const std::string& iv, const std::string& key)
|
||||
{
|
||||
std::string dec_data;
|
||||
dec_data.resize(data.size());
|
||||
|
||||
symmetric_CBC cbc;
|
||||
const auto des3 = find_cipher("3des");
|
||||
|
||||
cbc_start(des3, cs(iv.data()), cs(key.data()), static_cast<int>(key.size()), 0, &cbc);
|
||||
cbc_decrypt(cs(data.data()), cs(dec_data.data()), ul(data.size()), &cbc);
|
||||
cbc_done(&cbc);
|
||||
|
||||
return dec_data;
|
||||
}
|
||||
|
||||
std::string tiger::compute(const std::string& data, const bool hex)
|
||||
{
|
||||
return compute(cs(data.data()), data.size(), hex);
|
||||
}
|
||||
|
||||
std::string tiger::compute(const uint8_t* data, const size_t length, const bool hex)
|
||||
{
|
||||
uint8_t buffer[24] = {0};
|
||||
|
||||
hash_state state;
|
||||
tiger_init(&state);
|
||||
tiger_process(&state, data, ul(length));
|
||||
tiger_done(&state, buffer);
|
||||
|
||||
std::string hash(cs(buffer), sizeof(buffer));
|
||||
if (!hex) return hash;
|
||||
|
||||
return string::dump_hex(hash, "");
|
||||
}
|
||||
|
||||
std::string aes::encrypt(const std::string& data, const std::string& iv, const std::string& key)
|
||||
{
|
||||
std::string aligned_data = data;
|
||||
const auto mod = aligned_data.size() % 16;
|
||||
if (mod != 0)
|
||||
{
|
||||
aligned_data.resize(aligned_data.size() + (16 - mod));
|
||||
}
|
||||
|
||||
std::string enc_data;
|
||||
enc_data.resize(aligned_data.size());
|
||||
|
||||
symmetric_CBC cbc;
|
||||
const auto aes = find_cipher("aes");
|
||||
|
||||
cbc_start(aes, cs(iv.data()), cs(key.data()),
|
||||
static_cast<int>(key.size()), 0, &cbc);
|
||||
cbc_encrypt(cs(aligned_data.data()),
|
||||
cs(enc_data.data()),
|
||||
ul(aligned_data.size()), &cbc);
|
||||
cbc_done(&cbc);
|
||||
|
||||
return enc_data;
|
||||
}
|
||||
|
||||
std::string aes::decrypt(const std::string& data, const std::string& iv, const std::string& key)
|
||||
{
|
||||
std::string dec_data;
|
||||
dec_data.resize(data.size());
|
||||
|
||||
symmetric_CBC cbc;
|
||||
const auto aes = find_cipher("aes");
|
||||
|
||||
cbc_start(aes, cs(iv.data()), cs(key.data()),
|
||||
static_cast<int>(key.size()), 0, &cbc);
|
||||
cbc_decrypt(cs(data.data()),
|
||||
cs(dec_data.data()),
|
||||
ul(data.size()), &cbc);
|
||||
cbc_done(&cbc);
|
||||
|
||||
return dec_data;
|
||||
}
|
||||
|
||||
std::string hmac_sha1::compute(const std::string& data, const std::string& key)
|
||||
{
|
||||
std::string buffer;
|
||||
buffer.resize(20);
|
||||
|
||||
hmac_state state;
|
||||
hmac_init(&state, find_hash("sha1"), cs(key.data()), ul(key.size()));
|
||||
hmac_process(&state, cs(data.data()), static_cast<int>(data.size()));
|
||||
|
||||
auto out_len = ul(buffer.size());
|
||||
hmac_done(&state, cs(buffer.data()), &out_len);
|
||||
|
||||
buffer.resize(out_len);
|
||||
return buffer;
|
||||
}
|
||||
|
||||
std::string sha1::compute(const std::string& data, const bool hex)
|
||||
{
|
||||
return compute(cs(data.data()), data.size(), hex);
|
||||
}
|
||||
|
||||
std::string sha1::compute(const uint8_t* data, const size_t length, const bool hex)
|
||||
{
|
||||
uint8_t buffer[20] = {0};
|
||||
|
||||
hash_state state;
|
||||
sha1_init(&state);
|
||||
sha1_process(&state, data, ul(length));
|
||||
sha1_done(&state, buffer);
|
||||
|
||||
std::string hash(cs(buffer), sizeof(buffer));
|
||||
if (!hex) return hash;
|
||||
|
||||
return string::dump_hex(hash, "");
|
||||
}
|
||||
|
||||
std::string sha256::compute(const std::string& data, const bool hex)
|
||||
{
|
||||
return compute(cs(data.data()), data.size(), hex);
|
||||
}
|
||||
|
||||
std::string sha256::compute(const uint8_t* data, const size_t length, const bool hex)
|
||||
{
|
||||
uint8_t buffer[32] = {0};
|
||||
|
||||
hash_state state;
|
||||
sha256_init(&state);
|
||||
sha256_process(&state, data, ul(length));
|
||||
sha256_done(&state, buffer);
|
||||
|
||||
std::string hash(cs(buffer), sizeof(buffer));
|
||||
if (!hex) return hash;
|
||||
|
||||
return string::dump_hex(hash, "");
|
||||
}
|
||||
|
||||
std::string sha512::compute(const std::string& data, const bool hex)
|
||||
{
|
||||
return compute(cs(data.data()), data.size(), hex);
|
||||
}
|
||||
|
||||
std::string sha512::compute(const uint8_t* data, const size_t length, const bool hex)
|
||||
{
|
||||
uint8_t buffer[64] = {0};
|
||||
|
||||
hash_state state;
|
||||
sha512_init(&state);
|
||||
sha512_process(&state, data, ul(length));
|
||||
sha512_done(&state, buffer);
|
||||
|
||||
std::string hash(cs(buffer), sizeof(buffer));
|
||||
if (!hex) return hash;
|
||||
|
||||
return string::dump_hex(hash, "");
|
||||
}
|
||||
|
||||
namespace md5
|
||||
{
|
||||
std::string compute(const std::string& data, bool hex)
|
||||
{
|
||||
return compute(cs(data.data()), data.size(), hex);
|
||||
}
|
||||
|
||||
std::string compute(const uint8_t* data, size_t length, bool hex)
|
||||
{
|
||||
uint8_t buffer[16] = {0};
|
||||
|
||||
hash_state state;
|
||||
md5_init(&state);
|
||||
md5_process(&state, data, ul(length));
|
||||
md5_done(&state, buffer);
|
||||
|
||||
std::string hash(cs(buffer), sizeof(buffer));
|
||||
if (!hex) return hash;
|
||||
|
||||
return string::dump_hex(hash, "");
|
||||
}
|
||||
}
|
||||
|
||||
std::string base64::encode(const uint8_t* data, const size_t len)
|
||||
{
|
||||
std::string result;
|
||||
result.resize((len + 2) * 2);
|
||||
|
||||
auto out_len = ul(result.size());
|
||||
if (base64_encode(data, ul(len), result.data(), &out_len) != CRYPT_OK)
|
||||
{
|
||||
return {};
|
||||
}
|
||||
|
||||
result.resize(out_len);
|
||||
return result;
|
||||
}
|
||||
|
||||
std::string base64::encode(const std::string& data)
|
||||
{
|
||||
return base64::encode(cs(data.data()), static_cast<unsigned>(data.size()));
|
||||
}
|
||||
|
||||
std::string base64::decode(const std::string& data)
|
||||
{
|
||||
std::string result;
|
||||
result.resize((data.size() + 2) * 2);
|
||||
|
||||
auto out_len = ul(result.size());
|
||||
if (base64_decode(data.data(), ul(data.size()), cs(result.data()), &out_len) != CRYPT_OK)
|
||||
{
|
||||
return {};
|
||||
}
|
||||
|
||||
result.resize(out_len);
|
||||
return result;
|
||||
}
|
||||
|
||||
unsigned int jenkins_one_at_a_time::compute(const std::string& data)
|
||||
{
|
||||
return compute(data.data(), data.size());
|
||||
}
|
||||
|
||||
unsigned int jenkins_one_at_a_time::compute(const char* key, const size_t len)
|
||||
{
|
||||
unsigned int hash, i;
|
||||
for (hash = i = 0; i < len; ++i)
|
||||
{
|
||||
hash += key[i];
|
||||
hash += (hash << 10);
|
||||
hash ^= (hash >> 6);
|
||||
}
|
||||
hash += (hash << 3);
|
||||
hash ^= (hash >> 11);
|
||||
hash += (hash << 15);
|
||||
return hash;
|
||||
}
|
||||
|
||||
uint32_t random::get_integer()
|
||||
{
|
||||
uint32_t result;
|
||||
random::get_data(&result, sizeof(result));
|
||||
return result;
|
||||
}
|
||||
|
||||
uint32_t random::get_integer(const std::uint32_t min, const std::uint32_t max)
|
||||
{
|
||||
const auto range = max - min + 1;
|
||||
const auto value = random::get_integer();
|
||||
return value % range + min;
|
||||
}
|
||||
|
||||
std::string random::get_challenge()
|
||||
{
|
||||
std::string result;
|
||||
result.resize(sizeof(uint32_t));
|
||||
random::get_data(result.data(), result.size());
|
||||
return string::dump_hex(result, "");
|
||||
}
|
||||
|
||||
void random::get_data(void* data, const size_t size)
|
||||
{
|
||||
prng_.read(data, size);
|
||||
}
|
||||
|
||||
std::string random::get_data(const size_t size)
|
||||
{
|
||||
std::string data;
|
||||
data.resize(size);
|
||||
random::get_data(data.data(), size);
|
||||
return data;
|
||||
}
|
||||
}
|
||||
|
||||
#pragma warning(pop)
|
||||
@@ -0,0 +1,151 @@
|
||||
#pragma once
|
||||
|
||||
#include <string>
|
||||
#include <tomcrypt.h>
|
||||
#include "string.hpp"
|
||||
|
||||
namespace utils::cryptography
|
||||
{
|
||||
namespace ecc
|
||||
{
|
||||
class key final
|
||||
{
|
||||
public:
|
||||
key();
|
||||
~key();
|
||||
|
||||
key(key&& obj) noexcept;
|
||||
key(const key& obj);
|
||||
key& operator=(key&& obj) noexcept;
|
||||
key& operator=(const key& obj);
|
||||
|
||||
bool is_valid() const;
|
||||
|
||||
ecc_key& get();
|
||||
const ecc_key& get() const;
|
||||
|
||||
std::string get_public_key() const;
|
||||
|
||||
void set(const std::string& pub_key_buffer);
|
||||
|
||||
void deserialize(const std::string& key);
|
||||
|
||||
std::string serialize(int type = PK_PRIVATE) const;
|
||||
|
||||
void free();
|
||||
|
||||
bool operator==(key& key) const;
|
||||
|
||||
uint64_t get_hash() const;
|
||||
|
||||
private:
|
||||
ecc_key key_storage_{};
|
||||
};
|
||||
|
||||
key generate_key(int bits);
|
||||
key generate_key(int bits, const std::string& entropy);
|
||||
std::string sign_message(const key& key, const std::string& message);
|
||||
bool verify_message(const key& key, const std::string& message, const std::string& signature);
|
||||
|
||||
bool encrypt(const key& key, std::string& data);
|
||||
bool decrypt(const key& key, std::string& data);
|
||||
}
|
||||
|
||||
namespace rsa
|
||||
{
|
||||
std::string encrypt(const std::string& data, const std::string& hash, const std::string& key);
|
||||
std::string decrypt(const std::string& data, const std::string& hash, const std::string& key);
|
||||
}
|
||||
|
||||
namespace des3
|
||||
{
|
||||
std::string encrypt(const std::string& data, const std::string& iv, const std::string& key);
|
||||
std::string decrypt(const std::string& data, const std::string& iv, const std::string& key);
|
||||
}
|
||||
|
||||
namespace tiger
|
||||
{
|
||||
std::string compute(const std::string& data, bool hex = false);
|
||||
std::string compute(const uint8_t* data, size_t length, bool hex = false);
|
||||
}
|
||||
|
||||
namespace aes
|
||||
{
|
||||
std::string encrypt(const std::string& data, const std::string& iv, const std::string& key);
|
||||
std::string decrypt(const std::string& data, const std::string& iv, const std::string& key);
|
||||
}
|
||||
|
||||
namespace hmac_sha1
|
||||
{
|
||||
std::string compute(const std::string& data, const std::string& key);
|
||||
}
|
||||
|
||||
namespace sha1
|
||||
{
|
||||
std::string compute(const std::string& data, bool hex = false);
|
||||
std::string compute(const uint8_t* data, size_t length, bool hex = false);
|
||||
}
|
||||
|
||||
namespace sha256
|
||||
{
|
||||
std::string compute(const std::string& data, bool hex = false);
|
||||
std::string compute(const uint8_t* data, size_t length, bool hex = false);
|
||||
}
|
||||
|
||||
namespace sha512
|
||||
{
|
||||
std::string compute(const std::string& data, bool hex = false);
|
||||
std::string compute(const uint8_t* data, size_t length, bool hex = false);
|
||||
}
|
||||
|
||||
namespace md5
|
||||
{
|
||||
std::string compute(const std::string& data, bool hex = false);
|
||||
std::string compute(const uint8_t* data, size_t length, bool hex = false);
|
||||
}
|
||||
|
||||
namespace argon2
|
||||
{
|
||||
template <size_t HashLen = 32, size_t SaltLen = 16, std::uint32_t TCost = 2,
|
||||
std::uint32_t MCost = 1 << 6, std::uint32_t Threads = 1>
|
||||
std::string compute(const std::string& data, bool hex = false)
|
||||
{
|
||||
std::uint8_t buffer[HashLen]{};
|
||||
std::uint8_t salt[SaltLen]{};
|
||||
|
||||
argon2i_hash_raw(TCost, MCost, Threads, data.data(), data.size(), salt, SaltLen, buffer, HashLen);
|
||||
|
||||
const auto str = std::string{reinterpret_cast<char*>(buffer), HashLen};
|
||||
if (hex)
|
||||
{
|
||||
return string::dump_hex(str, "", false);
|
||||
}
|
||||
else
|
||||
{
|
||||
return str;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
namespace base64
|
||||
{
|
||||
std::string encode(const uint8_t* data, size_t len);
|
||||
std::string encode(const std::string& data);
|
||||
std::string decode(const std::string& data);
|
||||
}
|
||||
|
||||
namespace jenkins_one_at_a_time
|
||||
{
|
||||
unsigned int compute(const std::string& data);
|
||||
unsigned int compute(const char* key, size_t len);
|
||||
};
|
||||
|
||||
namespace random
|
||||
{
|
||||
uint32_t get_integer();
|
||||
uint32_t get_integer(const std::uint32_t min, const std::uint32_t max);
|
||||
std::string get_challenge();
|
||||
void get_data(void* data, size_t size);
|
||||
std::string get_data(const size_t size);
|
||||
}
|
||||
}
|
||||
+14
-40
@@ -9,36 +9,9 @@ namespace utils::http
|
||||
{
|
||||
namespace
|
||||
{
|
||||
struct progress_helper
|
||||
{
|
||||
const std::function<void(size_t)>* callback{};
|
||||
std::exception_ptr exception{};
|
||||
};
|
||||
|
||||
int progress_callback(void* clientp, const curl_off_t /*dltotal*/, const curl_off_t dlnow, const curl_off_t /*ultotal*/, const curl_off_t /*ulnow*/)
|
||||
{
|
||||
auto* helper = static_cast<progress_helper*>(clientp);
|
||||
|
||||
try
|
||||
{
|
||||
if (*helper->callback)
|
||||
{
|
||||
(*helper->callback)(static_cast<size_t>(dlnow));
|
||||
}
|
||||
}
|
||||
catch (...)
|
||||
{
|
||||
helper->exception = std::current_exception();
|
||||
return -1;
|
||||
}
|
||||
|
||||
return 0;
|
||||
}
|
||||
|
||||
size_t write_callback(void* contents, const size_t size, const size_t nmemb, void* userp)
|
||||
{
|
||||
auto* buffer = static_cast<std::string*>(userp);
|
||||
|
||||
const auto buffer = static_cast<std::string*>(userp);
|
||||
const auto total_size = size * nmemb;
|
||||
buffer->append(static_cast<char*>(contents), total_size);
|
||||
return total_size;
|
||||
@@ -46,10 +19,10 @@ namespace utils::http
|
||||
}
|
||||
|
||||
std::optional<result> get_data(const std::string& url, const std::string& fields,
|
||||
const headers& headers, const std::function<void(size_t)>& callback)
|
||||
const headers& headers, const std::string& method)
|
||||
{
|
||||
curl_slist* header_list = nullptr;
|
||||
auto* curl = curl_easy_init();
|
||||
const auto curl = curl_easy_init();
|
||||
if (!curl)
|
||||
{
|
||||
return {};
|
||||
@@ -68,22 +41,25 @@ namespace utils::http
|
||||
}
|
||||
|
||||
std::string buffer{};
|
||||
progress_helper helper{};
|
||||
helper.callback = &callback;
|
||||
|
||||
curl_easy_setopt(curl, CURLOPT_HTTPHEADER, header_list);
|
||||
curl_easy_setopt(curl, CURLOPT_URL, url.data());
|
||||
curl_easy_setopt(curl, CURLOPT_WRITEFUNCTION, write_callback);
|
||||
curl_easy_setopt(curl, CURLOPT_WRITEDATA, &buffer);
|
||||
curl_easy_setopt(curl, CURLOPT_XFERINFOFUNCTION, progress_callback);
|
||||
curl_easy_setopt(curl, CURLOPT_XFERINFODATA, &helper);
|
||||
curl_easy_setopt(curl, CURLOPT_NOPROGRESS, 0);
|
||||
|
||||
if (!fields.empty())
|
||||
{
|
||||
curl_easy_setopt(curl, CURLOPT_POSTFIELDS, fields.data());
|
||||
curl_easy_setopt(curl, CURLOPT_POSTFIELDSIZE, fields.size());
|
||||
}
|
||||
|
||||
if (!method.empty())
|
||||
{
|
||||
curl_easy_setopt(curl, CURLOPT_CUSTOMREQUEST, method.data());
|
||||
}
|
||||
|
||||
curl_easy_setopt(curl, CURLOPT_WRITEFUNCTION, write_callback);
|
||||
curl_easy_setopt(curl, CURLOPT_WRITEDATA, &buffer);
|
||||
curl_easy_setopt(curl, CURLOPT_NOPROGRESS, 1);
|
||||
|
||||
const auto code = curl_easy_perform(curl);
|
||||
|
||||
if (code == CURLE_OK)
|
||||
@@ -91,15 +67,13 @@ namespace utils::http
|
||||
result result;
|
||||
result.code = code;
|
||||
result.buffer = std::move(buffer);
|
||||
|
||||
return result;
|
||||
}
|
||||
else
|
||||
{
|
||||
result result;
|
||||
result.code = code;
|
||||
|
||||
return result;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
+2
-3
@@ -2,7 +2,6 @@
|
||||
|
||||
#include <string>
|
||||
#include <optional>
|
||||
#include <future>
|
||||
|
||||
#include <curl/curl.h>
|
||||
|
||||
@@ -17,5 +16,5 @@ namespace utils::http
|
||||
using headers = std::unordered_map<std::string, std::string>;
|
||||
|
||||
std::optional<result> get_data(const std::string& url, const std::string& fields = {},
|
||||
const headers& headers = {}, const std::function<void(size_t)>& callback = {});
|
||||
}
|
||||
const headers& headers = {}, const std::string& method = {});
|
||||
}
|
||||
|
||||
@@ -0,0 +1,96 @@
|
||||
#include <stdinc.hpp>
|
||||
|
||||
#include "thread_pool.hpp"
|
||||
|
||||
namespace utils
|
||||
{
|
||||
thread_pool::worker::worker()
|
||||
{
|
||||
}
|
||||
|
||||
void thread_pool::worker::start(thread_pool& pool)
|
||||
{
|
||||
this->thread_ = std::thread([&]
|
||||
{
|
||||
this->loop(pool);
|
||||
});
|
||||
}
|
||||
|
||||
void thread_pool::worker::stop()
|
||||
{
|
||||
if (this->thread_.joinable())
|
||||
{
|
||||
this->thread_.join();
|
||||
}
|
||||
}
|
||||
|
||||
void thread_pool::worker::loop(thread_pool& pool)
|
||||
{
|
||||
while (!pool.stopped_)
|
||||
{
|
||||
pool.run_job();
|
||||
}
|
||||
}
|
||||
|
||||
thread_pool::job_ptr thread_pool::pop_job()
|
||||
{
|
||||
thread_pool::job_ptr job = std::move(this->queue_.front());
|
||||
this->queue_.pop_front();
|
||||
return job;
|
||||
}
|
||||
|
||||
void thread_pool::run_job()
|
||||
{
|
||||
std::unique_lock<std::mutex> lock(this->mutex_);
|
||||
|
||||
this->event_.wait(lock, [&]()
|
||||
{
|
||||
return !this->queue_.empty() || this->stopped_;
|
||||
});
|
||||
|
||||
if (this->stopped_ || this->queue_.empty())
|
||||
{
|
||||
return;
|
||||
}
|
||||
|
||||
auto job = this->pop_job();
|
||||
lock.unlock();
|
||||
job->operator()();
|
||||
}
|
||||
|
||||
thread_pool::thread_pool(const std::size_t num_workers)
|
||||
{
|
||||
this->initialize(num_workers);
|
||||
}
|
||||
|
||||
void thread_pool::initialize(const std::size_t num_workers)
|
||||
{
|
||||
for (auto i = 0u; i < num_workers; i++)
|
||||
{
|
||||
this->workers_.emplace_back(std::make_unique<thread_pool::worker>());
|
||||
}
|
||||
}
|
||||
|
||||
void thread_pool::start()
|
||||
{
|
||||
for (auto& worker : this->workers_)
|
||||
{
|
||||
worker->start(*this);
|
||||
}
|
||||
}
|
||||
|
||||
void thread_pool::update()
|
||||
{
|
||||
|
||||
}
|
||||
|
||||
void thread_pool::stop()
|
||||
{
|
||||
this->stopped_ = true;
|
||||
this->event_.notify_all();
|
||||
for (auto& worker : this->workers_)
|
||||
{
|
||||
worker->stop();
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,60 @@
|
||||
#pragma once
|
||||
|
||||
namespace utils
|
||||
{
|
||||
class thread_pool
|
||||
{
|
||||
public:
|
||||
using job = std::function<void()>;
|
||||
using job_ptr = std::unique_ptr<job>;
|
||||
|
||||
friend class worker;
|
||||
class worker
|
||||
{
|
||||
public:
|
||||
worker();
|
||||
|
||||
friend class thread_pool;
|
||||
|
||||
void start(thread_pool& pool);
|
||||
void stop();
|
||||
void loop(thread_pool& pool);
|
||||
|
||||
private:
|
||||
std::thread thread_;
|
||||
|
||||
};
|
||||
|
||||
thread_pool() = default;
|
||||
thread_pool(const std::size_t num_workers);
|
||||
void initialize(const std::size_t num_workers);
|
||||
|
||||
void start();
|
||||
void update();
|
||||
void stop();
|
||||
|
||||
template <typename F>
|
||||
void push(F&& job)
|
||||
{
|
||||
if (this->stopped_)
|
||||
{
|
||||
return;
|
||||
}
|
||||
|
||||
std::lock_guard lock(this->mutex_);
|
||||
this->queue_.emplace_back(std::make_unique<thread_pool::job>(std::forward<F>(job)));
|
||||
this->event_.notify_one();
|
||||
}
|
||||
|
||||
private:
|
||||
thread_pool::job_ptr pop_job();
|
||||
void run_job();
|
||||
|
||||
std::mutex mutex_;
|
||||
std::atomic_bool stopped_;
|
||||
std::deque<job_ptr> queue_;
|
||||
std::condition_variable event_;
|
||||
std::deque<std::unique_ptr<worker>> workers_;
|
||||
|
||||
};
|
||||
}
|
||||
Reference in New Issue
Block a user